With this script, and a little RRDtool wizardry, you can generate graphs like this:
The image shows that there was a power outage between 11:00 and 12:00 (well, actually I pulled the plug for testing ;-).
The official GIT archive is hosted at
Use the command
git clone --branch stable/latest git://git.tvdr.de/bluetti-monitor.git
to clone the archive and check out the latest stable version.
ZIP archive
The source code for the latest version can be downloaded from
https://git.tvdr.de/?p=bluetti-monitor.git;a=snapshot;h=stable/latest;sf=zip
And here's the opening dialog between the AC300 and the IoT server:
[C->S] 20251001-223619 Raw data (20 bytes): 0000 00 01 00 10 49 4F 54 32 32 32 39 30 31 30 30 30 ....IOT222901000 0010 35 30 35 37 5057 [S->C] 20251001-223619 Raw data (8 bytes): 0000 00 01 00 04 68 DD 20 43 ....h. C [C->S] 20251001-223619 Raw data (68 bytes): 0000 10 42 00 04 4D 51 54 54 04 C2 00 3C 00 1A 34 33 .B..MQTT...<..43 0010 39 31 30 31 31 39 30 35 39 36 39 36 38 39 31 33 9101190596968913 0020 31 34 30 37 35 37 32 39 00 10 49 4F 54 32 32 32 14075729..IOT222 0030 39 30 31 30 30 30 35 30 35 37 00 08 34 33 38 30 9010005057..4380 0040 31 36 35 31 1651 [S->C] 20251001-223619 Raw data (4 bytes): 0000 20 02 00 00 ... [C->S] 20251001-223619 Raw data (23 bytes): 0000 00 02 00 13 41 43 33 30 30 26 32 32 33 32 30 30 ....AC300&223200 0010 30 31 XX XX XX XX XX 01XXXXX [S->C] 20251001-223619 Raw data (5 bytes): 0000 00 02 00 01 01 ..... [C->S] 20251001-223619 Raw data (49 bytes): 0000 82 2F 00 01 00 10 50 55 42 4C 49 43 2F 49 4F 54 ./....PUBLIC/IOT 0010 2F 50 4F 57 45 52 00 00 17 53 55 42 2F 41 43 33 /POWER...SUB/AC3 0020 30 30 2F 32 32 33 32 30 30 30 31 XX XX XX XX XX 00/22320001XXXXX 0030 00 . [S->C] 20251001-223619 Raw data (6 bytes): 0000 90 04 00 01 00 00 ......The connection is always initiated by the AC300, with a constant 20 byte packet, presumably telling the server something about the type of this device, or how it wishes to communicate with the server.
The server responds with an 8 byte packet, containing what appears to be a Unix time value in the last four bytes. However, it is about 18 hours behind UTC. Apparently the actual value doesn't matter, the AC300 doesn't set its clock to this value. Even random data works fine here.
Next, the AC300 (the "client") sends a packet indicating that this is going to be an MQTT protocol, which the server confirms with a constant 4 byte packet.
Following is the device type (AC300) and the device's serial number (partially XX'd here for obvious reasons), confirmed by the server with a constant 5 byte packet.
Finally the client sends some MQTT strings and the server confirms with a constant 6 byte packet.
After this initial data exchange, there is a valid connection between client and server, and from here on the client regularly sends data packets to the server every few seconds.
client | server |
---|---|
00 01 ... | 00 01 00 04 11 22 33 44 |
10 ... | 20 02 00 00 |
00 02 ... | 00 02 00 01 01 |
82 ... | 90 04 00 01 00 00 |
C0 00 | D0 00 |
E0 00 | n/a |
Of the client packets only the first one or two bytes are significant to determine the proper server side response. "C0 00" is a ping packet, which the client sends from time to time, and to which the server responds with "D0 00". With "E0 00" the client tells the server to disconnect.
All data packets from the AC300 start with 0x30, which in MQTT lingo means "PUBLISH". They follow this layout:
30 | PUBLISH |
C3 01 | remaining length (variable-length encoded, here: 195) |
00 17 | length of topic |
50 55 42 2F 41 43 33 30 30 2F 32 32 33 32 30 30 30 31 35 XX XX XX XX | topic (here: PUB/AC300/22320001XXXXX) |
01 | tag1 |
01 | tag2 |
10 00 82 00 50 A0...00 00 | payload data |
33 4E | CRC16 checksum over tag2 and payload data |
20251002-170741 10 00 46 00 3C 78 00 02 08 E7 00 01 FF FE 13 85 00 01 00 00 08 F0 00 00 00 00 13 85 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 02 14 00 03 00 63 00 45 00 01 00 00 14 C8 00 64 00 00 00 62 00 00 16 80 04 B0 01 4B 01 4B 01 4B 01 4B 01 4B 01 4B 01 4C 01 4B 01 4B 01 4B 01 4B 01 4B 01 4B 01 4B 01 4C 01 4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20251002-170806 E8 FB 83 F1 83 02 65 4C 4C 4C 4C 4C 4C 4C 4D 4C 4C 4C 4C 4C 20251002-170831 EA 06 F6 84 F2 FF FC 84 05 03 00 00 00 00 1E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20251002-170856 C0 62 F7 6B 83 CA 00 64 08 9A 83 1F 01 77 04 02 D0 20251002-170921 BD 73 84 02 C7 96 84 2B 6E 01 01 15 40 63 01 62 01 57 01 57 01 57 01 57 01 58 01 58 01 5A 01 57 01 57 01 57 01 58 01 57 01 58 01 57 01 5A 01 57 20251002-170946 D0 37 FB 26 01 DA 39 04 E1 36 00 C9 02 B8 64 65 00 DC 62 60 60 5F 60 60 60 60 61 60 60 60 60 60 60 60 20251002-171011 03 DD 01 FF FE 86 E5 00 00 00 86 2F 05 64 01 16 26 63 62 00 5C 59 5A 5A 5B 5A 63 5A 5B 5B 5C 5F 5A 62 5D 20251002-171036 DF 00 00 8A E7 8A 2B 02 02 00 15 CC 64 00 65 5A 5A 5A 5C 5A 5A 5A 5A 5A 5A 20251002-171101 E0 00 FF FC 86 E8 01 86 2A 04 01 A4 62 59 56 57 57 57 57 5E 57 57 57 59 5B 5B 57 5E 59 20251002-171126 E3 FD 85 EC 85 29 02 9A 65 58 58 59 58 58 58 58 5A 58 59 58 58 58 58 20251002-171151 DF 84 E7 84 28 01 86 62 57 55 55 56 56 56 5C 55 56 56 57 59 56 5C 20251002-171216 DE FE 85 E6 85 27 02 02 65 58 57 57 58 57 57 57 57 59 57 57 57 57 57 57 20251002-171241 D6 FC 84 02 DF 84 26 01 72 62 56 54 54 55 55 55 5B 55 55 55 56 58 58 55 5A 20251002-171306 FD 86 DE 86 25 02 65 57 56 56 57 56 56 56 56 58 57 57 57 56 56 56 20251002-171331 D7 FA 85 01 E0 85 04 01 68 62 55 53 54 54 54 54 5A 54 54 54 55 57 54 59 56 20251002-171401 D4 01 FD 84 02 DD 00 84 24 02 02 65 56 56 56 56 55 56 55 56 57 56 56 56 55 56 55 20251002-171426 DC FF E4 01 23 01 5E 62 55 52 53 53 54 54 59 53 54 54 55 56 53 58 55 20251002-171451 FE 86 E5 00 86 04 02 65 56 55 55 56 55 55 55 55 57 55 55 55 55 54 20251002-171516 DB 00 FC 8B E3 01 8B 22 02 01 54 62 54 52 52 53 53 53 58 53 53 53 54 56 56 53 57 54 20251002-171541 01 E4 02 65 55 54 54 55 54 54 54 54 56 55 55 55 54 54 54 55 20251002-171606 D7 01 8A 02 DF 00 8A 00 01 4A 62 53 52 52 52 53 52 57 52 53 52 53 55 52 57 54 20251002-171631 D3 FB 8B 01 DC 8B 21 02 02 65 55 54 54 55 54 54 53 54 56 54 54 54 53 54 53 20251002-171656 D2 FC DA 01 40 62 53 51 52 52 52 52 56 52 52 52 53 54 52 56 53 20251002-171726 D9 FD 02 E1 20 02 65 54 53 53 54 53 53 53 53 55 53 54 53 53 53 20251002-171756 D2 00 FB DA 01 01 36 62 52 51 51 52 52 52 56 52 52 52 52 54 51 55 20251002-171821 CB 01 8C 01 D3 00 8C 1F 02 2C 65 53 53 53 54 53 53 52 53 55 53 53 53 52 53 52 20251002-171846 CD 00 85 D5 85 04 01 62 52 50 51 51 51 51 55 51 51 51 52 53 51 55 52 20251002-171911 C8 D1 01 02 65 53 52 52 53 52 52 52 52 54 52 53 52 52 52 20251002-171936 01 FD 1E 02 01 22 62 52 50 50 51 51 51 54 51 51 51 51 51 54 20251002-172001 CE 00 FB 84 D7 84 00 02 65 52 52 53 52 52 51 52 54 52 52 52 51 52 51 20251002-172026 CA 01 FA 83 02 D3 00 83 02 01 62 51 50 50 50 50 51 54 51 50 50 51 52 50 54 51 20251002-172051 CD 85 D5 01 85 1D 04 02 65 52 52 51 52 51 51 52 53 52 52 51 52 51 52 20251002-172116 CC 00 FB 01 D4 02 01 62 51 4F 50 50 50 50 53 50 50 50 51 52 50 53 51 20251002-172146 CA FD 84 02 D2 84 04 02 65 52 51 51 52 51 51 51 51 53 51 52 51 51 51 20251002-172211 C6 FC CE 1C 01 18 62 50 4F 4F 50 50 50 53 50 50 50 50 51 4F 52 50 20251002-172236 C9 FD 01 D1 02 65 51 51 51 52 51 51 50 51 53 51 51 50 51 50 51 20251002-172301 02 00 00 01 0E 62 50 4F 4F 4F 4F 50 52 4F 4F 4F 50 51 4F 52 50 20251002-172326 C8 01 82 82 02 02 65 51 50 50 51 50 50 50 52 51 51 50 51 50 51 20251002-172351 CE FE 01 D7 1B 04 01 62 50 4E 4F 4F 4F 4F 52 4F 4F 4F 50 50 4F 52 50 20251002-172416 CB FB 81 02 D4 01 81 02 65 51 50 50 51 50 50 50 50 52 50 51 51 50 50 20251002-172446 FC 80 D3 80 01 04 62 4F 4E 4E 4F 4F 4F 51 4F 4F 4F 4F 50 4F 51 4F 20251002-172516 FE 01 00 02 02 65 50 50 50 51 50 50 4F 50 52 50 50 4F 50 4F 50 20251002-172541 C8 FD D0 1A 04 01 62 4F 4E 4E 4E 4F 4F 51 4F 4E 4E 4F 50 4E 51 4F 20251002-172606 C6 FE 81 CE 81 02 14 FA 65 50 4F 4F 50 4F 50 51 50 50 4F 50 4F 50 20251002-172631 C7 00 FB 80 02 D0 01 80 01 62 4F 4E 4E 4E 4E 4E 51 4E 4E 4E 4F 4F 4E 50 4FAfter observing this for a while, the meaning of several bytes could be identified. The most obvious thing here was the sequence of 16 two-byte words that range between 330 and 350, beginning at offset 76. Apparently these are the voltages of the individual battery cells, multiplied by 100.
Here's the list of all the observed sequences of payload data the AC300 sends:
10 00 00 00 24 48 ... 10 00 24 00 22 44 ... 10 00 46 00 3C 78 ... 10 00 82 00 50 A0 ... 10 0B B8 00 3E 7C ... 10 0B BF 00 02 04 ...These sequences are sent when connected to the server (some only when the Bluetti app accesses the device):
03 20 00 00 00 00 ... 03 76 41 43 33 30 ... 03 7C 00 00 00 03 ... 03 E6 00 04 02 ... 06 00 2B 00 ... 06 0B BE 00 ... 06 0B F5 00 ... 06 0B C0 00 ... 83 05 ... 86 02 ...
Options: -F DIR dump into Files in DIR -L Log also to stdout -M Mark changed bytes in payload -O dump Only changed packets -P dump Payload -R dump Raw data -T dump Topic -V dump Values -X dump data eXchange between client/server -a SECS Average values over SECS seconds (default: 300) -c Connect to bluetti server -i IP set IP number of bluetti IoT server (default: 47.254.169.86) -m MAIL send status mails to MAIL (default: none) -p PORT listen on PORT (default: 18760) -s DIR store values in a file in DIR -t SECS set timeout to SECS (default: 60 seconds)Options with uppercase letters are mainly for testing and debugging, and to determine the meaning of certain bytes. Lowercase options are used for normal operation.
A typical call might look like this:
bluetti-monitor -m email -s directoryThis will collect data from all AC300s in the local network and write the parameters into one file for each device, with the file name being the serial number of the device. No connection with the Bluetti IoT server is made, so the data never leaves the local betwork. Important events are reported via email to the given address.
Adding the -c option, as in
bluetti-monitor -m email -s directory -cwill establish a connection between the AC300 and the Bluetti IoT server, and "eavesdrop" on the data.
ACon = 1000,1000,1000 Cells1 = 332,332,332,332,332,332,333,332,332,332,332,332,332,332,333,332 Cells2 = 333,333,333,333,333,333,333,333,334,333,333,333,333,333,333,333 DCon = 0,0,0 Device = AC300 Grid = 0,0,0 LoadAC = 0,0,0 LoadDC = 0,0,0 MonitorVersion = 1.0.0 PDC1 = 0,0,0 Serial = 22320001XXXXX Server = 42,0,1000 SoC = 1000,1000,1000 SoC1 = 1000,1000,1000 SoC2 = 1000,1000,1000 Time = 251002-214033 Utility = 1000,1000,1000 VDC1 = 0,0,0 Voltage1 = 534,534,534 Voltage2 = 534,534,534Parameters with three comma separated numbers show the average, minimum and maximum value within the last five minutes (assuming the default -a option).
Percentage values are "times 10" for higher resolution.
Parameter names ending with 1, 2, 3 or 4 refer to that specific battery pack.
[AD]Con indicates whether the AC/DC output is on.
Cells[1234] shows the voltage of the 16 individual cells of that battery "times 10".
Device is the device type.
Grid is the input power drawn from the grid, in Watts.
Load[AD]C is the output power drawn on the AC/DC ports, in Watts.
MonitorVersion is the version number of the script that created this file.
[PV]DC[12] is the input power/voltage from the solar panel 1/2, in Watts/Volts.
Serial is the serial number of the device.
Server shows data packages from the server.
SoC is the "State of Charge" of the entire device.
SoC[1234] is the "State of Charge" of that specific battery pack.
Time is the system time of the device.
Voltage[1234] is the voltage of that specific battery pack "times 10".
Note that, especially right after starting a connection to the client, not all of these values may be present, yet.
I was able to locate the data for the voltage and power input of solar panel 1 (DC1), but couldn't find out where DC2 is published. Perhaps, like with the battery packs, it requires some trigger to switch between DC1 and DC2?
What also might be interesting are temperatures. I'm sure there must be some sensors in the device.
My other projects
Full Motion Flight Simulator
GeoTagger - Display and modify GPS location data in photos
Interface zur Verbindung einer digitalSTROM-Klemme GR-KL200 mit einem VELUX KLF 050x