157 lines
4.4 KiB
Python
157 lines
4.4 KiB
Python
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)
|