Some checks failed
Build RPi Pico firmware image / Build-Firmware (push) Successful in 4m43s
Check code formatting / Check-C-Format (push) Successful in 36m15s
Check code formatting / Check-Python-Flake8 (push) Failing after 10s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 5s
Run unit tests on host / Run-Unit-Tests (push) Successful in 9s
Run pytests / Check-Pytest (push) Successful in 11s
Allow choosing between the three security modes exposed by the micropython cyw43 wifi driver. Also allow setting up security in AP mode. Improve the WiFi section of the configuration UI. Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
139 lines
4.4 KiB
Python
139 lines
4.4 KiB
Python
# SPDX-License-Identifier: MIT
|
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
|
|
|
from errno import ENOENT
|
|
import json
|
|
import os
|
|
try:
|
|
from typing import TYPE_CHECKING, Mapping, Any
|
|
except ImportError:
|
|
TYPE_CHECKING = False
|
|
|
|
|
|
class Configuration:
|
|
DEFAULT_CONFIG = {
|
|
'LED_COUNT': 1,
|
|
'IDLE_TIMEOUT_SECS': 60,
|
|
'TAG_TIMEOUT_SECS': 5,
|
|
'BUTTON_MAP': {
|
|
'PLAY_PAUSE': 4,
|
|
'VOLUP': 0,
|
|
'VOLDOWN': 2,
|
|
'PREV': None,
|
|
'NEXT': 1,
|
|
},
|
|
'TAGMODE': 'tagremains',
|
|
'WIFI': {
|
|
'SSID': '',
|
|
'PASSPHRASE': '',
|
|
'SECURITY': 'wpa_wpa2',
|
|
},
|
|
'VOLUME_MAX': 255,
|
|
'VOLUME_BOOT': 16,
|
|
'LED_MAX': 255,
|
|
}
|
|
|
|
def __init__(self, config_path='/config.json'):
|
|
self.config_path = config_path
|
|
try:
|
|
with open(self.config_path, 'r') as conf_file:
|
|
self.config = json.load(conf_file)
|
|
self._merge_configs(self.DEFAULT_CONFIG, self.config)
|
|
except OSError as ex:
|
|
if ex.errno == ENOENT:
|
|
self.config = Configuration.DEFAULT_CONFIG
|
|
self._save()
|
|
else:
|
|
raise
|
|
except ValueError as ex:
|
|
print(f"Warning: Could not load configuration {self.config_path}:\n{ex}")
|
|
self._move_config_to_backup()
|
|
self.config = Configuration.DEFAULT_CONFIG
|
|
|
|
def _move_config_to_backup(self):
|
|
# Remove old backup
|
|
try:
|
|
os.remove(self.config_path + '.bup')
|
|
os.rename(self.config_path, self.config_path + '.bup')
|
|
except OSError as ex:
|
|
if ex.errno != ENOENT:
|
|
raise
|
|
os.sync()
|
|
|
|
def _merge_configs(self, default, config):
|
|
for k in default.keys():
|
|
if k not in config:
|
|
if isinstance(default[k], dict):
|
|
config[k] = default[k].copy()
|
|
else:
|
|
config[k] = default[k]
|
|
elif isinstance(default[k], dict):
|
|
self._merge_configs(default[k], config[k])
|
|
|
|
def _save(self):
|
|
with open(self.config_path + '.new', 'w') as conf_file:
|
|
json.dump(self.config, conf_file)
|
|
self._move_config_to_backup()
|
|
os.rename(self.config_path + '.new', self.config_path)
|
|
os.sync()
|
|
|
|
def _get(self, key):
|
|
return self.config[key]
|
|
|
|
def get_led_count(self) -> int:
|
|
return self._get('LED_COUNT')
|
|
|
|
def get_idle_timeout(self) -> int:
|
|
return self._get('IDLE_TIMEOUT_SECS')
|
|
|
|
def get_tag_timeout(self) -> int:
|
|
return self._get('TAG_TIMEOUT_SECS')
|
|
|
|
def get_button_map(self) -> Mapping[str, int | None]:
|
|
return self._get('BUTTON_MAP')
|
|
|
|
def get_tagmode(self) -> str:
|
|
return self._get('TAGMODE')
|
|
|
|
def get_wifi_ssid(self) -> str:
|
|
return self._get('WIFI')['SSID']
|
|
|
|
def get_wifi_passphrase(self) -> str:
|
|
return self._get('WIFI')['PASSPHRASE']
|
|
|
|
def get_wifi_security(self) -> str:
|
|
return self._get('WIFI')['SECURITY']
|
|
|
|
def get_volume_max(self) -> int:
|
|
return self._get('VOLUME_MAX')
|
|
|
|
def get_led_max(self) -> int:
|
|
return self._get('LED_MAX')
|
|
|
|
def get_volume_boot(self) -> int:
|
|
return self._get('VOLUME_BOOT')
|
|
|
|
# For the web API
|
|
def get_config(self) -> Mapping[str, Any]:
|
|
return self.config
|
|
|
|
def _validate(self, default, config, path=''):
|
|
for k in config.keys():
|
|
if k not in default:
|
|
raise ValueError(f'Invalid config key {path}/{k}')
|
|
if isinstance(default[k], dict):
|
|
if not isinstance(config[k], dict):
|
|
raise ValueError(f'Invalid config: Value of {path}/{k} must be mapping')
|
|
self._validate(default[k], config[k], f'{path}/{k}')
|
|
|
|
def set_config(self, config):
|
|
self._validate(self.DEFAULT_CONFIG, config)
|
|
if 'TAGMODE' in config and config['TAGMODE'] not in ['tagremains', 'tagstartstop']:
|
|
raise ValueError("Invalid TAGMODE: Must be 'tagremains' or 'tagstartstop'")
|
|
if 'WLAN' in config and 'SECURITY' in config['WLAN'] and \
|
|
config['WLAN']['SECURITY'] not in ['open', 'wpa_wpa2', 'wpa3', 'wpa2_wpa3']:
|
|
raise ValueError("Invalid WLAN SECURITY: Must be 'open', 'wpa_wpa2', 'wpa3' or 'wpa2_wpa3'")
|
|
self._merge_configs(self.config, config)
|
|
self.config = config
|
|
self._save()
|