From 530253b36a17b6959eae2efb601a0b81746fd9e8 Mon Sep 17 00:00:00 2001 From: jrgutier Date: Fri, 23 Oct 2026 18:49:04 -0500 Subject: [PATCH 1/2] Decode the trip target's SoC and minutes from their own fields charging.session.trip_target read field 2 as the target SoC. In the app's message (d5l) field 1 is the SoC, field 2 the minutes until it is reached and field 3 a status, so a target of 80% with 45 minutes to go was reported as 45%. Read all three, emitting the minutes as chargingTripTargetMinsRemaining, the name GraphQL already uses. --- src/rivian/parallax/charging.py | 16 ++++- src/rivian/parallax/proto/charging.proto | 7 +- src/rivian/parallax/proto/charging_pb2.py | 76 +++++++++++----------- src/rivian/parallax/proto/charging_pb2.pyi | 12 ++-- tests/parallax/test_charging.py | 38 +++++++++-- 5 files changed, 97 insertions(+), 52 deletions(-) diff --git a/src/rivian/parallax/charging.py b/src/rivian/parallax/charging.py index 4c41a3a..6529640 100644 --- a/src/rivian/parallax/charging.py +++ b/src/rivian/parallax/charging.py @@ -165,13 +165,23 @@ def decode_soc_slider(m: charging_pb2.SocSlider) -> dict[str, Any]: @RVMDecoder.register("charging.session.trip_target", charging_pb2.TripTarget) def decode_trip_target(m: charging_pb2.TripTarget) -> dict[str, Any]: - """charging.session.trip_target — the trip's target state of charge. + """charging.session.trip_target — the charge a planned trip needs. Fields: chargingTripTargetSoc: int | None (percent; None when not set) + chargingTripTargetMinsRemaining: int | None — minutes of charging + until the target SoC; None when no target or no estimate + _chargingTripTargetStatus: int — raw """ - soc = m.soc_limit - return {"chargingTripTargetSoc": None if soc in (0, 0xFFFF) else soc} + has_target = 1 <= m.soc <= 100 + minutes = m.time_estimate + return { + "chargingTripTargetSoc": m.soc if has_target else None, + "chargingTripTargetMinsRemaining": ( + minutes if has_target and minutes != 0xFFFF else None + ), + "_chargingTripTargetStatus": m.status, + } @RVMDecoder.register( diff --git a/src/rivian/parallax/proto/charging.proto b/src/rivian/parallax/proto/charging.proto index f9995a1..1b02853 100644 --- a/src/rivian/parallax/proto/charging.proto +++ b/src/rivian/parallax/proto/charging.proto @@ -99,9 +99,12 @@ message SocSlider { optional int32 soc_limit = 1; // percent } -// charging.session.trip_target -- unmapped. +// charging.session.trip_target -- the charge a planned trip needs. The app +// shows a trip target only while soc is 1-100. message TripTarget { - int32 soc_limit = 2; // percent; 0xFFFF when not set + int32 soc = 1; // percent; 0 when not set + int32 time_estimate = 2; // minutes until soc is reached; 0xFFFF when not set + int32 status = 3; // enum type unknown } // charging.smart_charging.settings -- not seen yet; the app's likely schema. diff --git a/src/rivian/parallax/proto/charging_pb2.py b/src/rivian/parallax/proto/charging_pb2.py index 37d0513..f58bf1d 100644 --- a/src/rivian/parallax/proto/charging_pb2.py +++ b/src/rivian/parallax/proto/charging_pb2.py @@ -24,31 +24,31 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$rivian/parallax/proto/charging.proto\x12\x18rivian.parallax.charging\"\xe8\x04\n\x12ScheduleTimeWindow\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x43\n\x06window\x18\x02 \x01(\x0b\x32\x33.rivian.parallax.charging.ScheduleTimeWindow.Window\x1a\xbe\x02\n\x06Window\x12\x12\n\nstart_time\x18\x01 \x01(\x05\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x11\n\x04\x61mps\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12L\n\x08location\x18\x05 \x01(\x0b\x32:.rivian.parallax.charging.ScheduleTimeWindow.GeoCoordinate\x12I\n\tstart_day\x18\x06 \x01(\x0e\x32\x36.rivian.parallax.charging.ScheduleTimeWindow.DayOfWeek\x12G\n\x07\x65nd_day\x18\x07 \x01(\x0e\x32\x36.rivian.parallax.charging.ScheduleTimeWindow.DayOfWeekB\x07\n\x05_amps\x1a\x34\n\rGeoCoordinate\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\"\x84\x01\n\tDayOfWeek\x12\x1b\n\x17\x44\x41Y_OF_WEEK_UNSPECIFIED\x10\x00\x12\n\n\x06SUNDAY\x10\x01\x12\n\n\x06MONDAY\x10\x02\x12\x0b\n\x07TUESDAY\x10\x03\x12\r\n\tWEDNESDAY\x10\x04\x12\x0c\n\x08THURSDAY\x10\x05\x12\n\n\x06\x46RIDAY\x10\x06\x12\x0c\n\x08SATURDAY\x10\x07\"\xb6\x01\n\x13SessionNotification\x12\x1e\n\x16unexpected_stop_reason\x18\x01 \x01(\x05\x12\x44\n\rderate_status\x18\x02 \x01(\x0e\x32-.rivian.parallax.charging.ChargerDerateStatus\x12\x39\n\x0b\x66\x61ult_chime\x18\x03 \x01(\x0e\x32$.rivian.parallax.charging.FaultChime\"\\\n\x14SessionRemoteCommand\x12\x44\n\x0fstart_available\x18\x01 \x01(\x0e\x32+.rivian.parallax.charging.StartAvailability\"1\n\tSocSlider\x12\x16\n\tsoc_limit\x18\x01 \x01(\x05H\x00\x88\x01\x01\x42\x0c\n\n_soc_limit\"\x1f\n\nTripTarget\x12\x11\n\tsoc_limit\x18\x02 \x01(\x05\"\xf3\x01\n\x15SmartChargingSettings\x12S\n\x0eready_by_times\x18\x01 \x03(\x0b\x32;.rivian.parallax.charging.SmartChargingSettings.ReadyByTime\x12\x1c\n\x14\x63lean_energy_enabled\x18\x02 \x01(\x08\x1ag\n\x0bReadyByTime\x12\r\n\x05hours\x18\x01 \x01(\x05\x12\x0f\n\x07minutes\x18\x02 \x01(\x05\x12\x38\n\x04\x64\x61ys\x18\x03 \x03(\x0e\x32*.rivian.parallax.charging.SmartChargingDay\"\xf0\x01\n\x11SmartChargingInfo\x12K\n\x0bresume_time\x18\x01 \x01(\x0b\x32\x36.rivian.parallax.charging.SmartChargingInfo.ResumeTime\x12I\n\x0cnotification\x18\x02 \x01(\x0e\x32\x33.rivian.parallax.charging.SmartChargingNotification\x12\x15\n\rschedule_type\x18\x03 \x01(\x05\x1a,\n\nResumeTime\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"\x1a\n\x18WeightedChargingForecast\"\xda\x01\n\rSessionStatus\x12H\n\x10\x63onnection_state\x18\x01 \x01(\x0e\x32).rivian.parallax.charging.ConnectionStateH\x00\x88\x01\x01\x12\x44\n\x0e\x63harging_state\x18\x02 \x01(\x0e\x32\'.rivian.parallax.charging.ChargingStateH\x01\x88\x01\x01\x12\x11\n\tevse_type\x18\x03 \x01(\x05\x42\x13\n\x11_connection_stateB\x11\n\x0f_charging_state\"v\n\x0eTimeEstimation\x12\x42\n\x08validity\x18\x01 \x01(\x0e\x32\x30.rivian.parallax.charging.TimeEstimationValidity\x12 \n\x18\x65stimated_time_remaining\x18\x02 \x01(\x05\"\x83\x03\n\x0b\x45nergyState\x12\x44\n\x0e\x63harging_state\x18\x01 \x01(\x0e\x32\'.rivian.parallax.charging.ChargingStateH\x00\x88\x01\x01\x12\x44\n\x0e\x63harger_status\x18\x02 \x01(\x0e\x32\'.rivian.parallax.charging.ChargerStatusH\x01\x88\x01\x01\x12\x14\n\x07\x66ield_3\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\r\n\x05power\x18\x04 \x01(\x02\x12\x14\n\x07\x66ield_8\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x10\n\x08\x66ield_10\x18\n \x01(\t\x12H\n\x10\x63onnection_state\x18\x0b \x01(\x0e\x32).rivian.parallax.charging.ConnectionStateH\x04\x88\x01\x01\x42\x11\n\x0f_charging_stateB\x11\n\x0f_charger_statusB\n\n\x08_field_3B\n\n\x08_field_8B\x13\n\x11_connection_state\"\x1d\n\x0cSessionPower\x12\r\n\x05power\x18\x01 \x01(\x02*\xb2\x05\n\x13\x43hargerDerateStatus\x12\x16\n\x12\x44\x45RATE_STATUS_NONE\x10\x00\x12\x1e\n\x1a\x44\x45RATE_STATUS_WARM_ADAPTER\x10\x01\x12\x1e\n\x1a\x44\x45RATE_STATUS_DC_WARM_PLUG\x10\x02\x12\x1e\n\x1a\x44\x45RATE_STATUS_AC_WARM_PLUG\x10\x03\x12\x1f\n\x1b\x44\x45RATE_STATUS_EVSE_DERATING\x10\x04\x12\x1d\n\x19\x44\x45RATE_STATUS_NEARING_TOC\x10\x05\x12/\n+DERATE_STATUS_NEAR_TOC_LFP_BATT_CALIBRATING\x10\x06\x12\"\n\x1e\x44\x45RATE_STATUS_HVAC_PRIORITIZED\x10\x07\x12!\n\x1d\x44\x45RATE_STATUS_BATTERY_HEATING\x10\x08\x12!\n\x1d\x44\x45RATE_STATUS_BATTERY_COOLING\x10\t\x12\x32\n.DERATE_STATUS_CELL_THERMAL_LIM_COLD_NO_CURRENT\x10\n\x12\x31\n-DERATE_STATUS_CELL_THERMAL_LIM_HOT_NO_CURRENT\x10\x0b\x12\'\n#DERATE_STATUS_CELL_THERMAL_LIM_COLD\x10\x0c\x12&\n\"DERATE_STATUS_CELL_THERMAL_LIM_HOT\x10\r\x12+\n\'DERATE_STATUS_PACK_HARDWARE_THERMAL_LIM\x10\x0e\x12 \n\x1c\x44\x45RATE_STATUS_HIGH_SOC_SIGMA\x10\x0f\x12\"\n\x1e\x44\x45RATE_STATUS_HV_BATTERY_FAULT\x10\x10\x12\x1d\n\x19\x44\x45RATE_STATUS_DCAC_EXPORT\x10\x11*\xdb\x03\n\nFaultChime\x12\x14\n\x10\x46\x41ULT_CHIME_NONE\x10\x00\x12%\n!FAULT_CHIME_CHARGING_DISABLED_ALL\x10\x01\x12$\n FAULT_CHIME_CHARGING_DISABLED_DC\x10\x02\x12-\n)FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_DC\x10\x03\x12\x36\n2FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_GRADIENT_DC\x10\x04\x12$\n FAULT_CHIME_CHARGING_DEGRADED_DC\x10\x05\x12$\n FAULT_CHIME_CHARGING_DISABLED_AC\x10\x06\x12-\n)FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_AC\x10\x07\x12$\n FAULT_CHIME_CHARGING_DEGRADED_AC\x10\x08\x12\x34\n0FAULT_CHIME_CHARGING_DISABLED_PARTIAL_CONNECTION\x10\t\x12,\n(FAULT_CHIME_CHARGING_DISABLED_NOT_PARKED\x10\n*j\n\x11StartAvailability\x12\x1a\n\x16START_AVAILABILITY_SNA\x10\x00\x12\x1c\n\x18START_AVAILABILITY_FALSE\x10\x01\x12\x1b\n\x17START_AVAILABILITY_TRUE\x10\x02*\x97\x02\n\x10SmartChargingDay\x12\"\n\x1eSMART_CHARGING_DAY_UNSPECIFIED\x10\x00\x12\x1d\n\x19SMART_CHARGING_DAY_MONDAY\x10\x01\x12\x1e\n\x1aSMART_CHARGING_DAY_TUESDAY\x10\x02\x12 \n\x1cSMART_CHARGING_DAY_WEDNESDAY\x10\x03\x12\x1f\n\x1bSMART_CHARGING_DAY_THURSDAY\x10\x04\x12\x1d\n\x19SMART_CHARGING_DAY_FRIDAY\x10\x05\x12\x1f\n\x1bSMART_CHARGING_DAY_SATURDAY\x10\x06\x12\x1d\n\x19SMART_CHARGING_DAY_SUNDAY\x10\x07*\xbc\x02\n\x19SmartChargingNotification\x12#\n\x1fSMART_CHARGING_NOTIFICATION_SNA\x10\x00\x12:\n6SMART_CHARGING_NOTIFICATION_CHARGING_WITH_CLEAN_ENERGY\x10\x01\x12/\n+SMART_CHARGING_NOTIFICATION_CHARGING_PAUSED\x10\x02\x12<\n8SMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_NOT_ENOUGH_TIME\x10\x03\x12O\nKSMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_FORECAST_UNAVAILABLE_OR_INCOMPLETE\x10\x04*\xaf\x01\n\x0f\x43onnectionState\x12\x19\n\x15\x43ONNECTION_STATE_INIT\x10\x00\x12!\n\x1d\x43ONNECTION_STATE_DISCONNECTED\x10\x01\x12\x1e\n\x1a\x43ONNECTION_STATE_CONNECTED\x10\x02\x12\x1a\n\x16\x43ONNECTION_STATE_ERROR\x10\x03\x12\"\n\x1e\x43ONNECTION_STATE_V2L_CONNECTED\x10\x04*\xc4\x06\n\rChargingState\x12\x1e\n\x1a\x43HARGING_STATE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x43HARGING_READY\x10\x01\x12\x17\n\x13\x43HARGING_CONNECTING\x10\x02\x12\x13\n\x0f\x43HARGING_ACTIVE\x10\x03\x12\x15\n\x11\x43HARGING_COMPLETE\x10\x04\x12\x16\n\x12\x43HARGING_SCHEDULED\x10\x05\x12\x1a\n\x16\x43HARGING_VEHICLE_ERROR\x10\x06\x12\x1a\n\x16\x43HARGING_STATION_ERROR\x10\x07\x12\x19\n\x15\x43HARGING_USER_STOPPED\x10\x08\x12\x1c\n\x18\x43HARGING_STATION_STOPPED\x10\t\x12\x1a\n\x16\x43HARGING_PAYMENT_ERROR\x10\n\x12\x17\n\x13\x43HARGING_CERT_ERROR\x10\x0b\x12\x16\n\x12\x43HARGING_TLS_ERROR\x10\x0c\x12(\n$CHARGING_ERROR_AC_ADAPTER_USED_ON_DC\x10\r\x12(\n$CHARGING_ERROR_DC_ADAPTER_USED_ON_AC\x10\x0e\x12\'\n#CHARGING_ERROR_INCOMPATIBLE_CHARGER\x10\x0f\x12\x1c\n\x18\x43HARGING_SD_COMPENSATION\x10\x10\x12\x16\n\x12WAITING_ON_CHARGER\x10\x11\x12%\n!CHARGER_NOT_READY_OR_INCOMPATIBLE\x10\x12\x12\x1c\n\x18\x43HARGING_VEHICLE_STOPPED\x10\x13\x12+\n\'CHARGING_PAYMENT_ERROR_START_RIVIAN_APP\x10\x14\x12&\n\"CHARGING_TLS_ERROR_UNKNOWN_CHARGER\x10\x15\x12&\n\"CHARGING_TLS_ERROR_UNEXPECTED_FAIL\x10\x16\x12\'\n#CHARGING_TLS_ERROR_START_RIVIAN_APP\x10\x17\x12\"\n\x1e\x43HARGING_SMART_CHARGING_PAUSED\x10\x18\x12\"\n\x1e\x43HARGING_SMART_CHARGING_ACTIVE\x10\x19*\xb4\x01\n\x16TimeEstimationValidity\x12!\n\x1dTIME_ESTIMATION_VALIDITY_NONE\x10\x00\x12\"\n\x1eTIME_ESTIMATION_VALIDITY_VALID\x10\x01\x12$\n TIME_ESTIMATION_VALIDITY_INVALID\x10\x02\x12-\n)TIME_ESTIMATION_VALIDITY_PACK_DISCHARGING\x10\x03*\xa0\x01\n\rChargerStatus\x12\x1e\n\x1a\x43HARGER_STATUS_UNSPECIFIED\x10\x00\x12 \n\x1c\x43HARGER_STATUS_NOT_CONNECTED\x10\x01\x12&\n\"CHARGER_STATUS_CONNECTED_NO_CHARGE\x10\x02\x12%\n!CHARGER_STATUS_CONNECTED_CHARGING\x10\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$rivian/parallax/proto/charging.proto\x12\x18rivian.parallax.charging\"\xe8\x04\n\x12ScheduleTimeWindow\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x43\n\x06window\x18\x02 \x01(\x0b\x32\x33.rivian.parallax.charging.ScheduleTimeWindow.Window\x1a\xbe\x02\n\x06Window\x12\x12\n\nstart_time\x18\x01 \x01(\x05\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x12\x11\n\x04\x61mps\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12L\n\x08location\x18\x05 \x01(\x0b\x32:.rivian.parallax.charging.ScheduleTimeWindow.GeoCoordinate\x12I\n\tstart_day\x18\x06 \x01(\x0e\x32\x36.rivian.parallax.charging.ScheduleTimeWindow.DayOfWeek\x12G\n\x07\x65nd_day\x18\x07 \x01(\x0e\x32\x36.rivian.parallax.charging.ScheduleTimeWindow.DayOfWeekB\x07\n\x05_amps\x1a\x34\n\rGeoCoordinate\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\"\x84\x01\n\tDayOfWeek\x12\x1b\n\x17\x44\x41Y_OF_WEEK_UNSPECIFIED\x10\x00\x12\n\n\x06SUNDAY\x10\x01\x12\n\n\x06MONDAY\x10\x02\x12\x0b\n\x07TUESDAY\x10\x03\x12\r\n\tWEDNESDAY\x10\x04\x12\x0c\n\x08THURSDAY\x10\x05\x12\n\n\x06\x46RIDAY\x10\x06\x12\x0c\n\x08SATURDAY\x10\x07\"\xb6\x01\n\x13SessionNotification\x12\x1e\n\x16unexpected_stop_reason\x18\x01 \x01(\x05\x12\x44\n\rderate_status\x18\x02 \x01(\x0e\x32-.rivian.parallax.charging.ChargerDerateStatus\x12\x39\n\x0b\x66\x61ult_chime\x18\x03 \x01(\x0e\x32$.rivian.parallax.charging.FaultChime\"\\\n\x14SessionRemoteCommand\x12\x44\n\x0fstart_available\x18\x01 \x01(\x0e\x32+.rivian.parallax.charging.StartAvailability\"1\n\tSocSlider\x12\x16\n\tsoc_limit\x18\x01 \x01(\x05H\x00\x88\x01\x01\x42\x0c\n\n_soc_limit\"@\n\nTripTarget\x12\x0b\n\x03soc\x18\x01 \x01(\x05\x12\x15\n\rtime_estimate\x18\x02 \x01(\x05\x12\x0e\n\x06status\x18\x03 \x01(\x05\"\xf3\x01\n\x15SmartChargingSettings\x12S\n\x0eready_by_times\x18\x01 \x03(\x0b\x32;.rivian.parallax.charging.SmartChargingSettings.ReadyByTime\x12\x1c\n\x14\x63lean_energy_enabled\x18\x02 \x01(\x08\x1ag\n\x0bReadyByTime\x12\r\n\x05hours\x18\x01 \x01(\x05\x12\x0f\n\x07minutes\x18\x02 \x01(\x05\x12\x38\n\x04\x64\x61ys\x18\x03 \x03(\x0e\x32*.rivian.parallax.charging.SmartChargingDay\"\xf0\x01\n\x11SmartChargingInfo\x12K\n\x0bresume_time\x18\x01 \x01(\x0b\x32\x36.rivian.parallax.charging.SmartChargingInfo.ResumeTime\x12I\n\x0cnotification\x18\x02 \x01(\x0e\x32\x33.rivian.parallax.charging.SmartChargingNotification\x12\x15\n\rschedule_type\x18\x03 \x01(\x05\x1a,\n\nResumeTime\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"\x1a\n\x18WeightedChargingForecast\"\xda\x01\n\rSessionStatus\x12H\n\x10\x63onnection_state\x18\x01 \x01(\x0e\x32).rivian.parallax.charging.ConnectionStateH\x00\x88\x01\x01\x12\x44\n\x0e\x63harging_state\x18\x02 \x01(\x0e\x32\'.rivian.parallax.charging.ChargingStateH\x01\x88\x01\x01\x12\x11\n\tevse_type\x18\x03 \x01(\x05\x42\x13\n\x11_connection_stateB\x11\n\x0f_charging_state\"v\n\x0eTimeEstimation\x12\x42\n\x08validity\x18\x01 \x01(\x0e\x32\x30.rivian.parallax.charging.TimeEstimationValidity\x12 \n\x18\x65stimated_time_remaining\x18\x02 \x01(\x05\"\x83\x03\n\x0b\x45nergyState\x12\x44\n\x0e\x63harging_state\x18\x01 \x01(\x0e\x32\'.rivian.parallax.charging.ChargingStateH\x00\x88\x01\x01\x12\x44\n\x0e\x63harger_status\x18\x02 \x01(\x0e\x32\'.rivian.parallax.charging.ChargerStatusH\x01\x88\x01\x01\x12\x14\n\x07\x66ield_3\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\r\n\x05power\x18\x04 \x01(\x02\x12\x14\n\x07\x66ield_8\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x10\n\x08\x66ield_10\x18\n \x01(\t\x12H\n\x10\x63onnection_state\x18\x0b \x01(\x0e\x32).rivian.parallax.charging.ConnectionStateH\x04\x88\x01\x01\x42\x11\n\x0f_charging_stateB\x11\n\x0f_charger_statusB\n\n\x08_field_3B\n\n\x08_field_8B\x13\n\x11_connection_state\"\x1d\n\x0cSessionPower\x12\r\n\x05power\x18\x01 \x01(\x02*\xb2\x05\n\x13\x43hargerDerateStatus\x12\x16\n\x12\x44\x45RATE_STATUS_NONE\x10\x00\x12\x1e\n\x1a\x44\x45RATE_STATUS_WARM_ADAPTER\x10\x01\x12\x1e\n\x1a\x44\x45RATE_STATUS_DC_WARM_PLUG\x10\x02\x12\x1e\n\x1a\x44\x45RATE_STATUS_AC_WARM_PLUG\x10\x03\x12\x1f\n\x1b\x44\x45RATE_STATUS_EVSE_DERATING\x10\x04\x12\x1d\n\x19\x44\x45RATE_STATUS_NEARING_TOC\x10\x05\x12/\n+DERATE_STATUS_NEAR_TOC_LFP_BATT_CALIBRATING\x10\x06\x12\"\n\x1e\x44\x45RATE_STATUS_HVAC_PRIORITIZED\x10\x07\x12!\n\x1d\x44\x45RATE_STATUS_BATTERY_HEATING\x10\x08\x12!\n\x1d\x44\x45RATE_STATUS_BATTERY_COOLING\x10\t\x12\x32\n.DERATE_STATUS_CELL_THERMAL_LIM_COLD_NO_CURRENT\x10\n\x12\x31\n-DERATE_STATUS_CELL_THERMAL_LIM_HOT_NO_CURRENT\x10\x0b\x12\'\n#DERATE_STATUS_CELL_THERMAL_LIM_COLD\x10\x0c\x12&\n\"DERATE_STATUS_CELL_THERMAL_LIM_HOT\x10\r\x12+\n\'DERATE_STATUS_PACK_HARDWARE_THERMAL_LIM\x10\x0e\x12 \n\x1c\x44\x45RATE_STATUS_HIGH_SOC_SIGMA\x10\x0f\x12\"\n\x1e\x44\x45RATE_STATUS_HV_BATTERY_FAULT\x10\x10\x12\x1d\n\x19\x44\x45RATE_STATUS_DCAC_EXPORT\x10\x11*\xdb\x03\n\nFaultChime\x12\x14\n\x10\x46\x41ULT_CHIME_NONE\x10\x00\x12%\n!FAULT_CHIME_CHARGING_DISABLED_ALL\x10\x01\x12$\n FAULT_CHIME_CHARGING_DISABLED_DC\x10\x02\x12-\n)FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_DC\x10\x03\x12\x36\n2FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_GRADIENT_DC\x10\x04\x12$\n FAULT_CHIME_CHARGING_DEGRADED_DC\x10\x05\x12$\n FAULT_CHIME_CHARGING_DISABLED_AC\x10\x06\x12-\n)FAULT_CHIME_CHARGING_DISABLED_PIN_TEMP_AC\x10\x07\x12$\n FAULT_CHIME_CHARGING_DEGRADED_AC\x10\x08\x12\x34\n0FAULT_CHIME_CHARGING_DISABLED_PARTIAL_CONNECTION\x10\t\x12,\n(FAULT_CHIME_CHARGING_DISABLED_NOT_PARKED\x10\n*j\n\x11StartAvailability\x12\x1a\n\x16START_AVAILABILITY_SNA\x10\x00\x12\x1c\n\x18START_AVAILABILITY_FALSE\x10\x01\x12\x1b\n\x17START_AVAILABILITY_TRUE\x10\x02*\x97\x02\n\x10SmartChargingDay\x12\"\n\x1eSMART_CHARGING_DAY_UNSPECIFIED\x10\x00\x12\x1d\n\x19SMART_CHARGING_DAY_MONDAY\x10\x01\x12\x1e\n\x1aSMART_CHARGING_DAY_TUESDAY\x10\x02\x12 \n\x1cSMART_CHARGING_DAY_WEDNESDAY\x10\x03\x12\x1f\n\x1bSMART_CHARGING_DAY_THURSDAY\x10\x04\x12\x1d\n\x19SMART_CHARGING_DAY_FRIDAY\x10\x05\x12\x1f\n\x1bSMART_CHARGING_DAY_SATURDAY\x10\x06\x12\x1d\n\x19SMART_CHARGING_DAY_SUNDAY\x10\x07*\xbc\x02\n\x19SmartChargingNotification\x12#\n\x1fSMART_CHARGING_NOTIFICATION_SNA\x10\x00\x12:\n6SMART_CHARGING_NOTIFICATION_CHARGING_WITH_CLEAN_ENERGY\x10\x01\x12/\n+SMART_CHARGING_NOTIFICATION_CHARGING_PAUSED\x10\x02\x12<\n8SMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_NOT_ENOUGH_TIME\x10\x03\x12O\nKSMART_CHARGING_NOTIFICATION_CLEAN_ENERGY_FORECAST_UNAVAILABLE_OR_INCOMPLETE\x10\x04*\xaf\x01\n\x0f\x43onnectionState\x12\x19\n\x15\x43ONNECTION_STATE_INIT\x10\x00\x12!\n\x1d\x43ONNECTION_STATE_DISCONNECTED\x10\x01\x12\x1e\n\x1a\x43ONNECTION_STATE_CONNECTED\x10\x02\x12\x1a\n\x16\x43ONNECTION_STATE_ERROR\x10\x03\x12\"\n\x1e\x43ONNECTION_STATE_V2L_CONNECTED\x10\x04*\xc4\x06\n\rChargingState\x12\x1e\n\x1a\x43HARGING_STATE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x43HARGING_READY\x10\x01\x12\x17\n\x13\x43HARGING_CONNECTING\x10\x02\x12\x13\n\x0f\x43HARGING_ACTIVE\x10\x03\x12\x15\n\x11\x43HARGING_COMPLETE\x10\x04\x12\x16\n\x12\x43HARGING_SCHEDULED\x10\x05\x12\x1a\n\x16\x43HARGING_VEHICLE_ERROR\x10\x06\x12\x1a\n\x16\x43HARGING_STATION_ERROR\x10\x07\x12\x19\n\x15\x43HARGING_USER_STOPPED\x10\x08\x12\x1c\n\x18\x43HARGING_STATION_STOPPED\x10\t\x12\x1a\n\x16\x43HARGING_PAYMENT_ERROR\x10\n\x12\x17\n\x13\x43HARGING_CERT_ERROR\x10\x0b\x12\x16\n\x12\x43HARGING_TLS_ERROR\x10\x0c\x12(\n$CHARGING_ERROR_AC_ADAPTER_USED_ON_DC\x10\r\x12(\n$CHARGING_ERROR_DC_ADAPTER_USED_ON_AC\x10\x0e\x12\'\n#CHARGING_ERROR_INCOMPATIBLE_CHARGER\x10\x0f\x12\x1c\n\x18\x43HARGING_SD_COMPENSATION\x10\x10\x12\x16\n\x12WAITING_ON_CHARGER\x10\x11\x12%\n!CHARGER_NOT_READY_OR_INCOMPATIBLE\x10\x12\x12\x1c\n\x18\x43HARGING_VEHICLE_STOPPED\x10\x13\x12+\n\'CHARGING_PAYMENT_ERROR_START_RIVIAN_APP\x10\x14\x12&\n\"CHARGING_TLS_ERROR_UNKNOWN_CHARGER\x10\x15\x12&\n\"CHARGING_TLS_ERROR_UNEXPECTED_FAIL\x10\x16\x12\'\n#CHARGING_TLS_ERROR_START_RIVIAN_APP\x10\x17\x12\"\n\x1e\x43HARGING_SMART_CHARGING_PAUSED\x10\x18\x12\"\n\x1e\x43HARGING_SMART_CHARGING_ACTIVE\x10\x19*\xb4\x01\n\x16TimeEstimationValidity\x12!\n\x1dTIME_ESTIMATION_VALIDITY_NONE\x10\x00\x12\"\n\x1eTIME_ESTIMATION_VALIDITY_VALID\x10\x01\x12$\n TIME_ESTIMATION_VALIDITY_INVALID\x10\x02\x12-\n)TIME_ESTIMATION_VALIDITY_PACK_DISCHARGING\x10\x03*\xa0\x01\n\rChargerStatus\x12\x1e\n\x1a\x43HARGER_STATUS_UNSPECIFIED\x10\x00\x12 \n\x1c\x43HARGER_STATUS_NOT_CONNECTED\x10\x01\x12&\n\"CHARGER_STATUS_CONNECTED_NO_CHARGE\x10\x02\x12%\n!CHARGER_STATUS_CONNECTED_CHARGING\x10\x03\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rivian.parallax.proto.charging_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_CHARGERDERATESTATUS']._serialized_start=2328 - _globals['_CHARGERDERATESTATUS']._serialized_end=3018 - _globals['_FAULTCHIME']._serialized_start=3021 - _globals['_FAULTCHIME']._serialized_end=3496 - _globals['_STARTAVAILABILITY']._serialized_start=3498 - _globals['_STARTAVAILABILITY']._serialized_end=3604 - _globals['_SMARTCHARGINGDAY']._serialized_start=3607 - _globals['_SMARTCHARGINGDAY']._serialized_end=3886 - _globals['_SMARTCHARGINGNOTIFICATION']._serialized_start=3889 - _globals['_SMARTCHARGINGNOTIFICATION']._serialized_end=4205 - _globals['_CONNECTIONSTATE']._serialized_start=4208 - _globals['_CONNECTIONSTATE']._serialized_end=4383 - _globals['_CHARGINGSTATE']._serialized_start=4386 - _globals['_CHARGINGSTATE']._serialized_end=5222 - _globals['_TIMEESTIMATIONVALIDITY']._serialized_start=5225 - _globals['_TIMEESTIMATIONVALIDITY']._serialized_end=5405 - _globals['_CHARGERSTATUS']._serialized_start=5408 - _globals['_CHARGERSTATUS']._serialized_end=5568 + _globals['_CHARGERDERATESTATUS']._serialized_start=2361 + _globals['_CHARGERDERATESTATUS']._serialized_end=3051 + _globals['_FAULTCHIME']._serialized_start=3054 + _globals['_FAULTCHIME']._serialized_end=3529 + _globals['_STARTAVAILABILITY']._serialized_start=3531 + _globals['_STARTAVAILABILITY']._serialized_end=3637 + _globals['_SMARTCHARGINGDAY']._serialized_start=3640 + _globals['_SMARTCHARGINGDAY']._serialized_end=3919 + _globals['_SMARTCHARGINGNOTIFICATION']._serialized_start=3922 + _globals['_SMARTCHARGINGNOTIFICATION']._serialized_end=4238 + _globals['_CONNECTIONSTATE']._serialized_start=4241 + _globals['_CONNECTIONSTATE']._serialized_end=4416 + _globals['_CHARGINGSTATE']._serialized_start=4419 + _globals['_CHARGINGSTATE']._serialized_end=5255 + _globals['_TIMEESTIMATIONVALIDITY']._serialized_start=5258 + _globals['_TIMEESTIMATIONVALIDITY']._serialized_end=5438 + _globals['_CHARGERSTATUS']._serialized_start=5441 + _globals['_CHARGERSTATUS']._serialized_end=5601 _globals['_SCHEDULETIMEWINDOW']._serialized_start=67 _globals['_SCHEDULETIMEWINDOW']._serialized_end=683 _globals['_SCHEDULETIMEWINDOW_WINDOW']._serialized_start=176 @@ -64,23 +64,23 @@ _globals['_SOCSLIDER']._serialized_start=964 _globals['_SOCSLIDER']._serialized_end=1013 _globals['_TRIPTARGET']._serialized_start=1015 - _globals['_TRIPTARGET']._serialized_end=1046 - _globals['_SMARTCHARGINGSETTINGS']._serialized_start=1049 - _globals['_SMARTCHARGINGSETTINGS']._serialized_end=1292 - _globals['_SMARTCHARGINGSETTINGS_READYBYTIME']._serialized_start=1189 - _globals['_SMARTCHARGINGSETTINGS_READYBYTIME']._serialized_end=1292 - _globals['_SMARTCHARGINGINFO']._serialized_start=1295 - _globals['_SMARTCHARGINGINFO']._serialized_end=1535 - _globals['_SMARTCHARGINGINFO_RESUMETIME']._serialized_start=1491 - _globals['_SMARTCHARGINGINFO_RESUMETIME']._serialized_end=1535 - _globals['_WEIGHTEDCHARGINGFORECAST']._serialized_start=1537 - _globals['_WEIGHTEDCHARGINGFORECAST']._serialized_end=1563 - _globals['_SESSIONSTATUS']._serialized_start=1566 - _globals['_SESSIONSTATUS']._serialized_end=1784 - _globals['_TIMEESTIMATION']._serialized_start=1786 - _globals['_TIMEESTIMATION']._serialized_end=1904 - _globals['_ENERGYSTATE']._serialized_start=1907 - _globals['_ENERGYSTATE']._serialized_end=2294 - _globals['_SESSIONPOWER']._serialized_start=2296 - _globals['_SESSIONPOWER']._serialized_end=2325 + _globals['_TRIPTARGET']._serialized_end=1079 + _globals['_SMARTCHARGINGSETTINGS']._serialized_start=1082 + _globals['_SMARTCHARGINGSETTINGS']._serialized_end=1325 + _globals['_SMARTCHARGINGSETTINGS_READYBYTIME']._serialized_start=1222 + _globals['_SMARTCHARGINGSETTINGS_READYBYTIME']._serialized_end=1325 + _globals['_SMARTCHARGINGINFO']._serialized_start=1328 + _globals['_SMARTCHARGINGINFO']._serialized_end=1568 + _globals['_SMARTCHARGINGINFO_RESUMETIME']._serialized_start=1524 + _globals['_SMARTCHARGINGINFO_RESUMETIME']._serialized_end=1568 + _globals['_WEIGHTEDCHARGINGFORECAST']._serialized_start=1570 + _globals['_WEIGHTEDCHARGINGFORECAST']._serialized_end=1596 + _globals['_SESSIONSTATUS']._serialized_start=1599 + _globals['_SESSIONSTATUS']._serialized_end=1817 + _globals['_TIMEESTIMATION']._serialized_start=1819 + _globals['_TIMEESTIMATION']._serialized_end=1937 + _globals['_ENERGYSTATE']._serialized_start=1940 + _globals['_ENERGYSTATE']._serialized_end=2327 + _globals['_SESSIONPOWER']._serialized_start=2329 + _globals['_SESSIONPOWER']._serialized_end=2358 # @@protoc_insertion_point(module_scope) diff --git a/src/rivian/parallax/proto/charging_pb2.pyi b/src/rivian/parallax/proto/charging_pb2.pyi index a46f6c6..07c1c6f 100644 --- a/src/rivian/parallax/proto/charging_pb2.pyi +++ b/src/rivian/parallax/proto/charging_pb2.pyi @@ -275,10 +275,14 @@ class SocSlider(_message.Message): def __init__(self, soc_limit: _Optional[int] = ...) -> None: ... class TripTarget(_message.Message): - __slots__ = ("soc_limit",) - SOC_LIMIT_FIELD_NUMBER: _ClassVar[int] - soc_limit: int - def __init__(self, soc_limit: _Optional[int] = ...) -> None: ... + __slots__ = ("soc", "time_estimate", "status") + SOC_FIELD_NUMBER: _ClassVar[int] + TIME_ESTIMATE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + soc: int + time_estimate: int + status: int + def __init__(self, soc: _Optional[int] = ..., time_estimate: _Optional[int] = ..., status: _Optional[int] = ...) -> None: ... class SmartChargingSettings(_message.Message): __slots__ = ("ready_by_times", "clean_energy_enabled") diff --git a/tests/parallax/test_charging.py b/tests/parallax/test_charging.py index 141cf0c..671c85e 100644 --- a/tests/parallax/test_charging.py +++ b/tests/parallax/test_charging.py @@ -184,13 +184,41 @@ def test_session_power() -> None: def test_trip_target() -> None: - """The trip's target SoC, or None for the "not set" value.""" + """The target SoC (#1) and the minutes until it's reached (#2).""" rvm = "charging.session.trip_target" - assert decode(rvm, charging.TripTarget(soc_limit=80)) == { - "chargingTripTargetSoc": 80 + message = charging.TripTarget(soc=80, time_estimate=45, status=1) + assert message.SerializeToString() == bytes.fromhex("0850102d1801") + assert decode(rvm, message) == { + "chargingTripTargetSoc": 80, + "chargingTripTargetMinsRemaining": 45, + "_chargingTripTargetStatus": 1, } - assert decode(rvm, charging.TripTarget(soc_limit=0xFFFF)) == { - "chargingTripTargetSoc": None + assert decode(rvm, charging.TripTarget(soc=80, time_estimate=0xFFFF)) == { + "chargingTripTargetSoc": 80, + "chargingTripTargetMinsRemaining": None, + "_chargingTripTargetStatus": 0, + } + + +@pytest.mark.parametrize("soc", [0, 101]) +def test_trip_target_not_set(soc: int) -> None: + """Without a 1-100 SoC there's no trip target, so no minutes either.""" + message = charging.TripTarget(soc=soc, time_estimate=45) + assert decode("charging.session.trip_target", message) == { + "chargingTripTargetSoc": None, + "chargingTripTargetMinsRemaining": None, + "_chargingTripTargetStatus": 0, + } + + +def test_trip_target_captured() -> None: + """A captured frame with no trip target: #2 = 0xFFFF and no #1.""" + message = charging.TripTarget.FromString(bytes.fromhex("10ffff03")) + assert (message.soc, message.time_estimate) == (0, 0xFFFF) + assert decode("charging.session.trip_target", message) == { + "chargingTripTargetSoc": None, + "chargingTripTargetMinsRemaining": None, + "_chargingTripTargetStatus": 0, } From bf4aaec508687959f4f0c15f93bd19b6b1eadc45 Mon Sep 17 00:00:00 2001 From: jrgutier Date: Fri, 23 Oct 2026 22:48:45 -0500 Subject: [PATCH 2/2] Report no trip target minutes when the estimate is absent or zero A frame with a target but no time estimate (#2 absent or 0) decoded as 0 minutes, which a consumer shows as "0 min". The app hides the minutes when they are <= 0, so report None for them, as for 0xFFFF. --- src/rivian/parallax/charging.py | 5 +++-- tests/parallax/test_charging.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/rivian/parallax/charging.py b/src/rivian/parallax/charging.py index 6529640..a968bbc 100644 --- a/src/rivian/parallax/charging.py +++ b/src/rivian/parallax/charging.py @@ -170,7 +170,8 @@ def decode_trip_target(m: charging_pb2.TripTarget) -> dict[str, Any]: Fields: chargingTripTargetSoc: int | None (percent; None when not set) chargingTripTargetMinsRemaining: int | None — minutes of charging - until the target SoC; None when no target or no estimate + until the target SoC; None when no target, or when the estimate is + absent, zero or 0xFFFF _chargingTripTargetStatus: int — raw """ has_target = 1 <= m.soc <= 100 @@ -178,7 +179,7 @@ def decode_trip_target(m: charging_pb2.TripTarget) -> dict[str, Any]: return { "chargingTripTargetSoc": m.soc if has_target else None, "chargingTripTargetMinsRemaining": ( - minutes if has_target and minutes != 0xFFFF else None + minutes if has_target and 0 < minutes < 0xFFFF else None ), "_chargingTripTargetStatus": m.status, } diff --git a/tests/parallax/test_charging.py b/tests/parallax/test_charging.py index 671c85e..40afd66 100644 --- a/tests/parallax/test_charging.py +++ b/tests/parallax/test_charging.py @@ -211,6 +211,17 @@ def test_trip_target_not_set(soc: int) -> None: } +def test_trip_target_no_estimate() -> None: + """A target with #2 absent has no minutes, rather than 0.""" + message = charging.TripTarget.FromString(bytes.fromhex("0850")) + assert (message.soc, message.time_estimate) == (80, 0) + assert decode("charging.session.trip_target", message) == { + "chargingTripTargetSoc": 80, + "chargingTripTargetMinsRemaining": None, + "_chargingTripTargetStatus": 0, + } + + def test_trip_target_captured() -> None: """A captured frame with no trip target: #2 = 0xFFFF and no #1.""" message = charging.TripTarget.FromString(bytes.fromhex("10ffff03"))