예제 #1
0
 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)
예제 #2
0
    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'])
예제 #3
0
	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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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)
예제 #9
0
    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)
예제 #10
0
    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)
예제 #11
0
    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)
예제 #12
0
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
예제 #13
0
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
예제 #14
0
 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),
         ])
예제 #15
0
	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)
예제 #16
0
    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)
예제 #17
0
    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)
예제 #18
0
    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()
예제 #19
0
	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)
예제 #20
0
    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)
예제 #21
0
    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')
예제 #22
0
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
예제 #23
0
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
예제 #24
0
    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)
예제 #25
0
    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))
예제 #26
0
    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)
예제 #27
0
    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)
예제 #28
0
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
예제 #29
0
파일: packet.py 프로젝트: lowgoz/dockergate
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
예제 #30
0
파일: packet.py 프로젝트: JohnTocher/cbus
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
예제 #31
0
 def test_invalid_ga(self):
     """test argument validation"""
     with self.assertRaises(ValueError):
         PointToMultipointPacket(sals=LightingOnSAL(999))
     with self.assertRaises(ValueError):
         PointToMultipointPacket(sals=LightingOffSAL(-1))