Skip to content

FAQ Frequently Asked Questions

stefan123t edited this page Jul 28, 2022 · 10 revisions

Welcome to the ahoy wiki!

FAQ Häufig gestellte Fragen

FAQ Frequently Asked Questions

Inhaltsverzeichnis


Allgemeines

Vielleicht könnt Ihr Bitte mal so die letzten Beiträge durchgehen und einiges als FAQs mit aufnehmen in der Hoffnung, das es nicht täglich neu gefragt wird ?

Das könnte vielleicht auch jemand machen, der aktuell nicht mit entwickelt, aber sich gut mit github auskennt ? Vielleicht auch 1 Dokument für alle Sourcen zusammen - denn einiges wäre ja Allgemeingültig ?

Welche Modelle werden unterstützt

Tabelle der Hersteller & Modelle

Alles was mit nRF24L01+ Modulen per 2.4GHz und ESB (Enhanced Shock Burst) von Nordic Semiconductors erreicht werden kann.

Welche Geräte / Funktionen werden nicht unterstützt

  • Werden Hoymiles HMT und HMS auch unterstützt ?

Nein HMT und HMS verwenden ein anderes Funkmodul und können daher (vorerst) auch nicht unterstützt werden. Ob die beiden Modelle auch die selben Kommandos verwenden ist bisher unbekannt.

Bitte im entsprechenden Forums Beitrag Hilfe anbieten bzw. weitere Entwicklung hierzu diskutieren.

  • Wird DTU-Pro-S auch unterstützt ?

Die DTU-Pro S ist für die o.g. Hoymiles HMT & HMS Modelle gebaut und verwendet ebenfalls ein anderes Funkmodul. Sie wird also ebenfalls auf absehbare Zeit nicht unterstützt.

Welche Kommandos werden von der DTU unterstützt

  • Statusabfrage
    • detaillierte Statusabfrage der aktuellen Werte
    • allgemeine Hardware / Software Informationen
  • Alarmmeldungen
    • alle Meldungen seit der letzten Nacht
    • neue Meldungen
  • Kontrollkommandos
    • Start des Wechselrichters (Boot)
    • Stop des Wechselrichters (Shutdown)
    • Restart des Wechselrichters (Reboot)
    • Power Limit (PowerLimit)
    • Power Factor (PowerFactor)
  • Parameter setzen
    • Netz Profil (GridProfile)

Welche Entfernung unterstützen die nRF24L01+ Funkmodule

Der Abstand zum HM-600 war nur knapp 2 m, und durch die Holzdecke + Schindeln.

Modbus via TCP oder RS485

Wird Modbus für Zähler auch unterstützt ?

Was sind SunSpec und Victron

SunSpec ist das, was z.B. SMA und Fronius sprechen um die WR zu regeln, du bekommst Werte vom WR und eine Steuerung wie z.B. Victron kann entsprechend nach Batteriestand und Hausverbrauch die Leistung reduzieren, so dass du Nulleinspeisung fahren kannst basiert auf Modbus, hat aber noch einige Register zur Identifikation zusätzlich

Was ist DRM

DRM ist ein Hardwareport, der für Australien entwickelt ist, da musst mit Widerständen und Schaltern gegen + und - Schalten, bisher keine vernünftigen Schaltungen gefunden, SunSpec ist nur möglich über RS485 wenn nicht Zero-Export aktiv ist, da der RS485 Port dann in den Slavemode geht und sonst mit dem Zähler/Messgerät als Master arbeitet

DRM Control Box DRM Modes

Was ist AntiReflux

Was ist MQTT

Welche MQTT Broker / Home Automation Systeme verwendet ihr


Hardware

Welche Hardware brauche ich für die verschiedenen Software Projekte

Reicht ein ESP8266 mit 1MB oder muss es unbedingt ein ESP8266 mit 4MB sein ?

...

Welche Hardware gibt es und welche Software läuft darauf

Raspberry Pi | Ahoy DTU tools/rpi ESP8266 | Ahoy DTU tools/esp8266 ESP8266 | Ahoy DTU tools/HoyDtuSim ESP8266 | Ahoy DTU tools/NRF24_SendRcv Arduino nano | Ahoy DTU tools/nano ESP32 | Open DTU ``

Welches nRF24L01+ Modul verwendet ihr ?

Muß es ein nrf24L01+ sein oder funktioniert auch ein nrf24L01 (ohne Plus) ?

Ich will mir eines bestellen, wo gibt es eine sichere Quelle ?

Welchen Mikroprozessor empfehlt ihr ?

ESP32 (OpenDTU) oder ESP8266 (Ahoy-DTU) sind beide zu empfehlen, da sie bereits eine WLAN / WiFi Antenne eingebaut haben. Der Unterschied liegt ein wenig am Preis und welche Software man darauf laufen lassen möchte. Wer bereits einen Raspberry Pi in Betrieb hat, der nahe genug an den Wechselrichtern ist, kann auch diesen direkt nutzen. Auf dem Raspberry Pi sind Änderungen am Code nicht immer mit einem Software-Update (Flashen) verbunden. Historisch wurde auch der Arduino nano für erste Scans beim Reverse-Engineering verwendet.

Wie ist das Gerät mit Spannung zu versorgen

Stabile Stromversorgung ...


Software / Installation

Die Software für den ESP32 (OpenDTU) ist ein bißchen übersichtlicher aufgebaut und wird von Thomas B. (@noby) vorbildlich gewartet.

Welche Software gibt es und mit welcher Hardware läuft sie

Ahoy auf ESP8266 läuft bei mir mehrfach "im Produktiv-Einsatz" seit Wochen. Damit füttere ich die InfluxDB.

Software for ESP32 to talk to Hoymiles Inverters https://github.com/tbnobody/OpenDTU/

Software for ESP8266, Raspberr https://github.com/grindylow/ahoy

https://github.com/DanielR92/ahoy/tree/main/tools/rpi

BIN Files für Ahoy-DTU

Wo und wie kommt man direkt ohne Entwicklungsumgebung an die .bin ?

Wo liegen die verschiedenen Versionen der .bin´s ?

Platform IO installieren

Wie definiere ich die Serielle Schnittstelle für den ESP7266/ESP32

  • unter Linux

Mit upload_port = /dev/ttyUSB0 in der platformio.ini unter [env:d1_mini] und/oder [env:node_mcu_v2]

Kommt der Fehler could not open port muß man noch die Berechtigung anpassen, der Benutzer muss in der dialout Gruppe sein unter Linux.

sudo adduser <mein_nutzer> dialout

Eventuell muss man sich sogar noch einmal abmelden und wieder anmelden, damit er die Berechtigungen aktualisiert.

  • unter Mac OS

  • unter Windows

Treiber installieren COM1:

Wie kann ich den ESP8266 konfigurieren

http://192.168.178.XXX/setup

Wo finde ich den original Source Code von Hoymiles

https://gitee.com/iotloves/hoymiles-DTU-PRO/

andycao1860-hoymiles-DTU-PRO.zip and 1 more file https://we.tl/t-tn7OWR4VdR

Welche EXPERIMENTELLEN Software Funktionen sind bisher noch nicht im Standard enthalten (Pull Request / Merge)

SD Card Support https://github.com/grindylow/ahoy/compare/main...stefan123t:ahoy:sd_card

Power Limit via mqtt https://github.com/grindylow/ahoy/pull/109

ESP8266 Async WebServer https://github.com/grindylow/ahoy/pull/107

Welche Software Funktionen sind geplant

  • State Machine

EMBED WITH ELLIOT: PRACTICAL STATE MACHINES https://hackaday.com/2015/09/04/embed-with-elliot-practical-state-machines/

Multithreading in C und Arduino http://stefanfrings.de/multithreading_arduino/index.html

Using State Machines In Your Designs http://aqdi.com/articles/using-state-machines-in-your-designs-3/

C and C++ Finite State Machine Framework http://block-net.de/Programmierung/cpp/fsm/fsm.html

SMC The State Machine Compiler http://smc.sourceforge.net/

Generate production ready source code from UML state diagrams – and activity diagrams! https://www.sinelabore.de/doku.php

Qfsm - A graphical tool for designing finite state machines http://qfsm.sourceforge.net/

Professioneller Arduino Code: Variablen, State-Machine und Klassen https://www.youtube.com/watch?v=MERB1lqqyl8

STATE YOUR INTENTIONS MORE CLEARLY WITH STATE MACHINES https://hackaday.com/2018/04/06/state-your-intentions-more-clearly-with-state-machines/ https://www.youtube.com/watch?v=v8KXa5uRavg

Artikel über Finite State Machines https://www.mikrocontroller.net/topic/248837


Konfiguration

Eintragen der Seriennummer

S/N eintragen, muss das "ULL" stehen bleiben (im Source) ? Format Beispiel direkt im Souce als Kommentar z.B.

Wieviele Wechselrichter unterstützt die Software

Ich habe mehr als 3 hoymiles HM-..., wird das auch Unterstützt ? (muss im Source geändert werden - ich weiss - aber neue Nutzer eher nicht)

Warum antwortet der Wechselrichter nicht

Antwortet der HM-.... auch ohne angeschlossene PV Module bzw. Nachts ?


Protokoll

Welche Kanäle werden für Senden / Empfangen verwendet, was ist Channel Hopping ?

Soviel ich weiß haben die nrf24l01 nur bis zu 126 Kanäle.

  • Senden

  • Empfangen

  • Channel Hopping

    • Empfangen, auf allen Kanälen
    • Senden, meist auf Kanal 2,403 GHz

Wie sehen Kommandos oder gesendete / empfangene Frames aus

0x7E 0x15 0x76 0x54 0x32 0x10 0x78 0x56 0x34 0x12 0x80 0x0B 0x00 0x `` 0x7F []

Was ist eine SingleFrameID / sind MultiFrameIDs

  • Single Frame ID Die Single Frame ID ist 0x80

  • Multi Frame ID Multi Frame IDs werden für Nachrichten >12 Byte Payload verwendet. Multi Frame IDs beginnen mit 0x01, 0x02, ..., das letzte Paket enthält die Ende-Kennung 0x8N (0x80 | 0x0N Frame ID). Es darf also maximal 0x7F = 127 Pakete in einer Nachricht / Payload oder 1524 Bytes geben.

Welche Prüfsummen (CRC = Cyclic Redundancy Check) gibt es und über welchen Teil der Nachrichten werden sie gebildet

                  |<-------------------------------------------------------CRC8----------------------------------------------->|
                                                            |<-------------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

Wie wird die CRC16 bzw. CRC-Modus berechnet

CRC-Berechnung, Einstellungen für HxD

Erzeuge Prüfsummen Verfügbare Algorithmen: Benutzerdefiniertes CRC (16-Bit) (*) Markierte Daten Benutzerdefiniertes CRC...

Benutzerdefiniertes CRC Bitbreite: (*) 16 Polynom: 8005 Startwert: FFFF Ausgabe XOR: 0000 Reflexion: [x] Eingabe [x] Ausgabe Ok

Die CRC16 geht mit folgenden Einstellungen: CRC16

CRC width Bit length: (*) CRC-16

CRC parametrization (*) Custom

CRC detailed parameters Input reflected: [ ] Result reflected: [x] Polynomial: 0x8005 Initial Value: 0xFFFF Final Xor Value: 0x0000

CRC Input Data (*) Bytes 0x00 0x00

Result CRC value: 0xB001

Oder kurz https://crccalc.com/?crc=0x0000&method=CRC-16/MODBUS&datatype=hex&outtype=0

CRC-16 über die Daten nach der MultiFrameID berechnen:

    for(i = 10; i < 24; i++)
    {
        if((i - 10) % 2 == 1)
        {
            TempCrc = (u16)(temp_dat[i - 1] << 8) | (temp_dat[i]);
            DatCrc =  CalcCRC16t(TempCrc, DatCrc);
        }
    }

    temp_dat[24] = (u8)(DatCrc >> 8);
    temp_dat[25] = (u8)(DatCrc);

CRC16_MODBUS_POLYNOM 0xA001 ist in crc.h definiert, das ist wohl ein reversed CRC-16-IBM Polynomial. wird u.a. für Bisync, Modbus, USB, ANSI X3.28, many others; also known as CRC-16 and CRC-16-ANSI verwendet.

Wie wird die CRC8 berechnet

Die CRC8 läßt sich z.B. auf http://www.sunshine2k.de/coding/javascript/crc/crc_js.html berechnen: CRC8

CRC width Bit length: (*) CRC-8

CRC parametrization (*) Custom

CRC detailed parameters Input reflected: [ ] Result reflected: [ ] Polynomial: 0x01 Initial Value: 0x00 Final Xor Value: 0x00

CRC Input Data (*) Bytes 0x51 0x81 0x10 0x15 0x07 0x81 0x10 0x15 0x07 0x81 0x00 0x00 0xB0 0x01

Result CRC value: 0x61

Oder ganz einfach mit https://crccalc.com/?crc=0x518110150781101507810000B001&method=CRC-8/ITU&datatype=hex&outtype=0

CRC8 über alles berechnen

    temp_dat[26] = Get_crc_xor((u8 *)temp_dat, 26);
#define CRC8_INIT 0x00
#define CRC8_POLY 0x01

#include "crc.h"

uint8_t crc8(uint8_t buf[], uint8_t len)
{
    uint8_t crc = CRC8_INIT;
    for (uint8_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (uint8_t b = 0; b < 8; b++) {
            crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
        }
    }
    return crc;
}

Wie kann man einen Zeitstempel berechnen

0x627b69fe = 1652255230 $ date --date='@1652255230' Wed 11 May 2022 09:47:10 AM CEST

62D80183 ist der Timestamp im UNIX Epoch Format: $ date --date="@$(echo 'ibase=16; 62D80183'|bc)" '+%F %H:%M:%S' 2022-07-20 15:22:11

Geht auch umgekehrt: $ echo "obase=16; $(date --date='2022-07-01 00:00:00' +%s)"|bc 62BE1CE0

bash commands mal in alias einbauen^^

$ date --date="@$(echo 'ibase=16; 62D80707'|bc)" '+%F %H:%M:%S' 2022-07-20 14:45:43

Was sind Backward/ForwardSubstitution

ForwardSubstitution und BackwardSubsitution sind eine Escape- und Unescape-Verfahren für die nRF24 Schnittstelle.

Da die Bytes Start of Frame 0x7E und End of Frame 0x7F reservierte Zeichen sind muß ggf. deren Auftreten ersetzt werden. Da 0x7E und 0x7F Steuerzeichen (Start Of Frame und End Of Frame) sind müssen diese Escaped werden. Dafür muss das Zeichen 7D herhalten, daher muss es auch Escaped werden. Rückwärts gehts eben genau umgekehrt.

Dies erfolgt anhand folgender Übersetzungstabelle:

Backward ForwardSubstitution
0x7D 0x7D5D
0x7E 0x7D5E
0x7F 0x7D5F

Hier wird 7D5F anstelle von 7F bzw. 7D5E anstelle von 7E und 7D5D anstelle von 7D verwendet. Ich hatte mich schon gewundert warum manche Pakete länger sind als andere =^D

Welche Kommandos (MainCmd) und Sub Kommandos (SubCmd) gibt es ?

MainCmd: 0x51 DEVCONTROL_ALL SubCmd / Control Type: Type_TurnOff = 0x01 + 0x00 wegen den Bitshifts bei der Übergabe des Parameters ?

Verbindungsstatus HM 7E 06 81101507 81101507 00 06 7F 7E 06 81101507 81101507 00 06 7F

Verbindungsstatus MI 7E 06 63500316 63500316 00 06 7F // MainCmd REQ_RF_SVERSISON 0x06 7E 06 63500316 63500316 00 06 7F 7E 06 63500316 63500316 00 06 7F

Autoscan und Scan nach dem MI 7E 01 73600117 73600117 00 01 7F // MainCmd CHANGE_MOD_2M_250K 0x01 Change Baud rate to 250kHz, SubCmd = 0x00

Autoscan und Scan nach dem MI 7E 02 63500316 63500316 00 02 7F // MainCmd BROADCAST 0x02, SubCmd = 0x00

Suche nach HM-WR ID aus dem System: 7E 02 81101507 81101507 00 02 7F // MainCmd BROADCAST 0x02, SubCmd = 0x00

51 63500316 63500316 5A5A 34 0C60 09 52 3168 // MainCmd CONTROL_LOCK_MI__LIMIT_POWER_ONOFF 0x51, SubCmd CONTROL_LIMIT_POWER_SUB 0x5A5A

danke für die Traces

Für MI-WR: MainCmd: 0x01 CHANGE_MOD_2M_250K 0x02 BROADCAST 0x06 REQ_RF_SVERSISON 0x07 REQ_RF_RVERSISON mit SubCmd = 0x00 alle in UsartNrf_Send_NetCmdToNrfCmd(). 0x0F REQ_VERSISON in UsartNrf_Send_PackNrfCmd() bzw. UsartNrf_Send_PackBaseCommand()

Ausgewertet wird bei 0x81 ANSWER_CHANGE_MOD_2M_250K direkt in UsartNrf_Process_Loop() und 0x02 BROADCAST in UsartNrf_Process_Inverter_NetCmd_SearchId() 0x86 ANSWER_REQ_RF_SVERSISON in UsartNrf_Process_Inverter_Version() bzw. UsartNrf_Process_Inverter_Version_InverterRf 0x87 ANSWER_REQ_RF_RVERSISON in UsartNrf_Process_Inverter_Version_DtuRf(), 0x8F ANSWER_REQ_VERSISON in UsartNrf_Process_Version_Inverter(),

Für den HM-WR: UsartNrf3_Send_Mi_Nrf_VER() mit temp_dat[0] = 0x06 (= MainCmd) die Auswertung erfolgt in UsartNrf_Process_Inverter_Version_InverterRf()

Seriennummer Deiner DTU-PRO posten, damit ich mir mal die CRC Prüfsummen ansehen kann. Ich bekomme die nämlich noch nicht sauber hin. 10F873600117

Die müssten noch in die Markdown Dokumentation auf grindylow-ahoy/docs/hoymiles-format-description.md aber ich würde das vermutlich erst machen, wenn ich die anderen Kommandos ebenfalls verstanden habe.

Wenn Du willst kannst Du es aber gerne schon mal anfangen / weitermachen. Ich habe vor einiger Zeit auch schon mal angefangen mit wavedrom einzelne Pakete bzw. Kommandos zu dokumentieren. Aber bisher war ja außer dem 0x0B RealTimeRunData_Debug bzw. "Zeit setzen" und damit Werte abfragen noch nicht so viel bekannt. [

Wie sieht das Device Info Kommando Alarm Data 0x11 / Alarm Update 0x12 aus ?

REQ_ARW_DAT_ALL 0x15 mit SubCmd: AlarmData = 17, // 0x11

suche mal nach NET_ALARM_DATA / NET_ALARM_UPDATE bzw. dem anderen AlarmUpdate das findet sich nicht überall als Substring wie AlarmData.

15 74 40 33 29 78 56 34 12 80 11 00 62 7b 69 fe 00 00 00 00 00 00 00 00 47 d7 bc

15 74403329 78563412 80 1100 62D80183 0000 0000 00000000 0765 FE --- AlarmData 0x11
15 74403329 78563412 80 1200 62D80183 0000 0000 00000000 FFC4 2A --- AlarmUpdate 0x12
15 74403329 78563412 80 1E00 62D80183 0000 0000 00000000 086A 0E --- GetSelfCheckState 0x1E
^^------------------------------------------------------------------ MainCmd 0x15 REQ_ARW_DAT_ALL
   ^^^^^^^^--------------------------------------------------------- WR Serial ID
            ^^^^^^^^------------------------------------------------ DTU Serial ID
                     ^^--------------------------------------------- MultiFrameID 0x80
                        ^^------------------------------------------ SubCmd bzw. DataType: 0x11 = AlarmData, 0x12 AlarmUpdate
                          ^^---------------------------------------- rev Protocol Revision ?
                             ^^^^^^^^------------------------------- UNIX timestamp 62BE1CE0 -> 2022-07-01 00:00:00
                                      ^^^^-------------------------- Gap always 0x0000
                                           ^^^^--------------------- 0x0000, nur bei AlarmData: WarnSerNub (Warning Serial Number)  
                                                ^^^^^^^^------------ Password always 0x0000
                                                         ^^^^------- CRC16 / CRC-Modbus über die UserData, excl. Frame ID!
                                                              ^^---- CRC8

0x80 ist die MultiFrameID für SingleFrame Nachrichten !

Dann kommt das SubCmd 0x11 bzw. in UsartNrf3_Send_PackPollMultiDataCommand wird das als DataType 0x11 und 0x00 (rev = Protokoll Revision/Version) bezeichnet (zusammen zwei Byte) und weiter übergeben bzw. gleichzeitig als CurRecSendPackageDataType gesetzt. temp_dat[9] = 0x80;//Multi-frame identification temp_dat[10] = DataType;//User data: data type CurRecSendPackageDataType = DataType;//The currently packaged data type temp_dat[11] = 0;//rev

Danach folgt der Timestamp, temp_dat[12] = (u8)(time >> 24); temp_dat[13] = (u8)(time >> 16); temp_dat[14] = (u8)(time >> 8); temp_dat[15] = (u8)(time);

das Gap (zwei Byte, steht im Excel) temp_dat[16] = Gap;//User data: data upload server interval temp_dat[17] = Gap >> 8;

und im Falle von AlarmData eine Unterscheidung: if(DataType == AlarmData) { // temp_dat[18] = (u8)((CurRealAlarmNum + 1) / 0xff); // temp_dat[19] = (u8)((CurRealAlarmNum + 1) % 0xff); temp_dat[18] = (u8)((WarnSerNub[PortNO]) / 0xff); temp_dat[19] = (u8)((WarnSerNub[PortNO]) % 0xff); } else { memset((u8 *) & (temp_dat[18]), 0, 2); // User data: the latest alarm serial number received on the same day }

Also zwei Bytes 0x00 00 um die letzte Alarm Serial Nummer zu abzufragen, bzw. die aktuelle Alarm Serial Number falls man schon welche kennt. Dann noch das Password also 0x00000000 (vier Bytes alle 0). memcpy((u8 *)(&temp_dat[20]), Password, 4); //User data: anti-theft password

Dann noch schnell die CRC-16 über die Daten nach der MultiFrameID drüber berechnet for(i = 10; i < 24; i++) { if((i - 10) % 2 == 1) { TempCrc = (u16)(temp_dat[i - 1] << 8) | (temp_dat[i]); DatCrc = CalcCRC16t(TempCrc, DatCrc); } }

temp_dat[24] = (u8)(DatCrc >> 8);
temp_dat[25] = (u8)(DatCrc);

und dann die CRC8 über alles. temp_dat[26] = Get_crc_xor((u8 *)temp_dat, 26);

Danach wird dann anhand des CurRecSendPackageDataType die entsprechende Antwort in UsartNrf3_Process_DevInform() bzw. UsartNrf3_Process_DevInform_Alarm() oder UsartNrf3_Process_DevInform_InformUpdate() geparst.

Routine UsartNrf3_Process_DevInform_Alarm wird verwendet um die o.a. Alarm Payload zu dekodieren. Es werden immer in 12 byte zusammengefaßt, wobei die ersten beiden die Alarm Version number darstellen:

00 01 <-- Alarm Version number
80 01 00 01 62 26 62 26 00 00 00 00 
00 d1 00 04 62 2e 00 00 00 00 00 00
00 d2 00 05 62 2e 00 00 00 00 00 00
00 cf 00 06 63 5a 00 00 00 00 00 dc
40 8f 00 0c 62 2e 69 fd 00 03 07 a3
40 93 00 0d 62 2e 69 fd 00 00 00 00
80 02 00 22 6c a4 6c a4 ff ff ff fa
80 02 00 23 6c ab 6c ab ff ff ff f9
80 02 00 24 6c ec 6c ec ff ff ff bf
80 02 00 25 6c f1 6c f1 ff ff ff fb
80 02 00 26 6c fb 6c fb ff ff ff f6
80 02 00 27 6d 18 6d 18 ff ff ff e3
80 02 00 28 6d 22 6d 22 ff ff ff f6
80 02 00 29 6d 27 6d 27 ff ff ff fb
80 02 00 2a 6d 32 6d 32 ff ff ff f5
|...| |...| |...| |...| |...| |...|
+2 +3 +4 +5 +6 +7 +8 +9 +10.. +12..
<WCode>     |     |     |     |
      <WNum>|     |     |     |
      <WarnSerNub>|     |     |
            <AlarmStartTime>  |
                  <AlarmEndTime> 
                        <AlarmData1> 
                              <AlarmData2>

AlarmId: Alarm_Id[0]), (u8 *)MIMajor[PortNO].Property.Pre_Id, 2); Alarm_Id[2]), (u8 *)MIMajor[PortNO].Property.Id, 4);

WCode: WCode = (u16)pProBuffer[i * 12 + 2] << 8 | pProBuffer[i * 12 + 3]; WNum: WNum), &(pProBuffer[i * 12 + 4]), 2); WarnSerNub: WarnSerNub[PortNO] = (u16)pProBuffer[i * 12 + 4] << 8 | (u16)pProBuffer[i * 12 + 5]; WTime1=AlarmStartTime: //Alarm start time AlarmTime = (u32)((u16)pProBuffer[i * 12 + 6] << 8) | ((u16)pProBuffer[i * 12 + 7]) + DateToSec(calendar); // AM AlarmTime = 12 * 60 * 60 + (u32)(((u16)pProBuffer[i * 12 + 6] << 8) | ((u16)pProBuffer[i * 12 + 7])) + DateToSec(calendar); // PM WTime2=AlarmEndTime : //Alarm end time AlarmTime = (u32)((u16)pProBuffer[i * 12 + 8] << 8) | ((u16)pProBuffer[i * 12 + 9]) + DateToSec(calendar); // AM AlarmTime = 12 * 60 * 60 + (u32)(((u16)pProBuffer[i * 12 + 8] << 8) | ((u16)pProBuffer[i * 12 + 9])) + DateToSec(calendar); // PM AlarmData1: Data1[0]), &(pProBuffer[i * 12 + 10]), 2); AlarmData2: Data2[0]), &(pProBuffer[i * 12 + 12]), 2);

Das was Jan-Jonas als a_count identifiziert hat ist in Wirklichkeit WarnSerNub bzw. das High byte davon ist auch WNum.

Das was in Deiner Ausgabe mit 0x4d02 0x4d02 als 19714 sec 19714 sec identifiziert wurde sind die Sekunden seit 0:00 bzw. 12:00 Uhr für den AlarmStartTime/Wtime1 bzw. AlarmEndeTime/WTime2 80 01 00 01 4d 02 4d 02 00 00 00 00: uptime=5:28:34 a_count=1 opcode=128 a_code=1 a_text=Inverter start BBHHHHH: (128, 1, 1, 19714, 19714, 0, 0)

Die untere Ausgabe mit 19714 das ist also 5:28:34 h eine normale Uhrzeit, AM da die u.a. AlarmStart und AlarmEnde AM/PM Bits 13 & 12 aus dem WCode 0 sind. Der WR kennt kein Datum, die DTU addiert hierzu das Datum. Dazu muss man jeweils das aktuelle Datum addieren, dann hat man einen echten Timestamp Lediglich den Datumswechsel scheint der WR anhand der von der DTU gesendeten Timestamps zu machen.

(WCode >> 14) & 0x03 : (0x8001 >>14) & 0x03 : (0x02) & 0x03 = 0x02 // bestimmt den sog. RunCode siehe unten (WCode >> 13) & 0x01 : (0x8001 >>13) & 0x01 : (0x04) & 0x01 = 0x00 // 0x00 = AM, 0x01 = PM AlarmStart (WCode >> 12) & 0x01 : (0x8001 >>12) & 0x01 : (0x08) & 0x01 = 0x00 // 0x00 = AM, 0x01 = PM AlarmEnde

Es wird offenbar immer das aktuelle Datum des Tages in Sekunden dazu gezählt und anhand des Bit 13 / 12 im WCode entschieden ob AlarmStartTime / AlarmEndTime vormittags oder nachmittags liegt/lag.

Aus Bit 14 & 15 des WCode wird noch ein sog. Run_Status[0] und [1] extrahiert. Was auch immer das aussagt? WCode >> 14) & 0x03)) == 0) Run_Status[0] = 0x00; Run_Status[1] = 0x08; WCode >> 14) & 0x03) == 1) Run_Status[0] = 0x00; Run_Status[1] = 0x03;

Was der Run_Status genau aussagt weiß man aktuell noch nicht oder?

Ach ja und AlarmData1/Data1 und AlarmData2/Data2 sind beide 0x0000

WCode ist also 0x8001 und WNum = 0x80

Für WCode 0x80 01 sind die Bits also

0x        8            0             0            1
15 14 13 12  11 10 09 08   07 06 05 04  03 02 01 00
 8  4  2  1   8  4  2  1    8  4  2  1   8  4  2  1    
 1  0  0  0   0  0  0  0    0  0  0  0   0  0  0  1
00 D1 00 04 4D 0A 00 00 00 00 00 00: 
 uptime=5:28:42 a_count=4 opcode=0 a_code=209 a_text=Port 1 no input
 BBHHHHH: (0, 209, 4, 19722, 0, 0, 0)

Hier ist WCode 0x00 D1, also ist ((WCode >> 14) & 0x03)) == 0) und somit Run_Status[0] = 0x00; und Run_Status[1] = 0x08;. Die Uhrzeit stimmt, da auch Bit 12&13 0 sind ist die AlarmStartTime/Wtime1 AM. Und die WarnSerNub = 0x00 04 und WNum = 0x00

40 8f 00 0c 4d 0a 54 d9 00 03 07 a3: 
 uptime=5:28:42 a_count=12 opcode=64 a_code=143 a_text=Grid undervoltage
 BBHHHHH: (64, 143, 12, 19722, 21721, 3, 1955)

Hier ist WCode 0x40 8f, also ist ((WCode >> 14) & 0x03)) == 1) und somit Run_Status[0] = 0x00; und Run_Status[1] = 0x03;. Die Uhrzeit stimmt, da auch Bit 12&13 wieder 0 sind ist auch die AlarmStartTime/Wtime1 0x4d0a = 5:28:42h AlarmEndTime/Wtime2 0x54 d9 =6:02:01h beide AM. Hier sind AlarmData1/Data1 = 0x0003 und AlarmData2/Data2 = 0x07a3 Und die WarnSerNub = 0x00 0c und WNum = 0x00

//CurAlarmState -- alarm state type
typedef enum
{
    InitState = 0,
    //There is a new alarm record
    HasNewAlarmRecord = 1,
    //There is a new wave recording alarm
    HasNewWaveRecord = 2,
    //No new alarm record
    HasNONewAlarmRecord = 3,
    //No new wave recording alarm
    HasNONewWaveRecord = 4,
    // Suspend the alarm
    AlarmInforUpdate_Server = 10,
    //APP state switch
    AlarmInforUpdate_App = 11,
} AlarmStateType;
extern volatile AlarmStateType CurAlarmState;
typedef union
{
    struct
    {
        //dong 20200520
        u16 WCode;
        u8 Alarm_Id[6] ;
        u8 WNum[2];
        u8 WTime1[4];
        u8 WTime2[4];
        u8 Data1[2];
        u8 Data2[2];
    } Data;
    u8 DataMsg[22];
} AlarmDataType;

Welche Device Control DEVCONTROL_ALL (0x51) Sub Kommandos gibt es

SubCmd =

  • Type_TurnOn (0x00) Boot
  • Type_TurnOff (0x01) Shutdown
  • Type_Restart (0x02)
  • Type_Lock (0x03)
  • Type_Unlock (0x04)
  • Type_CleanState_LockAndAlarm (0x14) Lock Alarm löschen
  • Type_ActivePowerContr (0x0B) Active Power Limit
  • Type_ReactivePowerContr (0x0C) Reactive Power Limit
  • Type_PFSet (0x0D) Power Faktor Limit
  • Type_SelfInspection (0x28) Selbsttest
typedef enum
{
    InverterDevInform_Simple = 0, // 0x00
    InverterDevInform_All = 1, // 0x01
    GridOnProFilePara = 2, // 0x02
    HardWareConfig = 3, // 0x03
    SimpleCalibrationPara = 4, // 0x04
    SystemConfigPara = 5, // 0x05
    RealTimeRunData_Debug = 11, // 0x0b
    RealTimeRunData_Reality = 12, // 0x0c
    RealTimeRunData_A_Phase = 13, // 0x0d
    RealTimeRunData_B_Phase = 14, // 0x0e
    RealTimeRunData_C_Phase = 15, // 0x0f
    //Alarm data - all unsent alarms
    AlarmData = 17, // 0x11
    //Alarm data - all pending alarms
    AlarmUpdate = 18, // 0x12
    RecordData = 19, // 0x13
    InternalData = 20, // 0x14
    GetLossRate = 21, // 0x15
    GetSelfCheckState = 30, // 0x1e
    InitDataState = 0xff, // 0xFF
} DataType;

Wie kann man das Passwort setzen / zurücksetzen

Mit SubCmd 0x03 Type_Lock und 0x04 Type_Unlock kann man ein Passwort (vierstellige Pin) setzen. AdminPasswd[4] = {10, 16, 50, 82}; // = 0x0A 0x10 0x32 0x52 oder 0x0A103252

Wie sieht ein Device Control Kommando aus ?

7E 51 81101507 81101507 81 00 00 B001 61 7F Type_TurnOn 0x00
7E 51 81101507 81101507 81 01 00 2000 F1 7F Type_TurnOff 0x01
7E 51 81101507 81101507 81 02 00 D000 02 7F Type_Restart 0x02
^^------------------------------------------ SOF Start of Frame 0x7E
   ^^--------------------------------------- MainCmd 0x51 DEVCONTROL_ALL
      ^^^^^^^^------------------------------ WR Serial ID
               ^^^^^^^^--------------------- DTU Serial ID wird vom NRF24 überschrieben, da initial vom Treiber gesetzt
                        ^^------------------ Single Frame ID
                           ^^--------------- SubCmd siehe oben
                           ^^^^^------------ Control Mode ? immer zwei Byte im Gen3 Protokoll
                                 ^^^^------- CRC16 / CRC-Modbus über die Daten, nach und excl. Frame ID!
                                      ^^---- CRC8
                                         ^^- EOF End of Frame 0x7F

Wie funktioniert das Active Power Limit (0x0B) Sub Kommando

Leistungsreduzierung HM300 · Issue #35 · tbnobody/OpenDTU https://github.com/tbnobody/OpenDTU/issues/35

Active Power Limit (0x0B)

21:06:33.342 > TX 51 72 61 55 82 78 56 34 12 81 0B 00 00 96 01 00 DC E0 BC 51 74 40 33 29 78 56 34 12 81 0b 00 00 96 01 00 dc e0 56 => 0x96 = dezimal 150, d.h. das sind 15.0 Watt 21:06:33.570 > Interrupt received 21:06:33.570 > 21:06:33.570 > >> RX OK: D1 72 61 55 82 72 61 55 82 81 00 00 0B 00 14 07 48

<0x51> <0x81> <0x0b 0x00> <0x00 0x96> <0x01 0x00> <CRC16/modbus> <?desc?-fix 0x01 0x00> <crc16/modbus>

Das mit dem 0x0b 0x00 hatte ich irgendwie anders interpretiert, da er den Wert ja mit (u16)(SubCmd << 8) in UsartNrf_Send_DevControlUpdate() übergibt und als u16 ControlMode in UsartNrf3_Send_PackDevControl() übernimmt. Also wird aus dem SubCmd=0x0b -> Control Mode=0x0b00. Und das wird dann wiederum als temp_dat[10] = 0x0b und temp_dat[11] = 0x00 an den NRF24 per Usart übergeben.

P_Limit1 is the absolute value of the power limit, the unit is W, with 1 decimal place

Dtu3Detail.Property.DRM_Limit_Switch oder Dtu3Detail.Property.SunSpec_Switch dann .Desc[0] = 0x00 .Desc[1] 0x01

Dtu3Detail.Property.Zero_Export_Switch oder Dtu3Detail.Property.Phase_Balance_Switch dann .Desc[0] = 0x00 .Desc[1] 0x00

vllt wäre es übersichtlicher, wenn du Desc[0] und [1] in der Reihenfolge vertauschst, das liest sich besser, so landet es ja am Ende im Befehl, erst [0] dann [1]

Traces der DTU Pro

Verschiedene Leistungen, ganzes File folgt in Kürze, das ist unformatiert zum Schluss hin ist es maximale Leistung, davor etwa 350-450W schwankend

63500316 ist der MI-600 (Klon) 81101507 ist der HM-1500

MI Kommandos

Device Info: 7E 09 63500316 63500316 00 09 7F // ??? Data A 7E 11 63500316 63500316 00 11 7F // ??? Data B

Device Control: 7E 51 63500316 63500316 5A5A 25 08CC B0 7F // Power Limit 37% ??? 7E 51 63500316 63500316 5A5A 5D 15CC D5 7F // Power Limit 93% ???

HM Kommandos

7E 15 81101507 81101507 FF EA 7F ???

Retransmits: 7E 15 81101507 81101507 82 97 7F 7E 15 81101507 81101507 83 96 7F 7E 15 81101507 81101507 84 91 7F 7E 15 81101507 81101507 85 90 7F 7E 15 81101507 81101507 86 93 7F 7E 15 81101507 81101507 87 92 7F 7E 15 81101507 81101507 88 9D 7F 7E 15 81101507 81101507 89 9C 7F 7E 15 81101507 81101507 8A 9F 7F 7E 15 81101507 81101507 8B 9E 7F

Device Info: RealTimeRunData_Debug 0x0B mit verschiedenen Zeitstempeln 7E 15 81101507 81101507 80 0B00 62D806F7 0000 259C 00000000 2C4F 0F 7F 7E 15 81101507 81101507 80 0B00 62D806F9 0000 259C 00000000 4C03 2D 7F 7E 15 81101507 81101507 80 0B00 62D806FA 0000 259C 00000000 BC17 CA 7F 7E 15 81101507 81101507 80 0B00 62D806FB 0000 259C 00000000 2C1A 56 7F

AlarmData 0x11 7E 15 81101507 81101507 80 1100 62D806FC 0000 259C 0000 0000 C627 9C 7F

Device Control: 7E 51 81101507 81101507 81 0B00 2B98 0000 6B89 8A 7F // Active Power Limit 0x0B; 0x2B98 = 1116.0 W

Wie funktionier das Power Faktor Setzen (0x0C) Kommando

81 0b 00 00 64 00 0a ^^------------------- SingleFrameID ^^---------------- SubCmd bzw. DevControlType: 0x0b = Type_ActivePowerContr ^^ ^^------------- Control Mode ^^ ^^------- PowerPFDev.SetValut 0x0064 = 4.0 W ^^ ^^- PowerPFDev.Desc das müsste m.W. immer (?) mit 0x00 01 angegeben sein

Sollte also 51 74403329 78563412 81 0D00 0000 0000 0601 14 oder sowas sein.... Schreibt halt m.W. einen PowerFactor, aber Du hast glaube ne DTU mit der Du das ggf. korrigieren kannst oder ?

Wie funktioniert das Status selbst Prüfen GetSelfCheckState (0x1E) Sub Kommando

Interessant wäre auch: GetSelfCheckState: // 0x1e Da kann man die Serial ID und SW/HW Version des Wechselrichters auslesen...

15 74403329 78563412 80 1100 62D80183 0000 0000 00000000 0765 FE --- AlarmData 0x11 15 74403329 78563412 80 1200 62D80183 0000 0000 00000000 FFC4 2A --- AlarmUpdate 0x12 15 74403329 78563412 80 1E00 62D80183 0000 0000 00000000 086A 0E --- GetSelfCheckState 0x1E ^^------------------------------------------------------------------ MainCmd 0x15 REQ_ARW_DAT_ALL ^^^^^^^^--------------------------------------------------------- WR Serial ID ^^^^^^^^------------------------------------------------ DTU Serial ID ^^--------------------------------------------- MultiFrameID 0x80 ^^------------------------------------------ SubCmd bzw. DataType: 0x11 = AlarmData, 0x12 AlarmUpdate ^^---------------------------------------- rev Protocol Revision ? ^^^^^^^^------------------------------- UNIX timestamp 62BE1CE0 -> 2022-07-01 00:00:00 ^^^^-------------------------- Gap always 0x0000 ^^^^--------------------- 0x0000, nur bei AlarmData: WarnSerNub (Warning Serial Number)
^^^^^^^^------------ Password always 0x0000 ^^^^------- CRC16 / CRC-Modbus über die UserData, excl. Frame ID! ^^---- CRC8

MainCmd 0x15, 0x80 MultiFrameID, SubCmd GetSelfCheckState 0x1E, Timestamp wie oben, Gap ist 0x0000 und Password ist 0x00000000

Probiere mal: 15 74403329 78563412 80 1E00 62D80183 0000 0000 00000000 086A 0E --- GetSelfCheckState 0x1E

Transmit 27 | 15 74 40 33 29 78 56 34 12 80 1E 00 62 D8 01 83 00 00 00 00 00 00 00 00 08 6A F7 Received 21 bytes channel 23: 95 74 40 33 29 74 40 33 29 81 00 01 00 00 00 00 00 00 CB 50 8E Payload: 00 01 00 00 00 00 00 00 cb 50

Das erste Byte 0x95 ist die Antwort 0x15 | 0x80 = 0x95

Welche Device Info REQ_ARW_DAT_ALL (0x15) Sub Kommandos gibt es

SubCmd =

  • InverterDevInform_Simple (0x00)
  • GridOnProFilePara (0x02)
  • HardWareConfig (0x03) ???
  • SimpleCalibrationPara (0x04) ???
  • SystemConfigPara (0x05) ???
  • RealTimeRunData_Debug (0x0B)
  • RealTimeRunData_Reality (0x0C)
  • RealTimeRunData_A_Phase (0x0D) ???
  • RealTimeRunData_B_Phase (0x0E) ???
  • RealTimeRunData_C_Phase (0x0F) ???
  • AlarmData (0x11)
  • AlarmUpdate (0x12)
  • RecordData (0x13)
  • InternalData (0x14) ???
  • GetLossRate (0x15) ???
  • GetSelfCheckState (0x1E) ???

Bei allen anderen Anfragen erfolgt vorher ein CurNetCmd = NET_INIT, also MainCmd = REQ_ARW_DAT_ALL (0x15) und SubCmd = RealTimeRunData_Reality (0x0c) bzw. RealTimeRunData_Debug (0x0b). Wir verwenden ja schon länger den zweiten Fall 0x0b für die Status Meldungen

Wie kann man den Device Status prüfen

7E 15 81101507 81101507 80 0B00 62D806FB 0000 259C 00000000 2C1A 56 7F
^^--------------------------------------------------------------------- SOF Start of Frame 0x7E
   ^^------------------------------------------------------------------ MainCmd 0x15 REQ_ARW_DAT_ALL
      ^^^^^^^^--------------------------------------------------------- WR Serial ID
               ^^^^^^^^------------------------------------------------ DTU Serial ID
               ^^^^^^^^--------------------- DTU Serial ID wird vom NRF24 überschrieben, da initial vom Treiber gesetzt
                        ^^--------------------------------------------- MultiFrameID 0x80 = SingleFrame
                           ^^------------------------------------------ SubCmd bzw. DataType: 0x0B = RealTimeRunData_Debug, 0x0C RealTimeRunData_Reality
                             ^^---------------------------------------- rev Protocol Revision ?
                           ^^^^---------------------------------------- Control Mode ? immer zwei Byte im Gen3 Protokoll
                                ^^^^^^^^------------------------------- UNIX timestamp 62BE1CE0 -> 2022-07-01 00:00:00
                                         ^^^^-------------------------- Gap always 0x0000
                                              ^^^^--------------------- 0x0000, nur bei AlarmData: WarnSerNub (Warning Serial Number)
                                                                         // User data: the latest alarm serial number received on the same day
                                                   ^^^^^^^^------------ Password always 0x00000000
                                                            ^^^^------- CRC16 / CRC-Modbus über die UserData, excl. Frame ID!
                                                            ^^^^------- CRC16 / CRC-Modbus über die Daten, nach und excl. Frame ID!
                                                                 ^^---- CRC8
                                                                    ^^- EOF End of Frame 0x7F

Welche Parameter Setzen PARASET_ALL (0x52) Kommandos gibt es

UsartNrf3_Send_PackSetPara

Sowas wie EleEnergySet 0x01 und AntitheftParaSet 0x04

  • EleEnergySet (0x01)
  • AntitheftParaSet (0x04) Passwort setzen

Welche File Multi-Package Kommandos gibt es

Wie wird das DOWN_PRO (0x0E) / DOWN_DAT (0x0A) verwendet

  • Type_Init (0xff) Das SubCmd = Type_Init (0xFF) scheint nur bei MainCmd = DOWN_PRO (0x0E) oder DOWN_DAT (0x0A) verwendet zu werden.

Was bedeutet die Antwort ANSWER_EXCEPTION_MULTI_ONE 0xF1 bzw. ANSWER_EXCEPTION_ONE_MULTI 0xFF

Das ist ein Fehler bzw. eine Fehlermeldung.

#define ANSWER_EXCEPTION_MULTI_ONE 0xF1
#define ANSWER_EXCEPTION_ONE_MULTI 0XFF

Fehler ist 0xF1 oder 0xFF je nachdem ob SingleFrame / MultiFrame Anfrage


Funkverbindung

Wird Auto-ACK vom Wechselrichter beim nRF24L01+ unterstützt

Tatsache ist, es ist sehr unwahrscheinlich, dass der WR antworten wird, wenn er nicht einmal ein ACK sendet.


Reverse Engineering

Wo und wie kann das Protokoll mitgeschnitten werden

  • Mit einem Scanner (HackRF oder nRF24L01+) auf der/n Funkfrequenz/en

  • Mit einem Logic Analyzer zwischen MCU und nRF24L01+

eine RX und eine TX Leitung zwischen den beiden Chips (UART). Der Mikrocontroller hat laut Datenblatt ([1], s.o.) genau eine serielle Schnittstelle, bei dem verbauten 32-pin-Gehäuse sollten die hier sein:

  • P0.4 (Pin 7): RXD
  • P0.3 (Pin 10): TXD

Was ist ein Scanner und welche gibt es

Welche Scanner eingesetzt wurden: ich nutzte die von den Libs RF24 und nrf_hal.

Clone this wiki locally