diff --git a/.gitignore b/.gitignore index 79a8a1d..f21e608 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dmypy.json # Pyre type checker .pyre/ +/.vs diff --git a/plugins/deye_plugin_ahoyDTU_mqtt_publisher.py b/plugins/deye_plugin_ahoyDTU_mqtt_publisher.py new file mode 100644 index 0000000..dad8912 --- /dev/null +++ b/plugins/deye_plugin_ahoyDTU_mqtt_publisher.py @@ -0,0 +1,168 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import logging +import json +from datetime import datetime + +from deye_plugin_loader import DeyePluginContext +from deye_events import DeyeEvent, DeyeEventProcessor, DeyeEvent, DeyeObservationEvent, DeyeLoggerStatusEvent +from deye_config import DeyeConfig +from deye_observation import Observation +from deye_mqtt import DeyeMqttClient + + +class ahoyDTUMQTTPublisher(DeyeEventProcessor): + """ + Publishes events to MQTT in ahoyDTU format + """ + + __names={ + ### CONVERT 2.5 vs 2500 - convert x1000 + "day_energy": "ch0/YieldDay", + "total_energy": "ch0/YieldTotal", + ### CONVERT 0.0 vs 249870 - ERROR Deye + # "uptime": "uptime", + + "ac/l1/voltage": "ch0/U_AC", + "ac/l1/current": "ch0/I_AC", + "ac/l1/power": "ch0/P_AC", + "ac/freq": "ch0/F_AC", + + "dc/pv1/voltage": "ch1/U_DC", + "dc/pv1/current": "ch1/I_DC", + "dc/pv1/power": "ch1/P_DC", + ### CONVERT 2.5 vs 2500 - convert x1000 + "dc/pv1/day_energy": "ch1/YieldDay", + "dc/pv1/total_energy": "ch1/YieldTotal", + + "dc/pv2/voltage": "ch2/U_DC", + "dc/pv2/current": "ch2/I_DC", + "dc/pv2/power": "ch2/P_DC", + ### CONVERT 2.5 vs 2500 - convert x1000 + "dc/pv2/day_energy": "ch2/YieldDay", + "dc/pv2/total_energy": "ch2/YieldTotal", + + "dc/pv3/voltage": "ch3/U_DC", + "dc/pv3/current": "ch3/I_DC", + "dc/pv3/power": "ch3/P_DC", + ### CONVERT 2.5 vs 2500 - convert x1000 + "dc/pv3/day_energy": "ch3/YieldDay", + "dc/pv3/total_energy": "ch3/YieldTotal", + + "dc/pv4/voltage": "ch4/U_DC", + "dc/pv4/current": "ch4/I_DC", + "dc/pv4/power": "ch4/P_DC", + ### CONVERT 2.5 vs 2500 - convert x1000 + "dc/pv4/day_energy": "ch4/YieldDay", + "dc/pv4/total_energy": "ch4/YieldTotal", + + "dc/total_power": "ch0/P_DC", + "radiator_temp": "ch0/Temp" + # ch0/Efficiency 95.509 --> now being calculated + + ### Deye parameters not provided by ahoyDTU + # "operating_power 0.0" + # "ac/active_power" + # "settings/active_power_regulation 10.0" + # "logger_status online" + ### ahoyDTU parameters not provided by Deye + # ch0/Q_AC 0 + # ch0/PF_AC 1 + # ch0/ALARM_MES_ID 73 + # ch1/Irradiation 50.095 + # ch2/Irradiation 50.643 + # wifi_rssi + # free_heap + # heap_frag + + ## tbd + # total/P_AC + # total/YieldTotal + # total/YieldDay + # total/P_DC + } + + def __init__(self, config: DeyeConfig, mqtt_client: DeyeMqttClient): + """Initializes the plugin + + Args: + config (DeyeConfig): provides access to general config + mqtt_client (DeyeMqttClient): provides access to existing mqtt client (if configured) + """ + self.__log = logging.getLogger(ahoyDTUMQTTPublisher.__name__) + self.__config = config + + ###################################### + # change target mqtt topic here + self.__mqttTopicPrefix = f"solar/Deye-Sun600" + + if mqtt_client is not None: + self.__mqtt_client = mqtt_client + else: + self.__log.error("No mqtt client defined - enable it in config file") + + def get_id(self): + return "ahuyDTU_mqtt_publisher" + + + def process(self, events: list[DeyeEvent]): + power_dc_total = 0 + power_ac_total = 0 + + for event in events: + if isinstance(event, DeyeObservationEvent): + ahoyDTUmqttTopic = ahoyDTUMQTTPublisher.__names.get(event.observation.sensor.mqtt_topic_suffix) + # if(event.observation.sensor.mqtt_topic_suffix == "ac/active_power"): --> efficieny > 1 + if(event.observation.sensor.mqtt_topic_suffix == "ac/l1/power"): + power_ac_total = float(event.observation.value) + if(event.observation.sensor.mqtt_topic_suffix == "dc/total_power"): + power_dc_total = float(event.observation.value) + if ahoyDTUmqttTopic is not None: + inverterValue=float(event.observation.value) + # YieldDay needs a conversion. Deye: 2.5, ahoy: 2500 + if ahoyDTUmqttTopic in ["ch0/YieldDay", "ch1/YieldDay", "ch2/YieldDay", "ch3/YieldDay", "ch4/YieldDay"]: + inverterValue=float(inverterValue*1000) + + # this is still a hack to access existing mqtt client + fullMQTTTopic = f"{self.__mqttTopicPrefix}/{ahoyDTUmqttTopic}" + self.__mqtt_client._DeyeMqttClient__do_publish(mqtt_topic=fullMQTTTopic, + value=inverterValue) + else: + self.__log.warn("Unknown ahoyDTU parameter - name: {} mqtt topic: {} value: {}".format( + event.observation.sensor.name, + event.observation.sensor.mqtt_topic_suffix, + event.observation.value)) + elif isinstance(event, DeyeLoggerStatusEvent): + self.__log.info("InvStatus: {}".format("online" if event.online else "Offline")) + else: + self.__log.warn(f"Unsupported event type {event.__class__}") + + efficiency = (power_ac_total / power_dc_total) * 100 + fullMQTTTopic = f"{self.__mqttTopicPrefix}/ch0/Efficiency" + self.__mqtt_client._DeyeMqttClient__do_publish(mqtt_topic=fullMQTTTopic, + value=efficiency) + + +class DeyePlugin: + def __init__(self, plugin_context: DeyePluginContext): + self.publisher = ahoyDTUMQTTPublisher(config=plugin_context.config, + mqtt_client=plugin_context.mqtt_client) + + + def get_event_processors(self) -> [DeyeEventProcessor]: + return [self.publisher] diff --git a/plugins/deye_plugin_sbfspot_mqtt_publisher.py b/plugins/deye_plugin_sbfspot_mqtt_publisher.py new file mode 100644 index 0000000..3c89403 --- /dev/null +++ b/plugins/deye_plugin_sbfspot_mqtt_publisher.py @@ -0,0 +1,129 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import logging +import json +from datetime import datetime + +from deye_plugin_loader import DeyePluginContext +from deye_events import DeyeEvent, DeyeEventProcessor, DeyeEvent, DeyeObservationEvent, DeyeLoggerStatusEvent +from deye_config import DeyeConfig +from deye_observation import Observation +from deye_mqtt import DeyeMqttClient + + +class DeyeSbfSpotMQTTPublisher(DeyeEventProcessor): + """ + Publishes events to MQTT in sbfSpot json format + """ + + __names={ + "day_energy": "EToday", + "total_energy": "ETotal", + "ac/l1/voltage": "UAC1", + "ac/l1/current": "IAC1", + "ac/l1/power": "PAC1", + "ac/freq": "GridFreq", + "dc/pv1/voltage": "UDC1", + "dc/pv1/current": "IDC1", + "dc/pv1/power": "PDC1", + "dc/pv2/voltage": "UDC2", + "dc/pv2/current": "IDC2", + "dc/pv2/power": "PDC2", + "dc/pv3/voltage": "UDC3", + "dc/pv3/current": "IDC3", + "dc/pv3/power": "PDC3", + "dc/pv4/voltage": "UDC4", + "dc/pv4/current": "IDC4", + "dc/pv4/power": "PDC4", + "dc/total_power": "PDCTot", + "radiator_temp": "InvTemperature", + } + + def __init__(self, config: DeyeConfig, mqtt_client: DeyeMqttClient): + """Initializes the plugin + + Args: + config (DeyeConfig): provides access to general config + mqtt_client (DeyeMqttClient): provides access to existing mqtt client (if configured) + """ + self.__log = logging.getLogger(DeyeSbfSpotMQTTPublisher.__name__) + self.__config = config + + # change target mqtt topic here + self.__sbfspot_mqtt_topic = f"{config.mqtt.topic_prefix}/sbfspot/report/Deye-SUN600" + + if mqtt_client is not None: + self.__mqtt_client = mqtt_client + else: + self.__log.error("No mqtt client defined - enable it in config file") + + + def get_id(self): + return "sbfSpot_mqtt_publisher" + + + def process(self, events: list[DeyeEvent]): + now = datetime.now() + + data = { + "InvClass" : "Deye", + "InvType": "SUN600", + "InvSerial": self.__config.logger.serial_number, + "Timestamp": str(now.strftime("%m/%d/%Y, %H:%M:%S")), + } + # + # Todo: format floats "{:.2f}".format(3.1415926) + + for event in events: + if isinstance(event, DeyeObservationEvent): + data.update(self.__handle_observation(event.observation)) + elif isinstance(event, DeyeLoggerStatusEvent): + data.update({"InvStatus": "Online" if event.online else "Offline"}) + else: + self.__log.warn(f"Unsupported event type {event.__class__}") + + self.__log.debug(json.dumps(data)) + # this is still a hack to access existing mqtt client + self.__mqtt_client._DeyeMqttClient__do_publish(mqtt_topic=self.__sbfspot_mqtt_topic, + value=json.dumps(data)) + + + def __handle_observation(self, observation: Observation) -> dict[str, str | float | int]: + + data = {} + + name = DeyeSbfSpotMQTTPublisher.__names.get(observation.sensor.mqtt_topic_suffix) + if name is not None: + data[name]=observation.value + else: + self.__log.warn("Unknown sbfSpot parameter: name {} mqtt topic {}".format( + observation.sensor.name, + observation.sensor.mqtt_topic_suffix)) + data["W-{}".format(observation.sensor.mqtt_topic_suffix)]=observation.value + + return data + + +class DeyePlugin: + def __init__(self, plugin_context: DeyePluginContext): + self.publisher = DeyeSbfSpotMQTTPublisher(config=plugin_context.config, + mqtt_client=plugin_context.mqtt_client) + + + def get_event_processors(self) -> [DeyeEventProcessor]: + return [self.publisher]