Skip to content

AP300: Add sensors, controls, and encrypted write support - #58

Closed
happytechca wants to merge 8 commits into
Patrick762:mainfrom
happytechca:main
Closed

AP300: Add sensors, controls, and encrypted write support#58
happytechca wants to merge 8 commits into
Patrick762:mainfrom
happytechca:main

Conversation

@happytechca

Copy link
Copy Markdown

Summary

This PR expands AP300 support with new sensors and writable controls, discovered through
register scanning on an Apex 300 unit I recently acquired.

I want to acknowledge upfront that this touches quite a few files (14) across several areas of the codebase

I would have preferred to keep it tighter, but adding writable controls to an encrypted device
required changes that cut across the fields, enums, device reader, and base device layers.

I tried to keep each change minimal and consistent with existing patterns.

AP300 sensors added

  • AC input frequency, voltage, current
  • AC output frequency, voltage
  • DC/AC input/output power
  • Time remaining

AP300 controls added

  • Switches: AC output, DC output (untested; no D1 hub to test), ECO, power lifting, charge from grid
  • Selects: charging mode, working mode, display timeout
  • Number sliders: battery SOC range start/end (0–100%)

New: NumberField

A new writeable field type (extends UIntField) for numeric slider controls like SOC
range. This required:

  • NumberField class in bluetti_bt_lib/fields/
  • get_number_fields() on BluettiDevice
  • NumberField handling in build_write_command() (float → int conversion)
  • Exclusion from get_sensor_fields() so sliders aren't surfaced as read-only sensors

New: WorkingMode enum

Enum with values: CUSTOM (1), SELF_CONSUMPTION (2), BACKUP (4), TIME_OF_USE (5)

New: DeviceReader.write() method

The existing DeviceWriter class explicitly does not support encryption ("Encryption on writes is not yet supported"), and lacks the BLE notification handler and key exchange infrastructure needed to communicate with encrypted devices like the AP300.

Rather than duplicating all of that plumbing into DeviceWriter, this PR adds a write() method to DeviceReader, which already has the full encryption handshake, notification handling, and connection lifecycle in place.

This keeps the encrypted write path minimal and avoids diverging two parallel implementations.

Tests (18 new, all 91 pass)

  • NumberField: writeable, parse, range validation, allowed write types
  • BluettiDevice: get_number_fields(), sensor exclusion, build_write_command with NumberField
  • DeviceReader.write(): switch write, number write, non-writeable rejection
  • Updated BLE mock to handle write commands (action=6) separately from reads

README updates

  • Added AP300 controls to the Controls table
  • Added @happytechca as AP300 contributor
  • Updated write command docs to reflect encryption support

A note to @Patrick762

I'm very open to feedback, suggestions, or alternative approaches. If you'd prefer this broken into smaller PRs, scoped differently, or reworked in any way. Thank you!

Side note: Most of this code has been written by Claude Code and manually reviewed before the PR.

happytechca and others added 8 commits March 20, 2026 09:54
Add 18 new fields confirmed via BLE register scanning:
- Sensors: time remaining, AC input frequency/current, AC output frequency/voltage
- Controls: AC/DC toggle, eco mode (DC/AC), charging mode, power lifting, SOC range
- Version: BMS version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove eco timer modes, eco min power thresholds, and BMS version
fields that add BLE polling overhead without providing actionable
data in Home Assistant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
V2 devices (AP300, etc.) use ECDH + AES-CBC encryption over BLE.
DeviceWriter has no handshake support, so writes silently failed.

Add write(field, value) to DeviceReader which reuses the existing
connect/handshake/notify/cleanup machinery to send encrypted write
commands. This unblocks switch and select controls on encrypted devices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NumberField extends UIntField with is_writeable()=True, enabling
writable number entities in HA. AP300's BATTERY_SOC_RANGE_START (2022)
and BATTERY_SOC_RANGE_END (2023) are now NumberField(min=0, max=100).

BluettiDevice gains get_number_fields() and get_sensor_fields() now
excludes NumberField instances. build_write_command() converts float
values to int for NumberField writes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WorkingMode enum: Custom=1, Self-consumption=2, Backup=4, Time of Use=5
- CTRL_WORKING_MODE SelectField at register 2005
- CTRL_CHARGE_FROM_GRID SwitchField at register 2207
- Both FieldName entries added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses existing DisplayMode enum (SEC30=2, MIN1=3, MIN5=4, NEVER=5)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add AP300 to the Controls table with all validated controls
- Add new control columns (working_mode, charge_from_grid, etc.)
- Add @happytechca as AP300 contributor
- Update write command docs to reflect encryption support
- Remove unused FieldName import from NumberField

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- NumberField: writeable, allowed types, parse, range validation
- BluettiDevice: get_number_fields, sensor exclusion, build_write_command
  with int and float values
- DeviceReader.write: switch field, number field, non-writeable field
- Update mock to handle write (action=6) commands separately from reads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@defire04

Copy link
Copy Markdown

Hey! I noticed we solved the same problem in different ways.

I ran into the exact same encryption write problem on my AC180P and opened PR #59 which fixes DeviceWriter directly rather than adding write() to DeviceReader. My thinking was that mixing write logic into the reader makes both harder to reason about over time, and the reader/writer split is a nice boundary to keep.

Then I noticed that even with encryption working, every toggle still took 5–14 seconds because of reconnecting on every write. So I opened a follow-up PR (#60) that introduces a DeviceConnection class to hold a single shared BLE session between reader and writer — subsequent writes drop to ~0.03s.

If you want to see how it all looks wired up on the HA integration side, I have a test branch here:
defire04/hassio-bluetti-bt @ test-encrypted-writes-and-persistent-ble-connection

If PR #59 lands, the DeviceReader.write() part of your PR would create two parallel ways to do the same thing. The AP300 device definition, NumberField, and WorkingMode enum though stand completely on their own — I think it would also be easier for @Patrick762 to review them independently without the write infrastructure mixed in.

Would you consider splitting that out into a separate PR? Happy to hear any thoughts or feedback on my approach too — always open to suggestions

@happytechca

Copy link
Copy Markdown
Author

@defire04 It's funny we worked on the same issue about a day apart.

I agree your approach is better, keeping the write logic in DeviceWriter rather than mixing it into DeviceReader makes a lot more sense from a separation of concerns perspective. And the persistent BLE connection in PR #60 is exactly what was needed. I had the same 5-14s toggle lag on my AP300 and it was on my list to investigate. Glad you already tackled it, initial tests looks good with my AP300 device.

Here's what I've done to move things forward:

  1. Opened PR #61 with just the AP300 device model — sensors, controls, NumberField, and WorkingMode enum. No write infrastructure or overlap with your PRs. Can be reviewed/merged on its own.

  2. Opened PR #234 on the HA integration side with the number platform and translations for the new AP300 controls.

  3. For testing right now, I've combined your work (PRs Enable write commands for encrypted Bluetti devices (AC180P and others) #59 + Persistent BLE connection for instant writes and original app compatibility  #60) with the AP300 model on my fork branches (ap300-combined on both repos). Everything works great on my AP300 — sensors, all controls, encrypted writes, fast persistent connection. Instructions for other AP300 users to test via HACS are in the hassio PR.

I'll close PR #58 since #61 supersedes it.
I prefer to use your PRs as the foundation for write controls on encrypted devices.

Thanks for your work!

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.

2 participants