Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions micro/app_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
# -- Runtime Flags ---
PRINTER_AWAITING_PLATE_CLEAR = False
PENDING_BUTTON_PRESS = False
BUTTON_PRESS_NEEDS_RECONCILIATION = False
BUTTON_PRESS_RETRY_AT_MS = None
CHAMBER_LIGHT_IS_ON = True
PRINTER_STATUS_UPDATE_REQUIRED = True
network = None
BUTTON_PRESS_RETRY_DELAY_MS = 5_000
AP_RETRY_INTERVAL_MS = wifi.AP_RETRY_INTERVAL_SECONDS * 1000


def should_flash_connection_failure():
Expand All @@ -27,7 +31,7 @@ def should_flash_connection_failure():


def should_flash_plate_clear():
return PRINTER_AWAITING_PLATE_CLEAR and not PENDING_BUTTON_PRESS
return PRINTER_AWAITING_PLATE_CLEAR or PENDING_BUTTON_PRESS


# Feed this from the main loop and during each bounded Wi-Fi attempt/backoff.
Expand All @@ -49,12 +53,14 @@ def should_flash_plate_clear():
def IRQ_button_press(pin):
global PENDING_BUTTON_PRESS
global PRINTER_AWAITING_PLATE_CLEAR
global BUTTON_PRESS_NEEDS_RECONCILIATION, BUTTON_PRESS_RETRY_AT_MS

# This runs in the hardware interrupt. Keep it allocation-free so the LED
# stops flashing even while a status request is still timing out.
# Keep the interrupt allocation-free and retain one intent until confirmed.
if PRINTER_AWAITING_PLATE_CLEAR:
PRINTER_AWAITING_PLATE_CLEAR = False
PENDING_BUTTON_PRESS = True
BUTTON_PRESS_NEEDS_RECONCILIATION = False
BUTTON_PRESS_RETRY_AT_MS = None


button = gpio_button.GPIOButton(
Expand Down Expand Up @@ -87,6 +93,7 @@ def IRQ_button_press(pin):
if network.is_ap_mode():
print("Wi-Fi unavailable; connect to the setup access point to update settings")
flasher.on()
last_ap_retry_ms = time.ticks_ms()

# -- Initialize API client --
api = bambuddy_api.BambuddyAPI(
Expand Down Expand Up @@ -121,28 +128,75 @@ def with_network_connection(request):


def handle_pending_button_press():
global PENDING_BUTTON_PRESS
global PRINTER_STATUS_UPDATE_REQUIRED
global PENDING_BUTTON_PRESS, BUTTON_PRESS_NEEDS_RECONCILIATION
global BUTTON_PRESS_RETRY_AT_MS
global PRINTER_AWAITING_PLATE_CLEAR, PRINTER_STATUS_UPDATE_REQUIRED

if not PENDING_BUTTON_PRESS:
return
if (
BUTTON_PRESS_RETRY_AT_MS is not None
and time.ticks_diff(BUTTON_PRESS_RETRY_AT_MS, time.ticks_ms()) > 0
):
return

if not printer_api_ready:
PENDING_BUTTON_PRESS = False
return

try:
with_network_connection(
lambda: api.clear_plate(config["printer"]["id"])
if BUTTON_PRESS_NEEDS_RECONCILIATION:
response = with_network_connection(
lambda: api.get_printer_status(config["printer"]["id"])
)
apply_printer_status(response)
BUTTON_PRESS_NEEDS_RECONCILIATION = False
if not PENDING_BUTTON_PRESS:
return

outcome = with_network_connection(
lambda: api.clear_plate_with_reconciliation(config["printer"]["id"])
)
if outcome["status"] is not None:
apply_printer_status(outcome["status"])
if outcome["resolved"]:
PENDING_BUTTON_PRESS = False
PRINTER_AWAITING_PLATE_CLEAR = False
BUTTON_PRESS_NEEDS_RECONCILIATION = False
BUTTON_PRESS_RETRY_AT_MS = None
PRINTER_STATUS_UPDATE_REQUIRED = outcome["status"] is None
return

print("Plate-clear request was not applied:", outcome["request_error"])
BUTTON_PRESS_RETRY_AT_MS = time.ticks_add(
time.ticks_ms(), BUTTON_PRESS_RETRY_DELAY_MS
)
PRINTER_STATUS_UPDATE_REQUIRED = True

except Exception as exc:
print("Failed to send plate clear request:", exc)
BUTTON_PRESS_NEEDS_RECONCILIATION = True
BUTTON_PRESS_RETRY_AT_MS = time.ticks_add(
time.ticks_ms(), BUTTON_PRESS_RETRY_DELAY_MS
)
PRINTER_STATUS_UPDATE_REQUIRED = True


PENDING_BUTTON_PRESS = False
def apply_printer_status(response):
global PRINTER_AWAITING_PLATE_CLEAR, CHAMBER_LIGHT_IS_ON
global PENDING_BUTTON_PRESS, BUTTON_PRESS_NEEDS_RECONCILIATION

PRINTER_AWAITING_PLATE_CLEAR = response["awaiting_plate_clear"]
CHAMBER_LIGHT_IS_ON = response["chamber_light"]
if PENDING_BUTTON_PRESS:
if PRINTER_AWAITING_PLATE_CLEAR:
BUTTON_PRESS_NEEDS_RECONCILIATION = False
else:
PENDING_BUTTON_PRESS = False
BUTTON_PRESS_NEEDS_RECONCILIATION = False


def handle_printer_status_update():
global PRINTER_AWAITING_PLATE_CLEAR, PRINTER_STATUS_UPDATE_REQUIRED
global CHAMBER_LIGHT_IS_ON
global PRINTER_STATUS_UPDATE_REQUIRED

if not printer_api_ready:
PRINTER_STATUS_UPDATE_REQUIRED = False
Expand All @@ -152,8 +206,7 @@ def handle_printer_status_update():
response = with_network_connection(
lambda: api.get_printer_status(config["printer"]["id"])
)
PRINTER_AWAITING_PLATE_CLEAR = response["awaiting_plate_clear"]
CHAMBER_LIGHT_IS_ON = response["chamber_light"]
apply_printer_status(response)

print(
"Printer awaiting plate clear:",
Expand Down Expand Up @@ -212,6 +265,14 @@ def debug_status():

restart_requested = web_server is not None and web_server.poll()

if (
network.is_ap_mode()
and time.ticks_diff(time.ticks_ms(), last_ap_retry_ms)
>= AP_RETRY_INTERVAL_MS
):
network.retry_station_from_access_point(watchdog_feed=watchdog.feed)
last_ap_retry_ms = time.ticks_ms()

# Push button press to API if pending
if not restart_requested and not network.is_ap_mode() and PENDING_BUTTON_PRESS:
handle_pending_button_press()
Expand Down
20 changes: 20 additions & 0 deletions micro/bambuddy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ def printer_is_awaiting_plate_clear(self, printer_id):
def clear_plate(self, printer_id):
return self._post(f"printers/{printer_id}/clear-plate")

def clear_plate_with_reconciliation(self, printer_id):
"""Keep a failed clear pending unless printer status proves it landed."""
try:
self.clear_plate(printer_id)
return {"resolved": True, "status": None, "request_error": None}
except Exception as request_error:
status = self.get_printer_status(printer_id)
if (
not isinstance(status, dict)
or not isinstance(status.get("awaiting_plate_clear"), bool)
):
raise BambuddyAPIError(
"Could not verify plate-clear request outcome"
)
return {
"resolved": not status["awaiting_plate_clear"],
"status": status,
"request_error": request_error,
}

def chamber_light_is_lit(self, printer_id):
status = self.get_printer_status(printer_id)
return status.get("chamber_light", False)
24 changes: 24 additions & 0 deletions micro/wifi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

DEFAULT_HOSTNAME = "bambutton"
RETRY_DELAY_SECONDS = 10
AP_RETRY_INTERVAL_SECONDS = 30
DEFAULT_AP_PASSWORD = "bambutton"
STA_IF = getattr(network, "STA_IF", getattr(network.WLAN, "IF_STA", 0))
AP_IF = getattr(network, "AP_IF", getattr(network.WLAN, "IF_AP", 1))
Expand Down Expand Up @@ -82,6 +83,29 @@ def ensure_connected(self, watchdog_feed=None, fallback_to_ap=False):
return self.connect_with_fallback(watchdog_feed=watchdog_feed)
return self.connect_forever(watchdog_feed=watchdog_feed)

def retry_station_from_access_point(self, watchdog_feed=None):
if not self.is_ap_mode():
return self.is_connected()

access_point = self.ap
try:
station = self.connect(watchdog_feed=watchdog_feed)
except Exception as exc:
print("Wi-Fi station retry failed:", exc)
try:
self.disconnect()
except Exception:
pass
return access_point

try:
access_point.active(False)
except Exception as exc:
print("Could not disable setup access point:", exc)
return access_point
self.ap = None
return station

def disconnect(self):
self.wlan.disconnect()

Expand Down
Loading
Loading