Skip to content

Commit

Permalink
Update type schema (#103)
Browse files Browse the repository at this point in the history
Update type schema and add message tests
  • Loading branch information
MartinHjelmare authored Aug 19, 2017
1 parent ab2149f commit 3736ad4
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 72 deletions.
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[report]
exclude_lines =
# need to re-specify the default pragma
pragma: no cover

def __repr__
raise AssertionError
raise NotImplementedError
5 changes: 5 additions & 0 deletions mysensors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,11 @@ def validate(self, protocol_version):
[SYSTEM_CHILD_ID],
msg='When message type is {}, child_id must be {}'.format(
self.type, SYSTEM_CHILD_ID)))
if (self.type == const.MessageType.internal and
self.sub_type in [
const.Internal.I_ID_REQUEST,
const.Internal.I_ID_RESPONSE]):
valid_child_ids = vol.Coerce(int)
valid_types = vol.All(vol.Coerce(int), vol.In(
[member.value for member in const.VALID_MESSAGE_TYPES],
msg='Not valid message type: {}'.format(self.type)))
Expand Down
3 changes: 2 additions & 1 deletion mysensors/const_14.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ class Stream(IntEnum):
Presentation.S_WEIGHT: [
SetReq.V_WEIGHT, SetReq.V_IMPEDANCE],
Presentation.S_POWER: [SetReq.V_WATT, SetReq.V_KWH],
Presentation.S_HEATER: [SetReq.V_TEMP],
Presentation.S_HEATER: [
SetReq.V_HEATER, SetReq.V_HEATER_SW, SetReq.V_TEMP],
Presentation.S_DISTANCE: [SetReq.V_DISTANCE],
Presentation.S_LIGHT_LEVEL: [SetReq.V_LIGHT_LEVEL],
Presentation.S_ARDUINO_NODE: [],
Expand Down
61 changes: 37 additions & 24 deletions mysensors/const_15.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class SetReq(IntEnum):
V_HVAC_SETPOINT_COOL = 44 # HVAC cold setpoint (Integer between 0-100)
V_HVAC_SETPOINT_HEAT = 45 # HVAC/Heater setpoint (Integer between 0-100)
# Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn")
V_HVAC_FLOW_MODE = 45
V_HVAC_FLOW_MODE = 46


class Internal(IntEnum):
Expand Down Expand Up @@ -219,49 +219,61 @@ class Stream(IntEnum):
Presentation.S_DIMMER: [
SetReq.V_STATUS, SetReq.V_PERCENTAGE, SetReq.V_WATT],
Presentation.S_COVER: [
SetReq.V_UP, SetReq.V_DOWN, SetReq.V_STOP, SetReq.V_DIMMER],
Presentation.S_TEMP: [SetReq.V_TEMP],
Presentation.S_HUM: [SetReq.V_HUM],
SetReq.V_UP, SetReq.V_DOWN, SetReq.V_STOP, SetReq.V_PERCENTAGE],
Presentation.S_TEMP: [SetReq.V_TEMP, SetReq.V_ID, SetReq.V_UNIT_PREFIX],
Presentation.S_HUM: [SetReq.V_HUM, SetReq.V_UNIT_PREFIX],
Presentation.S_BARO: [
SetReq.V_PRESSURE, SetReq.V_FORECAST],
SetReq.V_PRESSURE, SetReq.V_FORECAST, SetReq.V_UNIT_PREFIX],
Presentation.S_WIND: [
SetReq.V_WIND, SetReq.V_GUST, SetReq.V_DIRECTION],
SetReq.V_WIND, SetReq.V_GUST, SetReq.V_DIRECTION,
SetReq.V_UNIT_PREFIX],
Presentation.S_RAIN: [
SetReq.V_RAIN, SetReq.V_RAINRATE],
Presentation.S_UV: [SetReq.V_UV],
SetReq.V_RAIN, SetReq.V_RAINRATE, SetReq.V_UNIT_PREFIX],
Presentation.S_UV: [SetReq.V_UV, SetReq.V_UNIT_PREFIX],
Presentation.S_WEIGHT: [
SetReq.V_WEIGHT, SetReq.V_IMPEDANCE],
Presentation.S_POWER: [SetReq.V_WATT, SetReq.V_KWH],
Presentation.S_HEATER: [SetReq.V_TEMP],
Presentation.S_DISTANCE: [SetReq.V_DISTANCE],
Presentation.S_LIGHT_LEVEL: [SetReq.V_LIGHT_LEVEL],
SetReq.V_WEIGHT, SetReq.V_IMPEDANCE, SetReq.V_UNIT_PREFIX],
Presentation.S_POWER: [SetReq.V_WATT, SetReq.V_KWH, SetReq.V_UNIT_PREFIX],
Presentation.S_HEATER: [
SetReq.V_STATUS, SetReq.V_TEMP, SetReq.V_HVAC_SETPOINT_HEAT,
SetReq.V_HVAC_FLOW_STATE],
Presentation.S_DISTANCE: [SetReq.V_DISTANCE, SetReq.V_UNIT_PREFIX],
Presentation.S_LIGHT_LEVEL: [
SetReq.V_LIGHT_LEVEL, SetReq.V_LEVEL, SetReq.V_UNIT_PREFIX],
Presentation.S_ARDUINO_NODE: [],
Presentation.S_ARDUINO_REPEATER_NODE: [],
Presentation.S_LOCK: [SetReq.V_LOCK_STATUS],
Presentation.S_IR: [SetReq.V_IR_SEND, SetReq.V_IR_RECEIVE],
Presentation.S_WATER: [SetReq.V_FLOW, SetReq.V_VOLUME],
Presentation.S_AIR_QUALITY: [SetReq.V_DUST_LEVEL],
Presentation.S_WATER: [
SetReq.V_FLOW, SetReq.V_VOLUME, SetReq.V_UNIT_PREFIX],
Presentation.S_AIR_QUALITY: [SetReq.V_LEVEL, SetReq.V_UNIT_PREFIX],
Presentation.S_CUSTOM: [
SetReq.V_VAR1, SetReq.V_VAR2, SetReq.V_VAR3, SetReq.V_VAR4,
SetReq.V_VAR5],
Presentation.S_DUST: [SetReq.V_DUST_LEVEL],
SetReq.V_VAR5, SetReq.V_UNIT_PREFIX],
Presentation.S_DUST: [SetReq.V_LEVEL, SetReq.V_UNIT_PREFIX],
Presentation.S_SCENE_CONTROLLER: [SetReq.V_SCENE_ON, SetReq.V_SCENE_OFF],
Presentation.S_RGB_LIGHT: [SetReq.V_RGB, SetReq.V_WATT],
Presentation.S_RGBW_LIGHT: [SetReq.V_RGBW, SetReq.V_WATT],
Presentation.S_COLOR_SENSOR: [SetReq.V_RGB],
Presentation.S_RGB_LIGHT: [
SetReq.V_RGB, SetReq.V_WATT, SetReq.V_PERCENTAGE],
Presentation.S_RGBW_LIGHT: [
SetReq.V_RGBW, SetReq.V_WATT, SetReq.V_PERCENTAGE],
Presentation.S_COLOR_SENSOR: [SetReq.V_RGB, SetReq.V_UNIT_PREFIX],
Presentation.S_HVAC: [
SetReq.V_STATUS, SetReq.V_TEMP, SetReq.V_HVAC_SETPOINT_HEAT,
SetReq.V_HVAC_SETPOINT_COOL, SetReq.V_HVAC_FLOW_STATE,
SetReq.V_HVAC_FLOW_MODE, SetReq.V_HVAC_SPEED],
Presentation.S_MULTIMETER: [
SetReq.V_VOLTAGE, SetReq.V_CURRENT, SetReq.V_IMPEDANCE],
SetReq.V_VOLTAGE, SetReq.V_CURRENT, SetReq.V_IMPEDANCE,
SetReq.V_UNIT_PREFIX],
Presentation.S_SPRINKLER: [SetReq.V_STATUS, SetReq.V_TRIPPED],
Presentation.S_WATER_LEAK: [SetReq.V_TRIPPED, SetReq.V_ARMED],
Presentation.S_SOUND: [SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED],
Presentation.S_SOUND: [
SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED,
SetReq.V_UNIT_PREFIX],
Presentation.S_VIBRATION: [
SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED],
SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED,
SetReq.V_UNIT_PREFIX],
Presentation.S_MOISTURE: [
SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED],
SetReq.V_LEVEL, SetReq.V_TRIPPED, SetReq.V_ARMED,
SetReq.V_UNIT_PREFIX],
}


Expand Down Expand Up @@ -372,6 +384,7 @@ def validate_v_rgbw(value):
SetReq.V_HVAC_FLOW_MODE: str,
}

VALID_INTERNAL = dict(VALID_INTERNAL)
VALID_INTERNAL.update({
Internal.I_REQUEST_SIGNING: str,
Internal.I_GET_NONCE: str,
Expand Down
22 changes: 19 additions & 3 deletions mysensors/const_20.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,13 @@ class Internal(IntEnum):
I_GATEWAY_READY = 14
# Provides signing related preferences (first byte is preference version).
I_SIGNING_PRESENTATION = 15
I_REQUEST_SIGNING = 15 # alias from version 1.5
# Request for a nonce.
I_NONCE_REQUEST = 16
I_GET_NONCE = 16 # alias from version 1.5
# Payload is nonce data.
I_NONCE_RESPONSE = 17
I_GET_NONCE_RESPONSE = 17 # alias from version 1.5
I_HEARTBEAT = 18
I_PRESENTATION = 19
I_DISCOVER = 20
Expand Down Expand Up @@ -268,13 +271,22 @@ class Stream(IntEnum):
member: str for member in list(Presentation)
}

VALID_TYPES = dict(VALID_TYPES)
VALID_TYPES.update({
Presentation.S_POWER: [
SetReq.V_WATT, SetReq.V_KWH, SetReq.V_VAR, SetReq.V_VA,
SetReq.V_POWER_FACTOR, SetReq.V_UNIT_PREFIX],
Presentation.S_IR: [
SetReq.V_IR_SEND, SetReq.V_IR_RECEIVE, SetReq.V_IR_RECORD],
Presentation.S_CUSTOM: [
SetReq.V_VAR1, SetReq.V_VAR2, SetReq.V_VAR3, SetReq.V_VAR4,
SetReq.V_VAR5, SetReq.V_CUSTOM, SetReq.V_UNIT_PREFIX],
Presentation.S_INFO: [SetReq.V_TEXT],
Presentation.S_GAS: [SetReq.V_FLOW, SetReq.V_VOLUME],
Presentation.S_GAS: [SetReq.V_FLOW, SetReq.V_VOLUME, SetReq.V_UNIT_PREFIX],
Presentation.S_GPS: [SetReq.V_POSITION],
Presentation.S_WATER_QUALITY: [
SetReq.V_TEMP, SetReq.V_PH, SetReq.V_ORP, SetReq.V_EC,
SetReq.V_STATUS],
SetReq.V_STATUS, SetReq.V_UNIT_PREFIX],
})


Expand All @@ -291,6 +303,7 @@ def validate_gps(value):
return value


VALID_SETREQ = dict(VALID_SETREQ)
VALID_SETREQ.update({
SetReq.V_TEXT: str,
SetReq.V_CUSTOM: str,
Expand All @@ -306,11 +319,13 @@ def validate_gps(value):
msg='value should be between -1.0 and 1.0'),
})

VALID_INTERNAL = dict(VALID_INTERNAL)
VALID_INTERNAL.update({
Internal.I_HEARTBEAT: '',
Internal.I_PRESENTATION: '',
Internal.I_DISCOVER: '',
Internal.I_DISCOVER_RESPONSE: str,
Internal.I_DISCOVER_RESPONSE: vol.All(
vol.Coerce(int), vol.Range(min=0, max=MAX_NODE_ID), vol.Coerce(str)),
Internal.I_HEARTBEAT_RESPONSE: str,
Internal.I_LOCKED: str,
Internal.I_PING: vol.All(vol.Coerce(int), vol.Coerce(str)),
Expand All @@ -328,6 +343,7 @@ def validate_gps(value):
MessageType.stream: VALID_STREAM,
}

HANDLE_INTERNAL = dict(HANDLE_INTERNAL)
HANDLE_INTERNAL.update({
Internal.I_GATEWAY_READY: {
'log': 'info', 'msg': {
Expand Down
194 changes: 194 additions & 0 deletions tests/test_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""Test mysensors messages."""
from unittest import TestCase

from mysensors import get_const, Message
from mysensors.const_14 import Internal, MessageType

SET_FIXTURES_14 = {
'V_TEMP': '20.0',
'V_HUM': '30',
'V_LIGHT': '1',
'V_DIMMER': '99',
'V_PRESSURE': '101325',
'V_FORECAST': 'stable',
'V_RAIN': '30',
'V_RAINRATE': '2',
'V_WIND': '10',
'V_GUST': '20',
'V_DIRECTION': '270',
'V_UV': '7',
'V_WEIGHT': '10',
'V_DISTANCE': '100',
'V_IMPEDANCE': '10',
'V_ARMED': '1',
'V_TRIPPED': '1',
'V_WATT': '1000',
'V_KWH': '20',
'V_SCENE_ON': 'scene_3',
'V_SCENE_OFF': 'scene_4',
'V_HEATER': 'AutoChangeOver',
'V_HEATER_SW': '1',
'V_LIGHT_LEVEL': '99',
'V_VAR1': 'test1',
'V_VAR2': 'test2',
'V_VAR3': 'test3',
'V_VAR4': 'test4',
'V_VAR5': 'test5',
'V_UP': '',
'V_DOWN': '',
'V_STOP': '',
'V_IR_SEND': 'code',
'V_IR_RECEIVE': 'code',
'V_FLOW': '1.5',
'V_VOLUME': '3.0',
'V_LOCK_STATUS': '1',
'V_DUST_LEVEL': '80',
'V_VOLTAGE': '3.3',
'V_CURRENT': '1.2',
}

SET_FIXTURES_15 = dict(SET_FIXTURES_14)
SET_FIXTURES_15.update({
'V_STATUS': '1',
'V_PERCENTAGE': '99',
'V_HVAC_FLOW_STATE': 'AutoChangeOver',
'V_HVAC_SPEED': 'Auto',
'V_LEVEL': '89',
'V_RGB': 'ffffff',
'V_RGBW': 'ffffffff',
'V_ID': '1',
'V_UNIT_PREFIX': 'mV',
'V_HVAC_SETPOINT_COOL': '24.0',
'V_HVAC_SETPOINT_HEAT': '20.0',
'V_HVAC_FLOW_MODE': 'Auto',
})
SET_FIXTURES_15.pop('V_HEATER')
SET_FIXTURES_15.pop('V_HEATER_SW')

SET_FIXTURES_20 = dict(SET_FIXTURES_15)
SET_FIXTURES_20.update({
'V_TEXT': 'test text',
'V_CUSTOM': 'test custom',
'V_POSITION': '10.0,10.0,10.0',
'V_IR_RECORD': 'code_id_to_store',
'V_PH': '7.0',
'V_ORP': '300',
'V_EC': '5.5',
'V_VAR': '100',
'V_VA': '500',
'V_POWER_FACTOR': '0.9',
})

INTERNAL_FIXTURES_14 = {
'I_BATTERY_LEVEL': '99',
'I_TIME': '1500000000',
'I_VERSION': '1.4.1',
'I_ID_REQUEST': '',
'I_ID_RESPONSE': '254',
'I_INCLUSION_MODE': '1',
'I_CONFIG': 'M',
'I_FIND_PARENT': '',
'I_FIND_PARENT_RESPONSE': '254',
'I_LOG_MESSAGE': 'test log message',
'I_CHILDREN': 'C', # clear routing data for the node
'I_SKETCH_NAME': 'test sketch name',
'I_SKETCH_VERSION': '1.0.0',
'I_REBOOT': '',
'I_GATEWAY_READY': 'Gateway startup complete.',
}

INTERNAL_FIXTURES_15 = dict(INTERNAL_FIXTURES_14)
INTERNAL_FIXTURES_15.update({
'I_REQUEST_SIGNING': 'test signing request',
'I_GET_NONCE': 'test get nonce',
'I_GET_NONCE_RESPONSE': 'test get nonce response',
})

INTERNAL_FIXTURES_20 = dict(INTERNAL_FIXTURES_15)
INTERNAL_FIXTURES_20.update({
'I_HEARTBEAT': '',
'I_PRESENTATION': '',
'I_DISCOVER': '',
'I_DISCOVER_RESPONSE': '254',
'I_HEARTBEAT_RESPONSE': '123465',
'I_LOCKED': 'TMFV',
'I_PING': '123456',
'I_PONG': '123456',
'I_REGISTRATION_REQUEST': '2.0.0',
'I_REGISTRATION_RESPONSE': '1',
'I_DEBUG': 'test debug',
})


class TestMessage(TestCase):
"""Test the Message class and it's encode/decode functions."""

def test_encode(self):
"""Test encode of message."""
msg = Message()
cmd = msg.encode()
self.assertEqual(cmd, '0;0;0;0;0;\n')

msg.node_id = 1
msg.child_id = 255
msg.type = MessageType.internal
msg.sub_type = Internal.I_BATTERY_LEVEL
msg.ack = 0
msg.payload = 57

cmd = msg.encode()
self.assertEqual(cmd, '1;255;3;0;0;57\n')

def test_encode_bad_message(self):
"""Test encode of bad message."""
msg = Message()
msg.sub_type = 'bad'
cmd = msg.encode()
self.assertEqual(cmd, None)

def test_decode(self):
"""Test decode of message."""
msg = Message('1;255;3;0;0;57\n')
self.assertEqual(msg.node_id, 1)
self.assertEqual(msg.child_id, 255)
self.assertEqual(msg.type, MessageType.internal)
self.assertEqual(msg.sub_type, Internal.I_BATTERY_LEVEL)
self.assertEqual(msg.ack, 0)
self.assertEqual(msg.payload, '57')

def test_decode_bad_message(self):
"""Test decode of bad message."""
with self.assertRaises(ValueError):
Message('bad;bad;bad;bad;bad;bad\n')


def test_validate_set():
"""Test Set messages."""
versions = [
('1.4', SET_FIXTURES_14), ('1.5', SET_FIXTURES_15),
('2.0', SET_FIXTURES_20)]
for protocol_version, fixture in versions:
const = get_const(protocol_version)
for name, payload in fixture.items():
sub_type = const.SetReq[name]
msg = Message('1;0;1;0;{};{}\n'.format(sub_type, payload))
valid = msg.validate(protocol_version)
assert valid == {
'node_id': 1, 'child_id': 0, 'type': 1, 'ack': 0,
'sub_type': sub_type, 'payload': payload}


def test_validate_internal():
"""Test Internal messages."""
versions = [
('1.4', INTERNAL_FIXTURES_14), ('1.5', INTERNAL_FIXTURES_15),
('2.0', INTERNAL_FIXTURES_20)]
for protocol_version, fixture in versions:
const = get_const(protocol_version)
for name, payload in fixture.items():
sub_type = const.Internal[name]
msg = Message('1;255;3;0;{};{}\n'.format(sub_type, payload))
valid = msg.validate(protocol_version)
assert valid == {
'node_id': 1, 'child_id': 255, 'type': 3, 'ack': 0,
'sub_type': sub_type, 'payload': payload}
Loading

0 comments on commit 3736ad4

Please sign in to comment.