mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-07-21 17:13:21 +02:00
This commit is contained in:
@@ -79,6 +79,7 @@ from .radio import ( # noqa: E402
|
||||
_REGION_CHANNEL_PARAMS,
|
||||
_camelcase_enum_name,
|
||||
_computed_channel_frequency,
|
||||
_custom_preset_label,
|
||||
_ensure_radio_metadata,
|
||||
_enum_name_from_field,
|
||||
_has_field,
|
||||
|
||||
@@ -222,11 +222,46 @@ def _camelcase_enum_name(name: str | None) -> str | None:
|
||||
return "".join(camel_parts)
|
||||
|
||||
|
||||
def _custom_preset_label(lora_message: Any) -> str:
|
||||
"""Return a compact custom-radio label when ``use_preset`` is disabled.
|
||||
|
||||
Reads ``spread_factor``, ``bandwidth``, and ``coding_rate`` from
|
||||
*lora_message*. When all three are non-zero the label follows the same
|
||||
compact convention used by MeshCore (e.g. ``"Custom SF8/BW62/CR6"``).
|
||||
When any parameter is absent or zero, returns the bare ``"Custom"`` string
|
||||
so the caller always gets a non-empty label.
|
||||
|
||||
Args:
|
||||
lora_message: A LoRa config protobuf message or compatible object.
|
||||
|
||||
Returns:
|
||||
A string starting with ``"Custom"``.
|
||||
"""
|
||||
sf = getattr(lora_message, "spread_factor", None) or None
|
||||
bw = getattr(lora_message, "bandwidth", None) or None
|
||||
cr = getattr(lora_message, "coding_rate", None) or None
|
||||
if sf and bw and cr:
|
||||
return f"Custom SF{int(sf)}/BW{int(bw)}/CR{int(cr)}"
|
||||
return "Custom"
|
||||
|
||||
|
||||
def _modem_preset(lora_message: Any) -> str | None:
|
||||
"""Return the CamelCase modem preset configured on ``lora_message``."""
|
||||
"""Return the CamelCase modem preset configured on ``lora_message``.
|
||||
|
||||
When ``lora_message.use_preset`` is explicitly ``False``, the device is
|
||||
using raw radio parameters instead of a named preset. In that case the
|
||||
enum value (which defaults to 0 / LongFast even in custom mode) is
|
||||
ignored and :func:`_custom_preset_label` is returned instead.
|
||||
"""
|
||||
|
||||
if lora_message is None:
|
||||
return None
|
||||
# use_preset=False means the device is in custom-parameter mode. The
|
||||
# modem_preset enum field defaults to 0 (LongFast) even then, so we must
|
||||
# check this flag before reading the enum. `is False` (identity check)
|
||||
# avoids false positives when the attribute is absent (returns None).
|
||||
if getattr(lora_message, "use_preset", None) is False:
|
||||
return _custom_preset_label(lora_message)
|
||||
descriptor = getattr(lora_message, "DESCRIPTOR", None)
|
||||
fields_by_name = getattr(descriptor, "fields_by_name", {}) if descriptor else {}
|
||||
if "modem_preset" in fields_by_name:
|
||||
|
||||
@@ -615,6 +615,108 @@ class TestModemPreset:
|
||||
msg = SimpleNamespace(DESCRIPTOR=desc, modem_preset=99)
|
||||
assert ifaces._modem_preset(msg) is None
|
||||
|
||||
def test_use_preset_false_full_params(self):
|
||||
"""use_preset=False with all params returns compact custom label."""
|
||||
msg = SimpleNamespace(
|
||||
DESCRIPTOR=None,
|
||||
use_preset=False,
|
||||
spread_factor=8,
|
||||
bandwidth=62,
|
||||
coding_rate=6,
|
||||
)
|
||||
assert ifaces._modem_preset(msg) == "Custom SF8/BW62/CR6"
|
||||
|
||||
def test_use_preset_false_partial_params(self):
|
||||
"""use_preset=False with only some params returns bare 'Custom'."""
|
||||
msg = SimpleNamespace(
|
||||
DESCRIPTOR=None,
|
||||
use_preset=False,
|
||||
spread_factor=8,
|
||||
bandwidth=62,
|
||||
# coding_rate absent
|
||||
)
|
||||
assert ifaces._modem_preset(msg) == "Custom"
|
||||
|
||||
def test_use_preset_false_no_params(self):
|
||||
"""use_preset=False with no radio params returns bare 'Custom'."""
|
||||
msg = SimpleNamespace(DESCRIPTOR=None, use_preset=False)
|
||||
assert ifaces._modem_preset(msg) == "Custom"
|
||||
|
||||
def test_use_preset_true_falls_through_to_enum(self):
|
||||
"""use_preset=True is ignored; existing enum logic is used."""
|
||||
enum_val = SimpleNamespace(name="LONG_FAST")
|
||||
enum_type = SimpleNamespace(values_by_number={0: enum_val})
|
||||
field_desc = SimpleNamespace(enum_type=enum_type)
|
||||
desc = SimpleNamespace(fields_by_name={"modem_preset": field_desc})
|
||||
msg = SimpleNamespace(
|
||||
DESCRIPTOR=desc,
|
||||
use_preset=True,
|
||||
modem_preset=0,
|
||||
spread_factor=8,
|
||||
bandwidth=62,
|
||||
coding_rate=6,
|
||||
)
|
||||
assert ifaces._modem_preset(msg) == "LongFast"
|
||||
|
||||
def test_use_preset_absent_falls_through_to_enum(self):
|
||||
"""No use_preset attribute falls through to existing enum logic."""
|
||||
enum_val = SimpleNamespace(name="LONG_FAST")
|
||||
enum_type = SimpleNamespace(values_by_number={0: enum_val})
|
||||
field_desc = SimpleNamespace(enum_type=enum_type)
|
||||
desc = SimpleNamespace(fields_by_name={"modem_preset": field_desc})
|
||||
# SimpleNamespace with no use_preset attribute
|
||||
msg = SimpleNamespace(DESCRIPTOR=desc, modem_preset=0)
|
||||
assert ifaces._modem_preset(msg) == "LongFast"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _custom_preset_label
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCustomPresetLabel:
|
||||
"""Tests for :func:`interfaces._custom_preset_label`."""
|
||||
|
||||
def test_all_params_present(self):
|
||||
"""All three non-zero params produce the compact label."""
|
||||
msg = SimpleNamespace(spread_factor=12, bandwidth=125, coding_rate=5)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom SF12/BW125/CR5"
|
||||
|
||||
def test_integer_conversion(self):
|
||||
"""Float param values are cast to int in the label."""
|
||||
msg = SimpleNamespace(spread_factor=8.0, bandwidth=62.5, coding_rate=6.0)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom SF8/BW62/CR6"
|
||||
|
||||
def test_missing_coding_rate_returns_custom(self):
|
||||
"""Absent coding_rate yields the bare 'Custom' fallback."""
|
||||
msg = SimpleNamespace(spread_factor=8, bandwidth=62)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
def test_missing_bandwidth_returns_custom(self):
|
||||
"""Absent bandwidth yields the bare 'Custom' fallback."""
|
||||
msg = SimpleNamespace(spread_factor=8, coding_rate=6)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
def test_missing_spread_factor_returns_custom(self):
|
||||
"""Absent spread_factor yields the bare 'Custom' fallback."""
|
||||
msg = SimpleNamespace(bandwidth=62, coding_rate=6)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
def test_zero_bandwidth_treated_as_absent(self):
|
||||
"""A zero bandwidth value is treated as absent, yielding 'Custom'."""
|
||||
msg = SimpleNamespace(spread_factor=8, bandwidth=0, coding_rate=6)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
def test_zero_spread_factor_treated_as_absent(self):
|
||||
"""A zero spread_factor value is treated as absent, yielding 'Custom'."""
|
||||
msg = SimpleNamespace(spread_factor=0, bandwidth=62, coding_rate=6)
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
def test_no_params_at_all(self):
|
||||
"""A message with none of the param attributes returns 'Custom'."""
|
||||
msg = SimpleNamespace()
|
||||
assert ifaces._custom_preset_label(msg) == "Custom"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _ensure_radio_metadata caching
|
||||
|
||||
Reference in New Issue
Block a user