def mqtt_can_messages_not_supported(): return [ (Mqtt.message('NODE/F0/DISCOVERY/0/QUERY', ''), can.Message(arbitration_id=(Message.DISCOVERY | Node.can_encode(0xF0) | Device.can_encode(9) | Operation.STATE), data=None)), (Mqtt.message('NODE/F0/PWM_INPUT/9/SET', '1013'), can.Message(arbitration_id=(Message.PWM_INPUT | Node.can_encode(0xF0) | Device.can_encode(9) | Operation.STATE), data=pack('<H', 1013))), ]
def mqtt_can_messages(): return [ (Mqtt.message('NODE/69/PING/3/QUERY', '0xDEAD'), can.Message(arbitration_id=(Message.PING | Node.can_encode(0x69) | Device.can_encode(3) | Operation.QUERY), data=pack('<H', 0xDEAD))), (Mqtt.message( 'NODE/89/DATETIME/7/SET', json.dumps({ "year": 2020, "month": 6, "day": 16, "hour": 1, "minute": 23, "second": 45, "dayofweek": 2 })), can.Message(arbitration_id=(Message.DATETIME | Node.can_encode(0x89) | Device.can_encode(7) | Operation.SET), data=pack('<HBBBBBB', 2020, 6, 16, 1, 23, 45, 2))), (Mqtt.message( 'NODE/89/KEY/17/QUERY', json.dumps({ "keycode": 0x45, "action": KeyAction.UP.name, "ar_count": 5, "mp_count": 3 })), can.Message( arbitration_id=(Message.KEY | Node.can_encode(0x89) | Device.can_encode(0x17) | Operation.QUERY), data=pack('<BBBB', 0x45, 2, 5, 3))), (Mqtt.message( 'NODE/D4/DIGITAL_OUTPUT/9/SET', bytes(json.dumps({"cmd": DigitalOutput.ON.name}), 'utf-8')), can.Message( arbitration_id=(Message.DIGITAL_OUTPUT | Node.can_encode(0xD4) | Device.can_encode(9) | Operation.SET), data=pack('<B', DigitalOutput.ON.value))), (Mqtt.message( 'NODE/34/COVER/2/SET', bytes(json.dumps({ "cmd": Cover.OPEN.name, "position": 30 }), 'utf-8')), can.Message(arbitration_id=(Message.COVER | Node.can_encode(0x34) | Device.can_encode(2) | Operation.SET), data=pack('<BB', Cover.OPEN.value, 30))), ]
def can2mqtt_messages_valid(): return [ (Mqtt.message('NODE/69/DUST/1/STATE', bytes(json.dumps({"pm2.5": 5.5, "pm10": 10.1}), 'utf-8')), can.Message(arbitration_id = (Message.DUST | Node.can_encode(0x69) | Device.can_encode(0x01) | Operation.STATE), data=pack('<ff', 5.5, 10.1))), ]
def can2mqtt_messages_invalid(): return [ (Mqtt.message('NODE/69/DUST/0/SET', bytes(json.dumps({"pm2.5": 0, "pm10": 0}), 'utf-8')), can.Message(arbitration_id = (Message.DUST | Node.can_encode(0x69) | Device.can_encode(0x01) | Operation.SET), data=pack('<ff', 0.001, 0.001))), ]
def mqtt_can_messages(): pca963x_single_channel_msgs = [] for op in [Operation.SET]: pca963x_single_channel_msgs.append( (Mqtt.message('NODE/69/PCA963x/3/AMBER0/{}'.format(op.name), bytes(PCA963x.Command.TOGGLE.name, 'utf-8')), can.Message(arbitration_id = (Message.PCA963x | Node.can_encode(0x69) | Device.can_encode(0x03) | op), data=pack('<BB', PCA963x.Channel.AMBER0, PCA963x.Command.TOGGLE))) ) pca963x_single_channel_msgs.append( (Mqtt.message('NODE/69/PCA963x/3/AMBER0/{}'.format(op.name), bytes('123', 'utf-8')), can.Message(arbitration_id = (Message.PCA963x | Node.can_encode(0x69) | Device.can_encode(0x03) | op), data=pack('<BBB', PCA963x.Channel.AMBER0, PCA963x.Command.PWM, 123))) ) return pca963x_single_channel_msgs
def mqtt2can(mqtt_msg): """ Convert MQTT message into CAN frame """ match = re.match(r'^NODE/(?P<node>[0-9a-fA-F]+)/' '(?P<msg>[^/]+)/(?P<dev>[0-9a-fA-F]+)/' '((?P<extra>\S+)/)?(?P<op>[^/]+)$', mqtt_msg.topic) if not match: raise HomeCanMessageError('bad mqtt message') ## base format seems ok, extract parts for further processing node = match.group('node') dev = match.group('dev') try: msg = Message.mqtt_decode(match.group('msg')) except KeyError: raise HomeCanMessageError('wrong mqtt message type') op = Operation[match.group('op')] if op not in [Operation.QUERY, Operation.SET, Operation.RESET]: raise HomeCanBridgingForbidden('{} may not be translated from MQTT into CAN'.format(op.name)) ## FIXME should we translate all messages back and forth? #if op not in [HC_MESSAGE.QUERY, HC_MESSAGE.SET]: # raise HomeCanMessageError('wrong mqtt message type')` ## calculate CAN extended id can_eid = msg | Node.mqtt2can(node) | Device.mqtt2can(dev) | op ## prepare frame data based on msg type if msg == Message.PING: can_frame = _mqtt2can_ping(can_eid, msg, mqtt_msg.payload) elif msg == Message.DATETIME: can_frame = _mqtt2can_datetime(can_eid, msg, mqtt_msg.payload) elif msg == Message.KEY: can_frame = _mqtt2can_key(can_eid, msg, mqtt_msg.payload) elif msg in [Message.TEMPERATURE, Message.RHUMIDITY, Message.ILLUMINANCE, Message.PRESSURE]: can_frame = _mqtt2can_simple_sensor(can_eid, msg, mqtt_msg.payload) elif msg in [Message.DUST]: from can2mqtt.bridge_dust import _mqtt2can_dust can_frame = _mqtt2can_dust(can_eid, msg, mqtt_msg.payload) elif msg == Message.DIGITAL_OUTPUT: can_frame = _mqtt2can_digital_output(can_eid, msg, mqtt_msg.payload) elif msg == Message.PCA963x: from can2mqtt.bridge_pca963x import _mqtt2can_pca963x extra = match.group('extra') can_frame = _mqtt2can_pca963x(can_eid, msg, extra, mqtt_msg.payload) elif msg == Message.COVER: can_frame = _mqtt2can_cover(can_eid, msg, mqtt_msg.payload) else: raise HomeCanMessageNotSupported('mqtt message {} not yet supported'. format(msg.name)) return can_frame
def _can2mqtt_ping(can_frame): """ Parse HomeCan CAN frame containing ping message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg == Message.PING: raw_payload, = unpack('<H', can_frame.data) payload = '0x{:4X}'.format(raw_payload) else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_digital_output(can_frame): """ Parse HomeCan CAN frame containing digital output message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg == Message.DIGITAL_OUTPUT: cmd_raw, = unpack('<B', can_frame.data) cmd = DigitalOutput(cmd_raw).name payload = bytes(json.dumps({"state": cmd}), 'utf-8') else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_cover(can_frame): """ Parse HomeCan CAN frame containing cover message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg == Message.COVER: cmd_raw, position, = unpack('<BB', can_frame.data) cmd = Cover(cmd_raw).name payload = json.dumps({"cmd": cmd, "position": position}) else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_datetime(can_frame): """ Parse HomeCan CAN frame containing date/time message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg == Message.DATETIME: year, month, day, hour, minute, second, weekday = unpack('<HBBBBBB', can_frame.data) payload = json.dumps({"year": year, "month": month, "day": day, "hour": hour, "minute": minute, "second": second, "dayofweek": weekday}) else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_key(can_frame): """ Parse HomeCan CAN frame containing key message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg == Message.KEY: keycode, key_action_raw, ar_count, mp_count = unpack('<BBBB', can_frame.data) key_action = KeyAction(key_action_raw).name payload = json.dumps({"keycode": keycode, "action": key_action, "ar_count": ar_count, "mp_count": mp_count}) else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_simple_sensor_report(can_frame): """ Parse HomeCan CAN frame containing simple sensor message """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) if msg in [Message.TEMPERATURE, Message.RHUMIDITY]: raw_payload, = unpack('<f', can_frame.data) payload = '{:0.2f}'.format(raw_payload) elif msg in [Message.ILLUMINANCE, Message.PRESSURE]: raw_payload, = unpack('<H', can_frame.data) payload = '{:d}'.format(raw_payload) else: raise HomeCanMessageNotSupported('can message {} type not ' 'supported by {}'.format(msg.name, sys._getframe().f_code.co_name)) return Mqtt.message('NODE/{:X}/{}/{:X}/{}'.format( node_id, msg.name, device_id, op.name), payload)
def _can2mqtt_dust(can_frame): """ Parse HomeCan CAN frame containing data from dust sensors data[0-3] - data: pm2.5 data[4-7] - data: pm10 """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) ## op = Operation.can_decode(can_frame.arbitration_id) if op != Operation.STATE: raise HomeCanBridgingForbidden('operation {} not supported for {} ' 'messages'.format(op.name, msg.name)) ## pm2_5, pm10, = unpack('<ff', can_frame.data) ## return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}'.format(node_id, msg.name, device_id, Operation.STATE.name), bytes(json.dumps({ "pm2.5": round(pm2_5, 2), "pm10": round(pm10, 2) }), 'utf-8'))
def _can2mqtt_pca963x(can_frame): """ Parse HomeCan CAN frame containing commands for PCA963x modules data[0] - channel: red, green, blue, amber, rgb, rgba data[1] - command: set, brightness, sleep data[2-x] - color, brightness, time, etc. """ node_id = Node.can_decode(can_frame.arbitration_id) device_id = Device.can_decode(can_frame.arbitration_id) msg = Message.can_decode(can_frame.arbitration_id) op = Operation.can_decode(can_frame.arbitration_id) ## ch = PCA963x.Channel(can_frame.data[0]) cmd = PCA963x.Command(can_frame.data[1]) ## if ch in [ PCA963x.Channel.RED0, PCA963x.Channel.GREEN0, PCA963x.Channel.BLUE0, PCA963x.Channel.AMBER0, PCA963x.Channel.RED1, PCA963x.Channel.GREEN1, PCA963x.Channel.BLUE1, PCA963x.Channel.AMBER1 ]: ## if cmd in [ PCA963x.Command.OFF, PCA963x.Command.ON, PCA963x.Command.TOGGLE ]: payload = cmd.name return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, 'SWITCH', op.name), payload) elif cmd == PCA963x.Command.PWM_VALUE: payload = '{:X}'.format(can_frame.data[2]) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) elif cmd == PCA963x.Command.PWM_BRIGHTNESS: payload = '{:X}'.format(can_frame.data[2]) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) elif cmd == PCA963x.Command.PWM_SLEEP: payload = '{:X}'.format(can_frame.data[2] + (can_frame.data[3] << 8)) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) elif ch in [PCA963x.Channel.RGB0, PCA963x.Channel.RGB1]: ## if cmd == PCA963x.Command.PWM_VALUE: payload = '{:X},{:X},{:X}'.format(can_frame.data[2], can_frame.data[3], can_frame.data[4]) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) elif cmd == PCA963x.Command.PWM_BRIGHTNESS: payload = '{:X}'.format(can_frame.data[2]) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) elif cmd == PCA963x.Command.PWM_SLEEP: payload = '{:X}'.format(can_frame.data[2] + (can_frame.data[3] << 8)) return Mqtt.message( 'NODE/{:X}/{}/{:X}/{}/{}/{}'.format(node_id, msg.name, device_id, ch.name, cmd.name, op.name), payload) raise HomeCanMessageNotSupported('pca963x message configuration not ' 'supported by {}'.format( sys._getframe().f_code.co_name))
def can_message_discovery(): return can.Message( arbitration_id=(Message.DISCOVERY | Node.can_encode(0xF0) | Device.can_encode(9) | Operation.STATE), data=None)
def test_device_mqtt2can(hc_devices): for mqtt, can in hc_devices: assert Device.mqtt2can(mqtt) == can
def can_mqtt_messages(): return [ (Mqtt.message('NODE/69/PING/3/EVENT', '0xDEAD'), can.Message(arbitration_id=(Message.PING | Node.can_encode(0x69) | Device.can_encode(3) | Operation.EVENT), data=pack('<H', 0xDEAD))), (Mqtt.message( 'NODE/89/DATETIME/7/STATE', json.dumps({ "year": 2020, "month": 6, "day": 16, "hour": 1, "minute": 23, "second": 45, "dayofweek": 2 })), can.Message(arbitration_id=(Message.DATETIME | Node.can_encode(0x89) | Device.can_encode(7) | Operation.STATE), data=pack('<HBBBBBB', 2020, 6, 16, 1, 23, 45, 2))), (Mqtt.message( 'NODE/89/KEY/17/EVENT', json.dumps({ "keycode": 0x45, "action": KeyAction.UP.name, "ar_count": 5, "mp_count": 3 })), can.Message( arbitration_id=(Message.KEY | Node.can_encode(0x89) | Device.can_encode(0x17) | Operation.EVENT), data=pack('<BBBB', 0x45, 2, 5, 3))), (Mqtt.message('NODE/69/TEMPERATURE/3/STATE', '12.34'), can.Message( arbitration_id=(Message.TEMPERATURE | Node.can_encode(0x69) | Device.can_encode(3) | Operation.STATE), data=pack('<f', 12.34))), (Mqtt.message('NODE/12/RHUMIDITY/7/STATE', '54.32'), can.Message(arbitration_id=(Message.RHUMIDITY | Node.can_encode(0x12) | Device.can_encode(7) | Operation.STATE), data=pack('<f', 54.32))), (Mqtt.message('NODE/F0/ILLUMINANCE/9/STATE', '127'), can.Message( arbitration_id=(Message.ILLUMINANCE | Node.can_encode(0xF0) | Device.can_encode(9) | Operation.STATE), data=pack('<H', 127))), (Mqtt.message('NODE/F0/PRESSURE/9/STATE', '1013'), can.Message(arbitration_id=(Message.PRESSURE | Node.can_encode(0xF0) | Device.can_encode(9) | Operation.STATE), data=pack('<H', 1013))), (Mqtt.message( 'NODE/D4/DIGITAL_OUTPUT/9/STATE', bytes(json.dumps({"state": DigitalOutput.ON.name}), 'utf-8')), can.Message( arbitration_id=(Message.DIGITAL_OUTPUT | Node.can_encode(0xD4) | Device.can_encode(9) | Operation.STATE), data=pack('<B', DigitalOutput.ON.value))), (Mqtt.message('NODE/34/COVER/2/STATE', json.dumps({ "cmd": Cover.OPEN.name, "position": 30 })), can.Message(arbitration_id=(Message.COVER | Node.can_encode(0x34) | Device.can_encode(2) | Operation.STATE), data=pack('<BB', Cover.OPEN.value, 30))), ]
def test_device_can2mqtt(hc_devices): for mqtt, can in hc_devices: assert Device.can2mqtt(can) == mqtt