Добавить множитель и IQ в Python-тренды

This commit is contained in:
2026-09-05 03:43:22 +03:00
parent 286e454464
commit 59f847e900
2 changed files with 23 additions and 3 deletions

View File

@@ -54,6 +54,8 @@ class TrendSignal:
deviceType: int = 7
device: int = 13
byteOffset: int = 0
multiplier: float = 1.0
iq: int = 0
extended: bool = True
@classmethod
@@ -72,9 +74,11 @@ class TrendSignal:
for field in ("id", "name", "source", "address", "color", "valueType"):
if type(getattr(self, field)) is not str:
raise ValueError(f"{field} must be a string")
for field in ("order", "deviceType", "device", "byteOffset"):
for field in ("order", "deviceType", "device", "byteOffset", "iq"):
if type(getattr(self, field)) is not int:
raise ValueError(f"{field} must be an integer")
if type(self.multiplier) not in (int, float) or not math.isfinite(self.multiplier):
raise ValueError("multiplier must be a finite number")
if type(self.visible) is not bool or type(self.extended) is not bool:
raise ValueError("Visibility and CAN format must be boolean")
if not self.id.strip() or len(self.id) > 80 or not 1 <= self.order <= 9999:
@@ -98,6 +102,11 @@ class TrendSignal:
raise ValueError("Invalid ProtoCAN device")
if self.source == "CAN_RAW" and not 0 <= self.byteOffset <= 6:
raise ValueError("Word offset must be 0..6")
if not 0 <= self.iq <= 30:
raise ValueError("IQ must be 0..30")
def display_value(self, raw_value: float) -> float:
return raw_value * self.multiplier / (1 << self.iq)
def validate_settings(settings: Mapping[str, list[TrendSignal]]) -> None:
ids = set()
@@ -132,14 +141,15 @@ def decode_settings(text: str) -> dict[str, list[TrendSignal]]:
raise ValueError("profiles must be an object")
result = {}
fields = set(TrendSignal.__dataclass_fields__)
required_fields = fields - {"multiplier", "iq"}
for profile, items in profiles.items():
if not isinstance(items, list) or len(items) > MAX_SIGNALS:
raise ValueError("Expected up to 64 signals")
signals = []
for item in items:
if not isinstance(item, dict) or not fields <= item.keys():
if not isinstance(item, dict) or not required_fields <= item.keys():
raise ValueError("Missing signal fields")
signals.append(TrendSignal(**{key: item[key] for key in fields}))
signals.append(TrendSignal(**{key: item[key] for key in fields if key in item}))
result[profile] = signals
validate_settings(result)
return result

View File

@@ -14,6 +14,16 @@ FIXTURE = Path(__file__).resolve().parents[2] / "c/set-protocol/tests/fixtures/t
class TrendTests(unittest.TestCase):
def test_multiplier_and_iq_scale_display_and_old_json_defaults(self):
signal = TrendSignal("scaled", multiplier=2.5, iq=3)
self.assertEqual(10.0, signal.display_value(32.0))
signal.validate("TMS2812")
payload = json.loads(encode_settings({"TMS2812": [signal]}))
payload["profiles"]["TMS2812"][0].pop("multiplier")
payload["profiles"]["TMS2812"][0].pop("iq")
restored = decode_settings(json.dumps(payload))["TMS2812"][0]
self.assertEqual((1.0, 0), (restored.multiplier, restored.iq))
def test_shared_kotlin_fixture_and_round_trip(self):
settings = decode_settings(FIXTURE.read_text(encoding="utf-8"))
self.assertEqual(5, sum(map(len, settings.values())))