MQTT data decoding guidelines #222
Replies: 3 comments 2 replies
-
OutlookWhile more device models must be decoded and being described first, there is still a long way to go before the MQTT data can be commonly integrated into the Api library and the existing cache structures. Following are challenges and concerns:
In the long term, following enhancements could be possible for the library and the HA integration with the parallel MQTT interface:
|
Beta Was this translation helpful? Give feedback.
-
|
I can not stress enough how awesome your work here is, thank you for this! |
Beta Was this translation helpful? Give feedback.
-
MQTT command and state analysis and descriptionMeanwhile the MQTT command root topic was added to the mqtt_monitor as well. Once subscribed, you can see all commands that are send via the App to your device via the server. Following is the Realtime Trigger example command message sent to the device: Received message on topic: cmd/anker_power/A17C0/AZxxxxxxxxxxxx06/req
{'head': {'version': '1.0.0.1', 'client_id': 'android-anker_power-39xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx7c-d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxcb', 'sess_id': '1', 'msg_seq': 1, 'cmd': 17, 'cmd_status': 2, 'sign_code': 1, 'seed': '1', 'timestamp': 1760291317, 'device_pn': 'A17C0', 'device_sn': 'AZxxxxxxxxxxxx06'}, 'payload': '{"device_sn":"AZxxxxxxxxxxxx06","account_id":"39xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx7c","data":"/wkfAAMADwBXoQEiogIBAaMFAywBAAD+BQP16etofw=="}'}
2025-10-12 17:48:37 Device hex data:
ff:09:1f:00:03:00:0f:00:57:a1:01:22:a2:02:01:01:a3:05:03:2c:01:00:00:fe:05:03:f5:e9:eb:68:7f
--------------------- Solarbank_1 / A17C0 / 0057 / Header ----------------------
ff 09 : 2 Byte Anker Solix message marker (supposed 'ff 09')
1f 00 : 2 Byte total message length (31) in Bytes (Little Endian format)
03 00 0f: 3 Byte fixed message pattern (supposed `03 00/01 0f` for send/receive)
00 57 : 2 Byte message type pattern (varies per device model and message type)
: 1 Byte optional message increment ( 0)
-- Fields --|- Value (Hex/Decode Options)---------------------------------------
Fld Len Typ uIntLe/var sIntLe floatLe dblLe/4int
a1 01 -- 22
└-> 1 unk b'"' --> pattern_22
a2 02 01 01
└-> 2 ui 1 1 --> set_realtime_trigger
a3 05 03 2c:01:00:00
└-> 5 var 300;0 300 0.000 44;1;0;0 --> trigger_timeout_sec
fe 05 03 f5:e9:eb:68
└-> 5 var -5643;26859 1760291317 8912574951632641944715264.000 245;233;235;104 -> msg_timestamp
--------------------------------------------------------------------------------In this way you can figure out the various bytes and message types that are used to modify device settings via MQTT. In order to implement those, full message dumps will be required since all fields have to be constructed for command messages. Also the valid setting values must be described. How to decode device commands and corresponding state changesFirst some general rules:
In order to simplify this approach, the mqtt_monitor can now be started with command line options. This allows to automate launch options and the runtime of the monitor (e.g. for regular dumps via a cron job to monitor long term value behavior of unknown field values). If you use the mqtt_monitor for command decoding, give each dump file a corresponding prefix to recognize afterwards which device control was changed in the dump. Once you launch the monitor and Anker app in parallel, there is no need to enable the realtime trigger in the monitor. It will be triggered permanently by the app and you will see those 0057 trigger commands as well. Once you have a finished dump file, you can use a little tool that was started by @jmozmoz (Many thanks for this). An enhanced version of the tool is now available in the library and called grep_mqtt_cmd.py. It is not commonly described, because it cannot be used as is. You need to modify the source code and adopt it to the particular filename and device message types that you want to analyze. What does it do for you? Here is a short description how to adopt: # define screen width for diff lines
screen_width = 180
# define filename to be verified
test_file = Path(__file__).parent / "mqttdumps" / "A1763" / "CMD.AC.Smart.Modus.txt"
#test_file = Path(__file__).parent / "mqttdumps" / "A17C0_mqtt_dump_2025_14_02__22_14_35.txt"
# define messages that should not be printed
excluded_msgs = ["0421", "0900", "0057", "0040", "0857", "0901", "0902", "0903"] # A1763
#excluded_msgs = ["405", "0057", "0040", "0857"] # Solarbank
# define status message types that should be compared after a printed message
compare_msg = "0421"
#compare_msg = "0405"
# define words in lines that should be excluded in found differences
skip_diff_words = ["fd ", "fe ", "timestamp"]And here is an example snippet of the output, which shows the setting changes and state fields for AC output mode (smart/normal toggle): 10:08:42 INFO: ------------------------- Pps / A1763 / 0101 / Header --------------------------
ff 09 : 2 Byte Anker Solix message marker (supposed 'ff 09')
21 00 : 2 Byte total message length (33) in Bytes (Little Endian format)
03 00 0f: 3 Byte fixed message pattern (supposed `03 00/01 0f` for send/receive)
01 01 : 2 Byte message type pattern (varies per device model and message type)
: 1 Byte optional message increment ( 0)
-- Fields --|- Value (Hex/Decode Options)---------------------------------------
Fld Len Typ uIntLe/var sIntLe floatLe dblLe/4int
a1 01 -- 22
└-> 1 unk b'"' --> pattern_22
a6 02 01 00
└-> 2 ui 0 0 --> set_ac_output_mode
fd 0e 00 31:37:36:34:38:33:39:33:32:32:34:38:37
└-> 14 str b'1764839322487' --> msg_timestamp (2025-12-04 10:08:42)
--------------------------------------------------------------------------------
Found differences in: 0421
a4 1b 04 00:00:00:00:64:00:32:01:00:00:00:00:00:00:00:0a:00:01:00:00:00:00:00:64:0 | a4 1b 04 00:00:00:00:64:00:32:00:00:00:00:00:00:00:00:0a:00:01:00:00:00:00:00:64:0
1:03 | 1:03
└-> 27 bin b'\x00\x00\x00\x00d\x002\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00 | └-> 27 bin b'\x00\x00\x00\x00d\x002\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00
\x00\x00\x00d\x01\x03' --> {'04': {'name': 'ac_input_limit', 'type': b'\x02'}, '20': {'n | \x00\x00\x00d\x01\x03' --> {'04': {'name': 'ac_input_limit', 'type': b'\x02'}, '20': {'n
ame': 'ac_fast_charge_switch', 'type': b'\x01'}, '22': {'name': 'set_output_port_memory_ | ame': 'ac_fast_charge_switch', 'type': b'\x01'}, '22': {'name': 'set_output_port_memory_
switch', 'type': b'\x01'}} | switch', 'type': b'\x01'}}
10:08:47 INFO: ------------------------- Pps / A1763 / 0101 / Header --------------------------
ff 09 : 2 Byte Anker Solix message marker (supposed 'ff 09')
21 00 : 2 Byte total message length (33) in Bytes (Little Endian format)
03 00 0f: 3 Byte fixed message pattern (supposed `03 00/01 0f` for send/receive)
01 01 : 2 Byte message type pattern (varies per device model and message type)
: 1 Byte optional message increment ( 0)
-- Fields --|- Value (Hex/Decode Options)---------------------------------------
Fld Len Typ uIntLe/var sIntLe floatLe dblLe/4int
a1 01 -- 22
└-> 1 unk b'"' --> pattern_22
a6 02 01 01
└-> 2 ui 1 1 --> set_ac_output_mode
fd 0e 00 31:37:36:34:38:33:39:33:32:37:35:32:35
└-> 14 str b'1764839327525' --> msg_timestamp (2025-12-04 10:08:47)
--------------------------------------------------------------------------------
Found differences in: 0421
a4 1b 04 00:00:00:00:64:00:32:00:00:00:00:00:00:00:00:0a:00:01:00:00:00:00:00:64:0 | a4 1b 04 00:00:00:00:64:00:32:01:00:00:00:00:00:00:00:0a:00:01:00:00:00:00:00:64:0
1:03 | 1:03
└-> 27 bin b'\x00\x00\x00\x00d\x002\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00 | └-> 27 bin b'\x00\x00\x00\x00d\x002\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x01\x00\x00
\x00\x00\x00d\x01\x03' --> {'04': {'name': 'ac_input_limit', 'type': b'\x02'}, '20': {'n | \x00\x00\x00d\x01\x03' --> {'04': {'name': 'ac_input_limit', 'type': b'\x02'}, '20': {'n
ame': 'ac_fast_charge_switch', 'type': b'\x01'}, '22': {'name': 'set_output_port_memory_ | ame': 'ac_fast_charge_switch', 'type': b'\x01'}, '22': {'name': 'set_output_port_memory_
switch', 'type': b'\x01'}} | switch', 'type': b'\x01'}} You can directly see that only field a4 of status 0421 reflects a change after each toggle. The diff lines are not cut, but they are wrapped completely into the column width (screen width / 2), so you can find the difference anywhere in that row. Look at the existing device descriptions to see the various formats how they describe fields. The 'type' description is always required if the field type does not use any of the 4 base types above. Multibyte value changes should be easier to identify than simple toggles. Just copy the value bytes as set in the command message. You should find the same pattern in the status message somewhere. Note that multi byte values are always in reversed byte order (little endian format), just in case you want to convert values manually between Decimal numbers from the App and hex bytes as they may be reflected in the MQTT messages. Sometimes a device setting can also flip just one or more bits in a byte value field, typically when the field is bin type. Then a proper bitmask value must be identified and described, that is used to extract the value of the corresponding bits only. As such, the complete hex or decimal value of field may be meaningless, and must be seen as 8 bits where the toggle can change only one or more bits without modifying the others. This will result in completely different hex and decimal values, depending on the combination of existing and modified settings. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This document describes the general decoding requirements for MQTT data of your Anker Solix device.
MQTT Overview
MQTT data as it can be received from the Anker MQTT server is a binary, proprietary format. With the great ground work from people in flip-dots SolixBLE project, especially from @LeagueRaINi in this comment, the general data structure is described as he discovered it for the BT traffic. Fortunately with the great work from @rschoebel in discussion #218, we figured out that the MQTT data packets follow the same BT binary structure. So the data is not encrypted like the power Api data as it was originally assumed, it is proprietary hex data that is just base64 encoded in the MQTT message.
At the end it makes sense that BT and MQTT data structures are the same, since both interfaces are direct device interfaces from the mobile App to control the device in same way. The BT interface is just used for the local communication (and limited in distance), while the MQTT server is the remote communication interface via the cloud. There is additionally the power cloud Api interface, but the power cloud provides data and services around systems, which are a combination of one or more devices that interact. Furthermore all energy tracking and stats are done in the power cloud only. The mobile App combines and merges the data from all those interfaces in a way, that the source of the data is not recognizable. Only the direct device real time data is definitely being pulled directly from the MQTT server. The mobile App home screen data for Solarbank systems is probably from the power cloud server . They may however update almost in realtime, because an owning account can trigger real time data publishing via the MQTT server. While the power cloud servers are probably also subscribed to all your device topics, they also receive much more MQTT data while you have opened the system in the mobile App. Therefore the home screen can provide more frequent data updates as well (implemented for Solarbank 2+ systems). Other systems like power panels or X1 seem to map the MQTT data directly into the home screen, since they do not seem to have power consumption data on the power cloud.
Once an MQTT client subscribed to the MQTT cloud server under your specific device topic, it can receive data as provided regularly from your device. The frequency depends on the device model and its current state. Devices in standby will rarely send MQTT data to the cloud, while regular updates vary in a 60-300 seconds range typically.
Now you may wonder why the mobile app can display real time data for your device. Well, there is a trigger capability via the MQTT server to request real time data updates for a certain period of time. Fortunately, with great support from @rschoebel, we figured out the topic and data package that is published for the update trigger command and I was able to put that all into a framework that can be utilized in this Api library. This real time update trigger capability is also used in the mqtt_monitor tool.
While there is no local MQTT capability, the data via the MQTT server provide lots of other capabilities that still have to be evaluated. But this also comes with a challenge, and that is the binary format of the data. Since each device type, even various device configurations, can result in different binary data patterns and different fields or message types, it is up on the device owners, which is YOU, to monitor, decode and document the data stream for your specific model type.
And for the proper value decoding you have to get creative, and you need to do a lot of testing, setting changes and data comparisons, to identify which bytes reflect which values or settings of the device.
In order to simplify that approach, the library has an easy to use mqtt_monitor tool, that allows you to control the data flow and aids in various byte conversions already, in order to give you an idea what a message data 'field' could represent. The monitor uses color highlighting to simplify the data decoding. Following is a sanitized screenshot how such an MQTT message looks like for my device (unfortunately the solarbank battery was empty already when I took it, therefore you see only few values):
The decoder shows already field descriptions that must first be defined in the mqttmap.py module. The monitor structures the binary data already into the used Solix data pattern as it is known so far. Therefore various device message types can be structured in the same way and a common description pattern can be used.
So far there have been identified 7 different data field types 0x00 - 0x06, they are described in detail in the mqttmap.py module.
MQTT Monitor
In the monitor you just have to enter your Anker account credentials and country, then you will be asked which of the found owning devices under your account you want to monitor.
Note
It seems that only owning devices allow topic subscription, therefore the mobile App also has very limited capabilities once used with a system member account. Devices cannot be shared with members and members can typically not access any device of the shared system at all. They cannot see any real time data in the home screen either, since the App cannot trigger real time data updates through MQTT servers.
You can optionally log the screen output to a file for later review, because messages can wrap really fast while real time data is being received. Following is the usage menu of the monitor while monitoring messages. It reacts on key press. This was tested on Linux and Windows platforms, so it should hopefully also work on Mac OS.
The monitor will initially subscribe to the device root topic, but not trigger real time updates yet. So initially it will receive messages in the regular device intervals if you don't use the mobile App in parallel. Once messages from various topics are received, you can also toggle subscription to a dedicated topic. If you need to pause the message printout while using the message decoding view, you can simply unsubscribe from topics for immediate pause. To get real time messages, you need to enable the real time trigger. This will use a timeout of 60 seconds but continue to trigger within the timeout interval until you disable the real time trigger again. After disabling, you have to wait until the used timeout of 60 seconds is over. While RT trigger is active, the monitor will publish another trigger every 60 seconds to keep the device in real time data publish mode.
Note
The mobile app seems to use a real time trigger timeout of 5 minutes, the maximum timeout is unknown, but large timeouts don't make sense either, if the trigger can be republished as required. In order to have NO realtime triggered messages in the monitor, you have to wait at least 5 minutes after you closed the mobile app to ensure that its last realtime trigger timed out.
The value screen of the monitor shows the active monitor settings, the accumulated value extractions (if there are already field descriptions defined in mqttmap.py), some MQTT statistics, the received topics and the last message details. Following is a sanitized example,
In principle you need the message decoding output to identify the value representation of various fields and bytes, while the value screen allows to verify proper value extraction from byte data according to the mapping descriptions. The value screen is also useful to monitor descriptions for their correctness over longer periods. Sometimes you can only guess what a field value may stand for (for example output power, or internal total energy yields which may not be published via the App), while in another situation you discover that it shows values that must be a different interpretation of the field.
Documentation for your device type
In order to describe the field mappings to values, you should create a discussion post here in the repository (one per device model number). Since each device may use various message types, various patterns may have to be described. You can use the A17C0 description as example.
Optionally you can also enhance the mapping directly in the SOLIXMQTTMAP and create a pull request with your changes. The known field value formats are described in class
DeviceHexDataTypes. Some fields are base values, and their conversion was found to be fixed. Their corresponding value conversion is highlighted in color, other conversion types for those fields can be ignored typically. But there are other fields which may be composed of various formats, values etc, especially 0x05 and 0x06 format types. The conversion options are just options. You have to get creative to understand the real byte interpretation. You may need manual and other byte type conversion as well. Do not rely only on the presented conversion options of the built in decoder....Due to the data fuzziness, it is important to document the description with example value calculations here as well. The resulting unit should also be documented, typically power is in W, Energy in kWh and others may be %, °C or other units.
Important
Do not make final field descriptions before you validated the description in various device scenarios for correctness! You need to avoid to use different names for same value types across various devices. Preferably the power Api field names should be used also for MQTT data descriptions as long as they have the same value.
You can use field hints by ending the description name with a '?' to indicate that this is not validated yet and value must be used with care.
Tip
Some base fields also require creative conversions. For example software versions use integer value fields. But sometimes a byte stands for a single version digit, for other devices the whole byte may stand for the version, e.g. int 215 for version "2.1.5".
If field names contain "sw_" or "version", their value(s) will therefore be converted into a version string. Otherwise values are typically not converted into strings, except the bytes are supposed to use the 0x00 base type for string format.
If byte values need a conversion to correspond to final field value, this can be specified with a "factor" value in the field description. It will be used as multiplier for the byte value, while the float precision of the factor is being applied for int values.
Additional Reference:
The Anker Solix X1 now officially supports the modbus protocol. While those binary data format structure is different than the MQTT/BT format structures, the individual value format/unit/length/type may be identical. So looking at the supported values in this guide may help to understand which values and settings may be reported via MQTT and how the bytes may have to be decoded, especially for X1 device types.
So now its on you to understand MQTT data of your device.
Tip
Use your mobile App in parallel to the MQTT monitor and compare value decoding options or identify bit masks for setting changes that you perform in the App. Compare byte patterns to previous message byte patterns to recognize setting changes. Once a change is applied in the App, it is typically visible in the next message containing that setting, There is no big delay if you trigger real time data updates, and while you have the App open, it does the realtime trigger for you anyway. In that case, you can only 'u'nsubscribe from all messages to stop message output in the monitor while the App is opened.
Happy decoding!
Update Dec 2025:
It turned out that not every device reacts on real time trigger in each condition. However, it appears that those devices always react on a single status request command, where the same status messages are published as with the active realtime trigger (0405 and others).
So in principle the status request is useful for one immediate set of status messages, while the real time trigger may repeat that automatically in 3-10 second intervals for the given period of time (if accepted by the device).
Important
Depending on Anker's implementation of the status request command per device, the device may send only the typical real time status message, but NOT additional status messages types that may only be published with the realtime trigger active.
One example of missing status messages after status request command are Expansion status messages for SB2 and later devices. That means unique data only found in extra status messages may require the realtime trigger for the particular device.
The monitoring and export tools have been adopted accordingly in actual main branch:
I plan to make an additional button available in the HA integration beside the existing Real Time trigger. This will enable choice which of the 2 commands are more reliable to get 'real time' data. The immediate status request should be more reliable, and it can be automated in HA at desired intervals for a single update. With the RT, the device have to publish much more data as requested and maybe at another interval as desired (3-10 seconds typically).
To get a guideline how to decode MQTT commands from your app and the corresponding state change in the status messages, please refer to this comment
Beta Was this translation helpful? Give feedback.
All reactions