Skip to content

Commit

Permalink
Write only if hs queue is empty
Browse files Browse the repository at this point in the history
Improved documentation
Build with newser HS4 stack
  • Loading branch information
Sven Bunge committed Feb 4, 2024
1 parent fca1747 commit bc3d5e1
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 42 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions config.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<config>
<modules>
<module category="Energiemanagement" context="kostalKsemModbusTCP14181" id="14181" name="kostalKsemModbusTCP" internal_name="kostalKSEM_ModbusTCP" external_name="Kostal-KSEM ModbusTCP (14181)" version="1.1">
<module category="Energiemanagement" context="kostalKsemModbusTCP14181" id="14181" name="kostalKsemModbusTCP" internal_name="kostalKSEM_ModbusTCP" external_name="Kostal-KSEM ModbusTCP (14181)" version="1.2">
<inputs>
<input type="number" const_name="switch" init_value="0">Switch on (1) / off (0)</input>
<input type="number" const_name="fetch_interval" init_value="5">Seconds of the interval to read power values (default: 5 secs)</input>
Expand Down Expand Up @@ -45,7 +45,7 @@
<remanent_variables>
</remanent_variables>
<imports>
<import>hsl20_3_timer</import>
<import>hsl20_4_timer</import>
<import>lib/pymodbus</import>
</imports>
</module>
Expand Down
4 changes: 2 additions & 2 deletions doc/log14181.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
72 changes: 38 additions & 34 deletions src/14181_kostalKSEM_ModbusTCP.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# coding: UTF-8
# coding: utf-8

import pymodbus # To not delete this module reference!!
from pymodbus.constants import Endian
Expand All @@ -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
Expand Down Expand Up @@ -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! ####
Expand Down Expand Up @@ -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)

#############

Expand All @@ -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()

0 comments on commit bc3d5e1

Please sign in to comment.