diff --git a/.env.example b/.env.example index 3258614..7399be4 100644 --- a/.env.example +++ b/.env.example @@ -85,7 +85,12 @@ NETWORK_NAME=MeshCore Network NETWORK_CITY= NETWORK_COUNTRY= NETWORK_LOCATION= + +# Radio configuration (comma-delimited) +# Format: ,,,,, +# Example: EU/UK Narrow,869.618MHz,62.5kHz,8,8,22dBm NETWORK_RADIO_CONFIG= + NETWORK_CONTACT_EMAIL= NETWORK_CONTACT_DISCORD= diff --git a/src/meshcore_hub/common/schemas/__init__.py b/src/meshcore_hub/common/schemas/__init__.py index 2de871f..f9978e9 100644 --- a/src/meshcore_hub/common/schemas/__init__.py +++ b/src/meshcore_hub/common/schemas/__init__.py @@ -35,6 +35,9 @@ from meshcore_hub.common.schemas.members import ( MemberRead, MemberList, ) +from meshcore_hub.common.schemas.network import ( + RadioConfig, +) __all__ = [ # Events @@ -67,4 +70,6 @@ __all__ = [ "MemberUpdate", "MemberRead", "MemberList", + # Network + "RadioConfig", ] diff --git a/src/meshcore_hub/common/schemas/network.py b/src/meshcore_hub/common/schemas/network.py new file mode 100644 index 0000000..f19b130 --- /dev/null +++ b/src/meshcore_hub/common/schemas/network.py @@ -0,0 +1,65 @@ +"""Pydantic schemas for network configuration.""" + +from typing import Optional + +from pydantic import BaseModel + + +class RadioConfig(BaseModel): + """Parsed radio configuration from comma-delimited string. + + Format: ",,,,," + Example: "EU/UK Narrow,869.618MHz,62.5kHz,8,8,22dBm" + """ + + profile: Optional[str] = None + frequency: Optional[str] = None + bandwidth: Optional[str] = None + spreading_factor: Optional[int] = None + coding_rate: Optional[int] = None + tx_power: Optional[str] = None + + @classmethod + def from_config_string(cls, config_str: Optional[str]) -> Optional["RadioConfig"]: + """Parse a comma-delimited radio config string. + + Args: + config_str: Comma-delimited string in format: + ",,,,," + + Returns: + RadioConfig instance if parsing succeeds, None if input is None or empty + """ + if not config_str: + return None + + parts = [p.strip() for p in config_str.split(",")] + + # Handle partial configs by filling with None + while len(parts) < 6: + parts.append("") + + # Parse spreading factor and coding rate as integers + spreading_factor = None + coding_rate = None + + try: + if parts[3]: + spreading_factor = int(parts[3]) + except ValueError: + pass + + try: + if parts[4]: + coding_rate = int(parts[4]) + except ValueError: + pass + + return cls( + profile=parts[0] or None, + frequency=parts[1] or None, + bandwidth=parts[2] or None, + spreading_factor=spreading_factor, + coding_rate=coding_rate, + tx_power=parts[5] or None, + ) diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py index 81fabd6..f27ca3e 100644 --- a/src/meshcore_hub/web/app.py +++ b/src/meshcore_hub/web/app.py @@ -11,6 +11,7 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from meshcore_hub import __version__ +from meshcore_hub.common.schemas import RadioConfig logger = logging.getLogger(__name__) @@ -134,12 +135,17 @@ def get_templates(request: Request) -> Jinja2Templates: def get_network_context(request: Request) -> dict: """Get network configuration context for templates.""" + # Parse radio config from comma-delimited string + radio_config = RadioConfig.from_config_string( + request.app.state.network_radio_config + ) + return { "network_name": request.app.state.network_name, "network_city": request.app.state.network_city, "network_country": request.app.state.network_country, "network_location": request.app.state.network_location, - "network_radio_config": request.app.state.network_radio_config, + "network_radio_config": radio_config, "network_contact_email": request.app.state.network_contact_email, "network_contact_discord": request.app.state.network_contact_discord, "version": __version__, diff --git a/src/meshcore_hub/web/templates/home.html b/src/meshcore_hub/web/templates/home.html index d52b8f2..7bdf43f 100644 --- a/src/meshcore_hub/web/templates/home.html +++ b/src/meshcore_hub/web/templates/home.html @@ -50,11 +50,43 @@
{% if network_radio_config %} + {% if network_radio_config.profile %}
- Radio Config: - {{ network_radio_config }} + Profile: + {{ network_radio_config.profile }}
{% endif %} + {% if network_radio_config.frequency %} +
+ Frequency: + {{ network_radio_config.frequency }} +
+ {% endif %} + {% if network_radio_config.spreading_factor %} +
+ Spreading Factor: + {{ network_radio_config.spreading_factor }} +
+ {% endif %} + {% if network_radio_config.bandwidth %} +
+ Bandwidth: + {{ network_radio_config.bandwidth }} +
+ {% endif %} + {% if network_radio_config.coding_rate %} +
+ Coding Rate: + {{ network_radio_config.coding_rate }} +
+ {% endif %} + {% if network_radio_config.tx_power %} +
+ TX Power: + {{ network_radio_config.tx_power }} +
+ {% endif %} + {% endif %} {% if network_location and network_location != (0.0, 0.0) %}
Location: