From de90c19eb3b5ac5a15453c78d1d4ee48d9bf25cc Mon Sep 17 00:00:00 2001
From: Christian Ehrlicher <ch.ehrlicher@gmx.de>
Date: Mon, 12 Sep 2022 18:02:14 +0200
Subject: [PATCH] RPI: add handling for InverterDevInform_All message, handle
 RealTimeRunData_Reality similar to RealTimeRunData_Debug

---
 tools/rpi/hoymiles/__init__.py          | 15 ++++----
 tools/rpi/hoymiles/__main__.py          | 46 ++++++++++++++++++++-----
 tools/rpi/hoymiles/decoders/__init__.py | 40 +++++++++++++++++++++
 3 files changed, 84 insertions(+), 17 deletions(-)

diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py
index d6ca70d2..965971b8 100644
--- a/tools/rpi/hoymiles/__init__.py
+++ b/tools/rpi/hoymiles/__init__.py
@@ -482,21 +482,20 @@ def compose_esb_packet(packet, mtu=17, **params):
         fragment = compose_esb_fragment(packet[i:i+mtu], **params)
         yield fragment
 
-def compose_set_time_payload(timestamp=None):
+def compose_send_time_payload(cmdId):
     """
     Build set time request packet
 
-    :param timestamp: time to set (default: int(time.time()) )
-    :type timestamp: int
+    :param cmd to request
+    :type cmd: uint8
     :return: payload
     :rtype: bytes
     """
-    if not timestamp:
-        timestamp = int(time.time())
+    timestamp = int(time.time())
 
-    payload = b'\x0b\x00'
+    payload = struct.pack('>B', cmdId) + b'\x00'
     payload = payload + struct.pack('>L', timestamp)  # big-endian: msb at low address
-    payload = payload + b'\x00\x00\x00\x05\x00\x00\x00\x00'
+    payload = payload + b'\x00\x00\x00\x00\x00\x00\x00\x00'
 
     return frame_payload(payload)
 
@@ -649,7 +648,7 @@ class InverterTransaction:
         except StopIteration:
             seq_last = max(frames, key=lambda frame:frame.seq).seq if len(frames) else 0
             self.__retransmit_frame(seq_last + 1)
-            raise BufferError(f'Missing packet: Last packet {len(self.scratch)}')
+            raise BufferError(f'Missing packet: Last packet {seq_last + 1}')
 
         # Rebuild payload from unordered frames
         payload = b''
diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py
index 219b4449..7253b677 100644
--- a/tools/rpi/hoymiles/__main__.py
+++ b/tools/rpi/hoymiles/__main__.py
@@ -7,6 +7,7 @@ Hoymiles micro-inverters main application
 
 import sys
 import struct
+from enum import IntEnum
 import re
 import time
 from datetime import datetime
@@ -16,7 +17,7 @@ from yaml.loader import SafeLoader
 import paho.mqtt.client
 import hoymiles
 
-def main_loop():
+def main_loop(do_init):
     """Main loop"""
     inverters = [
             inverter for inverter in ahoy_config.get('inverters', [])
@@ -25,9 +26,29 @@ def main_loop():
     for inverter in inverters:
         if hoymiles.HOYMILES_DEBUG_LOGGING:
             print(f'Poll inverter {inverter["serial"]}')
-        poll_inverter(inverter)
-
-def poll_inverter(inverter, retries=4):
+        poll_inverter(inverter, do_init)
+
+class InfoCommands(IntEnum):
+    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
+    AlarmData = 17                # 0x11, Alarm data - all unsent alarms
+    AlarmUpdate = 18              # 0x12, Alarm data - all pending alarms
+    RecordData = 19               # 0x13
+    InternalData = 20             # 0x14
+    GetLossRate = 21              # 0x15
+    GetSelfCheckState = 30        # 0x1e
+    InitDataState = 0xff
+
+def poll_inverter(inverter, do_init, retries=4):
     """
     Send/Receive command_queue, initiate status poll on inverter
 
@@ -39,11 +60,15 @@ def poll_inverter(inverter, retries=4):
     dtu_ser = ahoy_config.get('dtu', {}).get('serial')
 
     # Queue at least status data request
-    command_queue[str(inverter_ser)].append(hoymiles.compose_set_time_payload())
+    inv_str = str(inverter_ser)
+    if do_init:
+      command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.InverterDevInform_All))
+#      command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.SystemConfigPara))
+    command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.RealTimeRunData_Debug))
 
-    # Putt all queued commands for current inverter on air
-    while len(command_queue[str(inverter_ser)]) > 0:
-        payload = command_queue[str(inverter_ser)].pop(0)
+    # Put all queued commands for current inverter on air
+    while len(command_queue[inv_str]) > 0:
+        payload = command_queue[inv_str].pop(0)
 
         # Send payload {ttl}-times until we get at least one reponse
         payload_ttl = retries
@@ -276,10 +301,13 @@ if __name__ == '__main__':
 
     loop_interval = ahoy_config.get('interval', 1)
     try:
+        do_init = True
         while True:
             t_loop_start = time.time()
 
-            main_loop()
+            main_loop(do_init)
+
+            do_init = False
 
             print('', end='', flush=True)
 
diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py
index 809dd796..4d8e218f 100644
--- a/tools/rpi/hoymiles/decoders/__init__.py
+++ b/tools/rpi/hoymiles/decoders/__init__.py
@@ -314,6 +314,28 @@ class EventsResponse(UnknownResponse):
                 print(f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk)))
             print(end='', flush=True)
 
+class HardwareInfoResponse(UnknownResponse):
+    def __init__(self, *args, **params):
+        super().__init__(*args, **params)
+        """
+        const byteAssign_t InfoAssignment[] = {
+            { FLD_FW_VERSION,           UNIT_NONE,   CH0,  0, 2, 1 },
+            { FLD_FW_BUILD_YEAR,        UNIT_NONE,   CH0,  2, 2, 1 },
+            { FLD_FW_BUILD_MONTH_DAY,   UNIT_NONE,   CH0,  4, 2, 1 },
+            { FLD_HW_ID,                UNIT_NONE,   CH0,  8, 2, 1 }
+        };
+        self.response = bytes('\x27\x1a\x07\xe5\x04\x4d\x03\x4a\x00\x68\x00\x00\x00\x00\xe6\xfb', 'latin1')
+        """
+        fw_version, fw_build_yyyy, fw_build_mmdd, unknown, hw_id = struct.unpack('>HHHHH', self.response[0:10])
+
+        fw_version_maj = int((fw_version / 10000))
+        fw_version_min = int((fw_version % 10000) / 100)
+        fw_version_pat = int((fw_version %   100))
+        fw_build_mm = int(fw_build_mmdd / 100)
+        fw_build_dd = int(fw_build_mmdd % 100)
+        print()
+        print(f'Firmware: {fw_version_maj}.{fw_version_min}.{fw_version_pat} build at {fw_build_dd}/{fw_build_mm}/{fw_build_yyyy}, HW revision {hw_id}')
+
 class DebugDecodeAny(UnknownResponse):
     """Default decoder"""
 
@@ -359,6 +381,9 @@ class DebugDecodeAny(UnknownResponse):
 
 
 # 1121-Series Intervers, 1 MPPT, 1 Phase
+class Hm300Decode01(HardwareInfoResponse):
+    """ Firmware version / date """
+
 class Hm300Decode02(EventsResponse):
     """ Inverter generic events log """
 
@@ -407,6 +432,9 @@ class Hm300Decode0B(StatusResponse):
         """ Inverter temperature in °C """
         return self.unpack('>H', 26)[0]/10
 
+class Hm300Decode0C(Hm300Decode0B):
+    """ 1121-series mirco-inverters status data """
+
 class Hm300Decode11(EventsResponse):
     """ Inverter generic events log """
 
@@ -415,6 +443,9 @@ class Hm300Decode12(EventsResponse):
 
 
 # 1141-Series Inverters, 2 MPPT, 1 Phase
+class Hm600Decode01(HardwareInfoResponse):
+    """ Firmware version / date """
+
 class Hm600Decode02(EventsResponse):
     """ Inverter generic events log """
 
@@ -492,6 +523,9 @@ class Hm600Decode0B(StatusResponse):
         """ Event counter """
         return self.unpack('>H', 40)[0]
 
+class Hm600Decode0C(Hm600Decode0B):
+    """ 1141-series mirco-inverters status data """
+
 class Hm600Decode11(EventsResponse):
     """ Inverter generic events log """
 
@@ -500,6 +534,9 @@ class Hm600Decode12(EventsResponse):
 
 
 # 1161-Series Inverters, 2 MPPT, 1 Phase
+class Hm1200Decode01(HardwareInfoResponse):
+    """ Firmware version / date """
+
 class Hm1200Decode02(EventsResponse):
     """ Inverter generic events log """
 
@@ -619,6 +656,9 @@ class Hm1200Decode0B(StatusResponse):
         """ Event counter """
         return self.unpack('>H', 60)[0]
 
+class Hm1200Decode0C(Hm1200Decode0B):
+    """ 1161-series mirco-inverters status data """
+
 class Hm1200Decode11(EventsResponse):
     """ Inverter generic events log """