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

Added DSMR-Reader config #22

Open
wants to merge 4 commits into
base: master
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
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,56 @@ The `values()` function is called every `refresh_rate` seconds. It gets passed t

Single phase devices should put the single phase values in the generic _and_ first phase specific values, for example: `power_active` and `p1_power_active`, but also `voltage_ln` and `p1n_voltage`.

### DSMR Reader

if you are already using [DSMR reader](https://dsmr-reader.readthedocs.io/) you can export the data via mqtt.
in DSMR reader go to the configuration screen, MQTT section, (Data source) Telegram: JSON.
the minimum needed;

```
[mapping]
# READING FIELD = JSON FIELD
electricity_delivered_1 = electricityImportedT1
electricity_returned_1 = electricityExportedT1
electricity_delivered_2 = electricityImportedT2
electricity_returned_2 = electricityExportedT2
electricity_currently_delivered = powerImportedActual
electricity_currently_returned = powerExportedActual
phase_voltage_l1 = instantaneousVoltageL1
phase_voltage_l2 = instantaneousVoltageL2
phase_voltage_l3 = instantaneousVoltageL3
phase_power_current_l1 = instantaneousCurrentL1
phase_power_current_l2 = instantaneousCurrentL2
phase_power_current_l3 = instantaneousCurrentL3
phase_currently_delivered_l1 = instantaneousActivePowerL1Plus
phase_currently_delivered_l2 = instantaneousActivePowerL2Plus
phase_currently_delivered_l3 = instantaneousActivePowerL3Plus
phase_currently_returned_l1 = instantaneousActivePowerL1Min
phase_currently_returned_l2 = instantaneousActivePowerL2Min
phase_currently_returned_l3 = instantaneousActivePowerL3Min
```

this json message will be send, when activated, on the 'dsmr/json' topic, this topic can be adjusted.
i'm using a [waveshare usb to rs485/modbus module](https://www.waveshare.com/usb-to-rs485.htm)

config example;

```
[server]
device = /dev/ttyUSB0
baud = 9600
timeout = 0.1
log_level = INFO
meters = meter1

[meter1]
type=mqttP1Dsmr-reader
host=<IP of MQTT broker>
port=<Port of MQTT broker>
meterValuesTopic=dsmr/json
willTopic=dsmr/will
```

## Contributing

Contributions are more than welcome, especially new device scripts, or modifications which broaden the use case of this tool.
Contributions are more than welcome, especially new device scripts, or modifications which broaden the use case of this tool.
235 changes: 235 additions & 0 deletions devices/mqttP1Dsmr-reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""P1 meter device for solaredge_meterproxy

This file is indended to be used with https://github.com/nmakel/solaredge_meterproxy by nmakel
and is to be stored in the devices folder

It consumes MQTT messages with meterdata from the P1 meter created with https://github.com/marcelrv/p1-reader

"""

from __future__ import division
from collections import deque

import logging
import sys
import time
import json
import paho.mqtt.client as mqtt
from datetime import datetime

__author__ = ["Jacques Mulders"]
__version__ = "1.0"
__copyright__ = "Copyright 2022, Marcel Verpaalen"
__license__ = "GPL"
__credits__ = ["NMakel", "Marcel Verpaalen"]

class MovingAverage(object):
def __init__(self, size):
"""
Initialize your data structure here.
:type size: int
"""
self.queue = deque(maxlen=size)

def next(self, val):
"""
:type val: int
:rtype: float
"""
self.queue.append(val)
return sum(self.queue) / len(self.queue)


lastValues = {}
logger = logging.getLogger()

# 1 measurement/message every 5 sec -> 15min demand average
demandAvg = MovingAverage(180)
demandL1Avg = MovingAverage(180)
demandL2Avg = MovingAverage(180)
demandL3Avg = MovingAverage(180)

def on_connect(client, userdata, flags, rc):
logger.info(
f"MQTT connected to {userdata['host']}:{userdata['port']} - topic: '{userdata['meterValuesTopic']}' with result code {rc}.")
client.subscribe(userdata["meterValuesTopic"])
if userdata['willTopic'] is not None:
client.publish(userdata['willTopic'], "MeterProxy Connected " +
str(datetime.now().strftime("%d/%m/%Y %H:%M:%S")))


def on_message(client, userdata, message):
global lastValues
# logger.debug("Dump variable %s " % json.dumps( userdata, indent=4, sort_keys=True))
decoded_message = str(message.payload.decode("utf-8"))
lastValues = json.loads(decoded_message)
# print(lastValues['powerImportedActual'], type(lastValues['powerImportedActual']))
# print(float(lastValues['powerImportedActual']), type(float(lastValues['powerImportedActual'])))
# print(lastValues['powerExportedActual'], type(lastValues['powerExportedActual']))

# Calc net power average
lastValues ['demand_power_active'] = demandAvg.next ( (float(lastValues['powerImportedActual']) * 1000) - (float(lastValues['powerExportedActual']) * 1000) )
lastValues ['l1_demand_power_active'] = demandL1Avg.next( (float(lastValues['instantaneousActivePowerL1Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL1Min']) * 1000) )
lastValues ['l2_demand_power_active'] = demandL2Avg.next( (float(lastValues['instantaneousActivePowerL2Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL2Min']) * 1000) )
lastValues ['l3_demand_power_active'] = demandL3Avg.next( (float(lastValues['instantaneousActivePowerL3Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL3Min']) * 1000) )
logger.debug(F'Received message in {message.topic}, avg demand: {lastValues ["demand_power_active"]}')

def on_disconnect(client, userdata, rc):
if rc != 0:
logger.info(F"Unexpected MQTT disconnection, with result code {rc}.")


def device(config):

# Configuration parameters:
#
# host ip or hostname of MQTT server
# port port of MQTT server
# keepalive keepalive time in seconds for MQTT server
# meterValuesTopic MQTT topic to subscribe to to receive meter values

host = config.get("host", fallback="localhost")
port = config.getint("port", fallback=1883)
keepalive = config.getint("keepalive", fallback=60)
meterValuesTopic = config.get("meterValuesTopic", fallback="meter")
willTopic = config.get("willTopic", fallback=None)
willMsg = config.get("willMsg", fallback="MeterProxy Disconnected")

topics = {
"host": host,
"port": port,
"meterValuesTopic": meterValuesTopic,
"willTopic": willTopic
}

try:
client = mqtt.Client(userdata=topics)
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
if willTopic is not None:
client.will_set(willTopic, payload=willMsg, qos=0, retain=False)
client.connect(host, port, keepalive)
client.loop_start()
logger.debug(
f"Started MQTT connection to server - topic: {host}:{port} - {meterValuesTopic}")
except:
logger.critical(
f"MQTT connection failed: {host}:{port} - {meterValuesTopic}")

return {
"client": client,
"host": host,
"port": port,
"keepalive": keepalive,
"meterValuesTopic": meterValuesTopic,
"willTopic": willTopic,
"willMsg": willMsg
}


def values(device):
if not device:
return {}
global lastValues
submitValues = {}

submitValues['l1n_voltage'] = float(lastValues['instantaneousVoltageL1'])
submitValues['l2n_voltage'] = float(lastValues['instantaneousVoltageL2'])
submitValues['l3n_voltage'] = float(lastValues['instantaneousVoltageL3'])
submitValues['voltage_ln'] = float(lastValues['instantaneousVoltageL1'])
submitValues['frequency'] = 50

submitValues ['power_active'] = (float(lastValues['powerImportedActual']) * 1000) - (float(lastValues['powerExportedActual'])* 1000)
submitValues ['l1_power_active']= (float(lastValues['instantaneousActivePowerL1Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL1Min']) * 1000)
submitValues ['l2_power_active']= (float(lastValues['instantaneousActivePowerL2Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL2Min']) * 1000)
submitValues ['l3_power_active']= (float(lastValues['instantaneousActivePowerL3Plus']) * 1000) - (float(lastValues['instantaneousActivePowerL3Min']) * 1000)

P1Current=False
if (P1Current):
submitValues['l1_current'] = float(lastValues[ 'instantaneousCurrentL1'])
submitValues['l2_current'] = float(lastValues[ 'instantaneousCurrentL2'])
submitValues['l3_current'] = float(lastValues[ 'instantaneousCurrentL3'])
else:
#calculate current as P1 provided current is rounded to integers
submitValues['l1_current'] = abs ( submitValues ['l1_power_active'] ) / float(lastValues['instantaneousVoltageL1'])
submitValues['l2_current'] = abs ( submitValues ['l2_power_active'] ) / float(lastValues['instantaneousVoltageL2'])
submitValues['l3_current'] = abs ( submitValues ['l3_power_active'] ) / float(lastValues['instantaneousVoltageL3'])

submitValues ['demand_power_active'] = lastValues ['demand_power_active']
submitValues ['l1_demand_power_active'] = lastValues ['l1_demand_power_active']
submitValues ['l2_demand_power_active'] = lastValues ['l2_demand_power_active']
submitValues ['l3_demand_power_active'] = lastValues ['l3_demand_power_active']

submitEnergy=True
if (submitEnergy):
submitValues['import_energy_active'] = float(lastValues['electricityImportedT1']) + float(lastValues['electricityImportedT2'])
submitValues['export_energy_active'] = float(lastValues['electricityExportedT1']) + float(lastValues['electricityExportedT2'])
# submitValues ["l1_import_energy_active"] = lastValues['import_energy_active']
# submitValues ["l1_export_energy_active"] = lastValues["export_energy_active"]
submitValues['energy_active'] = submitValues['import_energy_active'] - submitValues['export_energy_active']
submitValues["_input"] = lastValues

logger.debug("Dump values %s " % json.dumps( submitValues, indent=4, sort_keys=True))

return submitValues

# MQTT input is a json with one or more of the below elements
# "energy_active"
# "import_energy_active"
# "power_active"
# "l1_power_active"
# "l2_power_active"
# "l3_power_active"
# "voltage_ln"
# "l1n_voltage"
# "l2n_voltage"
# "l3n_voltage"
# "voltage_ll"
# "l12_voltage"
# "l23_voltage"
# "l31_voltage"
# "frequency"
# "l1_energy_active"
# "l2_energy_active"
# "l3_energy_active"
# "l1_import_energy_active"
# "l2_import_energy_active"
# "l3_import_energy_active"
# "export_energy_active"
# "l1_export_energy_active"
# "l2_export_energy_active"
# "l3_export_energy_active"
# "energy_reactive"
# "l1_energy_reactive"
# "l2_energy_reactive"
# "l3_energy_reactive"
# "energy_apparent"
# "l1_energy_apparent"
# "l2_energy_apparent"
# "l3_energy_apparent"
# "power_factor"
# "l1_power_factor"
# "l2_power_factor"
# "l3_power_factor"
# "power_reactive"
# "l1_power_reactive"
# "l2_power_reactive"
# "l3_power_reactive"
# "power_apparent"
# "l1_power_apparent"
# "l2_power_apparent"
# "l3_power_apparent"
# "l1_current"
# "l2_current"
# "l3_current"
# "demand_power_active"
# "minimum_demand_power_active"
# "maximum_demand_power_active"
# "demand_power_apparent"
# "l1_demand_power_active"
# "l2_demand_power_active"
# "l3_demand_power_active"