Permalink
2354 lines (2086 sloc) 79.3 KB
# This file is part of pyRFXtrx, a Python library to communicate with
# the RFXtrx family of devices from http://www.rfxcom.com/
# See https://github.com/Danielhiversen/pyRFXtrx for the latest version.
#
# Copyright (C) 2012 Edwin Woudt <edwin@woudt.nl>
#
# pyRFXtrx is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyRFXtrx 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyRFXtrx. See the file COPYING.txt in the distribution.
# If not, see <http://www.gnu.org/licenses/>.
"""
This module provides low level packet parsing and generation code for the
RFXtrx.
"""
# pylint: disable=C0302,R0902,R0903,R0911,R0913
# pylint: disable= too-many-lines, too-many-statements
def parse(data):
# pylint: disable=too-many-branches
""" Parse a packet from a bytearray """
if data[0] == 0 or len(data) < 2:
# null length packet - sometimes happens on initialization
return None
expected_length = data[0] + 1
if len(data) != expected_length:
return None
if data[1] == 0x01:
pkt = Status()
elif data[1] == 0x10:
pkt = Lighting1()
elif data[1] == 0x11:
pkt = Lighting2()
elif data[1] == 0x12:
pkt = Lighting3()
elif data[1] == 0x13:
pkt = Lighting4()
elif data[1] == 0x14:
pkt = Lighting5()
elif data[1] == 0x15:
pkt = Lighting6()
elif data[1] == 0x16:
pkt = Chime()
elif data[1] == 0x19:
pkt = RollerTrol()
elif data[1] == 0x1A:
pkt = Rfy()
elif data[1] == 0x20:
pkt = Security1()
elif data[1] == 0x50:
pkt = Temp()
elif data[1] == 0x4E:
pkt = Bbq()
elif data[1] == 0x51:
pkt = Humid()
elif data[1] == 0x52:
pkt = TempHumid()
elif data[1] == 0x53:
pkt = Baro()
elif data[1] == 0x54:
pkt = TempHumidBaro()
elif data[1] == 0x55:
pkt = Rain()
elif data[1] == 0x56:
pkt = Wind()
elif data[1] == 0x59:
pkt = Energy1()
elif data[1] == 0x5A:
pkt = Energy()
elif data[1] == 0x5B:
pkt = Energy4()
elif data[1] == 0x5C:
pkt = Energy5()
elif data[1] == 0x71:
pkt = RfxMeter()
else:
return None
pkt.load_receive(data)
return pkt
###############################################################################
# Packet class
###############################################################################
class Packet(object):
""" Abstract superclass for all low level packets """
_UNKNOWN_TYPE = "Unknown type ({0:#04x}/{1:#04x})"
_UNKNOWN_CMND = "Unknown command ({0:#04x})"
def __init__(self):
"""Constructor"""
self.data = None
self.packetlength = None
self.packettype = None
self.subtype = None
self.seqnbr = None
self.rssi = None
self.rssi_byte = None
self.type_string = None
self.id_string = None
def has_value(self, datatype):
"""Return True if the sensor supports the given data type.
sensor.has_value(RFXCOM_TEMPERATURE) is identical to calling
sensor.has_temperature().
"""
return hasattr(self, datatype)
def value(self, datatype):
"""Return the :class:`SensorValue` for the given data type.
sensor.value(RFXCOM_TEMPERATURE) is identical to calling
sensor.temperature().
"""
return getattr(self, datatype, None)
def __getattr__(self, name):
typename = name.replace("has_", "", 1)
if not name == typename:
return lambda: self.has_value(typename)
raise AttributeError(name)
def __eq__(self, other):
if not isinstance(other, Packet):
return False
return self.id_string == other.id_string
def __str__(self):
return self.id_string
def __repr__(self):
return self.__str__()
###############################################################################
# Status class
###############################################################################
def _decode_flags(data, words):
"""Decode flags """
words = words.split()
res = set()
for word in words:
if data % 2:
res.add(word)
data //= 2
return res
class Status(Packet):
"""
Data class for the Status packet type
"""
TYPES = {
0x50: '310MHz',
0x51: '315MHz',
0x53: '433.92MHz',
0x55: '868.00MHz',
0x56: '868.00MHz FSK',
0x57: '868.30MHz',
0x58: '868.30MHz FSK',
0x59: '868.35MHz',
0x5A: '868.35MHz FSK',
0x5B: '868.95MHz'
}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Status [subtype={0}, firmware={1}, devices={2}]") \
.format(self.type_string, self.firmware_version, self.devices)
def __init__(self):
"""Constructor"""
super(Status, self).__init__()
self.tranceiver_type = None
self.firmware_version = None
self.devices = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.tranceiver_type = data[5]
self.firmware_version = data[6]
devs = set()
devs.update(_decode_flags(data[7] / 0x80,
'undecoded'))
devs.update(_decode_flags(data[8],
'mertik lightwarerf hideki' +
' lacrosse fs20 proguard'))
devs.update(_decode_flags(data[9],
'x10 arc ac homeeasy ikeakoppla' +
' oregon ati visonic'))
self.devices = sorted(devs)
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
if self.tranceiver_type in self.TYPES:
self.type_string = self.TYPES[self.tranceiver_type]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = 'Unknown'
###############################################################################
# Lighting1 class
###############################################################################
class Lighting1(Packet):
"""
Data class for the Lighting1 packet type
"""
TYPES = {0x00: 'X10 lighting',
0x01: 'ARC',
0x02: 'ELRO AB400D',
0x03: 'Waveman',
0x04: 'Chacon EMW200',
0x05: 'IMPULS',
0x06: 'RisingSun',
0x07: 'Philips SBC',
0x08: 'Energenie'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
ALIAS_TYPES = {'KlikAanKlikUit code wheel': 0x01,
'NEXA code wheel': 0x01,
'CHACON code wheel': 0x01,
'HomeEasy code wheel': 0x01,
'Proove': 0x01,
'DomiaLite': 0x01,
'InterTechno': 0x01,
'AB600': 0x01}
"""
Mapping of subtype aliases to the corresponding subtype value
"""
HOUSECODES = {0x41: 'A', 0x42: 'B', 0x43: 'C', 0x44: 'D',
0x45: 'E', 0x46: 'F', 0x47: 'G', 0x48: 'H',
0x49: 'I', 0x4A: 'J', 0x4B: 'K', 0x4C: 'L',
0x4D: 'M', 0x4E: 'N', 0x4F: 'O', 0x50: 'P'}
"""
Mapping of housecode numeric values to strings, used in id_string
"""
COMMANDS = {0x00: 'Off',
0x01: 'On',
0x02: 'Dim',
0x03: 'Bright',
0x05: 'All/group Off',
0x06: 'All/group On',
0x07: 'Chime',
0xFF: 'Illegal command'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting1 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"rssi={4}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.cmnd_string, self.rssi)
def __init__(self):
"""Constructor"""
super(Lighting1, self).__init__()
self.housecode = None
self.unitcode = None
self.cmnd = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""Parse a string id into individual components"""
try:
self.packettype = 0x10
self.subtype = subtype
hcode = id_string[0:1]
for hcode_num in self.HOUSECODES:
if self.HOUSECODES[hcode_num] == hcode:
self.housecode = hcode_num
self.unitcode = int(id_string[1:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.housecode = data[4]
self.unitcode = data[5]
self.cmnd = data[6]
self.rssi_byte = data[7]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, housecode, unitcode, cmnd):
"""Load data from individual data fields"""
self.packetlength = 7
self.packettype = 0x10
self.subtype = subtype
self.seqnbr = seqnbr
self.housecode = housecode
self.unitcode = unitcode
self.cmnd = cmnd
self.rssi_byte = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr, self.housecode,
self.unitcode, self.cmnd, self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = self.HOUSECODES[self.housecode] + str(self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# Lighting2 class
###############################################################################
class Lighting2(Packet):
"""
Data class for the Lighting2 packet type
"""
TYPES = {0x00: 'AC',
0x01: 'HomeEasy EU',
0x02: 'ANSLUT'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
ALIAS_TYPES = {'KlikAanKlikUit automatic': 0x00,
'NEXA automatic': 0x00,
'CHACON autometic': 0x00,
'HomeEasy UK': 0x00}
"""
Mapping of subtype aliases to the corresponding subtype value
"""
COMMANDS = {0x00: 'Off',
0x01: 'On',
0x02: 'Set level',
0x03: 'Group off',
0x04: 'Group on',
0x05: 'Set group level'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting2 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"level={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.cmnd_string, self.level, self.rssi)
def __repr__(self):
return self.__str__()
def __init__(self):
"""Constructor"""
super(Lighting2, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id4 = None
self.id_combined = None
self.unitcode = None
self.cmnd = None
self.level = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""Parse a string id into individual components"""
try:
self.packettype = 0x11
self.subtype = subtype
self.id_combined = int(id_string[:7], 16)
self.id1 = self.id_combined >> 24
self.id2 = self.id_combined >> 16 & 0xff
self.id3 = self.id_combined >> 8 & 0xff
self.id4 = self.id_combined & 0xff
self.unitcode = int(id_string[8:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.id4 = data[7]
self.id_combined = (self.id1 << 24) + (self.id2 << 16) \
+ (self.id3 << 8) + self.id4
self.unitcode = data[8]
self.cmnd = data[9]
self.level = data[10]
self.rssi_byte = data[11]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd,
level):
"""Load data from individual data fields"""
self.packetlength = 0x0b
self.packettype = 0x11
self.subtype = subtype
self.seqnbr = seqnbr
self.id_combined = id_combined
self.id1 = id_combined >> 24
self.id2 = id_combined >> 16 & 0xff
self.id3 = id_combined >> 8 & 0xff
self.id4 = id_combined & 0xff
self.unitcode = unitcode
self.cmnd = cmnd
self.level = level
self.rssi_byte = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr, self.id1, self.id2,
self.id3, self.id4, self.unitcode, self.cmnd,
self.level, self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:07x}:{1}".format(self.id_combined, self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# Lighting3 class
###############################################################################
class Lighting3(Packet):
"""
Data class for the Lighting3 packet type
"""
TYPES = {0x00: 'Ikea Koppla'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
COMMANDS = {0x00: 'Bright',
0x08: 'Dim',
0x10: 'On',
0x11: 'Level 1',
0x12: 'Level 2',
0x13: 'Level 3',
0x14: 'Level 4',
0x15: 'Level 5',
0x16: 'Level 6',
0x17: 'Level 7',
0x18: 'Level 8',
0x19: 'Level 9',
0x1a: 'Off',
0x1c: 'Program'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting3 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"battery={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.cmnd_string, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Lighting3, self).__init__()
self.system = None
self.channel1 = None
self.channel2 = None
self.channel = None
self.cmnd = None
self.battery = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""Parse a string id into individual components"""
try:
self.packettype = 0x12
self.subtype = subtype
self.system = int(id_string[:1], 16)
self.channel = int(id_string[2:], 16)
self.channel1 = self.channel & 0xff
self.channel2 = self.channel >> 8
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.system = data[4]
self.channel1 = data[5]
self.channel2 = data[6]
self.channel = (self.channel2 << 8) + self.channel1
self.cmnd = data[7]
self.rssi_byte = data[8]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, system, channel, cmnd):
"""Load data from individual data fields"""
self.packetlength = 0x08
self.packettype = 0x12
self.subtype = subtype
self.seqnbr = seqnbr
self.system = system
self.channel = channel
self.channel1 = channel & 0xff
self.channel2 = channel >> 8
self.cmnd = cmnd
self.rssi_byte = 0
self.battery = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr, self.system,
self.channel1, self.channel2, self.cmnd,
self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:1x}:{1:03x}".format(self.system, self.channel)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# Lighting4 class
###############################################################################
class Lighting4(Packet):
"""
Data class for the Lighting4 packet type
"""
TYPES = {0x00: 'PT2262'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
COMMANDS = {0x00: 'Off',
0x01: 'On',
0x02: 'Off',
0x03: 'On',
0x04: 'Off',
0x05: 'On',
0x07: 'On',
0x09: 'On',
0x0c: 'On'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting4 [subtype={0}, seqnbr={1}, cmd={2}, pulse={3}, " +
"rssi={4}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.pulse, self.rssi)
def __init__(self):
"""Constructor"""
super(Lighting4, self).__init__()
self.cmd1 = None
self.cmd2 = None
self.cmd3 = None
self.cmd = None
self.pulsehigh = None
self.pulselow = None
self.pulse = None
self.cmnd_string = ""
def parse_id(self, subtype, id_string):
"""Parse a string id into individual components"""
try:
self.packettype = 0x13
self.subtype = subtype
self.cmd = int(id_string, 16)
self.cmd1 = self.cmd >> 16
self.cmd2 = (self.cmd >> 8) & 0xff
self.cmd3 = self.cmd & 0xff
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.cmd1 = data[4]
self.cmd2 = data[5]
self.cmd3 = data[6]
self.cmd = (self.cmd1 << 16) + (self.cmd2 << 8) + self.cmd3
self.pulsehigh = data[7]
self.pulselow = data[8]
self.pulse = (self.pulsehigh << 8) + self.pulselow
self.rssi_byte = data[9]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, cmd, pulse):
"""Load data from individual data fields"""
self.packetlength = 0x09
self.packettype = 0x13
self.subtype = subtype
self.seqnbr = seqnbr
self.cmd = cmd
self.cmd1 = self.cmd >> 16
self.cmd2 = (self.cmd >> 8) & 0xff
self.cmd3 = self.cmd & 0xff
self.pulse = pulse
self.pulsehigh = self.pulse >> 8
self.pulselow = self.pulse & 0xff
self.rssi_byte = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr,
self.cmd1, self.cmd2, self.cmd3,
self.pulsehigh, self.pulselow, self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:06x}".format(self.cmd)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmd is not None:
if self.cmd2 in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmd2]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmd)
###############################################################################
# Lighting5 class
###############################################################################
class Lighting5(Packet):
"""
Data class for the Lighting5 packet type
"""
TYPES = {0x00: 'LightwaveRF, Siemens',
0x01: 'EMW100 GAO/Everflourish',
0x02: 'BBSB new types',
0x03: 'MDREMOTE LED dimmer',
0x04: 'Conrad RSL2',
0x0f: 'ProMax/IT'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
ALIAS_TYPES = {'LightwaveRF': 0x00,
'Siemens': 0x00,
'EMW100 GAO': 0x01,
'Everflourish': 0x01,
'ProMax': 0x0f,
'IT': 0x0f}
"""
Mapping of subtype aliases to the corresponding subtype value
"""
COMMANDS_00 = {0x00: 'Off',
0x01: 'On',
0x02: 'Group off',
0x03: 'Mood1',
0x04: 'Mood2',
0x05: 'Mood3',
0x06: 'Mood4',
0x07: 'Mood5',
0x0a: 'Unlock',
0x0b: 'Lock',
0x0c: 'All lock',
0x0d: 'Close (inline relay)',
0x0e: 'Stop (inline relay)',
0x0f: 'Open (inline relay)',
0x10: 'Set level'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
COMMANDS_01 = {0x00: 'Off',
0x01: 'On',
0x02: 'Learn'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
COMMANDS_02_04_0F = {0x00: 'Off',
0x01: 'On',
0x02: 'Group off',
0x03: 'Group on'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
COMMANDS_03 = {0x00: 'Power',
0x01: 'Light',
0x02: 'Bright',
0x03: 'Dim',
0x04: '100%',
0x05: '50%',
0x06: '25%',
0x07: 'Mode+',
0x08: 'Speed-',
0x09: 'Speed+',
0x0a: 'Mode-'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
COMMANDS_XX = {0x00: 'Off',
0x01: 'On'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting5 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"level={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.cmnd_string, self.level, self.rssi)
def __init__(self):
"""Constructor"""
super(Lighting5, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id_combined = None
self.unitcode = None
self.cmnd = None
self.level = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""( a string id into individual components"""
try:
self.packettype = 0x14
self.subtype = subtype
self.id_combined = int(id_string[:6], 16)
self.id1 = self.id_combined >> 16
self.id2 = self.id_combined >> 8 & 0xff
self.id3 = self.id_combined & 0xff
self.unitcode = int(id_string[7:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
self.unitcode = data[7]
self.cmnd = data[8]
self.level = data[9]
self.rssi_byte = data[10]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd,
level):
"""Load data from individual data fields"""
self.packetlength = 0x0a
self.packettype = 0x14
self.subtype = subtype
self.seqnbr = seqnbr
self.id_combined = id_combined
self.id1 = id_combined >> 16
self.id2 = id_combined >> 8 & 0xff
self.id3 = id_combined & 0xff
self.unitcode = unitcode
self.cmnd = cmnd
self.level = level
self.rssi_byte = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr, self.id1, self.id2,
self.id3, self.unitcode, self.cmnd,
self.level, self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
# pylint: disable=too-many-branches
self.id_string = "{0:06x}:{1}".format(self.id_combined, self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.subtype == 0x00 and self.cmnd in self.COMMANDS_00:
self.cmnd_string = self.COMMANDS_00[self.cmnd]
elif self.subtype == 0x01 and self.cmnd in self.COMMANDS_01:
self.cmnd_string = self.COMMANDS_01[self.cmnd]
elif self.subtype == 0x02 and self.cmnd in self.COMMANDS_02_04_0F:
self.cmnd_string = self.COMMANDS_02_04_0F[self.cmnd]
elif self.subtype == 0x03 and self.cmnd in self.COMMANDS_03:
self.cmnd_string = self.COMMANDS_03[self.cmnd]
elif self.subtype == 0x04 and self.cmnd in self.COMMANDS_02_04_0F:
self.cmnd_string = self.COMMANDS_02_04_0F[self.cmnd]
elif self.subtype >= 0x05 and self.cmnd in self.COMMANDS_XX:
self.cmnd_string = self.COMMANDS_XX[self.cmnd]
elif self.subtype >= 0x0f and self.cmnd in self.COMMANDS_02_04_0F:
self.cmnd_string = self.COMMANDS_02_04_0F[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# Lighting6 class
###############################################################################
class Lighting6(Packet):
"""
Data class for the Lighting6 packet type
"""
TYPES = {0x00: 'Blyss'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
COMMANDS = {0x00: 'On',
0x01: 'Off',
0x02: 'Group on',
0x03: 'Group off'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __str__(self):
return ("Lighting6 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"cmndseqnbr={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.cmnd_string, self.cmndseqnbr, self.rssi)
def __init__(self):
"""Constructor"""
super(Lighting6, self).__init__()
self.id1 = None
self.id2 = None
self.id_combined = None
self.groupcode = None
self.unitcode = None
self.cmnd = None
self.cmndseqnbr = None
self.rfu = None
self.level = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""Parse a string id into individual components"""
try:
self.packettype = 0x15
self.subtype = subtype
self.id_combined = int(id_string[:4], 16)
self.id1 = self.id_combined >> 8 & 0xff
self.id2 = self.id_combined & 0xff
self.groupcode = ord(id_string[5])
self.unitcode = int(id_string[6:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id_combined = (self.id1 << 8) + self.id2
self.groupcode = data[6]
self.unitcode = data[7]
self.cmnd = data[8]
self.cmndseqnbr = data[9]
self.rfu = data[10]
self.rssi_byte = data[11]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, id_combined, groupcode, unitcode,
cmnd, cmndseqnbr):
"""Load data from individual data fields"""
self.packetlength = 0x0b
self.packettype = 0x15
self.subtype = subtype
self.seqnbr = seqnbr
self.id_combined = id_combined
self.id1 = id_combined >> 8 & 0xff
self.id2 = id_combined & 0xff
self.groupcode = groupcode
self.unitcode = unitcode
self.cmnd = cmnd
self.cmndseqnbr = cmndseqnbr
self.rfu = 0
self.rssi_byte = 0
self.rssi = 0
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr, self.id1, self.id2,
self.groupcode, self.unitcode, self.cmnd,
self.cmndseqnbr, self.rfu, self.rssi_byte])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:04x}:{1}{2}".format(self.id_combined,
chr(self.groupcode),
self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# SensorPacket class
###############################################################################
class SensorPacket(Packet):
"""
Abstract superclass for all sensor related packets
"""
HUMIDITY_TYPES = {0x00: 'dry',
0x01: 'comfort',
0x02: 'normal',
0x03: 'wet',
-1: 'unknown humidity'}
"""
Mapping of humidity types to string
"""
FORECAST_TYPES = {0x00: 'no forecast available',
0x01: 'sunny',
0x02: 'partly cloudy',
0x03: 'cloudy',
0x04: 'rain',
-1: 'unknown forecast'}
"""
Mapping of forecast types to string
"""
def __init__(self):
"""Constructor"""
super(SensorPacket, self).__init__()
###############################################################################
# Temp class
###############################################################################
class Temp(SensorPacket):
"""
Data class for the Temp1 packet type
"""
TYPES = {0x01: 'THR128/138, THC138',
0x02: 'THC238/268,THN132,THWR288,THRN122,THN122,AW129/131',
0x03: 'THWR800',
0x04: 'RTHN318',
0x05: 'La Crosse TX2, TX3, TX4, TX17',
0x06: 'TS15C',
0x07: 'Viking 02811',
0x08: 'La Crosse WS2300',
0x09: 'RUBiCSON',
0x0a: 'TFA 30.3133'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Temp [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
"battery={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.temp, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Temp, self).__init__()
self.id1 = None
self.id2 = None
self.temphigh = None
self.templow = None
self.temp = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.temphigh = data[6]
self.templow = data[7]
self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
if self.temphigh >= 0x80:
self.temp = -self.temp
self.rssi_byte = data[8]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Bbq class
###############################################################################
class Bbq(SensorPacket):
"""
Data class for the Temp1 packet type
"""
TYPES = {0x01: 'BBQ1 - Maverick ET-732'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Bbq [subtype={0}, seqnbr={1}, id={2}, temp1={3}, " +
"temp2={4}, battery={5}, rssi={6}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.temp1, self.temp2, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Bbq, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id_combined = None
self.temp1 = None
self.temp2 = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
self.temp1 = data[7]
self.temp2 = data[9]
self.rssi_byte = data[10]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:06x}:{1}".format(self.id_combined,
self.packettype)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Humid class
###############################################################################
class Humid(SensorPacket):
"""
Data class for the Humid packet type
"""
TYPES = {0x01: 'LaCrosse TX3',
0x02: 'LaCrosse WS2300'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Humid [subtype={0}, seqnbr={1}, id={2}, " +
"humidity={3}, humidity_status={4}, battery={5}, rssi={6}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.humidity, self.humidity_status,
self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Humid, self).__init__()
self.id1 = None
self.id2 = None
self.humidity = None
self.humidity_status = None
self.humidity_status_string = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.humidity = data[6]
self.humidity_status = data[7]
self.rssi_byte = data[8]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.humidity_status in self.HUMIDITY_TYPES:
self.humidity_status_string = \
self.HUMIDITY_TYPES[self.humidity_status]
else:
self.humidity_status_string = self.HUMIDITY_TYPES[-1]
###############################################################################
# TempHumid class
###############################################################################
class TempHumid(SensorPacket):
"""
Data class for the TempHumid packet type
"""
TYPES = {0x01: 'THGN122/123, THGN132, THGR122/228/238/268',
0x02: 'THGR810, THGN800',
0x03: 'RTGR328',
0x04: 'THGR328',
0x05: 'WTGR800',
0x06: 'THGR918/928, THGRN228, THGN500',
0x07: 'TFA TS34C, Cresta',
0x08: 'WT260,WT260H,WT440H,WT450,WT450H',
0x09: 'Viking 02035,02038',
0x0a: 'Rubicson'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("TempHumid [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
"humidity={4}, humidity_status={5}, battery={6}, rssi={7}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.temp, self.humidity, self.humidity_status,
self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(TempHumid, self).__init__()
self.id1 = None
self.id2 = None
self.temphigh = None
self.templow = None
self.temp = None
self.humidity = None
self.humidity_status = None
self.humidity_status_string = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.temphigh = data[6]
self.templow = data[7]
self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
if self.temphigh >= 0x80:
self.temp = -self.temp
self.humidity = data[8]
self.humidity_status = data[9]
self.rssi_byte = data[10]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.humidity_status in self.HUMIDITY_TYPES:
self.humidity_status_string = \
self.HUMIDITY_TYPES[self.humidity_status]
else:
self.humidity_status_string = self.HUMIDITY_TYPES[-1]
###############################################################################
# Baro class
###############################################################################
class Baro(SensorPacket):
"""
Data class for the Baro packet type
"""
TYPES = {}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Baro [subtype={0}, seqnbr={1}, id={2}, baro={3}, " +
"forecast={4}, battery={5}, rssi={6}]") \
.format(self.type_string, self.seqnbr, self.id_string, self.baro,
self.forecast, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Baro, self).__init__()
self.id1 = None
self.id2 = None
self.baro1 = None
self.baro2 = None
self.baro = None
self.forecast = None
self.forecast_string = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.baro1 = data[6]
self.baro2 = data[7]
self.baro = (self.baro1 << 8) + self.baro2
self.forecast = data[8]
self.rssi_byte = data[9]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.forecast in self.FORECAST_TYPES:
self.forecast_string = self.FORECAST_TYPES[self.forecast]
else:
self.forecast_string = self.FORECAST_TYPES[-1]
###############################################################################
# RFXMeter class
###############################################################################
class RfxMeter(SensorPacket):
"""
Data class for the RFXMeter packet type
"""
TYPES = {0x00: 'RFXMeter Data Packet'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("RFXMeter [subtype={0}, seqnbr={1}, id={2}, value3={3}, " +
"value2={4}, value1={5}, value={6}, rssi={7}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.value3, self.value2,
self.value1, self.value, self.rssi)
def __init__(self):
"""Constructor"""
super(RfxMeter, self).__init__()
self.idbyte = None
self.value = None
self.value3 = None
self.value2 = None
self.value1 = None
self.type_string = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.idbyte = data[4]
self.value3 = data[7]
self.value2 = data[8]
self.value1 = data[9]
self.value = (self.value3 << 16) + (self.value2 << 8) + self.value1
self.rssi_byte = data[10]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}".format(self.idbyte)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# TempHumidBaro class
###############################################################################
class TempHumidBaro(SensorPacket):
"""
Data class for the TempHumidBaro packet type
"""
TYPES = {0x01: 'BTHR918',
0x02: 'BTHR918N, BTHR968'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("TempHumidBaro [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
"humidity={4}, humidity_status={5}, baro={6}, forecast={7}, " +
"battery={8}, rssi={9}]") \
.format(self.type_string, self.seqnbr, self.id_string, self.temp,
self.humidity, self.humidity_status, self.baro,
self.forecast, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(TempHumidBaro, self).__init__()
self.id1 = None
self.id2 = None
self.temphigh = None
self.templow = None
self.temp = None
self.humidity = None
self.humidity_status = None
self.humidity_status_string = None
self.baro1 = None
self.baro2 = None
self.baro = None
self.forecast = None
self.forecast_string = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.temphigh = data[6]
self.templow = data[7]
self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
if self.temphigh >= 0x80:
self.temp = -self.temp
self.humidity = data[8]
self.humidity_status = data[9]
self.baro1 = data[10]
self.baro2 = data[11]
self.baro = (self.baro1 << 8) + self.baro2
self.forecast = data[12]
self.rssi_byte = data[13]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.humidity_status in self.HUMIDITY_TYPES:
self.humidity_status_string = \
self.HUMIDITY_TYPES[self.humidity_status]
else:
self.humidity_status_string = self.HUMIDITY_TYPES[-1]
if self.forecast in self.FORECAST_TYPES:
self.forecast_string = self.FORECAST_TYPES[self.forecast]
else:
self.forecast_string = self.FORECAST_TYPES[-1]
###############################################################################
# Rain class
###############################################################################
class Rain(SensorPacket):
"""
Data class for the rain packet type
"""
TYPES = {
0x01: "RGR126/682/918",
0x02: "PCR800",
0x03: "TFA",
0x04: "UPM RG700",
0x05: "WS2300",
0x06: "La Crosse TX5"}
def __str__(self):
return ("Rain [subtype={0}, seqnbr={1}, id={2}, rainrate={3}, " +
"raintotal={4}, battery={5}, rssi={6}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.rainrate, self.raintotal, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Rain, self).__init__()
self.id1 = None
self.id2 = None
self.rainrate1 = None
self.rainrate2 = None
self.rainrate = None
self.raintotal1 = None
self.raintotal2 = None
self.raintotal3 = None
self.raintotal = None
self.battery = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.rainrate1 = data[6]
self.rainrate2 = data[7]
self.rainrate = (self.rainrate1 << 8) + self.rainrate2
if self.subtype == 2:
self.rainrate = float(self.rainrate) / 100
self.raintotal1 = data[8]
self.raintotal2 = data[9]
self.raintotal3 = data[10]
self.raintotal = float((self.raintotal1 << 16) +
(self.raintotal2 << 8) +
self.raintotal3) / 10
self.rssi_byte = data[11]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Wind class
###############################################################################
class Wind(SensorPacket):
"""
Data class for the Wind packet type
"""
TYPES = {0x01: 'WTGR800',
0x02: 'WGR800',
0x03: 'STR918, WGR918, WGR928',
0x04: 'TFA',
0x05: 'UPM WDS500',
0x06: 'WS2300'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Wind [subtype={0}, seqnbr={1}, id={2}, direction={3}, " +
"average_speed={4}, gust={5}, temperature={6}, chill={7}, " +
"battery={8}, rssi={9}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.direction, self.average_speed, self.gust,
self.temperature, self.chill, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Wind, self).__init__()
self.id1 = None
self.id2 = None
self.direction = None
self.average_speed = None
self.gust = None
self.temperature = None
self.chill = None
self.battery = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.direction = data[6] * 256 + data[7]
self.average_speed = data[8] * 256.0 + data[9] / 10.0
self.gust = data[10] * 256.0 + data[11] / 10.0
self.temperature = (-1 * (data[12] >> 7)) * (
(data[12] & 0x7f) * 256.0 + data[13]) / 10.0
self.chill = (-1 * (data[14] >> 7)) * (
(data[14] & 0x7f) * 256.0 + data[15]) / 10.0
if self.subtype == 0x03:
self.battery = data[16] + 1 * 10
else:
self.rssi_byte = data[16]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Energy1 class
###############################################################################
class Energy1(SensorPacket):
"""
Data class for the Energy "ELEC1" packet type
"""
TYPES = {0x01: 'ELEC1'}
def __str__(self):
return ("Energy1 [subtype={0}, seqnbr={1}, id={2}, count={3}, " +
"current_amps1={4}, current_amps2={5}, current_amps3={6}, " +
"battery={7}, rssi={8}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.count, self.currentamps1, self.currentamps2,
self.currentamps3, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Energy1, self).__init__()
self.id1 = None
self.id2 = None
self.count = None
self.currentamps1 = None
self.currentamps2 = None
self.currentamps3 = None
self.battery = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.count = data[6]
self.currentamps1 = float((data[7] << 8) + data[8]) / 10
self.currentamps2 = float((data[9] << 8) + data[10]) / 10
self.currentamps3 = float((data[11] << 8) + data[12]) / 10
self.rssi_byte = data[13]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Energy class
###############################################################################
class Energy(SensorPacket):
"""
Data class for the Energy packet type
"""
TYPES = {0x01: 'CM119/160',
0x02: 'CM180'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Energy [subtype={0}, seqnbr={1}, id={2}, count={3}, " +
"current_watts={4}, total_watts={5}" +
"battery={6}, rssi={7}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.count, self.currentwatt, self.totalwatts,
self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Energy, self).__init__()
self.id1 = None
self.id2 = None
self.count = None
self.currentwatt = None
self.totalwatts = None
self.battery = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.count = data[6]
self.currentwatt = ((data[7] * pow(2, 24)) + (data[8] << 16) +
(data[9] << 8) + data[10])
self.totalwatts = ((data[11] * pow(2, 40)) + (data[12] * pow(2, 32)) +
(data[13] * pow(2, 24)) + (data[14] << 16) +
(data[15] << 8) + data[16]) / 223.666
if self.subtype == 0x03:
self.battery = data[17] + 1 * 10
else:
self.rssi_byte = data[17]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Energy4 class
###############################################################################
class Energy4(SensorPacket):
"""
Data class for the Energy "ELEC4" packet type
"""
TYPES = {0x01: 'CM180i'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Energy4 [subtype={0}, seqnbr={1}, id={2}, count={3}, " +
"current_amps1={4}, current_amps2={5}, current_amps3={6}, " +
"total_watts={7}, battery={8}, rssi={9}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.count, self.currentamps1, self.currentamps2,
self.currentamps3, self.totalwatthours, self.battery,
self.rssi)
def __init__(self):
"""Constructor"""
super(Energy4, self).__init__()
self.id1 = None
self.id2 = None
self.count = None
self.currentamps1 = None
self.currentamps2 = None
self.currentamps3 = None
self.totalwatthours = None
self.battery = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.count = data[6]
self.currentamps1 = float((data[7] << 8) + data[8]) / 10
self.currentamps2 = float((data[9] << 8) + data[10]) / 10
self.currentamps3 = float((data[11] << 8) + data[12]) / 10
self.totalwatthours = ((data[13] * pow(2, 40)) +
(data[14] * pow(2, 32)) +
(data[15] * pow(2, 24)) + (data[16] << 16) +
(data[17] << 8) + data[18]) / 223.666
self.rssi_byte = data[19]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Energy5 class
###############################################################################
class Energy5(SensorPacket):
"""
Data class for the Energy "ELEC5" packet type
"""
TYPES = {0x01: 'ELEC5'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Energy5 [subtype={0}, seqnbr={1}, id={2}, voltage={3}, " +
"current_amps={4}, current_watts={5}, total_watts={6}, " +
"powerfactor={7}, frequency={8}, rssi={9}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.voltage, self.currentamps, self.currentwatt,
self.totalwatthours, self.powerfactor, self.frequency,
self.rssi)
def __init__(self):
"""Constructor"""
super(Energy5, self).__init__()
self.id1 = None
self.id2 = None
self.voltage = None
self.currentamps = None
self.currentwatt = None
self.totalwatthours = None
self.powerfactor = None
self.frequency = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.voltage = data[6]
self.currentamps = float((data[7] << 8) + data[8]) / 100
self.currentwatt = float((data[9] << 8) + data[10]) / 10
self.totalwatthours = float((data[11] << 8) + data[12]) * 10
self.powerfactor = float(data[13]) / 100
self.frequency = float(data[14])
self.rssi_byte = data[15]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Chime class
###############################################################################
class Chime(SensorPacket):
"""
Data class for the Chime packet type
"""
TYPES = {0x00: 'Byron SX',
0x01: 'Byron MP001',
0x02: 'Select Plus',
0x03: 'Select Plus 3',
0x04: 'Envivo'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
def __str__(self):
return ("Chime [subtype={0}, seqnbr={1}, id={2}, sound={3}, " +
"battery={6}, rssi={7}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.sound, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Chime, self).__init__()
self.id1 = None
self.id2 = None
self.sound = None
self.battery = None
self.rssi = None
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.sound = data[6]
self.rssi_byte = data[7]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
###############################################################################
# Security1 class
###############################################################################
class Security1(SensorPacket):
"""
Data class for the Security1 packet type
"""
TYPES = {0x00: 'X10 Security',
0x01: 'X10 Security Motion Detector',
0x02: 'X10 Security Remote',
0x03: 'KD101 Smoke Detector',
0x04: 'Visonic Powercode Door/Window Sensor Primary Contact',
0x05: 'Visonic Powercode Motion Detector',
0x06: 'Visonic Codesecure',
0x07: 'Visonic Powercode Door/Window Sensor Auxilary Contact',
0x08: 'Meiantech',
0x09: 'Alecto SA30 Smoke Detector'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
STATUS = {0x00: 'Normal',
0x01: 'Normal Delayed',
0x02: 'Alarm',
0x03: 'Alarm Delayed',
0x04: 'Motion',
0x05: 'No Motion',
0x06: 'Panic',
0x07: 'End Panic',
0x08: 'IR',
0x09: 'Arm Away',
0x0A: 'Arm Away Delayed',
0x0B: 'Arm Home',
0x0C: 'Arm Home Delayed',
0x0D: 'Disarm',
0x10: 'Light 1 Off',
0x11: 'Light 1 On',
0x12: 'Light 2 Off',
0x13: 'Light 2 On',
0x14: 'Dark Detected',
0x15: 'Light Detected',
0x16: 'Battery low',
0x17: 'Pairing KD101',
0x80: 'Normal Tamper',
0x81: 'Normal Delayed Tamper',
0x82: 'Alarm Tamper',
0x83: 'Alarm Delayed Tamper',
0x84: 'Motion Tamper',
0x85: 'No Motion Tamper'}
"""
Mapping of numeric status values to strings, used in type_string
"""
def __str__(self):
return ("Security1 [subtype={0}, seqnbr={1}, id={2}, status={3}, " +
"battery={4}, rssi={5}]") \
.format(self.type_string, self.seqnbr, self.id_string,
self.security1_status_string, self.battery, self.rssi)
def __init__(self):
"""Constructor"""
super(Security1, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id_combined = None
self.security1_status = None
self.battery = None
self.rssi = None
self.security1_status_string = 'unknown'
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
self.security1_status = data[7]
self.rssi_byte = data[8]
self.battery = self.rssi_byte & 0x0f
self.rssi = self.rssi_byte >> 4
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:06x}:{1}".format(self.id_combined,
self.packettype)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.security1_status in self.STATUS:
self.security1_status_string = self.STATUS[self.security1_status]
###############################################################################
# Rfy class
###############################################################################
class Rfy(Packet):
"""
Data class for the Rfy packet type
"""
TYPES = {0x00: 'Rfy',
0x01: 'Rfy Extended',
0x03: 'ASA'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
COMMANDS = {0x00: 'Stop',
0x01: 'Up',
0x03: 'Down'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __repr__(self):
return self.__str__()
def __str__(self):
return "Rfy [subtype={0}, seqnbr={1}, id={2}, cmnd={3}]" \
.format(
self.subtype,
self.seqnbr,
self.id_string,
self.cmnd_string
)
def __init__(self):
"""Constructor"""
super(Rfy, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id_combined = None
self.unitcode = None
self.cmnd = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""( a string id into individual components"""
try:
self.packettype = 0x1a
self.subtype = subtype
self.id_combined = int(id_string[:6], 16)
self.id1 = self.id_combined >> 16
self.id2 = self.id_combined >> 8 & 0xff
self.id3 = self.id_combined & 0xff
self.unitcode = int(id_string[7:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.unitcode = data[7]
if self.packetlength > 7:
self.cmnd = data[8]
self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
self._set_strings()
def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd):
"""Load data from individual data fields"""
self.packetlength = 0x08
self.packettype = 0x1a
self.subtype = subtype
self.seqnbr = seqnbr
self.id_combined = id_combined
self.id1 = id_combined >> 16
self.id2 = id_combined >> 8 & 0xff
self.id3 = id_combined & 0xff
self.unitcode = unitcode
self.cmnd = cmnd
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr,
self.id1, self.id2, self.id3, self.unitcode,
self.cmnd])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:06x}:{1}".format(self.id_combined,
self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
###############################################################################
# RollerTrol class
###############################################################################
class RollerTrol(Packet):
"""
Data class for the RollerTrol packet type
"""
TYPES = {0x00: 'RollerTrol'}
"""
Mapping of numeric subtype values to strings, used in type_string
"""
COMMANDS = {0x02: 'Stop',
0x00: 'Up',
0x01: 'Down'}
"""
Mapping of command numeric values to strings, used for cmnd_string
"""
def __repr__(self):
return self.__str__()
def __str__(self):
return ("RollerTrol [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
"rssi={4}]") \
.format(
self.subtype,
self.seqnbr,
self.id_string,
self.cmnd_string,
self.rssi
)
def __init__(self):
"""Constructor"""
super(RollerTrol, self).__init__()
self.id1 = None
self.id2 = None
self.id3 = None
self.id_combined = None
self.unitcode = None
self.cmnd = None
self.cmnd_string = None
def parse_id(self, subtype, id_string):
"""( a string id into individual components"""
try:
self.packettype = 0x19
self.subtype = subtype
self.id_combined = int(id_string[:6], 16)
self.id1 = self.id_combined >> 16
self.id2 = self.id_combined >> 8 & 0xff
self.id3 = self.id_combined & 0xff
self.unitcode = int(id_string[7:])
self._set_strings()
except:
raise ValueError("Invalid id_string")
if self.id_string != id_string:
raise ValueError("Invalid id_string")
def load_receive(self, data):
"""Load data from a bytearray"""
self.data = data
self.packetlength = data[0]
self.packettype = data[1]
self.subtype = data[2]
self.seqnbr = data[3]
self.id1 = data[4]
self.id2 = data[5]
self.id3 = data[6]
self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
self.unitcode = data[7]
self.cmnd = data[8]
self.rssi_byte = data[9]
self.rssi = self.rssi_byte >> 4
self._set_strings()
def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd):
"""Load data from individual data fields"""
self.packetlength = 0x09
self.packettype = 0x19
self.subtype = subtype
self.seqnbr = seqnbr
self.id_combined = id_combined
self.id1 = id_combined >> 16
self.id2 = id_combined >> 8 & 0xff
self.id3 = id_combined & 0xff
self.unitcode = unitcode
self.cmnd = cmnd
self.rssi_byte = 0
self.rssi = self.rssi_byte >> 4
self.data = bytearray([self.packetlength, self.packettype,
self.subtype, self.seqnbr,
self.id1, self.id2, self.id3, self.unitcode,
self.cmnd, self.rssi])
self._set_strings()
def _set_strings(self):
"""Translate loaded numeric values into convenience strings"""
self.id_string = "{0:06x}:{1}".format(self.id_combined,
self.unitcode)
if self.subtype in self.TYPES:
self.type_string = self.TYPES[self.subtype]
else:
# Degrade nicely for yet unknown subtypes
self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
self.subtype)
if self.cmnd is not None:
if self.cmnd in self.COMMANDS:
self.cmnd_string = self.COMMANDS[self.cmnd]
else:
self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)