Initial commit
This commit is contained in:
7 files changed
+247
No files matched your search
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
FROM python:3
|
||||
|
||||
RUN mkdir /app && cd /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt /app
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY solaredge_modbus_mqtt.py /app
|
||||
COPY solaredge_modbus_mqtt /app/solaredge_modbus_mqtt
|
||||
|
||||
#ENV MQTT_HOST
|
||||
#ENV MQTT_USER
|
||||
#ENV MQTT_PASS
|
||||
#ENV MQTT_PORT
|
||||
|
||||
CMD ./solaredge_modbus_mqtt.py
|
||||
@@ -0,0 +1,7 @@
|
||||
### Installation
|
||||
|
||||
The easiest way is to run the docker container and set it to restart always. This basically makes it a system service that will always run.
|
||||
|
||||
```shell
|
||||
docker run --restart=always -e MODBUS_HOST=192.168.1.1 solaredge_modbus_mqtt:latest
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
paho-mqtt=1.5.0
|
||||
solaredge_modbus=0.2.0
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import solaredge_modbus
|
||||
import sys
|
||||
import time
|
||||
import paho.mqtt.client as mqtt
|
||||
from solaredge_modbus_mqtt import SolarData
|
||||
from pymodbus.exceptions import ConnectionException
|
||||
|
||||
if __name__ == "__main__":
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument("--modbus-host", type=str, default=os.environ.get('MODBUS_HOST', '127.0.0.1'), help="ModbusTCP address")
|
||||
argparser.add_argument("--modbus-port", type=int, default=os.environ.get('MODBUS_PORT', 1502), help="ModbusTCP port")
|
||||
argparser.add_argument("--modbus-timeout", type=int, default=os.environ.get('MODBUS_TIMEOUT', 1), help="Connection timeout")
|
||||
argparser.add_argument("--modbus-unit", type=int, default=os.environ.get('MODBUS_UNIT', 1), help="Modbus unit")
|
||||
|
||||
argparser.add_argument("--mqtt-host", type=str, default=os.environ.get('MQTT_HOST', '127.0.0.1'), help="MQTT Host")
|
||||
argparser.add_argument("--mqtt-port", type=int, default=os.environ.get('MQTT_PORT', 1883), help="MQTT port")
|
||||
argparser.add_argument("--mqtt-user", type=str, default=os.environ.get('MQTT_USER', None), help="MQTT Username")
|
||||
argparser.add_argument("--mqtt-pass", type=str, default=os.environ.get('MQTT_PASS', None), help="MQTT Password")
|
||||
|
||||
argparser.add_argument("--poll-interval", type=int, default=os.environ.get('POLL_INTERVAL', 3), help="Waiting time between each poll")
|
||||
|
||||
args = argparser.parse_args()
|
||||
|
||||
try:
|
||||
mqtt = mqtt.Client(client_id="", clean_session=True, userdata=None, protocol=mqtt.MQTTv311, transport="tcp")
|
||||
mqtt.username_pw_set(args.mqtt_user, args.mqtt_pass)
|
||||
mqtt.connect(args.mqtt_host, args.mqtt_port, 60)
|
||||
except OSError as err:
|
||||
print("Cannot connect to MQTT on {0}:{1}, error: {2}".format(args.mqtt_host, args.mqtt_port, err))
|
||||
sys.exit(1)
|
||||
|
||||
inverter = solaredge_modbus.Inverter(
|
||||
host=args.modbus_host,
|
||||
port=args.modbus_port,
|
||||
timeout=args.modbus_timeout,
|
||||
unit=args.modbus_unit
|
||||
)
|
||||
|
||||
run = True
|
||||
publishedAutoDiscovery = False
|
||||
while run:
|
||||
try:
|
||||
solar_data = SolarData(inverter.read_all())
|
||||
except ConnectionException as err:
|
||||
print("Cannot connect to SolarEdge Modbus server on {0}:{1}, error: {2}".format(args.modbus_host, args.modbus_port, err))
|
||||
sys.exit(2)
|
||||
|
||||
mqtt.loop()
|
||||
|
||||
if (publishedAutoDiscovery == False):
|
||||
for k, v in solar_data.data.items():
|
||||
message = {
|
||||
"name": "SolarEdge " + k.replace("_", " ").title(),
|
||||
"unique_id": "solaredge_" + k,
|
||||
"unit_of_measurement": SolarData.fields[k]['unit'] if 'unit' in SolarData.fields[k] else '',
|
||||
"state_topic": "home/hal_beneden/sensor/solaredge_inverter/" + k,
|
||||
"force_update": "True",
|
||||
"device": {
|
||||
"identifiers": ["solaredge_inverter"],
|
||||
"name": "SolarEdge Inverter"
|
||||
}
|
||||
}
|
||||
mqtt.publish(
|
||||
'test/homeassistant/sensor/solaredge_inverter/' + str(k) + '/config',
|
||||
json.dumps(message),
|
||||
qos=0,
|
||||
retain=True
|
||||
)
|
||||
|
||||
publishedAutoDiscovery = True
|
||||
|
||||
|
||||
for k, v in solar_data.data.items():
|
||||
mqtt.publish('test/home/hal_beneden/sensor/solaredge_inverter/' + k, payload=str(v), qos=0, retain=False)
|
||||
|
||||
time.sleep(args.poll_interval)
|
||||
@@ -0,0 +1,57 @@
|
||||
import math
|
||||
|
||||
class SolarData:
|
||||
|
||||
fields = {
|
||||
'model': {'field': 'c_model'},
|
||||
'version': {'field': 'c_version'},
|
||||
'serialnumber': {'field': 'c_serialnumber'},
|
||||
'deviceaddress': {'field': 'c_deviceaddress'},
|
||||
'sunspec_did': {'field': 'c_sunspec_did'},
|
||||
|
||||
'status': {},
|
||||
'vendor_status': {},
|
||||
|
||||
'current': {'scaleField': 'current_scale', 'unit': 'A'},
|
||||
'p1_current': {'scaleField': 'current_scale', 'unit': 'A'},
|
||||
'p2_current': {'scaleField': 'current_scale', 'unit': 'A'},
|
||||
'p3_current': {'scaleField': 'current_scale', 'unit': 'A'},
|
||||
|
||||
'p1_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
'p2_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
'p3_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
'p1n_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
'p2n_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
'p3n_voltage': {'scaleField': 'voltage_scale', 'unit': 'V'},
|
||||
|
||||
'power_ac': {'scaleField': 'power_ac_scale', 'unit': 'W'},
|
||||
'frequency': {'scaleField': 'frequency_scale', 'unit': 'Hz'},
|
||||
'power_apparent': {'scaleField': 'power_apparent_scale', 'unit': 'W'},
|
||||
'power_reactive': {'scaleField': 'power_reactive_scale', 'unit': 'W'},
|
||||
'power_factor': {'scaleField': 'power_factor_scale', 'unit': '%'},
|
||||
'energy_total': {'scaleField': 'energy_total_scale', 'unit': 'Wh'},
|
||||
'current_dc': {'scaleField': 'current_dc_scale', 'unit': 'A'},
|
||||
'voltage_dc': {'scaleField': 'voltage_dc_scale', 'unit': 'V'},
|
||||
'power_dc': {'scaleField': 'power_dc_scale', 'unit': 'W'},
|
||||
'temperature': {'scaleField': 'temperature_scale', 'unit': '°C'},
|
||||
}
|
||||
|
||||
def __init__(self, data):
|
||||
self.rawData = data
|
||||
self.data = self._parseData(data)
|
||||
|
||||
def _parseData(self, data):
|
||||
result = {}
|
||||
|
||||
for fieldName, config in self.fields.items():
|
||||
value = data[config['field']] if 'field' in config else data[fieldName]
|
||||
|
||||
if 'scaleField' in config:
|
||||
value = value * math.pow(10, data[config['scaleField']])
|
||||
|
||||
result[fieldName] = value
|
||||
|
||||
return result
|
||||
|
||||
def getModel(self):
|
||||
return self.data['model']
|
||||
@@ -0,0 +1,81 @@
|
||||
import unittest
|
||||
from . import SolarData
|
||||
|
||||
class TestStringMethods(unittest.TestCase):
|
||||
def testParse(self):
|
||||
solarData = SolarData({
|
||||
'c_model': 'SE7K-ABCDEFGH4',
|
||||
'c_version': '0004.0008.0028',
|
||||
'c_serialnumber': '7F51B342',
|
||||
'c_deviceaddress': 1,
|
||||
'c_sunspec_did': 103,
|
||||
'current': 301,
|
||||
'p1_current': 99,
|
||||
'p2_current': 102,
|
||||
'p3_current': 100,
|
||||
'current_scale': -2,
|
||||
'p1_voltage': 4056,
|
||||
'p2_voltage': 4066,
|
||||
'p3_voltage': 4075,
|
||||
'p1n_voltage': 2346,
|
||||
'p2n_voltage': 2342,
|
||||
'p3n_voltage': 2351,
|
||||
'voltage_scale': -1,
|
||||
'power_ac': 5970,
|
||||
'power_ac_scale': -1,
|
||||
'frequency': 4997,
|
||||
'frequency_scale': -2,
|
||||
'power_apparent': 7133,
|
||||
'power_apparent_scale': -1,
|
||||
'power_reactive': -3890,
|
||||
'power_reactive_scale': -1,
|
||||
'power_factor': -8376,
|
||||
'power_factor_scale': -2,
|
||||
'energy_total': 788428,
|
||||
'energy_total_scale': 0,
|
||||
'current_dc': 8126,
|
||||
'current_dc_scale': -4,
|
||||
'voltage_dc': 7471,
|
||||
'voltage_dc_scale': -1,
|
||||
'power_dc': 6071,
|
||||
'power_dc_scale': -1,
|
||||
'temperature': 4717,
|
||||
'temperature_scale': -2,
|
||||
'status': 4,
|
||||
'vendor_status': 0
|
||||
})
|
||||
|
||||
self.assertEqual('SE7K-ABCDEFGH4', solarData.data['model'])
|
||||
self.assertEqual('0004.0008.0028', solarData.data['version'])
|
||||
self.assertEqual('7F51B342', solarData.data['serialnumber'])
|
||||
self.assertEqual(1, solarData.data['deviceaddress'])
|
||||
self.assertEqual(103, solarData.data['sunspec_did'])
|
||||
|
||||
self.assertAlmostEqual(3.01, solarData.data['current'])
|
||||
self.assertAlmostEqual(0.99, solarData.data['p1_current'])
|
||||
self.assertAlmostEqual(1.02, solarData.data['p2_current'])
|
||||
self.assertAlmostEqual(1.0, solarData.data['p3_current'])
|
||||
|
||||
self.assertAlmostEqual(405.6, solarData.data['p1_voltage'])
|
||||
self.assertAlmostEqual(406.6, solarData.data['p2_voltage'])
|
||||
self.assertAlmostEqual(407.5, solarData.data['p3_voltage'])
|
||||
self.assertAlmostEqual(234.6, solarData.data['p1n_voltage'])
|
||||
self.assertAlmostEqual(234.2, solarData.data['p2n_voltage'])
|
||||
self.assertAlmostEqual(235.1, solarData.data['p3n_voltage'])
|
||||
|
||||
self.assertAlmostEqual(597.0, solarData.data['power_ac'])
|
||||
self.assertAlmostEqual(49.97, solarData.data['frequency'])
|
||||
self.assertAlmostEqual(713.3, solarData.data['power_apparent'])
|
||||
self.assertAlmostEqual(-389.0, solarData.data['power_reactive'])
|
||||
self.assertAlmostEqual(-83.76, solarData.data['power_factor'])
|
||||
self.assertAlmostEqual(788428, solarData.data['energy_total'])
|
||||
|
||||
self.assertAlmostEqual(0.8126, solarData.data['current_dc'])
|
||||
self.assertAlmostEqual(747.1, solarData.data['voltage_dc'])
|
||||
self.assertAlmostEqual(607.1, solarData.data['power_dc'])
|
||||
self.assertAlmostEqual(47.17, solarData.data['temperature'])
|
||||
self.assertAlmostEqual(4, solarData.data['status'])
|
||||
self.assertAlmostEqual(0, solarData.data['vendor_status'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user