diff --git a/roomba_980/__init__.py b/roomba_980/__init__.py
index ac2ab99e3..036d1a035 100755
--- a/roomba_980/__init__.py
+++ b/roomba_980/__init__.py
@@ -19,23 +19,23 @@
# along with SmartHome.py. If not, see .
#########################################################################
-import logging
+# TODO: das Modul ist im Sourcetree nicht vorhanden... woher soll das kommen?
from plugins.roomba_980.roomba import Roomba
from lib.model.smartplugin import SmartPlugin
-from lib.item import Items
+
class ROOMBA_980(SmartPlugin):
ALLOW_MULTIINSTANCE = False
- PLUGIN_VERSION = "1.0.1"
+ PLUGIN_VERSION = "1.0.2"
myroomba = None
- def __init__(self, sh, adress=None, blid=None, roombaPassword=None, cycle=900):
- self._address = adress
- self._blid = blid
- self._roombaPassword = roombaPassword
- self._cycle = cycle
+ def __init__(self):
+ self._address = self.get_parameter_value('adress')
+ self._blid = self.get_parameter_value('blid')
+ self._roombaPassword = self.get_parameter_value('roombaPassword')
+ self._cycle = self.get_parameter_value('cycle')
self._status_batterie = None
self._status_items = {}
@@ -61,7 +61,7 @@ def run(self):
self.alive = True
def stop(self):
- self.scheduler.remove('get_status')
+ self.scheduler_remove('get_status')
self.myroomba.disconnect()
self.alive = False
@@ -69,36 +69,35 @@ def __call__(self):
pass
def update_item(self, item, caller=None, source=None, dest=None):
- if caller != __name__:
+ if caller != __name__ and self.alive:
self.logger.debug('item_update {} '.format(item))
if self.get_iattr_value(item.conf, 'roomba_980') == "start":
- if item() == True:
- self.send_command("start")
+ if item() is True:
+ self.send_command("start")
elif self.get_iattr_value(item.conf, 'roomba_980') == "stop":
- if item() == True:
- self.send_command("stop")
+ if item() is True:
+ self.send_command("stop")
elif self.get_iattr_value(item.conf, 'roomba_980') == "dock":
- if item() == True:
- self.send_command("dock")
+ if item() is True:
+ self.send_command("dock")
def get_status(self):
status = self.myroomba.master_state
for status_item in self._status_items:
- if status_item == "status_batterie":
- self._status_items[status_item](status['state']['reported']['batPct'],__name__)
- elif status_item == "status_bin_full":
- self._status_items[status_item](status['state']['reported']['bin']['full'],__name__)
- elif status_item == "status_cleanMissionStatus_phase":
- self._status_items[status_item](status['state']['reported']['cleanMissionStatus']['phase'],__name__)
- elif status_item == "status_cleanMissionStatus_error":
- self._status_items[status_item](status['state']['reported']['cleanMissionStatus']['error'],__name__)
+ if status_item == "status_batterie":
+ self._status_items[status_item](status['state']['reported']['batPct'], __name__)
+ elif status_item == "status_bin_full":
+ self._status_items[status_item](status['state']['reported']['bin']['full'], __name__)
+ elif status_item == "status_cleanMissionStatus_phase":
+ self._status_items[status_item](status['state']['reported']['cleanMissionStatus']['phase'], __name__)
+ elif status_item == "status_cleanMissionStatus_error":
+ self._status_items[status_item](status['state']['reported']['cleanMissionStatus']['error'], __name__)
self.logger.debug('Status update')
def send_command(self, command):
- if self.myroomba != None:
- self.myroomba.send_command(command)
- self.logger.debug('send command: {} to Roomba'.format(command))
-
+ if self.myroomba is not None:
+ self.myroomba.send_command(command)
+ self.logger.debug('send command: {} to Roomba'.format(command))
diff --git a/roomba_980/plugin.yaml b/roomba_980/plugin.yaml
index 8b9ea3e8e..1da819cd6 100755
--- a/roomba_980/plugin.yaml
+++ b/roomba_980/plugin.yaml
@@ -7,17 +7,17 @@ plugin:
en: 'integration of the iRobot Roomba vacuum cleaner series 900'
maintainer: 'Zapfen83'
tester: '?'
- state: ready
+ state: development
keywords: irobot roomba # keywords, where applicable
# documentation: https://github.com/smarthomeNG/plugins/blob/develop/mqtt/README.md # url of documentation (wiki) page
# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
# Following entries are for Smart-Plugins:
- version: 1.0.1 # Plugin version
- sh_minversion: 1.5 # minimum shNG version to use this plugin
+ version: 1.0.2 # Plugin version
+ sh_minversion: 1.6 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: False
- restartable: unknown
+ multi_instance: false
+ restartable: true
classname: ROOMBA_980 # class containing the plugin
parameters:
@@ -25,28 +25,28 @@ parameters:
adress:
type: str
- default: True
+ default: true
description:
de: "Die IP Adresse des roomba Staubsaugers"
en: "The IP address of the roomba vacuum cleaner"
blid:
type: str
- default: True
+ default: true
description:
de: "Die blid des roomba Staubsaugers -> kann mit der getpassword.py ausgelesen werden"
en: "The blid of the roomba vacuum cleaner -> use getpassword.py to get it"
roombaPassword:
type: str
- default: True
+ default: true
description:
de: "Das Passwort des roomba Staubsaugers -> kann mit der getpassword.py ausgelesen werden"
en: "The password of the roomba vacuum cleaner -> use getpassword.py to get it"
cycle:
type: num
- default: False
+ default: false
description:
de: "update des items alle x Sekunden, default wert 900"
en: "update the state item every x secounds, default is 900"
diff --git a/sma/__init__.py b/sma/__init__.py
index 2a453c865..82d224a9e 100755
--- a/sma/__init__.py
+++ b/sma/__init__.py
@@ -11,29 +11,29 @@
#
# SMA-Plugin for SmartHomeNG. https://github.com/smarthomeNG//
#
-# License: Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
-# http://creativecommons.org/licenses/by-nc-sa/3.0/
+# License: Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
+# http://creativecommons.org/licenses/by-nc-sa/3.0/
#
-# You are free:
-# to Share — to copy, distribute and transmit the work
-# to Remix — to adapt the work
-# Under the following conditions:
-# Attribution:
-# You must attribute the work in the manner specified by the author or licensor
-# (but not in any way that suggests that they endorse you or your use of the work).
-# Noncommercial:
-# You may not use this work for commercial purposes.
-# Share Alike:
-# If you alter, transform, or build upon this work, you may distribute the resulting work
-# only under the same or similar license to this one.
+# You are free:
+# to Share — to copy, distribute and transmit the work
+# to Remix — to adapt the work
+# Under the following conditions:
+# Attribution:
+# You must attribute the work in the manner specified by the author or licensor
+# (but not in any way that suggests that they endorse you or your use of the work).
+# Noncommercial:
+# You may not use this work for commercial purposes.
+# Share Alike:
+# If you alter, transform, or build upon this work, you may distribute the resulting work
+# only under the same or similar license to this one.
#
# DISCLAIMER:
-# A user of this plugin acknowledges that he or she is receiving this
-# software on an "as is" basis and the user is not relying on the accuracy
-# or functionality of the software for any purpose. The user further
-# acknowledges that any use of this software will be at his own risk
-# and the copyright owner accepts no responsibility whatsoever arising from
-# the use or application of the software.
+# A user of this plugin acknowledges that he or she is receiving this
+# software on an "as is" basis and the user is not relying on the accuracy
+# or functionality of the software for any purpose. The user further
+# acknowledges that any use of this software will be at his own risk
+# and the copyright owner accepts no responsibility whatsoever arising from
+# the use or application of the software.
#########################################################################
import logging
@@ -42,7 +42,6 @@
import socket
from datetime import datetime
from dateutil import tz
-import itertools
from lib.model.smartplugin import SmartPlugin
@@ -185,19 +184,19 @@
class SMA(SmartPlugin):
ALLOW_MULTIINSTANCE = False
- PLUGIN_VERSION = "1.3.1"
+ PLUGIN_VERSION = "1.3.2"
- def __init__(self, smarthome, bt_addr, password="0000", update_cycle="60", allowed_timedelta="10"):
+ def __init__(self):
+ # TODO: self._own_bt_addr setzen
self.logger = logging.getLogger(__name__)
- self._sh = smarthome
- self._update_cycle = int(update_cycle)
+ self._inv_bt_addr = self.get_parameter_value('bt_addr')
+ self._inv_password = self.get_parameter_value('password')
+ self._update_cycle = self.get_parameter_value('update_cycle')
+ self._allowed_timedelta = self.get_parameter_value('allowed_timedelta')
self._fields = {}
self._requests = []
self._cmd_lock = threading.Lock()
self._reply_lock = threading.Condition()
- self._inv_bt_addr = bt_addr
- self._inv_password = password
- self._allowed_timedelta = int(allowed_timedelta)
self._inv_last_read_timestamp_utc = 0
self._inv_serial = 0
self._own_bt_addr_le = bytearray(BCAST_ADDR)
@@ -209,7 +208,7 @@ def __init__(self, smarthome, bt_addr, password="0000", update_cycle="60", allow
raise Exception("Python socket module does not support Bluetooth - see README.md how to install")
def _update_values(self):
- #logger.warning("sma: signal strength = {}%%".format(self._inv_get_bt_signal_strength()))
+ # logger.warning("sma: signal strength = {}%%".format(self._inv_get_bt_signal_strength()))
self._cmd_lock.acquire()
try:
for request in self._requests:
@@ -224,7 +223,7 @@ def _update_values(self):
self._reply_lock.release()
if ('LAST_UPDATE' in self._fields) and not (self._inv_last_read_timestamp_utc == 0):
self._inv_last_read_datetime = datetime.fromtimestamp(self._inv_last_read_timestamp_utc, tz.tzlocal())
- #self._inv_last_read_str = self._inv_last_read_datetime.strftime("%d.%m.%Y %H:%M:%S")
+ # self._inv_last_read_str = self._inv_last_read_datetime.strftime("%d.%m.%Y %H:%M:%S")
self._inv_last_read_str = self._inv_last_read_datetime.strftime("%d.%m. %H:%M ")
for item in self._fields['LAST_UPDATE']['items']:
item(self._inv_last_read_str, 'SMA', self._inv_serial)
@@ -241,7 +240,7 @@ def run(self):
self._plugin_active = self._plugin_active_item()
# "or self._is_connected" ensures the connection will be closed before terminating
while self.alive or self._is_connected:
- #self.logger.warning("sma: state self._is_connected = {} / self._plugin_active = {} / self.alive = {}".format(self._is_connected, self._plugin_active, self.alive))
+ # self.logger.warning("sma: state self._is_connected = {} / self._plugin_active = {} / self.alive = {}".format(self._is_connected, self._plugin_active, self.alive))
# connect to inverter if active but not connected
if self._plugin_active and not self._is_connected:
@@ -318,7 +317,7 @@ def run(self):
if not self.alive:
break
if msg is None:
- #self.logger.debug("sma: no msg...")
+ # self.logger.debug("sma: no msg...")
continue
if len(msg) >= 60:
i = 41
@@ -331,16 +330,16 @@ def run(self):
if lri not in lris:
self.logger.info("sma: unknown lri={:#06x} / cls={:#02x} / dataType={:#02x} - trying to continue".format(lri, cls, dataType))
if (dataType == 0x00) or (dataType == 0x40):
- i += 28
+ i += 28
elif (dataType == 0x08) or (dataType == 0x10):
- i += 40
+ i += 40
else:
self.logger.error("sma: rx - unknown datatype {:#02x}".format(dataType))
raise
continue
else:
timestamp_utc = int.from_bytes(msg[i + 4:i + 8], byteorder='little')
- value = eval(lri_evals[lris[lri][0]], dict(msg=msg,i=i,attribute_to_text=attribute_to_text))
+ value = eval(lri_evals[lris[lri][0]], dict(msg=msg, i=i, attribute_to_text=attribute_to_text))
i += lris[lri][1]
self.logger.debug("sma: lri={:#06x} / cls={:#02x} / timestamp={} / value={}".format(lri, cls, timestamp_utc, value))
if full_id in self._fields:
@@ -477,6 +476,7 @@ def _recv_smanet2_msg(self, no_timeout_warning=False):
# remove escape characters
i = 0
while True:
+ # TODO: if this works - fine, seems not to be standard Python 3?
if smanet2_msg[i] == 0x7d:
smanet2_msg[i + 1] ^= 0x20
del(smanet2_msg[i])
@@ -493,6 +493,7 @@ def _recv_smanet2_msg(self, no_timeout_warning=False):
def _recv_smanet1_msg_with_cmdcode(self, cmdcodes_expected=[0x0001]):
retries = 3
+ msg = None
while self.alive:
retries -= 1
if retries == 0:
@@ -522,7 +523,7 @@ def _send_msg(self, msg):
# set length fields
msg[1:3] = len(msg).to_bytes(2, byteorder='little')
msg[3] = msg[1] ^ msg[2] ^ 0x7e
- #print("tx: len={} / data=[{}]".format(len(msg), ' '.join(['0x%02x' % b for b in msg])))
+ # print("tx: len={} / data=[{}]".format(len(msg), ' '.join(['0x%02x' % b for b in msg])))
self._btsocket.send(msg)
def _calc_crc16(self, msg):
@@ -530,7 +531,7 @@ def _calc_crc16(self, msg):
for i in msg:
crc = (crc >> 8) ^ FCSTAB[(crc ^ i) & 0xFF]
crc ^= 0xFFFF
- #print("crc16 = {:x}".format(crc))
+ # print("crc16 = {:x}".format(crc))
return crc
def _inv_connect(self):
@@ -653,7 +654,7 @@ def _inv_send_request(self, request_set):
msg += SMANET2_HDR + bytes([0x09, 0xA0]) + BCAST_ADDR + bytes([0x00, 0x00]) + self._inv_bt_addr_le + bytes([0x00] + [0x00] + [0, 0, 0, 0]) + (self._send_count | 0x8000).to_bytes(2, byteorder='little')
msg += request_set[0].to_bytes(4, byteorder='little') + request_set[1].to_bytes(4, byteorder='little') + request_set[2].to_bytes(4, byteorder='little')
# send msg to inverter
- #self.logger.debug("sma: requesting {:#06x}-{:#06x}...".format(request_set[1], request_set[2]))
+ # self.logger.debug("sma: requesting {:#06x}-{:#06x}...".format(request_set[1], request_set[2]))
self._send_msg(msg)
def _inv_set_time(self):
@@ -666,7 +667,7 @@ def _inv_set_time(self):
msg += SMANET2_HDR + bytes([0x10, 0xA0]) + BCAST_ADDR + bytes([0x00, 0x00]) + self._inv_bt_addr_le + bytes([0x00] + [0x00] + [0, 0, 0, 0]) + (self._send_count | 0x8000).to_bytes(2, byteorder='little')
msg += int(0xF000020A).to_bytes(4, byteorder='little') + int(0x00236D00).to_bytes(4, byteorder='little') + int(0x00236D00).to_bytes(4, byteorder='little') + int(0x00236D00).to_bytes(4, byteorder='little')
local_time = int(time.time()).to_bytes(4, byteorder='little')
- msg += local_time + local_time + local_time + round((datetime.now()-datetime.utcnow()).total_seconds()).to_bytes(4, byteorder='little') + local_time + bytes([0x01, 0x00, 0x00, 0x00])
+ msg += local_time + local_time + local_time + round((datetime.now() - datetime.utcnow()).total_seconds()).to_bytes(4, byteorder='little') + local_time + bytes([0x01, 0x00, 0x00, 0x00])
# msg += local_time + local_time + local_time + time.localtime().tm_gmtoff.to_bytes(4, byteorder='little') + local_time + bytes([0x01, 0x00, 0x00, 0x00])
# send msg to inverter
- self._send_msg(msg)
\ No newline at end of file
+ self._send_msg(msg)
diff --git a/sma/plugin.yaml b/sma/plugin.yaml
index 3bc6bc912..f18e18f74 100755
--- a/sma/plugin.yaml
+++ b/sma/plugin.yaml
@@ -12,18 +12,18 @@ plugin:
documentation: https://smarthomeng.de/user/plugins_doc/config/sma.html # url of documentation (wiki) page
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/27997-beitrag-plugin-zum-lesen-von-sma-wechselrichtern-sunnyboy-5000tl-21-getestet
- version: 1.3.1 # Plugin version
- sh_minversion: 1.3 # minimum shNG version to use this plugin
+ version: 1.3.2 # Plugin version
+ sh_minversion: 1.6 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: False # plugin supports multi instance
- restartable: unknown
+ multi_instance: false # plugin supports multi instance
+ restartable: true
classname: SMA # class containing the plugin
parameters:
# Definition of parameters to be configured in etc/plugin.yaml
bt_addr:
type: str
- mandatory: True
+ mandatory: true
description:
de: 'Bluetooth-Adresse des SMA Wechselrichters (mit "hcitool scan" herauszufinden).'
en: 'Bluetooth address of SMA inverter (find out with "hcitool scan").'
@@ -31,14 +31,14 @@ parameters:
cycle:
type: int
default: 60
- mandatory: False
+ mandatory: false
description:
de: '(optional) Dieser Parameter muss normalerweise nicht angegeben werden. Er erlaubt es die Update-Frquenz anzupassen (Standard: alle 60 Sekunden).'
en: "(optional) This parameter usually doesn't have to be specified. It allows to change the update frequency (cycle every 60 seconds)."
password:
type: str
- mandatory: False
+ mandatory: false
description:
de: '(optional) Passwort des Wechselrichters im User-Mode. Default: 0000.'
en: '(optional) Password of the inverter in user mode. Default: 0000.'
@@ -46,7 +46,7 @@ parameters:
allowed_timedelta:
type: int
default: 10
- mandatory: False
+ mandatory: false
description:
de: '(optional) Erlaubter Zeitunterschied zwischen Systemzeit und Zeit des Wechselrichters. Falls der Inverter auf Systemzeit steht, kann die Prüfung mit -1 deaktiviert werden. Default: 10.'
en: '(optional) Allowed difference of inverter to system time - if above, inverter is set to system time - set to -1 to disable. Default: 10.'
@@ -55,7 +55,7 @@ item_attributes:
# Definition of item attributes defined by this plugin
sma:
type: str
- mandatory: True
+ mandatory: true
description:
de: 'Auszulesender Wert, möglich sind: PLUGIN_ACTIVE, AC_P_TOTAL, E_DAY, E_TOTAL, INV_SERIAL, INV_ADDRESS, LAST_UPDATE, DC_STRING1_P, DC_STRING2_P, DC_STRING1_U, DC_STRING2_U, DC_STRING1_I, DC_STRING2_I, OPERATING_TIME, FEEDING_TIME, GRID_FREQUENCY, STATUS, GRID_RELAY, SW_VERSION.'
en: 'Value to read. Possible: PLUGIN_ACTIVE, AC_P_TOTAL, E_DAY, E_TOTAL, INV_SERIAL, INV_ADDRESS, LAST_UPDATE, DC_STRING1_P, DC_STRING2_P, DC_STRING1_U, DC_STRING2_U, DC_STRING1_I, DC_STRING2_I, OPERATING_TIME, FEEDING_TIME, GRID_FREQUENCY, STATUS, GRID_RELAY, SW_VERSION.'
diff --git a/smarttv/__init__.py b/smarttv/__init__.py
index 07dde2fc5..654f13475 100755
--- a/smarttv/__init__.py
+++ b/smarttv/__init__.py
@@ -27,21 +27,23 @@
from lib.model.smartplugin import SmartPlugin
from uuid import getnode as getmac
+
class SmartTV(SmartPlugin):
ALLOW_MULTIINSTANCE = True
- PLUGIN_VERSION = "1.3.2"
+ PLUGIN_VERSION = "1.3.3"
- def __init__(self, smarthome, host, port=55000, tv_version='classic', delay=1):
+ def __init__(self):
self.logger = logging.getLogger(__name__)
- self._sh = smarthome
- self._host = host
- self._port = int(port)
- self._delay = delay
- if tv_version not in ['samsung_m_series', 'classic']:
+ self._tv_version = self.get_parameter_value('tv_version')
+ self._host = self.get_parameter_value('host')
+ self._port = self.get_parameter_value('port')
+ self._delay = self.get_parameter_value('delay')
+ if self._tv_version not in ['samsung_m_series', 'classic']:
self.logger.error('No valid tv_version attribute specified to plugin')
- self._tv_version = tv_version
- self.logger.debug("Smart TV plugin for {0} SmartTV device initalized".format(tv_version))
+ self._init_complete = False
+ else:
+ self.logger.debug("Smart TV plugin for {0} SmartTV device initalized".format(self._tv_version))
def push_samsung_m_series(self, key):
"""
@@ -68,7 +70,7 @@ def push_classic(self, key):
self.logger.debug("Connected to {0}:{1}".format(self._host, self._port))
except Exception:
self.logger.warning("Could not connect to %s:%s, to send key: %s." %
- (self._host, self._port, key))
+ (self._host, self._port, key))
return
src = s.getsockname()[0] # ip of remote
@@ -140,6 +142,8 @@ def parse_item(self, item):
return None
def update_item(self, item, caller=None, source=None, dest=None):
+ if not self.alive:
+ return
val = item()
if isinstance(val, str):
if val.startswith('KEY_'):
diff --git a/smarttv/plugin.yaml b/smarttv/plugin.yaml
index ef43b482d..8127d8b16 100755
--- a/smarttv/plugin.yaml
+++ b/smarttv/plugin.yaml
@@ -12,25 +12,25 @@ plugin:
# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page
# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
- version: 1.3.2 # Plugin version
- sh_minversion: 1.3 # minimum shNG version to use this plugin
+ version: 1.3.3 # Plugin version
+ sh_minversion: 1.6 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: True # plugin supports multi instance
- restartable: unknown
+ multi_instance: true # plugin supports multi instance
+ restartable: true
classname: SmartTV # class containing the plugin
parameters:
# Definition of parameters to be configured in etc/plugin.yaml
host:
type: str
- mandatory: True
+ mandatory: true
description:
de: 'IP Adresse des SmartTV Geräts.'
en: 'Specifies the ip address of your SmartTV device.'
port:
type: int
- mandatory: False
+ mandatory: false
default: 55000
description:
de: '(optional) Im Fall eines Ports zu setzen, der nicht dem Standard entspricht (Standardports siehe tv_version). Default: 55000.'
@@ -38,7 +38,7 @@ parameters:
tv_version:
type: str
- mandatory: False
+ mandatory: false
default: classic
description:
de: '(optional) Erlaubte Werte sind "classic" (Port 55000) oder "samsung_m_series" (Standardport: 8001). Default: "classic".'
@@ -46,7 +46,7 @@ parameters:
delay:
type: int
- mandatory: False
+ mandatory: false
default: 1
description:
de: '(optional) Verzögerung in Sekunden, falls mehr als eine Taste gesendet wird. Reduziert Probleme, wenn bspw. TV-Programme wie "135" aufgerufen werden. Default: 1'
@@ -57,7 +57,7 @@ item_attributes:
# Definition of item attributes defined by this plugin
smarttv:
type: str
- mandatory: True
+ mandatory: true
description:
de: 'Es gibt zwei Wege das Attribut zu nutzen.
* Auf einem Item des Typs `str` mit dem Wert "true": Jeder String der in das Item geschrieben wird, wird an den SmartTV gesendet.
diff --git a/sml/__init__.py b/sml/__init__.py
index aabb788d9..2e93df094 100755
--- a/sml/__init__.py
+++ b/sml/__init__.py
@@ -32,31 +32,32 @@
from lib.model.smartplugin import SmartPlugin
+
class Sml(SmartPlugin):
ALLOW_MULTIINSTANCE = True
- PLUGIN_VERSION = '1.0.0'
+ PLUGIN_VERSION = '1.0.1'
_v1_start = b'\x1b\x1b\x1b\x1b\x01\x01\x01\x01'
_v1_end = b'\x1b\x1b\x1b\x1b\x1a'
_units = { # Blue book @ http://www.dlms.com/documentation/overviewexcerptsofthedlmsuacolouredbooks/index.html
- 1 : 'a', 2 : 'mo', 3 : 'wk', 4 : 'd', 5 : 'h', 6 : 'min.', 7 : 's', 8 : '°', 9 : '°C', 10 : 'currency',
- 11 : 'm', 12 : 'm/s', 13 : 'm³', 14 : 'm³', 15 : 'm³/h', 16 : 'm³/h', 17 : 'm³/d', 18 : 'm³/d', 19 : 'l', 20 : 'kg',
- 21 : 'N', 22 : 'Nm', 23 : 'Pa', 24 : 'bar', 25 : 'J', 26 : 'J/h', 27 : 'W', 28 : 'VA', 29 : 'var', 30 : 'Wh',
- 31 : 'WAh', 32 : 'varh', 33 : 'A', 34 : 'C', 35 : 'V', 36 : 'V/m', 37 : 'F', 38 : 'Ω', 39 : 'Ωm²/h', 40 : 'Wb',
- 41 : 'T', 42 : 'A/m', 43 : 'H', 44 : 'Hz', 45 : 'Rac', 46 : 'Rre', 47 : 'Rap', 48 : 'V²h', 49 : 'A²h', 50 : 'kg/s',
- 51 : 'Smho'
+ 1: 'a', 2: 'mo', 3: 'wk', 4: 'd', 5: 'h', 6: 'min.', 7: 's', 8: '°', 9: '°C', 10: 'currency',
+ 11: 'm', 12: 'm/s', 13: 'm³', 14: 'm³', 15: 'm³/h', 16: 'm³/h', 17: 'm³/d', 18: 'm³/d', 19: 'l', 20: 'kg',
+ 21: 'N', 22: 'Nm', 23: 'Pa', 24: 'bar', 25: 'J', 26: 'J/h', 27: 'W', 28: 'VA', 29: 'var', 30: 'Wh',
+ 31: 'WAh', 32: 'varh', 33: 'A', 34: 'C', 35: 'V', 36: 'V/m', 37: 'F', 38: 'Ω', 39: 'Ωm²/h', 40: 'Wb',
+ 41: 'T', 42: 'A/m', 43: 'H', 44: 'Hz', 45: 'Rac', 46: 'Rre', 47: 'Rap', 48: 'V²h', 49: 'A²h', 50: 'kg/s',
+ 51: 'Smho'
}
_devices = {
- 'smart-meter-gateway-com-1' : 'hex'
+ 'smart-meter-gateway-com-1': 'hex'
}
- def __init__(self, smarthome, host=None, port=0, serialport=None, device="raw", cycle=300):
- self._sh = smarthome
- self.host = host
- self.port = int(port)
- self.serialport = serialport
- self.cycle = cycle
+ def __init__(self):
+ self.host = self.get_parameter_value('host')
+ self.port = self.get_parameter_value('port')
+ self.serialport = self.get_parameter_value('serialport')
+ self.cycle = self.get_parameter_value('cycle')
+ self.device = self.get_parameter_value('device')
self.connected = False
self._serial = None
self._sock = None
@@ -66,23 +67,24 @@ def __init__(self, smarthome, host=None, port=0, serialport=None, device="raw",
self._lock = threading.Lock()
self.logger = logging.getLogger(__name__)
- if device in self._devices:
- device = self._devices[device]
+ if self._device in self._devices:
+ self._device = self._devices[self._device]
- if device == "hex":
+ if self._device == "hex":
self._prepare = self._prepareHex
- elif device == "raw":
+ elif self._device == "raw":
self._prepare = self._prepareRaw
else:
- self.logger.warning("Device type \"{}\" not supported - defaulting to \"raw\"".format(device))
+ self.logger.warning("Device type \"{}\" not supported - defaulting to \"raw\"".format(self._device))
self._prepare = self._prepareRaw
def run(self):
self.alive = True
- self._sh.scheduler.add('Sml', self._refresh, cycle=self.cycle)
+ self.scheduler_add('Sml', self._refresh, cycle=self.cycle)
def stop(self):
self.alive = False
+ self.scheduler_remove('Sml')
self.disconnect()
def parse_item(self, item):
@@ -98,16 +100,8 @@ def parse_item(self, item):
return self.update_item
return None
- def parse_logic(self, logic):
- pass
-
- def update_item(self, item, caller=None, source=None, dest=None):
- if caller != 'Sml':
- pass
-
def connect(self):
self._lock.acquire()
- target = None
try:
if self.serialport is not None:
self._target = 'serial://{}'.format(self.serialport)
@@ -164,9 +158,9 @@ def _read(self, length):
raise e
return b''.join(total)
-
+
def _refresh(self):
- if self.connected:
+ if self.connected and self.alive:
start = time.time()
retry = 5
data = None
@@ -183,8 +177,8 @@ def _refresh(self):
if start_pos != -1 and end_pos == -1:
data = data[:start_pos]
elif start_pos != -1 and end_pos != -1:
- chunk = data[start_pos:end_pos+len(self._v1_end)+3]
- self.logger.debug('Found chunk at {} - {} ({} bytes):{}'.format(start_pos, end_pos, end_pos-start_pos, ''.join(' {:02x}'.format(x) for x in chunk)))
+ chunk = data[start_pos:end_pos + len(self._v1_end) + 3]
+ self.logger.debug('Found chunk at {} - {} ({} bytes):{}'.format(start_pos, end_pos, end_pos - start_pos, ''.join(' {:02x}'.format(x) for x in chunk)))
chunk_crc_str = '{:02X}{:02X}'.format(chunk[-2], chunk[-1])
chunk_crc_calc = self._crc16(chunk[:-2])
chunk_crc_calc_str = '{:02X}{:02X}'.format((chunk_crc_calc >> 8) & 0xff, chunk_crc_calc & 0xff)
@@ -236,27 +230,27 @@ def _parse(self, data):
packetsize = 7
self.logger.debug('Data ({} bytes):{}'.format(len(data), ''.join(' {:02x}'.format(x) for x in data)))
self._dataoffset = 0
- while self._dataoffset < builtins.len(data)-packetsize:
+ while self._dataoffset < builtins.len(data) - packetsize:
# Find SML_ListEntry starting with 0x77 0x07 and OBIS code end with 0xFF
- if data[self._dataoffset] == 0x77 and data[self._dataoffset+1] == 0x07 and data[self._dataoffset+packetsize] == 0xff:
+ if data[self._dataoffset] == 0x77 and data[self._dataoffset + 1] == 0x07 and data[self._dataoffset + packetsize] == 0xff:
packetstart = self._dataoffset
self._dataoffset += 1
try:
entry = {
- 'objName' : self._read_entity(data),
- 'status' : self._read_entity(data),
- 'valTime' : self._read_entity(data),
- 'unit' : self._read_entity(data),
- 'scaler' : self._read_entity(data),
- 'value' : self._read_entity(data),
- 'signature' : self._read_entity(data)
+ 'objName': self._read_entity(data),
+ 'status': self._read_entity(data),
+ 'valTime': self._read_entity(data),
+ 'unit': self._read_entity(data),
+ 'scaler': self._read_entity(data),
+ 'value': self._read_entity(data),
+ 'signature': self._read_entity(data)
}
# add additional calculated fields
entry['obis'] = '{}-{}:{}.{}.{}*{}'.format(entry['objName'][0], entry['objName'][1], entry['objName'][2], entry['objName'][3], entry['objName'][4], entry['objName'][5])
entry['valueReal'] = entry['value'] * 10 ** entry['scaler'] if entry['scaler'] is not None else entry['value']
- entry['unitName'] = self._units[entry['unit']] if entry['unit'] != None and entry['unit'] in self._units else None
+ entry['unitName'] = self._units[entry['unit']] if entry['unit'] is not None and entry['unit'] in self._units else None
values[entry['obis']] = entry
except Exception as e:
@@ -269,8 +263,8 @@ def _parse(self, data):
def _read_entity(self, data):
upack = {
- 5 : { 1 : '>b', 2 : '>h', 4 : '>i', 8 : '>q' }, # int
- 6 : { 1 : '>B', 2 : '>H', 4 : '>I', 8 : '>Q' } # uint
+ 5: {1: '>b', 2: '>h', 4: '>i', 8: '>q'}, # int
+ 6: {1: '>B', 2: '>H', 4: '>I', 8: '>Q'} # uint
}
result = None
@@ -296,23 +290,23 @@ def _read_entity(self, data):
self._parse_error('Tried to read {} bytes, but only have {}', [len, builtins.len(data) - self._dataoffset], data, self._dataoffset, packetstart)
elif type == 0: # octet string
- result = data[self._dataoffset:self._dataoffset+len]
+ result = data[self._dataoffset:self._dataoffset + len]
elif type == 5 or type == 6: # int or uint
- d = data[self._dataoffset:self._dataoffset+len]
+ d = data[self._dataoffset:self._dataoffset + len]
ulen = len
if ulen not in upack[type]: # extend to next greather unpack unit
- while ulen not in upack[type]:
- d = b'\x00' + d
- ulen += 1
+ while ulen not in upack[type]:
+ d = b'\x00' + d
+ ulen += 1
result = struct.unpack(upack[type][ulen], d)[0]
elif type == 7: # list
result = []
self._dataoffset += 1
- for i in range(0, len + 1):
+ for _ in range(0, len + 1):
result.append(self._read_entity(data))
return result
@@ -326,7 +320,7 @@ def _read_entity(self, data):
def _parse_error(self, msg, msgargs, data, dataoffset, packetstart):
position = dataoffset - packetstart
databytes = ''
- for i, b in enumerate(data[packetstart:packetstart+64]):
+ for i, b in enumerate(data[packetstart:packetstart + 64]):
databytes = databytes + ' {}{:02x}{}'.format(
'' if i != position - 1 else '<',
b,
@@ -348,24 +342,23 @@ def _prepareHex(self, data):
data = re.sub("[^a-f0-9]", " ", data)
data = re.sub("( +[a-f0-9]|[a-f0-9] +)", "", data)
data = data.encode()
- return bytes(''.join(chr(int(data[i:i+2], 16)) for i in range(0, len(data), 2)), "iso8859-1")
+ return bytes(''.join(chr(int(data[i:i + 2], 16)) for i in range(0, len(data), 2)), "iso8859-1")
def _crc16(self, data):
- crc = 0xffff
-
- p = 0;
- while p < len(data):
- c = 0xff & data[p]
- p = p + 1
+ crc = 0xffff
- for i in range(0, 8):
- if ((crc & 0x0001) ^ (c & 0x0001)):
- crc = (crc >> 1) ^ 0x8408
- else:
- crc = crc >> 1
- c = c >> 1
+ p = 0
+ while p < len(data):
+ c = 0xff & data[p]
+ p = p + 1
- crc = ~crc & 0xffff
+ for i in range(0, 8):
+ if ((crc & 0x0001) ^ (c & 0x0001)):
+ crc = (crc >> 1) ^ 0x8408
+ else:
+ crc = crc >> 1
+ c = c >> 1
- return ((crc << 8) | ((crc >> 8) & 0xff)) & 0xffff
+ crc = ~crc & 0xffff
+ return ((crc << 8) | ((crc >> 8) & 0xff)) & 0xffff
diff --git a/sml/plugin.yaml b/sml/plugin.yaml
index 91360c5b2..c031f5e10 100755
--- a/sml/plugin.yaml
+++ b/sml/plugin.yaml
@@ -12,11 +12,11 @@ plugin:
documentation: http://smarthomeng.de/user/plugins_doc/config/sml.html
# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
- version: 1.0.0 # Plugin version
- sh_minversion: 1.1 # minimum shNG version to use this plugin
+ version: 1.0.1 # Plugin version
+ sh_minversion: 1.6 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: True # plugin supports multi instance
- restartable: unknown
+ multi_instance: true # plugin supports multi instance
+ restartable: true
classname: Sml # class containing the plugin
parameters:
diff --git a/systemair/__init__.py b/systemair/__init__.py
index dc467c6ee..24a40b8e5 100755
--- a/systemair/__init__.py
+++ b/systemair/__init__.py
@@ -18,50 +18,47 @@
# You should have received a copy of the GNU General Public License
# along with this plugin. If not, see .
#########################################################################
-import logging
from time import sleep
import minimalmodbus
-from serial import SerialException
import serial
import threading
from ctypes import c_short
from lib.model.smartplugin import SmartPlugin
+
class Systemair(SmartPlugin):
- PLUGIN_VERSION = "1.3.0.1"
+ PLUGIN_VERSION = "1.3.1"
ALLOW_MULTIINSTANCE = False
- def __init__(self, smarthome, serialport, slave_address="1", update_cycle="30"):
- self._sh = smarthome
- self.logger = logging.getLogger(__name__)
+ def __init__(self):
self.instrument = None
- self.slave_address = int(slave_address)
+ self.serialport = self.get_parameter_value('serialport')
+ self.slave_address = self.get_parameter_value('slave_address')
+ self._update_cycle = self.get_parameter_value('update_cycle')
self._update_coil = {}
- self.serialport = serialport
- self.slave_address = slave_address
minimalmodbus.TIMEOUT = 3
- minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL=True
- self._sh.scheduler.add(__name__, self._read_modbus, prio=5, cycle=int(update_cycle))
+ minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
+ self.scheduler_add(__name__, self._read_modbus, prio=5, cycle=self._update_cycle)
self.my_reg_items = []
self.mod_write_repeat = 20 # if port is already open, e.g on auto-update,
# repeat mod_write attempt x times a 1 seconds
self._lockmb = threading.Lock() # modbus serial port lock
self.init_serial_connection(self.serialport, self.slave_address)
- self._reg_sets = [{'name':'fan', 'range':range(101, 138+1)},
- {'name':'heater', 'range':range(201, 221+1), 'scaled_signed':range(208, 218+1)},
- {'name':'damper', 'range':range(301, 301+1)},
- {'name':'rotor', 'range':range(351, 352+1)},
- {'name':'week', 'range':range(401, 459+1)},
- {'name':'system', 'range':range(501, 507+1)},
- {'name':'clock', 'range':range(551, 557+1)},
- {'name':'filter', 'range':range(601, 602+1)},
- {'name':'VTC_defr', 'range':range(651, 654+1)},
- {'name':'VTR_defr', 'range':range(671, 672+1)},
- {'name':'dig_in', 'range':range(701, 709+1)},
- {'name':'PCU_PB', 'range':range(751, 751+1)},
- {'name':'alarms', 'range':range(801, 802+1)},
- {'name':'demand', 'range':range(851, 859+1)},
- {'name':'wireless', 'range':range(901, 1020+1)},]
+ self._reg_sets = [{'name': 'fan', 'range': range(101, 138 + 1)},
+ {'name': 'heater', 'range': range(201, 221 + 1), 'scaled_signed': range(208, 218 + 1)},
+ {'name': 'damper', 'range': range(301, 301 + 1)},
+ {'name': 'rotor', 'range': range(351, 352 + 1)},
+ {'name': 'week', 'range': range(401, 459 + 1)},
+ {'name': 'system', 'range': range(501, 507 + 1)},
+ {'name': 'clock', 'range': range(551, 557 + 1)},
+ {'name': 'filter', 'range': range(601, 602 + 1)},
+ {'name': 'VTC_defr', 'range': range(651, 654 + 1)},
+ {'name': 'VTR_defr', 'range': range(671, 672 + 1)},
+ {'name': 'dig_in', 'range': range(701, 709 + 1)},
+ {'name': 'PCU_PB', 'range': range(751, 751 + 1)},
+ {'name': 'alarms', 'range': range(801, 802 + 1)},
+ {'name': 'demand', 'range': range(851, 859 + 1)},
+ {'name': 'wireless', 'range': range(901, 1020 + 1)},]
def init_serial_connection(self, serialport, slave_address):
try:
@@ -84,9 +81,9 @@ def _read_modbus(self):
for reg_set in self._reg_sets:
if 'range_used' in reg_set:
read_regs = dict(zip(reg_set['range_used'], self.instrument.read_registers(
- reg_set['range_used'].start -1,
- reg_set['range_used'].stop - reg_set['range_used'].start,
- functioncode = 3)))
+ reg_set['range_used'].start - 1,
+ reg_set['range_used'].stop - reg_set['range_used'].start,
+ functioncode=3)))
if 'scaled_signed' in reg_set:
for scaled_reg in reg_set['scaled_signed']:
read_regs[scaled_reg] = c_short(read_regs[scaled_reg]).value / 10
@@ -100,7 +97,7 @@ def _read_modbus(self):
# get coils
for coil_addr in self._update_coil:
- value = self.instrument.read_bit(coil_addr-1, functioncode=2)
+ value = self.instrument.read_bit(coil_addr - 1, functioncode=2)
if value is not None:
for item in self._update_coil[coil_addr]:
item(value, 'systemair_value_from_bus', "Coil {}".format(coil_addr))
@@ -123,7 +120,7 @@ def connect(self):
def update_item(self, item, caller=None, source=None, dest=None):
# ignore values from bus
- if caller == 'systemair_value_from_bus':
+ if caller == 'systemair_value_from_bus' or not self.alive:
return
if item in self.my_reg_items:
if self.has_iattr(item.conf, 'mod_write'):
@@ -138,9 +135,9 @@ def parse_item(self, item):
for reg_set in self._reg_sets:
if modbus_regaddr in reg_set['range']:
- if not 'regs_used' in reg_set:
+ if 'regs_used' not in reg_set:
reg_set['regs_used'] = dict()
- if not modbus_regaddr in reg_set['regs_used']:
+ if modbus_regaddr not in reg_set['regs_used']:
reg_set['regs_used'][modbus_regaddr] = set()
reg_set['regs_used'][modbus_regaddr].add(item)
@@ -155,7 +152,7 @@ def parse_item(self, item):
if self.has_iattr(item.conf, 'systemair_coiladdr'):
modbus_coiladdr = int(self.get_iattr_value(item.conf, 'systemair_coiladdr'))
self.logger.debug("systemair_value_from_bus: {0} connected to coil register {1:#04x}".format(item, modbus_coiladdr))
- if not modbus_coiladdr in self._update_coil:
+ if modbus_coiladdr not in self._update_coil:
self._update_coil[modbus_coiladdr] = set()
self._update_coil[modbus_coiladdr].add(item)
diff --git a/systemair/plugin.yaml b/systemair/plugin.yaml
index b1979bc82..98e015e4d 100755
--- a/systemair/plugin.yaml
+++ b/systemair/plugin.yaml
@@ -12,11 +12,11 @@ plugin:
# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/939623-systemair-modbus-plugin-zentrale-lüftungsanlage
- version: 1.3.0.1 # Plugin version
- sh_minversion: 1.3 # minimum shNG version to use this plugin
+ version: 1.3.1 # Plugin version
+ sh_minversion: 1.6 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: False # plugin supports multi instance
- restartable: unknown
+ multi_instance: false # plugin supports multi instance
+ restartable: true
classname: Systemair # class containing the plugin
parameters: