简体   繁体   English

在Raspberry Pi上模拟Gpio输入以进行测试

[英]Emulate Gpio input on Raspberry Pi for testing

I have a python script running on my RPi. 我在我的RPi上运行了一个python脚本。 It uses the Gpiozero library (that's really great by the way). 它使用Gpiozero库(顺便说一句,它非常棒)。

For testing purposes i was wondering if it was possible to emulate GPIO states somehow (say emulate pressing a button) and have it picked up by the gpiozero library. 出于测试目的,我想知道是否有可能以某种方式模拟GPIO状态(比如模拟按下按钮)并通过gpiozero库获取它。

Thanks ! 谢谢 !

TLDNR: Yes, it is possible. TLDNR:是的,这是可能的。

I am not aware of any already prepared solution that can help you achieve what you want to do. 我不知道任何已经准备好的解决方案可以帮助您实现您想要做的事情。 Hence I found it very interesting whether it is feasible at all. 因此,我觉得它是否可行是非常有趣的。

I was looking a while for a seam which can be used to stub the GPIO features and I have found that gpiozero uses GPIOZERO_PIN_FACTORY environmental variable to pick a backend. 我正在寻找可用于存根GPIO功能的接缝,我发现gpiozero 使用 GPIOZERO_PIN_FACTORY环境变量来选择后端。 The plan is to write own pin factory, that will provide possibility to test other scripts. 计划是编写自己的pin工厂,这将提供测试其他脚本的可能性。

NOTE: Please treat my solution as a proof of concept. 注意:请将我的解决方案视为概念证明。 It is far from being production ready. 它还远未准备好生产。

The idea is to get GPIO states out of script under test scope. 这个想法是让GPIO状态超出测试范围内的脚本。 My solution uses env variable RPI_STUB_URL to get path of unix socket which will be used to communicate with the stub controller . 我的解决方案使用env变量RPI_STUB_URL来获取将用于与stub controller通信的unix套接字的路径。

I have introduced very simple one request/response per connection protocol: 我已经介绍了每个连接协议非常简单的一个请求/响应:

  • "GF {pin}\\n" - ask what is the current function of the pin . “GF {pin} \\ n” - 询问pin的当前功能是什么。 Stub does not validate the response, but I would expect "input", "output" to be used. Stub不验证响应,但我希望使用“输入”,“输出”。
  • "SF {pin} {function}\\n" - request change of the pin's current function. “SF {pin} {function} \\ n” - 请求更改引脚的当前功能。 Stub does not validate the function, bu I would expect "input", "output" to be used. Stub不验证函数,我希望使用“输入”,“输出”。 Stub expects "OK" as a response. Stub希望“OK”作为回应。
  • "GS {pin}\\n" - ask what is the current state of the pin . “GS {pin} \\ n” - 询问pin的当前状态。 Stub expects values "0" or "1" as a response. Stub期望值“0”或“1”作为响应。
  • "SS {pin} {value|]n" - request change of the pin's current state. “SS {pin} {value |] n” - 请求更改引脚的当前状态。 Stub expects "OK" as a response. Stub希望“OK”作为回应。

My "stub package" contains following files: 我的“存根包”包含以下文件:

- setup.py # This file is needed in every package, isn't it?
- rpi_stub/ 
   - __init__.py # This file collects entry points
   - stubPin.py # This file implements stub backend for gpiozero
   - controller.py # This file implements server for my stub
   - trigger.py # This file implements client side feature of my stub

Let's start with setup.py content: 让我们从setup.py内容开始:

from setuptools import setup, find_packages

setup(
    name="Raspberry PI GPIO stub",
    version="0.1",
    description="Package with stub plugin for gpiozero library",
    packages=find_packages(),
        install_requires = ["gpiozero"],
    include_package_data=True,
    entry_points="""
[console_scripts]
stub_rpi_controller=rpi_stub:controller_main
stub_rpi_trigger=rpi_stub:trigger_main
[gpiozero_pin_factories]
stub_rpi=rpi_stub:make_stub_pin
"""
)

It defines two console_scripts entry points one for controller and one for trigger. 它定义了两个console_scripts入口点,一个用于控制器,另一个用于触发器。 And one pin factory for gpiozero. 和gpiozero的一个针工厂。

Now rpi_stub/__init__.py : 现在rpi_stub/__init__.py

import rpi_stub.stubPin 
from rpi_stub.controller import controller_main
from rpi_stub.trigger import trigger_main

def make_stub_pin(number):
    return stubPin.StubPin(number)

It is rather simple file. 这是一个相当简单的文件。

File rpi_stub/trigger.py : 文件rpi_stub/trigger.py

import socket
import sys

def trigger_main():
   socket_addr = sys.argv[1]
   sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
   sock.connect(socket_addr)
   request = "{0}\n".format(" ".join(sys.argv[2:]))
   sock.sendall(request.encode())
   data = sock.recv(1024)
   sock.close()
   print(data.decode("utf-8"))

trigger allows you to make your own request. trigger允许您发出自己的请求。 You can use it to check what is the state of GPIO pin or change it. 您可以使用它来检查GPIO引脚的状态或更改它。

File rpi_stub/controller.py : 文件rpi_stub/controller.py

import socketserver
import sys

functions = {}
states = {}

class MyHandler(socketserver.StreamRequestHandler):

    def _respond(self, response):
        print("Sending response: {0}".format(response))
        self.wfile.write(response.encode())

    def _handle_get_function(self, data):
        print("Handling get_function: {0}".format(data))
        try:
          self._respond("{0}".format(functions[data[0]]))
        except KeyError:
          self._respond("input")

    def _handle_set_function(self, data):
        print("Handling set_function: {0}".format(data))
        functions[data[0]] = data[1]
        self._respond("OK")

    def _handle_get_state(self, data):
        print("Handling get_state: {0}".format(data))
        try:
          self._respond("{0}".format(states[data[0]]))
        except KeyError:
          self._respond("0")

    def _handle_set_state(self, data):
        print("Handling set_state: {0}".format(data))
        states[data[0]] = data[1]
        self._respond("OK")

    def handle(self):
        data = self.rfile.readline()
        print("Handle: {0}".format(data))
        data = data.decode("utf-8").strip().split(" ")

        if data[0] == "GF":
            self._handle_get_function(data[1:])
        elif data[0] == "SF":
            self._handle_set_function(data[1:])
        elif data[0] == "GS":
            self._handle_get_state(data[1:])
        elif data[0] == "SS":
            self._handle_set_state(data[1:])
        else:
            self._respond("Not understood")

def controller_main():
    socket_addr = sys.argv[1]
    server = socketserver.UnixStreamServer(socket_addr, MyHandler)
    server.serve_forever()

This file contains the simplest server I was able to write. 该文件包含我能够编写的最简单的服务器。

And the most complicated file rpi_stub/stubPin.py : 最复杂的文件rpi_stub/stubPin.py

from gpiozero.pins import Pin
import os
import socket
from threading import Thread
from time import sleep

def dummy_func():
   pass

def edge_detector(pin):
   print("STUB: Edge detector for pin: {0} spawned".format(pin.number))
   while pin._edges != "none":
      new_state = pin._get_state()
      print("STUB: Edge detector for pin {0}: value {1} received".format(pin.number, new_state))
      if new_state != pin._last_known:
          print("STUB: Edge detector for pin {0}: calling callback".format(pin.number))
          pin._when_changed()
      pin._last_known = new_state 
      sleep(1)
   print("STUB: Edge detector for pin: {0} ends".format(pin.number))


class StubPin(Pin):

    def __init__(self, number):
        super(StubPin, self).__init__()
        self.number = number
        self._when_changed = dummy_func
        self._edges = "none"
        self._last_known = 0

    def _make_request(self, request):
        server_address = os.getenv("RPI_STUB_URL", None)
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.connect(server_address)
        sock.sendall(request.encode())
        data = sock.recv(1024)
        sock.close()
        return data.decode("utf-8")

    def _get_function(self):
        response = self._make_request("GF {pin}\n".format(pin=self.number))
        return response;

    def _set_function(self, function):
        response = self._make_request("SF {pin} {function}\n".format(pin=self.number, function=function))
        if response != "OK":
          raise Exception("STUB Not understood", response)

    def _get_state(self):
        response = self._make_request("GS {pin}\n".format(pin=self.number))
        if response == "1":
           return 1
        else:
           return 0

    def _set_pull(self, value):
         pass

    def _set_edges(self, value):
         print("STUB: set edges called: {0}".format(value))
         if self._edges == "none" and value != "none":
             self._thread = Thread(target=edge_detector,args=(self,))
             self._thread.start()
         if self._edges != "none" and value == "none":
             self._edges = value;
             self._thread.join()
         self._edges = value
         pass

    def _get_when_changed(self, value):
         return self._when_changed

    def _set_when_changed(self, value):
         print("STUB: set when changed: {0}".format(value))
         self._when_changed = value

    def _set_state(self, value):
        response = self._make_request("SS {pin} {value}\n".format(pin=self.number, value=value))
        if response != "OK":
          raise Exception("Not understood", response)

The file defines StubPin which extends Pin from gpiozero . 该文件定义了StubPin ,它从gpiozero扩展了Pin It defines all functions that was mandatory to be overriden. 它定义了必须覆盖的所有函数。 It also contains very naive edge detection as it was needed for gpio.Button to work. 它还包含非常天真的边缘检测,因为gpio.Button需要它才能工作。

Let's make a demo :). 我们来做一个演示:)。 Let's create virtualenv which gpiozero and my package installed: 让我们创建virtualenv,其中安装了gpiozero和我的包:

$ virtualenv -p python3 rpi_stub_env
[...] // virtualenv successfully created
$ source ./rpi_stub_env/bin/activate
(rpi_stub_env)$ pip install gpiozero
[...] // gpiozero installed
(rpi_stub_env)$ python3 setup.py install
[...] // my package installed

Now let's create stub controller (open in other terminal etc.): 现在让我们创建存根控制器(在其他终端等处打开):

(rpi_stub_env)$ stub_rpi_controller /tmp/socket.sock

I will use the following script example.py : 我将使用以下脚本example.py

from gpiozero import Button                                                                                         
from time import sleep                                                                                              

button = Button(2)

while True:
    if button.is_pressed:
       print("Button is pressed")
    else:
       print("Button is not pressed")
    sleep(1)

Let's execute it: (rpi_stub_env)$ RPI_STUB_URL=/tmp/socket.sock GPIOZERO_PIN_FACTORY=stub_rpi python example.py 让我们执行它:(rpi_stub_env)$ RPI_STUB_URL = / tmp / socket.sock GPIOZERO_PIN_FACTORY = stub_rpi python example.py

By default the script prints that the button is pressed. 默认情况下,脚本会打印按下按钮。 Now let's press the button: 现在让我们按下按钮:

(rpi_stub_env)$ stub_rpi_trigger /tmp/socket.sock SS 2 1

Now the script should print that the button is not pressed. 现在脚本应该打印出没有按下按钮。 If you execute the following command it will be pressed again: 如果执行以下命令,将再次按下该命令:

(rpi_stub_env)$ stub_rpi_trigger /tmp/socket.sock SS 2 0

I hope it will help you. 我希望它会对你有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM