Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Knx interfacer #200

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions conf/interfacer_examples/KNX/knx.emonhub.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[[KNX]]
Type = EmonHubKNXInterfacer
[[[init_settings]]]
gateway_ip = 192.168.254.1
port = 3671
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 5
validate_checksum = False
nodeid=1
nodename = KNX
[[[[meters]]]]
[[[[[compteur]]]]]
[[[[[[voltage]]]]]]
group=10/0/1
eis=DPT-14
[[[[[[intensite]]]]]]
group=10/1/1
eis=DPT-14
[[[[[[puissance]]]]]]
group=10/2/1
eis=DPT-14
[[[[[[consommation]]]]]]
group=10/3/1
eis=DPT-12
[[[[[[consommationWh]]]]]]
group=10/5/1
eis=DPT-12
[[[[[compteurNew]]]]]
[[[[[[voltage]]]]]]
group=10/0/2
eis=DPT-14
[[[[[[intensite]]]]]]
group=10/1/2
eis=DPT-14


66 changes: 66 additions & 0 deletions conf/interfacer_examples/KNX/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
### KNX Reader to read value from knx group using a knx Gateway

KNX is a international standard for home automation.
KNX is based on a bus, where each device can communicate using knx group.
A Knx group is address using a knx address group notation of the form x/y/z.

To configure this interfacers, you would need:

to fill the global init_settings, mainly:

- **gateway_ip** The ip address of your ip gateway device.
- **port** The port of the gateway device (3671 is the default).
- **local_ip** If your server have multiple ip interface, indicate the ip of the interface link to the gateway device.

In runtimeseetings, you will have to list the group you want to read.
You can make some grouping by indicating a devicename under the meters section

Each device can contains single or many group read section of the form:
[[[[[[groupName]]]]]]
group=10/0/1
eis=DPT-14

- **groupName:** Will indicate the name of the group, indicate what you want, it will be the name of the input in emoncms inputs.
- **group:** The address of the group
- **eis:** The KNX type use for this group (please refer to KNX official documentation for the list).


```text
[[KNX]]
Type = EmonHubKNXInterfacer
[[[init_settings]]]
gateway_ip = 192.168.254.1
port = 3671
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 5
validate_checksum = False
nodeid=1
nodename = KNX
[[[[meters]]]]
[[[[[compteur]]]]]
[[[[[[voltage]]]]]]
group=10/0/1
eis=DPT-14
[[[[[[intensite]]]]]]
group=10/1/1
eis=DPT-14
[[[[[[puissance]]]]]]
group=10/2/1
eis=DPT-14
[[[[[[consommation]]]]]]
group=10/3/1
eis=DPT-12
[[[[[[consommationWh]]]]]]
group=10/5/1
eis=DPT-12
[[[[[compteurNew]]]]]
[[[[[[voltage]]]]]]
group=10/0/2
eis=DPT-14
[[[[[[intensite]]]]]]
group=10/1/2
eis=DPT-14



273 changes: 273 additions & 0 deletions src/interfacers/EmonHubKNXInterfacer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import time
import json
import re
import Cargo
import serial
import struct
import asyncio
import concurrent.futures
import threading

from emonhub_interfacer import EmonHubInterfacer
from xknx import XKNX
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xknx should be integrated in the dependencies....or maybe just a mention in the interfacer README

from xknx.io import ConnectionConfig, ConnectionType

from xknx.devices import Light
from xknx.devices import NumericValue
from xknx.devices import RawValue
from xknx.devices import Sensor
from xknx.dpt import DPTArray
from xknx.telegram import GroupAddress, Telegram
from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
from xknx.core import ValueReader

"""
[[KNX]]
Type = EmonHubKNXInterfacer
[[[init_settings]]]
gateway_ip = 192.168.254.40
port = 3691
local_ip = 192.168.254.1
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
validate_checksum = False
nodename = KNX
[[[[meters]]]]
[[[[[compteur]]]]]
group=1/1/1
eis=DPT-14
[[[[[[consommationWh]]]]]]
group=10/5/1
eis=DPT-12
"""

"""class EmonHubKNXInterfacer

KNX interfacer

"""

class EmonHubKNXInterfacer(EmonHubInterfacer):

def __init__(self, name, gateway_ip="127.0.0.1", local_ip="127.0.0.1", port=3671):
"""Initialize Interfacer

"""
# Initialization
super(EmonHubKNXInterfacer, self).__init__(name)

# This line will stop the default values printing to logfile at start-up
# self._settings.update(self._defaults)

# Interfacer specific settings
self._KNX_settings = {'read_interval': 10.0,
'nodename':'KNX',
'validate_checksum': True,
'meters':[]}

self._last_read_time = 0

try:
self.loop = asyncio.get_event_loop()

task = self.loop.create_task(self.initKnx(gateway_ip, local_ip))
self.loop.run_until_complete(task)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a throught, is it threadsafe to use run_until_complete like that ?


self.cargoList = {}

except ModuleNotFoundError as err:
self._log.error(err)
self.ser = False


def action(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is really needed.

super().action()


async def initKnx(self, gateway_ip, local_ip):
connection_config = ConnectionConfig(
connection_type=ConnectionType.TUNNELING,
gateway_ip=gateway_ip,
local_ip = local_ip
)

self._log.debug("Connect to KNX Gateway : " + gateway_ip)
self.xknx = XKNX(connection_config=connection_config, connection_state_changed_cb = self.connection_state_changed_cb,device_updated_cb=self.device_updated_cb, daemon_mode=False)

async def startKnx(self):
try:
await self.xknx.start()
except Exception as err:
self._log.error("KNX Error start:")
self._log.error(err);

def connection_state_changed_cb(self, state):
self._log.debug("KNX CnxUpdate:" )
self._log.debug(state)


def device_updated_cb(self, device):
value = device.resolve_state()
name = device.name
unit = device.unit_of_measurement()

self._log.info("Device:" + name + ' <> ' + str(value))

pos = name.index("_")
meter = name[0:pos]
key = name[pos+1:]

meterObj=self._settings['meters'][meter]
dptConf = meterObj[key]
if 'divider' in dptConf:
divider = dptConf["divider"]
if divider != '':
value = float(value) / float(divider)



result = {}
result[key] = [value,unit]


if meter in self.cargoList:
c = self.cargoList[meter]
self.add_result_to_cargo(c, result)
else:
cargoNew = Cargo.new_cargo("", False,[], [])
cargoNew.nodeid = meter
self.cargoList[meter] = cargoNew
self.add_result_to_cargo(cargoNew, result)



def add_result_to_cargo(self, cargo, result):
if result != None:

for key in result:
cargo.names.append(key)
cargo.realdata.append(result[key][0])
else:
self._log.info("Decoded KNX data: None")


async def setupSensor(self):
metters = self._settings['meters']

self.sensor={}
for metter in metters:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible to use items() like you do below ?

dpPoint = metters[metter]

for dpKey in dpPoint:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same I think you can iterate with items()

use var name like dp_config (snake case) and not dpConfig (camel case). the OEM maintainers are not using pylint to analyse their code but I think they should :-) I think the xknx lib is following this pattern and I think emonhub began like that, see the emonhub_interfacer base code

dpConfig = dpPoint[dpKey]

group = dpConfig["group"]
eis = dpConfig["eis"]

self._log.debug("add Sensors:" + metter+"_"+dpKey + ' <> ' + group)
self.sensor[metter+"_"+dpKey] = Sensor(self.xknx, metter + "_" + dpKey, value_type=eis, group_address_state=group, always_callback=True)
self.xknx.devices.async_add(self.sensor[metter+"_"+dpKey])




def add(self, cargo):
self.buffer.storeItem(f)


async def waitSensor(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used somewhere ?

pass


def read(self):
"""Read data and process

Return data as a list: [NodeID, val1, val2]

"""

interval = int(self._settings['read_interval'])
if time.time() - self._last_read_time < interval:
return


self._last_read_time = time.time()

#self.displayCargo("read")
result = self.cargoList;
self.cargoList ={};

return result;

def start(self):
self._log.info("Start KNX interface")

task = self.loop.create_task(self.setupSensor())
self.loop.run_until_complete(task)

task = self.loop.create_task(self.startKnx())
self.loop.run_until_complete(task)


self.updater = self.Updater(self)
self.updater.start()

super().start()


def set(self, **kwargs):
for key, setting in self._KNX_settings.items():
# Decide which setting value to use
if key in kwargs:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do setting = kwargs.get(key, self._KNX_settings[key]) and remove the whole if else

setting = kwargs[key]
else:
setting = self._KNX_settings[key]

if key in self._settings and self._settings[key] == setting:
continue
elif key == 'read_interval':
self._log.info("Setting %s read_interval: %s", self.name, setting)
self._settings[key] = float(setting)
continue
elif key == 'nodename':
self._log.info("Setting %s nodename: %s", self.name, setting)
self._settings[key] = str(setting)
continue
elif key == 'validate_checksum':
self._log.info("Setting %s validate_checksum: %s", self.name, setting)
self._settings[key] = True
if setting=='False':
self._settings[key] = False
continue
elif key == 'meters':
self._log.info("Setting %s meters: %s", self.name, json.dumps(setting))
self._settings['meters'] = {}
for meter in setting:
# default
address = 1
meter_type = "standard"
records = []

self._settings['meters'][meter] = setting[meter]
continue
else:
self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key)

# include kwargs from parent
super().set(**kwargs)



class Updater(threading.Thread):
def __init__(self, knxIntf):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you dont give any var when you initialize the updater in self.start. What is knxIntf ?

super().__init__()
self.loop = asyncio.get_event_loop()
self.knxIntf = knxIntf

pass

def run(self):
while not self.knxIntf.stop:
self.loop.run_until_complete(asyncio.sleep(1))
pass
1 change: 1 addition & 0 deletions src/interfacers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"EmonHubRedisInterfacer",
"EmonHubSDM120Interfacer",
"EmonHubMBUSInterfacer",
"EmonHubKNXInterfacer",
"EmonHubMinimalModbusInterfacer",
"EmonHubBleInterfacer",
"EmonHubGoodWeInterfacer",
Expand Down