Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configuration/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"topic_path": "$me",
# "topic_subscribe": [ "$me/in", "$me/exec", upgrade_topic ],
# "topic_subscribe": [ "$me/in", "$me/exec", upgrade_topic, "$all/log" ],
"topic_subscribe": [ "$me/in", "$me/exec", lca_schedule_topic, upgrade_topic ],
"topic_subscribe": [ "$me/in", "$me/exec", lca_schedule_topic, upgrade_topic, 'public/+/0/out' ],
"lca_schedule_topic": lca_schedule_topic,
"upgrade_topic": upgrade_topic,

Expand Down
3 changes: 3 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@
application = __import__(application_name)
application.initialise()

import plugins
plugins.initialise()

aiko.event.loop_thread()
47 changes: 47 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Plugins

## What's a plugin?

**Plugins aren't the same as applications.** In particular: we only want
to be running one application at a time. To get a little RFC2119, the
application MAY assume it has sole control of what's shown on the OLED
displays, and MAY assume any touch sensor or push buttons are for it to
handle. Eventually, their `initialise` function might get a matching
`uninitialise`, or we might get _very_ clever and remove their handlers
automatically.

Plugins, on the other hand, MUST co-operate with each other, MUST NOT
assume they control the displays or inputs, and SHOULD work together.
We'll eventually run into conflicts we can't resolve without being
able to disable specific plugins, but let's see how long we can last.

## How do I enable and disable plugins?

Set the parameter `plugin_xxx_disabled` to stop your badge automatically
calling `plugins.xxx.initialise()` at startup. You can set that in
`configuration/main.py`, and check it with the code:

```python
import configuration
configuration.main.parameter("plugins_enabled")
```

## How do I add plugins?

Add Python modules to the `plugins` directory. They MUST contain an
`initialise` function. They MUST NOT be named `initialise`, as that
would conflict with the plugin system's initialisation code.

You'll be able to see your plugins at the REPL:

```plain
MicroPython v1.13 on 2020-09-02; ESP32 module with ESP32
Type "help()" for more information.
>>> import plugins
>>> dir(plugins)
['__class__', '__name__', '__file__', '__path__', 'initialise', 'auto_shake', 'test2']
>>> plugins.auto_shake.messages
[('public/esp32_10521c5de548/0/out', '(boot v05 swagbadge)')]
>>> plugins.test2.tested
True
```
23 changes: 23 additions & 0 deletions plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def initialise():
from aiko.common import log
from configuration.main import parameter
import os, sys

for basename in os.listdir(__path__):
pathname = "/".join([__path__, basename])
if pathname == __file__:
continue
s_ifmt = os.stat(pathname)[0] & 61440
if basename[-3:] == ".py" and s_ifmt == 32768:
submodule = basename[:-3]
elif s_ifmt == 16384:
submodule = basename
assert submodule not in locals()
if parameter("plugin_{}_disabled".format(submodule)):
continue
try:
module = ".".join([__name__, submodule])
__import__(module, globals(), locals(), [module]).initialise()
log("plugin: {}".format(submodule))
except Exception as err:
sys.print_exception(err, sys.stderr)
26 changes: 26 additions & 0 deletions plugins/auto_shake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Automatically shakes newly woken badges using @enderboi's protocol

import aiko, binascii, machine, sys, time

my_shake_source = binascii.hexlify(machine.unique_id()).decode('ascii')
my_reply_topic = aiko.mqtt.get_topic_path("public") + "/in"
messages = []

def on_message(topic, payload_in):
messages.append((topic, payload_in))
if len(messages) > 5:
messages.pop(0)
if payload_in.startswith("(boot ") and payload_in.endswith(" swagbadge)"):
# TODO use aiko.event instead
time.sleep_ms(500)
hostname = topic.split('/')[1]
reply_topic = "/".join(["public", hostname, "0", "in"])
if reply_topic != my_reply_topic:
aiko.mqtt.client.publish(reply_topic, "(shake 230 {})".format(my_shake_source))
aiko.mqtt.client.publish(my_reply_topic, "(oled:log shook {})".format(hostname))
return False

def initialise():
# TODO broadcast message to indicate badge can be shook
# TODO shake only badges that broadcast that message
aiko.mqtt.add_message_handler(on_message, "$all/out")
61 changes: 61 additions & 0 deletions plugins/flash_leds_on_boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Flash configured LEDs on boot.
# Tested with 235X pixels on SAO_1, data to IO19, and configuration.led.settings =
# {'zigzag': False, 'dimension': (235,), 'apa106': False, 'neopixel_pin': 19}

import aiko.led
from binascii import unhexlify, b2a_base64
from gc import collect
from time import sleep_us, ticks_us, ticks_diff

DURATION_MS = 250
COLOURS = ["000000", "e40303", "ff8c00", "ffed00", "008026", "004dff", "750787", "000000"]
COMPENSATION_MEASUREMENTS = 5

def h2c(hex):
return tuple(v for v in unhexlify(hex))

def measure_write_time_us(write):
collect()
before_us = ticks_us()
for _ in range(0, COMPENSATION_MEASUREMENTS): write()
return ticks_diff(ticks_us(), before_us) // COMPENSATION_MEASUREMENTS

def noop(): pass

def swipe(buf, cbuf, step, callback=noop):
lcbuf = len(cbuf)
lbuf = len(buf)
for head in range(0 - lcbuf, lbuf, step):
lt = 0 - min(0, head)
rt = min(0, lbuf - (head + lcbuf))
w = lcbuf - lt + rt
buf[head + lt : head + lt + w] = cbuf[lt : lcbuf + rt]
callback()

def make_cbuf(hex_colours, colour_width=1):
ncolours = len(hex_colours)
colours = [aiko.led.apply_dim(h2c(hex)) for hex in hex_colours]
cbuf = bytearray(3 * ncolours * colour_width)
for c in range(ncolours):
colour = bytearray(colours[c])
for s in range(colour_width):
offset = (c * colour_width + s) * 3
cbuf[offset:offset+3] = colour
return memoryview(cbuf)

def initialise():
ncolours = len(COLOURS)
pixel = aiko.led.np
write = aiko.led.np.write
write_time_us = measure_write_time_us(write)
colour_width = 1
while write_time_us * (ncolours * colour_width + pixel.n) // colour_width > DURATION_MS * 1000:
colour_width += 1
cbuf = make_cbuf(COLOURS, colour_width)
expected_steps = (len(cbuf) + len(pixel.buf) + 3) // (colour_width * 3)
delay_us = max(0, DURATION_MS * 1000 // expected_steps - write_time_us)
def callback():
write()
sleep_us(delay_us)
collect()
swipe(pixel.buf, cbuf, colour_width * 3, callback)
5 changes: 5 additions & 0 deletions plugins/test2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tested = False
def initialise():
print("testing 2")
global tested
tested = True
8 changes: 8 additions & 0 deletions scripts/aiko.mpf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ md examples
md lib
md lib/aiko
# md lib/umqtt
md plugins
# md plugins/test2

exec print("### Copy applications/*.py ###")
# put applications/nodebots.py
Expand All @@ -17,6 +19,12 @@ put applications/default.py
put applications/schedule/schedule.py
put applications/swagbadge.py

exec print("### Copy plugins/*.py ###")
put plugins/__init__.py
# put plugins/auto_shake.py
# put plugins/flash_leds_on_boot.py
# put plugins/test2/__init__.py

exec print("### Copy configuration/*.py ###")
put configuration/keys.db
put configuration/led.py
Expand Down
3 changes: 3 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mkdir $RELEASE_PATHNAME/configuration
mkdir $RELEASE_PATHNAME/examples
mkdir $RELEASE_PATHNAME/lib
mkdir $RELEASE_PATHNAME/lib/aiko
mkdir $RELEASE_PATHNAME/plugins

cp applications/default.py $RELEASE_PATHNAME/applications
cp applications/schedule/schedule.py $RELEASE_PATHNAME/applications/schedule
Expand Down Expand Up @@ -51,6 +52,8 @@ cp lib/shutil.py $RELEASE_PATHNAME/lib
cp lib/ssd1306.py $RELEASE_PATHNAME/lib
cp lib/threading.py $RELEASE_PATHNAME/lib

cp plugins/__init__.py $RELEASE_PATHNAME/plugins

cp main.py $RELEASE_PATHNAME

find $RELEASE_PATHNAME -type f \( -exec md5sum {} \; -exec wc -c {} \; \) | paste - - | column -t | tr -s "[:blank:]" | cut -d" " -f1,3,4 | sort -k 3 >$MANIFEST
Expand Down