-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #896 from CannonRS/ksemmodbus
Ksemmodbus
- Loading branch information
Showing
11 changed files
with
419 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Modbus Plugin for Kostal Smart Energy Meter | ||
|
||
#### Version 1.6.2 | ||
|
||
This plugin connects your Kostal Smart Energy Meter (https://www.kostal-solar-electric.com/) via ModBus with SmarthomeNG. | ||
- read out all Smart Meter data | ||
|
||
## Change history | ||
|
||
- work with newer versions of pymodbus too | ||
|
||
### Changes Since version 1.x.x | ||
|
||
- No Changes so far | ||
|
||
|
||
### Requirements needed software | ||
|
||
* Python > 3.5 | ||
* pip install pymodbus | ||
* SmarthomeNG >= 1.6.0 | ||
|
||
## Configuration | ||
|
||
### 1) /smarthome/etc/plugin.yaml | ||
|
||
Enable the plugin in plugin.yaml, type in the Smart Meters IP address and configure the ModBus Port and update cycle(seconds). | ||
|
||
```yaml | ||
Ksemmodbus: | ||
plugin_name: ksemmodbus | ||
ksem_ip: 'XXX.XXX.XXX.XXX' | ||
modbus_port: '502' | ||
update_cycle: '20' | ||
``` | ||
### 2) /smarthome/items/kostal.yaml | ||
Create an item based on the template files/kostal_item_template.yaml | ||
## Examples | ||
Thats it! Now you can start using the plugin within SmartVisu. | ||
For example: | ||
#### Get data from Energy Meter: | ||
```html | ||
<p>Active Power - : {{basic.value('KSEM_Beszug','Kostal.ksem.ksem_0','W')}} </p> | ||
<p>Active Power + : {{basic.value('KSEM_Einspeisen','Kostal.ksem.ksem_2','W')}} </p> | ||
|
||
``` | ||
#### The following data are stored in the respective items: | ||
| Addr (dec) | Description | Format | Unit | | ||
|-------------------|---------------------------------------------------|--------|---------| | ||
| ksem_0 | Active power+ | U32 | W | | ||
| ksem_2 | Active power- | U32 | W | | ||
| ksem_4 | Reactive power+ | U32 | var | | ||
| ksem_6 | Reactive power- | U32 | var | | ||
| ksem_16 | Apparent power+ | U32 | VA | | ||
| ksem_18 | Apparent power- | U32 | VA | | ||
| ksem_24 | Power factor | Float | - | | ||
| ksem_512 | Active energy+ | U64 | Wh | | ||
| ksem_516 | Active energy- | U64 | Wh | | ||
| ksem_520 | Reactive energy+ | U64 | varh | | ||
| ksem_524 | Reactive energy- | U64 | varh | | ||
| ksem_544 | Apparent energy+ | U64 | VAh | | ||
| ksem_548 | Apparent energy- | U64 | VAh | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#!/usr/bin/env python3 | ||
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab | ||
######################################################################### | ||
# Copyright 2019 Thomas Hengsberg <[email protected]> | ||
# Copyright 2022 Ronny Schulz | ||
######################################################################### | ||
# This file is part of SmartHomeNG. | ||
# | ||
# Sample plugin for new plugins to run with SmartHomeNG version 1.4 and | ||
# upwards. | ||
# | ||
# SmartHomeNG is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# SmartHomeNG is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with SmartHomeNG. If not, see <http://www.gnu.org/licenses/>. | ||
# | ||
######################################################################### | ||
|
||
from lib.model.smartplugin import * | ||
|
||
from .ksem import Ksem | ||
|
||
class Ksemmodbus(SmartPlugin): | ||
PLUGIN_VERSION = '1.6.2' | ||
ksem = 'None' | ||
_items = [] | ||
|
||
def __init__(self, sh, *args, **kwargs): | ||
self.ksem = Ksem(self.get_parameter_value("ksem_ip"),self.get_parameter_value("modbus_port")) | ||
self._cycle = int(self.get_parameter_value("update_cycle")) | ||
return | ||
|
||
def run(self): | ||
self.logger.debug("Run method called") | ||
self.scheduler_add('poll_device', self.poll_device, cycle=self._cycle) | ||
self.alive = True | ||
|
||
def stop(self): | ||
self.logger.debug("Stop method called") | ||
self.alive = False | ||
|
||
def parse_item(self, item): | ||
for i in self.ksem.decRow: | ||
s = 'ksem_' + str(i) | ||
if self.has_iattr(item.conf, s): | ||
self._items.append(item) | ||
|
||
def poll_device(self): | ||
ksem_data = self.ksem.get_data() | ||
for item in self._items: | ||
for i in range (0,len(ksem_data)): | ||
s = 'ksem_' + str(ksem_data[i].adrDec) | ||
if self.has_iattr(item.conf, s): | ||
item(self.ksem.registers[i].value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
Kostal: | ||
ksem: | ||
ksem_0: | ||
ksem_0: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_2: | ||
ksem_2: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_0: | ||
ksem_0: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_4: | ||
ksem_4: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_6: | ||
ksem_6: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_16: | ||
ksem_16: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_18: | ||
ksem_18: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_24: | ||
ksem_24: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_512: | ||
ksem_512: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_516: | ||
ksem_516: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_5520: | ||
ksem_520: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_524: | ||
ksem_524: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_544: | ||
ksem_544: '' | ||
visu_acl: ro | ||
type: num | ||
ksem_548: | ||
ksem_548: '' | ||
visu_acl: ro | ||
type: num |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import binascii | ||
import hashlib | ||
import hmac | ||
import locale | ||
import time | ||
import logging | ||
import pymodbus | ||
|
||
from lib.model.smartplugin import * | ||
|
||
# pymodbus library from https://github.com/riptideio/pymodbus | ||
from pymodbus.version import version | ||
pymodbus_baseversion = int(version.short().split('.')[0]) | ||
|
||
if pymodbus_baseversion > 2: | ||
# for newer versions of pymodbus | ||
from pymodbus.client.tcp import ModbusTcpClient | ||
else: | ||
# for older versions of pymodbus | ||
from pymodbus.client.sync import ModbusTcpClient | ||
|
||
from pymodbus.payload import BinaryPayloadDecoder | ||
from pymodbus.constants import Endian | ||
|
||
class Ksem: | ||
def __init__(self, ip, port): | ||
self.__init_registers() | ||
self.client = ModbusTcpClient(ip, port=port) | ||
|
||
def __init_registers(self): | ||
self.registers = [] | ||
for i in range(0, len(self.decRow)): | ||
reg = Register(self.decRow[i],self.__descriptionRow[i],self.__typeRow[i],self.__unitRow[i]) | ||
self.registers.append(reg) | ||
|
||
def get_data(self): | ||
try: | ||
self.client.connect() | ||
for i in self.registers: | ||
if i.type == "Float": | ||
i.value = self.__read_float(i.adrDec) | ||
elif i.type == "U16": | ||
i.value = self.__read_u16(i.adrDec) | ||
elif i.type == "U16_2": | ||
i.value = self.__read_u16_2(i.adrDec) | ||
elif i.type == "U32": | ||
i.value = self.__read_u32(i.adrDec) | ||
elif i.type == "U64": | ||
i.value = self.__read_u64(i.adrDec) | ||
elif i.type == "S16": | ||
i.value = self.__read_s16(i.adrDec) | ||
except Exception as exc: | ||
print("Error getting Data from Kostal Smart Energy Meter :", exc) | ||
|
||
self.client.close() | ||
return self.registers | ||
|
||
def __read_float(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 2, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 2, unit=71) | ||
float_value = BinaryPayloadDecoder.fromRegisters(result.registers,byteorder=Endian.Big,wordorder=Endian.Little) | ||
return round(float_value.decode_32bit_float(), 2) | ||
|
||
def __read_u16(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 1, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 1, unit=71) | ||
u16_value = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big,wordorder=Endian.Little) | ||
return u16_value.decode_16bit_uint() | ||
|
||
def __read_u16_2(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 2, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 2, unit=71) | ||
u16_2_value = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big,wordorder=Endian.Little) | ||
return u16_2_value.decode_16bit_uint() | ||
|
||
def __read_s16(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 1, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 1, unit=71) | ||
s16_value = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big,wordorder=Endian.Little) | ||
return s16_value.decode_16bit_uint() | ||
|
||
def __read_u32(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 2, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 2, unit=71) | ||
u32_value = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Big) | ||
return round((u32_value.decode_32bit_uint() * 0.1), 2) | ||
|
||
def __read_u64(self, adr_dec): | ||
if pymodbus_baseversion > 2: | ||
result = self.client.read_holding_registers(adr_dec, 4, slave=71) | ||
else: | ||
result = self.client.read_holding_registers(adr_dec, 4, unit=71) | ||
u64_value = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Big) | ||
return round((u64_value.decode_64bit_uint() * 0.0001),2) | ||
|
||
|
||
decRow = [0, 2, 4, 6, 16, 18, 24, 512, 516, 520, 524, 544, 548] | ||
|
||
__descriptionRow = ['Active Power +', 'Active Power -', 'Reactive Power +', 'Reactive Power -', 'Apparent power +', 'Apparent power -', 'Power Factor', 'Active energy+', 'Active energy-', 'Reactive energy+', 'Reactive energy-', 'Apparent energy+', 'Apparent energy-'] | ||
|
||
__unitRow = ['W', 'W', 'var', 'var', 'VA', 'VA', '', 'Wh', 'Wh' ,'varh', 'varh', 'VAh', 'VAh'] | ||
|
||
__typeRow = ['U32', 'U32', 'U32', 'U32', 'U32', 'U32', 'Float', 'U64', 'U64', 'U64', 'U64', 'U64', 'U64'] | ||
|
||
|
||
class Register: | ||
def __init__(self, adr_dec,description,val_type,unit): | ||
self.adrDec = adr_dec | ||
self.description = description | ||
self.type = val_type | ||
self.unit = unit | ||
self.value = '' |
Oops, something went wrong.