def test_ensure_unique_string(self): """Test ensure_unique_string.""" self.assertEqual( "Beer_3", util.ensure_unique_string("Beer", ["Beer", "Beer_2"])) self.assertEqual( "Beer", util.ensure_unique_string("Beer", ["Wine", "Soda"]))
def update_wemo_state(device): """ Update the state of specified WeMo device. """ # We currently only support switches if not is_switch(device): return try: entity_id = sno_to_ent[device.serialnumber] except KeyError: # New device, set it up entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(device.name)), list(ent_to_dev.keys())) sno_to_ent[device.serialnumber] = entity_id ent_to_dev[entity_id] = device state = STATE_ON if device.get_state(True) else STATE_OFF state_attr = {ATTR_FRIENDLY_NAME: device.name} if isinstance(device, pywemo.Insight): pass # Should work but doesn't.. #state_attr[ATTR_TODAY_KWH] = device.today_kwh #state_attr[ATTR_CURRENT_POWER] = device.current_power #state_attr[ATTR_TODAY_ON_TIME] = device.today_on_time #state_attr[ATTR_TODAY_STANDBY_TIME] = device.today_standby_time hass.states.set(entity_id, state, state_attr)
def async_generate_entity_id(entity_id_format: str, name: Optional[str], current_ids: Optional[List[str]]=None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" name = (name or DEVICE_DEFAULT_NAME).lower() return ensure_unique_string( entity_id_format.format(slugify(name)), current_ids)
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 find_registration_name(self, data): """Find a registration name matching data or generate a unique one.""" endpoint = data.get(ATTR_SUBSCRIPTION).get(ATTR_ENDPOINT) for key, registration in self.registrations.items(): subscription = registration.get(ATTR_SUBSCRIPTION) if subscription.get(ATTR_ENDPOINT) == endpoint: return key return ensure_unique_string('unnamed device', self.registrations)
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None, location_name: str=None, gps: GPSType=None, gps_accuracy=None, battery: str=None, attributes: dict=None): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: yield from device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes) if device.track: yield from device.async_update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' ')) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device yield from device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes) if device.track: yield from device.async_update_ha_state() self.hass.bus.async_fire(EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, }) # During init, we ignore the group if self.group is not None: yield from self.group.async_update_tracked_entity_ids( list(self.group.tracking) + [device.entity_id]) # lookup mac vendor string to be stored in config yield from device.set_vendor_for_mac() # update known_devices.yaml self.hass.async_add_job( self.async_update_config(self.hass.config.path(YAML_DEVICES), dev_id, device) )
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None): """ Generate a unique entity ID based on given entity IDs or used ids. """ if current_ids is None: if hass is None: raise RuntimeError("Missing required parameter currentids or hass") current_ids = hass.states.entity_ids() return ensure_unique_string( entity_id_format.format(slugify(name.lower())), current_ids)
def async_generate_entity_id(self, domain, suggested_object_id): """Generate an entity ID that does not conflict. Conflicts checked against registered and currently existing entities. """ return ensure_unique_string( '{}.{}'.format(domain, slugify(suggested_object_id)), chain(self.entities.keys(), self.hass.states.async_entity_ids(domain)) )
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None): """Generate a unique entity ID based on given entity IDs or used IDs.""" name = (name or DEVICE_DEFAULT_NAME).lower() if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") current_ids = hass.states.entity_ids() return ensure_unique_string( entity_id_format.format(slugify(name)), current_ids)
def async_generate_entity_id(entity_id_format: str, name: Optional[str], current_ids: Optional[List[str]]=None, hass: Optional[HomeAssistant]=None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") current_ids = hass.states.async_entity_ids() name = (name or DEVICE_DEFAULT_NAME).lower() return ensure_unique_string( entity_id_format.format(slugify(name)), current_ids)
def convert_csv_config(csv_path, yaml_path): """ Convert CSV config file format to YAML. """ used_ids = set() with open(csv_path) as inp: for row in csv.DictReader(inp): dev_id = util.ensure_unique_string( (util.slugify(row['name']) or DEVICE_DEFAULT_NAME).lower(), used_ids) used_ids.add(dev_id) device = Device(None, None, None, row['track'] == '1', dev_id, row['device'], row['name'], row['picture']) update_config(yaml_path, dev_id, device) return True
def async_generate_entity_id(entity_id_format: str, name: Optional[str], current_ids: Optional[List[str]] = None, hass: Optional[HomeAssistant] = None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") current_ids = hass.states.async_entity_ids() name = (name or DEVICE_DEFAULT_NAME).lower() return ensure_unique_string(entity_id_format.format(slugify(name)), current_ids)
def async_generate_entity_id( self, domain, suggested_object_id, known_object_ids=None ): """Generate an entity ID that does not conflict. Conflicts checked against registered and currently existing entities. """ return ensure_unique_string( "{}.{}".format(domain, slugify(suggested_object_id)), chain( self.entities.keys(), self.hass.states.async_entity_ids(domain), known_object_ids if known_object_ids else [], ), )
def generate_entity_id(entity_id_format: str, name: Optional[str], current_ids: Optional[List[str]] = None, hass: Optional[HomeAssistant] = None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") return run_callback_threadsafe(hass.loop, async_generate_entity_id, entity_id_format, name, current_ids, hass).result() name = (slugify(name) or slugify(DEVICE_DEFAULT_NAME)).lower() return ensure_unique_string(entity_id_format.format(name), current_ids)
def see(self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy=None, battery: str = None, attributes: dict = None): """Notify the device tracker that you see a device.""" with self.lock: if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: device.seen(host_name, location_name, gps, gps_accuracy, battery, attributes) if device.track: device.update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device(self.hass, self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' ')) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device device.seen(host_name, location_name, gps, gps_accuracy, battery, attributes) if device.track: device.update_ha_state() # During init, we ignore the group if self.group is not None: self.group.update_tracked_entity_ids( list(self.group.tracking) + [device.entity_id]) update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def __init__(self, hass, name, entity_ids=None, user_defined=True): self.hass = hass self.name = name self.user_defined = user_defined self.entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), hass.states.entity_ids(DOMAIN)) self.tracking = [] self.group_on, self.group_off = None, None if entity_ids is not None: self.update_tracked_entity_ids(entity_ids) else: self.force_update()
def generate_entity_id(entity_id_format: str, name: Optional[str], current_ids: Optional[List[str]] = None, hass: Optional[HomeAssistant] = None) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" if current_ids is None: if hass is None: raise ValueError("Missing required parameter currentids or hass") return run_callback_threadsafe( hass.loop, async_generate_entity_id, entity_id_format, name, current_ids, hass ).result() name = (slugify(name) or slugify(DEVICE_DEFAULT_NAME)).lower() return ensure_unique_string( entity_id_format.format(name), current_ids)
def _generate_entity_ids(self, need_entity_id): """ Generate entity ids for a list of devices. """ # Setup entity_ids for the new devices used_entity_ids = [info['entity_id'] for device, info in self.tracked.items() if device not in need_entity_id] for device in need_entity_id: name = self.tracked[device]['name'] entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), used_entity_ids) used_entity_ids.append(entity_id) self.tracked[device]['entity_id'] = entity_id
def sensor_discovered(service, info): """ Called when a sensor is discovered. """ platform = get_component("{}.{}".format(DOMAIN, DISCOVERY_PLATFORMS[service])) discovered = platform.devices_discovered(hass, config, info) for sensor in discovered: if sensor is not None and sensor not in sensors.values(): sensor.entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(sensor.name)), sensors.keys()) sensors[sensor.entity_id] = sensor sensor.update_ha_state(hass) sensor_group.update_tracked_entity_ids(sensors.keys())
def async_generate_entity_id( self, domain: str, suggested_object_id: str, known_object_ids: Optional[Iterable[str]] = None, ) -> str: """Generate an entity ID that does not conflict. Conflicts checked against registered and currently existing entities. """ return ensure_unique_string( f"{domain}.{slugify(suggested_object_id)}", chain( self.entities.keys(), self.hass.states.async_entity_ids(domain), known_object_ids if known_object_ids else [], ), )
def post(self, request): """Accept the POST request for push registrations from a browser.""" try: data = REGISTER_SCHEMA(request.json) except vol.Invalid as ex: return self.json_message(humanize_error(request.json, ex), HTTP_BAD_REQUEST) name = ensure_unique_string('unnamed device', self.registrations.keys()) self.registrations[name] = data if not _save_config(self.json_path, self.registrations): return self.json_message('Error saving registration.', HTTP_INTERNAL_SERVER_ERROR) return self.json_message('Push notification subscriber registered.')
def sensor_discovered(service, info): """ Called when a sensor is discovered. """ platform = get_component("{}.{}".format( DOMAIN, DISCOVERY_PLATFORMS[service])) discovered = platform.devices_discovered(hass, config, info) for sensor in discovered: if sensor is not None and sensor not in sensors.values(): sensor.entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(sensor.name)), sensors.keys()) sensors[sensor.entity_id] = sensor sensor.update_ha_state(hass) sensor_group.update_tracked_entity_ids(sensors.keys())
def setup_chromecast(casts, host): """ Tries to convert host to Chromecast object and set it up. """ # Check if already setup if any(cast.host == host for cast in casts.values()): return try: cast = pychromecast.PyChromecast(host) entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format( util.slugify(cast.device.friendly_name)), casts.keys()) casts[entity_id] = cast except pychromecast.ChromecastConnectionError: pass
def see(self, mac=None, dev_id=None, host_name=None, location_name=None, gps=None, gps_accuracy=None, battery=None): """Notify the device tracker that you see a device.""" with self.lock: if mac is None and dev_id is None: raise HomeAssistantError("Neither mac or device id passed in") elif mac is not None: mac = mac.upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or "") or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: device.seen(host_name, location_name, gps, gps_accuracy, battery) if device.track: device.update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, self.consider_home, self.home_range, self.track_new, dev_id, mac, (host_name or dev_id).replace("_", " "), ) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device device.seen(host_name, location_name, gps, gps_accuracy, battery) if device.track: device.update_ha_state() # During init, we ignore the group if self.group is not None: self.group.update_tracked_entity_ids(list(self.group.tracking) + [device.entity_id]) update_config(self.hass.config.path(YAML_DEVICES), dev_id, device)
def async_generate_entity_id( entity_id_format: str, name: str | None, current_ids: Iterable[str] | None = None, hass: HomeAssistant | None = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" name = (name or DEVICE_DEFAULT_NAME).lower() preferred_string = entity_id_format.format(slugify(name)) if current_ids is not None: return ensure_unique_string(preferred_string, current_ids) if hass is None: raise ValueError("Missing required parameter current_ids or hass") test_string = preferred_string tries = 1 while not hass.states.async_available(test_string): tries += 1 test_string = f"{preferred_string}_{tries}" return test_string
def test_ensure_unique_string(): """Test ensure_unique_string.""" assert util.ensure_unique_string("Beer", ["Beer", "Beer_2"]) == "Beer_3" assert util.ensure_unique_string("Beer", ["Wine", "Soda"]) == "Beer"
def setup(hass, config): """ Track states and offer events for switches. """ logger = logging.getLogger(__name__) if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger): return False switch_type = config[DOMAIN][ha.CONF_TYPE] switch_init = get_component('switch.{}'.format(switch_type)) if switch_init is None: logger.error("Error loading switch component %s", switch_type) return False switches = switch_init.get_switches(hass, config[DOMAIN]) if len(switches) == 0: logger.error("No switches found") return False # Setup a dict mapping entity IDs to devices ent_to_switch = {} no_name_count = 1 for switch in switches: name = switch.get_name() if name is None: name = "Switch #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), list(ent_to_switch.keys())) switch.entity_id = entity_id ent_to_switch[entity_id] = switch # pylint: disable=unused-argument def update_states(time, force_reload=False): """ Update states of all switches. """ # First time this method gets called, force_reload should be True if force_reload or \ datetime.now() - update_states.last_updated > \ MIN_TIME_BETWEEN_SCANS: logger.info("Updating switch states") update_states.last_updated = datetime.now() for switch in switches: switch.update_ha_state(hass) update_states(None, True) def handle_switch_service(service): """ Handles calls to the switch services. """ devices = [ ent_to_switch[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_switch ] if not devices: devices = switches for switch in devices: if service.service == SERVICE_TURN_ON: switch.turn_on() else: switch.turn_off() switch.update_ha_state(hass) # Track all wemos in a group group.setup_group(hass, GROUP_NAME_ALL_SWITCHES, ent_to_switch.keys(), False) # Update state every 30 seconds hass.track_time_change(update_states, second=[0, 30]) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) return True
async def async_see( self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy: int = None, battery: int = None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, picture: str = None, icon: str = None, consider_home: timedelta = None): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') if mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: await device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type, consider_home) if device.track: await device.async_update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, consider_home or self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' '), picture=picture, icon=icon, hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device await device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: await device.async_update_ha_state() # During init, we ignore the group if self.group and self.track_new: self.hass.async_create_task( self.hass.async_call( DOMAIN_GROUP, SERVICE_SET, { ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES), ATTR_VISIBLE: False, ATTR_NAME: GROUP_NAME_ALL_DEVICES, ATTR_ADD_ENTITIES: [device.entity_id]})) self.hass.bus.async_fire(EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, ATTR_MAC: device.mac, }) # update known_devices.yaml self.hass.async_create_task( self.async_update_config( self.hass.config.path(YAML_DEVICES), dev_id, device) )
def _read_known_devices_file(self): """ Parse and process the known devices file. """ known_dev_path = self.hass.get_config_path(KNOWN_DEVICES_FILE) # Return if no known devices file exists if not os.path.isfile(known_dev_path): return self.lock.acquire() self.untracked_devices.clear() with open(known_dev_path) as inp: default_last_seen = datetime(1990, 1, 1) # To track which devices need an entity_id assigned need_entity_id = [] # All devices that are still in this set after we read the CSV file # have been removed from the file and thus need to be cleaned up. removed_devices = set(self.tracked.keys()) try: for row in csv.DictReader(inp): device = row['device'] if row['track'] == '1': if device in self.tracked: # Device exists removed_devices.remove(device) else: # We found a new device need_entity_id.append(device) self.tracked[device] = { 'name': row['name'], 'last_seen': default_last_seen } # Update state_attr with latest from file state_attr = {ATTR_FRIENDLY_NAME: row['name']} if row['picture']: state_attr[ATTR_ENTITY_PICTURE] = row['picture'] self.tracked[device]['state_attr'] = state_attr else: self.untracked_devices.add(device) # Remove existing devices that we no longer track for device in removed_devices: entity_id = self.tracked[device]['entity_id'] _LOGGER.info("Removing entity %s", entity_id) self.hass.states.remove(entity_id) self.tracked.pop(device) # Setup entity_ids for the new devices used_entity_ids = [ info['entity_id'] for device, info in self.tracked.items() if device not in need_entity_id ] for device in need_entity_id: name = self.tracked[device]['name'] entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), used_entity_ids) used_entity_ids.append(entity_id) self.tracked[device]['entity_id'] = entity_id if not self.tracked: _LOGGER.warning("No devices to track. Please update %s.", known_dev_path) _LOGGER.info("Loaded devices from %s", known_dev_path) except KeyError: self.invalid_known_devices_file = True _LOGGER.warning(("Invalid known devices file: %s. " "We won't update it with new found devices."), known_dev_path) finally: self.lock.release()
def setup(hass, config): """ Track states and offer events for switches. """ logger = logging.getLogger(__name__) if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger): return False switch_type = config[DOMAIN][ha.CONF_TYPE] if switch_type == 'wemo': switch_init = get_wemo_switches else: logger.error("Unknown switch type specified: %s", switch_type) return False switches = switch_init(config[DOMAIN]) if len(switches) == 0: logger.error("No switches found") return False # Setup a dict mapping entity IDs to devices ent_to_switch = {} no_name_count = 1 for switch in switches: name = switch.get_name() if name is None: name = "Switch #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), list(ent_to_switch.keys())) switch.entity_id = entity_id ent_to_switch[entity_id] = switch # pylint: disable=unused-argument def update_states(time, force_reload=False): """ Update states of all switches. """ # First time this method gets called, force_reload should be True if force_reload or \ datetime.now() - update_states.last_updated > \ MIN_TIME_BETWEEN_SCANS: logger.info("Updating switch states") update_states.last_updated = datetime.now() for switch in switches: switch.update_ha_state(hass) update_states(None, True) def handle_switch_service(service): """ Handles calls to the switch services. """ devices = [ent_to_switch[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_switch] if not devices: devices = switches for switch in devices: if service.service == SERVICE_TURN_ON: switch.turn_on() else: switch.turn_off() switch.update_ha_state(hass) # Track all wemos in a group group.setup_group(hass, GROUP_NAME_ALL_SWITCHES, ent_to_switch.keys(), False) # Update state every 30 seconds hass.track_time_change(update_states, second=[0, 30]) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) return True
def async_see(self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy=None, battery: str = None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, picture: str = None, icon: str = None): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: yield from device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: yield from device.async_update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' '), picture=picture, icon=icon, hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device yield from device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: yield from device.async_update_ha_state() # During init, we ignore the group if self.group and self.track_new: self.group.async_set_group( self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False, name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id]) # lookup mac vendor string to be stored in config yield from device.set_vendor_for_mac() self.hass.bus.async_fire(EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, ATTR_MAC: device.mac, ATTR_VENDOR: device.vendor, }) # update known_devices.yaml self.hass.async_add_job( self.async_update_config( self.hass.config.path(YAML_DEVICES), dev_id, device) )
def setup(hass, config): """ Exposes light control via statemachine and services. """ # Load built-in profiles and custom profiles profile_paths = [os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), hass.get_config_path(LIGHT_PROFILES_FILE)] profiles = {} for profile_path in profile_paths: if os.path.isfile(profile_path): with open(profile_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 %s", profile_path) return False lights = platform_devices_from_config(config, DOMAIN, hass, _LOGGER) if not lights: return False ent_to_light = {} no_name_count = 1 for light in lights: name = light.get_name() if name is None: name = "Light #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), ent_to_light.keys()) light.entity_id = entity_id ent_to_light[entity_id] = light # pylint: disable=unused-argument def update_lights_state(now): """ Update the states of all the lights. """ for light in lights: light.update_ha_state(hass) update_lights_state(None) # Track all lights in a group group.setup_group( hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), 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 lights = [ent_to_light[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_light] if not lights: lights = list(ent_to_light.values()) params = {} transition = util.convert(dat.get(ATTR_TRANSITION), int) if transition is not None: params[ATTR_TRANSITION] = transition if service.service == SERVICE_TURN_OFF: for light in lights: # pylint: disable=star-args light.turn_off(**params) 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: *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile if ATTR_BRIGHTNESS in dat: # We pass in the old value as the default parameter if parsing # of the new one goes wrong. params[ATTR_BRIGHTNESS] = util.convert( dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS)) if ATTR_XY_COLOR in dat: try: # xy_color should be a list containing 2 floats xycolor = dat.get(ATTR_XY_COLOR) # Without this check, a xycolor with value '99' would work if not isinstance(xycolor, str): params[ATTR_XY_COLOR] = [float(val) for val in xycolor] 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: params[ATTR_XY_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 if ATTR_FLASH in dat: if dat[ATTR_FLASH] == FLASH_SHORT: params[ATTR_FLASH] = FLASH_SHORT elif dat[ATTR_FLASH] == FLASH_LONG: params[ATTR_FLASH] = FLASH_LONG for light in lights: # pylint: disable=star-args light.turn_on(**params) for light in lights: light.update_ha_state(hass, 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
def async_see(self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy=None, battery: str = None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: yield from device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: yield from device.async_update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device(self.hass, self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' ')) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device yield from device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: yield from device.async_update_ha_state() self.hass.bus.async_fire(EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, }) # During init, we ignore the group if self.group is not None: yield from self.group.async_update_tracked_entity_ids( list(self.group.tracking) + [device.entity_id]) # lookup mac vendor string to be stored in config yield from device.set_vendor_for_mac() # update known_devices.yaml self.hass.async_add_job( self.async_update_config(self.hass.config.path(YAML_DEVICES), dev_id, device))
def setup(hass, config): """ Listen for chromecast events. """ logger = logging.getLogger(__name__) try: import pychromecast except ImportError: logger.exception(("Failed to import pychromecast. " "Did you maybe not install the 'pychromecast' " "dependency?")) return False if CONF_HOSTS in config[DOMAIN]: hosts = config[DOMAIN][CONF_HOSTS].split(",") # If no hosts given, scan for chromecasts else: logger.info("Scanning for Chromecasts") hosts = pychromecast.discover_chromecasts() casts = {} for host in hosts: try: cast = pychromecast.PyChromecast(host) entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify( cast.device.friendly_name)), casts.keys()) casts[entity_id] = cast except pychromecast.ChromecastConnectionError: pass if not casts: logger.error("Could not find Chromecasts") return False def update_chromecast_state(entity_id, chromecast): """ Retrieve state of Chromecast and update statemachine. """ chromecast.refresh() status = chromecast.app state_attr = {ATTR_FRIENDLY_NAME: chromecast.device.friendly_name} if status and status.app_id != pychromecast.APP_ID['HOME']: state = status.app_id ramp = chromecast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state != pychromecast.RAMP_STATE_UNKNOWN: if ramp.state == pychromecast.RAMP_STATE_PLAYING: state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING else: state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED if ramp.content_id: state_attr[ATTR_MEDIA_CONTENT_ID] = ramp.content_id if ramp.title: state_attr[ATTR_MEDIA_TITLE] = ramp.title if ramp.artist: state_attr[ATTR_MEDIA_ARTIST] = ramp.artist if ramp.album: state_attr[ATTR_MEDIA_ALBUM] = ramp.album if ramp.image_url: state_attr[ATTR_MEDIA_IMAGE_URL] = ramp.image_url if ramp.duration: state_attr[ATTR_MEDIA_DURATION] = ramp.duration state_attr[ATTR_MEDIA_VOLUME] = ramp.volume else: state = STATE_NO_APP hass.states.set(entity_id, state, state_attr) def update_chromecast_states(time): # pylint: disable=unused-argument """ Updates all chromecast states. """ logger.info("Updating Chromecast status") for entity_id, cast in casts.items(): update_chromecast_state(entity_id, cast) def _service_to_entities(service): """ Helper method to get entities from service. """ entity_ids = extract_entity_ids(hass, service) if entity_ids: for entity_id in entity_ids: cast = casts.get(entity_id) if cast: yield entity_id, cast else: yield from casts.items() def turn_off_service(service): """ Service to exit any running app on the specified ChromeCast and shows idle screen. Will quit all ChromeCasts if nothing specified. """ for entity_id, cast in _service_to_entities(service): cast.quit_app() update_chromecast_state(entity_id, cast) def volume_up_service(service): """ Service to send the chromecast the command for volume up. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.volume_up() def volume_down_service(service): """ Service to send the chromecast the command for volume down. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.volume_down() def media_play_pause_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.playpause() def media_play_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state == pychromecast.RAMP_STATE_STOPPED: ramp.playpause() def media_pause_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state == pychromecast.RAMP_STATE_PLAYING: ramp.playpause() def media_next_track_service(service): """ Service to send the chromecast the command for next track. """ for entity_id, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: next(ramp) update_chromecast_state(entity_id, cast) def play_youtube_video_service(service, video_id): """ Plays specified video_id on the Chromecast's YouTube channel. """ if video_id: # if service.data.get('video') returned None for entity_id, cast in _service_to_entities(service): pychromecast.play_youtube_video(video_id, cast.host) update_chromecast_state(entity_id, cast) hass.track_time_change(update_chromecast_states) hass.services.register(DOMAIN, SERVICE_TURN_OFF, turn_off_service) hass.services.register(DOMAIN, SERVICE_VOLUME_UP, volume_up_service) hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN, volume_down_service) hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, media_play_pause_service) hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY, media_play_service) hass.services.register(DOMAIN, SERVICE_MEDIA_PAUSE, media_pause_service) hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, media_next_track_service) hass.services.register( DOMAIN, "start_fireplace", lambda service: play_youtube_video_service(service, "eyU3bRy2x44")) hass.services.register( DOMAIN, "start_epic_sax", lambda service: play_youtube_video_service(service, "kxopViU98Xo")) hass.services.register( DOMAIN, SERVICE_YOUTUBE_VIDEO, lambda service: play_youtube_video_service(service, service.data.get('video'))) update_chromecast_states(None) return True
def setup(hass, config): """ Exposes light control via statemachine and services. """ if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER): return False light_type = config[DOMAIN][ha.CONF_TYPE] if light_type == 'hue': light_init = get_hue_lights else: _LOGGER.error("Unknown light type specified: %s", light_type) return False lights = light_init(hass, config[DOMAIN]) if len(lights) == 0: _LOGGER.error("No lights found") return False ent_to_light = {} no_name_count = 1 for light in lights: name = light.get_name() if name is None: name = "Light #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), list(ent_to_light.keys())) light.entity_id = entity_id ent_to_light[entity_id] = light # Load built-in profiles and custom profiles profile_paths = [os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), hass.get_config_path(LIGHT_PROFILES_FILE)] profiles = {} for profile_path in profile_paths: if os.path.isfile(profile_path): with open(profile_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 %s", profile_path) return False # pylint: disable=unused-argument def update_lights_state(now): """ Update the states of all the lights. """ for light in lights: light.update_ha_state(hass) update_lights_state(None) # Track all lights in a group group.setup_group( hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), 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 lights = [ent_to_light[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_light] if not lights: lights = list(ent_to_light.values()) transition = util.convert(dat.get(ATTR_TRANSITION), int) if service.service == SERVICE_TURN_OFF: for light in lights: light.turn_off(transition=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 for light in lights: light.turn_on(transition=transition, brightness=bright, xy_color=color) for light in lights: light.update_ha_state(hass, 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
async def async_see(self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy: int = None, battery: int = None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, picture: str = None, icon: str = None, consider_home: timedelta = None): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: raise HomeAssistantError('Neither mac or device id passed in') elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or '') or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: await device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes, source_type, consider_home) if device.track: await device.async_update_ha_state() return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device(self.hass, consider_home or self.consider_home, self.track_new, dev_id, mac, (host_name or dev_id).replace('_', ' '), picture=picture, icon=icon, hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device await device.async_seen(host_name, location_name, gps, gps_accuracy, battery, attributes, source_type) if device.track: await device.async_update_ha_state() # During init, we ignore the group if self.group and self.track_new: self.hass.async_create_task( self.hass.async_call( DOMAIN_GROUP, SERVICE_SET, { ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES), ATTR_VISIBLE: False, ATTR_NAME: GROUP_NAME_ALL_DEVICES, ATTR_ADD_ENTITIES: [device.entity_id] })) self.hass.bus.async_fire( EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, ATTR_MAC: device.mac, }) # update known_devices.yaml self.hass.async_create_task( self.async_update_config(self.hass.config.path(YAML_DEVICES), dev_id, device))
def setup(hass, config): """ Track states and offer events for switches. """ logger = logging.getLogger(__name__) switches = platform_devices_from_config(config, DOMAIN, hass, logger) if not switches: return False # Setup a dict mapping entity IDs to devices ent_to_switch = {} no_name_count = 1 for switch in switches: name = switch.get_name() if name is None: name = "Switch #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), ent_to_switch.keys()) switch.entity_id = entity_id ent_to_switch[entity_id] = switch # pylint: disable=unused-argument @util.Throttle(MIN_TIME_BETWEEN_SCANS) def update_states(now): """ Update states of all switches. """ logger.info("Updating switch states") for switch in switches: switch.update_ha_state(hass) update_states(None) def handle_switch_service(service): """ Handles calls to the switch services. """ devices = [ent_to_switch[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_switch] if not devices: devices = switches for switch in devices: if service.service == SERVICE_TURN_ON: switch.turn_on() else: switch.turn_off() switch.update_ha_state(hass) # Track all switches in a group group.setup_group(hass, GROUP_NAME_ALL_SWITCHES, ent_to_switch.keys(), False) # Update state every 30 seconds hass.track_time_change(update_states, second=[0, 30]) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) return True
def setup(hass, config): """ Exposes light control via statemachine and services. """ # Load built-in profiles and custom profiles profile_paths = [os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), hass.get_config_path(LIGHT_PROFILES_FILE)] profiles = {} for profile_path in profile_paths: if os.path.isfile(profile_path): with open(profile_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 %s", profile_path) return False lights = platform_devices_from_config(config, DOMAIN, hass, _LOGGER) if not lights: return False ent_to_light = {} no_name_count = 1 for light in lights: name = light.get_name() if name is None: name = "Light #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), ent_to_light.keys()) light.entity_id = entity_id ent_to_light[entity_id] = light # pylint: disable=unused-argument def update_lights_state(now): """ Update the states of all the lights. """ for light in lights: light.update_ha_state(hass) update_lights_state(None) # Track all lights in a group group.setup_group( hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), 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 lights = [ent_to_light[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_light] if not lights: lights = list(ent_to_light.values()) params = {} transition = util.convert(dat.get(ATTR_TRANSITION), int) if transition is not None: params[ATTR_TRANSITION] = transition if service.service == SERVICE_TURN_OFF: for light in lights: # pylint: disable=star-args light.turn_off(**params) 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: *params[ATTR_XY_COLOR], params[ATTR_BRIGHTNESS] = profile if ATTR_BRIGHTNESS in dat: # We pass in the old value as the default parameter if parsing # of the new one goes wrong. params[ATTR_BRIGHTNESS] = util.convert( dat.get(ATTR_BRIGHTNESS), int, params.get(ATTR_BRIGHTNESS)) if ATTR_XY_COLOR in dat: try: # xy_color should be a list containing 2 floats xycolor = dat.get(ATTR_XY_COLOR) # Without this check, a xycolor with value '99' would work if not isinstance(xycolor, str): params[ATTR_XY_COLOR] = [float(val) for val in xycolor] 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: params[ATTR_XY_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 for light in lights: # pylint: disable=star-args light.turn_on(**params) for light in lights: light.update_ha_state(hass, 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
def setup(hass, config): """ Exposes light control via statemachine and services. """ if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER): return False light_type = config[DOMAIN][ha.CONF_TYPE] light_init = get_component('light.{}'.format(light_type)) if light_init is None: _LOGGER.error("Unknown light type specified: %s", light_type) return False lights = light_init.get_lights(hass, config[DOMAIN]) if len(lights) == 0: _LOGGER.error("No lights found") return False ent_to_light = {} no_name_count = 1 for light in lights: name = light.get_name() if name is None: name = "Light #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), list(ent_to_light.keys())) light.entity_id = entity_id ent_to_light[entity_id] = light # Load built-in profiles and custom profiles profile_paths = [ os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), hass.get_config_path(LIGHT_PROFILES_FILE) ] profiles = {} for profile_path in profile_paths: if os.path.isfile(profile_path): with open(profile_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 %s", profile_path) return False # pylint: disable=unused-argument def update_lights_state(now): """ Update the states of all the lights. """ for light in lights: light.update_ha_state(hass) update_lights_state(None) # Track all lights in a group group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, ent_to_light.keys(), 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 lights = [ ent_to_light[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_light ] if not lights: lights = list(ent_to_light.values()) transition = util.convert(dat.get(ATTR_TRANSITION), int) if service.service == SERVICE_TURN_OFF: for light in lights: light.turn_off(transition=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 for light in lights: light.turn_on(transition=transition, brightness=bright, xy_color=color) for light in lights: light.update_ha_state(hass, 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
def test_ensure_unique_string(self): """Test ensure_unique_string.""" assert "Beer_3" == \ util.ensure_unique_string("Beer", ["Beer", "Beer_2"]) assert "Beer" == \ util.ensure_unique_string("Beer", ["Wine", "Soda"])
def setup(hass, config): """ Track states and offer events for switches. """ logger = logging.getLogger(__name__) switches = platform_devices_from_config(config, DOMAIN, hass, logger) if not switches: return False # Setup a dict mapping entity IDs to devices ent_to_switch = {} no_name_count = 1 for switch in switches: name = switch.get_name() if name is None: name = "Switch #{}".format(no_name_count) no_name_count += 1 entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), ent_to_switch.keys()) switch.entity_id = entity_id ent_to_switch[entity_id] = switch # pylint: disable=unused-argument @util.Throttle(MIN_TIME_BETWEEN_SCANS) def update_states(now): """ Update states of all switches. """ logger.info("Updating switch states") for switch in switches: switch.update_ha_state(hass) update_states(None) def handle_switch_service(service): """ Handles calls to the switch services. """ devices = [ ent_to_switch[entity_id] for entity_id in extract_entity_ids(hass, service) if entity_id in ent_to_switch ] if not devices: devices = switches for switch in devices: if service.service == SERVICE_TURN_ON: switch.turn_on() else: switch.turn_off() switch.update_ha_state(hass) # Track all switches in a group group.setup_group(hass, GROUP_NAME_ALL_SWITCHES, ent_to_switch.keys(), False) # Update state every 30 seconds hass.track_time_change(update_states, second=[0, 30]) hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) return True
if device.track: device.async_write_ha_state() return # If it's None then device is not None and we can't get here. assert dev_id is not None # Guard from calling see on entity registry entities. entity_id = f"{DOMAIN}.{dev_id}" if registry.async_is_registered(entity_id): LOGGER.error("The see service is not supported for this entity %s", entity_id) return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, consider_home or self.consider_home, self.track_new, dev_id, mac, picture=picture, icon=icon, ) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device await device.async_seen( host_name,
async def async_see( self, mac: str = None, dev_id: str = None, host_name: str = None, location_name: str = None, gps: GPSType = None, gps_accuracy: int = None, battery: int = None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, picture: str = None, icon: str = None, consider_home: timedelta = None, ): """Notify the device tracker that you see a device. This method is a coroutine. """ registry = await async_get_registry(self.hass) if mac is None and dev_id is None: raise HomeAssistantError("Neither mac or device id passed in") if mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: dev_id = util.slugify(host_name or "") or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: await device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type, consider_home, ) if device.track: await device.async_update_ha_state() return # Guard from calling see on entity registry entities. entity_id = f"{DOMAIN}.{dev_id}" if registry.async_is_registered(entity_id): LOGGER.error("The see service is not supported for this entity %s", entity_id) return # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( self.hass, consider_home or self.consider_home, self.track_new, dev_id, mac, picture=picture, icon=icon, ) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device await device.async_seen( host_name, location_name, gps, gps_accuracy, battery, attributes, source_type, ) if device.track: await device.async_update_ha_state() self.hass.bus.async_fire( EVENT_NEW_DEVICE, { ATTR_ENTITY_ID: device.entity_id, ATTR_HOST_NAME: device.host_name, ATTR_MAC: device.mac, }, ) # update known_devices.yaml self.hass.async_create_task( self.async_update_config(self.hass.config.path(YAML_DEVICES), dev_id, device))
def test_ensure_unique_string(self): """Test ensure_unique_string.""" self.assertEqual("Beer_3", util.ensure_unique_string("Beer", ["Beer", "Beer_2"])) self.assertEqual("Beer", util.ensure_unique_string("Beer", ["Wine", "Soda"]))
def setup(hass, config): """ Listen for chromecast events. """ logger = logging.getLogger(__name__) try: import pychromecast except ImportError: logger.exception(("Failed to import pychromecast. " "Did you maybe not install the 'pychromecast' " "dependency?")) return False if 'hosts' in config[DOMAIN]: hosts = config[DOMAIN]['hosts'].split(",") # If no hosts given, scan for chromecasts else: logger.info("Scanning for Chromecasts") hosts = pychromecast.discover_chromecasts() casts = {} for host in hosts: try: cast = pychromecast.PyChromecast(host) entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format( util.slugify(cast.device.friendly_name)), list(casts.keys())) casts[entity_id] = cast except pychromecast.ChromecastConnectionError: pass if not casts: logger.error("Could not find Chromecasts") return False def update_chromecast_state(entity_id, chromecast): """ Retrieve state of Chromecast and update statemachine. """ chromecast.refresh() status = chromecast.app state_attr = {components.ATTR_FRIENDLY_NAME: chromecast.device.friendly_name} if status and status.app_id != pychromecast.APP_ID['HOME']: state = status.app_id ramp = chromecast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state != pychromecast.RAMP_STATE_UNKNOWN: if ramp.state == pychromecast.RAMP_STATE_PLAYING: state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING else: state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED if ramp.content_id: state_attr[ATTR_MEDIA_CONTENT_ID] = ramp.content_id if ramp.title: state_attr[ATTR_MEDIA_TITLE] = ramp.title if ramp.artist: state_attr[ATTR_MEDIA_ARTIST] = ramp.artist if ramp.album: state_attr[ATTR_MEDIA_ALBUM] = ramp.album if ramp.image_url: state_attr[ATTR_MEDIA_IMAGE_URL] = ramp.image_url if ramp.duration: state_attr[ATTR_MEDIA_DURATION] = ramp.duration state_attr[ATTR_MEDIA_VOLUME] = ramp.volume else: state = STATE_NO_APP hass.states.set(entity_id, state, state_attr) def update_chromecast_states(time): # pylint: disable=unused-argument """ Updates all chromecast states. """ logger.info("Updating Chromecast status") for entity_id, cast in casts.items(): update_chromecast_state(entity_id, cast) def _service_to_entities(service): """ Helper method to get entities from service. """ entity_ids = components.extract_entity_ids(hass, service) if entity_ids: for entity_id in entity_ids: cast = casts.get(entity_id) if cast: yield entity_id, cast else: yield from casts.items() def turn_off_service(service): """ Service to exit any running app on the specified ChromeCast and shows idle screen. Will quit all ChromeCasts if nothing specified. """ for entity_id, cast in _service_to_entities(service): cast.quit_app() update_chromecast_state(entity_id, cast) def volume_up_service(service): """ Service to send the chromecast the command for volume up. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.volume_up() def volume_down_service(service): """ Service to send the chromecast the command for volume down. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.volume_down() def media_play_pause_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: ramp.playpause() def media_play_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state == pychromecast.RAMP_STATE_STOPPED: ramp.playpause() def media_pause_service(service): """ Service to send the chromecast the command for play/pause. """ for _, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp and ramp.state == pychromecast.RAMP_STATE_PLAYING: ramp.playpause() def media_next_track_service(service): """ Service to send the chromecast the command for next track. """ for entity_id, cast in _service_to_entities(service): ramp = cast.get_protocol(pychromecast.PROTOCOL_RAMP) if ramp: next(ramp) update_chromecast_state(entity_id, cast) def play_youtube_video_service(service, video_id): """ Plays specified video_id on the Chromecast's YouTube channel. """ if video_id: # if service.data.get('video') returned None for entity_id, cast in _service_to_entities(service): pychromecast.play_youtube_video(video_id, cast.host) update_chromecast_state(entity_id, cast) hass.track_time_change(update_chromecast_states) hass.services.register(DOMAIN, components.SERVICE_TURN_OFF, turn_off_service) hass.services.register(DOMAIN, components.SERVICE_VOLUME_UP, volume_up_service) hass.services.register(DOMAIN, components.SERVICE_VOLUME_DOWN, volume_down_service) hass.services.register(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, media_play_pause_service) hass.services.register(DOMAIN, components.SERVICE_MEDIA_PLAY, media_play_service) hass.services.register(DOMAIN, components.SERVICE_MEDIA_PAUSE, media_pause_service) hass.services.register(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, media_next_track_service) hass.services.register(DOMAIN, "start_fireplace", lambda service: play_youtube_video_service(service, "eyU3bRy2x44")) hass.services.register(DOMAIN, "start_epic_sax", lambda service: play_youtube_video_service(service, "kxopViU98Xo")) hass.services.register(DOMAIN, SERVICE_YOUTUBE_VIDEO, lambda service: play_youtube_video_service(service, service.data.get( 'video'))) update_chromecast_states(None) return True
def _read_known_devices_file(self): """ Parse and process the known devices file. """ known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE) # Return if no known devices file exists if not os.path.isfile(known_dev_path): return self.lock.acquire() self.untracked_devices.clear() with open(known_dev_path) as inp: default_last_seen = datetime(1990, 1, 1) # To track which devices need an entity_id assigned need_entity_id = [] # All devices that are still in this set after we read the CSV file # have been removed from the file and thus need to be cleaned up. removed_devices = set(self.tracked.keys()) try: for row in csv.DictReader(inp): device = row['device'].upper() if row['track'] == '1': if device in self.tracked: # Device exists removed_devices.remove(device) else: # We found a new device need_entity_id.append(device) self.tracked[device] = { 'name': row['name'], 'last_seen': default_last_seen } # Update state_attr with latest from file state_attr = { ATTR_FRIENDLY_NAME: row['name'] } if row['picture']: state_attr[ATTR_ENTITY_PICTURE] = row['picture'] self.tracked[device]['state_attr'] = state_attr else: self.untracked_devices.add(device) # Remove existing devices that we no longer track for device in removed_devices: entity_id = self.tracked[device]['entity_id'] _LOGGER.info("Removing entity %s", entity_id) self.hass.states.remove(entity_id) self.tracked.pop(device) # Setup entity_ids for the new devices used_entity_ids = [info['entity_id'] for device, info in self.tracked.items() if device not in need_entity_id] for device in need_entity_id: name = self.tracked[device]['name'] entity_id = util.ensure_unique_string( ENTITY_ID_FORMAT.format(util.slugify(name)), used_entity_ids) used_entity_ids.append(entity_id) self.tracked[device]['entity_id'] = entity_id if not self.tracked: _LOGGER.warning( "No devices to track. Please update %s.", known_dev_path) _LOGGER.info("Loaded devices from %s", known_dev_path) except KeyError: self.invalid_known_devices_file = True _LOGGER.warning( ("Invalid known devices file: %s. " "We won't update it with new found devices."), known_dev_path) finally: self.lock.release()