This commit is contained in:
Louis King
2025-12-04 16:12:51 +00:00
parent d283a8c79b
commit fc0dc1a448
5 changed files with 116 additions and 3 deletions

View File

@@ -85,7 +85,12 @@ NETWORK_NAME=MeshCore Network
NETWORK_CITY=
NETWORK_COUNTRY=
NETWORK_LOCATION=
# Radio configuration (comma-delimited)
# Format: <profile>,<frequency>,<bandwidth>,<spreading_factor>,<coding_rate>,<tx_power>
# Example: EU/UK Narrow,869.618MHz,62.5kHz,8,8,22dBm
NETWORK_RADIO_CONFIG=
NETWORK_CONTACT_EMAIL=
NETWORK_CONTACT_DISCORD=

View File

@@ -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",
]

View File

@@ -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: "<profile>,<frequency>,<bandwidth>,<spreading_factor>,<coding_rate>,<tx_power>"
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:
"<profile>,<frequency>,<bandwidth>,<spreading_factor>,<coding_rate>,<tx_power>"
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,
)

View File

@@ -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__,

View File

@@ -50,11 +50,43 @@
</h2>
<div class="space-y-2">
{% if network_radio_config %}
{% if network_radio_config.profile %}
<div class="flex justify-between">
<span class="opacity-70">Radio Config:</span>
<span class="font-mono">{{ network_radio_config }}</span>
<span class="opacity-70">Profile:</span>
<span class="font-mono">{{ network_radio_config.profile }}</span>
</div>
{% endif %}
{% if network_radio_config.frequency %}
<div class="flex justify-between">
<span class="opacity-70">Frequency:</span>
<span class="font-mono">{{ network_radio_config.frequency }}</span>
</div>
{% endif %}
{% if network_radio_config.spreading_factor %}
<div class="flex justify-between">
<span class="opacity-70">Spreading Factor:</span>
<span class="font-mono">{{ network_radio_config.spreading_factor }}</span>
</div>
{% endif %}
{% if network_radio_config.bandwidth %}
<div class="flex justify-between">
<span class="opacity-70">Bandwidth:</span>
<span class="font-mono">{{ network_radio_config.bandwidth }}</span>
</div>
{% endif %}
{% if network_radio_config.coding_rate %}
<div class="flex justify-between">
<span class="opacity-70">Coding Rate:</span>
<span class="font-mono">{{ network_radio_config.coding_rate }}</span>
</div>
{% endif %}
{% if network_radio_config.tx_power %}
<div class="flex justify-between">
<span class="opacity-70">TX Power:</span>
<span class="font-mono">{{ network_radio_config.tx_power }}</span>
</div>
{% endif %}
{% endif %}
{% if network_location and network_location != (0.0, 0.0) %}
<div class="flex justify-between">
<span class="opacity-70">Location:</span>