Skip to content

Commit 9285d31

Browse files
committed
Add websocket server, refactoring, cleanup
1 parent 3e56a8b commit 9285d31

17 files changed

+648
-121
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 Filip Melik
3+
Copyright (c) 2025 Filip Melik
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

application/config_manager.py

+30-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
CFG_KEY_WAVELOG_API_URL,
88
CFG_KEY_WAVELOG_API_KEY,
99
CFG_KEY_WAVELOG_API_CALL_TIMEOUT,
10-
CFG_KEY_WAVELOG_API_CALL_INTERVAL,
10+
CFG_KEY_WAVELOG_API_CALL_HEARTBEAT_TIME,
1111
CFG_KEY_RADIO_NAME,
1212
CFG_KEY_RADIO_BAUD_RATE,
1313
CFG_KEY_RADIO_DATA_BITS,
@@ -16,25 +16,38 @@
1616
CFG_KEY_RADIO_DRIVER_NAME,
1717
CFG_KEY_RADIO_REPLY_TIMEOUT,
1818
CFG_KEY_RADIO_POLLING_INTERVAL,
19-
DEVICE_NAME,
20-
CFG_KEY_DNS_NAME,
19+
CFG_KEY_USER_CALLSIGN,
20+
CFG_KEY_STARTUP_SCREEN_WAIT_TIME,
21+
CFG_KEY_XML_RPC_SERVER_PORT,
22+
CFG_KEY_GENERAL_API_SERVER_PORT,
23+
CFG_KEY_WEBSOCKET_SERVER_ENDPOINT_URL,
2124
)
2225

2326

2427
class ConfigManager:
2528

26-
def read_config(self) -> dict:
29+
def __init__(self):
30+
self._config = self._read_config()
31+
32+
def get_config(self) -> dict:
33+
"""
34+
Return cached config
35+
"""
36+
return self._config
37+
38+
def _read_config(self) -> dict:
2739
"""
2840
Read configuration from config file. In case config file not
2941
"""
3042
default_values = { # default values
31-
CFG_KEY_DNS_NAME: DEVICE_NAME.lower(),
43+
CFG_KEY_USER_CALLSIGN: "",
44+
CFG_KEY_STARTUP_SCREEN_WAIT_TIME: "3",
3245
CFG_KEY_WIFI_NAME: "",
3346
CFG_KEY_WIFI_PASS: "",
3447
CFG_KEY_WAVELOG_API_URL: "",
3548
CFG_KEY_WAVELOG_API_KEY: "",
3649
CFG_KEY_WAVELOG_API_CALL_TIMEOUT: "1",
37-
CFG_KEY_WAVELOG_API_CALL_INTERVAL: "2",
50+
CFG_KEY_WAVELOG_API_CALL_HEARTBEAT_TIME: "30",
3851
CFG_KEY_RADIO_NAME: "",
3952
CFG_KEY_RADIO_BAUD_RATE: "19200",
4053
CFG_KEY_RADIO_DATA_BITS: "8",
@@ -43,6 +56,9 @@ def read_config(self) -> dict:
4356
CFG_KEY_RADIO_DRIVER_NAME: "",
4457
CFG_KEY_RADIO_REPLY_TIMEOUT: "0.5",
4558
CFG_KEY_RADIO_POLLING_INTERVAL: "1",
59+
CFG_KEY_XML_RPC_SERVER_PORT: "12345",
60+
CFG_KEY_GENERAL_API_SERVER_PORT: "54321",
61+
CFG_KEY_WEBSOCKET_SERVER_ENDPOINT_URL: "",
4662
}
4763

4864
config = {}
@@ -57,6 +73,14 @@ def read_config(self) -> dict:
5773

5874
return config
5975

76+
def get_device_id(self) -> str:
77+
"""
78+
Get device ID
79+
"""
80+
callsign = self._config[CFG_KEY_USER_CALLSIGN].lower()
81+
radio_name = self._config[CFG_KEY_RADIO_NAME].lower()
82+
83+
return f"{callsign}-{radio_name}"
6084

6185
def save_config(self, config: dict):
6286
"""

application/constants.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
from micropython import const
22

3-
DEVICE_NAME = const("WLTrxInterface")
3+
DEFAULT_DEVICE_NAME = const("WLTrxInterface")
44
CONFIG_FILE_PATH = const("config.json")
55
RADIO_DRIVER_FILE_PATH = const("radio_driver.ini")
6-
SETUP_FILE_PATH = const("setup.html")
6+
SETUP_FILE_PATH = const("setup_page/setup.html")
77

8-
SPLASH_SCREEN_WAIT_TIME = const(3) # seconds
98
WIFI_CONNECTED_SCREEN_WAIT_TIME = const(2) # seconds
109

11-
API_HEARTBEAT_TIME = const(20) # seconds
12-
1310
# Config keys
14-
CFG_KEY_DNS_NAME = const("dnsName")
11+
CFG_KEY_USER_CALLSIGN = const("userCallsign")
12+
CFG_KEY_STARTUP_SCREEN_WAIT_TIME = const("startupScreenWaitTime")
1513
CFG_KEY_WIFI_NAME = const("wifiName")
1614
CFG_KEY_WIFI_PASS = const("wifiPass")
1715
CFG_KEY_WAVELOG_API_URL = const("wavelogApiUrl")
1816
CFG_KEY_WAVELOG_API_KEY = const("wavelogApiKey")
1917
CFG_KEY_WAVELOG_API_CALL_TIMEOUT = const("wavelogApiCallTimeout")
20-
CFG_KEY_WAVELOG_API_CALL_INTERVAL= const("wavelogApiCallInterval")
18+
CFG_KEY_WAVELOG_API_CALL_HEARTBEAT_TIME = const("wavelogApiCallHeartbeatTime")
2119
CFG_KEY_RADIO_NAME = const("radioName")
2220
CFG_KEY_RADIO_BAUD_RATE = const("radioBaudRate")
2321
CFG_KEY_RADIO_DATA_BITS = const("radioDataBits")
@@ -26,6 +24,9 @@
2624
CFG_KEY_RADIO_REPLY_TIMEOUT = const("radioReplyTimeout")
2725
CFG_KEY_RADIO_POLLING_INTERVAL = const("radioPollingInterval")
2826
CFG_KEY_RADIO_DRIVER_NAME = const("radioDriverName")
27+
CFG_KEY_XML_RPC_SERVER_PORT = const("xmlRpcServerPort")
28+
CFG_KEY_GENERAL_API_SERVER_PORT = const("generalApiServerPort")
29+
CFG_KEY_WEBSOCKET_SERVER_ENDPOINT_URL = const("websocketServerEndpointUrl")
2930

3031
# Message broker topics
3132
TOPIC_TRX_STATUS = const("trx_status")

application/main_app.py

+17-12
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import os
44
from application.config_manager import ConfigManager
55
from application.constants import (
6-
DEVICE_NAME,
6+
DEFAULT_DEVICE_NAME,
77
CFG_KEY_RADIO_DRIVER_NAME,
88
CFG_KEY_WIFI_NAME,
99
CFG_KEY_RADIO_BAUD_RATE,
1010
CFG_KEY_RADIO_DATA_BITS,
1111
CFG_KEY_RADIO_STOP_BITS,
1212
CFG_KEY_RADIO_PARITY,
13-
SPLASH_SCREEN_WAIT_TIME,
1413
RADIO_DRIVER_FILE_PATH,
14+
CFG_KEY_STARTUP_SCREEN_WAIT_TIME,
1515
)
1616
from application.setup_manager import SetupManager
1717
from application.wifi_manager import WifiManager
@@ -168,12 +168,14 @@ async def run(self):
168168
self._logger.debug("Starting websocket client task")
169169
websocket_client_task=WebsocketClientTask(
170170
logger=self._logger,
171+
message_broker=self._message_broker,
172+
omnirig_helper=self._omnirig_helper,
173+
omnirig_command_executor=self._omnirig_command_executor,
174+
config_manager=self._config_manager,
171175
)
172176
asyncio.create_task(websocket_client_task.run())
177+
asyncio.create_task(websocket_client_task.run_trx_status_messages_subscriber())
173178
self._tasks_to_stop_when_setup_is_launched.append(websocket_client_task)
174-
175-
# todo server for cloudlog offline - conditional based on config?
176-
# todo websocket client for wavelog bandlist???
177179

178180
asyncio.get_event_loop().run_forever()
179181

@@ -211,7 +213,7 @@ async def _wait_for_setup_button_long_press_event(
211213

212214
self._stop_tasks_that_dont_need_to_run_when_setup_is_launched()
213215

214-
access_point_ssid = DEVICE_NAME
216+
access_point_ssid = DEFAULT_DEVICE_NAME
215217
self._logger.debug(f"Creating wi-fi access point with ssid '{access_point_ssid}'")
216218
device_ip_address = self._wifi_manager.create_wifi_access_point(
217219
essid=access_point_ssid
@@ -235,9 +237,9 @@ def _run_startup_sequence(self):
235237
"""
236238
Perform necessary initialization tasks
237239
"""
238-
config = self._config_manager.read_config()
239-
self._display_splash_screen(config=config)
240-
time.sleep(SPLASH_SCREEN_WAIT_TIME) # TODO configurable?
240+
config = self._config_manager.get_config()
241+
self._display_splash_screen(config_manager=self._config_manager)
242+
time.sleep(int(config[CFG_KEY_STARTUP_SCREEN_WAIT_TIME]))
241243

242244
self._logger.info("Setting up wi-fi")
243245
self._wifi_manager.setup_wifi_as_client()
@@ -248,7 +250,7 @@ def _radio_driver_is_missing(self, config_manager: ConfigManager) -> bool:
248250
"""
249251
Check if radio driver was not yet set up by user on the setup screen
250252
"""
251-
config = config_manager.read_config()
253+
config = config_manager.get_config()
252254
radio_driver_file_exists = RADIO_DRIVER_FILE_PATH in os.listdir()
253255
config_entry_exists = config.get(CFG_KEY_RADIO_DRIVER_NAME)
254256
return not radio_driver_file_exists or not config_entry_exists
@@ -296,10 +298,13 @@ def _configure_uart(self, config: dict):
296298
f"{uart_baudrate}-{uart_bits}-{parity_str}-{uart_stop_bits}"
297299
)
298300

299-
def _display_splash_screen(self, config: dict):
301+
def _display_splash_screen(self, config_manager: ConfigManager):
300302
"""
301303
Display splash (startup) screen with some useful info
302304
"""
305+
config = config_manager.get_config()
306+
device_id = config_manager.get_device_id()
307+
303308
uart_baudrate = int(config.get(CFG_KEY_RADIO_BAUD_RATE))
304309
uart_bits = int(config.get(CFG_KEY_RADIO_DATA_BITS))
305310
uart_stop_bits = int(config.get(CFG_KEY_RADIO_STOP_BITS))
@@ -313,7 +318,7 @@ def _display_splash_screen(self, config: dict):
313318
parity = 'N'
314319

315320
text_rows = [
316-
DEVICE_NAME,
321+
device_id,
317322
"",
318323
f"Radio: {config.get(CFG_KEY_RADIO_DRIVER_NAME)}",
319324
f"Wifi: {config.get(CFG_KEY_WIFI_NAME)}",

application/setup_manager.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
CFG_KEY_RADIO_DRIVER_NAME,
99
CFG_KEY_RADIO_REPLY_TIMEOUT,
1010
CFG_KEY_WAVELOG_API_CALL_TIMEOUT,
11-
RADIO_DRIVER_FILE_PATH,
11+
RADIO_DRIVER_FILE_PATH, CFG_KEY_RADIO_POLLING_INTERVAL,
1212
)
1313
from application.config_manager import ConfigManager
1414
from helpers.display_helper import DisplayHelper
@@ -75,7 +75,7 @@ async def _setup_page_handler(self, request, response):
7575
with open(SETUP_FILE_PATH, "r") as fp:
7676
setup_html = fp.read()
7777

78-
config = self._config_manager.read_config()
78+
config = self._config_manager.get_config()
7979

8080
# fix the value of radioDriverName so it is not empty string in on the setup page
8181
if not config.get(CFG_KEY_RADIO_DRIVER_NAME):
@@ -111,7 +111,7 @@ async def _save_config_handler(self, request, response):
111111
radio_driver_temp_file_name = "radio_driver_file_buffer.tmp"
112112
radio_driver_file_buffer = open(radio_driver_temp_file_name, "wb")
113113
uploaded_radio_driver_file_name = None
114-
config = self._config_manager.read_config()
114+
config = self._config_manager.get_config()
115115
boundary = content_type.split("boundary=", 1)[1]
116116
parser = PushMultipartParser(boundary=boundary, content_length=content_length)
117117

@@ -140,12 +140,13 @@ async def _save_config_handler(self, request, response):
140140
part._mark_complete()
141141
if (
142142
part.name == CFG_KEY_RADIO_REPLY_TIMEOUT
143+
or part.name == CFG_KEY_RADIO_POLLING_INTERVAL
143144
or part.name == CFG_KEY_WAVELOG_API_CALL_TIMEOUT
144145
):
145146
# replace decimal comma for point due to possible browser locale setting
146-
config[part.name] = str(part.value.replace(",", "."))
147+
config[part.name] = str(part.value.replace(",", ".")).strip()
147148
else:
148-
config[part.name] = str(part.value)
149+
config[part.name] = str(part.value).strip()
149150

150151
part = None # free up the resources
151152
parser.close()

application/wifi_manager.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import network
22

33
from application.config_manager import ConfigManager
4-
from application.constants import CFG_KEY_DNS_NAME, CFG_KEY_WIFI_PASS, CFG_KEY_WIFI_NAME
4+
from application.constants import CFG_KEY_WIFI_PASS, CFG_KEY_WIFI_NAME
55
from helpers.logger import Logger
66

77

@@ -39,8 +39,7 @@ def setup_wifi_as_client(self):
3939
"""
4040
Setup wi-fi for station mode (as a client that will connect to the router)
4141
"""
42-
config = self._config_manager.read_config()
43-
dhcp_hostname = config[CFG_KEY_DNS_NAME]
42+
dhcp_hostname = self._config_manager.get_device_id()
4443

4544
self._logger.debug(
4645
f"Activating wifi interface, setting to station mode "
@@ -54,7 +53,7 @@ def connect_to_wifi(self):
5453
"""
5554
Connect to wi-fi saved in config and block until connected
5655
"""
57-
config = self._config_manager.read_config()
56+
config = self._config_manager.get_config()
5857
ssid = config[CFG_KEY_WIFI_NAME]
5958
password = config[CFG_KEY_WIFI_PASS]
6059

helpers/display_helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ssd1306
1+
from lib import ssd1306
22

33

44
class DisplayHelper:

0 commit comments

Comments
 (0)