def test_slow_ramp(self): """test very slow ramps""" p1 = PointToMultipointPacket( sals=LightingRampSAL(1, 18 * 60, 255)).encode_packet() p2 = PointToMultipointPacket( sals=LightingRampSAL(1, 17 * 60, 255)).encode_packet() self.assertEqual(p1, p2)
def test_s23_13_1(self): """Example in s23.13.1 of decoding a time.""" # Set network time to 10:43:23 with no DST offset expected_time = time(10, 43, 23) # Slight change from guide: p = self.decode_pm(b'\\05DF000D010A2B1700C2g\r', from_pci=False) self.assertEqual(len(p), 1) s = p[0] self.assertIsInstance(s, ClockUpdateSAL) self.assertTrue(s.is_time) self.assertFalse(s.is_date) self.assertEqual(s.val, expected_time) # Library doesn't handle DST offset, so this flag is dropped. # check that it encodes properly again # fuzzy match to allow packet that has no DST information self.assertIn(p.encode_packet(), [b'05DF000D010A2B1700C2', b'05DF000D010A2B17FFC3']) self.assertEqual(p.confirmation, b'g') # check that the same value would encode p = PointToMultipointPacket(sals=clock_update_sal(expected_time)) self.assertIn(p.encode_packet(), [b'05DF000D010A2B1700C2', b'05DF000D010A2B17FFC3'])
def lighting_group_ramp(self, source_addr, group_addr, duration, level=1.0): """ Ramps (fades) a group address to a specified lighting level. Note: CBus only supports a limited number of fade durations, in decreasing accuracy up to 17 minutes (1020 seconds). Durations longer than this will throw an error. A duration of 0 will ramp "instantly" to the given level. :param source_addr: Source address of the event. :type source_addr: int :param group_id: The group address to ramp. :type group_id: int :param duration: Duration, in seconds, that the ramp should occur over. :type duration: int :param level: An amount between 0.0 and 1.0 indicating the brightness to set. :type level: float :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket(application=APP_LIGHTING) p.source_address = source_addr p.sal.append(LightingRampSAL(p, group_addr, duration, level)) p.checksum = self.checksum return self._send(p)
def test_lighting_encode_decode_client(self): """test of encode then decode, with packets from a client""" orig = PointToMultipointPacket(sals=LightingOnSAL(27)) data = b'\\' + orig.encode_packet() + b'\r' d = self.decode_pm(data, from_pci=False) self.assertEqual(len(orig), len(d)) self.assertIsInstance(d[0], LightingOnSAL) self.assertEqual(orig[0].group_address, d[0].group_address)
def test_lighting_encode_decode(self): """test of encode then decode""" orig = PointToMultipointPacket(sals=LightingOnSAL(27)) orig.source_address = 5 data = orig.encode_packet() + b'\r\n' d = self.decode_pm(data) self.assertEqual(orig.source_address, d.source_address) self.assertEqual(len(orig), len(d)) self.assertIsInstance(d[0], LightingOnSAL) self.assertEqual(orig[0].group_address, d[0].group_address)
def lighting_group_ramp(self, group_addr: int, duration: int, level: int = 255): """ Ramps (fades) a group address to a specified lighting level. Note: CBus only supports a limited number of fade durations, in decreasing accuracy up to 17 minutes (1020 seconds). Durations longer than this will throw an error. A duration of 0 will ramp "instantly" to the given level. :param group_addr: The group address to ramp. :type group_addr: int :param duration: Duration, in seconds, that the ramp should occur over. :type duration: int :param level: A value between 0 and 255 indicating the brightness. :type level: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket( sals=LightingRampSAL(group_addr, duration, level)) return self._send(p)
def lighting_group_terminate_ramp(self, group_addr: Union[int, Iterable[int]]): """ Stops ramping a group address at the current point. :param group_addr: Group address to stop ramping of. :type group_addr: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ if not isinstance(group_addr, Iterable): group_addr = [group_addr] group_addr = [int(g) for g in group_addr] group_addr_count = len(group_addr) if group_addr_count > 9: # maximum 9 group addresses per packet raise ValueError( f'group_addr iterable length is > 9 ({group_addr_count})') p = PointToMultipointPacket( sals=[LightingTerminateRampSAL(ga) for ga in group_addr]) return self._send(p)
def lighting_group_off(self, group_addr: Union[int, Iterable[int]]): """ Turns off the lights for the given group_id. :param group_addr: Group address(es) to turn the lights off for, up to 9 :type group_addr: int, or iterable of ints of length <= 9. :returns: Single-byte string with code for the confirmation event. :rtype: string """ if not isinstance(group_addr, Iterable): group_addr = [group_addr] group_addr = [int(g) for g in group_addr] group_addr_count = len(group_addr) if group_addr_count > 9: # maximum 9 group addresses per packet raise ValueError( f'group_addr iterable length is > 9 ({group_addr_count})') p = PointToMultipointPacket( sals=[LightingOffSAL(ga) for ga in group_addr]) return self._send(p)
def lighting_group_terminate_ramp(self, group_addr): """ Stops ramping a group address at the current point. :param group_addr: Group address to stop ramping of. :type group_addr: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ if not isinstance(group_addr, Iterable): group_addr = [group_addr] group_addr = [int(g) for g in group_addr] if len(group_addr) > 9: # maximum 9 group addresses per packet raise ValueError, 'group_addr iterable length is > 9 (%r)' % len( group_addr) p = PointToMultipointPacket(application=APP_LIGHTING) for ga in group_addr: p.sal.append(LightingTerminateRampSAL(p, ga)) return self._send(p)
def lighting_group_off(self, group_addr): """ Turns off the lights for the given group_id. :param group_addr: Group address(es) to turn the lights off for, up to 9. :type group_addr: int, or iterable of ints of length <= 9. :returns: Single-byte string with code for the confirmation event. :rtype: string """ if not isinstance(group_addr, Iterable): group_addr = [group_addr] group_addr = [int(g) for g in group_addr] if len(group_addr) > 9: # maximum 9 group addresses per packet raise ValueError, 'group_addr iterable length is > 9 (%r)' % len( group_addr) p = PointToMultipointPacket(application=APP_LIGHTING) for ga in group_addr: p.sal.append(LightingOffSAL(p, ga)) return self._send(p)
def lighting_group_terminate_ramp(self, source_addr, group_addr): """ Stops ramping a group address at the current point. :param source_addr: Source address of the event. :type source_addr: int :param group_addr: Group address to stop ramping of. :type group_addr: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket(checksum=self.checksum, sals=LightingTerminateRampSAL(group_addr)) p.source_address = source_addr return self._send(p)
def lighting_encode_decode_client_test(): "self-made tests of encode then decode, with packets from a client." orig = PointToMultipointPacket(application=APP_LIGHTING) orig.sal.append(LightingOnSAL(orig, 27)) data = orig.encode() d, r = decode_packet(data, server_packet=False) assert isinstance(orig, PointToMultipointPacket) assert len(orig.sal) == len(d.sal) assert isinstance(d.sal[0], LightingOnSAL) assert orig.sal[0].group_address == d.sal[0].group_address # ensure there is no remaining data to be parsed assert r == None
def test_invalid_multiple_application_sal(self): """Argument validation - SALs from different applications.""" with self.assertRaisesRegex( ValueError, r'SAL .+ of application ff, .+ has application 38'): PointToMultipointPacket(sals=[ LightingOffSAL(1), StatusRequestSAL(level_request=True, group_address=1, child_application=Application.LIGHTING), ])
def lighting_group_terminate_ramp(self, source_addr, group_addr): """ Stops ramping a group address at the current point. :param source_addr: Source address of the event. :type source_addr: int :param group_addr: Group address to stop ramping of. :type group_addr: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket(application=APP_LIGHTING) p.source_address = source_addr p.sal.append(LightingTerminateRampSAL(p, group_addr)) p.checksum = self.checksum return self._send(p)
def test_datetime_object(self): moment = datetime(2019, 12, 31, 23, 59, 13) p = PointToMultipointPacket(sals=clock_update_sal(moment)) p = self.decode_pm(b'\\' + p.encode_packet() + b'g\r', from_pci=False) d = t = None self.assertEqual(2, len(p)) for sal in p: self.assertIsInstance(sal, ClockUpdateSAL) if sal.is_time and not sal.is_date: self.assertIsNone(t) t = sal.val elif sal.is_date and not sal.is_time: self.assertIsNone(d) d = sal.val self.assertEqual(moment.date(), d) self.assertEqual(moment.time(), t)
def lighting_group_off(self, source_addr, group_addr): """ Turns off the lights for the given group_addr. :param source_addr: Source address of the event. :type source_addr: int :param group_addr: Group address to turn the lights on for. :type group_addr: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket(checksum=self.checksum, sals=LightingOffSAL(group_addr)) p.source_address = source_addr return self._send(p)
def test_invalid_sal(self): p = PointToMultipointPacket() with self.assertRaisesRegex(ValueError, 'application .+ None'): p.encode_packet() p.application = 0x100 with self.assertRaisesRegex(ValueError, 'application .+ in range'): p.encode_packet()
def lighting_group_off(self, source_addr, group_addr): """ Turns off the lights for the given group_id. :param source_addr: Source address of the event. :type source_addr: int :param group_id: Group address to turn the lights on for. :type group_id: int :returns: Single-byte string with code for the confirmation event. :rtype: string """ p = PointToMultipointPacket(application=APP_LIGHTING) p.source_address = source_addr p.sal.append(LightingOffSAL(p, group_addr)) p.checksum = self.checksum return self._send(p)
def test_temperature_encode_decode(self): """self-made tests of encode then decode""" orig = PointToMultipointPacket(sals=[ TemperatureBroadcastSAL(10, 0.5), TemperatureBroadcastSAL(11, 56) ]) orig.source_address = 5 data = orig.encode_packet() + b'\r\n' d = self.decode_pm(data) self.assertIsInstance(orig, PointToMultipointPacket) self.assertEqual(orig.source_address, d.source_address) self.assertEqual(len(orig), len(d)) for x in range(len(d)): self.assertIsInstance(d[x], TemperatureBroadcastSAL) self.assertEqual(orig[x].group_address, d[x].group_address) self.assertEqual(orig[x].temperature, d[x].temperature)
def test_s23_13_2(self): """Example in s23.13.2 of decoding a date.""" # Set network date to 2005-02-25 (Friday) expected_date = date(2005, 2, 25) p = self.decode_pm(b'\\05DF000E0207D502190411g\r', from_pci=False) self.assertEqual(len(p), 1) s = p[0] self.assertIsInstance(s, ClockUpdateSAL) self.assertTrue(s.is_date) self.assertFalse(s.is_time) self.assertEqual(s.val, expected_date) # check that it encodes properly again self.assertEqual(p.encode_packet(), b'05DF000E0207D502190411') self.assertEqual(p.confirmation, b'g') # check that the same value would encode p = PointToMultipointPacket(sals=clock_update_sal(expected_date)) self.assertEqual(p.encode_packet(), b'05DF000E0207D502190411')
def temperature_encode_decode_test(): "self-made tests of encode then decode" orig = PointToMultipointPacket(application=APP_TEMPERATURE) orig.source_address = 5 orig.sal.append(TemperatureBroadcastSAL(orig, 10, 0.5)) orig.sal.append(TemperatureBroadcastSAL(orig, 11, 56)) data = orig.encode() d, r = decode_packet(data) assert isinstance(orig, PointToMultipointPacket) assert orig.source_address == d.source_address assert len(orig.sal) == len(d.sal) for x in range(len(d.sal)): assert isinstance(d.sal[x], TemperatureBroadcastSAL) assert orig.sal[x].group_address == d.sal[x].group_address assert orig.sal[x].temperature == d.sal[x].temperature # ensure there is no remaining data to be parsed assert r == None
def clock_datetime(self, when: Optional[datetime] = None): """ Sends the system's local time to the CBus network. :param when: The time and date to send to the CBus network. Defaults to current local time. :type when: datetime.datetime """ if when is None: when = datetime.now() p = PointToMultipointPacket(sals=clock_update_sal(when)) return self._send(p)
def test_remove_sals(self): # create a packet p = PointToMultipointPacket(sals=LightingOffSAL(1)) self.assertEqual(1, len(p)) p.clear_sal() self.assertEqual(0, len(p)) # We should be able to add a different app p.append_sal(StatusRequestSAL(level_request=True, group_address=1, child_application=Application.LIGHTING)) self.assertEqual(1, len(p)) # Adding another lighting SAL should fail with self.assertRaisesRegex(ValueError, r'has application ff$'): p.append_sal(LightingOffSAL(1)) self.assertEqual(1, len(p))
def clock_datetime(self, when=None): """ Sends the system's local time to the CBus network. :param when: The time and date to send to the CBus network. Defaults to current local time. :type when: datetime.datetime """ if when == None: when = datetime.now() p = PointToMultipointPacket(application=APP_CLOCK) p.sal.append(ClockUpdateSAL(p, CLOCK_DATE, when.date())) p.sal.append(ClockUpdateSAL(p, CLOCK_TIME, when.time())) return self._send(p)
def on_clock_update(self, val): """ Event called when a clock application "update time" is recieved. :param variable: Clock variable to update. :type variable: int :param val: Clock value :type variable: datetime.date or datetime.time """ logger.debug("recv: clock update: %r" % val) # DEBUG: randomly trigger lights p = PointToMultipointPacket(self.checksum, sals=LightingOnSAL(random.randint(1, 100))) p.source_address = random.randint(1, 100) self._send_later(p) p = PointToMultipointPacket(self.checksum, sals=LightingOffSAL(random.randint(1, 100))) p.source_address = random.randint(1, 100) self._send_later(p)
def decode_packet( data: bytes, checksum: bool = True, strict: bool = True, from_pci: bool = True) \ -> Tuple[Union[BasePacket, AnyCAL, None], int]: """ Decodes a single C-Bus Serial Interface packet. The return value is a tuple: 0. The packet that was parsed, or None if there was no packet that could be parsed. 1. The buffer position that we parsed up to. This may be non-zero even if the packet was None (eg: Cancel request). Note: this decoder does not support unaddressed packets (such as Standard Format Status Replies). Note: Direct Command Access causes this method to return AnyCAL instead of a BasePacket. :param data: The data to parse, in encapsulated serial format. :param checksum: If True, requires a checksum for all packets :param strict: If True, returns InvalidPacket whenever checksum is incorrect. Otherwise, only emits a warning. :param from_pci: If True, parses the packet as if it were sent from/by a PCI -- if your software was sent packets by a PCI, this is what you want. If False, this parses the packet as if it were sent to a PCI; parsing messages that software expecting to communicate with a PCI sends. This could be used to build a fake PCI, or analyse the behaviour of other C-Bus software. """ confirmation = None consumed = 0 # Serial Interface User Guide s4.2.7 device_managment_cal = False if data == b'': return None, 0 # There are some special transport-layer flags that need to be handled # before parsing the rest of the message. if from_pci: if data.startswith(b'+'): # + return PowerOnPacket(), consumed + 1 elif data.startswith(b'!'): # ! # buffer is full / invalid checksum, some requests may be dropped. # serial interface guide s4.3.3 p28 return PCIErrorPacket(), consumed + 1 if len(data) < MIN_MESSAGE_SIZE: # Not enough data in the buffer to process yet. return None, 0 if data[0] in CONFIRMATION_CODES: success = indexbytes(data, 1) == 0x2e # . code = data[:1] return ConfirmationPacket(code, success), consumed + 2 end = data.find(END_RESPONSE) else: if data.startswith(b'~'): # Reset # Serial Interface Guide, s4.2.3 return ResetPacket(), consumed + 1 elif data.startswith(b'null'): # Toolkit is buggy, just ignore it. return None, consumed + 4 elif (data.startswith(b'|' + END_COMMAND) or data.startswith(b'||' + END_COMMAND)): # SMART + CONNECT shortcut consumed += data.find(END_COMMAND) + 1 return SmartConnectShortcutPacket(), consumed else: # Check if we need to discard a message # Serial interface guide, s4.2.4 nlp = data.find(END_COMMAND) qp = data.find(b'?') if -1 < qp < nlp: # Discard data before the "?", and continue return None, consumed + qp + 1 end = data.find(END_COMMAND) # Look for ending character(s). If there is none, break out now. if end == -1: return None, consumed # Make it so the end of the buffer is where the end of the command is, and # consume the command up to and including the ending byte(s). data = data[:end] if from_pci: consumed += end + len(END_RESPONSE) else: consumed += end + len(END_COMMAND) if not data: # Empty command, break out! return None, consumed if not from_pci: if data.startswith(b'@'): # Once-off BASIC mode command, Serial Interface Guide, s4.2.7 checksum = False device_managment_cal = True data = data[1:] elif data.startswith(b'\\'): data = data[1:] else: device_managment_cal = True if data[-1] not in HEX_CHARS: # then there is a confirmation code at the end. confirmation = int2byte(indexbytes(data, -1)) if confirmation not in CONFIRMATION_CODES: if strict: return InvalidPacket( payload=data, exception=ValueError( 'Confirmation code is not in range g..z') ), consumed else: warnings.warn('Confirmation code is not in range g..z') # strip confirmation byte data = data[:-1] for c in data: if c not in HEX_CHARS: return InvalidPacket( payload=data, exception=ValueError( f'Non-base16 input: {c:x} in {data}')), consumed # base16 decode data = b16decode(data) # get the checksum, if it's there. if checksum: # check the checksum if not validate_cbus_checksum(data): real_checksum = get_real_cbus_checksum(data) if strict: return InvalidPacket( payload=data, exception=ValueError( f'C-Bus checksum incorrect (expected 0x{real_checksum:x}) ' f'and strict mode is enabled: {data}')), consumed else: warnings.warn( f'C-Bus checksum incorrect (expected 0x{real_checksum:x}) ' f'in data {data}', UserWarning) # strip checksum data = data[:-1] # flags (serial interface guide s3.4) flags = byte2int(data) try: address_type = DestinationAddressType(flags & 0x07) # "reserved", "must be set to 0" # rc = (flags >> 3) & 0x03 dp = (flags & 0x20) == 0x20 # priority class priority_class = PriorityClass((flags >> 6) & 0x03) # increment ourselves along data = data[1:] # handle source address if from_pci: source_addr = byte2int(data) data = data[1:] else: source_addr = None if dp: # device management flag set! # this is used to set parameters of the PCI p = DeviceManagementPacket.decode_packet( data=data, checksum=checksum, priority_class=priority_class) elif device_managment_cal: cal, cal_len = PointToPointPacket.decode_cal(data) return cal, consumed + cal_len elif address_type == DestinationAddressType.POINT_TO_POINT: # decode as point-to-point packet p = PointToPointPacket.decode_packet(data=data, checksum=checksum, priority_class=priority_class) elif address_type == DestinationAddressType.POINT_TO_MULTIPOINT: # decode as point-to-multipoint packet p = PointToMultipointPacket.decode_packet( data=data, checksum=checksum, priority_class=priority_class) elif (address_type == DestinationAddressType.POINT_TO_POINT_TO_MULTIPOINT): # decode as point-to-point-to-multipoint packet # return PointToPointToMultipointPacket.decode_packet(data, checksum, # flags, destination_address_type, rc, dp, priority_class) raise NotImplementedError('Point-to-point-to-multipoint') else: raise NotImplementedError( f'Destination address type = 0x{address_type:x}') if not from_pci: p.confirmation = confirmation p.source_address = None elif source_addr: p.source_address = source_addr p.confirmation = None except Exception as e: p = InvalidPacket(payload=data, exception=e) return p, consumed
def decode_packet(data, checksum=True, strict=True, server_packet=True): """ Decodes a packet from or send to the PCI. Returns a tuple, the packet that was parsed and the remainder that was unparsed (in the case of some special commands. If no packet was able to be parsed, the first element of the tuple will be None. However there may be some circumstances where there is still a remainder to be parsed (cancel request). """ data = data.strip() if data == '': return None, None # packets from clients have some special flags which we need to handle. if server_packet: if data[0] == '+': data = data[1:] return PowerOnPacket(), data elif data[0] == '!': # buffer is full / invalid checksum, some requests may be dropped. # serial interface guide s4.3.3 p28 data = data[1:] return PCIErrorPacket(), data if data[0] in CONFIRMATION_CODES: success = data[1] == '.' code = data[0] data = data[2:] return ConfirmationPacket(code, success), data else: if data == '~~~': # reset return ResetPacket(), None elif data == '|': # smart + connect shortcut return SmartConnectShortcutPacket(), None elif '?' in data: # discard data before the ?, and resubmit for processing. data = data.split('?')[-1] return None, data if data[0] == '\\': data = data[1:] if data[0] == '@': # this causes it to be once-off a "basic" mode command. data = data[1:] checksum = False if data[-1] not in HEX_CHARS: # then there is a confirmation code at the end. confirmation = data[-1] if confirmation not in CONFIRMATION_CODES: if strict: raise ValueError, "Confirmation code is not a lowercase letter in g - z" else: warnings.warn( 'Confirmation code is not a lowercase letter in g - z') data = data[:-1] else: confirmation = None for c in data: if c not in HEX_CHARS: raise ValueError, "Non-base16 input: %r in %r" % (c, data) # get the checksum, if it's there. if checksum: # check the checksum if not validate_cbus_checksum(data): real_checksum = get_real_cbus_checksum(data) if strict: raise ValueError, "C-Bus checksum incorrect (expected %r) and strict mode is enabled: %r." % ( real_checksum, data) else: warnings.warn( "C-Bus checksum incorrect (expected %r) in data %r" % (real_checksum, data), UserWarning) # strip checksum data = data[:-2] # base16 decode data = b16decode(data) # flags (serial interface guide s3.4) flags = ord(data[0]) destination_address_type = flags & 0x07 # "reserved", "must be set to 0" rc = (flags & 0x18) >> 3 dp = (flags & 0x20) == 0x20 # priority class priority_class = (flags & 0xC0) >> 6 # increment ourselves along data = data[1:] # handle source address if server_packet: source_addr = ord(data[0]) data = data[1:] else: source_addr = None if dp: # device management flag set! # this is used to set parameters of the PCI p = DeviceManagementPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) elif destination_address_type == DAT_PP: # decode as point-to-point packet p = PointToPointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) #raise NotImplementedError, 'Point-to-point' elif destination_address_type == DAT_PM: # decode as point-to-multipoint packet p = PointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) elif destination_address_type == DAT_PPM: # decode as point-to-point-to-multipoint packet #return PointToPointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) raise NotImplementedError, 'Point-to-point-tomultipoint' if not server_packet and confirmation: p.confirmation = confirmation p.source_address = None elif source_addr: p.source_address = source_addr p.confirmation = None return p, None
def decode_packet(data, checksum=True, strict=True, server_packet=True): """ Decodes a packet from or send to the PCI. Returns a tuple, the packet that was parsed and the remainder that was unparsed (in the case of some special commands. If no packet was able to be parsed, the first element of the tuple will be None. However there may be some circumstances where there is still a remainder to be parsed (cancel request). """ data = data.strip() if data == '': return None, None # packets from clients have some special flags which we need to handle. if server_packet: if data[0] == '+': data = data[1:] return PowerOnPacket(), data elif data[0] == '!': # buffer is full / invalid checksum, some requests may be dropped. # serial interface guide s4.3.3 p28 data = data[1:] return PCIErrorPacket(), data if data[0] in CONFIRMATION_CODES: success = data[1] == '.' code = data[0] data = data[2:] return ConfirmationPacket(code, success), data else: if data == '~~~': # reset return ResetPacket(), None elif data == '|': # smart + connect shortcut return SmartConnectShortcutPacket(), None elif '?' in data: # discard data before the ?, and resubmit for processing. data = data.split('?')[-1] return None, data if data[0] == '\\': data = data[1:] if data[0] == '@': # this causes it to be once-off a "basic" mode command. data = data[1:] checksum = False if data[-1] not in HEX_CHARS: # then there is a confirmation code at the end. confirmation = data[-1] if confirmation not in CONFIRMATION_CODES: if strict: raise ValueError, "Confirmation code is not a lowercase letter in g - z" else: warnings.warn('Confirmation code is not a lowercase letter in g - z') data = data[:-1] else: confirmation = None for c in data: if c not in HEX_CHARS: raise ValueError, "Non-base16 input: %r in %r" % (c, data) # get the checksum, if it's there. if checksum: # check the checksum if not validate_cbus_checksum(data): real_checksum = get_real_cbus_checksum(data) if strict: raise ValueError, "C-Bus checksum incorrect (expected %r) and strict mode is enabled: %r." % (real_checksum, data) else: warnings.warn("C-Bus checksum incorrect (expected %r) in data %r" % (real_checksum, data), UserWarning) # strip checksum data = data[:-2] # base16 decode data = b16decode(data) # flags (serial interface guide s3.4) flags = ord(data[0]) destination_address_type = flags & 0x07 # "reserved", "must be set to 0" rc = (flags & 0x18) >> 3 dp = (flags & 0x20) == 0x20 # priority class priority_class = (flags & 0xC0) >> 6 # increment ourselves along data = data[1:] # handle source address if server_packet: source_addr = ord(data[0]) data = data[1:] else: source_addr = None if dp: # device management flag set! # this is used to set parameters of the PCI p = DeviceManagementPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) elif destination_address_type == DAT_PP: # decode as point-to-point packet p = PointToPointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) #raise NotImplementedError, 'Point-to-point' elif destination_address_type == DAT_PM: # decode as point-to-multipoint packet p = PointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) elif destination_address_type == DAT_PPM: # decode as point-to-point-to-multipoint packet #return PointToPointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class) raise NotImplementedError, 'Point-to-point-tomultipoint' if not server_packet and confirmation: p.confirmation = confirmation p.source_address = None elif source_addr: p.source_address = source_addr p.confirmation = None return p, None
def test_invalid_ga(self): """test argument validation""" with self.assertRaises(ValueError): PointToMultipointPacket(sals=LightingOnSAL(999)) with self.assertRaises(ValueError): PointToMultipointPacket(sals=LightingOffSAL(-1))