Files
PolterHID/settings.py
T

98 lines
3.0 KiB
Python

"""Persistent configuration for PolterHID.
`settings.json` is deliberately kept outside the source tree and is created on
first boot. The web password is stored in clear text because TLS is explicitly
not used; use this only on a trusted training Wi-Fi network.
"""
try:
import ujson as json
except ImportError:
import json
DEFAULTS = {
"wifi_ssid": "CHANGE_ME",
"wifi_password": "CHANGE_ME",
"web_password": "change-this-before-deploying",
# Set false when validating on a board without the T-Dongle-S3 display.
"display_enabled": False,
"enabled": True,
"return_enabled": True,
"mouse_enabled": True,
"mouse_distance": 1,
"return_min_seconds": 45,
"return_max_seconds": 120,
"mouse_min_seconds": 20,
"mouse_max_seconds": 60,
}
SETTINGS_FILE = "settings.json"
def load():
"""Return validated settings, creating the file on first boot."""
values = DEFAULTS.copy()
try:
with open(SETTINGS_FILE, "r") as file:
stored = json.load(file)
if isinstance(stored, dict):
for key in DEFAULTS:
if key in stored:
values[key] = stored[key]
except (OSError, ValueError):
save(values)
_validate(values)
return values
def save(values):
"""Atomically replace the settings file where the port supports rename."""
_validate(values)
temporary = SETTINGS_FILE + ".tmp"
with open(temporary, "w") as file:
json.dump(values, file)
try:
import os
os.remove(SETTINGS_FILE)
except OSError:
pass
import os
os.rename(temporary, SETTINGS_FILE)
def apply_form(values, form):
"""Update configurable runtime values from a decoded HTTP form."""
for key in ("enabled", "return_enabled", "mouse_enabled"):
values[key] = form.get(key) == "on"
for key in (
"return_min_seconds", "return_max_seconds",
"mouse_min_seconds", "mouse_max_seconds", "mouse_distance",
):
if key in form:
try:
values[key] = int(form[key])
except (TypeError, ValueError):
raise ValueError("%s must be a whole number" % key)
_validate(values)
def _validate(values):
for key in ("wifi_ssid", "wifi_password", "web_password"):
if not isinstance(values.get(key), str) or not values[key]:
raise ValueError("%s must be a non-empty string" % key)
distance = values["mouse_distance"]
if not isinstance(distance, int) or distance < 1 or distance > 127:
raise ValueError("mouse distance must be between 1 and 127 pixels")
for prefix in ("return", "mouse"):
minimum = values[prefix + "_min_seconds"]
maximum = values[prefix + "_max_seconds"]
if not isinstance(minimum, int) or not isinstance(maximum, int):
raise ValueError("%s intervals must be integers" % prefix)
if minimum < 1 or maximum < minimum or maximum > 86400:
raise ValueError("invalid %s interval" % prefix)