APP_DIR = "/system/apps/visitor_stats" import sys import os os.chdir(APP_DIR) sys.path.insert(0, APP_DIR) from badgeware import run, State, rtc, set_brightness, clamp import wifi import urequests # Enable RTC interrupts for hourly refresh rtc.enable_timer_interrupt(True) rtc.set_timer(150) class AppState: Display = 0 ConnectWiFi = 1 FetchData = 2 Error = 3 # Default state state = { "total_hits": 0, "unique_visitors": 0, "last_updated": "Never", "font": "ignore", "brightness": 0.1, } State.load("visitor_stats", state) set_brightness(state["brightness"]) font_list = dir(rom_font) font_index = font_list.index(state["font"]) app_state = AppState.Display error_message = "" API_URL = "https://api.ritual.sh/analytics/stats" def fetch_stats(): try: print(f"Fetching from {API_URL}") response = urequests.get(API_URL) print(f"Response status: {response.status_code}") if response.status_code == 200: data = response.json() print(f"Data received: {data}") response.close() return data response.close() except Exception as e: print(f"Fetch error: {e}") return None def format_number(n): if n >= 1_000_000: value = n / 1000000 return f"{value:.1f}".rstrip("0").rstrip(".") + "m" elif n >= 1_000: value = n / 1000 return f"{value:.1f}".rstrip("0").rstrip(".") + "k" else: return str(n) def draw_display(): # Pen variables screen.pen = color.white ## Variables visitor_count = format_number(state["total_hits"]) unique_count = format_number(state["unique_visitors"]) + "d" screen.font = rom_font.troll visitor_size = screen.measure_text(visitor_count) visitor_x = (screen.width - visitor_size[0]) // 2 screen.text(visitor_count, vec2(visitor_x, -4)) screen.font = rom_font.desert unique_size = screen.measure_text(unique_count) unique_x = (screen.width - unique_size[0]) // 2 screen.text(unique_count, vec2(unique_x, visitor_size[1] - 10)) def update(): """Main update loop.""" global app_state, state, error_message, font_index wifi.tick() # Brightness controls if io.BUTTON_UP in io.pressed: state["brightness"] += 0.1 if io.BUTTON_DOWN in io.pressed: state["brightness"] -= 0.1 state["brightness"] = clamp(state["brightness"], 0.1, 1.0) set_brightness(state["brightness"]) # Manual refresh with button B if io.BUTTON_B in io.pressed: print("Button B pressed - manual refresh") app_state = AppState.ConnectWiFi if rtc.read_timer_flag(): print("RTC timer fired - auto refresh") rtc.clear_timer_flag() rtc.set_timer(150) app_state = AppState.ConnectWiFi # State machine if app_state == AppState.Display: draw_display() elif app_state == AppState.ConnectWiFi: draw_display() print("Connecting to WiFi...") if wifi.connect(): print("WiFi connected") app_state = AppState.FetchData else: # WiFi failed - silently revert to display with old data print("WiFi connection failed - keeping old data") app_state = AppState.Display elif app_state == AppState.FetchData: draw_display() print("WiFi connected, fetching data...") data = fetch_stats() print(f"Result: {data}") if data: state["total_hits"] = data.get("totalHits", 0) state["unique_visitors"] = data.get("uniqueVisitors", 0) # Extract time from timestamp (format: "2026-01-29 15:33:29") ts = data.get("lastUpdated", "") if " " in ts: state["last_updated"] = ts.split(" ")[1][:5] # HH:MM else: state["last_updated"] = "--:--" print(f"Updated state: {state}") State.save("visitor_stats", state) else: # Fetch failed - silently revert to display with old data print("Fetch failed - keeping old data") app_state = AppState.Display elif app_state == AppState.Error: # Any button press returns to display if io.pressed: app_state = AppState.Display def on_exit(): """Save state when exiting.""" State.save("visitor_stats", state) if __name__ == "__main__": run(update=update, on_exit=on_exit)