some changes, meter reading works so far
This commit is contained in:
parent
f30c8e7eb3
commit
3f0c80fd42
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
__pycache__
|
14
config.ini
Normal file
14
config.ini
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[mqtt]
|
||||||
|
broker = 172.16.2.16
|
||||||
|
port = 1883
|
||||||
|
# login =
|
||||||
|
# password =
|
||||||
|
# ca =
|
||||||
|
# cert =
|
||||||
|
# key =
|
||||||
|
subscribeTopic = test1
|
||||||
|
meterPublishTopic = IoT/PV/Meter
|
||||||
|
meterPublishPeriod = 15
|
||||||
|
|
||||||
|
[modbus]
|
||||||
|
gateway = 172.16.2.42
|
@ -1,26 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["setuptools>=61.0"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "pv-controller"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = [
|
|
||||||
{ name="Wolfgang Hottgenroth", email="wolfgang.hottgenroth@icloud.com" },
|
|
||||||
]
|
|
||||||
description = "Special tool to control my PV installation"
|
|
||||||
readme = "README.md"
|
|
||||||
license = { file="LICENSE" }
|
|
||||||
requires-python = ">=3.10"
|
|
||||||
classifiers = [
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
]
|
|
||||||
dependencies = [
|
|
||||||
"loguru>=0.6.0"
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
"Homepage" = "https://home.hottis.de/gitlab/wolutator/pv-controller"
|
|
||||||
"Bug Tracker" = "https://home.hottis.de/gitlab/wolutator/pv-controller/-/issues"
|
|
48
src/pv_controller/MeterPublish.py
Normal file
48
src/pv_controller/MeterPublish.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from threading import Event
|
||||||
|
from loguru import logger
|
||||||
|
from MqttBase import AbstractMqttPublisher
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
REGISTERS = [
|
||||||
|
{ "addr":0x0048, "attr": "importEnergy", "name":"Import active energy", "unit":"kWh" },
|
||||||
|
{ "addr":0x004a, "attr": "exportEnergy", "name":"Export active energy", "unit":"kWh" },
|
||||||
|
{ "addr":0x000c, "attr": "activePower", "name":"Active power", "unit":"W" },
|
||||||
|
{ "addr":0x0058, "attr": "positivePower", "name":"Positive power", "unit":"W" },
|
||||||
|
{ "addr":0x005c, "attr": "reversePower", "name":"Reverse power", "unit":"W" },
|
||||||
|
{ "addr":0x0000, "attr": "voltage", "name":"Voltage", "unit":"V" },
|
||||||
|
{ "addr":0x0006, "attr": "current", "name":"Current", "unit":"A" }
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MeterPublish(AbstractMqttPublisher):
|
||||||
|
def __init__(self, config, modbusHandler):
|
||||||
|
super().__init__(config)
|
||||||
|
self.modbusHandler = modbusHandler
|
||||||
|
self.registers = REGISTERS
|
||||||
|
|
||||||
|
def localLoop(self):
|
||||||
|
cnt = 0
|
||||||
|
while not self.killBill:
|
||||||
|
cnt += 1
|
||||||
|
topic = self.config["meterPublishTopic"]
|
||||||
|
payload = str(cnt)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = { r['attr']: 0.0 for r in self.registers }
|
||||||
|
payload['status'] = "Error"
|
||||||
|
payload['timestamp'] = datetime.datetime.isoformat(datetime.datetime.utcnow())
|
||||||
|
for reg in self.registers:
|
||||||
|
v = self.modbusHandler.readInputRegister(reg['addr'])
|
||||||
|
logger.info(f"{reg['name']}: {v} {reg['unit']}")
|
||||||
|
payload[reg['attr']] = float(f"{v:0.2f}")
|
||||||
|
payload['status'] = "Ok"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Caught exception: {str(e)}")
|
||||||
|
|
||||||
|
payload['cnt'] = cnt
|
||||||
|
payloadStr = json.dumps(payload)
|
||||||
|
self.client.publish(topic, payloadStr)
|
||||||
|
logger.warning(f"mqtt message sent: {topic} -> {payloadStr}")
|
||||||
|
|
||||||
|
self.killEvent.wait(timeout=float(self.config["meterPublishPeriod"]))
|
93
src/pv_controller/ModbusBase.py
Normal file
93
src/pv_controller/ModbusBase.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from pymodbus.client import ModbusTcpClient
|
||||||
|
from pymodbus.exceptions import ModbusIOException
|
||||||
|
from pymodbus.register_read_message import ReadHoldingRegistersResponse, ReadInputRegistersResponse
|
||||||
|
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder
|
||||||
|
from pymodbus.constants import Endian
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
|
class LocalModbusException(Exception):
|
||||||
|
def __init__(self, msg='', cause=None):
|
||||||
|
super().__init__(self)
|
||||||
|
self.msg = msg
|
||||||
|
self.cause = cause
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"LocalModbusException: Msg:{self.msg}, Cause:{self.cause}"
|
||||||
|
|
||||||
|
class ModbusHandler:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config['modbus']
|
||||||
|
self.client = ModbusTcpClient(self.config['gateway'])
|
||||||
|
|
||||||
|
def readInputRegister(self, addr):
|
||||||
|
self.client.connect()
|
||||||
|
try:
|
||||||
|
res = self.client.read_input_registers(addr, 2, slave=2)
|
||||||
|
if (isinstance(res, ReadInputRegistersResponse)):
|
||||||
|
v = BinaryPayloadDecoder.fromRegisters(res.registers, byteorder=Endian.Big, wordorder=Endian.Big).decode_32bit_float()
|
||||||
|
return v
|
||||||
|
elif (isinstance(res, LocalModbusException)):
|
||||||
|
msg = f"Error: {type(res)}, Content: {res}"
|
||||||
|
logger.warning(msg)
|
||||||
|
raise LocalModbusException(msg=msg, cause=res)
|
||||||
|
else:
|
||||||
|
msg = f"Unknown type: {type(res)}, Content: {res}"
|
||||||
|
logger.warning(msg)
|
||||||
|
raise LocalModbusException(msg=msg)
|
||||||
|
finally:
|
||||||
|
self.client.close()
|
||||||
|
|
||||||
|
def writeCoil(self, addr, value):
|
||||||
|
self.client.connect()
|
||||||
|
try:
|
||||||
|
res = self.client.write_coil(addr, value, slave=1)
|
||||||
|
logger.debug(f"write coil result {res}")
|
||||||
|
return value
|
||||||
|
finally:
|
||||||
|
self.client.close()
|
||||||
|
|
||||||
|
|
||||||
|
#client = ModbusTcpClient("172.16.2.42")
|
||||||
|
#registers = [
|
||||||
|
# { "addr":0x0046, "name":"Frequency", "unit":"Hz" },
|
||||||
|
# { "addr":0x0048, "name":"Import active energy", "unit":"kWh" },
|
||||||
|
# { "addr":0x004a, "name":"Export active energy", "unit":"kWh" },
|
||||||
|
# { "addr":0x004c, "name":"Import reactive energy", "unit":"kvarh" },
|
||||||
|
# { "addr":0x004e, "name":"Export reactive energy", "unit":"kvarh" },
|
||||||
|
# { "addr":0x000c, "name":"Active power", "unit":"W" },
|
||||||
|
# { "addr":0x0058, "name":"Positive power", "unit":"W" },
|
||||||
|
# { "addr":0x005c, "name":"Reverse power", "unit":"W" },
|
||||||
|
# { "addr":0x0000, "name":"Voltage", "unit":"V" },
|
||||||
|
# { "addr":0x0006, "name":"Current", "unit":"A" }
|
||||||
|
#]
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#cnt = 0
|
||||||
|
#coilState = True
|
||||||
|
#per = 2
|
||||||
|
#while True:
|
||||||
|
# try:
|
||||||
|
# for reg in registers:
|
||||||
|
# v = readInputRegister(client, reg['addr'])
|
||||||
|
# logger.info(f"{reg['name']}: {v} {reg['unit']}")
|
||||||
|
#
|
||||||
|
# cnt += 1
|
||||||
|
# if ((cnt == per) == 0):
|
||||||
|
# cnt = 0
|
||||||
|
# coilState = not coilState
|
||||||
|
# per = 2 if coilState else 10
|
||||||
|
# logger.info(f"Write {coilState} to coil")
|
||||||
|
# writeCoil(client, 0, coilState)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# sleep(2)
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Caught exception: {str(e)}")
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
from threading import Event
|
|
||||||
from loguru import logger
|
|
||||||
from MqttBase import AbstractMqttPublisher
|
|
||||||
|
|
||||||
|
|
||||||
class TestPublish(AbstractMqttPublisher):
|
|
||||||
def __init__(self, config):
|
|
||||||
super().__init__(config)
|
|
||||||
|
|
||||||
def localLoop(self):
|
|
||||||
cnt = 0
|
|
||||||
while not self.killBill:
|
|
||||||
cnt += 1
|
|
||||||
topic = self.config["publishTopic"]
|
|
||||||
payload = str(cnt)
|
|
||||||
self.client.publish(topic, payload)
|
|
||||||
logger.warning("mqtt message sent: {} -> {}".format(topic, payload))
|
|
||||||
|
|
||||||
self.killEvent.wait(timeout=float(self.config["publishPeriod"]))
|
|
@ -1,56 +0,0 @@
|
|||||||
from TestPublish import TestPublish
|
|
||||||
from TestSubscribe import TestSubscribe
|
|
||||||
from loguru import logger
|
|
||||||
import argparse
|
|
||||||
import configparser
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
deathBell = threading.Event()
|
|
||||||
|
|
||||||
def exceptHook(args):
|
|
||||||
global deathBell
|
|
||||||
logger.error("Exception in thread caught: {}".format(args))
|
|
||||||
deathBell.set()
|
|
||||||
logger.error("rang the death bell")
|
|
||||||
|
|
||||||
|
|
||||||
logger.info("example1 starting")
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="example1")
|
|
||||||
parser.add_argument('--config', '-f',
|
|
||||||
help='Config file, default is $pwd/config.ini',
|
|
||||||
required=False,
|
|
||||||
default='./config.ini')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(args.config)
|
|
||||||
|
|
||||||
testSubscribeThread = TestSubscribe(config)
|
|
||||||
testSubscribeThread.start()
|
|
||||||
logger.info("testSubscribe started")
|
|
||||||
|
|
||||||
testPublishThread = TestPublish(config)
|
|
||||||
testPublishThread.start()
|
|
||||||
logger.info("testPublish started")
|
|
||||||
|
|
||||||
threading.excepthook = exceptHook
|
|
||||||
logger.info("Threading excepthook set")
|
|
||||||
|
|
||||||
logger.info("example1 is running")
|
|
||||||
|
|
||||||
|
|
||||||
deathBell.wait()
|
|
||||||
logger.error("example1 is dying")
|
|
||||||
|
|
||||||
testSubscribeThread.stop()
|
|
||||||
testPublishThread.stop()
|
|
||||||
|
|
||||||
testSubscribeThread.join()
|
|
||||||
logger.error("testSubscribe joined")
|
|
||||||
|
|
||||||
testPublishThread.join()
|
|
||||||
logger.error("testPublish joined")
|
|
||||||
|
|
||||||
logger.error("example1 is terminated")
|
|
64
src/pv_controller/pvc.py
Normal file
64
src/pv_controller/pvc.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from MeterPublish import MeterPublish
|
||||||
|
# from TestSubscribe import TestSubscribe
|
||||||
|
from ModbusBase import ModbusHandler
|
||||||
|
from loguru import logger
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
import configparser
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
l = logging.getLogger()
|
||||||
|
for h in l.handlers:
|
||||||
|
l.removeHandler(h)
|
||||||
|
|
||||||
|
deathBell = threading.Event()
|
||||||
|
|
||||||
|
def exceptHook(args):
|
||||||
|
global deathBell
|
||||||
|
logger.error("Exception in thread caught: {}".format(args))
|
||||||
|
deathBell.set()
|
||||||
|
logger.error("rang the death bell")
|
||||||
|
|
||||||
|
|
||||||
|
logger.info("pv controller starting")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="pv controller")
|
||||||
|
parser.add_argument('--config', '-f',
|
||||||
|
help='Config file, default is $pwd/config.ini',
|
||||||
|
required=False,
|
||||||
|
default='./config.ini')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(args.config)
|
||||||
|
|
||||||
|
# testSubscribeThread = TestSubscribe(config)
|
||||||
|
# testSubscribeThread.start()
|
||||||
|
# logger.info("testSubscribe started")
|
||||||
|
|
||||||
|
modbusHandler = ModbusHandler(config)
|
||||||
|
|
||||||
|
meterPublishThread = MeterPublish(config, modbusHandler)
|
||||||
|
meterPublishThread.start()
|
||||||
|
logger.info("meterPublishThread started")
|
||||||
|
|
||||||
|
threading.excepthook = exceptHook
|
||||||
|
logger.info("Threading excepthook set")
|
||||||
|
|
||||||
|
logger.info("pv controller is running")
|
||||||
|
|
||||||
|
|
||||||
|
deathBell.wait()
|
||||||
|
logger.error("pv controller is dying")
|
||||||
|
|
||||||
|
# testSubscribeThread.stop()
|
||||||
|
meterPublishThread.stop()
|
||||||
|
|
||||||
|
# testSubscribeThread.join()
|
||||||
|
# logger.error("testSubscribe joined")
|
||||||
|
|
||||||
|
meterPublishThread.join()
|
||||||
|
logger.error("meterPublishThread joined")
|
||||||
|
|
||||||
|
logger.error("pv controller is terminated")
|
Loading…
x
Reference in New Issue
Block a user