Files
lamatrix/main.py
2022-10-24 17:35:24 +02:00

204 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python
#
# This is a project to drive a 32x8 or 16x16 LED matrix based on the popular
# WS2812 RGB LEDs using a microcontroller running MicroPython (preferrably
# one with 4MB RAM although modules with 1MB also work).
#
# -- noah@hack.se, 2018
#
import sys
import time
import gc
from math import ceil
from ledmatrix import LedMatrix
# This is to make sure we have a large contiguous block of RAM on devices with
# 520kB RAM after all modules and modules have been compiled and instantiated.
#
# In the weather scene, the ussl module needs a large chunk of around 1850
# bytes, and without the dummy allocation below the heap will be too
# fragmented after all the initial processing to find such a large chunk.
large_temp_chunk = bytearray(3400)
pycom_board = False
esp8266_board = False
if hasattr(sys,'implementation') and sys.implementation.name == 'micropython':
import ujson as json
import machine
from network import WLAN
from os import uname
tmp = uname()
if tmp.sysname == 'esp8266':
esp8266_board = True
from upyhal import uPyHAL as HAL
else:
pycom_board = True
from pycomhal import PycomHAL as HAL
tmp = None
del uname
else:
import json
import os
import signal
# Kludge to allow this project to be used with a Raspberry Pi instead of
# an MCU: see https://github.com/noahwilliamsson/lamatrix/issues/1
try:
# If the rpi_ws281x Python module is available, then use that...
from raspberrypihal import RaspberryPiHAL as HAL
except exception as e:
# ...else assume that there's an MCU (driving the display) connected
# to a serial port
print('HAL: failed: {}'.format(e.args[0]))
from arduinoserialhal import ArduinoSerialHAL as HAL
gc.collect()
from renderloop import RenderLoop
def sigint_handler(sig, frame):
"""
Clear display when the program is terminated by Ctrl-C or SIGTERM
"""
global driver
driver.clear_display()
driver.set_auto_time(True)
sys.exit(0)
if __name__ == '__main__':
f = open('config.json')
config = json.loads(f.read())
f.close()
del json
# Initialize HAL
driver = HAL(config)
if not esp8266_board and not pycom_board:
# We're running on the host computer here
ports = [
'/dev/tty.usbmodem575711', # Teensy 3.x on macOS
'/dev/tty.usbserial-DQ008J7R', # Pycom device on macOS
'/dev/ttyUSB0', # Linux
'/dev/ttyACM0', # Linux
]
for port in ports:
if os.path.exists(port):
config['port'] = port
break
# Disable automatic rendering of time
driver.set_auto_time(False)
# Trap Ctrl-C and service termination
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigint_handler)
# Initialize led matrix framebuffer on top of HAL
display = LedMatrix(driver, config['LedMatrix'])
driver.clear_display()
if pycom_board:
# We're running under MCU here
from bootscene import BootScene
scene = BootScene(display, config['Boot'])
wlan = WLAN(mode=WLAN.STA)
if not wlan.isconnected():
print('WLAN: Scanning for networks')
scene.render(0,0,0)
default_ssid, default_auth = wlan.ssid(), wlan.auth()
candidates = wlan.scan()
for conf in config['networks']:
nets = [candidate for candidate in candidates if candidate.ssid == conf['ssid']]
if not nets:
continue
print('WLAN: Connecting to known network: {}'.format(nets[0].ssid))
wlan.connect(nets[0].ssid, auth=(nets[0].sec, conf['password']))
for i in range(1,40):
scene.render(i, 0, 0)
time.sleep(0.2)
if wlan.isconnected():
break
if wlan.isconnected():
break
scene.render(0, 0, 0)
if not wlan.isconnected():
# TODO: This will only use SSID/auth settings from NVRAM during cold boots
print('WLAN: No known networks, enabling AP with ssid={}, pwd={}'.format(default_ssid, default_auth[1]))
wlan.init(mode=WLAN.AP, ssid=default_ssid, auth=default_auth, channel=6)
else:
display.clear()
print('WLAN: Connected with IP: {}'.format(wlan.ifconfig()[0]))
# Initialize RTC now that we're connected
driver.set_rtc(scene)
scene.render(0,0,0)
scene = None
del BootScene
elif esp8266_board:
pass
# This is where it all begins
r = RenderLoop(display, config)
if 'Clock' in config:
from clockscene import ClockScene
scene = ClockScene(display, config['Clock'])
r.add_scene(scene)
gc.collect()
if 'Demo' in config:
from demoscene import DemoScene
scene = DemoScene(display, config['Demo'])
r.add_scene(scene)
gc.collect()
if 'Weather' in config:
from weatherscene import WeatherScene
scene = WeatherScene(display, config['Weather'])
r.add_scene(scene)
gc.collect()
if 'Fire' in config:
from firescene import FireScene
scene = FireScene(display, config['Fire'])
r.add_scene(scene)
gc.collect()
if 'Animation' in config:
from animationscene import AnimationScene
scene = AnimationScene(display, config['Animation'])
r.add_scene(scene)
gc.collect()
# Now that we're all setup, release the large chunk
large_temp_chunk = None
# Render scenes forever
while True:
# Process input
button_state = 0
if pycom_board:
# When running under MicroPython on the MCU we need to deal with
# any input coming from the host over the serial link
while True:
button_state = driver.process_input()
if driver.enable_auto_time:
# If the host disconnected we'll hand over control to the
# game loop which will take care of updating the display
break
else:
# When running under regular Python on the host computer we need
# to pick up any button presses sent over the serial link from
# the Arduino firmware
n = 0
while True:
# Drain output from MCU and detect button presses
line = driver.process_input()
if not line:
break
event = line.strip()
if event == 'LEFTB_SHRT_PRESS':
button_state = 1
elif event == 'LEFTB_LONG_PRESS':
button_state = 2
else:
print('MCU: {}'.format(event))
r.next_frame(button_state)