Skip to content

feat(gen2): share the Gen 2 status, timer, display and SoC-limit surface with the C1000 G2 - #76

Draft
kb1ibt wants to merge 13 commits into
flip-dots:mainfrom
kb1ibt:pr/gen2-shared
Draft

kb1ibt wants to merge 13 commits into
flip-dots:mainfrom
kb1ibt:pr/gen2-shared

Conversation

@kb1ibt

@kb1ibt kb1ibt commented Sep 20, 2026

Copy link
Copy Markdown

The C1000 G2 (A1763) and C2000 G2 (A1783) run the same platform, and the C2000 G2 PR put the whole Gen 2 settings surface on the C2000 G2 only. This moves the part that is read from the shared c421/c900 tags and set through the shared 4101/4102/4103 groups down to C1000G2, so the A1763 gets it and C2000G2 inherits it:

  • Status: charging_status, charge_discharge_status, firmware_updating, time_remaining / hours_remaining / days_remaining, ac_frequency, ac_charging_power, max_input_power, ac_input_port, ac_timer / ac_timer_remaining, dc_timer / dc_timer_remaining, power_saving_mode_enabled, dc_power_saving_mode_enabled, device_timeout, ac_fast_charge_enabled, port_memory_enabled, is_display_on, display_mode, display_timeout, and the software_version* block.
  • Commands: turn_display_on / turn_display_off, set_display_mode(LightStatus), set_display_timeout(DisplayTimeout), set_max_battery_percentage / set_min_battery_percentage, set_ac_timer / set_dc_timer, get_status_update().
  • Stays on C2000G2: the realtime latch in _post_connect, set_ac_charging_power (its 500-1800 W range is the A1783's), the BP2000 expansion block and the c490 summary.

The decode and the frames were confirmed on the C2000 G2; on the C1000 G2 they read the same tags but have not been confirmed on hardware, which the class note says. #22 has several A1763 owners on 1.1.4.x firmware who can check it. The command tests move to the C1000 G2 with the code; the index table ticks the C1000 G2 rows that come with it.

It also names the Gen 2 DC input port for what it is: the shared XT-60i port takes a solar array or the 12V car adapter, so dc_input_port is now the primary property and solar_port stays as an alias so the existing interface keeps working.

Suite: 408 passed.

🤖 Generated with Claude Code

kb1ibt and others added 13 commits September 13, 2026 14:30
The Anker manufacturer record (company id 0xffff) carries the device MAC,
model, and a capability byte declaring which negotiation path the device
accepts -- readable at scan time, before any frame is sent. Add a parser
for it as the basis for choosing the cleartext vs encrypted handshake per
device rather than by product class.

Capability is length-relative (last byte when present, absent on the F3800),
so it is derived from the sku length rather than read at a fixed offset.
Decoded against the app's own field values for five bench records.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Move the AES-GCM negotiation out of PrimeDevice into SolixBLEDevice so any
device can take it. The path is selected from the capability byte in the
device's advertisement when the caller passes one, otherwise from the class
default (encrypted for Prime devices, plain text for the Solix power
stations). The device's declared MTU and auth mode from 4803 are echoed in
4005, and the timezone confer carries the local UTC offset.

A fresh P-256 key pair is generated for every negotiation instead of the
hard-coded key pairs; the keys the recorded test vectors were captured with
move to the test constants and are pinned by the fake_time fixture. The
cipher and ECDH primitives live in utilities as plain functions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The client registration on the encrypted path now carries a client token
(the class identifier unless the caller passes one) and the link only counts
as negotiated once the device accepts it. Firmware that pairs clients
answers a new token with status 9 and waits for its button: the device
reports pairing_required, runs the registered pairing callbacks so a user
can be prompted, and connect() keeps waiting instead of restarting the
handshake. The grant the device then pushes on pattern 030101 authorizes
the link. The device remembers the token, so later connections with the
same token are accepted without a press.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
_post_connect on the C1000 G2 and the MagGo 3-in-1 still called
_send_command with the pre-parameters signature, so the subscribe that
starts their telemetry was never sent. The raw payload a10121 is parameter
a1 with value 21, so the bytes on the wire are unchanged. Adds a command
test for the C1000 G2 subscribe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Some devices post a second live frame beside their telemetry stream: a
protobuf message built against a schema the device names in the frame.
The walker flattens it to a .path map, with packed integer arrays declared
per schema so they are not mistaken for sub-messages, and a per-schema
field map names and scales the values. A frame naming an unknown schema is
kept by path and logged once. The summary, its schema name and the time of
the last post are exposed on the device, and state-change callbacks run
when one arrives.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds the C2000(X) Gen 2 power station on top of the C1000 G2, which shares
its telemetry framing and AC/DC control. It negotiates on the encrypted
path, which its current firmware requires, subscribes and arms the
realtime latch on connect, and decodes the 4103 system group (display,
timeouts, SoC limits), the status and time-remaining fields, the version
block, and the BP2000 expansion block. Status is polled with
get_status_update() like the other power stations.

The c490 device summary the unit posts about every nine minutes is decoded
through field maps for its two current schema revisions, and the energy
ledgers and DC input voltage it carries are exposed as properties that
read None until a post arrives; the summary is gated by a schema map the
device only holds after reaching the vendor cloud, so a unit kept off the
network may never post one.

The command test harness now checks every packet a command sends, so a
setup routine that sends two can be asserted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The accepted set was the three values that happened to appear in the
captures. The app's picker offers 10 s, 20 s, 30 s, 1 min, 5 min and
30 min, so the device now accepts all six and DisplayTimeout gains S10
for the shortest one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each group-command setting gets a PARAMETERS_* constant with a lambda for
its value, like the other devices, instead of a helper that built the
dictionary at call time.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… C1000 G2

The C1000 G2 and C2000 G2 run the same platform, but the settings surface
added with the C2000 G2 lived only on that class. Everything read from the
shared telemetry tags and set through the shared 4101, 4102 and 4103
groups now lives on C1000G2, with its command tests, and C2000G2 inherits
it. The C2000 G2 keeps what is its own: the realtime latch, its AC
charging-power range, the expansion block and the device summary. The
shared fields were confirmed on the C2000 G2 and read the same tags on the
C1000 G2, where they await confirmation on hardware.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… as an alias

The Gen 2 DC input is the shared XT-60i port: a solar array or the 12V
cigarette-socket car adapter both report there, so solar_port understated
it. dc_input_port is now the primary property; solar_port stays as an alias
so the existing interface (and HaSolixBLE) keeps working. Both note the
port. C2000 G2 inherits it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The XT-60i DC input also takes the Anker SOLIX Alternator Charger (DC-DC
from the vehicle's starter battery), alongside solar and the 12V car
adapter; the docstring now lists all three. Battery-to-battery vehicle
charging and RV expansion use the separate expansion connector, not this
port, so the note distinguishes them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ladamczyk-it

Copy link
Copy Markdown

Hardware confirmation on a C1000 Gen 2 (A1763), firmware with capability byte 04 (encrypted handshake, see #70):

  • SoC limits: 4103 with aa (max) / ab (min), type 01, 1 byte each → applied immediately; telemetry d9[4]/d9[5] follows (tested 90→95→90 and 10→15→10).
  • AC charge limit: 4101 with a4, type 02, 2 bytes LE watts (100–1200, step 100) → telemetry a4[5:7] follows within ~1 s (tested 500→1100→500, 500→300→500).
  • AC ultrafast charge: 4101 with a7, type 01, 0/1 → telemetry a4[21] follows (tested on→off).

Also noticed: 4090 (usage mode a2, backup SoC a5, TOU schedule a7 with a3/a4/a6) is accepted over BLE and reflected in d9, but the station never activates a tariff (active tariff stays 0, AC input stays on). TOU on this model seems to be cloud-driven.

Happy to open a follow-up PR with set_ac_charge_limit() and the fast-charge switch once this is merged.

This branch has not been deployed

No deployments
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