Skip to content

fix(ble): correct climate command protobuf payloads - #51

Merged
Bre77 merged 2 commits into
mainfrom
fm/tfa-ble-pr4-climate-m6
Jul 9, 2026
Merged

fix(ble): correct climate command protobuf payloads#51
Bre77 merged 2 commits into
mainfrom
fm/tfa-ble-pr4-climate-m6

Conversation

@Bre77

@Bre77 Bre77 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Intent

PR-4 of the BLE production-readiness program: live-verify the CLIMATE command group over BLE against the real test car (m5-btproxy, VIN LRW3F7EK4NC716336), per the master plan report.md PR-4 entry and the resolved decisions in decisions-resolved.md. Climate commands are all INFO-domain, all classified AUTO (snapshot->restore, no physical/mechanical risk) in the plan's safety matrix - this PR is the deliberate 'safest actuation' batch, classified HOLD-FOR-CAPTAIN only because it is the first real live actuation of this command group (same pattern as PR-3 for closures/locks).

Scope: all 15 climate commands listed in the plan's climate table (auto_conditioning_start/stop, set_temps, set_climate_keeper_mode, set_preconditioning_max, set_bioweapon_mode, set_cabin_overheat_protection, set_cop_temp, set_recirculation, remote_seat_heater_request, remote_seat_cooler_request, remote_auto_seat_climate_request, remote_steering_wheel_heater_request, remote_steering_wheel_heat_level_request, remote_auto_steering_wheel_heat_climate_request).

Deliberate per-command outcomes (all discovered live, not code changes I chose arbitrarily):

  • 5 commands fully live-verified with the mandated snapshot->act->verify->restore->confirm cycle: auto_conditioning_start, auto_conditioning_stop, set_temps, set_climate_keeper_mode, set_cabin_overheat_protection. Car confirmed restored to its exact original snapshot after each.
  • 6 commands were live-attempted but the vehicle itself cleanly rejected them (ACK decoded correctly, state read back unchanged, so no restore was needed and nothing was left mutated): set_cop_temp returns {'result': False, 'reason': 'not_supported'} on this car; the five remote seat/steering-wheel comfort commands (remote_seat_heater_request, remote_auto_seat_climate_request, remote_steering_wheel_heater_request, remote_steering_wheel_heat_level_request, remote_auto_steering_wheel_heat_climate_request) all return {'result': False, 'reason': 'cabin comfort remote settings not enabled'} - I confirmed this maps to the read-only climate_state().remote_heater_control_enabled field being False on this car (a touchscreen/app-level 'Remote Climate' toggle this library has no command to flip). I documented this discovered gate in AGENTS.md rather than trying to work around it.
  • 4 commands were deliberately deferred to mocked-transport-only tests, NOT live-fired, because I confirmed via a full live climate_state() read that they cannot be safely snapshot/verify/restored on this car: set_bioweapon_mode (no HEPA filter on this Model 3 - the bioweapon_mode_on field is absent from every live read, same 'no HW on this rig' pattern as sun_roof/tonneau elsewhere in the plan) and remote_seat_cooler_request (no seat coolers on this Model 3 - seat_fan_front_left/right absent), plus set_preconditioning_max and set_recirculation, for which I confirmed (against proto/vehicle.proto for set_recirculation, and by reasoning about is_preconditioning tracking actual HVAC activity rather than the override flag for set_preconditioning_max) that no ClimateState field reliably reflects the command's effect, so a live actuation could not be safely verified or restored per the plan's mandatory safety rules. This was a deliberate safety-driven scope decision, not an oversight.

Bug found and fixed live: remote_seat_heater_request's medium level (seat_heater_level=2) built the protobuf field name 'SEAT_HEATER_MEDIUM', but the proto's actual oneof field is 'SEAT_HEATER_MED' (proto/car_server.proto) - this raised AttributeError at call time (not caught by pyright, since it's a dict-of-string-keys-expanded-as-kwargs pattern) and has been broken since the method was first written. Fixed the string and the misleading comment above it (which incorrectly documented HvacSeatCoolerLevel enum values instead of the actual SEAT_HEATER_* names used), and added a dedicated regression test (test_medium_level_regression) locking the correct proto field name via WhichOneof.

Also documented in AGENTS.md: the remote_heater_control_enabled gate discovery, and a general caution about the dict-of-string-kwargs pattern used for HvacSeatHeaterAction/HvacSeatCoolerAction construction bypassing pyright's static checking (explains why the medium-level bug went unnoticed).

Added tests/test_ble_climate_commands.py: 18 new mocked-transport regression tests covering proto construction and reply/rejection decoding for all 15 climate commands (including the deferred-live ones, per the existing PR-CT precedent of shipping mocked-transport-only coverage for commands that can't be live-verified on this rig). Full test suite (109 tests) passes, ruff clean, pyright strict clean. The car was left in its exact original climate state, confirmed via a final full climate_state() read before disconnecting.

What Changed

  • Fixed BLE signed-command construction for medium seat-heater level by using the proto's SEAT_HEATER_MED oneof field, with regression coverage for the generated climate action payload.
  • Added mocked BLE transport coverage for the climate command group, including HVAC start/stop, temperature, keeper/preconditioning, cabin overheat, recirculation, seat comfort, and steering-wheel heat commands.
  • Documented BLE climate command behavior and vehicle-side limitations discovered during verification, including unsupported COP temperature and the remote_heater_control_enabled gate for remote comfort commands.

Risk Assessment

✅ Low: The branch makes a narrow protobuf field-name correction and adds focused mocked BLE coverage without changing broader command flow or public API behavior.

Testing

Inspected the PR-4 climate diff, ran the focused BLE climate suite and the full test suite successfully, then generated reviewer-visible JSON evidence showing the public VehicleBluetooth climate calls produce encrypted INFO-domain protobuf actions, decode vehicle-style rejections, and map seat-heater level 2 to SEAT_HEATER_MED; transient uv/pytest artifacts were removed from the worktree.

Evidence: BLE climate command evidence
{
  "capture_count": 16,
  "commands": [
    {
      "command": "auto_conditioning_start",
      "decoded_action": {
        "power_on": true
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacAutoAction"
    },
    {
      "command": "auto_conditioning_stop",
      "decoded_action": {
        "power_on": false
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacAutoAction"
    },
    {
      "command": "set_temps",
      "decoded_action": {
        "driver_temp_celsius": 21.5,
        "passenger_temp_celsius": 19.0
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacTemperatureAdjustmentAction"
    },
    {
      "command": "set_climate_keeper_mode",
      "decoded_action": {
        "ClimateKeeperAction": 1
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacClimateKeeperAction"
    },
    {
      "command": "set_preconditioning_max",
      "decoded_action": {
        "manual_override": true,
        "on": true
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacSetPreconditioningMaxAction"
    },
    {
      "command": "set_bioweapon_mode",
      "decoded_action": {
        "manual_override": true,
        "on": true
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacBioweaponModeAction"
    },
    {
      "command": "set_cabin_overheat_protection",
      "decoded_action": {
        "fan_only": true,
        "on": true
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "setCabinOverheatProtectionAction"
    },
    {
      "command": "set_cop_temp_not_supported",
      "decoded_action": {
        "copActivationTemp": "CopActivationTempMedium"
      },
      "decoded_reply": {
        "reason": "not_supported",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "setCopTempAction"
    },
    {
      "command": "set_recirculation",
      "decoded_action": {
        "on": true
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacRecirculationAction"
    },
    {
      "command": "remote_seat_heater_request_medium_regression",
      "decoded_action": {
        "seat_heater_level_oneof": "SEAT_HEATER_MED",
        "seat_position_oneof": "CAR_SEAT_FRONT_LEFT"
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacSeatHeaterActions"
    },
    {
      "command": "remote_seat_heater_request_remote_settings_disabled",
      "decoded_action": {
        "seat_heater_level_oneof": "SEAT_HEATER_LOW",
        "seat_position_oneof": "CAR_SEAT_FRONT_LEFT"
      },
      "decoded_reply": {
        "reason": "cabin comfort remote settings not enabled",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacSeatHeaterActions"
    },
    {
      "command": "remote_seat_cooler_request",
      "decoded_action": {
        "seat_cooler_level": 2,
        "seat_position": 1
      },
      "decoded_reply": {
        "reason": "",
        "result": true
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacSeatCoolerActions"
    },
    {
      "command": "remote_auto_seat_climate_request",
      "decoded_action": {
        "on": true,
        "seat_position": 1,
        "seat_position_source": "AutoSeat.FRONT_LEFT (1-indexed)"
      },
      "decoded_reply": {
        "reason": "cabin comfort remote settings not enabled",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "autoSeatClimateAction"
    },
    {
      "command": "remote_steering_wheel_heater_request",
      "decoded_action": {
        "power_on": true
      },
      "decoded_reply": {
        "reason": "cabin comfort remote settings not enabled",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "hvacSteeringWheelHeaterAction"
    },
    {
      "command": "remote_steering_wheel_heat_level_request",
      "decoded_action": {
        "stw_heat_level": "StwHeatLevel_Low"
      },
      "decoded_reply": {
        "reason": "cabin comfort remote settings not enabled",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "stwHeatLevelAction"
    },
    {
      "command": "remote_auto_steering_wheel_heat_climate_request",
      "decoded_action": {
        "on": false
      },
      "decoded_reply": {
        "reason": "cabin comfort remote settings not enabled",
        "result": false
      },
      "encrypted_payload": true,
      "sent_domain": "DOMAIN_INFOTAINMENT",
      "vehicle_action_oneof": "autoStwHeatAction"
    }
  ],
  "notes": [
    "Every captured command sent an encrypted RoutableMessage to DOMAIN_INFOTAINMENT.",
    "remote_seat_heater_request level 2 decodes to SEAT_HEATER_MED, proving the PR-4 regression fix.",
    "Vehicle-style rejected replies decode to the same public dict shape returned to callers."
  ],
  "scope": "BLE climate command group through VehicleBluetooth public methods over mocked signed-command transport",
  "unique_climate_command_count": 15,
  "vin_used_by_mock": "5YJXCAE43LF123456"
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • uv run pytest tests/test_ble_climate_commands.py
  • uv run pytest tests
  • Generated /tmp/no-mistakes-evidence/01KX36CNX8N4J2MQXBV2T4E4HY/ble_climate_command_evidence.json by invoking VehicleBluetooth public climate methods over tests/ble_mocked_transport.py and decrypting each sent RoutableMessage.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

firstmate crewmate added 2 commits July 9, 2026 20:35
Live-verified snapshot->act->verify->restore->confirm against the test
car for auto_conditioning_start/stop, set_temps, set_climate_keeper_mode,
and set_cabin_overheat_protection. Live-attempted the remaining commands
and documented two real vehicle-side constraints discovered in the
process (not library bugs): set_cop_temp is rejected "not_supported" on
this car, and every remote seat/steering-wheel comfort command is gated
by the read-only remote_heater_control_enabled flag. set_bioweapon_mode,
remote_seat_cooler_request (no HW on this Model 3), set_preconditioning_max,
and set_recirculation (no observable ClimateState field) ship with
mocked-transport tests only.

Also fixes a real bug found live: remote_seat_heater_request built the
proto field name "SEAT_HEATER_MEDIUM" for level=2, but the proto's actual
field is "SEAT_HEATER_MED" - medium seat heat has been broken since
inception. Locked with a regression test.
@Bre77
Bre77 force-pushed the fm/tfa-ble-pr4-climate-m6 branch from 2344ad6 to 9bb02f5 Compare July 9, 2026 10:36
@Bre77
Bre77 merged commit 6f996a5 into main Jul 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant