2b4f4f034da79ff9a476ba0cd78499d0a7ea3dc4
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4ff6242c8f |
The last message in the map, and it reboots the charger
0108 was the one thing in the MQTT inventory nobody had wired: the device power mode, whose single documented value restarts the charger. It is the only way to reboot a charger that is on neither a CSMS nor the local network — which is most of them — so the cloud transport sends it now, and "reset" reaches it too, since that is what the OCPP path has always called the same act. Nothing waits for a confirmation: the device that would send it is the device rebooting, so the command answers at once and says the charger drops off the cloud for about a minute. The gate is unchanged and now covers both spellings — an explicit confirm plus a password step-up, audited either way. Modbus still refuses, because no register does this, but its refusal now names both transports that can rather than only the CSMS. Both clients already had the reset button and its password prompt; they were hidden in every mode that reads the device, which is why the cloud never showed one. Modbus is now the only mode without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b2d333a63f |
The charger was never asked what it is set to
The trigger buys telemetry and only telemetry, so a charger that has been read a hundred times and commanded none reports amps, volts and nothing else: no schedule, no balancing, no Modbus server, not even its firmware. The message that asks for that half is 0040, and the reference keeps it commented out because the app sends its timestamp without a value type. The app is what the charger answers, so the oddity is reproduced rather than corrected — sent when the settings half is missing or older than ten minutes, waited four seconds for, and after three unanswered requests still sent but no longer waited on. The three settings the panel has and the writer did not — swipe up, swipe down, smart touch — are writable now, which is all eleven of the 0100 commands. Nothing else in the map was missing: every named field of every message was already decoded, and the raw keys the card shows are fields the reference does not name either. Both cards drop a row with nothing in it, which turned a charger that reports only its ceiling into a charger that reports no current range at all. Half a range is still a bound. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3f9d5b943f |
Four questions asked of an account that answers twenty-one
The connector called ten endpoints of the read surface the map lists, and the charger card showed four views. Everything else an EV charger can reach is now a capability too: the sessions and the history, the savings, the sharing, the binding, the group, the Wi-Fi, the firmware and its update log, the tamper records, the site's own detail, price, networks and energy — plus the vehicle catalogue, dynamic pricing, the currencies and the notification views. Thirty-eight endpoints, one action each. The two message views are GET, so the request path grew a GET half that shares the login retry with the POST one. The per-charger fan-out asks all of them, six at a time rather than one after another, and a charger that belongs to a site brings that site's four views with it once the by-serial lookup has found it. A view that answers with nothing now says so instead of vanishing: the station record is empty for a standalone charger because it has no station, which is an answer worth reading. And "source 0" in the OCPP box carries the address the account's endpoint list gives it. Anker's account-level writes stay out, as do the endpoints whose payloads were only ever read out of the app package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fb42791f8d |
The card beside it had boxes, so this one gets boxes
Charger information was one long list of everything the service knows; the readings card next to it had been splitting its fields into a box per group all along. Same treatment here: Device, Status, Network and On the account, plus the service's own fields and the per-charger views, each in its own sunken section under a heading. Both clients, since the web and the phone draw the same card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1a7f04cba0 |
A dash printed above the value it was missing
The Charger information card read "—" beside State and OCPP status while the raw
block three rows below it printed chargerStatus 1 and ocpp_connect_status 2. The
account had answered both. The merge asked for the state as evChargerStatus,
operating_state or status, which is how the standalone and station views spell
it, and the bound-device view — the one this account actually answers from —
spells it chargerStatus. The OCPP state it never asked that view for at all. All
three views now read through one fillDevice, which tries every spelling a view is
known to use, so a value any of them sends reaches the row that was drawing a
dash for want of it.
The same views were carrying the whole box-on-the-wall half unread: the Wi-Fi
network and its MAC, the signal strength, the Bluetooth MAC, the time zone, when
the account bound the charger, how the app can reach it — BLE, Wi-Fi — and the
product shot for the model, which now sits beside the charger's name in both
apps. Named rows, in three languages, the way the register map's readings are
named.
One field wanted the opposite treatment. The device record carries blue_password,
the charger's own Bluetooth pairing password, and the card was printing it in
clear into every screenshot anyone takes of that page. Any leaf key holding a
password, secret, token, private key or certificate is now masked in the raw
block: that the field exists is worth reporting, its value is not.
Four endpoints answer only when a serial is named, so none of them could belong
to the list the card is drawn from, and nothing had ever called them. The station
record, the charging totals, the OCPP backend and the RFID cards now arrive
through a charger-details capability behind
GET …/anker-solix/chargers/{sn}/details, asked for the charger being looked at,
best effort, each view reporting its own failure — an account that is not the
owner cannot read the cards, which is a fact about the account rather than an
error in the read.
Those four are shown under the cloud's own keys, and that is not an oversight.
The REST map documents which endpoints exist and what each is for; it does not
document a single one of their payloads. Naming those fields is the next commit,
made from what actually comes back, now that there is somewhere to see it.
Not touched: the endpoints the map marks ready but unwired — session history,
site price, OTA, sharing, notifications — each a feature rather than a row on this
card; and the unmapped ones, which the map warns delete sessions and unbind
devices with payloads nobody has ever seen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8e2073fc4c |
The map knew the names the card was showing as hex
Every field in the cloud MQTT map that has a documented meaning now reads as a named row, on the same labels the register map uses for the same quantities. The raw block stays, and shrinks to what genuinely nobody has identified — which is the only honest reason for a key like 0410.b9 to be on screen at all. Three fields the reference decodes for nobody are decoded here. ac is where the charge is coming from — off or paused, grid, solar — and it is called chargingSource rather than chargingMode, because that name already belongs to a Modbus register and the last time a cloud field borrowed one, d9 spent a release reporting the wrong thing under the right name. b6 is the session's order id. f1, f2 and f3 are the identity fields the reference marks multi-value: four bytes read as the parts of a version, in the order they arrive, which is what the account view's own firmware string looks like. If the panel shows those parts reversed, the order is the thing to flip — it is the one assumption here that the wire has not yet confirmed. The rest was already decoded and simply never drawn. The readings card now shows the session's start, its id, the charging source, whether a cable is in, the charging window, and — since a reading is worth what its age is — the live-stream flag and both stream clocks, because telemetry and settings arrive on different messages with different triggers. Per-phase session energy joins the phase matrix as a fourth column, appearing on the transport that counts a session and staying away from the one that does not, exactly as the reactive and apparent pair does. The settings block gains the fourteen the register map has no address for: plug lock, auto restart, random delay, the schedule and its mode, the weekend window and how the weekend is handled, the light-off schedule and window, the breaker limit, the solar mode and its minimum current, automatic phase switching, the three panel gestures, and what the two balancing features are watching — the meter and monitor serials by name, their two unpinned numbers as the numbers they are. A local network block says whether the charger's own Modbus server is on and where, which is the answer the Modbus mode's setup screen otherwise has to be given by hand. The device block gains the controller version. A test now holds the line the projection quietly drew: every name in the message maps must reach a snapshot field. A name added to a map without a field to land in would otherwise surface in the raw block looking like something we understood. Left raw: a1, the frame opener the charger echoes back; b7, which the map itself calls unidentified; b9, bc and bd, which appear in no map; the five-minute 0400; and 0857 — a message type the reference's closed inventory of fourteen does not contain and this charger publishes anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
197ff73a39 |
The one card with a button is the one with a crooked arrow
Four of the charging cards fold from a header that is a single button: the title at the left, the arrow hard against the right edge. The fifth has a refresh button in its header, and that button was placed after the toggle — so the arrow ended up a button's width in from the edge, alone among the five, and the eye finds it by searching rather than by knowing where it is. The header now spends its width the way the others do. The heading keeps the title and stays the drag handle, the refresh button takes the place beside the edge, and the arrow is its own control at the end of the row. It folds the card exactly as the heading does, so nothing that worked before stops working; only the order changed. The same layout lives in the Phone App's _FoldCard, with the same fault, so the arrow moves past the action slot there too. All five cards share that widget and only this one passes an action, which is why the other four look identical before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2425a8d3d6 |
A field we have no name for is still a field it sent
Two cards on the Charging page were answering with a fraction of what the charger and the account actually report, and in both the losses happened quietly, in a parse that kept the fields it recognised and dropped the rest on the floor. Charger information asked three account-wide views and kept fourteen fields. A charger registered on its own is absent from the site view, which is the only one of the three carrying state, charge power and OCPP status — so exactly the charger that stands alone got the column of dashes, and nothing said why. The per-charger station record, get_evcharger_station_info, is what the mobile app opens when you tap a charger, and it is the one view that answers for a charger outside a station; it is now the fourth view, asked per charger, a failure there costing that charger's row and no more. Alongside it, every field each of the four views sent is kept as attrs, under the cloud's own key, nested objects joined with a dot and arrays carrying their index. First view to answer a key wins, which is the rule the named fields already merged by. Two hundred keys and two hundred and forty runes per value keep a station record with a session list from becoming the whole card. Charger readings lost data twice over. The frame decoder skipped any field byte its per-message map could not name, and a message type with no map decoded to nothing at all; those fields are now kept under the message and the byte they arrived in — 0410.c9 — decoded but unscaled, because a factor is half of a meaning and we do not have the other half. Then the projection read forty-odd names into typed fields and dropped the remainder: sessionStartedAt, the per-phase session energies, the three touch modes, the load-balance monitor and its meter flag, the solar monitor. Those land in extra, and the list maintains itself — the four accessors note every key they read, extra is what is left, and a field modelled later stops appearing there without anyone remembering to remove it. Keeping unnamed fields had one consequence worth guarding. An unmapped message now decodes to something rather than nothing, and ingest stamped settingsAt for anything that was not telemetry — the timestamp a control command waits on to say the charger acknowledged it. A frame we cannot read is not an acknowledgement, so the stamp is now conditional on the message type being one we map, while its fields are kept either way. Both cards show the remainder as what it is: the service's own key, no unit, no translation, no renaming, under a heading that says whose words these are. The blocks appear only when there is something in them, so a Modbus charger's readings card and a charger the cloud says nothing more about are unchanged. Naming one of these fields is a later commit, made from evidence; inventing a label for it today would only make a guess look settled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1dc20461de |
A raw key, a bare number, and two columns of dashes
Switch the control mode to the Anker cloud and the charging cards still read as though they were built for Modbus. Three separate causes, and none of them was the shared layout — the cards already branch on the control mode in sixteen places, which is why the address fields, the reset button and the skip-delay button each appear only under the transport that has them. The loudest was a missing string. The cloud transport arrived with three keys the templates call and the language files never got: cloudNote, cloudLocalFound and skipDelay. A key missing from English returns itself, deliberately, so that a gap shows up in the UI instead of rendering as a blank — and it did, as "charging.control.cloudNote" sitting in the connection card where a sentence belongs. All three are added, in both surfaces and all three languages, so the Phone App is not left showing the same raw key. The second was an enum wearing one name over two transports. Modbus register 20087 reports the phase mode as 1 single-phase or 3 three-phase; the cloud's own field reports 0 automatic or 1 single-phase. Only phaseMode1 and phaseMode3 had labels, so a cloud charger sitting on automatic rendered "Running on 0" — the enum fell back to printing the number, which is the right fallback and the wrong answer. phaseMode0 is added. Worth naming the shape of this one: it is the same collision that made the cloud's d9 field wrong when it was called chargingMode after the register at 20088, and a third transport reporting a 3 that means something else would break it again. The third was honest but useless. Reactive and apparent power are registers of their own and the cloud has no message carrying either, so over that transport those two columns could only ever be three dashes each. They now appear when the charger actually reports them, which also tidies up a Modbus charger whose firmware leaves them out. The layout stays shared. A value both transports report should keep one name and one row, and what each transport can be told still branches where it has to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
576df58776 |
Go the way the owner's phone already goes
Control had two transports and neither fitted the ordinary customer. OCPP waits for the charger to dial in, which needs a public endpoint it can reach, a certificate, and a firmware willing to talk to our CSMS. Modbus TCP dials the charger, which needs the server on the charger's own network. Between them they cover a charger we host and a charger we stand next to; the common case is a charger behind someone else's router, and that had nothing. It was never unreachable, though. The charger holds a connection open to Anker's own broker — it is how the mobile app drives it from anywhere, and it is the mqttStatus register the Modbus snapshot has been reporting all along. So a third control mode joins that broker as the account: get_user_mqtt_info issues a client certificate, mTLS to aiot-mqtt-eu.anker.com:8883, and commands go out on the same topics the app publishes on. Nothing on the customer's side has to be forwarded, addressed or certificated. What travels is not an API call. The payload is a JSON envelope around a base64 binary frame the device itself speaks — marker, little-endian length, message type, name/length/type/value fields, XOR checksum — so mqttframe.go is a codec rather than a client, written from the message maps in anker-solix-api and anchored on the one frame that project documents byte for byte. A frame whose fields do not tile exactly up to the checksum is refused rather than half-read: these arrive over a link we do not control, and a truncated frame must not read as a charger reporting zeros. Two of the charger's habits shape the rest. It publishes nothing unless asked, so a status read arms a telemetry trigger and waits for the next frame, and a poll inside that window answers from what has since arrived. And a broker connection costs a fetched certificate and a TLS handshake while the plugin manager builds a throwaway instance per request — so the connection lives on the account's shared session beside the auth token, for exactly the reason the token lives there, and closes itself after five idle minutes. The transport also sees two signals no other one does: the boost flag, and the plug and start countdowns. The package doc has said since the first commit that they are never set and the derived mode must do without them. Here they are set, so a charger that has been told to start and is counting down a delay says so rather than sitting in "preparing", and "skip the delay" is offered only while there is a delay to skip. The clients generalise instead of growing a second layout. Both snapshots name the same quantities the same way, so what was Modbus-only in the readouts is now whichever transport read the charger — ModbusStatus becomes ChargerStatus on the phone, mb becomes dev on the web. What each transport can be *told* still differs, and the buttons branch on that: reset and clear-limit stay with OCPP, the timeout and phase registers with Modbus, skip-delay with the cloud. A command a transport has no equivalent for is refused by name, saying which one has it. The cost is worth saying plainly. This leans on Anker's cloud being up and on an unofficial protocol the app may change under us, where Modbus leans on nothing but the LAN. And it is checked against the reference implementation's own worked example rather than against hardware — there is no charger on this end to point it at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
641f427db4 |
The chargers you own, on the phone as well
The web's Charging page grew a real home half while the phone kept a demo one. There, a charger is a record imported from a connected service; here it was a hardcoded row called "Home charger", and the only real thing on the tab was a single OCPP control card. Modbus had been a working transport for a while, and the phone had no way to give it the address it needs. The home tab is now the four cards the web shows, about whichever charger is picked, and the list of the ones you have imported. Control acts on the charger and offers what the transport actually has — boost on Modbus, clear-limit and reset on OCPP. Connection asks for a serial or an address depending on which, and falls back to a text box for a serial the account does not list. Readings render the Modbus snapshot the way it gets asked about: the per-phase matrix, what the charger is doing, what it is set to, what it is, and any alarm word. Information stands without a control mode at all, because what a charger is is known either way; beside it the service's own view of whether it is reachable, asked for when the tab is opened rather than on every build. Rearranging is the one place the two apps differ, for the reason the car's view picker already differs: the web drags the tab bar and the card headings, and on a touch screen the bar owns that gesture and a heading is the fold toggle. Both arrangements are made in a sheet with handles instead, and still saved to chargerTabOrder / chargerCardOrder on the profile — so an arrangement made in either app shows up in the other. Which cards are folded stays on the device. Settings groups its integrations into the same categories the API Server panel does, says Online as well as Offline, and shows firmware in a charger's line. Shared strings are copied out of the web's i18n files rather than retyped, per TRANSLATIONS.md; only the arrange sheet's own three are written here. The readings and information cards look up some sixty keys by name at render time, so models_format_test now guards those the way it guards the car's — an enum value is deliberately left out, since a charger may report a number this release has never heard of and falling back to it is the point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e138fad3f4 |
Anker: every charger on the account, not just the ones outside a station
get_user_bind_and_not_in_station_evchargers is the only list the connector ever asked for, and its name says exactly what it withholds. A charger that belongs to a system is not in it. Its userBindEvChargersCount, though, counts every charger bound to the account — so an owner with two chargers in a system got "authenticated; 2 EV charger(s) bound to account" from the health probe and an empty list from the capability that is supposed to show them. A working login that finds nothing. So the capability now asks every view the cloud has and merges them by serial. The standalone list still answers for chargers standing on their own; get_site_list walks the systems and reads each one through get_scen_info, falling back to get_system_running_info where that is silent — the power-service / HES split charger-state already knows; and get_relate_and_bind_devices contributes model, firmware and the Wi-Fi flag, and discovers anything in the A519 family that the first two missed. Whichever way a charger was registered, one of the three has it. The merge is first-writer-wins per field rather than last view overwriting: the standalone record knows the name, the site record knows the live state, and neither should blank what the other established. A view that fails is a warning on the document instead of an error on the call, because one dead endpoint should not cost the chargers the other two found. Only losing all three is a failure. When nothing comes back at all the response says so in its own words and names the remaining suspect — country picks the regional server, and the wrong one authenticates happily and shows an empty account. The other half of "not showing any chargers" was that neither client ever showed a list. The serial was a text box, and the number is printed on a charger hanging on a wall. Both apps now list what the account holds — name, serial, model, site, state, an offline badge — and hand the serial to the OCPP control card instead of asking anyone to go and read it. Where control is off the list still stands on its own, as the answer to the first question an owner has after entering credentials. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
13f50aa9a4 |
Phone App: add Charging screen + Settings integration tabs
Brings the Flutter app to parity with the recent Web App changes, which touched features the Phone App did not yet have — so this builds the Charging and Integrations subsystems, then applies the tab/fold structure. Charging (new nav destination): split into "Public chargers" (stylized discovery map + demo session + nearby public stations) and "Home chargers" (the real Anker Solix OCPP control card — serial refresh, connector/energy tiles, start/stop, current limit, password step-up on reset — plus the user's home charger list). Gated by the per-user control mode, degrading to a Settings hint when off. Settings: split into "Personal settings" (the existing account/appearance/ profile/security/privacy/danger sections) and "Integrations". The latter holds foldable Toyota and Anker Solix cards over the superadmin -> org -> user cascade: locked fields with "inherited from" notes, org-admin scope switch, enable toggle, save/test with health result, and Anker OCPP token provisioning. Both tabs stay mounted (IndexedStack) so in-flight edits survive a switch. Adds the integration + OCPP control endpoints to api.dart, the resolved IntegrationView/Scope/Field, IntegrationHealth and AnkerControl models, and charging.*/settings.tabs.*/nav.charging strings (en/pl/da) plus settings.integrations.* (en) — mirroring the Web App's own pl/da coverage, which leaves integrations and charger control untranslated as an English fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |