betsy-button

Family health button
git clone https://git.woozle.org/neale/betsy-button.git

betsy-button / web
Neale Pickett  ·  2025-06-29

main.py

  1import gc
  2import json
  3import machine
  4import network
  5import requests
  6import time
  7
  8import blinker
  9
 10Millisecond = 1
 11Second = 1000 * Millisecond
 12Minute = 60 * Second
 13Hour = 60 * Minute
 14
 15black = (0, 0, 0)
 16red = (255, 0, 0)
 17green = (0, 255, 0)
 18blue = (0, 0, 255)
 19cyan = (0, 85, 160)
 20yellow = (160, 85, 0)
 21ok_colors = tuple((0, i, 0) for i in (191, 223, 255, 223))
 22
 23gc.enable()
 24
 25# Set up interrupt for pressing the button
 26do_checkin = False
 27def buttonPress(p):
 28    global do_checkin
 29    do_checkin = True
 30    blinker.set(green, green, cyan, now=True)
 31buttonPin = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
 32buttonPin.irq(buttonPress, trigger=machine.Pin.IRQ_FALLING, wake=machine.SLEEP|machine.DEEPSLEEP)
 33
 34# Set up interrupt for pressing the on-board boot button
 35running = True
 36def bootPress(p):
 37    global running
 38    running = False
 39bootPin = machine.Pin(9, machine.Pin.IN, machine.Pin.PULL_UP)
 40bootPin.irq(bootPress, trigger=machine.Pin.IRQ_FALLING, wake=machine.SLEEP|machine.DEEPSLEEP)
 41
 42# Wireless connection
 43wlan = network.WLAN(network.WLAN.IF_STA)
 44wlan.active(True)
 45wlan.config(reconnects=3)
 46def connect(config):
 47    if wlan.isconnected():
 48        return
 49    print("Trying to connect to wlan")
 50    blinker.set(cyan, cyan, cyan, black)
 51    networks = []
 52    while running and not wlan.isconnected():
 53        if not networks:
 54            networks = list(config["networks"].items())
 55        ssid, psk = networks.pop()
 56        print("  ", ssid)
 57        wlan.connect(ssid, psk)
 58        time.sleep(10)
 59    print("Connected!")
 60
 61def fetch(url, method="GET"):
 62    print(method, url, end="")
 63    code, reason, resp = -1, "Internal error", None
 64    try:
 65        if method == "GET":
 66            resp = requests.get(url)
 67        elif method == "POST":
 68            resp = requests.post(url)
 69        else:
 70            reason = "Unsupported Method: %s" % method
 71    except Exception as err:
 72        reason = err
 73
 74    if not resp:
 75        blinker.set(red, yellow, cyan, blue)
 76        print("ERR", reason)
 77    else:
 78        code = resp.status_code
 79        print(" ", resp.status_code, resp.reason.decode("ascii"))
 80    return code
 81        
 82
 83def main():
 84    global do_checkin
 85
 86    print("Loading configuration")
 87    blinker.set(yellow, black)
 88    with open("config.json") as f:
 89        config = json.load(f)
 90    network.hostname(config.get("hostname", "betsy-button"))
 91    
 92    print("Running main loop")
 93    next_update = 0
 94    while running:
 95        connect(config)
 96        now = time.time() * Second
 97
 98        if do_checkin:
 99            code = fetch(config["url"], method="POST")
100            if code == 200:
101                do_checkin = False
102                next_update = 0
103        elif now >= next_update:
104            code = fetch(config["url"])
105            if code == 200:
106                blinker.set(*ok_colors)
107                next_update = now + (2 * Hour)
108            elif code in (304, 404):
109                blinker.set(red, red, red, red, red, black, red, black)
110                next_update = now + (2 * Hour)
111            else:
112                next_update = now + (2 * Minute)
113                # Keep flashing the loading or error colors
114
115            # If the device has only been up a little while,
116            # poll more frequently.
117            # This allows users to test things out to make sure it all works.
118            if time.ticks_ms() < 5 * Minute:
119                next_update = now + (10 * Second)
120
121        gc.collect()
122        if next_update:
123            time.sleep(2)
124
125try:
126    main()
127finally:
128    print("Exiting to MicroPython REPL")
129    blinker.set((5, 5, 3))