Skip to content

Commit 35fb45e

Browse files
authored
Merge pull request #59 from haydenroche5/ctx_rtx
2 parents 4da3cf4 + 5e258e6 commit 35fb45e

File tree

6 files changed

+437
-56
lines changed

6 files changed

+437
-56
lines changed

notecard/gpio.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
"""GPIO abstractions for note-python."""
2+
3+
from .platform import platform
4+
5+
if platform == 'circuitpython':
6+
import digitalio
7+
elif platform == 'micropython':
8+
import machine
9+
elif platform == 'raspbian':
10+
import RPi.GPIO as rpi_gpio
11+
12+
13+
class GPIO:
14+
"""GPIO abstraction.
15+
16+
Supports GPIO on CircuitPython, MicroPython, and Raspbian (Raspberry Pi).
17+
"""
18+
19+
IN = 0
20+
OUT = 1
21+
PULL_UP = 2
22+
PULL_DOWN = 3
23+
PULL_NONE = 4
24+
25+
def direction(self, direction):
26+
"""Set the direction of the pin.
27+
28+
Does nothing in this base class. Should be implemented by subclasses.
29+
"""
30+
pass
31+
32+
def pull(self, pull):
33+
"""Set the pull of the pin.
34+
35+
Does nothing in this base class. Should be implemented by subclasses.
36+
"""
37+
pass
38+
39+
def value(self, value=None):
40+
"""Set the output or get the current level of the pin.
41+
42+
Does nothing in this base class. Should be implemented by subclasses.
43+
"""
44+
pass
45+
46+
@staticmethod
47+
def setup(pin, direction, pull=None, value=None):
48+
"""Set up a GPIO.
49+
50+
The platform is detected internally so that the user doesn't need to
51+
write platform-specific code themselves.
52+
"""
53+
if platform == 'circuitpython':
54+
return CircuitPythonGPIO(pin, direction, pull, value)
55+
elif platform == 'micropython':
56+
return MicroPythonGPIO(pin, direction, pull, value)
57+
elif platform == 'raspbian':
58+
return RpiGPIO(pin, direction, pull, value)
59+
60+
def __init__(self, pin, direction, pull=None, value=None):
61+
"""Initialize the GPIO.
62+
63+
Pin and direction are required arguments. Pull and value will be set
64+
only if given.
65+
"""
66+
self.direction(direction)
67+
68+
if pull is not None:
69+
self.pull(pull)
70+
71+
if value is not None:
72+
self.value(value)
73+
74+
75+
class CircuitPythonGPIO(GPIO):
76+
"""GPIO for CircuitPython."""
77+
78+
def direction(self, direction):
79+
"""Set the direction of the pin.
80+
81+
Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
82+
ValueError.
83+
"""
84+
if direction == GPIO.IN:
85+
self.pin.direction = digitalio.Direction.INPUT
86+
elif direction == GPIO.OUT:
87+
self.pin.direction = digitalio.Direction.OUTPUT
88+
else:
89+
raise ValueError(f"Invalid pin direction: {direction}.")
90+
91+
def pull(self, pull):
92+
"""Set the pull of the pin.
93+
94+
Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
95+
GPIO.PULL_NONE. Other values cause a ValueError.
96+
"""
97+
if pull == GPIO.PULL_UP:
98+
self.pin.pull = digitalio.Pull.UP
99+
elif pull == GPIO.PULL_DOWN:
100+
self.pin.pull = digitalio.Pull.DOWN
101+
elif pull == GPIO.PULL_NONE:
102+
self.pin.pull = None
103+
else:
104+
raise ValueError(f"Invalid pull value: {pull}.")
105+
106+
def value(self, value=None):
107+
"""Set the output or get the current level of the pin.
108+
109+
If value is not given, returns the level of the pin (i.e. the pin is an
110+
input). If value is given, sets the level of the pin (i.e. the pin is an
111+
output).
112+
"""
113+
if value is None:
114+
return self.pin.value
115+
else:
116+
self.pin.value = value
117+
118+
def __init__(self, pin, direction, pull=None, value=None):
119+
"""Initialize the GPIO.
120+
121+
Pin and direction are required arguments. Pull and value will be set
122+
only if given.
123+
"""
124+
self.pin = digitalio.DigitalInOut(pin)
125+
super().__init__(pin, direction, pull, value)
126+
127+
128+
class MicroPythonGPIO(GPIO):
129+
"""GPIO for MicroPython."""
130+
131+
def direction(self, direction):
132+
"""Set the direction of the pin.
133+
134+
Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
135+
ValueError.
136+
"""
137+
if direction == GPIO.IN:
138+
self.pin.init(mode=machine.Pin.IN)
139+
elif direction == GPIO.OUT:
140+
self.pin.init(mode=machine.Pin.OUT)
141+
else:
142+
raise ValueError(f"Invalid pin direction: {direction}.")
143+
144+
def pull(self, pull):
145+
"""Set the pull of the pin.
146+
147+
Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
148+
GPIO.PULL_NONE. Other values cause a ValueError.
149+
"""
150+
if pull == GPIO.PULL_UP:
151+
self.pin.init(pull=machine.Pin.PULL_UP)
152+
elif pull == GPIO.PULL_DOWN:
153+
self.pin.init(pull=machine.Pin.PULL_DOWN)
154+
elif pull == GPIO.PULL_NONE:
155+
self.pin.init(pull=None)
156+
else:
157+
raise ValueError(f"Invalid pull value: {pull}.")
158+
159+
def value(self, value=None):
160+
"""Set the output or get the current level of the pin.
161+
162+
If value is not given, returns the level of the pin (i.e. the pin is an
163+
input). If value is given, sets the level of the pin (i.e. the pin is an
164+
output).
165+
"""
166+
if value is None:
167+
return self.pin.value()
168+
else:
169+
self.pin.init(value=value)
170+
171+
def __init__(self, pin, direction, pull=None, value=None):
172+
"""Initialize the GPIO.
173+
174+
Pin and direction are required arguments. Pull and value will be set
175+
only if given.
176+
"""
177+
self.pin = machine.Pin(pin)
178+
super().__init__(pin, direction, pull, value)
179+
180+
181+
class RpiGPIO(GPIO):
182+
"""GPIO for Raspbian (Raspberry Pi)."""
183+
184+
def direction(self, direction):
185+
"""Set the direction of the pin.
186+
187+
Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
188+
ValueError.
189+
"""
190+
if direction == GPIO.IN:
191+
self.rpi_direction = rpi_gpio.IN
192+
rpi_gpio.setup(self.pin, direction=rpi_gpio.IN)
193+
elif direction == GPIO.OUT:
194+
self.rpi_direction = rpi_gpio.OUT
195+
rpi_gpio.setup(self.pin, direction=rpi_gpio.OUT)
196+
else:
197+
raise ValueError(f"Invalid pin direction: {direction}.")
198+
199+
def pull(self, pull):
200+
"""Set the pull of the pin.
201+
202+
Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
203+
GPIO.PULL_NONE. Other values cause a ValueError.
204+
"""
205+
if pull == GPIO.PULL_UP:
206+
rpi_gpio.setup(self.pin,
207+
direction=self.rpi_direction,
208+
pull_up_down=rpi_gpio.PUD_UP)
209+
elif pull == GPIO.PULL_DOWN:
210+
rpi_gpio.setup(self.pin,
211+
direction=self.rpi_direction,
212+
pull_up_down=GPIO.PUD_DOWN)
213+
elif pull == GPIO.PULL_NONE:
214+
rpi_gpio.setup(self.pin,
215+
direction=self.rpi_direction,
216+
pull_up_down=rpi_gpio.PUD_OFF)
217+
else:
218+
raise ValueError(f"Invalid pull value: {pull}.")
219+
220+
def value(self, value=None):
221+
"""Set the output or get the current level of the pin.
222+
223+
If value is not given, returns the level of the pin (i.e. the pin is an
224+
input). If value is given, sets the level of the pin (i.e. the pin is an
225+
output).
226+
"""
227+
if value is None:
228+
return rpi_gpio.input(self.pin)
229+
else:
230+
rpi_gpio.output(self.pin, value)
231+
232+
def __init__(self, pin, direction, pull=None, value=None):
233+
"""Initialize the GPIO.
234+
235+
Pin and direction are required arguments. Pull and value will be set
236+
only if given.
237+
"""
238+
self.pin = pin
239+
super().__init__(pin, direction, pull, value)

notecard/notecard.py

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import os
3535
import json
3636
import time
37+
from .timeout import start_timeout, has_timed_out
38+
from .transaction_manager import TransactionManager
3739

3840
use_periphery = False
3941
use_micropython = False
@@ -52,7 +54,6 @@
5254

5355
use_i2c_lock = not use_periphery and not use_micropython
5456

55-
5657
NOTECARD_I2C_ADDRESS = 0x17
5758

5859
# The notecard is a real-time device that has a fixed size interrupt buffer.
@@ -61,37 +62,6 @@
6162
CARD_REQUEST_SEGMENT_MAX_LEN = 250
6263
CARD_REQUEST_SEGMENT_DELAY_MS = 250
6364

64-
if not use_rtc:
65-
if use_circuitpython:
66-
import supervisor
67-
from supervisor import ticks_ms
68-
69-
_TICKS_PERIOD = 1 << 29
70-
_TICKS_MAX = _TICKS_PERIOD - 1
71-
_TICKS_HALFPERIOD = _TICKS_PERIOD // 2
72-
73-
def ticks_diff(ticks1, ticks2):
74-
"""Compute the signed difference between two ticks values."""
75-
diff = (ticks1 - ticks2) & _TICKS_MAX # noqa: F821
76-
diff = ((diff + _TICKS_HALFPERIOD) # noqa: F821
77-
& _TICKS_MAX) - _TICKS_HALFPERIOD # noqa: F821
78-
return diff
79-
if use_micropython:
80-
from utime import ticks_diff, ticks_ms # noqa: F811
81-
82-
83-
def has_timed_out(start, timeout_secs):
84-
"""Determine whether a timeout interval has passed during communication."""
85-
if not use_rtc:
86-
return ticks_diff(ticks_ms(), start) > timeout_secs * 1000
87-
else:
88-
return time.time() > start + timeout_secs
89-
90-
91-
def start_timeout():
92-
"""Start the timeout interval for I2C communication."""
93-
return ticks_ms() if not use_rtc else time.time()
94-
9565

9666
def prepareRequest(req, debug=False):
9767
"""Format the request string as a JSON object and add a newline."""
@@ -137,10 +107,14 @@ def serialReset(port):
137107
raise Exception("Notecard not responding")
138108

139109

140-
def serialTransaction(port, req, debug):
110+
def serialTransaction(port, req, debug, txn_manager=None):
141111
"""Perform a single write to and read from a Notecard."""
142112
req_json = prepareRequest(req, debug)
143113

114+
transaction_timeout_secs = 30
115+
if txn_manager:
116+
txn_manager.start(transaction_timeout_secs)
117+
144118
seg_off = 0
145119
seg_left = len(req_json)
146120
while True:
@@ -156,8 +130,10 @@ def serialTransaction(port, req, debug):
156130
break
157131
time.sleep(CARD_REQUEST_SEGMENT_DELAY_MS / 1000)
158132

159-
rsp_json = port.readline()
133+
if txn_manager:
134+
txn_manager.stop()
160135

136+
rsp_json = port.readline()
161137
if debug:
162138
print(rsp_json.rstrip())
163139

@@ -206,6 +182,7 @@ def __init__(self):
206182
self._user_agent['os_family'] = os.name
207183
else:
208184
self._user_agent['os_family'] = os.uname().machine
185+
self._transaction_manager = None
209186

210187
def _preprocessReq(self, req):
211188
"""Inspect the request for hub.set and add the User Agent."""
@@ -231,6 +208,10 @@ def UserAgentSent(self):
231208
"""Return true if the User Agent has been sent to the Notecard."""
232209
return self._user_agent_sent
233210

211+
def SetTransactionPins(self, rtx_pin, ctx_pin):
212+
"""Set the pins used for RTX and CTX."""
213+
self._transaction_manager = TransactionManager(rtx_pin, ctx_pin)
214+
234215

235216
class OpenSerial(Notecard):
236217
"""Notecard class for Serial communication."""
@@ -258,13 +239,13 @@ def Transaction(self, req):
258239
if use_serial_lock:
259240
try:
260241
self.lock.acquire(timeout=5)
261-
return serialTransaction(self.uart, req, self._debug)
242+
return serialTransaction(self.uart, req, self._debug, self._transaction_manager)
262243
except Timeout:
263244
raise Exception("Notecard in use")
264245
finally:
265246
self.lock.release()
266247
else:
267-
return serialTransaction(self.uart, req, self._debug)
248+
return serialTransaction(self.uart, req, self._debug, self._transaction_manager)
268249

269250
def Reset(self):
270251
"""Reset the Notecard."""
@@ -349,12 +330,15 @@ def Transaction(self, req):
349330
pass
350331

351332
try:
333+
transaction_timeout_secs = 30
334+
if self._transaction_manager:
335+
self._transaction_manager.start(transaction_timeout_secs)
336+
352337
self._sendPayload(req_json)
353338

354339
chunk_len = 0
355340
received_newline = False
356341
start = start_timeout()
357-
transaction_timeout_secs = 30
358342
while True:
359343
time.sleep(.001)
360344
reg = bytearray(2)
@@ -391,6 +375,8 @@ def Transaction(self, req):
391375

392376
finally:
393377
self.unlock()
378+
if self._transaction_manager:
379+
self._transaction_manager.stop()
394380

395381
if self._debug:
396382
print(rsp_json.rstrip())

0 commit comments

Comments
 (0)