Featureful Python controller code for WS2811/WS2812/NeoPixels
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

164 行
4.7KB

  1. #!/usr/bin/python3
  2. from pixels import TermLEDs
  3. import board
  4. import neopixel
  5. import json
  6. import os
  7. import sys
  8. import time
  9. from pathlib import Path
  10. import pyinotify, threading
  11. import paho.mqtt.subscribe as subscribe
  12. from pattern import Pattern
  13. lock_reload = threading.Lock()
  14. cond_reload = threading.Condition(lock_reload)
  15. reload = False
  16. ignore_file = False
  17. config = {}
  18. new_config = None
  19. # Monitor config from file
  20. if len(sys.argv) > 1:
  21. config_file = sys.argv[1]
  22. else:
  23. config_file = "blinky.json"
  24. if not Path(config_file).is_file():
  25. print("WARNING: Config file does not exist:", config_file)
  26. def file_thread(file):
  27. class EventHandler(pyinotify.ProcessEvent):
  28. def process_IN_CLOSE_WRITE(self, event):
  29. global reload, new_config, ignore_file
  30. with lock_reload:
  31. if ignore_file:
  32. print("Ignoring file change")
  33. ignore_file = False
  34. else:
  35. print("Triggering file reload")
  36. new_config = None
  37. reload = True
  38. cond_reload.notify_all()
  39. wm = pyinotify.WatchManager()
  40. notifier = pyinotify.Notifier(wm, EventHandler())
  41. wdd = wm.add_watch(file, pyinotify.IN_CLOSE_WRITE)
  42. notifier.loop()
  43. threading.Thread(target=file_thread, args=[config_file]).start()
  44. # Monitor config from MQTT
  45. class mqtt:
  46. topic = "blinky/config"
  47. hostname = "mqtt.jrhoffa.com"
  48. auth = {'username':"blinky", 'password':"rainbow"}
  49. tls = {'ca_certs':"/etc/ssl/certs/ca-certificates.crt"}
  50. def mqtt_thread():
  51. def on_message(client, userdata, message):
  52. global reload, new_config
  53. with lock_reload:
  54. print("Triggering MQTT reload")
  55. try:
  56. new_config = json.loads(message.payload)
  57. reload = True
  58. cond_reload.notify_all()
  59. except:
  60. print("Invalid MQTT config")
  61. global reload, new_config
  62. while True:
  63. print(f"Subscribing to MQTT topic {mqtt.topic}")
  64. try:
  65. message = subscribe.simple(mqtt.topic, hostname=mqtt.hostname, auth=mqtt.auth, port=8883, tls=mqtt.tls)
  66. except Exception as e:
  67. print("MQTT subscription error:", e)
  68. message = None
  69. if not message:
  70. print("Subscription failed - retrying")
  71. time.sleep(1)
  72. else:
  73. with lock_reload:
  74. print("Triggering MQTT reload")
  75. try:
  76. new_config = json.loads(message.payload)
  77. reload = True
  78. cond_reload.notify_all()
  79. except:
  80. print("Invalid MQTT config")
  81. threading.Thread(target=mqtt_thread).start()
  82. ### Entry Point ##
  83. framerate = 30
  84. length = None
  85. patterns = None
  86. pixels = None
  87. with lock_reload:
  88. while True:
  89. print("Loading config")
  90. reload = False
  91. try:
  92. # If it wasn't delivered, load from file
  93. new_config = new_config or json.load(open(config_file, 'r'))
  94. if 'length' in new_config:
  95. new_length = new_config['length']
  96. if length != new_length:
  97. length = new_length
  98. config['length'] = length
  99. pixels = neopixel.NeoPixel(board.D18, length, pixel_order=neopixel.RGB, auto_write=False)
  100. #pixels = TermLEDs(length)
  101. print("String configured")
  102. patterns = [(pattern.get('length', length), Pattern.from_dict(pattern)) for pattern in new_config['patterns']]
  103. config['patterns'] = new_config['patterns']
  104. print("Pattern configured")
  105. except Exception as e:
  106. print("Failed to load config:", e)
  107. finally:
  108. print("Saving config")
  109. ignore_file = True
  110. json.dump(config, open(config_file, 'w'))
  111. if not length or not pixels or not patterns:
  112. print("Waiting for valid config")
  113. while not reload:
  114. cond_reload.wait()
  115. else:
  116. print("Starting animation")
  117. last = target = time.time()
  118. while not reload:
  119. lock_reload.release()
  120. # Generate and show new light pattern
  121. pixels[:] = [x for y in (pattern.step(length) for (length, pattern) in patterns) for x in y]
  122. pixels.show()
  123. # Go ahead and sleep, maintaining maximum framerate
  124. target += (1 / framerate)
  125. now = time.time()
  126. sleeptime = target - now
  127. if sleeptime > 0:
  128. time.sleep(sleeptime)
  129. lock_reload.acquire()