|
|
|
@@ -5,7 +5,9 @@ import board |
|
|
|
import neopixel |
|
|
|
import json |
|
|
|
import os |
|
|
|
import sys |
|
|
|
import time |
|
|
|
from pathlib import Path |
|
|
|
|
|
|
|
import pyinotify, threading |
|
|
|
import paho.mqtt.subscribe as subscribe |
|
|
|
@@ -16,22 +18,34 @@ from pattern import Pattern |
|
|
|
lock_reload = threading.Lock() |
|
|
|
cond_reload = threading.Condition(lock_reload) |
|
|
|
reload = False |
|
|
|
config = None |
|
|
|
ignore_file = False |
|
|
|
config = {} |
|
|
|
new_config = None |
|
|
|
|
|
|
|
|
|
|
|
# Monitor config from file |
|
|
|
|
|
|
|
config_file = "blinky.json" |
|
|
|
if len(sys.argv) > 1: |
|
|
|
config_file = sys.argv[1] |
|
|
|
else: |
|
|
|
config_file = "blinky.json" |
|
|
|
|
|
|
|
if not Path(config_file).is_file(): |
|
|
|
print("WARNING: Config file does not exist:", config_file) |
|
|
|
|
|
|
|
def file_thread(file): |
|
|
|
class EventHandler(pyinotify.ProcessEvent): |
|
|
|
def process_IN_CLOSE_WRITE(self, event): |
|
|
|
global reload, config |
|
|
|
global reload, new_config, ignore_file |
|
|
|
with lock_reload: |
|
|
|
print("Triggering file reload") |
|
|
|
config = None |
|
|
|
reload = True |
|
|
|
cond_reload.notify_all() |
|
|
|
if ignore_file: |
|
|
|
print("Ignoring file change") |
|
|
|
ignore_file = False |
|
|
|
else: |
|
|
|
print("Triggering file reload") |
|
|
|
new_config = None |
|
|
|
reload = True |
|
|
|
cond_reload.notify_all() |
|
|
|
|
|
|
|
wm = pyinotify.WatchManager() |
|
|
|
notifier = pyinotify.Notifier(wm, EventHandler()) |
|
|
|
@@ -52,17 +66,37 @@ class mqtt: |
|
|
|
|
|
|
|
def mqtt_thread(): |
|
|
|
def on_message(client, userdata, message): |
|
|
|
global reload, config |
|
|
|
global reload, new_config |
|
|
|
with lock_reload: |
|
|
|
print("Triggering MQTT reload") |
|
|
|
try: |
|
|
|
config = json.loads(message.payload) |
|
|
|
new_config = json.loads(message.payload) |
|
|
|
reload = True |
|
|
|
cond_reload.notify_all() |
|
|
|
except: |
|
|
|
print("Invalid MQTT config") |
|
|
|
|
|
|
|
subscribe.callback(on_message, mqtt.topic, hostname=mqtt.hostname, auth=mqtt.auth, port=8883, tls=mqtt.tls) |
|
|
|
global reload, new_config |
|
|
|
while True: |
|
|
|
print(f"Subscribing to MQTT topic {mqtt.topic}") |
|
|
|
try: |
|
|
|
message = subscribe.simple(mqtt.topic, hostname=mqtt.hostname, auth=mqtt.auth, port=8883, tls=mqtt.tls) |
|
|
|
except Exception as e: |
|
|
|
print("MQTT subscription error:", e) |
|
|
|
message = None |
|
|
|
if not message: |
|
|
|
print("Subscription failed - retrying") |
|
|
|
time.sleep(1) |
|
|
|
else: |
|
|
|
with lock_reload: |
|
|
|
print("Triggering MQTT reload") |
|
|
|
try: |
|
|
|
new_config = json.loads(message.payload) |
|
|
|
reload = True |
|
|
|
cond_reload.notify_all() |
|
|
|
except: |
|
|
|
print("Invalid MQTT config") |
|
|
|
|
|
|
|
|
|
|
|
threading.Thread(target=mqtt_thread).start() |
|
|
|
|
|
|
|
@@ -81,27 +115,37 @@ with lock_reload: |
|
|
|
reload = False |
|
|
|
|
|
|
|
try: |
|
|
|
config = config or json.load(open(config_file)) |
|
|
|
|
|
|
|
new_length = config['length'] |
|
|
|
|
|
|
|
if length != new_length: |
|
|
|
print("New string config") |
|
|
|
length = new_length |
|
|
|
pixels = neopixel.NeoPixel(board.D18, length, pixel_order=neopixel.RGB, auto_write=False) |
|
|
|
#pixels = TermLEDs(length) |
|
|
|
|
|
|
|
patterns = [(pattern['length'], Pattern.from_dict(pattern)) for pattern in config['patterns']] |
|
|
|
# If it wasn't delivered, load from file |
|
|
|
new_config = new_config or json.load(open(config_file, 'r')) |
|
|
|
|
|
|
|
if 'length' in new_config: |
|
|
|
new_length = new_config['length'] |
|
|
|
if length != new_length: |
|
|
|
length = new_length |
|
|
|
config['length'] = length |
|
|
|
pixels = neopixel.NeoPixel(board.D18, length, pixel_order=neopixel.RGB, auto_write=False) |
|
|
|
#pixels = TermLEDs(length) |
|
|
|
print("String configured") |
|
|
|
|
|
|
|
patterns = [(pattern.get('length', length), Pattern.from_dict(pattern)) for pattern in new_config['patterns']] |
|
|
|
config['patterns'] = new_config['patterns'] |
|
|
|
print("Pattern configured") |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
print("Failed to load config:", e) |
|
|
|
|
|
|
|
if not pixels or not patterns: |
|
|
|
finally: |
|
|
|
print("Saving config") |
|
|
|
ignore_file = True |
|
|
|
json.dump(config, open(config_file, 'w')) |
|
|
|
|
|
|
|
if not length or not pixels or not patterns: |
|
|
|
print("Waiting for valid config") |
|
|
|
while not reload: |
|
|
|
cond_reload.wait() |
|
|
|
else: |
|
|
|
target = time.time() |
|
|
|
print("Starting animation") |
|
|
|
last = target = time.time() |
|
|
|
while not reload: |
|
|
|
lock_reload.release() |
|
|
|
|
|
|
|
@@ -112,7 +156,8 @@ with lock_reload: |
|
|
|
# Go ahead and sleep, maintaining maximum framerate |
|
|
|
target += (1 / framerate) |
|
|
|
now = time.time() |
|
|
|
if target > now: |
|
|
|
time.sleep(target - now) |
|
|
|
sleeptime = target - now |
|
|
|
if sleeptime > 0: |
|
|
|
time.sleep(sleeptime) |
|
|
|
|
|
|
|
lock_reload.acquire() |