def __init__(self, hass, device_scanner, error_scanning=None): self.states = hass.states self.device_scanner = device_scanner self.error_scanning = error_scanning or TIME_SPAN_FOR_ERROR_IN_SCANNING self.logger = logging.getLogger(__name__) self.lock = threading.Lock() # Dictionary to keep track of known devices and devices we track self.known_devices = {} # Did we encounter an invalid known devices file self.invalid_known_devices_file = False self._read_known_devices_file() # Wrap it in a func instead of lambda so it can be identified in # the bus by its __name__ attribute. def update_device_state(time): # pylint: disable=unused-argument """ Triggers update of the device states. """ self.update_devices() hass.track_time_change(update_device_state) hass.services.register(DOMAIN, SERVICE_DEVICE_TRACKER_RELOAD, lambda service: self._read_known_devices_file()) self.update_devices() group.setup(hass, GROUP_NAME_ALL_DEVICES, self.device_entity_ids)
def test_setup(self): """ Test setup method. """ self.assertTrue( group.setup( self.hass, { group.DOMAIN: { 'second_group': { 'entities': 'light.Bowl, ' + self.group_entity_id, 'icon': 'mdi:work', 'view': True, }, 'test_group': 'hello.world,sensor.happy', } })) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) self.assertEqual(STATE_ON, group_state.state) self.assertEqual(set((self.group_entity_id, 'light.bowl')), set(group_state.attributes['entity_id'])) self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) self.assertEqual('mdi:work', group_state.attributes.get(ATTR_ICON)) self.assertTrue(group_state.attributes.get(group.ATTR_VIEW)) self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('test_group')) self.assertEqual(STATE_UNKNOWN, group_state.state) self.assertEqual(set(('sensor.happy', 'hello.world')), set(group_state.attributes['entity_id'])) self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) self.assertIsNone(group_state.attributes.get(ATTR_ICON)) self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW)) self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
def test_setup(self): """ Test setup method. """ self.assertTrue( group.setup( self.hass, { group.DOMAIN: { 'second_group': '{},light.Bowl'.format(self.group_name) } })) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) self.assertEqual(STATE_ON, group_state.state) self.assertFalse(group_state.attributes[group.ATTR_AUTO])
def test_setup(self): """ Test setup method. """ self.assertTrue( group.setup( self.hass, { group.DOMAIN: { 'second_group': 'light.Bowl, ' + self.group_entity_id } })) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) self.assertEqual(STATE_ON, group_state.state) self.assertEqual(set((self.group_entity_id, 'light.bowl')), set(group_state.attributes['entity_id'])) self.assertFalse(group_state.attributes[group.ATTR_AUTO])
def test_setup(self): """Test setup method.""" self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) test_group = group.Group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) self.assertTrue( group.setup( self.hass, { group.DOMAIN: { 'second_group': { 'entities': 'light.Bowl, ' + test_group.entity_id, 'icon': 'mdi:work', 'view': True, }, 'test_group': 'hello.world,sensor.happy', } })) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) self.assertEqual(STATE_ON, group_state.state) self.assertEqual(set((test_group.entity_id, 'light.bowl')), set(group_state.attributes['entity_id'])) self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) self.assertEqual('mdi:work', group_state.attributes.get(ATTR_ICON)) self.assertTrue(group_state.attributes.get(group.ATTR_VIEW)) self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('test_group')) self.assertEqual(STATE_UNKNOWN, group_state.state) self.assertEqual(set(('sensor.happy', 'hello.world')), set(group_state.attributes['entity_id'])) self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) self.assertIsNone(group_state.attributes.get(ATTR_ICON)) self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW)) self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
def setup(hass, light_control): """ Exposes light control via statemachine and services. """ logger = logging.getLogger(__name__) ent_to_light = {} light_to_ent = {} def _update_light_state(light_id, light_state): """ Update statemachine based on the LightState passed in. """ name = light_control.get_name(light_id) or "Unknown Light" try: entity_id = light_to_ent[light_id] except KeyError: # We have not seen this light before, set it up # Create entity id logger.info("Found new light {}".format(name)) entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), list(ent_to_light.keys())) ent_to_light[entity_id] = light_id light_to_ent[light_id] = entity_id state_attr = {ATTR_FRIENDLY_NAME: name} if light_state.on: state = STATE_ON if light_state.brightness: state_attr[ATTR_BRIGHTNESS] = light_state.brightness if light_state.color: state_attr[ATTR_XY_COLOR] = light_state.color else: state = STATE_OFF hass.states.set(entity_id, state, state_attr) def update_light_state(light_id): """ Update the state of specified light. """ _update_light_state(light_id, light_control.get(light_id)) # pylint: disable=unused-argument def update_lights_state(time, force_reload=False): """ Update the state of all the lights. """ # First time this method gets called, force_reload should be True if (force_reload or datetime.now() - update_lights_state.last_updated > MIN_TIME_BETWEEN_SCANS): logger.info("Updating light status") update_lights_state.last_updated = datetime.now() for light_id, light_state in light_control.gets().items(): _update_light_state(light_id, light_state) # Update light state and discover lights for tracking the group update_lights_state(None, True) if len(ent_to_light) == 0: logger.error("No lights found") return False # Track all lights in a group group.setup(hass, GROUP_NAME_ALL_LIGHTS, light_to_ent.values()) # Load built-in profiles and custom profiles profile_paths = [os.path.dirname(__file__), os.getcwd()] profiles = {} for dir_path in profile_paths: file_path = os.path.join(dir_path, LIGHT_PROFILES_FILE) if os.path.isfile(file_path): with open(file_path) as inp: reader = csv.reader(inp) # Skip the header next(reader, None) try: for profile_id, color_x, color_y, brightness in reader: profiles[profile_id] = (float(color_x), float(color_y), int(brightness)) except ValueError: # ValueError if not 4 values per row # ValueError if convert to float/int failed logger.error( "Error parsing light profiles from {}".format( file_path)) return False def handle_light_service(service): """ Hande a turn light on or off service call. """ # Get and validate data dat = service.data # Convert the entity ids to valid light ids light_ids = [ent_to_light[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_light] if not light_ids: light_ids = list(ent_to_light.values()) transition = util.convert(dat.get(ATTR_TRANSITION), int) if service.service == SERVICE_TURN_OFF: light_control.turn_light_off(light_ids, transition) else: # Processing extra data for turn light on request # We process the profile first so that we get the desired # behavior that extra service data attributes overwrite # profile values profile = profiles.get(dat.get(ATTR_PROFILE)) if profile: *color, bright = profile else: color, bright = None, None if ATTR_BRIGHTNESS in dat: bright = util.convert(dat.get(ATTR_BRIGHTNESS), int) if ATTR_XY_COLOR in dat: try: # xy_color should be a list containing 2 floats xy_color = dat.get(ATTR_XY_COLOR) if len(xy_color) == 2: color = [float(val) for val in xy_color] except (TypeError, ValueError): # TypeError if xy_color is not iterable # ValueError if value could not be converted to float pass if ATTR_RGB_COLOR in dat: try: # rgb_color should be a list containing 3 ints rgb_color = dat.get(ATTR_RGB_COLOR) if len(rgb_color) == 3: color = util.color_RGB_to_xy(int(rgb_color[0]), int(rgb_color[1]), int(rgb_color[2])) except (TypeError, ValueError): # TypeError if rgb_color is not iterable # ValueError if not all values can be converted to int pass light_control.turn_light_on(light_ids, transition, bright, color) # Update state of lights touched. If there was only 1 light selected # then just update that light else update all if len(light_ids) == 1: update_light_state(light_ids[0]) else: update_lights_state(None, True) # Update light state every 30 seconds hass.track_time_change(update_lights_state, second=[0, 30]) # Listen for light on and light off service calls hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_light_service) return True