Skip to content

Commit 093f07b

Browse files
committed
iotest4.py added.
1 parent 6607123 commit 093f07b

File tree

5 files changed

+132
-7
lines changed

5 files changed

+132
-7
lines changed

DRIVERS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,14 @@ Constructor arguments (defaults in brackets):
168168
1. `func` The **function** to call on timeout (default `None`).
169169
2. `args` A tuple of arguments for the **function** (default `()`).
170170
3. `can_alloc` Boolean, default `True`. See below.
171+
4. `duration` Integer, default 1000ms. The default timer period where no value
172+
is passed to the `trigger` method.
171173

172174
Methods:
173175

174-
1. `trigger` mandatory argument `duration`. A timeout will occur after
175-
`duration` ms unless retriggered.
176+
1. `trigger` optional argument `duration=0`. A timeout will occur after
177+
`duration` ms unless retriggered. If no arg is passed the period will be that
178+
of the `duration` passed to the constructor.
176179
2. `stop` No argument. Cancels the timeout, setting the `running` status
177180
`False`. The timer can be restarted by issuing `trigger` again.
178181
3. `running` No argument. Returns the running status of the object.

aswitch.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@
3838

3939

4040
class Delay_ms(object):
41-
def __init__(self, func=None, args=(), can_alloc=True):
41+
def __init__(self, func=None, args=(), can_alloc=True, duration=1000):
4242
self.func = func
4343
self.args = args
4444
self.can_alloc = can_alloc
45+
self.duration = duration # Default duration
4546
self.tstop = None # Not running
4647
self.loop = asyncio.get_event_loop()
4748
if not can_alloc:
@@ -57,8 +58,10 @@ async def _run(self):
5758
def stop(self):
5859
self.tstop = None
5960

60-
def trigger(self, duration): # Update end time
61-
if self.can_alloc and self.tstop is None:
61+
def trigger(self, duration=0): # Update end time
62+
if duration <= 0:
63+
duration = self.duration
64+
if self.can_alloc and self.tstop is None: # No killer task is running
6265
self.tstop = time.ticks_add(time.ticks_ms(), duration)
6366
# Start a task which stops the delay after its period has elapsed
6467
self.loop.create_task(self.killer())

gps/as_GPS.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def __init__(self, sreader, local_offset=0, fix_cb=lambda *_ : None, cb_mask=RMC
9494
self._fix_cb = fix_cb
9595
self.cb_mask = cb_mask
9696
self._fix_cb_args = fix_cb_args
97+
self.battery = False # Assume no backup battery
9798

9899
# CPython compatibility. Import utime or time for fix time handling.
99100
try:
@@ -307,11 +308,15 @@ def _set_date_time(self, utc_string, date_string):
307308
def _gprmc(self, gps_segments): # Parse RMC sentence
308309
self._valid &= ~RMC
309310
# Check Receiver Data Valid Flag ('A' active)
310-
if gps_segments[2] != 'A':
311-
raise ValueError
311+
if not self.battery:
312+
if gps_segments[2] != 'A':
313+
raise ValueError
312314

313315
# UTC Timestamp and date. Can raise ValueError.
314316
self._set_date_time(gps_segments[1], gps_segments[9])
317+
# Check Receiver Data Valid Flag ('A' active)
318+
if gps_segments[2] != 'A':
319+
raise ValueError
315320

316321
# Data from Receiver is Valid/Has Fix. Longitude / Latitude
317322
# Can raise ValueError.

gps/as_tGPS.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def _isr(self, _):
7979
msecs += self._update_ms
8080
if msecs >= 86400000: # Next PPS will deal with rollover
8181
return
82+
if self.t_ms == msecs: # No RMC message has arrived: nothing to do
83+
return
8284
self.t_ms = msecs # Current time in ms past midnight
8385
self.acquired = acquired
8486
# Set RTC if required and if last RMC indicated a 1 second boundary

iotest4.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# iotest4.py Test PR #3836. Demonstrate the anomaly with a read/write device.
2+
# User class write() performs unbuffered writing.
3+
# For simplicity this uses buffered read: unbuffered is tested by iotest2.py.
4+
5+
# Run iotest4.test() to see expected output
6+
# iotest4.test(False) to demonstrate the issue.
7+
8+
# Pass/Fail is determined by whether the StreamReader and StreamWriter operate
9+
# on the same (fail) or different (pass) objects.
10+
# I suspect that the issue is with select/ipoll (uasyncio __init__.py)
11+
# The fault is either in select/poll or uasyncio __init__.py.
12+
# As soon as PollEventLoop.add_writer() is called, reading stops.
13+
# PollEventLoop.add_writer() is called when StreamWriter.awrite() issues
14+
# yield IOWrite(self.s), which for unbuffered devices is after the 1st char
15+
# of a multi-char buf is written.
16+
17+
import io, pyb
18+
import uasyncio as asyncio
19+
import micropython
20+
micropython.alloc_emergency_exception_buf(100)
21+
22+
MP_STREAM_POLL_RD = const(1)
23+
MP_STREAM_POLL_WR = const(4)
24+
MP_STREAM_POLL = const(3)
25+
MP_STREAM_ERROR = const(-1)
26+
27+
def printbuf(this_io):
28+
print(this_io.wbuf[:this_io.wprint_len])
29+
30+
class MyIO(io.IOBase):
31+
def __init__(self, read=False, write=False):
32+
if read:
33+
self.ready_rd = False
34+
self.rbuf = b'ready\n' # Read buffer
35+
pyb.Timer(4, freq = 1, callback = self.do_input)
36+
if write:
37+
self.wbuf = bytearray(100) # Write buffer
38+
self.wprint_len = 0
39+
self.widx = 0
40+
self.wch = b''
41+
pyb.Timer(5, freq = 10, callback = self.do_output)
42+
43+
# Read callback: emulate asynchronous input from hardware.
44+
# Typically would put bytes into a ring buffer and set .ready_rd.
45+
def do_input(self, t):
46+
self.ready_rd = True # Data is ready to read
47+
48+
# Write timer callback. Emulate hardware: if there's data in the buffer
49+
# write some or all of it
50+
def do_output(self, t):
51+
if self.wch:
52+
self.wbuf[self.widx] = self.wch
53+
self.widx += 1
54+
if self.wch == ord('\n'):
55+
self.wprint_len = self.widx # Save for schedule
56+
micropython.schedule(printbuf, self)
57+
self.widx = 0
58+
self.wch = b''
59+
60+
61+
def ioctl(self, req, arg): # see ports/stm32/uart.c
62+
ret = MP_STREAM_ERROR
63+
if req == MP_STREAM_POLL:
64+
ret = 0
65+
if arg & MP_STREAM_POLL_RD:
66+
if self.ready_rd:
67+
ret |= MP_STREAM_POLL_RD
68+
if arg & MP_STREAM_POLL_WR:
69+
if not self.wch:
70+
ret |= MP_STREAM_POLL_WR # Ready if no char pending
71+
return ret
72+
73+
# Emulate a device with buffered read. Return the buffer, falsify read ready
74+
# Read timer sets ready.
75+
def readline(self):
76+
self.ready_rd = False
77+
return self.rbuf
78+
79+
# Emulate unbuffered hardware which writes one character: uasyncio waits
80+
# until hardware is ready for the next. Hardware ready is emulated by write
81+
# timer callback.
82+
def write(self, buf, off, sz):
83+
self.wch = buf[off] # Hardware starts to write a char
84+
return 1 # 1 byte written. uasyncio waits on ioctl write ready
85+
86+
async def receiver(myior):
87+
sreader = asyncio.StreamReader(myior)
88+
while True:
89+
res = await sreader.readline()
90+
print('Received', res)
91+
92+
async def sender(myiow):
93+
swriter = asyncio.StreamWriter(myiow, {})
94+
await asyncio.sleep(5)
95+
count = 0
96+
while True:
97+
count += 1
98+
tosend = 'Wrote Hello MyIO {}\n'.format(count)
99+
await swriter.awrite(tosend.encode('UTF8'))
100+
await asyncio.sleep(2)
101+
102+
def test(good=True):
103+
if good:
104+
myior = MyIO(read=True)
105+
myiow = MyIO(write=True)
106+
else:
107+
myior = MyIO(read=True, write=True)
108+
myiow = myior
109+
loop = asyncio.get_event_loop()
110+
loop.create_task(receiver(myior))
111+
loop.create_task(sender(myiow))
112+
loop.run_forever()

0 commit comments

Comments
 (0)