From 6cc3fb0df373916a15b754f3e5e777ac60dc83dc Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 7 Aug 2026 10:09:32 -0600 Subject: [PATCH 1/8] Update parallax tire pressure status to use all caps OK --- src/rivian/parallax.py | 4 ++-- tests/test_parallax.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index cf882da..7c0bc37 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -541,7 +541,7 @@ def decode_tires(payload: str) -> dict[str, Any]: Returns dict with keys: - tirePressureFrontLeft, tirePressureFrontRight, etc. (bar) - - tirePressureStatusFrontLeft, etc. ("Ok") + - tirePressureStatusFrontLeft, etc. ("OK") """ if not payload: return {} @@ -560,7 +560,7 @@ def decode_tires(payload: str) -> dict[str, Any]: if in_num == 1 and in_type == 0: pos = in_val elif in_num == 2 and in_type == 0: - status = "Ok" if in_val == 1 else "Warning" + status = "OK" if in_val == 1 else "Warning" elif in_num == 3 and in_type == 1: # 64-bit float (bar) pressure = round(in_val, 2) diff --git a/tests/test_parallax.py b/tests/test_parallax.py index e76af75..7892501 100644 --- a/tests/test_parallax.py +++ b/tests/test_parallax.py @@ -221,10 +221,10 @@ def test_decode_odometer() -> None: def test_decode_tires() -> None: """Test dynamics.tires.state decoder.""" - # Nested tire: pos=1 (FL), status=1 (Ok), pressure=3.48 (float) + # Nested tire: pos=1 (FL), status=1 (OK), pressure=3.48 (float) inner = ( b"\x08\x01" # field 1 = 1 (FL) - + b"\x10\x01" # field 2 = 1 (status Ok) + + b"\x10\x01" # field 2 = 1 (status OK) + b"\x19" + struct.pack(" None: result = decode_tires(payload_b64) assert result.get("tirePressureFrontLeft") == 3.48 - assert result.get("tirePressureStatusFrontLeft") == "Ok" + assert result.get("tirePressureStatusFrontLeft") == "OK" def test_decode_closures() -> None: From a07d00ae34bab352695d8f40f4198a7f8d4f76c9 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 7 Aug 2026 10:52:16 -0600 Subject: [PATCH 2/8] Inverse lock state check --- src/rivian/parallax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index 7c0bc37..4805c4e 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -432,7 +432,7 @@ def decode_locks(payload: str) -> dict[str, Any]: if lid and lid in LOCK_MAP and state_val is not None: # 1 = unlocked, 2 = locked - result[LOCK_MAP[lid]] = "locked" if state_val == 2 else "unlocked" + result[LOCK_MAP[lid]] = "locked" if state_val == 1 else "unlocked" return result except Exception: From 26976ed26bc4416b97d8bce1b1c53682b4e696b5 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 7 Aug 2026 10:53:20 -0600 Subject: [PATCH 3/8] Fix test --- tests/test_parallax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_parallax.py b/tests/test_parallax.py index 7892501..bac9f8b 100644 --- a/tests/test_parallax.py +++ b/tests/test_parallax.py @@ -258,8 +258,8 @@ def test_decode_locks() -> None: payload_b64 = base64.b64encode(outer).decode() result = decode_locks(payload_b64) - assert result.get("doorFrontLeftLocked") == "locked" - assert result.get("doorFrontRightLocked") == "unlocked" + assert result.get("doorFrontLeftLocked") == "unlocked" + assert result.get("doorFrontRightLocked") == "locked" def test_decode_cabin_temperatures() -> None: From 956347766ff8836ed6874a20c0e5ed3ccb806171 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 7 Aug 2026 11:13:42 -0600 Subject: [PATCH 4/8] Also fix note on lock/unlock state --- src/rivian/parallax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index 4805c4e..a20e13e 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -431,7 +431,7 @@ def decode_locks(payload: str) -> dict[str, Any]: state_val = in_val if lid and lid in LOCK_MAP and state_val is not None: - # 1 = unlocked, 2 = locked + # 1 = locked, 2 = unlocked result[LOCK_MAP[lid]] = "locked" if state_val == 1 else "unlocked" return result From 4ecf87b0adf39e66e7c66894690dbe651acade4b Mon Sep 17 00:00:00 2001 From: Greg Rhoades Date: Fri, 7 Aug 2026 13:38:57 -0400 Subject: [PATCH 5/8] fix(parallax): sum active segment durations for timeElapsed calculation --- src/rivian/parallax.py | 11 ++++++----- tests/test_parallax.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index a20e13e..d1e70f4 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -251,18 +251,19 @@ def decode_charging_graph_global(payload: str) -> dict[str, Any]: ] first_seg = active_segments[0] if active_segments else segments[0] - last_seg = active_segments[-1] if active_segments else segments[-1] result: dict[str, Any] = {} if "start_ms" in first_seg: st = datetime.fromtimestamp(first_seg["start_ms"] / 1000, timezone.utc) result["startTime"] = st.strftime("%Y-%m-%dT%H:%M:%S.%f%z") - if active_segments and "start_ms" in first_seg and "end_ms" in last_seg: - result["timeElapsed"] = max( - 0, int((last_seg["end_ms"] - first_seg["start_ms"]) / 1000) + if active_segments: + result["timeElapsed"] = sum( + max(0, int((s["end_ms"] - s["start_ms"]) / 1000)) + for s in active_segments + if "end_ms" in s and "start_ms" in s ) - elif not active_segments: + else: result["timeElapsed"] = 0 latest_segment = segments[-1] diff --git a/tests/test_parallax.py b/tests/test_parallax.py index bac9f8b..03b4c1c 100644 --- a/tests/test_parallax.py +++ b/tests/test_parallax.py @@ -185,6 +185,50 @@ def test_decode_charging_graph_global_stopped_state() -> None: assert result.get("kilometersChargedPerHour") == 0.0 +def test_decode_charging_graph_global_resumed_session() -> None: + """Test charging graph when charging resumes after an idle pause.""" + # Active Segment 1: 1785695977217 -> 1785696037217 (60s, 5.8 kW, state=3) + seg1 = ( + b"\x08\x48" + + bytes([21]) + + struct.pack(" 1785696637217 (600s pause, state=8) + seg2 = ( + b"\x08\x48" + + b"\x18\xe1\xd2\x9c\x9e\xfc3" + + b"\x20\xa1\xa9\xb8\x9e\xfc3" + + b"\x30\x08" + ) + # Active Segment 3: 1785696637217 -> 1785696757217 (120s resumed, 5.8 kW, state=3) + seg3 = ( + b"\x08\x48" + + bytes([21]) + + struct.pack(" None: """Test charging.session.status decoder.""" # field 1 = plugConnectionStatus (1), field 2 = displayStatus (3), field 3 = evseType (2) From cf153ebf0acbbae02642417fba27e01ad7495425 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sun, 9 Aug 2026 09:11:03 -0600 Subject: [PATCH 6/8] Fix vehicle mileage multiplier --- src/rivian/parallax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index d1e70f4..a8a14d6 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -456,8 +456,8 @@ def decode_odometer(payload: str) -> dict[str, Any]: for field_num, wire_type, value in fields: if field_num == 1 and wire_type == 0: - # Value is distance in miles; HA expects meters (1 mile = 1609.344 meters) - result["vehicleMileage"] = round(value * 1609.344, 1) + # Value is distance in km; HA expects meters + result["vehicleMileage"] = value * 1000 return result except Exception: From 11b77da2878129860f21705981a14f468711a9bd Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sun, 9 Aug 2026 09:17:26 -0600 Subject: [PATCH 7/8] Fix vehicle mileage multiplier --- tests/test_parallax.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_parallax.py b/tests/test_parallax.py index 03b4c1c..ee5ba67 100644 --- a/tests/test_parallax.py +++ b/tests/test_parallax.py @@ -259,8 +259,7 @@ def test_decode_odometer() -> None: payload_b64 = base64.b64encode(raw).decode() result = decode_odometer(payload_b64) - expected_meters = round(17114 * 1609.344, 1) - assert result.get("vehicleMileage") == expected_meters + assert result.get("vehicleMileage") == 17114000 def test_decode_tires() -> None: From fb7d7a564f58ec5407d97a093f0f76cbadd8c2ea Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sun, 9 Aug 2026 10:19:33 -0600 Subject: [PATCH 8/8] Fix cabin climate temperature mapping --- src/rivian/parallax.py | 5 ++++- tests/test_parallax.py | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index a8a14d6..15a079e 100644 --- a/src/rivian/parallax.py +++ b/src/rivian/parallax.py @@ -145,6 +145,7 @@ def decode_cabin_temperatures(payload: str) -> dict[str, Any]: Returns dict with keys: - cabinClimateInteriorTemperature: float (Celsius) + - cabinClimateDriverTemperature: float (Celsius) """ if not payload: return {} @@ -154,8 +155,10 @@ def decode_cabin_temperatures(payload: str) -> dict[str, Any]: result: dict[str, Any] = {} for field_num, wire_type, value in fields: - if field_num == 4 and wire_type == 5: # interior temp (float, Celsius) + if field_num == 3 and wire_type == 5: # interior temp (float, Celsius) result["cabinClimateInteriorTemperature"] = round(value, 1) + if field_num == 4 and wire_type == 5: # interior temp (float, Celsius) + result["cabinClimateDriverTemperature"] = round(value, 1) return result except Exception: diff --git a/tests/test_parallax.py b/tests/test_parallax.py index ee5ba67..5cd4e54 100644 --- a/tests/test_parallax.py +++ b/tests/test_parallax.py @@ -312,7 +312,14 @@ def test_decode_cabin_temperatures() -> None: payload_b64 = base64.b64encode(raw).decode() result = decode_cabin_temperatures(payload_b64) - assert result.get("cabinClimateInteriorTemperature") == 23.5 + assert result.get("cabinClimateDriverTemperature") == 23.5 + + raw = b"\x1d\x00\x00\xf8A%\x00\x00\xacA" + payload_b64 = base64.b64encode(raw).decode() + + result = decode_cabin_temperatures(payload_b64) + assert result.get("cabinClimateInteriorTemperature") == 31 + assert result.get("cabinClimateDriverTemperature") == 21.5 def test_decode_power_state() -> None: