diff --git a/app/radio.py b/app/radio.py
index 95d5dd0..02837a1 100644
--- a/app/radio.py
+++ b/app/radio.py
@@ -128,6 +128,8 @@ class RadioManager:
self._setup_lock: asyncio.Lock | None = None
self._setup_in_progress: bool = False
self._setup_complete: bool = False
+ self.path_hash_mode: int = 0
+ self.path_hash_mode_supported: bool = False
async def _acquire_operation_lock(
self,
@@ -272,6 +274,22 @@ class RadioManager:
"set_flood_scope failed (firmware may not support it): %s", exc
)
+ # Query path hash mode support (best-effort; older firmware won't report it)
+ try:
+ device_query = await mc.commands.send_device_query()
+ if device_query and "path_hash_mode" in device_query.payload:
+ self.path_hash_mode = device_query.payload["path_hash_mode"]
+ self.path_hash_mode_supported = True
+ logger.info("Path hash mode: %d (supported)", self.path_hash_mode)
+ else:
+ self.path_hash_mode = 0
+ self.path_hash_mode_supported = False
+ logger.debug("Firmware does not report path_hash_mode")
+ except Exception as exc:
+ self.path_hash_mode = 0
+ self.path_hash_mode_supported = False
+ logger.debug("Failed to query path_hash_mode: %s", exc)
+
# Sync contacts/channels from radio to DB and clear radio
logger.info("Syncing and offloading radio data...")
result = await sync_and_offload_all(mc)
@@ -412,6 +430,8 @@ class RadioManager:
await self._meshcore.disconnect()
self._meshcore = None
self._setup_complete = False
+ self.path_hash_mode = 0
+ self.path_hash_mode_supported = False
logger.debug("Radio disconnected")
async def reconnect(self, *, broadcast_on_success: bool = True) -> bool:
diff --git a/app/routers/radio.py b/app/routers/radio.py
index 9c53e77..93d8aec 100644
--- a/app/routers/radio.py
+++ b/app/routers/radio.py
@@ -28,6 +28,12 @@ class RadioConfigResponse(BaseModel):
tx_power: int = Field(description="Transmit power in dBm")
max_tx_power: int = Field(description="Maximum transmit power in dBm")
radio: RadioSettings
+ path_hash_mode: int = Field(
+ default=0, description="Path hash mode (0=1-byte, 1=2-byte, 2=3-byte)"
+ )
+ path_hash_mode_supported: bool = Field(
+ default=False, description="Whether firmware supports path hash mode setting"
+ )
class RadioConfigUpdate(BaseModel):
@@ -36,6 +42,9 @@ class RadioConfigUpdate(BaseModel):
lon: float | None = None
tx_power: int | None = Field(default=None, description="Transmit power in dBm")
radio: RadioSettings | None = None
+ path_hash_mode: int | None = Field(
+ default=None, description="Path hash mode (0=1-byte, 1=2-byte, 2=3-byte)"
+ )
class PrivateKeyUpdate(BaseModel):
@@ -64,6 +73,8 @@ async def get_radio_config() -> RadioConfigResponse:
sf=info.get("radio_sf", 0),
cr=info.get("radio_cr", 0),
),
+ path_hash_mode=radio_manager.path_hash_mode,
+ path_hash_mode_supported=radio_manager.path_hash_mode_supported,
)
@@ -103,6 +114,15 @@ async def update_radio_config(update: RadioConfigUpdate) -> RadioConfigResponse:
cr=update.radio.cr,
)
+ if update.path_hash_mode is not None:
+ if not radio_manager.path_hash_mode_supported:
+ raise HTTPException(
+ status_code=400, detail="Firmware does not support path hash mode setting"
+ )
+ logger.info("Setting path hash mode to %d", update.path_hash_mode)
+ await mc.commands.set_path_hash_mode(update.path_hash_mode)
+ radio_manager.path_hash_mode = update.path_hash_mode
+
# Sync time with system clock
await sync_radio_time(mc)
diff --git a/frontend/src/components/settings/SettingsRadioSection.tsx b/frontend/src/components/settings/SettingsRadioSection.tsx
index 33b8e48..0aed907 100644
--- a/frontend/src/components/settings/SettingsRadioSection.tsx
+++ b/frontend/src/components/settings/SettingsRadioSection.tsx
@@ -47,6 +47,7 @@ export function SettingsRadioSection({
const [bw, setBw] = useState('');
const [sf, setSf] = useState('');
const [cr, setCr] = useState('');
+ const [pathHashMode, setPathHashMode] = useState('0');
const [gettingLocation, setGettingLocation] = useState(false);
const [busy, setBusy] = useState(false);
const [rebooting, setRebooting] = useState(false);
@@ -77,6 +78,7 @@ export function SettingsRadioSection({
setBw(String(config.radio.bw));
setSf(String(config.radio.sf));
setCr(String(config.radio.cr));
+ setPathHashMode(String(config.path_hash_mode));
}, [config]);
useEffect(() => {
@@ -159,6 +161,8 @@ export function SettingsRadioSection({
return null;
}
+ const parsedPathHashMode = parseInt(pathHashMode, 10);
+
return {
name,
lat: parsedLat,
@@ -170,6 +174,11 @@ export function SettingsRadioSection({
sf: parsedSf,
cr: parsedCr,
},
+ ...(config.path_hash_mode_supported &&
+ !isNaN(parsedPathHashMode) &&
+ parsedPathHashMode !== config.path_hash_mode
+ ? { path_hash_mode: parsedPathHashMode }
+ : {}),
};
};
@@ -427,6 +436,33 @@ export function SettingsRadioSection({
+ {config.path_hash_mode_supported && (
+ <>
+
Compatibility Warning
++ ALL nodes along a message's route — your radio, every repeater, and the + recipient — must be running firmware that supports the selected mode. Messages + sent with 2-byte or 3-byte hops will be dropped by any node on older firmware. +
+