Compare commits

...

5 Commits

14 changed files with 141 additions and 22 deletions

8
.gitignore vendored
View File

@ -6,6 +6,8 @@
# General # General
/_tmp /_tmp
/.upload_cache /.upload_cache
*_bak
*_bak[0-9]
# Python # Python
__pycache__ __pycache__
@ -18,8 +20,12 @@ __pycache__
*.mpy *.mpy
# Configuration files # Configuration files
/Makefile.env
/src/lightbar_cfg.py
/src/webrepl_cfg.py /src/webrepl_cfg.py
/src/wlan_cfg.py /src/wlan_cfg.py
# FreeCAD backups # 3D modeling temporary files (FreeCAD backups, Cura projects, G-code files)
*.FCStd1 *.FCStd1
*.3mf
*.gcode

View File

@ -1,16 +1,19 @@
# Configuration # Configuration defaults (please use Makefile.env to override these, see Makefile.env.example)
LIGHTBAR_HOST ?= 192.168.17.22 LIGHTBAR_HOST = 192.168.13.12
LIGHTBAR_URL := http://$(LIGHTBAR_HOST) LIGHTBAR_URL = http://$(LIGHTBAR_HOST)
WEBREPL_CLI := webrepl_cli.py WEBREPL_CLI = webrepl_cli.py
WEBREPL_HOST := $(LIGHTBAR_HOST) WEBREPL_HOST = $(LIGHTBAR_HOST)
WEBREPL_PASSWORD ?= acab WEBREPL_PASSWORD = ultra-secret-webrepl-password
REPL_TTY_PATH ?= /dev/ttyUSB0 REPL_TTY_PATH = /dev/ttyUSB0
# Include local config
-include Makefile.env
# Directory for saving timestamps of when files were last uploaded # Directory for saving timestamps of when files were last uploaded
UPLOAD_CACHE_DIR := .upload_cache UPLOAD_CACHE_DIR := .upload_cache
# Auto-detect all .py files that should be uploaded # Auto-detect all .py files that should be uploaded
SRC_UPLOAD_TARGETS := $(patsubst %.py,$(UPLOAD_CACHE_DIR)/%.py.timestamp,$(wildcard src/*.py src/**/*.py)) SRC_UPLOAD_TARGETS := $(patsubst %.py,$(UPLOAD_CACHE_DIR)/%.py.timestamp,$(wildcard src/*.py src/**/*.py src/**/**/*.py))
LIB_UPLOAD_TARGETS := $(patsubst %.py,$(UPLOAD_CACHE_DIR)/%.py.timestamp,$(wildcard lib/*.py)) LIB_UPLOAD_TARGETS := $(patsubst %.py,$(UPLOAD_CACHE_DIR)/%.py.timestamp,$(wildcard lib/*.py))
# Default target # Default target
@ -52,7 +55,7 @@ $(UPLOAD_CACHE_DIR)/%.py.timestamp :: %.py
repl-create-directories: repl-create-directories:
@echo "(Note: If the directory already exists, rshell will print \"Unable to create [DIR]\")" @echo "(Note: If the directory already exists, rshell will print \"Unable to create [DIR]\")"
@echo @echo
rshell -p $(REPL_TTY_PATH) mkdir /pyboard/lib /pyboard/lightbar rshell -p $(REPL_TTY_PATH) mkdir /pyboard/lib /pyboard/lightbar /pyboard/lightbar/endpoints
# Clear the .upload_cache directory that contains the last upload timestamps # Clear the .upload_cache directory that contains the last upload timestamps
.PHONY: clear-upload-cache clear-upload-cache-src .PHONY: clear-upload-cache clear-upload-cache-src

11
Makefile.env.example Normal file
View File

@ -0,0 +1,11 @@
# This is an example file.
# Copy this to Makefile.env and adjust it to the IP and password of your lightbar.
# IP address or hostname of your lightbar
LIGHTBAR_HOST := 192.168.13.12
# Password for WebREPL as set in webrepl_cfg.py (used to transfer files to the ESP32)
WEBREPL_PASSWORD := ultra-secret-webrepl-password
# Device path to the serial port of the ESP32 when connected via USB
REPL_TTY_PATH := /dev/ttyUSB0

10
examples/lightbar_cfg.py Normal file
View File

@ -0,0 +1,10 @@
# Example file for Lightbar configuration. Copy to src/lightbar_cfg.py, adjust and upload to the board.
# Port to start the HTTP server on
SERVER_PORT = 80
# Whether the HTTP server should be started in debug mode
SERVER_DEBUG = False
# Amount of LEDs in the lightbar
NEOPIXEL_COUNT = 28

Binary file not shown.

Binary file not shown.

6
misc/chip_pinout.txt Normal file
View File

@ -0,0 +1,6 @@
Board: ESP32 NodeMCU
Pins:
- Neopixels: GPIO26
- Status LED: GPIO4
- Button: GPIO21 (against GND)

View File

@ -1,10 +1,12 @@
# boot.py -- This file is executed on every boot (including wake-boot from deepsleep) # boot.py -- This file is executed on every boot (including wake-boot from deepsleep)
from lightbar import wlan_manager import uasyncio
import webrepl import webrepl
# Connect to WLAN as defined in wlan_cfg.py from lightbar import wlan_manager
wlan_manager.connect()
# Asynchronously connect to WLAN as defined in wlan_cfg.py
uasyncio.create_task(wlan_manager.connect_async())
# Start WebREPL on default port 8266 with password defined in webrepl_cfg.PASS # Start WebREPL on default port 8266 with password defined in webrepl_cfg.PASS
webrepl.start() webrepl.start()

View File

@ -0,0 +1,43 @@
import uasyncio
from machine import Pin
from neopixel import NeoPixel
# Constants
NEOPIXEL_PIN = 26
class LightbarController:
"""
Controls the LEDs using the NeoPixel library.
"""
neopixel_count: int
neopixel: NeoPixel
def __init__(self, *, neopixel_count: int):
print('[LightbarController] Initializing Neopixels')
self.neopixel_count = neopixel_count
self.neopixel = NeoPixel(Pin(NEOPIXEL_PIN), neopixel_count)
def start(self):
uasyncio.create_task(self.run_loop())
async def run_loop(self):
print('[LightbarController] Turning on Neopixels')
np = self.neopixel
np.fill((0, 0, 0))
color_set = [
(255, 0, 0),
(255, 255, 0),
(0, 255, 0),
(0, 255, 255),
(0, 0, 255),
(255, 0, 255),
]
while True:
for i in range(self.neopixel_count):
np[i] = color_set[i % len(color_set)]
np.write()
await uasyncio.sleep(0.5)

View File

@ -2,11 +2,14 @@ import machine
import uasyncio import uasyncio
from microdot_asyncio import Microdot from microdot_asyncio import Microdot
from lightbar.frontend import frontend from lightbar.endpoints.frontend import frontend
from lightbar.rest_api import rest_api from lightbar.endpoints.rest_api import rest_api
class Lightbar(Microdot): class LightbarServer(Microdot):
"""
Runs the HTTP server to control the lightbar.
"""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.mount(frontend) self.mount(frontend)

View File

@ -1,6 +1,8 @@
import network
import time import time
import network
from machine import Pin
def connect() -> network.WLAN: def connect() -> network.WLAN:
""" """
@ -19,13 +21,21 @@ def connect() -> network.WLAN:
wlan = network.WLAN(network.STA_IF) wlan = network.WLAN(network.STA_IF)
wlan.active(True) wlan.active(True)
status_led = Pin(4, Pin.OUT)
status_led.off()
if not wlan.isconnected(): if not wlan.isconnected():
wlan.connect(wlan_cfg.SSID, wlan_cfg.PASSWORD) wlan.connect(wlan_cfg.SSID, wlan_cfg.PASSWORD)
print('[wlan_manager] Connecting to WLAN "{}" '.format(wlan_cfg.SSID), end='') print('[wlan_manager] Connecting to WLAN "{}" '.format(wlan_cfg.SSID), end='')
while not wlan.isconnected(): while not wlan.isconnected():
print('.', end='') print('.', end='')
time.sleep(1)
# Let the LED blink to indicate the connection attempt
status_led.on()
time.sleep(0.5)
status_led.off()
time.sleep(0.5)
print('\n[wlan_manager] Connected!') print('\n[wlan_manager] Connected!')
else: else:
print('[wlan_manager] Already connected to WLAN "{}"'.format(wlan.config('essid'))) print('[wlan_manager] Already connected to WLAN "{}"'.format(wlan.config('essid')))
@ -35,4 +45,16 @@ def connect() -> network.WLAN:
wlan.ifconfig(wlan_cfg.IFCONFIG) wlan.ifconfig(wlan_cfg.IFCONFIG)
print('[wlan_manager] IP config: {}'.format(wlan.ifconfig())) print('[wlan_manager] IP config: {}'.format(wlan.ifconfig()))
# Let the LED blink 2 times quickly to indicate that the WLAN is connected
for _ in range(2):
time.sleep(0.125)
status_led.on()
time.sleep(0.125)
status_led.off()
return wlan return wlan
async def connect_async():
connect()

View File

@ -1,5 +1,18 @@
from lightbar.app import Lightbar from lightbar.lightbar_controller import LightbarController
from lightbar.lightbar_server import LightbarServer
print('[main] Starting lightbar HTTP server...') try:
api_server = Lightbar() import lightbar_cfg
api_server.run(port=80, debug=True) except Exception as e:
print('[main] Could not import lightbar_cfg.py: {}'.format(str(e)))
raise e
print('[main] Starting lightbar controller')
lightbar_controller = LightbarController(neopixel_count=lightbar_cfg.NEOPIXEL_COUNT)
lightbar_controller.start()
print('[main] Starting lightbar HTTP server')
api_server = LightbarServer()
api_server.run(port=lightbar_cfg.SERVER_PORT, debug=lightbar_cfg.SERVER_DEBUG)
print('[main] HTTP server shut down! End of main')