async def var_abs( self, var: lcn_defs.Var, value_or_float: Union[float, lcn_defs.VarValue], unit: lcn_defs.VarUnit = lcn_defs.VarUnit.NATIVE, software_serial: Optional[int] = None, ) -> bool: """Send a command to set the absolute value to a variable. :param Var var: Variable :param float value: Absolute value to set :param VarUnit unit: Unit of variable :returns: True if command was sent successfully, False otherwise :rtype: bool """ if isinstance(value_or_float, lcn_defs.VarValue): value = value_or_float else: value = lcn_defs.VarValue.from_var_unit(value_or_float, unit, True) if software_serial is None: await self.serial_known software_serial = self.software_serial if lcn_defs.Var.to_var_id(var) != -1: # Absolute commands for variables 1-12 are not supported if self.addr_id == 4 and self.is_group: # group 4 are status messages return await self.send_command( not self.is_group, PckGenerator.update_status_var(var, value.to_native()), ) # We fake the missing command by using reset and relative # commands. success = await self.send_command( not self.is_group, PckGenerator.var_reset(var, software_serial) ) if not success: return False return await self.send_command( not self.is_group, PckGenerator.var_rel( var, lcn_defs.RelVarRef.CURRENT, value.to_native(), software_serial ), ) return await self.send_command( not self.is_group, PckGenerator.var_abs(var, value.to_native()) )
async def timeout(self, failed: bool = False, block_id: int = 0) -> None: """Is called on OEM text request timeout.""" if not failed: await self.addr_conn.send_command( False, PckGenerator.request_oem_text(block_id)) else: self.oem_text_known.set()
async def send_keys_hit_deferred( self, keys: List[List[bool]], delay_time: int, delay_unit: lcn_defs.TimeUnit) -> List[bool]: """Send a command to send keys deferred. :param list(bool)[4][8] keys: 2d-list with [table_id][key_id] bool values, if command should be sent to specific key :param int delay_time: Delay time :param TimeUnit delay_unit: Unit of time :returns: True if command was sent successfully, False otherwise :rtype: list of bool """ results: List[bool] = [] for table_id, key_states in enumerate(keys): if True in key_states: results.append( await self.send_command( not self.is_group, PckGenerator.send_keys_hit_deferred( table_id, delay_time, delay_unit, key_states), ), ) return results
async def timeout(self, failed: bool = False) -> None: """Is called on dynamic group membership request timeout.""" if not failed: await self.addr_conn.send_command( False, PckGenerator.request_group_membership_dynamic()) else: self.groups_known.set()
async def var_rel( self, var: lcn_defs.Var, value: Union[float, lcn_defs.VarValue], unit: lcn_defs.VarUnit = lcn_defs.VarUnit.NATIVE, value_ref: lcn_defs.RelVarRef = lcn_defs.RelVarRef.CURRENT, software_serial: Optional[int] = None, ) -> bool: """Send a command to change the value of a variable. :param Var var: Variable :param float value: Relative value to add (may also be negative) :param VarUnit unit: Unit of variable :returns: True if command was sent successfully, False otherwise :rtype: bool """ if not isinstance(value, lcn_defs.VarValue): value = lcn_defs.VarValue.from_var_unit(value, unit, True) if software_serial is None: await self.serial_known software_serial = self.software_serial return await self.send_command( not self.is_group, PckGenerator.var_rel(var, value_ref, value.to_native(), software_serial), )
async def request_status_outputs_timeout(self, failed: bool = False, output_port: int = 0) -> None: """Is called on output status request timeout.""" if not failed: await self.addr_conn.send_command( False, PckGenerator.request_output_status(output_port))
async def send_keys( self, keys: List[List[bool]], cmd: lcn_defs.SendKeyCommand ) -> List[bool]: """Send a command to send keys. :param list(bool)[4][8] keys: 2d-list with [table_id][key_id] bool values, if command should be sent to specific key :param SendKeyCommand cmd: command to send for each table :returns: True if command was sent successfully, False otherwise :rtype: list of bool """ coros = [] for table_id, key_states in enumerate(keys): if True in key_states: cmds = [lcn_defs.SendKeyCommand.DONTSEND] * 4 cmds[table_id] = cmd coros.append( self.send_command( not self.is_group, PckGenerator.send_keys(cmds, key_states) ) ) results = await asyncio.gather(*coros) return results
async def timeout(self, failed: bool = False) -> None: """Is called on serial request timeout.""" if not failed: await self.addr_conn.send_command(False, PckGenerator.request_serial()) else: self.serial_known.set()
async def request_status_var_timeout( self, failed: bool = False, var: Optional[lcn_defs.Var] = None) -> None: """Is called on variable status request timeout.""" assert var is not None # Detect if we can send immediately or if we have to wait for a # "typeless" response first has_type_in_response = lcn_defs.Var.has_type_in_response( var, self.addr_conn.software_serial) if not has_type_in_response: # Use the chance to remove a failed "typeless variable" request try: await asyncio.wait_for(self.last_var_lock.acquire(), timeout=3.0) except asyncio.TimeoutError: pass self.last_requested_var_without_type_in_response = var # Send variable request await self.addr_conn.send_command( False, PckGenerator.request_var_status(var, self.addr_conn.software_serial), )
async def on_successful_login(self) -> None: """Is called after connection to LCN bus system is established.""" _LOGGER.debug("%s login successful.", self.connection_id) await self.send_command( PckGenerator.set_operation_mode(self.dim_mode, self.status_mode), to_host=True, ) self.task_registry.create_task(self.ping())
async def store_scene( self, register_id: int, scene_id: int, output_ports: Sequence[lcn_defs.OutputPort] = (), relay_ports: Sequence[lcn_defs.RelayPort] = (), ramp: Optional[int] = None, ) -> bool: """Store states in the given scene. :param int register_id: Register id 0..9 :param int scene_id: Scene id 0..9 :param list(OutputPort) output_ports: Output ports to store as list :param list(RelayPort) relay_ports: Relay ports to store as list :param int ramp: Ramp value :returns: True if command was sent successfully, False otherwise :rtype: bool """ success = await self.send_command( not self.is_group, PckGenerator.change_scene_register(register_id) ) if not success: return False coros = [] if output_ports: coros.append( self.send_command( not self.is_group, PckGenerator.store_scene_output(scene_id, output_ports, ramp), ) ) if relay_ports: coros.append( self.send_command( not self.is_group, PckGenerator.store_scene_relay(scene_id, relay_ports), ) ) results = await asyncio.gather(*coros) return all(results)
async def control_led(self, led: lcn_defs.LedPort, state: lcn_defs.LedStatus) -> bool: """Send a command to control a led. :param LedPort led: Led port :param LedStatus state: Led status """ return await self.send_command( not self.is_group, PckGenerator.control_led(led.value, state))
async def on_auth(self, success: bool) -> None: """Is called after successful authentication.""" if success: _LOGGER.debug("%s authorization successful!", self.connection_id) self.authentication_completed_future.set_result(True) # Try to set the PCHK decimal mode await self.send_command(PckGenerator.set_dec_mode(), to_host=True) else: _LOGGER.debug("%s authorization failed!", self.connection_id) self.authentication_completed_future.set_exception(PchkAuthenticationError)
async def beep(self, sound: lcn_defs.BeepSound, count: int) -> bool: """Send a command to make count number of beep sounds. :param BeepSound sound: Beep sound style :param int count: Number of beeps (1..15) :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command(not self.is_group, PckGenerator.beep(sound, count))
async def toggle_all_outputs(self, ramp: int) -> bool: """Generate a command that toggles all output-ports. Toggle Mode: (on->off, off->on). :param int ramp: Ramp time in milliseconds :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command(not self.is_group, PckGenerator.toggle_all_outputs(ramp))
async def rel_output(self, output_id: int, percent: float) -> bool: """Send a command to change the value of an output-port. :param int output_id: Output id 0..3 :param float percent: Relative brightness in percent -100..100 :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.rel_output(output_id, percent))
async def control_motors_relays( self, states: List[lcn_defs.MotorStateModifier]) -> bool: """Send a command to control motors via relays. :param states: The 4 modifiers for the cover states as a list :type states: list(:class: `~pypck.lcn-defs.MotorStateModifier`) :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.control_motors_relays(states))
async def lock_regulator(self, reg_id: int, state: bool) -> bool: """Send a command to lock a regulator. :param int reg_id: Regulator id :param bool state: Lock state (locked=True, unlocked=False) :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.lock_regulator(reg_id, state))
async def send_command(self, wants_ack: bool, pck: Union[str, bytes]) -> bool: """Send a command to the module represented by this class. :param bool wants_ack: Also send a request for acknowledge. :param str pck: PCK command (without header). """ header = PckGenerator.generate_address_header( self.addr, self.conn.local_seg_id, wants_ack ) if isinstance(pck, str): return await self.conn.send_command(header + pck) return await self.conn.send_command(header.encode() + pck)
async def toggle_output(self, output_id: int, ramp: int) -> bool: """Send a command that toggles a single output-port. Toggle mode: (on->off, off->on). :param int output_id: Output id 0..3 :param int ramp: Ramp time in milliseconds :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.toggle_output(output_id, ramp))
async def activate_scene( self, register_id: int, scene_id: int, output_ports: Sequence[lcn_defs.OutputPort] = (), relay_ports: Sequence[lcn_defs.RelayPort] = (), ramp: Optional[int] = None, ) -> bool: """Activate the stored states for the given scene. :param int register_id: Register id 0..9 :param int scene_id: Scene id 0..9 :param list(OutputPort) output_ports: Output ports to activate as list :param list(RelayPort) relay_ports: Relay ports to activate as list :param int ramp: Ramp value :returns: True if command was sent successfully, False otherwise :rtype: bool """ success = await self.send_command( not self.is_group, PckGenerator.change_scene_register(register_id)) if not success: return False result = True if output_ports: result &= await self.send_command( not self.is_group, PckGenerator.activate_scene_output(scene_id, output_ports, ramp), ) if relay_ports: result &= await self.send_command( not self.is_group, PckGenerator.activate_scene_relay(scene_id, relay_ports), ) return result
async def scan_segment_couplers( self, num_tries: int = 3, timeout_msec: int = 1500 ) -> None: """Scan for segment couplers on the bus. This is a convenience coroutine which handles all the logic when scanning segment couplers on the bus. Because of heavy bus traffic, not all segment couplers might respond to a scan command immediately. The coroutine will make 'num_tries' attempts to send a scan command and waits 'timeout_msec' after the last segment coupler response before proceeding to the next try. :param int num_tries: Scan attempts (default=3) :param int timeout_msec: Timeout in msec for each try (default=3000) """ for _ in range(num_tries): await self.send_command( PckGenerator.generate_address_header( LcnAddr(3, 3, True), self.local_seg_id, False ) + PckGenerator.segment_coupler_scan() ) # Wait loop which is extended on every segment coupler response while True: try: await asyncio.wait_for( self.segment_coupler_response_received.acquire(), timeout_msec / 1000, ) except asyncio.TimeoutError: break # No segment coupler expected (num_tries=0) if len(self.segment_coupler_ids) == 0: _LOGGER.debug("%s: No segment coupler found.", self.connection_id) self.segment_scan_completed_event.set()
async def dim_output(self, output_id: int, percent: float, ramp: int) -> bool: """Send a dim command for a single output-port. :param int output_id: Output id 0..3 :param float percent: Brightness in percent 0..100 :param int ramp: Ramp time in milliseconds :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.dim_output(output_id, percent, ramp) )
async def lock_keys(self, table_id: int, states: List[lcn_defs.KeyLockStateModifier]) -> bool: """Send a command to lock keys. :param int table_id: Table id: 0..3 :param keyLockStateModifier states: The 8 modifiers for the key lock states as a list :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.lock_keys(table_id, states))
async def scan_modules(self, num_tries: int = 3, timeout_msec: int = 3000) -> None: """Scan for modules on the bus. This is a convenience coroutine which handles all the logic when scanning modules on the bus. Because of heavy bus traffic, not all modules might respond to a scan command immediately. The coroutine will make 'num_tries' attempts to send a scan command and waits 'timeout_msec' after the last module response before proceeding to the next try. :param int num_tries: Scan attempts (default=3) :param int timeout_msec: Timeout in msec for each try (default=3000) """ segment_coupler_ids = ( self.segment_coupler_ids if self.segment_coupler_ids else [0] ) for _ in range(num_tries): for segment_id in segment_coupler_ids: if segment_id == self.local_seg_id: segment_id = 0 await self.send_command( PckGenerator.generate_address_header( LcnAddr(segment_id, 3, True), self.local_seg_id, True ) + PckGenerator.empty() ) # Wait loop which is extended on every serial number received while True: try: await asyncio.wait_for( self.module_serial_number_received.acquire(), timeout_msec / 1000, ) except asyncio.TimeoutError: break
async def control_relays_timer( self, time_msec: int, states: List[lcn_defs.RelayStateModifier]) -> bool: """Send a command to control relays. :param int time_msec: Duration of timer in milliseconds :param states: The 8 modifiers for the relay states as alist :type states: list(:class:`~pypck.lcn_defs.RelayStateModifier`) :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.control_relays_timer(time_msec, states))
async def var_reset(self, var: lcn_defs.Var, software_serial: Optional[int] = None) -> bool: """Send a command to reset the variable value. :param Var var: Variable :returns: True if command was sent successfully, False otherwise :rtype: bool """ if software_serial is None: await self.serial_known software_serial = self.software_serial return await self.send_command( not self.is_group, PckGenerator.var_reset(var, software_serial))
async def lock_keys_tab_a_temporary( self, delay_time: int, delay_unit: lcn_defs.TimeUnit, states: List[bool] ) -> bool: """Send a command to lock keys in table A temporary. :param int delay_time: Time to lock keys :param TimeUnit delay_unit: Unit of time :param list(bool) states: The 8 lock states of the keys as list (locked=True, unlocked=False) :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.lock_keys_tab_a_temporary(delay_time, delay_unit, states), )
async def dyn_text(self, row_id: int, text: str) -> bool: """Send dynamic text to a module. :param int row_id: Row id 0..3 :param str text: Text to send (up to 60 bytes) :returns: True if command was sent successfully, False otherwise :rtype: bool """ encoded_text = text.encode(lcn_defs.LCN_ENCODING) parts = [encoded_text[12 * part:12 * part + 12] for part in range(5)] result = True for part_id, part in enumerate(parts): result &= await self.send_command( not self.is_group, PckGenerator.dyn_text_part(row_id, part_id, part), ) return result
async def control_motors_outputs( self, state: lcn_defs.MotorStateModifier, reverse_time: Optional[lcn_defs.MotorReverseTime] = None, ) -> bool: """Send a command to control a motor via output ports 1+2. :param MotorStateModifier state: The modifier for the cover state :param MotorReverseTime reverse_time: Reverse time for modules with FW<190C :type state: :class: `~pypck.lcn-defs.MotorStateModifier` :returns: True if command was sent successfully, False otherwise :rtype: bool """ return await self.send_command( not self.is_group, PckGenerator.control_motors_outputs(state, reverse_time), )