diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example index 04da1f18a..7cb0c3b71 100644 --- a/tools/rpi/ahoy.yml.example +++ b/tools/rpi/ahoy.yml.example @@ -73,7 +73,17 @@ ahoy: inverters: - name: 'balkon' serial: 114172220003 - txpower: 'low' # txpower per inverter (min,low,high,max) + txpower: 'low' # txpower per inverter (min,low,high,max) mqtt: - send_raw_enabled: false # allow inject debug data via mqtt - topic: 'hoymiles/114172221234' # defaults to 'hoymiles/{serial}' + send_raw_enabled: false # allow inject debug data via mqtt + topic: 'hoymiles/114172221234' # defaults to '{inverter-name}/{serial}' + strings: # list all available strings + - s_name: 'String 1 left' # String 1 name + s_maxpower: 395 # String 1 max power in Wp + - s_name: 'String 2 right' # String 2 name + s_maxpower: 400 # String 2 max power in Wp + - s_name: 'String 3 up' # String 3 name + s_maxpower: 405 # String 3 max power in Wp + - s_name: 'String 4 down' # String 4 name + s_maxpower: 410 # String 4 max power in Wp + diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index b9ebc45e1..c379f2e49 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -74,9 +74,11 @@ def __init__(self, *args, **params): self.inverter_ser = params.get('inverter_ser', None) self.inverter_name = params.get('inverter_name', None) self.dtu_ser = params.get('dtu_ser', None) - self.response = args[0] + strings = params.get('strings', None) + self.inv_strings = strings + if isinstance(params.get('time_rx', None), datetime): self.time_rx = params['time_rx'] else: @@ -91,7 +93,7 @@ def __dict__(self): class StatusResponse(Response): """Inverter StatusResponse object""" - e_keys = ['voltage','current','power','energy_total','energy_daily','powerfactor', 'reactive_power'] + e_keys = ['voltage','current','power','energy_total','energy_daily','powerfactor', 'reactive_power', 'irradiation'] temperature = None frequency = None powerfactor = None @@ -333,7 +335,7 @@ def __init__(self, *args, **params): { 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_unknown, UNIT_NONE, CH0, 6, 2, 1 }, + { FLD_FW_Build_Hour_Minute, UNIT_NONE, CH0, 6, 2, 1 }, { FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }, { FLD_unknown, UNIT_NONE, CH0, 10, 2, 1 }, { FLD_unknown, UNIT_NONE, CH0, 12, 2, 1 }, @@ -341,16 +343,21 @@ def __init__(self, *args, **params): }; 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, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10]) + + responce_info = self.response + logging.debug(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}') 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) - logging.debug(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}') - responce_info = self.response - logging.debug(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}') + fw_build_HH = int(fw_build_hhmm / 100) + fw_build_MM = int(fw_build_hhmm % 100) + logging.debug(f'Firmware: {fw_version_maj}.{fw_version_min}.{fw_version_pat} '\ + f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\ + f'HW revision {hw_id}') class DebugDecodeAny(UnknownResponse): """Default decoder""" @@ -426,6 +433,12 @@ def dc_energy_total_0(self): def dc_energy_daily_0(self): """ String 1 daily energy in Wh """ return self.unpack('>H', 12)[0] + @property + def dc_irradiation_0(self): + """ String 1 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property def ac_voltage_0(self): @@ -492,6 +505,12 @@ def dc_energy_total_0(self): def dc_energy_daily_0(self): """ String 1 daily energy in Wh """ return self.unpack('>H', 22)[0] + @property + def dc_irradiation_0(self): + """ String 1 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property def dc_voltage_1(self): @@ -513,6 +532,12 @@ def dc_energy_total_1(self): def dc_energy_daily_1(self): """ String 2 daily energy in Wh """ return self.unpack('>H', 24)[0] + @property + def dc_irradiation_1(self): + """ String 2 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3) @property def ac_voltage_0(self): @@ -587,6 +612,12 @@ def dc_energy_total_0(self): def dc_energy_daily_0(self): """ String 1 daily energy in Wh """ return self.unpack('>H', 20)[0] + @property + def dc_irradiation_0(self): + """ String 1 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property def dc_voltage_1(self): @@ -608,6 +639,12 @@ def dc_energy_total_1(self): def dc_energy_daily_1(self): """ String 2 daily energy in Wh """ return self.unpack('>H', 22)[0] + @property + def dc_irradiation_0(self): + """ String 2 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3) @property def dc_voltage_2(self): @@ -629,6 +666,12 @@ def dc_energy_total_2(self): def dc_energy_daily_2(self): """ String 3 daily energy in Wh """ return self.unpack('>H', 42)[0] + @property + def dc_irradiation_0(self): + """ String 3 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3) @property def dc_voltage_3(self): @@ -650,6 +693,12 @@ def dc_energy_total_3(self): def dc_energy_daily_3(self): """ String 4 daily energy in Wh """ return self.unpack('>H', 44)[0] + @property + def dc_irradiation_0(self): + """ String 4 irratiation in percent """ + if self.inv_strings is None: + return None + return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3) @property def ac_voltage_0(self): diff --git a/tools/rpi/hoymiles/outputs.py b/tools/rpi/hoymiles/outputs.py index 4913f145f..e70c3b8d7 100644 --- a/tools/rpi/hoymiles/outputs.py +++ b/tools/rpi/hoymiles/outputs.py @@ -115,14 +115,18 @@ def store_status(self, response, **params): data_stack.append(f'{measurement},string={string_id},type=power value={string["power"]:.2f} {ctime}') data_stack.append(f'{measurement},string={string_id},type=YieldDay value={string["energy_daily"]:.2f} {ctime}') data_stack.append(f'{measurement},string={string_id},type=YieldTotal value={string["energy_total"]/1000:.4f} {ctime}') + if 'irradiation' in string: + data_stack.append(f'{measurement},string={string_id},type=Irradiation value={string["irradiation"]:.2f} {ctime}') string_id = string_id + 1 + # Global if data['event_count'] is not None: data_stack.append(f'{measurement},type=total_events value={data["event_count"]} {ctime}') if data['powerfactor'] is not None: data_stack.append(f'{measurement},type=pf value={data["powerfactor"]:f} {ctime}') data_stack.append(f'{measurement},type=frequency value={data["frequency"]:.3f} {ctime}') - data_stack.append(f'{measurement},type=temperature value={data["temperature"]:.2f} {ctime}') + + data_stack.append(f'{measurement},type=Temp value={data["temperature"]:.2f} {ctime}') if data['energy_total'] is not None: data_stack.append(f'{measurement},type=total value={data["energy_total"]/1000:.3f} {ctime}') @@ -205,15 +209,19 @@ def store_status(self, response, **params): for string in data['strings']: self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage']) self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current']) + self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power']) self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily']) self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000) + if 'irradiation' in string: + self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation']) string_id = string_id + 1 # Global if data['powerfactor'] is not None: self.client.publish(f'{topic}/pf', data['powerfactor']) self.client.publish(f'{topic}/frequency', data['frequency']) - self.client.publish(f'{topic}/temperature', data['temperature']) + + self.client.publish(f'{topic}/Temp', data['temperature']) if data['energy_total'] is not None: self.client.publish(f'{topic}/total', data['energy_total']/1000) @@ -265,13 +273,16 @@ def store_status(self, data, session): self.try_publish(ts, f'dc_power{string_id}', string['power']) self.try_publish(ts, f'dc_YieldDay{string_id}', string['energy_daily']) self.try_publish(ts, f'dc_YieldTotal{string_id}', string['energy_total']) + if 'irradiation' in string: + self.try_publish(ts, f'dc_Irradiation{string_id}', string['irradiation']) string_id = string_id + 1 + # Global if data['powerfactor'] is not None: self.try_publish(ts, f'powerfactor', data['powerfactor']) self.try_publish(ts, f'frequency', data['frequency']) - self.try_publish(ts, f'temperature', data['temperature']) + self.try_publish(ts, f'Temp', data['temperature']) if data['energy_total'] is not None: self.try_publish(ts, f'total', data['energy_total'])