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 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_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): """ 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_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 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, 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 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 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 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 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 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_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 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 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 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 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 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 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_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 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 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_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 test_invalid_ga(self): """test argument validation""" with self.assertRaises(ValueError): PointToMultipointPacket(sals=LightingOnSAL(999)) with self.assertRaises(ValueError): PointToMultipointPacket(sals=LightingOffSAL(-1))