Skip to content

Commit 125f128

Browse files
committed
Minor code improvements, notably to demos.
1 parent 0a16235 commit 125f128

File tree

11 files changed

+216
-179
lines changed

11 files changed

+216
-179
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ This will pause until connectivity has been established. It can be issued at
636636
any time: if the `Connection` has already been instantiated, that instance will
637637
be returned. The `Connection` constructor should not be called by applications.
638638

639-
The `Connection` instance:
639+
#### The `Connection` instance
640640

641641
Methods (asynchronous):
642642
1. `readline` No args. Pauses until data received. Returns a line.
@@ -656,6 +656,10 @@ Methods (synchronous):
656656
Class Method (synchronous):
657657
1. `close_all` No args. Closes all sockets: call on exception (e.g. ctrl-c).
658658

659+
Bound variable:
660+
1. `nconns` Maintains a count of (re)connections for information or monitoring
661+
of outages.
662+
659663
The `Connection` class is awaitable. If
660664
```python
661665
await connection_instance

iot/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# __init__.py Common utility functions for micropython-iot
2+
3+
# Released under the MIT licence.
4+
# Copyright (C) Peter Hinch 2019-2020
5+
6+
# Now uses and requires uasyncio V3. This is incorporated in daily builds
7+
# and release builds later than V1.12
8+
# Under CPython requires CPython 3.8 or later.
9+
10+
# Create message ID's. Initially 0 then 1 2 ... 254 255 1 2
11+
def gmid():
12+
mid = 0
13+
while True:
14+
yield mid
15+
mid = (mid + 1) & 0xff
16+
mid = mid if mid else 1
17+
18+
# Return True if a message ID has not already been received
19+
def isnew(mid, lst=bytearray(32)):
20+
if mid == -1:
21+
for idx in range(32):
22+
lst[idx] = 0
23+
return
24+
idx = mid >> 3
25+
bit = 1 << (mid & 7)
26+
res = not(lst[idx] & bit)
27+
lst[idx] |= bit
28+
lst[(idx + 16 & 0x1f)] = 0
29+
return res
30+
31+
# Minimal implementation of set for integers in range 0-255
32+
class SetByte:
33+
def __init__(self):
34+
self._ba = bytearray(32)
35+
36+
def __bool__(self):
37+
return any(self._ba)
38+
39+
def __contains__(self, i):
40+
return (self._ba[i >> 3] & 1 << (i & 7)) > 0
41+
42+
def discard(self, i):
43+
self._ba[i >> 3] &= ~(1 << (i &7))
44+
45+
def add(self, i):
46+
self._ba[i >> 3] |= 1 << (i & 7)

iot/client.mpy

-201 Bytes
Binary file not shown.

iot/client.py

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222
import utime
2323
import machine
2424
import uerrno as errno
25-
from .primitives import gmid, isnew, launch, SetByte # __init__.py
26-
Event = asyncio.Event
27-
Lock = asyncio.Lock
28-
25+
from . import gmid, isnew, SetByte # __init__.py
26+
from .primitives import launch
2927
gc.collect()
3028
from micropython import const
3129

@@ -103,8 +101,8 @@ def inner(feed=WDT_CB):
103101
import esp
104102
esp.sleep_type(esp.SLEEP_NONE) # Improve connection integrity at cost of power consumption.
105103

106-
self._evfail = Event()
107-
self._s_lock = Lock() # For internal send conflict.
104+
self._evfail = asyncio.Event()
105+
self._s_lock = asyncio.Lock() # For internal send conflict.
108106
self._last_wr = utime.ticks_ms()
109107
self._lineq = deque((), 20, True) # 20 entries, throw on overflow
110108
self.connects = 0 # Connect count for test purposes/app access
@@ -185,9 +183,8 @@ async def _write(self, line):
185183
# After an outage wait until something is received from server
186184
# before we send.
187185
await self
188-
async with self._s_lock:
189-
if await self._send(line):
190-
return
186+
if await self._send(line):
187+
return
191188

192189
# send fail. _send has triggered _evfail. Await response.
193190
while self():
@@ -263,14 +260,7 @@ async def _run(self):
263260
tsk_reader = asyncio.create_task(self._reader())
264261
# Server reads ID immediately, but a brief pause is probably wise.
265262
await asyncio.sleep_ms(50)
266-
# No need for lock yet.
267-
try:
268-
if not await self._send(self._my_id):
269-
raise OSError
270-
except OSError:
271-
if init:
272-
await self.bad_server()
273-
else:
263+
if await self._send(self._my_id):
274264
tsk_ka = asyncio.create_task(self._keepalive())
275265
if self._concb is not None:
276266
# apps might need to know connection to the server acquired
@@ -284,6 +274,8 @@ async def _run(self):
284274
if self._concb is not None:
285275
# apps might need to know if they lost connection to the server
286276
launch(self._concb, False, *self._concbargs)
277+
elif init:
278+
await self.bad_server()
287279
finally:
288280
init = False
289281
self._close() # Close socket but not wdt
@@ -325,16 +317,14 @@ async def _reader(self): # Entry point is after a (re) connect.
325317
self.connects += 1 # update connect count
326318

327319
async def _sendack(self, mid):
328-
async with self._s_lock:
329-
await self._send('{:02x}\n'.format(mid))
320+
await self._send('{:02x}\n'.format(mid))
330321

331322
async def _keepalive(self):
332323
while True:
333324
due = self._tim_ka - utime.ticks_diff(utime.ticks_ms(), self._last_wr)
334325
if due <= 0:
335-
async with self._s_lock:
336-
# error sets ._evfail, .run cancels this coro
337-
await self._send(b'\n')
326+
# error sets ._evfail, .run cancels this coro
327+
await self._send(b'\n')
338328
else:
339329
await asyncio.sleep_ms(due)
340330

@@ -376,26 +366,27 @@ async def _readline(self, to):
376366
line = b''.join((line, d)) if line else d
377367

378368
async def _send(self, d): # Write a line to socket.
379-
start = utime.ticks_ms()
380-
while d:
381-
try:
382-
ns = self._sock.send(d) # OSError if client closes socket
383-
except OSError as e:
384-
err = e.args[0]
385-
if err == errno.EAGAIN: # Would block: await server read
386-
await asyncio.sleep_ms(100)
369+
async with self._s_lock:
370+
start = utime.ticks_ms()
371+
while d:
372+
try:
373+
ns = self._sock.send(d) # OSError if client closes socket
374+
except OSError as e:
375+
err = e.args[0]
376+
if err == errno.EAGAIN: # Would block: await server read
377+
await asyncio.sleep_ms(100)
378+
else:
379+
self._verbose and print('_send fail. Disconnect')
380+
self._evfail.set()
381+
return False # peer disconnect
387382
else:
388-
self._verbose and print('_send fail. Disconnect')
389-
self._evfail.set()
390-
return False # peer disconnect
391-
else:
392-
d = d[ns:]
393-
if d: # Partial write: pause
394-
await asyncio.sleep_ms(20)
395-
if utime.ticks_diff(utime.ticks_ms(), start) > self._to:
396-
self._verbose and print('_send fail. Timeout.')
397-
self._evfail.set()
398-
return False
399-
400-
self._last_wr = utime.ticks_ms()
383+
d = d[ns:]
384+
if d: # Partial write: pause
385+
await asyncio.sleep_ms(20)
386+
if utime.ticks_diff(utime.ticks_ms(), start) > self._to:
387+
self._verbose and print('_send fail. Timeout.')
388+
self._evfail.set()
389+
return False
390+
391+
self._last_wr = utime.ticks_ms()
401392
return True

iot/primitives/__init__.py

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
# __init__.py Common utility functions for micropython-iot
1+
# __init__.py Common functions for uasyncio primitives
22

33
# Released under the MIT licence.
44
# Copyright (C) Peter Hinch 2019-2020
55

6-
# Now uses and requires uasyncio V3. This is incorporated in daily builds
7-
# and release builds later than V1.12
8-
# Under CPython requires CPython 3.8 or later.
9-
106
try:
117
import uasyncio as asyncio
128
except ImportError:
@@ -23,44 +19,10 @@ def launch(func, *tup_args):
2319
loop = asyncio.get_event_loop()
2420
loop.create_task(res)
2521

26-
27-
# Create message ID's. Initially 0 then 1 2 ... 254 255 1 2
28-
def gmid():
29-
mid = 0
30-
while True:
31-
yield mid
32-
mid = (mid + 1) & 0xff
33-
mid = mid if mid else 1
34-
35-
# Return True if a message ID has not already been received
36-
def isnew(mid, lst=bytearray(32)):
37-
if mid == -1:
38-
for idx in range(32):
39-
lst[idx] = 0
40-
return
41-
idx = mid >> 3
42-
bit = 1 << (mid & 7)
43-
res = not(lst[idx] & bit)
44-
lst[idx] |= bit
45-
lst[(idx + 16 & 0x1f)] = 0
46-
return res
47-
48-
# Minimal implementation of set for integers in range 0-255
49-
class SetByte:
50-
def __init__(self):
51-
self._ba = bytearray(32)
52-
53-
def __bool__(self):
54-
for x in self._ba:
55-
if x:
56-
return True
57-
return False
58-
59-
def __contains__(self, i):
60-
return (self._ba[i >> 3] & 1 << (i & 7)) > 0
61-
62-
def discard(self, i):
63-
self._ba[i >> 3] &= ~(1 << (i &7))
64-
65-
def add(self, i):
66-
self._ba[i >> 3] |= 1 << (i & 7)
22+
def set_global_exception():
23+
def _handle_exception(loop, context):
24+
import sys
25+
sys.print_exception(context["exception"])
26+
sys.exit()
27+
loop = asyncio.get_event_loop()
28+
loop.set_exception_handler(_handle_exception)

iot/qos/c_qos.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
gc.collect()
1717
from iot import client
1818
import urandom
19+
from .check_mid import CheckMid
1920

2021
# Optional LED. led=None if not required
21-
from sys import platform
22+
from sys import platform, exit, print_exception
2223
if platform == 'pyboard': # D series
2324
from pyb import LED
2425
led = LED(1)
@@ -27,17 +28,18 @@
2728
led = Pin(2, Pin.OUT, value=1) # Optional LED
2829
# End of optionalLED
2930

31+
def _handle_exception(loop, context):
32+
print_exception(context["exception"])
33+
exit()
34+
3035
class App:
3136
def __init__(self, verbose):
3237
self.verbose = verbose
3338
self.cl = client.Client(local.MY_ID, local.SERVER,
3439
local.PORT, local.SSID, local.PW,
3540
local.TIMEOUT, verbose=verbose, led=led)
3641
self.tx_msg_id = 0
37-
self.dupes = 0 # Incoming dupe count
38-
self.missing = 0
39-
self.last = 0
40-
self.rxbuf = []
42+
self.cm = CheckMid() # Check message ID's for dupes, missing etc.
4143

4244
async def start(self):
4345
self.verbose and print('App awaiting connection.')
@@ -51,30 +53,16 @@ async def reader(self):
5153
while True:
5254
line = await self.cl.readline()
5355
data = ujson.loads(line)
54-
rxmid = data[0]
55-
if rxmid in self.rxbuf:
56-
self.dupes += 1
57-
else:
58-
self.rxbuf.append(rxmid)
56+
self.cm(data[0]) # Update statistics
5957
print('Got', data, 'from server app')
6058

61-
def count_missed(self):
62-
if len(self.rxbuf) >= 25:
63-
idx = 0
64-
while self.rxbuf[idx] < self.last + 10:
65-
idx += 1
66-
self.last += 10
67-
self.missing += 10 - idx
68-
self.rxbuf = self.rxbuf[idx:]
69-
return self.missing
70-
7159
# Send [ID, (re)connect count, free RAM, duplicate message count, missed msgcount]
7260
async def writer(self):
7361
self.verbose and print('Started writer')
7462
while True:
7563
gc.collect()
7664
data = [self.tx_msg_id, self.cl.connects, gc.mem_free(),
77-
self.dupes, self.count_missed()]
65+
self.cm.dupe, self.cm.miss]
7866
self.tx_msg_id += 1
7967
print('Sent', data, 'to server app\n')
8068
dstr = ujson.dumps(data)
@@ -88,6 +76,8 @@ def close(self):
8876
app = None
8977
async def main():
9078
global app # For finally clause
79+
loop = asyncio.get_event_loop()
80+
loop.set_exception_handler(_handle_exception)
9181
app = App(verbose=True)
9282
await app.start()
9383

0 commit comments

Comments
 (0)