98 lines
3.0 KiB
Python
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)
|