Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mqtt output plugins for SBFspot and ahoyDTU #121

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,4 @@ dmypy.json

# Pyre type checker
.pyre/
/.vs
168 changes: 168 additions & 0 deletions plugins/deye_plugin_ahoyDTU_mqtt_publisher.py
Original file line number Diff line number Diff line change
@@ -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]
129 changes: 129 additions & 0 deletions plugins/deye_plugin_sbfspot_mqtt_publisher.py
Original file line number Diff line number Diff line change
@@ -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]