Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

support setting enums by numeric values #19

Merged
merged 4 commits into from
Feb 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion enocean/protocol/EEP.xml
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
</profiles>
<profiles description="HVAC Components" func="0x20">
<profile description="Battery Powered Actuator (BI-DIR)" type="0x01">
<data>
<data direction="1">
<value shortcut="CV" description="Current Value" offset="0" size="8" unit="%">
<range>
<min>0</min>
Expand Down Expand Up @@ -241,6 +241,60 @@
</scale>
</value>
</data>
<data direction="2">
<value shortcut="SP" description="Valve Position" offset="0" size="8" unit="%">
<range>
<min>0</min>
<max>100</max>
</range>
<scale>
<min>0</min>
<max>100</max>
</scale>
</value>
<value shortcut="TMP" description="Temperature from RCU" offset="8" size="8" unit="C">
<range>
<min>0</min>
<max>255</max>
</range>
<scale>
<min>0</min>
<max>40</max>
</scale>
</value>
<enum shortcut="RIN" description="Run init sequence" offset="16" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="LFS" description="Lift set" offset="17" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="VO" description="Valve open / maintenance" offset="18" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="VC" description="Valve closed" offset="19" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="SB" description="Summer bit, Reduction of energy consumption" offset="20" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="SPS" description="Set point selection" offset="21" size="1">
<item description="Valve position" value="0" />
<item description="Temperature set point" value="1" />
</enum>
<enum shortcut="SPI" description="Set point inverse" offset="22" size="1">
<item description="false" value="0" />
<item description="true" value="1" />
</enum>
<enum shortcut="RCU" description="Select function" offset="23" size="1">
<item description="RCU" value="0" />
<item description="service on" value="1" />
</enum>
</data>
</profile>
</profiles>
</telegram>
Expand Down
40 changes: 25 additions & 15 deletions enocean/protocol/eep.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@


class EEP(object):
_profile = None
_data_description = None

def __init__(self):
Expand Down Expand Up @@ -89,6 +88,7 @@ def _set_raw(self, target, raw_value, bitarray):
return bitarray

def _set_value(self, target, value, bitarray):
''' set given numeric value to target field in bitarray '''
# derive raw value
rng = target.find('range')
rng_min = float(rng.find('min').text)
Expand All @@ -101,23 +101,31 @@ def _set_value(self, target, value, bitarray):
return self._set_raw(target, int(raw_value), bitarray)

def _set_enum(self, target, value, bitarray):
if isinstance(value, int):
raise ValueError('No integers here, use the description string provided in EEP.')

''' set given enum value (by string or integer value) to target field in bitarray '''
# derive raw value
value_item = target.find('item', {'description': value})
if value_item is None:
raise ValueError('Enum description for value "%s" not found in EEP.' % (value))
raw_value = int(value_item['value'])
if isinstance(value, int):
# check whether this value exists
if target.find('item', {'value': value}):
# set integer values directly
raw_value = value
else:
raise ValueError('Enum value "%s" not found in EEP.' % (value))
else:
value_item = target.find('item', {'description': value})
if value_item is None:
raise ValueError('Enum description for value "%s" not found in EEP.' % (value))
raw_value = int(value_item['value'])
return self._set_raw(target, raw_value, bitarray)

def _set_boolean(self, target, data, bitarray):
''' set given value to target bit in bitarray '''
bitarray[int(target['offset'])] = data
return bitarray

def find_profile(self, rorg, func, type):
def find_profile(self, rorg, func, type, direction=None):
''' Find profile and data description, matching RORG, FUNC and TYPE '''
if not self.ok:
logging.warning("Not ready.")
return False

rorg = self.soup.find('telegram', {'rorg': self._get_hex(rorg)})
Expand All @@ -135,11 +143,13 @@ def find_profile(self, rorg, func, type):
logger.warn('Cannot find type in EEP!')
return False

# store identified profile
self._profile = profile

# extract data description
self._data_description = self._profile.find('data')
# the direction tag is optional
if direction is None:
self._data_description = profile.find('data')
else:
self._data_description = profile.find('data', {'direction': direction})

if not self._data_description:
logger.warn('Cannot find data description in EEP!')
self._data_description = []
Expand All @@ -151,7 +161,7 @@ def get_values(self, bitarray, status):
if not self.ok:
return [], {}

if not self._profile or not self._data_description:
if not self._data_description:
return [], {}

output = {}
Expand All @@ -171,7 +181,7 @@ def set_values(self, rorg, data, status, properties):
if not self.ok:
return data, status

if not self._profile or not self._data_description:
if not self._data_description:
return data, status

for property, value in properties.items():
Expand Down
18 changes: 9 additions & 9 deletions enocean/protocol/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def parse_msg(buf):
return PARSE_RESULT.OK, buf, p

@staticmethod
def create(packet_type, rorg, func, type,
def create(packet_type, rorg, func, type, direction=None,
destination=[0xFF, 0xFF, 0xFF, 0xFF],
sender=[0xDE, 0xAD, 0xBE, 0xEF],
learn=False, **kwargs):
Expand Down Expand Up @@ -187,7 +187,7 @@ def create(packet_type, rorg, func, type,
# and no security (security not supported as per EnOcean Serial Protocol).
p.optional = [3] + destination + [0xFF] + [0]

p.select_eep(func, type)
p.select_eep(func, type, direction)
p.set_eep(kwargs)
if rorg in [RORG.BS1, RORG.BS4] and not learn:
if rorg == RORG.BS1:
Expand All @@ -200,7 +200,7 @@ def create(packet_type, rorg, func, type,
# For example, stuff like checking RadioPacket.learn should be set.
p = Packet.parse_msg(p.build())[2]
p.rorg = rorg
p.parse_eep(func, type)
p.parse_eep(func, type, direction)
return p

def parse(self):
Expand All @@ -217,18 +217,18 @@ def parse(self):
self.repeater_count = self._from_bitarray(self.bit_status[4:])
return self.parsed

def select_eep(self, func, type):
def select_eep(self, func, type, direction=None):
''' Set EEP based on FUNC and TYPE '''
# set EEP profile
self.rorg_func = func
self.rorg_type = type
return self.eep.find_profile(self.rorg, func, type)
return self.eep.find_profile(self.rorg, func, type, direction)

def parse_eep(self, func=None, type=None):
def parse_eep(self, func=None, type=None, direction=None):
''' Parse EEP based on FUNC and TYPE '''
# set EEP profile, if demanded
if func is not None and type is not None:
self.select_eep(func, type)
self.select_eep(func, type, direction)
# parse data
provides, values = self.eep.get_values(self.bit_data, self.bit_status)
self.parsed.update(values)
Expand Down Expand Up @@ -269,11 +269,11 @@ def __str__(self):
return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str)

@staticmethod
def create(rorg, func, type,
def create(rorg, func, type, direction=None,
destination=[0xFF, 0xFF, 0xFF, 0xFF],
sender=[0xDE, 0xAD, 0xBE, 0xEF],
learn=False, **kwargs):
return Packet.create(PACKET.RADIO, rorg, func, type, destination, sender, learn, **kwargs)
return Packet.create(PACKET.RADIO, rorg, func, type, direction, destination, sender, learn, **kwargs)

def parse(self):
self.destination = self._combine_hex(self.optional[1:5])
Expand Down
15 changes: 15 additions & 0 deletions tests/test_eep.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,18 @@ def test_eep_parsing():
assert p.rorg_type == 0x05
assert p.status == 0x00
assert p.repeater_count == 0


def test_eep_direction():
status, buf, p = Packet.parse_msg(bytearray([
0x55,
0x00, 0x0A, 0x07, 0x01,
0xEB,
0xA5, 0x32, 0x20, 0x89, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00,
0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x43
]))
assert sorted(p.parse_eep(0x20, 0x01, 1)) == ['ACO', 'BCAP', 'CCO', 'CV', 'DWO', 'ENIE', 'ES', 'FTS', 'SO', 'TMP']
assert p.parsed['CV']['value'] == 50
assert sorted(p.parse_eep(0x20, 0x01, 2)) == ['LFS', 'RCU', 'RIN', 'SB', 'SP', 'SPI', 'SPS', 'TMP', 'VC', 'VO']
assert p.parsed['SP']['value'] == 50
35 changes: 29 additions & 6 deletions tests/test_packet_creation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
from __future__ import print_function, unicode_literals, division
from nose.tools import raises

from enocean.protocol.packet import Packet, RadioPacket
from enocean.protocol.constants import PACKET, RORG
Expand Down Expand Up @@ -29,6 +30,14 @@ def test_packet_assembly():
0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x43
])
PACKET_CONTENT_4 = bytearray([
0x55,
0x00, 0x0A, 0x07, 0x01,
0xEB,
0xA5, 0x32, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00,
0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x80
])

# manually assemble packet
p = Packet(PACKET.RADIO)
Expand All @@ -55,7 +64,7 @@ def test_packet_assembly():
assert list(packet_serialized) == list(PACKET_CONTENT_2)

# update data based on EEP
p.select_eep(0x20, 0x01)
p.select_eep(0x20, 0x01, 1)
prop = {
'CV': 50,
'TMP': 21.5,
Expand All @@ -71,18 +80,18 @@ def test_packet_assembly():
assert p.rorg_type == 0x01

# Test the easier method of sending packets.
p = Packet.create(PACKET.RADIO, rorg=RORG.BS4, func=0x20, learn=True, type=0x01, **prop)
p = Packet.create(PACKET.RADIO, rorg=RORG.BS4, func=0x20, learn=True, type=0x01, direction=1, **prop)
packet_serialized = p.build()
assert len(packet_serialized) == len(PACKET_CONTENT_3)
assert list(packet_serialized) == list(PACKET_CONTENT_3)
assert p.rorg_func == 0x20
assert p.rorg_type == 0x01

# Test creating RadioPacket directly.
p = RadioPacket.create(rorg=RORG.BS4, func=0x20, learn=True, type=0x01, **prop)
p = RadioPacket.create(rorg=RORG.BS4, func=0x20, learn=True, type=0x01, direction=2, SP=50)
packet_serialized = p.build()
assert len(packet_serialized) == len(PACKET_CONTENT_3)
assert list(packet_serialized) == list(PACKET_CONTENT_3)
assert len(packet_serialized) == len(PACKET_CONTENT_4)
assert list(packet_serialized) == list(PACKET_CONTENT_4)
assert p.rorg_func == 0x20
assert p.rorg_type == 0x01

Expand Down Expand Up @@ -188,9 +197,10 @@ def test_switch():
0x61
])

# test also enum setting by integer value with EB0
p = RadioPacket.create(rorg=RORG.RPS, func=0x02, type=0x02, sender=[0x00, 0x29, 0x89, 0x79],
SA='No 2nd action',
EBO='pressed',
EBO=1,
R1='Button BI',
T21=True,
NU=True,
Expand All @@ -217,3 +227,16 @@ def test_switch():
packet_serialized = p.build()
assert len(packet_serialized) == len(SWITCH)
assert list(packet_serialized) == list(SWITCH)


@raises(ValueError)
def test_illegal_eep_enum1():
p = RadioPacket.create(rorg=RORG.RPS, func=0x02, type=0x02, sender=[0x00, 0x29, 0x89, 0x79],
EBO='inexisting',
)

@raises(ValueError)
def test_illegal_eep_enum2():
p = RadioPacket.create(rorg=RORG.RPS, func=0x02, type=0x02, sender=[0x00, 0x29, 0x89, 0x79],
EBO=2,
)