diff --git a/README.md b/README.md index 22b7698c..fda91fe4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ Various tools, examples, and documentation for communicating with Hoymiles micro In particular: -* `doc\hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project -* The `tools` folder contains various software tools for RaspberryPi and Arduino +* `doc/hoymiles-format-description.txt` is a detailed description of the communications format and the history of this project +* `doc/getting-started-ESP8266.md` shows the hardware setup for an ESP8266-based system +* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32 Contributors are always welcome! diff --git a/doc/AhoyMiles.fzz b/doc/AhoyMiles.fzz new file mode 100644 index 00000000..c072d083 Binary files /dev/null and b/doc/AhoyMiles.fzz differ diff --git a/doc/AhoyMiles_bb.png b/doc/AhoyMiles_bb.png new file mode 100644 index 00000000..9704aeda Binary files /dev/null and b/doc/AhoyMiles_bb.png differ diff --git a/doc/AhoyMiles_schem.png b/doc/AhoyMiles_schem.png new file mode 100644 index 00000000..b955be78 Binary files /dev/null and b/doc/AhoyMiles_schem.png differ diff --git a/doc/HM-400 data.xlsx b/doc/HM-400 data.xlsx new file mode 100755 index 00000000..d6295022 Binary files /dev/null and b/doc/HM-400 data.xlsx differ diff --git a/doc/getting-started-ESP8266.md b/doc/getting-started-ESP8266.md new file mode 100644 index 00000000..d3e064b6 --- /dev/null +++ b/doc/getting-started-ESP8266.md @@ -0,0 +1,45 @@ +# Getting Started with an ESP8266 + +Wire Connections + +```ditaa + +-----------+ +-----------+ + | ESP8266 |--colour--| nRF24L01+ | + | | | | + | GND |---black--|[GND] | + | +3.3V |----red---| VCC | + | D4 |---grey---| CE | + | D8 |--purple--| CSN | + | D5 |---blue---| SCK | + | D7 |---green--| MOSI | + | D6 |---brown--| MISO | + | D3 |--yellow--| IRQ | + +-----------+ +-----------+ +``` + +![plot](./AhoyMiles_bb.png) + +Fritzing diagrams & schematics +* [AhoyMiles_bb.png](./AhoyMiles_bb.png) +* [AhoyMiles_schem.png](./AhoyMiles_schem.png) +* [AhoyMiles.fzz](./AhoyMiles.fzz) + +Libraries to be installed in Arduino IDE: +* RF24 +* TimeLib + +Verify & Compile +* Connect to WiFi Network `ESP AHOY` +* Use password `esp_8266` +* Connect to Network settings + +Setup +* WiFi + * Enter SSID `mynetwork` + * Enter Password `mypassword` +* Device Host Name + * Enter Device Name `esp-ahoy` +* General + * Hoymiles Address (e.g. 114173123456) `11:41:73:12:34:56` + * [x] Reboot device after successful save +Save diff --git a/doc/hoymiles-format-description.txt b/doc/hoymiles-format-description.txt index 7ed155d0..5dec0f7b 100644 --- a/doc/hoymiles-format-description.txt +++ b/doc/hoymiles-format-description.txt @@ -1,560 +1,597 @@ -About this Document -=================== - -This description aims to document the data format that Hoymiles -micro inverters use to communicate their current operating state. - -The original Hoymiles setup requires connectivity to "the cloud", -see [this section below](#system-description). - -With the information documented here, it is possible to interact with -a set of Hoymiles micro inverters in a purely "offline" way, i.e. -without requiring internet access or any connectivity to a "cloud". - -The only required hardware is a Nordic "NRF24L01+" wireless module. - -The `ahoy` project at [5] collects software for various platforms, -including Aduino and RaspberryPi. - -> Note: Some of the sections in this document are (still) in German. Translations -> may be provided if and when necessary. - - -Origin, Contributors --------------------- - -The information in this document was gathered in a large community -effort which started out with [this post][1] -on the German [mikrocontroller.net][2] forum. - -As of April 2022, this effort is still ongoing. Not all details have -been documented yet, and not all secrets have been uncovered. - -Multiple members of the community have already successfully retrieved (and -continue to successfully retrieve) data from their Hoymiles micro inverters. - -Here's a list of some of the early contributors: - -- sorbit: created the original mikrocontroller.net thread -- Martin (Gast): DTU and RF analysis -- Hubi: protocol analysis -- Marcel: initial analysis and much logging and interpretation -- Pascal A. (pasarn): various datagram fields, crc8 -- Frank H. (fh_): discovered time_t -- Thomas B. (tbnobody): protocol analysis, logging -- Arnaldo G. (arnaldo_g): data capturing -- Oliver F (of22): protocol analysis, logging -- Martin G. (petersilie): protocol analysis, logging, RaspberryPi - - - -System Description -================== - -Ein Setup wie von Hoymiles vorgesehen, sieht wie folgt aus: - -- Eine "DTU" kommuniziert mit vielen Wechselrichtern. -- Die Kommunikation geht immer von der DTU aus: - DTU stellt Anfrage und erwartet eine Antwort vom WR. -- Dafür muss die DTU die Adressen (=Seriennummern) aller WR kennen. -- Diese werden der DTU im Rahmen eines Einrichtungsprozesses beigebracht. - -``` - Nordic - "Enh. Shockburst" - 2.4 GHz - \|/ <-----------------> \|/ - | | - +-------+ +-----------+ - | DTU | | MI-600 | - +-------+ +-----------+-+ - | MI-1500 | - +-----------+-+ - | MI-... | - +-----------+ - : - : - ABBILDUNG 1: Systemübersicht -``` - - -``` - Nordic - WLAN "Enh. Shockburst" - 2.4 GHz - \|/ \|/ - | | - +---------+ +-----------+ - | ESP8266 | | NRF24LE1E | - +---------+ +-----------+ - ^ ^ - | | - | +----------+ | - +-----> | GD32F303 | <-----+ - (B) +----------+ (C) - - ABBILDUNG 2: Innerer Aufbau "DTU" -``` - - -``` - Nordic - "Enh. Shockburst" - NRF24LE1E 2.4 GHz - +------------------+ \|/ - +----------+ | | | | - | GD32F303 | <----->| µC | NRF24L01+ |-------+ - +----------+ (C) | | | - +------+-----------+ - - ABBILDUNG 3: Detailansicht GD32F303 - NRF24LE1E -``` - - - -Adressierung -============ - -Die Seriennummern der DTU und der WR werden wie folgt als Adressen für die -Kommunikation verwendet: - -**Interne Kommunikation**: Die meisten Datenpakete enthalten Quell- und -Zieladresse der jeweiligen Gesprächspartner. Hier werden 4-Byte-Adressen -verwendet, die direkt aus den letzten 8 Stellen der Seriennummer des -Wechselrichters bzw. der DTU gewonnen werden: - -Beispiel: Seriennummer ....72818832 - -Innerhalb der Pakete auf (C) wird daraus die 4-Byte-Adresse -0x72, 0x81, 0x88, 0x32 gebildet. Das ist die BCD-Darstellung -der letzen 8 Dezimalziffern. - -**NRF24 addressing scheme**: Over the air, the inverters communicate using -the [Nordic "Enhanced Shockburst" Protocol][3] configured for -5-byte addresses. - -The inverter serial number is converted into a "Shockburst" address -as follows: - -- encode the final 8 digits of the serial number in BCD format: - `0x72, 0x81, 0x88, 0x32` -- reverse the order of the bytes: - `0x32, 0x88, 0x81, 0x72` -- append a byte containing 0x01: - `0x32, 0x88, 0x81, 0x72, 0x01` - -In this example, the resulting "Shockburst" address is: 0x3288817201. - -**Additional example**, this time for inverter with serial number 99973104619: - -The datasheet specifies the over-the-air packet format: "Most Significant Byte -(MSB) to the left" (cf [datasheet figure 11][3]) - - Address := Byte_4, Byte_3, Byte_2, Byte_1, Byte_0 - ("LSByte must be unique") - -so 0x1946107301 results in - - 19 46 10 73 01 "on the wire" - -Old-style NRF Libraries take uint64_t addresses. In this case, the correct -address to pass to the library would be (uint64_t)0x1946107301ULL. - -The ["Optimized high speed nRF24L01+ driver"][4] -actually wants `uint8_t*`, which maybe makes more sense. -But apparently it still wants the bytes in order LSB to MSB (even though the chip will -then put them out in MSB-to-LSB order. - -So in this case, the correct sequence of bytes to pass to the library -would be "\x01\x73\x10\x46\x19". - -Figure 4 below is an annotated example of an "Enhanced Shockburst" packet as -seen on the air. - -``` -+----------+--------------------+--------------------+---------------------+------------+ -| preamble | dst 5-byte-address | PCF (9-bit) | payload (>=1 bytes) | 2-byte-CRC | -+----------+--------------------+--------------------+---------------------+------------+ -| | | e.g. 0x0d8: | | | -| 0x55 | addr[4]...addr[0] | 0b011011 00 0 | | | -| or | MSB ... LSB | len=27 PID nACK | | | -| 0xAA | | | | | -| | | e.g. 0x0da | | | -| | | 0b011011 01 0 | | | -| | | len027 PID nACK | | | -+----------+--------------------+--------------------+---------------------+------------+ - -PCF: Packet control field -PID: Packet IDentification (to detect/avoid duplicates), cycles through 0...3 - - FIGURE 4: Enhanced Shockburst On-Air Data Format -``` - - - -Messages -======== - -Initial protocol analysis focused on the data exchanged on link (C) in figure (3). -Not all the frames observed on this link will result in an actual RF transmission, -and some translation/mangling/processing happens inside the NRF24LE1E, in particular - -- replacement of serial numbers -- recalculation of CRCs - -These packets (which are all framed in 0x7e...0x7f bytes) are described in section -[Encapsulated Packets](#encapsulated-packets) below. - -More recent efforts focus mainly on the actual "Enhanced Shockburst" packets -that are transmitted over the air. These packets are described in section -[Enhanced Shockburst Payloads](#Enhanced-Shockburst-Payloads), and the -information contained in this section is more up to date. - - - - -Encapsulated Packets --------------------- - -These are packets as observed on Link (C) in figure (3). - - -Nachricht: DTU an WR: "Init" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 07 00 00 00 00 00 00 00 00 00 07 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ -Bedeutung SOF MID WR ser# WR ser# ? CRC8 EOF -``` - ? - - -Nachricht: DTU an WR: "Init 2" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 07 72 81 88 32 72 81 88 32 00 07 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ -Bedeutung SOF MID DTU ser# DTU ser# ? CRC8 EOF -Einheit BCD (letzte 8) BCD (letzte 8) ? ? -Beispiel 72818832 72818832 ? -``` - - - -Nachricht 0x80: DTU an WR: "Zeit setzen" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - |<-------------CRC16 'modbus' für CRC_M----------------->| - 7E 15 72 22 02 00 72 22 02 00 80 0B 00 62 09 04 9b 00 00 00 00 00 00 00 00 F2 68 F0 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^ ^^ -Bedeutung SOF MID WR ser# WR ser# CMD ? TIME (UTC) CRC_M CRC8 EOF -Einheit BCD (letzte 8) BCD (letzte 8) ? [s] HI LO -Beispiel 72220200 72220200 ? 2022-02-13 -``` - 13:16:11 - - -Nachricht 0x81: DTU an WR: "Anfrage DC-Daten" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - -GD->NRF 7E 15 70 51 43 68 70 51 43 68 81 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) - ^^^^^^^^^^^ ^^ ^^ ^^ - | (wird von CMD CRC8 EOF - | NRF ersetzt) | (wird von NRF - v v neu berechnet) - -on-air 15 70 51 43 68 70 53 54 53 81 BA -(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ - WR ser # DTU ser # -``` - - -Nachricht 0x82: DTU an WR: "Anfrage AC-Daten" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - -GD->NRF 7E 15 70 51 43 68 70 51 43 68 82 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) - ^^^^^^^^^^^ ^^ ^^ ^^ - | (wird von CMD CRC8 EOF - | NRF ersetzt) | (wird von NRF - v v neu berechnet) - -on-air 15 70 51 43 68 70 53 54 53 82 B9 -(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ - WR ser # DTU ser # -``` - - -Nachricht 0x83: DTU an WR: "Anfrage DC-Daten" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - -GD->NRF 7E 15 70 51 43 68 70 51 43 68 83 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) - ^^^^^^^^^^^ ^^ ^^ ^^ - | (wird von CMD CRC8 EOF - | NRF ersetzt) | (wird von NRF - v v neu berechnet) - -on-air 15 70 51 43 68 70 53 54 53 83 B8 -(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ - WR ser # DTU ser # -``` - - -Nachricht 0x85: DTU an WR: "???" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - -GD->NRF 7E 15 70 51 43 68 70 51 43 68 85 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) - ^^^^^^^^^^^ ^^ ^^ ^^ - | (wird von CMD CRC8 EOF - | NRF ersetzt) | (wird von NRF - v v neu berechnet) - -on-air 15 70 51 43 68 70 53 54 53 85 BE -(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ - WR ser # DTU ser # -``` - - -Nachricht 0xFF: DTU an WR: "???" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - -GD->NRF 7E 15 70 51 43 68 70 51 43 68 FF xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) - ^^^^^^^^^^^ ^^ ^^ ^^ - | (wird von CMD CRC8 EOF - | NRF ersetzt) | (wird von NRF - v v neu berechnet) - -on-air 15 70 51 43 68 70 53 54 53 FF C4 -(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ - WR ser # DTU ser # -``` - - -Nachricht 0x01: WR an DTU: "Aktuelle DC Daten" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 95 72 22 02 00 72 22 02 00 01 00 01 01 4c 03 bd 0c 46 00 b5 00 03 00 05 00 00 BD 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ -Bedeutung SOF MID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p PV2.u PV2.i PV2.p ? CRC8 EOF -Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W] ? -Beispiel 72220200 72220200 ? 33.2V 9.57A 317.2W 18.1V 0.03A 0.5W ? -``` - - -Nachricht 0x02: WR an DTU: "Aktuelle AC Daten" (?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 95 72 22 02 00 72 22 02 00 02 28 23 00 00 24 44 00 3C 00 00 09 0F 13 88 0B D5 83 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ -Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? AC.u AC.f AC.p CRC8 EOF -Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01Hz] [0.1W] -Beispiel 72220200 72220200 ? 9284 60 231.9V 50.00Hz 302.9W -``` - - -Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?) -``` ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 95 72 22 02 00 72 22 02 00 83 00 03 00 83 03 E8 00 B2 00 0A FD 26 1E 7F - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ -Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? ? ? ? CRC8 EOF -Einheit BCD (letzte 8) BCD (letzte 8) ? -Beispiel 72220200 72220200 ? 131 1000 178 10 -``` - - - -Hinweise --------- - -Die "on-air (payload)" Bytes geben nur die Nutzlast der gesendeten Shockburst-Pakete an. -Intern enthalten diese Pakete auch die Zieladresse, die Länge, eine CRC. - - - -***************************************************************************************************************************************************************************************** - - - -Enhanced Shockburst Payloads ----------------------------- - -- These are the packets that are exchanged between inverters and DTU via the Nordic - "Enhanced Shockburst" protocol. -- Each payload is preceded by a preamble, and terminated by a 16-bit CRC, as described - in the [Nordic datasheet][3]. See also figure 4 above. - - - -``` -CMD 0x80: DTU --> WR: "Set time/date" (?) ----------------------------------------------------------------------------------------------------------------------------------------------- - |<-------------CRC16 'modbus' für CRC_M----------------->| - 15 72220200 72220200 80 0B 00 62 09 04 9b 00 00 00 00 00 00 00 00 F2 68 F0 - ^^ ^^^^^^^^ ^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^ -Name MID DTU_SER# DTU_SER# CMD uk1 TIME (local) SEQ? CRC_M CRC8 -Units see "addressing" ? [s-since-epoch] HI LO -Example 72220200 72220200 ? 2022-02-13 - 13:16:11 -``` -- This message will cause the inverter to transmit a CMD=0x01, CMD=0x02, and, occasionally, also a CMD=0x83 message - to the DTU with serial number DTU_SER#. -- Values of "0xb0, 0x00" and "0x11, 0x00" have been observed for "UK1". Their meaning is unknown. -- "SEQ" was observed to contain increasing numbers when sent by a Hoymiles DTU. In particular, - each issued "command" (e.g. "switch inverter on", "switch inverter off") appears to increase this - value. A constant value of 0x0000 or 0x0005 appears to work just fine. -- Repeatedly sending the same TIME information (instead of correctly increasing time) - [has been shown](https://www.mikrocontroller.net/topic/525778?page=2#7021386) to result - in identical behaviour, the inverter still replies as described above. - - -``` -CMD 0x01: WR --> DTU: "Current DC data" (?) (shown for an HM-700) ----------------------------------------------------------------------------------------------------------------------------------------------- - - 95 72 22 02 00 72 22 02 00 01 00 01 01 4c 03 bd 0c 46 00 b5 00 03 00 05 00 00 BD 7F - ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ -NameMID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p PV2.u PV2.i PV2.p ? CRC8 EOF -Units BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W] ? -Example 72220200 72220200 ? 33.2V 9.57A 317.2W 18.1V 0.03A 0.5W ? -``` -- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed: - - HM-400 (single channel): - - HM-700 (2-channel): - - HM-1500 (4-channel): -``` -TODO TODO TODO -73109025 73109025 01 00 01 014F 0003 000B 0000 40AE 03AC 08E6 7C - ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ - 335 3 11 940 2278 - 33.5V 0.03A 1.1W 940W 22.78kW - -95 71603546 71603546 01 00 01 015D 004D 00B3 010C 0270 0001 3419 64 B327 B327 1 - ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ - 349 77 179 1 13337 - 34.9V 0.77A 1.79W 1 133.37kW -``` - - -``` -Nachricht 0x02: WR an DTU: "Aktuelle AC Daten" (?) ----------------------------------------------------------------------------------------------------------------------------------------------- - - 7E 95 72 22 02 00 72 22 02 00 02 28 23 00 00 24 44 00 3C 00 00 09 0F 13 88 0B D5 83 - ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^ -Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? AC.u AC.f AC.p CRC8 -Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01Hz] [0.1W] -Beispiel 72220200 72220200 ? 9284 60 231.9V 50.00Hz 302.9W -``` -- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed: - - ... - -``` -Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?) ----------------------------------------------------------------------------------------------------------------------------------------------- - - 95 72 22 02 00 72 22 02 00 83 00 03 00 83 03 E8 00 B2 00 0A FD 26 1E - ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ -Bedeutung MID WR ser# WR ser# CMD ? ? ? ? ? ? CRC8 -Einheit BCD (letzte 8) BCD (letzte 8) ? -Beispiel 72220200 72220200 ? 131 1000 178 10 -``` - - -Legend -====== - -**MID**: Message-ID. Antworten haben Bit 7 gesetzt, -``` - z.B. Frage 0x15 --> Antwort 0x95. - z.B. Frage 0x07 --> Antwort 0x87. - Für Kommunikation GD <--> NRF -``` - -**CMD**: - Befehl an den WR hat Bit 7 gesetzt - 0x80 "Zeit setzen" - 0x81 "Anfrage DC-Daten", erwartete Antwort: 0x01 - 0x82 "Anfrage AC-Daten", erwartete Antwort: 0x02 - 0x83 "?" - 0x85 "?" - 0xFF "?" - Antworten vom WR haben Bit 7 gelöscht: - 0x01 "Aktuelle DC-Daten" - 0x02 "Aktuelle AC-Daten" - -**SOF**: Start-of-Frame 0x7e - -**EOF**: End-of-Frame 0x7f - -**CRC8**: CRC8 mit poly=1 init=0 xor=0, für alle Bytes zwischen SOF und CRC8. - Beispiel in Python: -``` - >>> import crcmod - >>> f = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0) - >>> payload = bytes((0x95,0x72,0x22,0x02,0x00,0x72,0x22,0x02,0x00,0x83,0x00,0x03,0x00,0x83,0x03,0xE8,0x00,0xB2,0x00,0x0A,0xFD,0x26)) - >>> hex(f(payload)) - '0x1e' -``` - -**CRC_M**: CRC16 wie für "Modbus"-Protokoll, High-Byte gefolgt von Low-Byte - Beispiel in Python: -``` - >>> import crcmod - >>> f = crcmod.predefined.mkPredefinedCrcFun('modbus') - >>> payload = bytes((0x0B,0x00,0x62,0x2F,0x45,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00)) - >>> hex(f(payload)) - '0x3bd6' -``` - -**TIME**: Aktuelle (DTU-)Zeit als Unix "time_t" (Sekunden seit 1970-01-01) - - -Glossary -======== - -**WR**: Wechselrichter (inverter) - -**DTU**: Data Terminal Unit (?). Die Hoymiles-Bezeichnung für den Kommunikations-Master. - -**BCD**: Binary Coded Decimal - - -Notizen -======= - -0x014c = 332 -0x03bd = 957 -0x0c64 = 3172 -0x6209049b = 1644758171 -datetime.datetime.utcfromtimestamp(0x6209049b): datetime.datetime(2022, 2, 13, 13, 16, 11) - - -References -========== - -- [1]: https://www.mikrocontroller.net/topic/525778 "The post that started the community effort" -- [2]: https://www.mikrocontroller.net "mikrocontroller.net" -- [3]: https://infocenter.nordicsemi.com/pdf/nRF24LE1_PS_v1.6.pdf "Nordic NRF24LE01+ datasheet" -- [4]: https://nrf24.github.io/RF24 "Optimized high speed nRF24L01+ driver documentation" -- [5]: https://github.com/grindylow/ahoy "AHOY Communications Project" - - -Revision History -================ - -2022-03-09 / Petersilie / erste Version -2022-03-10 / Petersilie / r2 / Nachrichten "02 28 23" und "82 00 03" ergänzt. Sauberer ausgerichtet. Python Beispiel für CRC. -2022-03-12 / Petersilie / r3 / Erste on-air Formate hinzu. CMD-IDs hinzu. Neue Nachrichten von arnaldo_g hinzu. Übersicht hinzu. -2022-03-15 / Petersilie / r4 / Nachricht 0x80: Mystery-Bytes am Ende "dechiffriert" -2022-03-16 / Petersilie / r5 / ESP ist ein ESP8266, nicht ESP32 (danke an @tbnobody) -2022-03-27 / Petersilie / all future revisions are now versioned via Git. +About this Document +=================== + +This description aims to document the data format that Hoymiles +micro inverters use to communicate their current operating state. + +The original Hoymiles setup requires connectivity to "the cloud", +see [this section below](#system-description). + +With the information documented here, it is possible to interact with +a set of Hoymiles micro inverters in a purely "offline" way, i.e. +without requiring internet access or any connectivity to a "cloud". + +The only required hardware is a Nordic "NRF24L01+" wireless module. + +The `ahoy` project at [5] collects software for various platforms, +including Aduino and RaspberryPi. + +> Note: Some of the sections in this document are (still) in German. Translations +> may be provided if and when necessary. + + +Origin, Contributors +-------------------- + +The information in this document was gathered in a large community +effort which started out with [this post][1] +on the German [mikrocontroller.net][2] forum. + +As of April 2022, this effort is still ongoing. Not all details have +been documented yet, and not all secrets have been uncovered. + +Multiple members of the community have already successfully retrieved (and +continue to successfully retrieve) data from their Hoymiles micro inverters. + +Here's a list of some of the early contributors: + +- sorbit: created the original mikrocontroller.net thread +- Martin (Gast): DTU and RF analysis +- Hubi: protocol analysis +- Marcel: initial analysis and much logging and interpretation +- Pascal A. (pasarn): various datagram fields, crc8 +- Frank H. (fh_): discovered time_t +- Thomas B. (tbnobody): protocol analysis, logging +- Arnaldo G. (arnaldo_g): data capturing +- Oliver F (of22): protocol analysis, logging +- Martin G. (petersilie): protocol analysis, logging, RaspberryPi + + + +System Description +================== + +Ein Setup wie von Hoymiles vorgesehen, sieht wie folgt aus: + +- Eine "DTU" kommuniziert mit vielen Wechselrichtern. +- Die Kommunikation geht immer von der DTU aus: + DTU stellt Anfrage und erwartet eine Antwort vom WR. +- Dafür muss die DTU die Adressen (=Seriennummern) aller WR kennen. +- Diese werden der DTU im Rahmen eines Einrichtungsprozesses beigebracht. + +``` + Nordic + "Enh. Shockburst" + 2.4 GHz + \|/ <-----------------> \|/ + | | + +-------+ +-----------+ + | DTU | | MI-600 | + +-------+ +-----------+-+ + | MI-1500 | + +-----------+-+ + | MI-... | + +-----------+ + : + : + ABBILDUNG 1: Systemübersicht +``` + + +``` + Nordic + WLAN "Enh. Shockburst" + 2.4 GHz + \|/ \|/ + | | + +---------+ +-----------+ + | ESP8266 | | NRF24LE1E | + +---------+ +-----------+ + ^ ^ + | | + | +----------+ | + +-----> | GD32F303 | <-----+ + (B) +----------+ (C) + + ABBILDUNG 2: Innerer Aufbau "DTU" +``` + + +``` + Nordic + "Enh. Shockburst" + NRF24LE1E 2.4 GHz + +------------------+ \|/ + +----------+ | | | | + | GD32F303 | <----->| µC | NRF24L01+ |-------+ + +----------+ (C) | | | + +------+-----------+ + + ABBILDUNG 3: Detailansicht GD32F303 - NRF24LE1E +``` + + + +Adressierung +============ + +Die Seriennummern der DTU und der WR werden wie folgt als Adressen für die +Kommunikation verwendet: + +**Interne Kommunikation**: Die meisten Datenpakete enthalten Quell- und +Zieladresse der jeweiligen Gesprächspartner. Hier werden 4-Byte-Adressen +verwendet, die direkt aus den letzten 8 Stellen der Seriennummer des +Wechselrichters bzw. der DTU gewonnen werden: + +Beispiel: Seriennummer ....72818832 + +Innerhalb der Pakete auf (C) wird daraus die 4-Byte-Adresse +0x72, 0x81, 0x88, 0x32 gebildet. Das ist die BCD-Darstellung +der letzen 8 Dezimalziffern. + +**NRF24 addressing scheme**: Over the air, the inverters communicate using +the [Nordic "Enhanced Shockburst" Protocol][3] configured for +5-byte addresses. + +The inverter serial number is converted into a "Shockburst" address +as follows: + +- encode the final 8 digits of the serial number in BCD format: + `0x72, 0x81, 0x88, 0x32` +- reverse the order of the bytes: + `0x32, 0x88, 0x81, 0x72` +- append a byte containing 0x01: + `0x32, 0x88, 0x81, 0x72, 0x01` + +In this example, the resulting "Shockburst" address is: 0x3288817201. + +**Additional example**, this time for inverter with serial number 99973104619: + +The datasheet specifies the over-the-air packet format: "Most Significant Byte +(MSB) to the left" (cf [datasheet figure 11][3]) + + Address := Byte_4, Byte_3, Byte_2, Byte_1, Byte_0 + ("LSByte must be unique") + +so 0x1946107301 results in + + 19 46 10 73 01 "on the wire" + +Old-style NRF Libraries take uint64_t addresses. In this case, the correct +address to pass to the library would be (uint64_t)0x1946107301ULL. + +The ["Optimized high speed nRF24L01+ driver"][4] +actually wants `uint8_t*`, which maybe makes more sense. +But apparently it still wants the bytes in order LSB to MSB (even though the chip will +then put them out in MSB-to-LSB order. + +So in this case, the correct sequence of bytes to pass to the library +would be "\x01\x73\x10\x46\x19". + +Figure 4 below is an annotated example of an "Enhanced Shockburst" packet as +seen on the air. + +``` ++----------+--------------------+--------------------+---------------------+------------+ +| preamble | dst 5-byte-address | PCF (9-bit) | payload (>=1 bytes) | 2-byte-CRC | ++----------+--------------------+--------------------+---------------------+------------+ +| | | e.g. 0x0d8: | | | +| 0x55 | addr[4]...addr[0] | 0b011011 00 0 | | | +| or | MSB ... LSB | len=27 PID nACK | | | +| 0xAA | | | | | +| | | e.g. 0x0da | | | +| | | 0b011011 01 0 | | | +| | | len027 PID nACK | | | ++----------+--------------------+--------------------+---------------------+------------+ + +PCF: Packet control field +PID: Packet IDentification (to detect/avoid duplicates), cycles through 0...3 + + FIGURE 4: Enhanced Shockburst On-Air Data Format +``` + + + +Messages +======== + +Initial protocol analysis focused on the data exchanged on link (C) in figure (3). +Not all the frames observed on this link will result in an actual RF transmission, +and some translation/mangling/processing happens inside the NRF24LE1E, in particular + +- replacement of serial numbers +- recalculation of CRCs + +These packets (which are all framed in 0x7e...0x7f bytes) are described in section +[Encapsulated Packets](#encapsulated-packets) below. + +More recent efforts focus mainly on the actual "Enhanced Shockburst" packets +that are transmitted over the air. These packets are described in section +[Enhanced Shockburst Payloads](#Enhanced-Shockburst-Payloads), and the +information contained in this section is more up to date. + + + + +Encapsulated Packets +-------------------- + +These are packets as observed on Link (C) in figure (3). + + +Nachricht: DTU an WR: "Init" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 07 00 00 00 00 00 00 00 00 00 07 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ +Bedeutung SOF MID WR ser# WR ser# ? CRC8 EOF +``` + ? + + +Nachricht: DTU an WR: "Init 2" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 07 72 81 88 32 72 81 88 32 00 07 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ +Bedeutung SOF MID DTU ser# DTU ser# ? CRC8 EOF +Einheit BCD (letzte 8) BCD (letzte 8) ? ? +Beispiel 72818832 72818832 ? +``` + + + +Nachricht 0x80: DTU an WR: "Zeit setzen" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + |<-------------CRC16 'modbus' für CRC_M----------------->| + 7E 15 72 22 02 00 72 22 02 00 80 0B 00 62 09 04 9b 00 00 00 00 00 00 00 00 F2 68 F0 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^ ^^ +Bedeutung SOF MID WR ser# WR ser# CMD ? TIME (UTC) CRC_M CRC8 EOF +Einheit BCD (letzte 8) BCD (letzte 8) ? [s] HI LO +Beispiel 72220200 72220200 ? 2022-02-13 +``` + 13:16:11 + + +Nachricht 0x81: DTU an WR: "Anfrage DC-Daten" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + +GD->NRF 7E 15 70 51 43 68 70 51 43 68 81 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) + ^^^^^^^^^^^ ^^ ^^ ^^ + | (wird von CMD CRC8 EOF + | NRF ersetzt) | (wird von NRF + v v neu berechnet) + +on-air 15 70 51 43 68 70 53 54 53 81 BA +(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ + WR ser # DTU ser # +``` + + +Nachricht 0x82: DTU an WR: "Anfrage AC-Daten" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + +GD->NRF 7E 15 70 51 43 68 70 51 43 68 82 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) + ^^^^^^^^^^^ ^^ ^^ ^^ + | (wird von CMD CRC8 EOF + | NRF ersetzt) | (wird von NRF + v v neu berechnet) + +on-air 15 70 51 43 68 70 53 54 53 82 B9 +(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ + WR ser # DTU ser # +``` + + +Nachricht 0x83: DTU an WR: "Anfrage DC-Daten" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + +GD->NRF 7E 15 70 51 43 68 70 51 43 68 83 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) + ^^^^^^^^^^^ ^^ ^^ ^^ + | (wird von CMD CRC8 EOF + | NRF ersetzt) | (wird von NRF + v v neu berechnet) + +on-air 15 70 51 43 68 70 53 54 53 83 B8 +(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ + WR ser # DTU ser # +``` + + +Nachricht 0x85: DTU an WR: "???" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + +GD->NRF 7E 15 70 51 43 68 70 51 43 68 85 xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) + ^^^^^^^^^^^ ^^ ^^ ^^ + | (wird von CMD CRC8 EOF + | NRF ersetzt) | (wird von NRF + v v neu berechnet) + +on-air 15 70 51 43 68 70 53 54 53 85 BE +(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ + WR ser # DTU ser # +``` + + +Nachricht 0xFF: DTU an WR: "???" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + +GD->NRF 7E 15 70 51 43 68 70 51 43 68 FF xx 7F ...... (NOCH NICHT VERIFIZIERT / GESEHEN) + ^^^^^^^^^^^ ^^ ^^ ^^ + | (wird von CMD CRC8 EOF + | NRF ersetzt) | (wird von NRF + v v neu berechnet) + +on-air 15 70 51 43 68 70 53 54 53 FF C4 +(payload) ^^^^^^^^^^^ ^^^^^^^^^^^ + WR ser # DTU ser # +``` + + +Nachricht 0x01: WR an DTU: "Aktuelle DC Daten" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 95 72 22 02 00 72 22 02 00 01 00 01 01 4c 03 bd 0c 46 00 b5 00 03 00 05 00 00 BD 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ +Bedeutung SOF MID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p PV2.u PV2.i PV2.p ? CRC8 EOF +Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W] ? +Beispiel 72220200 72220200 ? 33.2V 9.57A 317.2W 18.1V 0.03A 0.5W ? +``` + + +Nachricht 0x02: WR an DTU: "Aktuelle AC Daten" (?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 95 72 22 02 00 72 22 02 00 02 28 23 00 00 24 44 00 3C 00 00 09 0F 13 88 0B D5 83 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ +Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? AC.u AC.f AC.p CRC8 EOF +Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01Hz] [0.1W] +Beispiel 72220200 72220200 ? 9284 60 231.9V 50.00Hz 302.9W +``` + + +Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?) +``` +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 95 72 22 02 00 72 22 02 00 83 00 03 00 83 03 E8 00 B2 00 0A FD 26 1E 7F + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ +Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? ? ? ? CRC8 EOF +Einheit BCD (letzte 8) BCD (letzte 8) ? +Beispiel 72220200 72220200 ? 131 1000 178 10 +``` + + + +Hinweise +-------- + +Die "on-air (payload)" Bytes geben nur die Nutzlast der gesendeten Shockburst-Pakete an. +Intern enthalten diese Pakete auch die Zieladresse, die Länge, eine CRC. + + + +***************************************************************************************************************************************************************************************** + + + +Enhanced Shockburst Payloads +---------------------------- + +- These are the packets that are exchanged between inverters and DTU via the Nordic + "Enhanced Shockburst" protocol. +- Each payload is preceded by a preamble, and terminated by a 16-bit CRC, as described + in the [Nordic datasheet][3]. See also figure 4 above. + + + +``` +CMD 0x80: DTU --> WR: "Set time/date" (?) +---------------------------------------------------------------------------------------------------------------------------------------------- + |<-------------CRC16 'modbus' für CRC_M----------------->| + 15 72220200 72220200 80 0B 00 62 09 04 9b 00 00 00 00 00 00 00 00 F2 68 F0 + ^^ ^^^^^^^^ ^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^ +Name MID DTU_SER# DTU_SER# CMD uk1 TIME (local) SEQ? CRC_M CRC8 +Units see "addressing" ? [s-since-epoch] HI LO +Example 72220200 72220200 ? 2022-02-13 + 13:16:11 +``` +- This message will cause the inverter to transmit a CMD=0x01, CMD=0x02, and, occasionally, also a CMD=0x83 message + to the DTU with serial number DTU_SER#. +- Values of "0xb0, 0x00" and "0x11, 0x00" have been observed for "UK1". Their meaning is unknown. +- "SEQ" was observed to contain increasing numbers when sent by a Hoymiles DTU. In particular, + each issued "command" (e.g. "switch inverter on", "switch inverter off") appears to increase this + value. A constant value of 0x0000 or 0x0005 appears to work just fine. +- Repeatedly sending the same TIME information (instead of correctly increasing time) + [has been shown](https://www.mikrocontroller.net/topic/525778?page=2#7021386) to result + in identical behaviour, the inverter still replies as described above. + + +``` +CMD 0x01: WR --> DTU: "Current DC data" (?) (shown for an HM-700 and HM-400) +---------------------------------------------------------------------------------------------------------------------------------------------- +HM-700 (2-channel): + 95 72 22 02 00 72 22 02 00 01 00 01 01 4c 03 bd 0c 46 00 b5 00 03 00 05 00 00 BD 7F + ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ +NameMID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p PV2.u PV2.i PV2.p ? CRC8 EOF +Units BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.1V] [0.01A] [.1W] ? +Example 72220200 72220200 ? 33.2V 9.57A 317.2W 18.1V 0.03A 0.5W ? + + +HM-400 (1-channel): +byte 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 + 95 73 10 xx yy 73 10 xx yy 01 00 01 01 9A 00 46 01 21 00 00 FA E6 00 84 09 0C F5 DD BD 7F + ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ +NameMID WR ser# WR ser# CMD ? PV1.u PV1.i PV1.p DC? P total? DC? P day V AC CRC8 EOF +Units BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01A] [.1W] [0.001kWh] [1Wh] [0.1V] ? +Example 7310xxyy 7310xxyy ? 41.0V 0.70A 28.9W 64.23kWh 132Wh 231.6V ? + +legend +PVx.u: DC voltage of panel x +PVx.i: DC current of panel x +PVx.p: DC power of panel x +WR ser#: inverter serial, e.g. 11217310xxyy (HM-400) => 7310xxyy +P tot: DC (or AC)? power total (monthly/yearly?) +P day: DC (or AC)? power daily +V AC: AC voltage + +``` +- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed: + - HM-400 (single channel): + - HM-700 (2-channel): + - HM-1500 (4-channel): +``` +TODO TODO TODO +73109025 73109025 01 00 01 014F 0003 000B 0000 40AE 03AC 08E6 7C + ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ + 335 3 11 940 2278 + 33.5V 0.03A 1.1W 940W 22.78kW + +95 71603546 71603546 01 00 01 015D 004D 00B3 010C 0270 0001 3419 64 B327 B327 1 + ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ + 349 77 179 1 13337 + 34.9V 0.77A 1.79W 1 133.37kW +``` + + +``` +Nachricht 0x02: WR an DTU: "Aktuelle AC Daten" (?) +---------------------------------------------------------------------------------------------------------------------------------------------- + + 7E 95 72 22 02 00 72 22 02 00 02 28 23 00 00 24 44 00 3C 00 00 09 0F 13 88 0B D5 83 + ^^ ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^ +Bedeutung SOF MID WR ser# WR ser# CMD ? ? ? AC.u AC.f AC.p CRC8 +Einheit BCD (letzte 8) BCD (letzte 8) ? [0.1V] [0.01Hz] [0.1W] +Beispiel 72220200 72220200 ? 9284 60 231.9V 50.00Hz 302.9W +``` +- The exact meaning of the contents of this message varies depending on inverter type. So far, the following variants have been observed: + - until now, message never observed using a HM-400 + +``` +CMD 0x82: WR --> DTU: "???" (?) (shown for an HM-400) +---------------------------------------------------------------------------------------------------------------------------------------------- +HM-400 (1-channel): +byte 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 + 95 73 10 xx yy 73 10 xx yy 82 13 8A 01 1C 00 00 00 0C 03 E8 00 65 00 06 3C 1D 36 9E 8D 1 + ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^ ^^ +NameMID WR ser# WR ser# CMD Freq P AC ? I AC ? Temp ? ? ? CRC8? EOF? +Units BCD (letzte 8) BCD (letzte 8) [0.01Hz] [0.1W] ? [0.01A] ? [0.1°C] ? ? ? +Example 7310xxyy 7310xxyy 50,02Hz 28,40W ? 0,120A ? 10,10°C ? ? ? + +legend +Freq: frequency of inverter +P AC: AC power of inverter +I AC: AC current of inverter +Temp: temperature of inverter +WR ser#: inverter serial, e.g. 11217310xxyy (HM-400) => 7310xxyy + +``` +Nachricht 0x83: WR an DTU (?): "???" (nach CMD wäre das eher auch eine Antwort vom WR?) +---------------------------------------------------------------------------------------------------------------------------------------------- + + 95 72 22 02 00 72 22 02 00 83 00 03 00 83 03 E8 00 B2 00 0A FD 26 1E + ^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ +Bedeutung MID WR ser# WR ser# CMD ? ? ? ? ? ? CRC8 +Einheit BCD (letzte 8) BCD (letzte 8) ? +Beispiel 72220200 72220200 ? 131 1000 178 10 +``` + + +Legend +====== + +**MID**: Message-ID. Antworten haben Bit 7 gesetzt, +``` + z.B. Frage 0x15 --> Antwort 0x95. + z.B. Frage 0x07 --> Antwort 0x87. + Für Kommunikation GD <--> NRF +``` + +**CMD**: + Befehl an den WR hat Bit 7 gesetzt + 0x80 "Zeit setzen" + 0x81 "Anfrage DC-Daten", erwartete Antwort: 0x01 + 0x82 "Anfrage AC-Daten", erwartete Antwort: 0x02 + 0x83 "?" + 0x85 "?" + 0xFF "?" + Antworten vom WR haben Bit 7 gelöscht: + 0x01 "Aktuelle DC-Daten" + 0x02 "Aktuelle AC-Daten" + +**SOF**: Start-of-Frame 0x7e + +**EOF**: End-of-Frame 0x7f + +**CRC8**: CRC8 mit poly=1 init=0 xor=0, für alle Bytes zwischen SOF und CRC8. + Beispiel in Python: +``` + >>> import crcmod + >>> f = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0) + >>> payload = bytes((0x95,0x72,0x22,0x02,0x00,0x72,0x22,0x02,0x00,0x83,0x00,0x03,0x00,0x83,0x03,0xE8,0x00,0xB2,0x00,0x0A,0xFD,0x26)) + >>> hex(f(payload)) + '0x1e' +``` + +**CRC_M**: CRC16 wie für "Modbus"-Protokoll, High-Byte gefolgt von Low-Byte + Beispiel in Python: +``` + >>> import crcmod + >>> f = crcmod.predefined.mkPredefinedCrcFun('modbus') + >>> payload = bytes((0x0B,0x00,0x62,0x2F,0x45,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00)) + >>> hex(f(payload)) + '0x3bd6' +``` + +**TIME**: Aktuelle (DTU-)Zeit als Unix "time_t" (Sekunden seit 1970-01-01) + + +Glossary +======== + +**WR**: Wechselrichter (inverter) + +**DTU**: Data Terminal Unit (?). Die Hoymiles-Bezeichnung für den Kommunikations-Master. + +**BCD**: Binary Coded Decimal + + +Notizen +======= + +0x014c = 332 +0x03bd = 957 +0x0c64 = 3172 +0x6209049b = 1644758171 +datetime.datetime.utcfromtimestamp(0x6209049b): datetime.datetime(2022, 2, 13, 13, 16, 11) + + +References +========== + +- [1]: https://www.mikrocontroller.net/topic/525778 "The post that started the community effort" +- [2]: https://www.mikrocontroller.net "mikrocontroller.net" +- [3]: https://infocenter.nordicsemi.com/pdf/nRF24LE1_PS_v1.6.pdf "Nordic NRF24LE01+ datasheet" +- [4]: https://nrf24.github.io/RF24 "Optimized high speed nRF24L01+ driver documentation" +- [5]: https://github.com/grindylow/ahoy "AHOY Communications Project" + + +Revision History +================ + +2022-03-09 / Petersilie / erste Version +2022-03-10 / Petersilie / r2 / Nachrichten "02 28 23" und "82 00 03" ergänzt. Sauberer ausgerichtet. Python Beispiel für CRC. +2022-03-12 / Petersilie / r3 / Erste on-air Formate hinzu. CMD-IDs hinzu. Neue Nachrichten von arnaldo_g hinzu. Übersicht hinzu. +2022-03-15 / Petersilie / r4 / Nachricht 0x80: Mystery-Bytes am Ende "dechiffriert" +2022-03-16 / Petersilie / r5 / ESP ist ein ESP8266, nicht ESP32 (danke an @tbnobody) +2022-03-27 / Petersilie / all future revisions are now versioned via Git. diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index f62089d5..c54335c8 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -482,7 +482,7 @@ void app::saveValues(bool webSend = true) { // type mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20); uint8_t type = atoi(buf); - mEep->write(ADDR_INV_TYPE + (i * MAX_NAME_LENGTH), type); + mEep->write(ADDR_INV_TYPE + i, type); } interval = mWeb->arg("invInterval").toInt(); diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 74a51a0b..1aa22cee 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -25,7 +25,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 2 -#define VERSION_PATCH 10 +#define VERSION_PATCH 11 //------------------------------------- @@ -39,7 +39,7 @@ typedef struct { // EEPROM //------------------------------------- #define SSID_LEN 32 -#define PWD_LEN 32 +#define PWD_LEN 63 #define DEVNAME_LEN 16 #define CRC_LEN 2 // uint16_t diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h new file mode 100644 index 00000000..b36eac03 --- /dev/null +++ b/tools/esp8266/hmDefines.h @@ -0,0 +1,122 @@ +#ifndef __HM_DEFINES_H__ +#define __HM_DEFINES_H__ + +#include "debug.h" +#include + +// units +enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT}; +const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%"}; + +// field types +enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, + FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT}; +const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", + "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct"}; + + +union serial_u { + uint64_t u64; + uint8_t b[8]; +}; + + +// CH0 is default channel (freq, ac, temp) +enum {CH0 = 0, CH1, CH2, CH3, CH4}; +// received command ids, special command CMDFF for calculations +enum {CMD01 = 0x01, CMD02, CMD03, CMD82 = 0x82, CMD83, CMD84, CMDFF=0xff}; + +enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200, INV_TYPE_HM400}; +const char* const invTypes[] = {"HM600", "HM1200 / HM1500", "HM400"}; +#define NUM_INVERTER_TYPES 3 + +typedef struct { + uint8_t fieldId; // field id + uint8_t unitId; // uint id + uint8_t ch; // channel 0 - 3 + uint8_t cmdId; // received command id + uint8_t start; // pos of first byte in buffer + uint8_t num; // number of bytes in buffer + uint16_t div; // divisor +} byteAssign_t; + + +/** + * indices are built for the buffer starting with cmd-id in first byte + * (complete payload in buffer) + * */ + +//------------------------------------- +// HM400 HM350?, HM300? +//------------------------------------- +const byteAssign_t hm400assignment[] = { + { FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 }, + { FLD_YT, UNIT_KWH, CH1, CMD01, 9, 4, 1000 }, + { FLD_YD, UNIT_WH, CH1, CMD01, 13, 2, 1000 }, + { FLD_UAC, UNIT_V, CH0, CMD01, 15, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, CMD82, 1, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, CMD82, 3, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, CMD82, 7, 2, 100 }, + { FLD_T, UNIT_C, CH0, CMD82, 11, 2, 10 } +}; +#define HM400_LIST_LEN (sizeof(hm400assignment) / sizeof(byteAssign_t)) + + +//------------------------------------- +// HM600, HM700 +//------------------------------------- +const byteAssign_t hm600assignment[] = { + { FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, CMD01, 7, 2, 10 }, + { FLD_UDC, UNIT_V, CH2, CMD01, 9, 2, 10 }, + { FLD_IDC, UNIT_A, CH2, CMD01, 11, 2, 100 }, + { FLD_PDC, UNIT_W, CH2, CMD01, 13, 2, 10 }, + { FLD_YW, UNIT_WH, CH0, CMD02, 1, 2, 1 }, + { FLD_YT, UNIT_KWH, CH0, CMD02, 3, 4, 1000 }, + { FLD_YD, UNIT_WH, CH1, CMD02, 7, 2, 1 }, + { FLD_YD, UNIT_WH, CH2, CMD02, 9, 2, 1 }, + { FLD_UAC, UNIT_V, CH0, CMD02, 11, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, CMD02, 13, 2, 100 }, + { FLD_IAC, UNIT_A, CH0, CMD02, 15, 2, 10 }, + { FLD_T, UNIT_C, CH0, CMD83, 7, 2, 10 } +}; +#define HM600_LIST_LEN (sizeof(hm600assignment) / sizeof(byteAssign_t)) + + +//------------------------------------- +// HM1200, HM1500 +//------------------------------------- +const byteAssign_t hm1200assignment[] = { + { FLD_UDC, UNIT_V, CH1, CMD01, 3, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, CMD01, 5, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, CMD01, 9, 2, 10 }, + { FLD_YD, UNIT_WH, CH1, CMD02, 5, 2, 1 }, + { FLD_YT, UNIT_KWH, CH1, CMD01, 13, 4, 1000 }, + { FLD_UDC, UNIT_V, CH2, CMD02, 9, 2, 10 }, + { FLD_IDC, UNIT_A, CH2, CMD01, 7, 2, 100 }, + { FLD_PDC, UNIT_W, CH2, CMD01, 11, 2, 10 }, + { FLD_YD, UNIT_WH, CH2, CMD02, 7, 2, 1 }, + { FLD_YT, UNIT_KWH, CH2, CMD02, 1, 4, 1000 }, + { FLD_IDC, UNIT_A, CH3, CMD02, 11, 2, 100 }, + { FLD_PDC, UNIT_W, CH3, CMD02, 15, 2, 10 }, + { FLD_YD, UNIT_WH, CH3, CMD03, 11, 2, 1 }, + { FLD_YT, UNIT_KWH, CH3, CMD03, 3, 4, 1000 }, + { FLD_IDC, UNIT_A, CH4, CMD02, 13, 2, 100 }, + { FLD_PDC, UNIT_W, CH4, CMD03, 1, 2, 10 }, + { FLD_YD, UNIT_WH, CH4, CMD03, 13, 2, 1 }, + { FLD_YT, UNIT_KWH, CH4, CMD03, 7, 4, 1000 }, + { FLD_UAC, UNIT_V, CH0, CMD03, 15, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, CMD84, 7, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, CMD84, 3, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, CMD84, 1, 2, 100 }, + { FLD_PCT, UNIT_PCT, CH0, CMD84, 9, 2, 10 }, + { FLD_T, UNIT_C, CH0, CMD84, 11, 2, 10 } +}; +#define HM1200_LIST_LEN (sizeof(hm1200assignment) / sizeof(byteAssign_t)) + + + +#endif /*__HM_DEFINES_H__*/ diff --git a/tools/esp8266/hmInverter.h b/tools/esp8266/hmInverter.h new file mode 100644 index 00000000..9be3a981 --- /dev/null +++ b/tools/esp8266/hmInverter.h @@ -0,0 +1,143 @@ +#ifndef __HM_INVERTER_H__ +#define __HM_INVERTER_H__ + +#include "hmDefines.h" + +template +class Inverter { + public: + uint8_t id; // unique id + char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1" + uint8_t type; // integer which refers to inverter type + byteAssign_t* assign; // type of inverter + uint8_t listLen; // length of assignments + serial_u serial; // serial number as on barcode + serial_u radioId; // id converted to modbus + uint8_t channels; // number of PV channels (1-4) + RECORDTYPE *record; // pointer for values + + Inverter() { + getAssignment(); + toRadioId(); + record = new RECORDTYPE[listLen]; + memset(record, 0, sizeof(RECORDTYPE) * listLen); + } + + ~Inverter() { + // TODO: cleanup + } + + uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) { + uint8_t pos = 0; + for(; pos < listLen; pos++) { + if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId)) + break; + } + return (pos >= listLen) ? 0xff : pos; + } + + const char *getFieldName(uint8_t pos) { + return fields[assign[pos].fieldId]; + } + + const char *getUnit(uint8_t pos) { + return units[assign[pos].unitId]; + } + + uint8_t getChannel(uint8_t pos) { + return assign[pos].ch; + } + + uint8_t getCmdId(uint8_t pos) { + return assign[pos].cmdId; + } + + void addValue(uint8_t pos, uint8_t buf[]) { + uint8_t ptr = assign[pos].start; + uint8_t end = ptr + assign[pos].num; + uint16_t div = assign[pos].div; + + uint32_t val = 0; + do { + val <<= 8; + val |= buf[ptr]; + } while(++ptr != end); + + record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + } + + RECORDTYPE getValue(uint8_t pos) { + return record[pos]; + } + + private: + void toRadioId(void) { + radioId.u64 = 0ULL; + radioId.b[4] = serial.b[0]; + radioId.b[3] = serial.b[1]; + radioId.b[2] = serial.b[2]; + radioId.b[1] = serial.b[3]; + radioId.b[0] = 0x01; + } + + void getAssignment(void) { + if(INV_TYPE_HM600 == type) { + listLen = (uint8_t)(HM600_LIST_LEN); + assign = (byteAssign_t*)hm600assignment; + channels = 2; + } + else if(INV_TYPE_HM1200 == type) { + listLen = (uint8_t)(HM1200_LIST_LEN); + assign = (byteAssign_t*)hm1200assignment; + channels = 4; + } + else if(INV_TYPE_HM400 == type) { + listLen = (uint8_t)(HM400_LIST_LEN); + assign = (byteAssign_t*)hm400assignment; + channels = 1; + } + else { + listLen = 0; + channels = 0; + assign = NULL; + } + } +}; + + +/** + * To calculate values which are not transmitted by the unit there is a generic + * list of functions which can be linked to the assignment. + * The special command 0xff (CMDFF) must be used. + */ + +typedef float (*func_t)(Inverter<> *); +typedef struct { + uint8_t funcId; // unique id + func_t func; // function pointer +} calcFunc_t; + +static float calcYieldTotalCh0(Inverter<> *iv) { + if(NULL != iv) { + float yield[iv->channels]; + for(uint8_t i = 1; i <= iv->channels; i++) { + uint8_t pos = iv->getPosByChFld(i, FLD_YT); + //yield[i-1] = iv->getValue(iv) + } + } + return 1.0; +} + +static float calcYieldDayCh0(Inverter<> *iv) { + return 1.0; +} + +enum {CALC_YT_CH0 = 0, CALC_YD_CH0}; + +const calcFunc_t calcFunctions[] = { + { CALC_YT_CH0, &calcYieldTotalCh0 }, + { CALC_YD_CH0, &calcYieldDayCh0 } +}; + + +#endif /*__HM_INVERTER_H__*/