浏览代码

Make blinky service-ready

New features include:
 - General robustness
 - Configurable config path
 - MQTT retry
master
父节点
当前提交
3c9dd7b1d6
共有 1 个文件被更改,包括 70 次插入25 次删除
  1. +70
    -25
      blinky.py

+ 70
- 25
blinky.py 查看文件

@@ -5,7 +5,9 @@ import board
import neopixel import neopixel
import json import json
import os import os
import sys
import time import time
from pathlib import Path


import pyinotify, threading import pyinotify, threading
import paho.mqtt.subscribe as subscribe import paho.mqtt.subscribe as subscribe
@@ -16,22 +18,34 @@ from pattern import Pattern
lock_reload = threading.Lock() lock_reload = threading.Lock()
cond_reload = threading.Condition(lock_reload) cond_reload = threading.Condition(lock_reload)
reload = False reload = False
config = None
ignore_file = False
config = {}
new_config = None




# Monitor config from file # 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): def file_thread(file):
class EventHandler(pyinotify.ProcessEvent): class EventHandler(pyinotify.ProcessEvent):
def process_IN_CLOSE_WRITE(self, event): def process_IN_CLOSE_WRITE(self, event):
global reload, config
global reload, new_config, ignore_file
with lock_reload: 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() wm = pyinotify.WatchManager()
notifier = pyinotify.Notifier(wm, EventHandler()) notifier = pyinotify.Notifier(wm, EventHandler())
@@ -52,17 +66,37 @@ class mqtt:


def mqtt_thread(): def mqtt_thread():
def on_message(client, userdata, message): def on_message(client, userdata, message):
global reload, config
global reload, new_config
with lock_reload: with lock_reload:
print("Triggering MQTT reload") print("Triggering MQTT reload")
try: try:
config = json.loads(message.payload)
new_config = json.loads(message.payload)
reload = True reload = True
cond_reload.notify_all() cond_reload.notify_all()
except: except:
print("Invalid MQTT config") 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() threading.Thread(target=mqtt_thread).start()


@@ -81,27 +115,37 @@ with lock_reload:
reload = False reload = False


try: 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: except Exception as e:
print("Failed to load config:", 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") print("Waiting for valid config")
while not reload: while not reload:
cond_reload.wait() cond_reload.wait()
else: else:
target = time.time()
print("Starting animation")
last = target = time.time()
while not reload: while not reload:
lock_reload.release() lock_reload.release()


@@ -112,7 +156,8 @@ with lock_reload:
# Go ahead and sleep, maintaining maximum framerate # Go ahead and sleep, maintaining maximum framerate
target += (1 / framerate) target += (1 / framerate)
now = time.time() now = time.time()
if target > now:
time.sleep(target - now)
sleeptime = target - now
if sleeptime > 0:
time.sleep(sleeptime)


lock_reload.acquire() lock_reload.acquire()

正在加载...
取消
保存