diff --git a/src/rivian/parallax.py b/src/rivian/parallax.py index cf882da..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: @@ -251,18 +254,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] @@ -431,8 +435,8 @@ 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 - result[LOCK_MAP[lid]] = "locked" if state_val == 2 else "unlocked" + # 1 = locked, 2 = unlocked + result[LOCK_MAP[lid]] = "locked" if state_val == 1 else "unlocked" return result except Exception: @@ -455,8 +459,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: @@ -541,7 +545,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 +564,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..5cd4e54 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) @@ -215,16 +259,15 @@ 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: """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: @@ -258,8 +301,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: @@ -269,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: