def test_actions_press_release(self): orm = InputCoreMapperTest._dto_to_orm( action=240, basic_actions=[2, 1, 236, 0, 2, 2, 236, 255]) self._validate_orm(orm, in_use=True, enable_press_and_release=True, basic_action_press=True, basic_action_release=True) self.assertEqual(BasicAction(action_type=19, action=0, device_nr=1), orm.basic_action_press) self.assertEqual(BasicAction(action_type=19, action=0, device_nr=2), orm.basic_action_release) dto = InputCoreMapperTest._orm_to_dto( input_link={ 'enable_1s_press': False, 'enable_2s_press': False, 'enable_double_press': False }, basic_action_press=BasicAction(action_type=19, action=0, device_nr=1), basic_action_release=BasicAction(action_type=19, action=0, device_nr=2)) self.assertEqual(240, dto.action) self.assertEqual([2, 1, 236, 0, 2, 2, 236, 255], dto.basic_actions)
def save_global_led_feedback_configuration( global_feedbacks, activate=True): # type: (List[GlobalFeedbackDTO], bool) -> None # Important assumption in the below code to make this strategy solvable: If any of the 4 feedbacks is # given, they all are assumed to be given. global_configuration = GlobalConfiguration() if global_configuration.groupaction_any_output_changed > 255: group_action = GroupActionController.get_unused_group_action() if group_action is None: raise ValueError( 'No GroupAction available to store LED feedback configuration' ) else: group_action = GroupActionController.load_group_action( global_configuration.groupaction_any_output_changed) for global_feedback_dto in global_feedbacks: holds_data, holds_configuration = CANFeedbackController._available_led_data( global_feedback_dto) if not holds_data: continue # No change required # First, delete everything related to this global_feedback_dto if global_feedback_dto.id in [0, 16]: action = 73 extra_parametery_msb = 0 if global_feedback_dto.id == 16 else 1 else: action = 71 if 0 < global_feedback_dto.id < 16 else 70 extra_parametery_msb = global_feedback_dto.id - ( 1 if global_feedback_dto.id < 16 else 17) for basic_action in group_action.actions[:]: if basic_action.action_type == 20 and basic_action.action == action: if basic_action.extra_parameter_msb == extra_parametery_msb: group_action.actions.remove(basic_action) # Then, add the relevant entries back again if holds_configuration: for field in CANFeedbackController.FIELDS: feedback_led_dto = getattr(global_feedback_dto, field) if field in global_feedback_dto.loaded_fields and feedback_led_dto.id is not None: basic_action = BasicAction( action_type=20, action=action, device_nr=feedback_led_dto.id) basic_action.extra_parameter_msb = extra_parametery_msb basic_action.extra_parameter_lsb = CANFeedbackController._blinking_string_to_byte( feedback_led_dto.function) group_action.actions.append(basic_action) # Save changes if group_action.actions: if group_action.name == '': group_action.name = 'Global feedback' global_configuration.groupaction_any_output_changed = group_action.id else: group_action.name = '' global_configuration.groupaction_any_output_changed = CANFeedbackController.WORD_MAX global_configuration.save(activate=False) GroupActionController.save_group_action(group_action, ['name', 'actions'], activate=False) if activate: MemoryActivator.activate()
def classic_actions_to_core_input_configuration(action, basic_actions): # type: (Optional[int], List[int]) -> Dict[str, Any] data = { 'input_link': { 'output_id': 1023, 'dimming_up': True, 'enable_press_and_release': True, 'enable_1s_press': True, 'enable_2s_press': True, 'not_used': True, 'enable_double_press': True } } # type: Dict[str, Any] if action is None or action == 255: return data data['input_link'].update({ 'dimming_up': False, 'enable_press_and_release': False, 'enable_1s_press': False, 'enable_2s_press': False, 'not_used': False, 'enable_double_press': False }) if action < 240: # 240 means "execute the list of basic actions" data['input_link']['output_id'] = action return data action_types = set(basic_actions[i] for i in range(0, len(basic_actions), 2)) if 207 in action_types: if len(basic_actions) != 2: raise ValueError( 'Timing settings cannot be combined with other actions') data['input_link']['enable_2s_press'] = True data['basic_action_2s_press'] = BasicAction( action_type=19, action=0, device_nr=basic_actions[1]) return data if action_types - {2, 236}: raise ValueError('Only executing GroupActions is supported') release_data = False release_action = None # type: Optional[int] press_action = None # type: Optional[int] for i in range(0, len(basic_actions), 2): action_type = basic_actions[i] action_number = basic_actions[i + 1] if action_type == 236: release_data = action_number == 0 elif release_data: release_action = action_number else: press_action = action_number if press_action is not None: data['input_link']['enable_press_and_release'] = True data['basic_action_press'] = BasicAction(action_type=19, action=0, device_nr=press_action) if release_action is not None: data['input_link']['enable_press_and_release'] = True data['basic_action_release'] = BasicAction( action_type=19, action=0, device_nr=release_action) return data
def save_output_led_feedback_configuration(output, output_dto, activate=True): # type: (OutputConfiguration, OutputDTO, bool) -> None holds_data, holds_configuration = CANFeedbackController._available_led_data( output_dto) if not holds_data: return # No change required group_action_id = output.output_groupaction_follow group_action = None # type: Optional[GroupAction] # Needed for keeping Mypy happy... if group_action_id <= 255: group_action = GroupActionController.load_group_action( group_action_id) for basic_action in group_action.actions[:]: if basic_action.action_type == 20 and basic_action.action in [ 50, 51 ]: group_action.actions.remove(basic_action) else: if not holds_configuration: return # No GroupAction configured, and no configurion. Nothing to do. group_action = GroupActionController.get_unused_group_action() if group_action is None: raise ValueError( 'No GroupAction available to store LED feedback configuration' ) for field in CANFeedbackController.FIELDS: feedback_led_dto = getattr(output_dto, field) if field in output_dto.loaded_fields and feedback_led_dto.id is not None: action = CANFeedbackController._inverted_string_to_action( feedback_led_dto.function) basic_action = BasicAction(action_type=20, action=action, device_nr=feedback_led_dto.id) basic_action.extra_parameter_lsb = CANFeedbackController._blinking_string_to_byte( feedback_led_dto.function) basic_action.extra_parameter_msb = CANFeedbackController._brightness_string_to_byte( feedback_led_dto.function) group_action.actions.append(basic_action) if group_action.actions: if group_action.name == '': group_action.name = 'Output {0}'.format(output.id) output.output_groupaction_follow = group_action.id else: group_action.name = '' output.output_groupaction_follow = CANFeedbackController.WORD_MAX output.save(activate=False) GroupActionController.save_group_action(group_action, ['name', 'actions'], activate=False) if activate: MemoryActivator.activate()
def test_mappings(self): normal_scenarios = [ ([160, 5], [(0, 0, 5)]), # Turn output 5 off ([161, 5], [(0, 1, 5)]), # Turn output 5 on ([162, 5], [(0, 16, 5)]), # Toggle output 5 ([240, 0, 243, 5, 240, 255], [(100, 0), (100, 10, 5), (100, 255)]), # If output 5 is on ([240, 0, 247, 5, 249, 10, 240, 255], [(100, 0), (100, 19, 5, 10), (100, 255)]), # If temp sensor 5 > 10 ([240, 0, 247, 37, 248, 10, 240, 255], [(100, 0), (100, 23, 5, 10), (100, 255)]), # If hum sensor 5 == 10 ([240, 0, 247, 69, 250, 10, 240, 255], [(100, 0), (100, 27, 5, 10), (100, 255)]), # If brightness sensor 5 < 10 ([240, 0, 247, 228, 249, 10, 240, 255], [(100, 0), (100, 40, 10), (100, 255)]), # If hour > 10 ([240, 0, 247, 229, 248, 10, 240, 255], [(100, 0), (100, 44, 10), (100, 255)]), # If minutes == 10 ([240, 0, 247, 230, 250, 3, 240, 255], [(100, 0), (100, 48, 3), (100, 255)]), # If day < 3 ([171, 5], [(251, 0, 5, 0)]), # Turn off all lights on floor 5 ([172, 5], [(251, 0, 5, 1)]), # Turn on all lights on floor 5 ([173, 5], [(251, 0, 5, 2)]), # Toggle all lights on floor 5 ([171, 255], [(251, 0, 65535, 0) ]), # Turn off all lights (on all floors) ([172, 255], [(251, 0, 65535, 1) ]), # Turn on all lights (on all floors) ([173, 255], [(251, 0, 65535, 2)]), # Toggle all lights (on all floors) ([116, 5], [(1, 250, 5, 1)]), # Disable input 5 ([117, 5], [(1, 250, 5, 0)]) ] # Enable input 5 incompatible_scenarios = [ ([255, 255], [], (True, [])), # Classic actions that are not supported on the Core (None, [(255, 255)], (False, [])) ] # Core actions that are not supported on the Classic for scenario in normal_scenarios + incompatible_scenarios: classic_scenario = scenario[0] core_scenario = scenario[1] if len(scenario) == 3: complete_expected, classic_expected = scenario[2] else: complete_expected = True classic_expected = classic_scenario core_expected = [BasicAction(*entry) for entry in core_scenario] if classic_scenario is not None: core_result = GroupActionMapper.classic_actions_to_core_actions( classic_scenario) self.assertEqual(core_expected, core_result) complete_result, classic_result = GroupActionMapper.core_actions_to_classic_actions( core_expected) self.assertEqual(classic_expected, classic_result) self.assertEqual(complete_expected, complete_result)
def _set_led(self, led, on, mode): # type: (str, bool, str) -> None if led not in FrontpanelCoreController.LED_TO_BA: return action = FrontpanelCoreController.LED_TO_BA[led] if mode not in FrontpanelCoreController.BLINKING_MAP: return state = self._led_drive_states.get(led) if state != (on, mode): extra_parameter = FrontpanelCoreController.BLINKING_MAP[mode] self._master_communicator.do_basic_action( BasicAction(action_type=210, action=action, device_nr=1 if on else 0, extra_parameter=extra_parameter)) self._led_drive_states[led] = on, mode
def activate(self): # type: () -> None with self._activate_lock: logger.info('MEMORY: Writing') write_cache, write_lock = self._get_write_cache() with write_lock: data_written = self._store_data(write_cache) self._clear_write_cache() if data_written: logger.info('MEMORY: Activating') self._self_activated = True self._activation_event.clear() self._core_communicator.do_basic_action( BasicAction(action_type=200, action=1), timeout=MemoryFile.ACTIVATE_TIMEOUT) self._activation_event.wait(timeout=60.0) else: logger.info('MEMORY: No activation requred')
def data(self): if self.type == Event.Types.OUTPUT: data = {'output': self._device_nr} if self._action in [0, 1]: timer_type = self._data[1] # type: int timer_value = self._word_decode(self._data[2:]) or 0 # type: int timer = Timer.event_timer_type_to_seconds(timer_type, timer_value) data.update({'type': Event.OutputEventTypes.STATUS, 'status': self._action == 1, 'dimmer_value': self._data[0], 'timer': timer}) else: data.update({'type': Event.OutputEventTypes.LOCKING, 'locked': self._data[0] == 1}) return data if self.type == Event.Types.INPUT: return {'input': self._device_nr, 'status': self._action == 1} if self.type == Event.Types.SENSOR: sensor_type = Event.SensorType.UNKNOWN sensor_value = None if self._action == 0: sensor_type = Event.SensorType.TEMPERATURE sensor_value = Temperature.system_value_to_temperature(self._data[1]) elif self._action == 1: sensor_type = Event.SensorType.HUMIDITY sensor_value = Humidity.system_value_to_humidity(self._data[1]) elif self._action == 2: sensor_type = Event.SensorType.BRIGHTNESS sensor_value = self._word_decode(self._data[0:2]) return {'sensor': self._device_nr, 'type': sensor_type, 'value': sensor_value} if self.type == Event.Types.THERMOSTAT: origin_map = {0: Event.ThermostatOrigins.SLAVE, 1: Event.ThermostatOrigins.MASTER} return {'origin': origin_map.get(self._action, Event.ThermostatOrigins.UNKNOWN), 'thermostat': self._device_nr, 'mode': self._data[0], 'setpoint': self._data[1]} if self.type == Event.Types.BUTTON_PRESS: return {'button': self._device_nr, 'state': self._data[0]} if self.type == Event.Types.LED_BLINK: word_25 = self._device_nr word_50 = self._word_decode(self._data[0:2]) word_75 = self._word_decode(self._data[2:4]) leds = {} for i in range(16): if word_25 & (1 << i): leds[i] = Event.LedFrequencies.BLINKING_25 elif word_50 & (1 << i): leds[i] = Event.LedFrequencies.BLINKING_50 elif word_75 & (1 << i): leds[i] = Event.LedFrequencies.BLINKING_75 else: leds[i] = Event.LedFrequencies.SOLID return {'chip': self._device_nr, 'leds': leds} if self.type == Event.Types.LED_ON: word_on = self._word_decode(self._data[0:2]) leds = {} for i in range(16): leds[i] = Event.LedStates.ON if word_on & (1 << i) else Event.LedStates.OFF return {'chip': self._device_nr, 'leds': leds} if self.type == Event.Types.POWER: return {'bus': Event.Bus.RS485 if self._device_nr == 0 else Event.Bus.CAN, 'power': self._data[0 > 1]} if self.type == Event.Types.SYSTEM: type_map = {0: Event.SystemEventTypes.EEPROM_ACTIVATE, 1: Event.SystemEventTypes.ONBOARD_TEMP_CHANGED} event_type = type_map.get(self._action, Event.SystemEventTypes.UNKNOWN) event_data = {'type': event_type} if event_type == Event.SystemEventTypes.ONBOARD_TEMP_CHANGED: event_data['temperature'] = self._data[0] return event_data if self.type == Event.Types.UCAN: event_data = {'address': self._address_helper.decode(bytearray([self._device_nr & 0xFF]) + self._data[0:2])} if self._data[2] == 0 and self._data[3] == 0: event_data['type'] = Event.UCANEventTypes.POWER_OUT_ERROR elif self._data[2] == 0 and self._data[3] == 1: event_data['type'] = Event.UCANEventTypes.POWER_OUT_RESTORED elif self._data[2] == 0 and self._data[3] == 2: event_data['type'] = Event.UCANEventTypes.POWER_OUT_OFF elif self._data[2] == 2 and self._data[3] == 3: event_data['type'] = Event.UCANEventTypes.POWER_OUT_ON elif self._data[2] == 1: event_data.update({'type': Event.UCANEventTypes.I2C_ERROR, 'i2c_address': self._data[3]}) else: event_data.update({'type': Event.UCANEventTypes.UNKNOWN, 'data': self._data[2:4]}) return event_data if self.type == Event.Types.EXECUTED_BA: return {'basic_action': BasicAction(action_type=self._data[0], action=self._data[1], device_nr=self._device_nr, extra_parameter=self._word_decode(self._data[2:4]))} if self.type == Event.Types.EXECUTE_GATEWAY_API: return {'action': self._data[3], 'device_nr': self._device_nr, 'extra_parameter': self._word_decode(self._data[0:2])} return None
def test_individual_feedback_leds(self): output = OutputConfiguration.deserialize({'id': 0}) # Setup basic LED feedback output_dto = OutputDTO( id=0, can_led_1=FeedbackLedDTO( id=5, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_3=FeedbackLedDTO( id=7, function=FeedbackLedDTO.Functions.MB_B8_INVERTED)) # Save led feedback config CANFeedbackController.save_output_led_feedback_configuration( output, output_dto) # Validate correct data in created GA self.assertEqual(0, output.output_groupaction_follow) group_action = GroupActionController.load_group_action(0) self.assertEqual([ BasicAction( action_type=20, action=50, device_nr=5, extra_parameter=65280), BasicAction( action_type=20, action=51, device_nr=7, extra_parameter=32514) ], group_action.actions) self.assertEqual('Output 0', group_action.name) # Alter GA extra_ba = BasicAction(action_type=123, action=123) # Some random BA group_action.actions.append(extra_ba) group_action.name = 'Foobar' GroupActionController.save_group_action(group_action, ['name', 'actions']) # Validate loading data output_dto = OutputDTO(id=0) CANFeedbackController.load_output_led_feedback_configuration( output, output_dto) self.assertEqual( FeedbackLedDTO(id=5, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), output_dto.can_led_1) self.assertEqual( FeedbackLedDTO(id=7, function=FeedbackLedDTO.Functions.MB_B8_INVERTED), output_dto.can_led_2) # Moved to 2 # Change led feedback config output_dto.can_led_2.function = FeedbackLedDTO.Functions.ON_B8_INVERTED CANFeedbackController.save_output_led_feedback_configuration( output, output_dto) # Validate stored led feedback data output_dto = OutputDTO(id=0) CANFeedbackController.load_output_led_feedback_configuration( output, output_dto) self.assertEqual( FeedbackLedDTO(id=5, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), output_dto.can_led_1) self.assertEqual( FeedbackLedDTO(id=7, function=FeedbackLedDTO.Functions.ON_B8_INVERTED), output_dto.can_led_2) # Validate GA changes group_action = GroupActionController.load_group_action(0) self.assertEqual([extra_ba] + [ BasicAction( action_type=20, action=50, device_nr=5, extra_parameter=65280), BasicAction( action_type=20, action=51, device_nr=7, extra_parameter=32512) ], group_action.actions) self.assertEqual('Foobar', group_action.name)
def test_global_feedback_leds(self): global_configuration = GlobalConfiguration() # Verify base self.assertEqual(65535, global_configuration.groupaction_any_output_changed) self.assertEqual( {}, CANFeedbackController.load_global_led_feedback_configuration()) # Store feedback "0" (nr of lights == 0) global_feedback_0 = GlobalFeedbackDTO( id=0, can_led_1=FeedbackLedDTO( id=5, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_3=FeedbackLedDTO( id=7, function=FeedbackLedDTO.Functions.ON_B8_INVERTED), can_led_4=FeedbackLedDTO( id=9, function=FeedbackLedDTO.Functions.FB_B8_NORMAL)) CANFeedbackController.save_global_led_feedback_configuration( [global_feedback_0]) # +- 256 = MSB is 1 = lights # Validate | +- 0 = Solid on, 1 = Fast blinking expected_basic_actions_0 = [ BasicAction(action_type=20, action=73, device_nr=5, extra_parameter=256 + 0), BasicAction(action_type=20, action=73, device_nr=7, extra_parameter=256 + 0), BasicAction(action_type=20, action=73, device_nr=9, extra_parameter=256 + 1) ] expected_global_feedback_0 = GlobalFeedbackDTO( id=0, can_led_1=FeedbackLedDTO( id=5, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_2=FeedbackLedDTO( id=7, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_3=FeedbackLedDTO( id=9, function=FeedbackLedDTO.Functions.FB_B16_NORMAL)) global_configuration = GlobalConfiguration() group_action = GroupActionController.load_group_action(0) self.assertEqual(0, global_configuration.groupaction_any_output_changed) self.assertEqual('Global feedback', group_action.name) self.assertEqual(expected_basic_actions_0, group_action.actions) self.assertEqual( {0: expected_global_feedback_0}, CANFeedbackController.load_global_led_feedback_configuration()) # Prepare feedback "3" (nr of lights > 2) global_feedback_3 = GlobalFeedbackDTO( id=3, can_led_1=FeedbackLedDTO( id=11, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_3=FeedbackLedDTO( id=13, function=FeedbackLedDTO.Functions.FB_B8_INVERTED)) expected_global_feedback_3 = GlobalFeedbackDTO( id=3, can_led_1=FeedbackLedDTO( id=11, function=FeedbackLedDTO.Functions.ON_B16_NORMAL), can_led_2=FeedbackLedDTO( id=13, function=FeedbackLedDTO.Functions.FB_B16_NORMAL)) expected_basic_actions_3 = [ BasicAction(action_type=20, action=71, device_nr=11, extra_parameter=512 + 0), BasicAction(action_type=20, action=71, device_nr=13, extra_parameter=512 + 1) ] # | +- 0 = Solid on, 1 = Fast blinking # +- 512 = MSB is 2 = nr of lights # Store in various scenarios, all should yield the same response save_scenarios = [[global_feedback_3], [global_feedback_0, global_feedback_3]] for save_scenario in save_scenarios: CANFeedbackController.save_global_led_feedback_configuration( save_scenario) global_configuration = GlobalConfiguration() group_action = GroupActionController.load_group_action(0) self.assertEqual( 0, global_configuration.groupaction_any_output_changed) self.assertEqual( expected_basic_actions_0 + expected_basic_actions_3, group_action.actions) self.assertEqual( { 0: expected_global_feedback_0, 3: expected_global_feedback_3 }, CANFeedbackController.load_global_led_feedback_configuration()) # Add extra BA that should not be removed by altering global feedback extra_basic_actions = [BasicAction(action_type=123, action=123)] group_action.actions += extra_basic_actions group_action.name = 'Foobar' GroupActionController.save_group_action(group_action, ['name', 'actions']) # Save without scenario (will re-save data, but should not alter) CANFeedbackController.save_global_led_feedback_configuration([]) group_action = GroupActionController.load_group_action(0) self.assertEqual('Foobar', group_action.name) self.assertEqual( expected_basic_actions_0 + expected_basic_actions_3 + extra_basic_actions, group_action.actions) # Save full scenario (will remove feedback BAs and save them again at the end of the GA) CANFeedbackController.save_global_led_feedback_configuration( save_scenarios[1]) group_action = GroupActionController.load_group_action(0) self.assertEqual('Foobar', group_action.name) self.assertEqual( extra_basic_actions + expected_basic_actions_0 + expected_basic_actions_3, group_action.actions) # Prepare feedbacks "16" (nr of outputs == 0) and "20" (nr of outputs > 3) global_feedback_16 = GlobalFeedbackDTO( id=16, can_led_1=FeedbackLedDTO( id=15, function=FeedbackLedDTO.Functions.ON_B16_NORMAL)) global_feedback_20 = GlobalFeedbackDTO( id=20, can_led_1=FeedbackLedDTO( id=17, function=FeedbackLedDTO.Functions.ON_B16_NORMAL)) expected_global_feedback_16 = GlobalFeedbackDTO( id=16, can_led_1=FeedbackLedDTO( id=15, function=FeedbackLedDTO.Functions.ON_B16_NORMAL)) expected_global_feedback_20 = GlobalFeedbackDTO( id=20, can_led_1=FeedbackLedDTO( id=17, function=FeedbackLedDTO.Functions.ON_B16_NORMAL)) expected_basic_actions_16 = [ BasicAction(action_type=20, action=73, device_nr=15, extra_parameter=0 + 0) ] # 0 = MSB is 0 = outputs expected_basic_actions_20 = [ BasicAction(action_type=20, action=70, device_nr=17, extra_parameter=768 + 0) ] # 768 = MSB is 3 = nr of outputs # Store CANFeedbackController.save_global_led_feedback_configuration([ global_feedback_0, global_feedback_3, global_feedback_16, global_feedback_20 ]) # Validate global_configuration = GlobalConfiguration() group_action = GroupActionController.load_group_action(0) self.assertEqual(0, global_configuration.groupaction_any_output_changed) self.assertEqual( extra_basic_actions + expected_basic_actions_0 + expected_basic_actions_3 + expected_basic_actions_16 + expected_basic_actions_20, group_action.actions) self.assertEqual( { 0: expected_global_feedback_0, 3: expected_global_feedback_3, 16: expected_global_feedback_16, 20: expected_global_feedback_20 }, CANFeedbackController.load_global_led_feedback_configuration()) # Remove 3 empty_global_feedback_3 = GlobalFeedbackDTO(id=3, can_led_1=None, can_led_2=None, can_led_3=None, can_led_4=None) CANFeedbackController.save_global_led_feedback_configuration( [empty_global_feedback_3, global_feedback_20]) # Validate global_configuration = GlobalConfiguration() group_action = GroupActionController.load_group_action(0) self.assertEqual(0, global_configuration.groupaction_any_output_changed) self.assertEqual( extra_basic_actions + expected_basic_actions_0 + expected_basic_actions_16 + expected_basic_actions_20, group_action.actions) self.assertEqual( { 0: expected_global_feedback_0, 16: expected_global_feedback_16, 20: expected_global_feedback_20 }, CANFeedbackController.load_global_led_feedback_configuration())
def classic_actions_to_core_actions(classic_actions): # type: (List[int]) -> List[BasicAction] if len(classic_actions) % 2 != 0: raise ValueError('Classic actions must be a multiple of two') actions = [] for i in range(0, len(classic_actions), 2): action_type = classic_actions[i] action_number = classic_actions[i + 1] # 0: Simple Action (Old instruction set, please do not use anymore) # 1: Simple Decision, ignore THEN/ELSE action, ignore previous decision (Old instruction set, please do not use anymore) if action_type == 2: # 2: Execute Group Action actions.append(BasicAction(action_type=19, action=0, device_nr=action_number)) # 3: Put this Scheduled Action in the scheduled action queue # 4: Used by the system (to indicate in the queue which input has triggered the actions in the queue) # 7: Remove this Scheduled Action from the Scheduled action queue # 9: Simple Decision, perform THEN/ELSE action, ignore previous decision (Old instruction set, please do not use anymore) # 17: Simple Decision, ignore THEN/ELSE action, perform "OR" with previous decision (Old instruction set, please do not use anymore) # 25: Simple Decision, perform THEN/ELSE action, perform "OR" with previous decision (Old instruction set, please do not use anymore) # 49: Simple Decision, ignore THEN/ELSE action, perform "AND" with previous decision (Old instruction set, please do not use anymore) # 57: Simple Decision, perform THEN/ELSE action, perform "AND" with previous decision (Old instruction set, please do not use anymore) # 60: Will sent Event (API instruction EV) to the Beagle Bone Black and RTI RS232 port (when enabled) with Event Code x # 64: x=0 Put all Modules in lower power state (switch off leds except power and status led), x=1 Normal power state, x=2 normal power state for 2 minutes # 65: Flash led of output x # 66: Flash led of input x # 67: Flash led of sensor x elif action_type == 68: # 68: Press virtual input x actions.append(BasicAction(action_type=1, action=0, device_nr=action_number)) elif action_type == 69: # 69: Release virtual input x actions.append(BasicAction(action_type=1, action=1, device_nr=action_number)) # 70: Switch OFF 5V out off all temperature modules (switch ON 5V will automatically happen after 5 minutes) # 71: Switch ON 5V out off all temperature modules elif action_type == 72: # 72: Reset 12V out power on the gateway so all remote modules connected on BUS1 of the Gateway will get a power reset (Master will not respond during 5 seconds) actions.append(BasicAction(action_type=253, action=0, device_nr=0)) actions.append(BasicAction(action_type=253, action=0, device_nr=1)) # 73: Switch ON DALI group x, see DALI Installation Guide for more details # 74: Switch OFF DALI group x, see DALI Installation Guide for more details elif action_type == 75: # 75: Switch ON CAN power of all CAN controls (micro CAN's will receive power) actions.append(BasicAction(action_type=253, action=1, device_nr=1)) elif action_type == 76: # 76: Switch OFF CAN power of all CAN controls (micro CAN's won't receive any power and will be switched off) actions.append(BasicAction(action_type=253, action=1, device_nr=0)) # 79: This Basic Action will set the CleanTimerQueue setting. When Basic actions are added to the timer queue (for delayed action for example), the Master processor will check for the same Basic actions and remove the previous one. When x=0 -> Clean Timer Queue is disabled, when x<>0 -> Clean Timer Queue is enabled (standard setting) - see #Delaying Instructions and see BA235 # 80 -> 91: Thermostat related elif action_type == 100: # 100: Roller/Shutter x up (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=1, device_nr=action_number)) elif action_type == 101: # 101: Roller/Shutter x down (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=2, device_nr=action_number)) elif action_type == 102: # 102: Roller/Shutter x stop (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=0, device_nr=action_number)) elif action_type == 103: # 103: Roller/Shutter x up/stop/down/stop... (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=5, device_nr=action_number)) # 104: All Roller/Shutters of group x up (only to be used in Large Installation mode, x<30) # 105: All Roller/Shutters of group x down (only to be used in Large Installation mode, x<30) # 106: All Roller/Shutters of group x stop (only to be used in Large Installation mode, x<30) # 107: All Roller/Shutters of group x up/stop/down/stop... (only to be used in Large Installation mode, x<30) elif action_type == 108: # 108: Roller/Shutter x up/stop/up/stop... (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=3, device_nr=action_number)) elif action_type == 109: # 109: Roller/Shutter x down/stop/down/stop... (only to be used in Large Installation mode, x<120) actions.append(BasicAction(action_type=10, action=4, device_nr=action_number)) # 110: All Roller/Shutters of group x up/stop/up/stop... (only to be used in Large Installation mode, x<30) # 111: All Roller/Shutters of group x down/stop/down/stop... (only to be used in Large Installation mode, x<30) # 112: The Timer of all Roller/Shutters will be disabled (x=0) or enabled (x=1) # 113: Enable/disable automatic Roller/Shutter Lock functionality for all Roller/Shutters: When x=0, the roller/shutters will work normally. When x>0, the Roller/shutters will be locked and the normal BA's to stop, up or down a shutter (or group) will be disabled. When a timer was activated to stop a Roller/shutter, even when the automatic Roller/shutter functionality is enabled, will still be executed. # 116: Disable input x (0-239) # 117: Enable input x (0-239) # 118: Reset Pulse Counters of all Modules # 120: Put free variable x (0-31) at zero # 121: Decrease free variable x (0-31) with 1 # 122: Increase free variable x (0-31) with 1 # 123: Decrease free variable x (0-31) with 2 # 124: Increase free variable x (0-31) with 2 # 125: Decrease free variable x (0-31) with 3 # 126: Increase free variable x (0-31) with 3 # 128 -> 143: Thermostat related # 144: Reserved (for Oled) # 145 -> 149: Thermostat related # 153: Light/Output x on with std timer and overrule/overwrite timer value when light is already switched on # 154: Increase light/output level of output/light x with 1 step until programmed Maximum (63) light level is achieved (x<240) # 155: Increase light/output level of output/light x with 2 steps until programmed Maximum (63) light level is achieved (x<240) # 156: Increase light/output level of output/light x with 3 steps until programmed Maximum (63) light level is achieved (x<240) # 157: Dim light/output x down with 1 step until programmed Minimum light level is achieved (x<240) # 158: Dim light/output x down with 2 steps until programmed Minimum light level is achieved (x<240) # 159: Dim light/output x down with 3 steps until programmed Minimum light level is achieved (x<240) elif action_type == 160: # 160: Light/Output x Off (x<240) actions.append(BasicAction(action_type=0, action=0, device_nr=action_number)) elif action_type == 161: # 161: Light/Output x On (x<240, with standard timer setting, with last dimmer value) actions.append(BasicAction(action_type=0, action=1, device_nr=action_number)) elif action_type == 162: # 162: Toggle light/Output x (x<240, with standard timer setting, with last dimmer value), see #Toggling Lights actions.append(BasicAction(action_type=0, action=16, device_nr=action_number)) elif action_type == 163: # 163: All lights off (x=any value but <240) actions.append(BasicAction(action_type=0, action=255, device_nr=1)) elif action_type == 164: # 164: All outputs including lights off (x=any value but <240) actions.append(BasicAction(action_type=0, action=255, device_nr=2)) elif action_type == 165: # 165: Light/Output x On (x<240, with standard timer setting, at minimum dimmer value) actions.append(BasicAction(action_type=0, action=2, device_nr=action_number, extra_parameter=1)) elif action_type == 166: # 166: Light/Output x On (x<240, with standard timer setting, at maximum dimmer value) actions.append(BasicAction(action_type=0, action=2, device_nr=action_number, extra_parameter=255)) # 167: Light/Output x On (x<240, with standard timer setting, decrease dimmer value with 5) # 168: Light/Output x On (x<240, with standard timer setting, increase dimmer value with 5) elif action_type == 169: # 169: Set Dimmer value x at minimum (leaving the output at the current state) actions.append(BasicAction(action_type=0, action=9, device_nr=action_number, extra_parameter=1)) elif action_type == 170: # 170: Set Dimmer value x at maximum (leaving the output at the current state) actions.append(BasicAction(action_type=0, action=9, device_nr=action_number, extra_parameter=255)) # 171: All lights OFF of a certain floor level or group (x=floor level or group, x=0..254, when x=255 then all lights are selected) # 172: All lights ON of a certain floor level or group (x=floor level or group, x=0..254, when x=255 then all lights are selected) # 173: Toggle all lights of a certain floor or group (x=floor level or group, x=0..254, when x=255 then all lights are selected), see #Toggling a Floor # 174: Toggle Follow function ON (see #Toggling Lights), action number not used but must be < 240 # 175: Toggle Follow function OFF (see #Toggling Lights), action number not used but must be < 240 elif 176 <= action_type <= 184: # 176: Light/Output x On with dimmer at 10% (x<240, with standard timer setting) # 177: Light/Output x On with dimmer at 20% (x<240, with standard timer setting) # ... # 183: Light/Output x On with dimmer at 80% (x<240, with standard timer setting) # 184: Light/Output x On with dimmer at 90% (x<240, with standard timer setting) actions.append(BasicAction(action_type=0, action=2, device_nr=action_number, extra_parameter={176: 25, 177: 51, 178: 76, 179: 102, 180: 127, 181: 153, 182: 178, 183: 204, 184: 229}[action_type])) elif 185 <= action_type <= 194: # 185: Toggle/Output light x with dimmer at 10% (x<240, with standard timer setting) # 186: Toggle light/Output x with dimmer at 20% (x<240, with standard timer setting) # ... # 193: Toggle light/Output x with dimmer at 90% (x<240, with standard timer setting) # 194: Toggle light/Output x with dimmer at 100% (x<240, with standard timer setting) actions.append(BasicAction(action_type=0, action=17, device_nr=action_number, extra_parameter={185: 25, 186: 51, 187: 76, 188: 102, 189: 127, 190: 153, 191: 178, 192: 204, 193: 229, 194: 255}[action_type])) elif 195 <= action_type <= 200: # 195: Light/Output x on with timer at 2 min 30 and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers # 196: Light/Output x on with timer at 7 min 30 and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers # 197: Light/Output x on with timer at 15 min and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers # 198: Light/Output x on with timer at 25 min and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers # 199: Light/Output x on with timer at 37 min and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers # 200: Light/Output x on with timer at 52 min and overrule timer value when light is already switched on (x<240, with last dimmer value) - see #Timers actions.append(BasicAction(action_type=0, action=4, device_nr=action_number, extra_parameter={195: 150, 196: 450, 197: 900, 198: 1500, 199: 2220, 200: 3120}[action_type])) elif 201 <= action_type <= 206: # 201: Light/Output x on with timer at 2 min 30 but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) # 202: Light/Output x on with timer at 7 min 30 but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) # 203: Light/Output x on with timer at 15 min but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) # 204: Light/Output x on with timer at 25 min but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) # 205: Light/Output x on with timer at 37 min but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) # 206: Light/Output x on with timer at 52 min but doesn't overrule timer value when light is already switched on (x<240, with last dimmer value) actions.append(BasicAction(action_type=0, action=7, device_nr=action_number, extra_parameter={201: 150, 202: 450, 203: 900, 204: 1500, 205: 2220, 206: 3120}[action_type])) # 207: When current input is pressed for more than 2 seconds, execute group action x (See #Long Press) # 208: When current input is pressed for more than 3 seconds, execute group action x (See #Long Press) # 209: When current input is pressed for more than 4 seconds, execute group action x (See #Long Press) # 210: When current input is pressed for more than 5 seconds, execute group action x (See #Long Press) # 211: When current input is pressed for more than 6 seconds, execute group action x (See Note "Long Press") # 212: Switch CAN led x OFF (see #Important Remarks) # 213: Switch CAN led x ON (see #Important Remarks) # 214: Fast blinking of CAN led x (see #Important Remarks) # 215: Medium blinking of CAN led x (see #Important Remarks) # 216: Slow blinking of CAN led x (see #Important Remarks) # 217: Fade ON/OFF of CAN led x (see #Important Remarks) elif action_type in [218, 219]: # TODO: Tricky one, these are included in a single action on the Core so both parameters # should be set at the same time. Idea: scan the whole sequence for the other one hoping # that they are both set at the same GroupAction/Input # 218: Set minimum brightness of all CAN leds at value x (see #Important Remarks) # 219: Set maximum brightness of all CAN leds at value x (see #Important Remarks) raise ValueError('Cannot convert multi-instructions') # 235: Delay all next instructions with x seconds (x>0 and <249), x=255 -> All next instruction will be executed normally (see #Delaying Instructions and see BA79) # 236: Execute all next actions at button release (x=0), x=255 -> All next instructions will be executed normally (see #Additional Actions) # 237: Set the freely assigned validation bit x to 1 (x=0 to 255) # 238: Set the freely assigned validation bit x to 0 (x=0 to 255) # 239: Toggle the freely assigned validation bit x (x=0 to 255) elif action_type == 240: # 240: IF THEN ELSE ENDIF if action_number == 0: # X=0 -> IF actions.append(BasicAction(action_type=100, action=0)) elif action_number == 1: # X=1 -> AND actions.append(BasicAction(action_type=100, action=90)) elif action_number == 2: # X=2 -> OR actions.append(BasicAction(action_type=100, action=91)) elif action_number == 10: # X=10 -> THEN actions.append(BasicAction(action_type=100, action=150)) elif action_number == 20: # X=20 -> ELSE actions.append(BasicAction(action_type=100, action=200)) elif action_number == 255: # X=255 -> ENDIF actions.append(BasicAction(action_type=100, action=255)) else: # X = 3 -> XOR, X=4 -> NAND, X=5 -> NOR, X=6 -> NXOR # TODO: Implement once available raise ValueError('Cannot convert operators: NAND, NOR, NXOR') elif action_type == 241: # 241: Check if input x is ON (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=12, device_nr=action_number)) elif action_type == 242: # 242: Check if input x is OFF (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=13, device_nr=action_number)) elif action_type == 243: # 243: Check if Light/Output x is ON (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=10, device_nr=action_number)) elif action_type == 244: # 244: Check if Light/Output x is OFF (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=11, device_nr=action_number)) elif action_type == 245: # 245: Check if Validation bit x is ON (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=14, device_nr=action_number)) elif action_type == 246: # 246: Check if Validation bit x is OFF (To be used with IF THEN ELSE instruction) actions.append(BasicAction(action_type=100, action=15, device_nr=action_number)) elif 247 <= action_type <= 250: # TODO: Need multi instruction parsing # 247: Check if temperature sensor 0-31 (x=0-31) or if humidity sensor 0-31 (x=32-63) or if light sensor 0-31 (x=64-95) or if temperature setpoint 0-23 (x=96-119) or if free variable 0-31 (x=128-159) or if time hour (x=228) or if time minute (x=229) or if day (x=230) is or if thermostat mode (x=235) (always to be followed by action type 248 or 249 or 250) (see #Additional Input Values). All environmental parameters are written in System Value # 248: is equal to x (to be used always with action type 247) (see #Additional Input Values) # 249: is higher than x (to be used always with action type 247) (see #Additional Input Values) # 250: is lower than x (to be used always with action type 247) (see #Additional Input Values) raise ValueError('Cannot convert multi-instructions') elif action_type == 254: # 254: Reset the Master actions.append(BasicAction(action_type=254, action=0)) return actions
def test_save_allocations(self): """ This test validates whether writing a GA will store its BAs in the appropriate location. This is checked on two ways: 1. A free space map is generated that is used to validate where the free slots are located 2. A human readable / visual overview is generated of all BA's action type values in the first 42 addresses Legend: X = Used by a few pre-defined GAs n = Actual data, as every insert uses different action types, this can be used to verify whether data is written correctly, as whether the old data is overwritten when needed. _ = Slot that was never used Note: As an allocation table is used, the BA space is not cleared, only the reference is removed! """ memory = {} for page in range(256, 381): memory[page] = bytearray([255] * 256) GroupActionTest._setup_master_communicator(memory) space_map = GroupActionController._free_address_space_map() self.assertEqual('__________________________________________', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({4200: [0]}, space_map) # Generate "pre-defined" GAs for group_action_id, address in {10: 0, 11: 2, 12: 5, 13: 8, 14: 14, 15: 25, 16: (41, 4199)}.items(): start, end = (address[0], address[1]) if isinstance(address, tuple) else (address, address) GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], group_action_id * 4, self._word_helper.encode(start) + self._word_helper.encode(end)) memory[GroupActionTest.ACTIONS_START_PAGE][start * 6] = 100 + group_action_id space_map = GroupActionController._free_address_space_map() self.assertEqual('X_X__X__X_____X__________X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({1: [1], 2: [3, 6], 5: [9], 10: [15], 15: [26]}, space_map) # Store GA with 1 BA group_action_1 = GroupAction(id=1, actions=[BasicAction(1, 0)]) GroupActionController.save_group_action(group_action_1, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X__X__X_____X__________X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [3, 6], 5: [9], 10: [15], 15: [26]}, space_map) # Store another GA with 1 BA group_action_2 = GroupAction(id=2, actions=[BasicAction(2, 0)]) GroupActionController.save_group_action(group_action_2, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X2_X__X_____X__________X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({1: [4], 2: [6], 5: [9], 10: [15], 15: [26]}, space_map) # GA is update dto two BAs group_action_2 = GroupAction(id=2, actions=[BasicAction(3, 0), BasicAction(3, 0)]) GroupActionController.save_group_action(group_action_2, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X33X__X_____X__________X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [6], 5: [9], 10: [15], 15: [26]}, space_map) # First GA is extended group_action_1 = GroupAction(id=1, actions=[BasicAction(4, 0), BasicAction(4, 0)]) GroupActionController.save_group_action(group_action_1, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X33X44X_____X__________X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({1: [1], 5: [9], 10: [15], 15: [26]}, space_map) # Add large GA group_action_3 = GroupAction(id=3, actions=[BasicAction(5, 0), BasicAction(5, 0), BasicAction(5, 0), BasicAction(5, 0), BasicAction(5, 0), BasicAction(5, 0)]) GroupActionController.save_group_action(group_action_3, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X33X44X_____X555555____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({1: [1], 4: [21], 5: [9], 15: [26]}, space_map) # Large GA is reduced group_action_3 = GroupAction(id=3, actions=[BasicAction(6, 0), BasicAction(6, 0), BasicAction(6, 0)]) GroupActionController.save_group_action(group_action_3, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X1X33X44X666__X555555____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({1: [1], 2: [12], 10: [15], 15: [26]}, space_map, 'Reduced GA should be moved') # Another GA is added with only one BA group_action_4 = GroupAction(id=4, actions=[BasicAction(7, 0)]) GroupActionController.save_group_action(group_action_4, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X7X33X44X666__X555555____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [12], 10: [15], 15: [26]}, space_map, 'Reduced GA should be moved') # Another large GA is added group_action_5 = GroupAction(id=5, actions=[BasicAction(8, 0), BasicAction(8, 0), BasicAction(8, 0), BasicAction(8, 0)]) GroupActionController.save_group_action(group_action_5, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X7X33X44X666__X888855____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [12], 6: [19], 15: [26]}, space_map, 'Reduced GA should be moved') # Large GA is "deleted" group_action_5 = GroupAction(id=5, actions=[]) GroupActionController.save_group_action(group_action_5, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X7X33X44X666__X888855____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [12], 10: [15], 15: [26]}, space_map, 'Reduced GA should be moved') # A GA with too many BAs is added group_action_6 = GroupAction(id=6, actions=[BasicAction(8, 0)] * 16) with self.assertRaises(RuntimeError): GroupActionController.save_group_action(group_action_6, ['actions']) space_map = GroupActionController._free_address_space_map() self.assertEqual('X7X33X44X666__X888855____X_______________X', GroupActionTest._get_readable_ba_block(memory)) # | | | | | | | | | # 0 5 10 15 20 25 30 35 40 self.assertEqual({2: [12], 10: [15], 15: [26]}, space_map, 'Memory is not changed')
def test_list_group_actions(self): memory = {} for page in range(256, 381): memory[page] = bytearray([255] * 256) GroupActionTest._setup_master_communicator(memory) group_actions = GroupActionController.load_group_actions() self.assertEqual(256, len(group_actions), 'There should be 256 GAs') for i in range(256): self.assertFalse(group_actions[i].in_use, 'GA {0} should not be in use'.format(i)) # Set valid start address GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], 0 * 4, self._word_helper.encode(0)) group_actions = GroupActionController.load_group_actions() self.assertEqual(256, len(group_actions), 'There should still be 256 GAs') for i in range(256): self.assertFalse(group_actions[i].in_use, 'GA {0} should not be in use'.format(i)) group_action = GroupActionController.load_group_action(0) self.assertEqual(group_actions[0], group_action, 'The GA is equal (same id, same name, same in_use state)') self.assertFalse(group_action.in_use, 'The GA is still not in use') # Add BA at start address and set a name basic_action_1 = BasicAction(0, 0) GroupActionTest._write(memory[GroupActionTest.ACTIONS_START_PAGE], 0 * 6, basic_action_1.encode()) GroupActionTest._write(memory[GroupActionTest.GANAMES_START_PAGE], 0 * 16, GroupActionTest._encode_string('test')) group_action = GroupActionController.load_group_action(0) self.assertNotEqual(group_actions[0], group_action, 'The GA changed (name is set)') self.assertEqual('test', group_action.name) self.assertFalse(group_action.in_use, 'The GA is still not in use') # Write valid end address but remove BA GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], 0 * 4 + 2, self._word_helper.encode(0)) GroupActionTest._write(memory[GroupActionTest.ACTIONS_START_PAGE], 0 * 6, [255, 255, 255, 255, 255, 255]) group_action = GroupActionController.load_group_action(0) self.assertFalse(group_action.in_use, 'The GA is not in use yet (no BAs defined)') # Restore BA GroupActionTest._write(memory[GroupActionTest.ACTIONS_START_PAGE], 0 * 6, basic_action_1.encode()) group_action = GroupActionController.load_group_action(0) self.assertTrue(group_action.in_use, 'The GA is now in use (has name and BA)') self.assertEqual(1, len(group_action.actions), 'There should be one GA') self.assertEqual(basic_action_1, group_action.actions[0], 'The expected BA should be configured') # Make the GA point to two BAs GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], 0 * 4 + 2, self._word_helper.encode(1)) group_action = GroupActionController.load_group_action(0) self.assertTrue(group_action.in_use, 'The GA is still in use') self.assertEqual(1, len(group_action.actions), 'An empty BA should be excluded') self.assertEqual(basic_action_1, group_action.actions[0], 'The valid BA should still be included') # Write second BA basic_action_2 = BasicAction(0, 1) GroupActionTest._write(memory[GroupActionTest.ACTIONS_START_PAGE], 1 * 6, basic_action_2.encode()) group_action = GroupActionController.load_group_action(0) self.assertTrue(group_action.in_use, 'The GA is still in use') self.assertEqual(2, len(group_action.actions), 'Both BAs should be included') self.assertEqual([basic_action_1, basic_action_2], group_action.actions, 'The valid BAs should be included') group_actions = GroupActionController.load_group_actions() self.assertEqual(256, len(group_actions), 'There should be 256 GAs') for i in range(1, 256): self.assertFalse(group_actions[i].in_use, 'GA {0} should not be in use'.format(i)) self.assertEqual(group_action, group_actions[0], 'The list should correctly point to the first GA') # Set name of third GA, store BA and set addresses basic_action_3 = BasicAction(0, 2) GroupActionTest._write(memory[GroupActionTest.ACTIONS_START_PAGE], 2 * 6, basic_action_3.encode()) GroupActionTest._write(memory[GroupActionTest.GANAMES_START_PAGE], 2 * 16, GroupActionTest._encode_string('three')) GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], 2 * 4, self._word_helper.encode(2)) GroupActionTest._write(memory[GroupActionTest.ADDRESS_START_PAGE], 2 * 4 + 2, self._word_helper.encode(2)) group_action_2 = GroupActionController.load_group_action(2) group_actions = GroupActionController.load_group_actions() self.assertEqual(256, len(group_actions), 'There should be 256 GAs') for i in range(0, 256): if i in [0, 2]: continue self.assertFalse(group_actions[i].in_use, 'GA {0} should not be in use'.format(i)) self.assertEqual(group_action, group_actions[0], 'The list should correctly point to the first GA') self.assertEqual(group_action_2, group_actions[2], 'The list should correctly point to the first GA')
def classic_actions_to_core_input_configuration(action, basic_actions): # type: (Optional[int], List[int]) -> Dict[str, Any] # Default data data = { 'input_link': { 'output_id': 1023, 'dimming_up': True, 'enable_press_and_release': True, 'enable_1s_press': True, 'enable_2s_press': True, 'not_used': True, 'enable_double_press': True }, 'basic_action_press': BasicAction.empty(), 'basic_action_release': BasicAction.empty(), 'basic_action_1s_press': BasicAction.empty(), 'basic_action_2s_press': BasicAction.empty(), 'basic_action_double_press': BasicAction.empty() } # type: Dict[str, Any] # Disabled input if action is None or action == 255: return data # Change default data data['input_link'].update({ 'dimming_up': False, 'enable_press_and_release': False, 'enable_1s_press': False, 'enable_2s_press': False, 'not_used': False, 'enable_double_press': False }) # If theaction is < 240, it means that the input directly controls an output if action < 240: data['input_link']['output_id'] = action return data # If the action is 241 or 242 if action in [241, 242]: data['input_link']['enable_press_and_release'] = True data['basic_action_press'] = BasicAction( action_type=0, action=255, device_nr=2 if action == 241 else 1) return data # Otherwise, it means that the input is supposed to execute a list of basic actions # but this is not supported anymore on the Core. # TODO: Convert any list of actions to one or more group actions and use these instead action_types = set(basic_actions[i] for i in range(0, len(basic_actions), 2)) # Delayed action(s) if 207 in action_types: if len(basic_actions) != 2: raise ValueError( 'Timing settings cannot be combined with other actions') data['input_link']['enable_2s_press'] = True data['basic_action_2s_press'] = BasicAction( action_type=19, action=0, device_nr=basic_actions[1]) return data # Possible single on-press actions if action_types - {2, 236}: actions = GroupActionMapper.classic_actions_to_core_actions( basic_actions) if len(actions) != 1: raise ValueError( 'Only simple input configrations are supported') data['input_link']['enable_press_and_release'] = True data['basic_action_press'] = actions[0] return data # Press/release actions release_data = False release_action = None # type: Optional[int] press_action = None # type: Optional[int] for i in range(0, len(basic_actions), 2): action_type = basic_actions[i] action_number = basic_actions[i + 1] if action_type == 236: release_data = action_number == 0 elif release_data: release_action = action_number else: press_action = action_number if press_action is not None: data['input_link']['enable_press_and_release'] = True data['basic_action_press'] = BasicAction(action_type=19, action=0, device_nr=press_action) if release_action is not None: data['input_link']['enable_press_and_release'] = True data['basic_action_release'] = BasicAction( action_type=19, action=0, device_nr=release_action) return data
def decode(self, data): # type: (bytearray) -> BasicAction from master.core.basic_action import BasicAction # Prevent circular import return BasicAction.decode(data)
def test_basicaction_field(self): self._test_field(MemoryBasicActionField, [[[], BasicAction(10, 10, 0, 0), bytearray([10, 10, 0, 0, 0, 0])], [[], BasicAction(10, 20, 256, 256), bytearray([10, 20, 1, 0, 1, 0])]])