An Introduction To OneGPIO Gateways
A OneGPIO Gateway is a specialized Banyan component that is target-hardware specific. It subscribes to and translates OneGPIO command messages to and from native target hardware GPIO API calls. OneGPIO Gateways for the Arduino, ESP-8266, and Raspberry Pi are included with this distribution.
A base class, called GatewayBase, is made available to simplify creating a OneGPIO Gateway. This class encapsulates both BanyanBase functionality as well as OneGPIO Gateway functionality. When implementing a OneGPIO Gateway, you may choose any target GPIO API. For example, in creating the Raspberry Pi Gateway, the pigpio library was chosen. If you prefer to use some other API, there are no restrictions to do so.
There is also a Python asyncio version of the base class, called GatewayBaseAIO. This additional base class was necessary to support the pymata-express asyncio GPIO library for the Arduino. It is very similar to the GatewayBase class, and so it will not be discussed here.
Understanding The GatewayBase Class
Let's look at the code. Below are code sections that are followed by a discussion.
23 from python_banyan.banyan_base import BanyanBase
24
25
26 class GatewayBase(BanyanBase):
27 """
28 This class provides a common front end abstraction for all asyncio hardware gateways.
29 """
30
31 # pin modes
32 DIGITAL_INPUT_MODE = 0
33 DIGITAL_OUTPUT_MODE = 1
34 PWM_OUTPUT_MODE = 2
35 ANALOG_INPUT_MODE = 3
36 ANALOG_OUTPUT_MODE = 4
37 DIGITAL_INPUT_PULLUP_MODE = 5
38 I2C_MODE = 6
39 TONE_MODE = 7
40 SERVO_MODE = 8
41 STEPPER_MODE = 9
42 SONAR_MODE = 10
43 WILD_CARD_MODE = 11
44
45 # board types
46 ARDUINO = 0
47 RPi = 1
48 ESP8266 = 2
Since GatewayBase is derived by the BanyanBase class, we import BanyanBase on line 23.
Lines 31-43 define a set of pin mode "constants" as class variables.
Lines 46-48 define some common board type identifiers.
51 def __init__(self, back_plane_ip_address=None, subscriber_port='43125',
52 publisher_port='43124', process_name='',
53 subscriber_list=None, board_type=None, ):
54 """
55
56 :param back_plane_ip_address: banyan_base back_planeIP Address -
57 if not specified, it will be set to the local computer
58 :param subscriber_port: banyan_base back plane subscriber port.
59 This must match that of the banyan_base backplane
60 :param publisher_port: banyan_base back plane publisher port.
61 This must match that of the banyan_base
62 backplane
63 :param process_name: Component identifier
64 :param subscriber_list: a tuple or list of topics to be subscribed to
65 :param board_type: micro-controller type ID
66
67 """
The __init__ method accepts the standard BanyanBase input parameters, as well as 2 additional parameters. The subscriber_list parameter allows the user to supply a list of subscription topics for the gateway, and board_type is an optional parameter that will allow the user to supply a board type id.
68 if board_type:
69 self.board_type = board_type
70
71 if subscriber_list:
72 self.subscriber_list = subscriber_list
73 else:
74 self.subscriber_list = ('all')
75
76 # dictionaries for pin modes set by user
77 # an entry is board type specific
78
79 # this dictionary initially contains an entry for each default
80 # digital input pin
81
82 self.pins_dictionary = {}
83
84 # a pin can optionally be given a tag, it is used as a key to find
85 # pin number
86 # tag(string): pin(integer)
87 self.tags_dictionary = {}
88
89 self.init_pins_dictionary()
90
In this section, we save the input parameters and establish some data structures.
On line 74, if a subscriber_list was not provided, a default subscription topic of all is used as the lone entry into the subscriber_list. Of course, subscription topics may be added at any time during run-time.
Line 82 creates an empty pins_dictionary. This dictionary is used by each hardware-specific gateway to store pin information, such as the pin's mode and current state.
Line 87 creates an empty tags_dictionary. When setting a pin mode, if a tag is provided, an entry is made into this dictionary. The tag is used as a key, and the pin number is the entry's value.
Line 89 calls the init_pins_dictionary method. See the discussion below for line 126 for more information about this method.
91 # initialize the parent
92 super(GatewayBase, self).__init__(back_plane_ip_address=back_plane_ip_address,
93 subscriber_port=subscriber_port,
94 publisher_port=publisher_port,
95 process_name=process_name,
96 )
Line 91 initializes the BanyanBase class
98 self.command_dictionary = {'analog_write': self.analog_write,
99 'digital_write': self.digital_write,
100 'disable_analog_reporting': self.disable_analog_reporting,
101 'disable_digital_reporting': self.disable_digital_reporting,
102 'enable_analog_reporting': self.disable_analog_reporting,
103 'enable_digital_reporting': self.disable_digital_reporting,
104 'i2c_read': self.i2c_read,
105 'i2c_write': self.i2c_write,
106 'play_tone': self.play_tone,
107 'pwm_write': self.pwm_write,
108 'servo_position': self.servo_position,
109 'set_mode_analog_input': self.set_mode_analog_input,
110 'set_mode_digital_input': self.set_mode_digital_input,
111 'set_mode_digital_input_pullup': self.set_mode_digital_input_pullup,
112 'set_mode_digital_output': self.set_mode_digital_output,
113 'set_mode_i2c': self.set_mode_i2c,
114 'set_mode_pwm': self.set_mode_pwm,
115 'set_mode_servo': self.set_mode_servo,
116 'set_mode_sonar': self.set_mode_sonar,
117 'set_mode_stepper': self.set_mode_stepper,
118 'set_mode_tone': self.set_mode_tone,
119 'stepper_write': self.stepper_write,
120 }
Lines 98-120 create a command_dictionary. Every OneGPIO Gateway contains a command_dictionary.
The command_dictionary maps OneGPIO commands to methods that ultimately process the command. A OneGPIO command string is used as a key, and the value for each key is a method reference that will be called on line 161 below.
121
122 if subscriber_list is not None:
123 for topic in subscriber_list:
124 self.set_subscriber_topic(topic)
The __init__ method concludes by subscribing to all the topics within the subscriber_list.
126 def init_pins_dictionary(self):
127 """
128 This method will initialize the pins dictionary
129 This is handled within the class for each hardware type
130 """
131 raise NotImplementedError
This method must be overwritten by each hardware-specific OneGPiO Gateway, even if not needed. This is to ensure that you have not forgotten to implement this method.
133 def incoming_message_processing(self, topic, payload):
134 """
135 Messages are sent here from the receive_loop
136 :param topic: Message Topic string
137 :param payload: Message Data
138 :return:
139 """
140 # process payload command
141 try:
142 command = payload['command']
143 except KeyError:
144 print(payload)
145 raise
146
147 # if a tag is provided and the tag is in the dictionary, fetch
148 # the associated pin number
149 if 'tag' in payload:
150 tag = payload['tag']
151 if tag:
152 if tag in self.tags_dictionary:
153 pin = self.tags_dictionary[tag]
154 # the pin is optional if using tag, so add it to the payload
155 payload['pin'] = pin
156 else:
157 self.tags_dictionary[payload['tag']] = payload['pin']
158
159 # if command is in the command dictionary, execute the command
160 if command in self.command_dictionary.keys():
161 self.command_dictionary[command](topic, payload)
162
163 # for unknown requests, pass them along to the hardware gateway to handle
164 else:
165 self.additional_banyan_messages(topic, payload)
166
Lines 133-168 implement the Banyan incoming_message_processing method. Received OneGPIO commands are processed by this method.
Line 141-145 retrieves the command key string of the OneGPIO incoming message.
Line 149 checks to see if an optional tag key is in the message.
If it is, lines 149-158 process the tag. If the tag is not in the tags_dictionary, the tag and its associated pin number are added to the dictionary.
Lines 160-165 check to see if the value of the command key is within the command_dictionary. If it is, the command method is called.
If it is not found, the additional_banyan_messages method is called. This allows you to add hardware-specific commands not found in the command dictionary easily.
179 def analog_write(self, topic, payload):
180 """
181 This method will pass any messages not handled by this class to the
182 specific gateway class. Must be overwritten by the hardware gateway class.
183 :param topic: message topic
184 :param payload: message payload
185 """
186 raise NotImplementedError
The remainder of the class is the set of command methods that need to be overwritten in the derived class. Lines 179-186 are an illustration of the analog_write method. All the other command methods follow a similar pattern (lines 170-378).
Some Specific Command Examples
To illustrate the flexibility of using the OneGPIO specification, let's look at some sample OneGPIO command implementations. Both examples presented below are from the Raspberry Pi Gateway.
Adding A Custom Spin To The Command
When implementing a OneGPIO command, you can go beyond a simple one to one mapping between the OneGPIO command and the underlying GPIO API.
Let's look at set_mode_digital_input for the Raspberry Pi. The Raspberry Pi Gateway uses the pigpio GPIO library. Of course, you can use any GPIO library you wish to choose.
def set_mode_digital_input(self, topic, payload):
"""
This method sets a pin as digital input.
:param topic: message topic
:param payload: {"command": "set_mode_digital_input", "pin": “PIN”, "tag":”TAG” }
"""
pin = payload['pin']
entry = self.pins_dictionary[pin]
entry['mode'] = self.DIGITAL_INPUT_MODE
self.pi.set_glitch_filter(pin, 20000)
self.pi.set_mode(pin, pigpio.INPUT)
self.pi.set_pull_up_down(pin, pigpio.PUD_DOWN)
self.pi.callback(pin, pigpio.EITHER_EDGE, self.input_callback)
The pin number is extracted from the OneGPIO payload. The pin mode is set within the pins_dictionary for the pin.
Next, it performs 4 pigpio functions.
- A glitch filter to debounce the pin.
- The pin mode is set to input.
- The internal pull-up/pull-down resistor for the pin is set to pull-down.
- A callback method is set. This method is called whenever there is a state change for the pin.
The OneGPIO Application Component has no knowledge of all of this underlying logic. It merely sends the command to set the pin mode to digital input, and the OneGPIO Gateway interprets the behavior for the specific target hardware.
Adding Some More Spin
The Raspberry Pi GPIO does not directly support analog input. But since you control the definition of a command, the gateway can implement anything you choose, including using an external device to perform analog input.
Here we implement analog input for the Raspberry Pi by using a PFC8591 A/D converter.
def set_mode_analog_input(self, topic, payload):
"""
This method programs a PCF8591 AD/DA for analog input.
:param topic: message topic
:param payload: {"command": "set_mode_analog_input",
"pin": “PIN”, "tag":”TAG” }
"""
# pin is used as channel number
value = None
i2c_handle = self.pi.i2c_open(1, 72, 0)
pin = payload['pin']
self.pi.i2c_write_byte_data(i2c_handle, 64 | (pin & 0x03), 0)
time.sleep(0.1)
for i in range(3):
value = self.pi.i2c_read_byte(i2c_handle)
self.pi.i2c_close(i2c_handle)
# publish an analog input report
payload = {'report': 'analog_input', 'pin': pin,
'value': value}
self.publish_payload(payload, 'from_rpi_gateway')
The A/D device is i2c based, and so this method implements the i2c communication. The method retrieves the current value for one of the 4 channels supported by the PCF8591 A/D converter. It then publishes that value using a OneGPIO report message.
Copyright (C) 2017-2020 Alan Yorinks All Rights Reserved