diff --git a/.gitignore b/.gitignore index 2ee4b679..21ae2a57 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ src/web/html/tmp/* *.suo *.ipch src/output.map + +/.venv diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py index dbe564cc..46a9275c 100644 --- a/tools/rpi/hoymiles/__init__.py +++ b/tools/rpi/hoymiles/__init__.py @@ -344,6 +344,9 @@ class HoymilesNRF: if not radio.begin(): raise RuntimeError('Can\'t open radio') + + if not radio.isChipConnected(): + logging.warning("could not connect to NRF24 radio") self.txpower = radio_config.get('txpower', 'max') @@ -411,7 +414,7 @@ class HoymilesNRF: self.radio.startListening() fragments = [] - + received_sth=False # Receive: Loop t_end = time.monotonic_ns()+timeout while time.monotonic_ns() < t_end: @@ -431,7 +434,7 @@ class HoymilesNRF: ch_rx=self.rx_channel, ch_tx=self.tx_channel, time_rx=datetime.now() ) - + received_sth=True yield fragment else: @@ -447,7 +450,11 @@ class HoymilesNRF: self.radio.setChannel(self.rx_channel) self.radio.startListening() - time.sleep(0.004) + time.sleep(0.005) + + if not received_sth: + raise TimeoutError + def next_rx_channel(self): """ diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index fef496f7..581b5acb 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -103,10 +103,11 @@ class SunsetHandler: def sun_status2mqtt(self, dtu_ser, dtu_name): if not mqtt_client or not self.suntimes: return - local_sunrise = self.suntimes.riselocal(datetime.now()).strftime("%d.%m.%YT%H:%M") - local_sunset = self.suntimes.setlocal(datetime.now()).strftime("%d.%m.%YT%H:%M") - local_zone = self.suntimes.setlocal(datetime.now()).tzinfo._key + if self.suntimes: + local_sunrise = self.suntimes.riselocal(datetime.now()).strftime("%d.%m.%YT%H:%M") + local_sunset = self.suntimes.setlocal(datetime.now()).strftime("%d.%m.%YT%H:%M") + local_zone = self.suntimes.setlocal(datetime.now()).tzinfo.key mqtt_client.info2mqtt({'topic' : f'{dtu_name}/{dtu_ser}'}, \ {'dis_night_comm' : 'True', \ 'local_sunrise' : local_sunrise, \ @@ -235,14 +236,14 @@ def poll_inverter(inverter, dtu_ser, do_init, retries): if isinstance(result, hoymiles.decoders.StatusResponse): data = result.__dict__() - if 'event_count' in data: + if data is not None and 'event_count' in data: if event_message_index[inv_str] < data['event_count']: event_message_index[inv_str] = data['event_count'] command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.AlarmData, alarm_id=event_message_index[inv_str])) if mqtt_client: mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None)) - + if influx_client: influx_client.store_status(result) @@ -409,7 +410,7 @@ if __name__ == '__main__': str(g_inverter_ser), g_inverter.get('mqtt', {}).get('topic', f'hoymiles/{g_inverter_ser}') + '/command' ) - mqtt_client.subscribe(topic_item[1]) + mqtt_client.client.subscribe(topic_item[1]) mqtt_command_topic_subs.append(topic_item) # start main-loop diff --git a/tools/rpi/hoymiles/outputs.py b/tools/rpi/hoymiles/outputs.py index 11971a85..aa574f2d 100644 --- a/tools/rpi/hoymiles/outputs.py +++ b/tools/rpi/hoymiles/outputs.py @@ -227,6 +227,11 @@ class MqttOutputPlugin(OutputPluginFactory): """ data = response.__dict__() + + if data is None: + logging.warn("received data object is empty") + return + topic = params.get('topic', None) if not topic: topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}' @@ -243,31 +248,33 @@ class MqttOutputPlugin(OutputPluginFactory): # AC Data phase_id = 0 phase_sum_power = 0 - for phase in data['phases']: - self.client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter/{phase_id}/current', phase['current'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter/{phase_id}/power', phase['power'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter/{phase_id}/frequency', phase['frequency'], self.qos, self.ret) - phase_id = phase_id + 1 - phase_sum_power += phase['power'] + if data['phases'] is not None: + for phase in data['phases']: + self.client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter/{phase_id}/current', phase['current'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter/{phase_id}/power', phase['power'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter/{phase_id}/frequency', phase['frequency'], self.qos, self.ret) + phase_id = phase_id + 1 + phase_sum_power += phase['power'] # DC Data string_id = 0 string_sum_power = 0 - for string in data['strings']: - if 'name' in string: - string_name = string['name'].replace(" ","_") - else: - string_name = string_id - self.client.publish(f'{topic}/emeter-dc/{string_name}/voltage', string['voltage'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_name}/current', string['current'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_name}/power', string['power'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldDay', string['energy_daily'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldTotal', string['energy_total']/1000, self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_name}/Irradiation', string['irradiation'], self.qos, self.ret) - string_id = string_id + 1 - string_sum_power += string['power'] + if data['strings'] is not None: + for string in data['strings']: + if 'name' in string: + string_name = string['name'].replace(" ","_") + else: + string_name = string_id + self.client.publish(f'{topic}/emeter-dc/{string_name}/voltage', string['voltage'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/current', string['current'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/power', string['power'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldDay', string['energy_daily'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldTotal', string['energy_total']/1000, self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/Irradiation', string['irradiation'], self.qos, self.ret) + string_id = string_id + 1 + string_sum_power += string['power'] # Global if data['event_count'] is not None: @@ -279,19 +286,23 @@ class MqttOutputPlugin(OutputPluginFactory): self.client.publish(f'{topic}/YieldTotal', data['yield_total']/1000, self.qos, self.ret) if data['yield_today'] is not None: self.client.publish(f'{topic}/YieldToday', data['yield_today']/1000, self.qos, self.ret) - self.client.publish(f'{topic}/Efficiency', data['efficiency'], self.qos, self.ret) + if data['efficiency'] is not None: + self.client.publish(f'{topic}/Efficiency', data['efficiency'], self.qos, self.ret) elif isinstance(response, HardwareInfoResponse): - self.client.publish(f'{topic}/Firmware/Version',\ - f'{data["FW_ver_maj"]}.{data["FW_ver_min"]}.{data["FW_ver_pat"]}', self.qos, self.ret) - - self.client.publish(f'{topic}/Firmware/Build_at',\ - f'{data["FW_build_dd"]}/{data["FW_build_mm"]}/{data["FW_build_yy"]}T{data["FW_build_HH"]}:{data["FW_build_MM"]}',\ - self.qos, self.ret) - - self.client.publish(f'{topic}/Firmware/HWPartId',\ - f'{data["FW_HW_ID"]}', self.qos, self.ret) + if data["FW_ver_maj"] is not None and data["FW_ver_min"] is not None and data["FW_ver_pat"] is not None: + self.client.publish(f'{topic}/Firmware/Version',\ + f'{data["FW_ver_maj"]}.{data["FW_ver_min"]}.{data["FW_ver_pat"]}', self.qos, self.ret) + + if data["FW_build_dd"] is not None and data["FW_build_mm"] is not None and data["FW_build_yy"] is not None and data["FW_build_HH"] is not None and data["FW_build_MM"] is not None: + self.client.publish(f'{topic}/Firmware/Build_at',\ + f'{data["FW_build_dd"]}/{data["FW_build_mm"]}/{data["FW_build_yy"]}T{data["FW_build_HH"]}:{data["FW_build_MM"]}',\ + self.qos, self.ret) + + if data["FW_HW_ID"] is not None: + self.client.publish(f'{topic}/Firmware/HWPartId',\ + f'{data["FW_HW_ID"]}', self.qos, self.ret) else: raise ValueError('Data needs to be instance of StatusResponse or a instance of HardwareInfoResponse')