Status Box
Light Box Status Page using addressable RGB LEDs to give Red/Amber/Green Status of various applications. It uses a named Status Page on an Uptime Kuma instance to configure the lights and associated monitors.
API
- Do we need to login to Uptime Kuma to access all the required endpoints?
- Using the Uptime Kuma API, the light-box can get the Status Page, which lists the monitors. The light-box then produces a mapping of monitor to LED address. API endpoint =
/api/status-page/status-shed - Using repeated polls to heartbeat endpoint any changes in the status can be identified and the appropriate LED colour updated. API endpoint =
/api/status-page/heartbeat/status-shed
Python Terminal Version
Same code, but outputs light box to the terminal, using colours. The terminal colours used the blessings library.
Microcontroller Version
Hardware
Initially trying with Pico W but may want to swicth to https://thepihut.com/products/challenger-rp2040-wifi-ble-mkii-chip-antenna or perhaps Zero 2 W, because of more computing poower and USB-C for more electrical power to LEDs.
CircuitPython Wifi Code
Based on Adafruit
import os, ipaddress, wifi, socketpool, adafruit_requests
print("Connecting to WiFi")
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
print("Connected to WiFi")
pool = socketpool.SocketPool(wifi.radio)
# prints MAC address to REPL
print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
# prints IP address to REPL
print("My IP address is", wifi.radio.ipv4_address)
ipv4 = ipaddress.ip_address("192.168.182.2")
print(f"Ping Pi1 {wifi.radio.ping(ipv4)*1000:.3f} ms")
url = "http://192.168.182.2:8888/dummystatus?minutes=5"
requests = adafruit_requests.Session(pool)
response = requests.get(url)
print("Text Response: ", response.text)Network Scan for Uptime Kuma Server
wifi.radio.ipv4_address and wifi.radio.ipv4_subnet returns IP address, netmask.
After connecting, use IP address and netmask to search for Kuma servers on port 3001,
which have a status page called status-box or status-xxxx (as the slug) where xxxx is the last 4 digits of the Raspberry Pi Pico serial number. The complication of the serial number version is to support having more than one StatusBox with different pages.
The IP addresses to scan can be dervied by using IPv4Network, but CircuitPython ipaddress module does not support IPv4Network!
Need to write my own method to iterate through local network addreses based on
print("My IP address is", wifi.radio.ipv4_address)
# My IP address is 192.168.182.229
print("My IP subnet", wifi.radio.ipv4_subnet)
# My IP subnet 255.255.255.0VS Code With Circuit Python
Based on Intro to CircuitPython
- boot Pico into file mode
- Open CIRCUITPY folder in VS Code
- Ensure correct board is selected at bottom-right.
- Open Command Palette (Shift-Ctrl-P) and type “Circuit Python: Open Serial Monitor”
Mapping grid cell to LED in order:
$ python3
Python 3.8.16 (default, Mar 2 2023, 03:21:46)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(",".join(sorted("a985173b6c52")))
1,2,3,5,5,6,7,8,9,a,b,c
>>> myMap = dict(zip("StasuTLEDxoB",range(1,13)))
>>> myMap
{'S': 1, 't': 2, 'a': 3, 's': 4, 'u': 5, 'T': 6, 'L': 7, 'E': 8, 'D': 9, 'x': 10, 'o': 11, 'B': 12}
>>> sorted(myMap.items())
[('B', 12), ('D', 9), ('E', 8), ('L', 7), ('S', 1), ('T', 6), ('a', 3), ('o', 11), ('s', 4), ('t', 2), ('u', 5), ('x', 10)]
>>> "".join([hex(n-1) for _,n in sorted(myMap.items())])
'0xb0x80x70x60x00x50x20xa0x30x10x40x9'
>>> "".join([hex(n-1) for _,n in sorted(myMap.items())]).replace("0x","")
'b876052a3149'Obsolete Info on using MicroPython
I am now using CircuitPython, so the following is no longer relevant. Just kept for reference.
Raspberry Pi Pico W version
Need to connect over Wifi, so use network.WLAN as described in Projects.RaspberryPi.org.
import network
import socket
from time import sleep
ssid = 'NAME OF YOUR WIFI NETWORK'
password = 'YOUR SECRET PASSWORD'
def connect():
#Connect to WLAN
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while wlan.isconnected() == False:
print('Waiting for connection...')
sleep(1)
# ipAddr, netmask, ipGW, ipDNS = wlan.ifconfig()
return wlan.ifconfig()
try:
connect()
except KeyboardInterrupt:
machine.reset()wlan.ifconfig() returns a list of IP address, netmask, gateway address (default router), DNS server.
After connecting, use IP address and netmask to search for Kuma servers on port 3001,
which have a status page called status-box or status-xxxx (as the slug) where xxxx is the last 4 digits of the Raspberry Pi Pico serial number. The complication of the serial number version is to support having more than one StatusBox with different pages.
The IP addresses to scan can be dervied by using IPv4Network, but CircuitPython ipaddress module does not support IPv4Network!
Need to write my own method to iterate through local network addreses based on
print("My IP address is", wifi.radio.ipv4_address)
# My IP address is 192.168.182.229
print("My IP subnet", wifi.radio.ipv4_subnet)
# My IP subnet 255.255.255.0from ipaddress import IPv4Address, IPv4Network
def main(args):
try:
myNet = IPv4Network(f"{args.addr}/{args.netmask}", strict=False)
myIp = IPv4Address(args.addr)
except Exception as ex:
print(f"{ex}")
return
print(f"Given IP={args.addr}/{args.netmask}, will scan {myNet.with_prefixlen} ",end='')
print(f"which is {myNet.num_addresses} hosts{' (less self)' if not args.scan_self else ''} on port {args.port}")
for ip in myNet:
if not args.scan_self and ip == myIp:
print(f"\nskipping own IP address of {myIp}")
else:
print(f"{ip}, ", end='', flush=True)
time.sleep(1)
print("done!")NB MicroPython on Pico supports urlllib.urequest, so I have rewritten the code to use urllib.urequest.urlopen, but it has yet to be tested on a Pico. urequest does support HTTPS, but there is some question on support of certificate verification NB documentation should note this. The docs for urequests, suggests that Whenever possible, use urllib.urequest
# using urllib.request.urlopen because it has a MicroPython equivalent
# from urllib import urequest as request
from urllib import request
import json
def getStatus():
try:
with request.urlopen("http://localhost:3001/api/status-page/status-box") as f:
data = json.load(f)
except Exception as ex:
data = {"Exception": type(ex), "mesg": repr(ex)}
return data