From bc3d5e14e1715c7653b9b424929fc9c3fb49bb9c Mon Sep 17 00:00:00 2001 From: Sven Bunge Date: Sun, 4 Feb 2024 21:15:59 +0100 Subject: [PATCH] Write only if hs queue is empty Improved documentation Build with newser HS4 stack --- README.md | 9 ++-- config.xml | 4 +- doc/log14181.md | 4 +- src/14181_kostalKSEM_ModbusTCP.py | 72 ++++++++++++++++--------------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 9e16f95..40252ad 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Kostal KSEM ModbusTCP (14181) -Gira Homeserver 4 Logikmodule to poll power values from Kostal Smart Energy Meter (KSEM) via Modbus TCP. +# Kostal KSEM G1 / G2 ModbusTCP (14181) + +Gira Homeserver 4 Logic module to poll power values from Kostal Smart Energy Meter (KSEM) G1 / G2 via Modbus TCP. ## Developer Notes -Developed for the GIRA HomeServer 4.10 / 4.11! +Developed for the GIRA HomeServer 4.12. Could work with prior versions. Licensed under the LGPL to keep all copies & forks free! :exclamation: **If you fork this project and distribute the module by your own CHANGE the Logikbaustein-ID because 14181 is only for this one and registered to @SvenBunge !!** :exclamation: @@ -19,7 +20,7 @@ The latest version of the module is also available in the [KNX-User Forum Downlo ## Documentation -This module fetches power / grid information of the Kostal Smart Energy Meter (KSEM). Has been tested with KSEM Firmware 1.3.0. +This module fetches power / grid information of the Kostal Smart Energy Meter (KSEM). More [detailed documentation](doc/log14181.md) diff --git a/config.xml b/config.xml index f95f436..276a67e 100644 --- a/config.xml +++ b/config.xml @@ -1,7 +1,7 @@ - + Switch on (1) / off (0) Seconds of the interval to read power values (default: 5 secs) @@ -45,7 +45,7 @@ - hsl20_3_timer + hsl20_4_timer lib/pymodbus diff --git a/doc/log14181.md b/doc/log14181.md index fa90ff3..3099615 100644 --- a/doc/log14181.md +++ b/doc/log14181.md @@ -1,6 +1,6 @@ -# Kostal KSEM ModbusTCP v1.1 +# Kostal KSEM G1/G2 Homeserver Baustein -Dieser Logikbaustein liest regelmäßig Werte aus dem Smart Energy Meter des Herstellers *Kostal*, auch KSEM genannt, ein. +Dieser Logikbaustein für den Homeserver Firmware 4.12 liest regelmäßig Werte aus dem Smart Energy Meter des Herstellers *Kostal*, auch KSEM genannt, ein. ## Eingänge diff --git a/src/14181_kostalKSEM_ModbusTCP.py b/src/14181_kostalKSEM_ModbusTCP.py index 0f5ec40..b535d05 100644 --- a/src/14181_kostalKSEM_ModbusTCP.py +++ b/src/14181_kostalKSEM_ModbusTCP.py @@ -1,4 +1,4 @@ -# coding: UTF-8 +# coding: utf-8 import pymodbus # To not delete this module reference!! from pymodbus.constants import Endian @@ -12,12 +12,12 @@ ######################################################################################################## ##** Code created by generator - DO NOT CHANGE! **## -class KostalKSEM_ModbusTCP14181(hsl20_3.BaseModule): +class KostalKSEM_ModbusTCP14181(hsl20_4.BaseModule): def __init__(self, homeserver_context): - hsl20_3.BaseModule.__init__(self, homeserver_context, "kostalKsemModbusTCP14181") + hsl20_4.BaseModule.__init__(self, homeserver_context, "kostalKsemModbusTCP14181") self.FRAMEWORK = self._get_framework() - self.LOGGER = self._get_logger(hsl20_3.LOGGING_NONE,()) + self.LOGGER = self._get_logger(hsl20_4.LOGGING_NONE,()) self.PIN_I_SWITCH=1 self.PIN_I_FETCH_INTERVAL=2 self.PIN_I_KSEM_IP=3 @@ -50,7 +50,6 @@ def __init__(self, homeserver_context): self.PIN_O_COUNTER_REACTIVE_POWER_MINUS=27 self.PIN_O_COUNTER_APPARENT_POWER_PLUS=28 self.PIN_O_COUNTER_APPARENT_POWER_MINUS=29 - self.FRAMEWORK._run_in_context_thread(self.on_init) ######################################################################################################## #### Own written code can be placed after this commentblock . Do not change or delete commentblock! #### @@ -116,103 +115,107 @@ def on_interval(self): def read_sum_values(self, payload_dec): # Active Power Plus 0,1 / Minus 2,3 combined and transformed (uint) current_active_power = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_ACTIVE_POWER, current_active_power) + self.write_output(self.PIN_O_ACTIVE_POWER, current_active_power) # Reactive Power Plus 4,5 / Minus 6,7 combined and transformed (uint) current_reactive_power = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_REACTIVE_POWER, current_reactive_power) + self.write_output(self.PIN_O_REACTIVE_POWER, current_reactive_power) payload_dec.skip_bytes(16) # skip 8 registers # Apparent Power Plus 16,17 / Minus 18,19 combined and transformed (uint) current_apparent_power = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_APPARENT_POWER, current_apparent_power) + self.write_output(self.PIN_O_APPARENT_POWER, current_apparent_power) payload_dec.skip_bytes(8) # skip 4 registers # Power Factor 24,25 transformed (int!) - self._set_output_value(self.PIN_O_POWER_FACTOR, (payload_dec.decode_32bit_int() / 1000.0)) + self.write_output(self.PIN_O_POWER_FACTOR, (payload_dec.decode_32bit_int() / 1000.0)) # Frequency 26,27 transformed (uint) - self._set_output_value(self.PIN_O_SUPPLY_FREQUENCY, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_SUPPLY_FREQUENCY, (payload_dec.decode_32bit_uint() / 1000.0)) def read_l1_values(self, payload_dec): payload_dec.skip_bytes(24) # Skip over 12 registers # Active Power L1 40,41 / Minus 42,43 combined and transformed (uint) active_power_l1 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_ACTIVE_POWER_L1, active_power_l1) + self.write_output(self.PIN_O_ACTIVE_POWER_L1, active_power_l1) # Reactive Power L1 44,45 / Minus 46,47 combined and transformed (uint) reactive_power_l1 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_REACTIVE_POWER_L1, reactive_power_l1) + self.write_output(self.PIN_O_REACTIVE_POWER_L1, reactive_power_l1) payload_dec.skip_bytes(16) # skip 8 registers # Apparent Power L1 56,57 / Minus 58,59 combined and transformed (uint) apparent_power_l1 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_APPARENT_POWER_L1, apparent_power_l1) + self.write_output(self.PIN_O_APPARENT_POWER_L1, apparent_power_l1) # Current L1 60/61 transformed (uint) / Voltage L1 62/63 transformed (uint) - self._set_output_value(self.PIN_O_CURRENT_L1, (payload_dec.decode_32bit_uint() / 1000.0)) - self._set_output_value(self.PIN_O_VOLTAGE_L1, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_CURRENT_L1, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_VOLTAGE_L1, (payload_dec.decode_32bit_uint() / 1000.0)) # Power Factor L1 64/65 transformed (int!) - self._set_output_value(self.PIN_O_POWER_FACTOR_L1, (payload_dec.decode_32bit_int() / 1000.0)) + self.write_output(self.PIN_O_POWER_FACTOR_L1, (payload_dec.decode_32bit_int() / 1000.0)) def read_l2_values(self, payload_dec): # Active Power L2 80,81 / Minus 82,83 combined and transformed (uint) active_power_l2 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_ACTIVE_POWER_L2, active_power_l2) + self.write_output(self.PIN_O_ACTIVE_POWER_L2, active_power_l2) # Reactive Power L2 84,85 / Minus 86,87 combined and transformed (uint) reactive_power_l2 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_REACTIVE_POWER_L2, reactive_power_l2) + self.write_output(self.PIN_O_REACTIVE_POWER_L2, reactive_power_l2) payload_dec.skip_bytes(16) # skip 8 registers # Apparent Power L2 96,97 / Minus 98,99 combined and transformed (uint) apparent_power_l2 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_APPARENT_POWER_L2, apparent_power_l2) + self.write_output(self.PIN_O_APPARENT_POWER_L2, apparent_power_l2) # Current L2 100/101 transformed (uint) / Voltage L2 102/103 transformed (uint) - self._set_output_value(self.PIN_O_CURRENT_L2, (payload_dec.decode_32bit_uint() / 1000.0)) - self._set_output_value(self.PIN_O_VOLTAGE_L2, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_CURRENT_L2, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_VOLTAGE_L2, (payload_dec.decode_32bit_uint() / 1000.0)) # Power Factor L2 104/105 transformed (int!) - self._set_output_value(self.PIN_O_POWER_FACTOR_L2, (payload_dec.decode_32bit_int() / 1000.0)) + self.write_output(self.PIN_O_POWER_FACTOR_L2, (payload_dec.decode_32bit_int() / 1000.0)) def read_l3_values(self, payload_dec): payload_dec.skip_bytes(28) # Skip over 14 registers # Active Power L3 120,121 / Minus 122,123 combined and transformed (uint) active_power_l3 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_ACTIVE_POWER_L3, active_power_l3) + self.write_output(self.PIN_O_ACTIVE_POWER_L3, active_power_l3) # Reactive Power L3 124,125 / Minus 126,127 combined and transformed (uint) reactive_power_l3 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_REACTIVE_POWER_L3, reactive_power_l3) + self.write_output(self.PIN_O_REACTIVE_POWER_L3, reactive_power_l3) payload_dec.skip_bytes(16) # skip 8 registers # Apparent Power L3 136,137 / Minus 138,139 combined and transformed (uint) apparent_power_l3 = (payload_dec.decode_32bit_uint() - payload_dec.decode_32bit_uint()) / 10.0 - self._set_output_value(self.PIN_O_APPARENT_POWER_L3, apparent_power_l3) + self.write_output(self.PIN_O_APPARENT_POWER_L3, apparent_power_l3) # Current L3 140/141 transformed (uint) / Voltage L3 142/143 transformed (uint) - self._set_output_value(self.PIN_O_CURRENT_L3, (payload_dec.decode_32bit_uint() / 1000.0)) - self._set_output_value(self.PIN_O_VOLTAGE_L3, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_CURRENT_L3, (payload_dec.decode_32bit_uint() / 1000.0)) + self.write_output(self.PIN_O_VOLTAGE_L3, (payload_dec.decode_32bit_uint() / 1000.0)) # Power Factor L3 144/145 transformed (int!) - self._set_output_value(self.PIN_O_POWER_FACTOR_L3, (payload_dec.decode_32bit_int() / 1000.0)) + self.write_output(self.PIN_O_POWER_FACTOR_L3, (payload_dec.decode_32bit_int() / 1000.0)) def read_counter_values(self, payload_dec): # Active Power from grid 512-515 - transformed to kWh - self._set_output_value(self.PIN_O_COUNTER_ACTIVE_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_ACTIVE_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) # Active Power feed-in 516-519 - transformed to kWh - self._set_output_value(self.PIN_O_COUNTER_ACTIVE_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_ACTIVE_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) # Reactive Power from grid 520-523 - transformed to kvarh - self._set_output_value(self.PIN_O_COUNTER_REACTIVE_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_REACTIVE_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) # Reactive Power feed-in 524-527 - transformed to kvarh - self._set_output_value(self.PIN_O_COUNTER_REACTIVE_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_REACTIVE_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) # Reactive Power from grid 544-547 - transformed to kVAh - self._set_output_value(self.PIN_O_COUNTER_APPARENT_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_APPARENT_POWER_PLUS, (payload_dec.decode_64bit_uint() / 10000.0)) # Reactive Power feed-in 548-551 - transformed to kVAh - self._set_output_value(self.PIN_O_COUNTER_APPARENT_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) + self.write_output(self.PIN_O_COUNTER_APPARENT_POWER_MINUS, (payload_dec.decode_64bit_uint() / 10000.0)) + + def write_output(self, pin_output_num_id, value): + if self._can_set_output(): # Check if output queue of HS is not full + self._set_output_value(pin_output_num_id, value) ############# @@ -228,3 +231,4 @@ def on_input_value(self, index, value): if value == 1: self.interval.set_interval(self._get_input_value(self.PIN_I_FETCH_INTERVAL) * 1000, self.on_interval) self.interval.start() +