Ejemplo n.º 1
0
class AlexaConfigStore:
    """A configuration store for Alexa."""

    _STORAGE_VERSION = 1
    _STORAGE_KEY = DOMAIN

    def __init__(self, hass):
        """Initialize a configuration store."""
        self._data = None
        self._hass = hass
        self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY)

    @property
    def authorized(self):
        """Return authorization status."""
        return self._data[STORE_AUTHORIZED]

    @callback
    def set_authorized(self, authorized):
        """Set authorization status."""
        if authorized != self._data[STORE_AUTHORIZED]:
            self._data[STORE_AUTHORIZED] = authorized
            self._store.async_delay_save(lambda: self._data, 1.0)

    async def async_load(self):
        """Load saved configuration from disk."""
        if data := await self._store.async_load():
            self._data = data
        else:
Ejemplo n.º 2
0
class CacheStore:
    _STORAGE_VERSION = 1
    _STORAGE_KEY = f'{DOMAIN}.cache'

    def __init__(self, hass):
        self._hass = hass
        self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY)
        self._data = {STORE_CACHE_ATTRS: {}}

    def get_attr_value(self, entity_id: str, attr: str) -> Any | None:
        """Return a cached value of attribute for entity."""
        if entity_id not in self._data[STORE_CACHE_ATTRS]:
            return None

        return self._data[STORE_CACHE_ATTRS][entity_id].get(attr)

    @callback
    def save_attr_value(self, entity_id: str, attr: str, value: Any):
        """Cache entity's attribute value to disk."""
        if entity_id not in self._data[STORE_CACHE_ATTRS]:
            self._data[STORE_CACHE_ATTRS][entity_id] = {}
            has_changed = True
        else:
            has_changed = self._data[STORE_CACHE_ATTRS][entity_id][attr] != value

        self._data[STORE_CACHE_ATTRS][entity_id][attr] = value

        if has_changed:
            self._store.async_delay_save(lambda: self._data, 5.0)

    async def async_load(self):
        data = await self._store.async_load()
        if data:
            self._data = data
Ejemplo n.º 3
0
class GoogleConfigStore:
    """A configuration store for google assistant."""

    _STORAGE_VERSION = 1
    _STORAGE_KEY = DOMAIN

    def __init__(self, hass):
        """Initialize a configuration store."""
        self._hass = hass
        self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY)
        self._data = {STORE_AGENT_USER_IDS: {}}

    @property
    def agent_user_ids(self):
        """Return a list of connected agent user_ids."""
        return self._data[STORE_AGENT_USER_IDS]

    @callback
    def add_agent_user_id(self, agent_user_id):
        """Add an agent user id to store."""
        if agent_user_id not in self._data[STORE_AGENT_USER_IDS]:
            self._data[STORE_AGENT_USER_IDS][agent_user_id] = {}
            self._store.async_delay_save(lambda: self._data, 1.0)

    @callback
    def pop_agent_user_id(self, agent_user_id):
        """Remove agent user id from store."""
        if agent_user_id in self._data[STORE_AGENT_USER_IDS]:
            self._data[STORE_AGENT_USER_IDS].pop(agent_user_id, None)
            self._store.async_delay_save(lambda: self._data, 1.0)

    async def async_load(self):
        """Store current configuration to disk."""
        if data := await self._store.async_load():
            self._data = data
Ejemplo n.º 4
0
class EntityMapStorage:
    """
    Holds a cache of entity structure data from a paired HomeKit device.

    HomeKit has a cacheable entity map that describes how an IP or BLE
    endpoint is structured. This object holds the latest copy of that data.

    An endpoint is made of accessories, services and characteristics. It is
    safe to cache this data until the c# discovery data changes.

    Caching this data means we can add HomeKit devices to HA immediately at
    start even if discovery hasn't seen them yet or they are out of range. It
    is also important for BLE devices - accessing the entity structure is
    very slow for these devices.
    """

    def __init__(self, hass):
        """Create a new entity map store."""
        self.hass = hass
        self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY)
        self.storage_data = {}

    async def async_initialize(self):
        """Get the pairing cache data."""
        raw_storage = await self.store.async_load()
        if not raw_storage:
            # There is no cached data about HomeKit devices yet
            return

        self.storage_data = raw_storage.get("pairings", {})

    def get_map(self, homekit_id):
        """Get a pairing cache item."""
        return self.storage_data.get(homekit_id)

    def async_create_or_update_map(self, homekit_id, config_num, accessories):
        """Create a new pairing cache."""
        data = {"config_num": config_num, "accessories": accessories}
        self.storage_data[homekit_id] = data
        self._async_schedule_save()
        return data

    def async_delete_map(self, homekit_id):
        """Delete pairing cache."""
        if homekit_id not in self.storage_data:
            return

        self.storage_data.pop(homekit_id)
        self._async_schedule_save()

    @callback
    def _async_schedule_save(self):
        """Schedule saving the entity map cache."""
        self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY)

    @callback
    def _data_to_save(self):
        """Return data of entity map to store in a file."""
        return {"pairings": self.storage_data}
Ejemplo n.º 5
0
class PersonManager:
    """Manage person data."""

    def __init__(self, hass: HomeAssistantType, component: EntityComponent,
                 config_persons):
        """Initialize person storage."""
        self.hass = hass
        self.component = component
        self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
        self.storage_data = None

        config_data = self.config_data = OrderedDict()
        for conf in config_persons:
            person_id = conf[CONF_ID]

            if person_id in config_data:
                _LOGGER.error(
                    "Found config user with duplicate ID: %s", person_id)
                continue

            config_data[person_id] = conf

    @property
    def storage_persons(self):
        """Iterate over persons stored in storage."""
        return list(self.storage_data.values())

    @property
    def config_persons(self):
        """Iterate over persons stored in config."""
        return list(self.config_data.values())

    async def async_initialize(self):
        """Get the person data."""
        raw_storage = await self.store.async_load()

        if raw_storage is None:
            raw_storage = {
                'persons': []
            }

        storage_data = self.storage_data = OrderedDict()

        for person in raw_storage['persons']:
            storage_data[person[CONF_ID]] = person

        entities = []
        seen_users = set()

        for person_conf in self.config_data.values():
            person_id = person_conf[CONF_ID]
            user_id = person_conf.get(CONF_USER_ID)

            if user_id is not None:
                if await self.hass.auth.async_get_user(user_id) is None:
                    _LOGGER.error(
                        "Invalid user_id detected for person %s", person_id)
                    continue

                if user_id in seen_users:
                    _LOGGER.error(
                        "Duplicate user_id %s detected for person %s",
                        user_id, person_id)
                    continue

                seen_users.add(user_id)

            entities.append(Person(person_conf, False))

        # To make sure IDs don't overlap between config/storage
        seen_persons = set(self.config_data)

        for person_conf in storage_data.values():
            person_id = person_conf[CONF_ID]
            user_id = person_conf[CONF_USER_ID]

            if person_id in seen_persons:
                _LOGGER.error(
                    "Skipping adding person from storage with same ID as"
                    " configuration.yaml entry: %s", person_id)
                continue

            if user_id is not None and user_id in seen_users:
                _LOGGER.error(
                    "Duplicate user_id %s detected for person %s",
                    user_id, person_id)
                continue

            # To make sure all users have just 1 person linked.
            seen_users.add(user_id)

            entities.append(Person(person_conf, True))

        if entities:
            await self.component.async_add_entities(entities)

        self.hass.bus.async_listen(EVENT_USER_REMOVED, self._user_removed)

    async def async_create_person(
            self, *, name, device_trackers=None, user_id=None):
        """Create a new person."""
        if not name:
            raise ValueError("Name is required")

        if user_id is not None:
            await self._validate_user_id(user_id)

        person = {
            CONF_ID: uuid.uuid4().hex,
            CONF_NAME: name,
            CONF_USER_ID: user_id,
            CONF_DEVICE_TRACKERS: device_trackers or [],
        }
        self.storage_data[person[CONF_ID]] = person
        self._async_schedule_save()
        await self.component.async_add_entities([Person(person, True)])
        return person

    async def async_update_person(self, person_id, *, name=_UNDEF,
                                  device_trackers=_UNDEF, user_id=_UNDEF):
        """Update person."""
        current = self.storage_data.get(person_id)

        if current is None:
            raise ValueError("Invalid person specified.")

        changes = {
            key: value for key, value in (
                (CONF_NAME, name),
                (CONF_DEVICE_TRACKERS, device_trackers),
                (CONF_USER_ID, user_id)
            ) if value is not _UNDEF and current[key] != value
        }

        if CONF_USER_ID in changes and user_id is not None:
            await self._validate_user_id(user_id)

        self.storage_data[person_id].update(changes)
        self._async_schedule_save()

        for entity in self.component.entities:
            if entity.unique_id == person_id:
                entity.person_updated()
                break

        return self.storage_data[person_id]

    async def async_delete_person(self, person_id):
        """Delete person."""
        if person_id not in self.storage_data:
            raise ValueError("Invalid person specified.")

        self.storage_data.pop(person_id)
        self._async_schedule_save()
        ent_reg = await self.hass.helpers.entity_registry.async_get_registry()

        for entity in self.component.entities:
            if entity.unique_id == person_id:
                await entity.async_remove()
                ent_reg.async_remove(entity.entity_id)
                break

    @callback
    def _async_schedule_save(self) -> None:
        """Schedule saving the area registry."""
        self.store.async_delay_save(self._data_to_save, SAVE_DELAY)

    @callback
    def _data_to_save(self) -> dict:
        """Return data of area registry to store in a file."""
        return {
            'persons': list(self.storage_data.values())
        }

    async def _validate_user_id(self, user_id):
        """Validate the used user_id."""
        if await self.hass.auth.async_get_user(user_id) is None:
            raise ValueError("User does not exist")

        if any(person for person
               in chain(self.storage_data.values(),
                        self.config_data.values())
               if person.get(CONF_USER_ID) == user_id):
            raise ValueError("User already taken")

    async def _user_removed(self, event: Event):
        """Handle event that a person is removed."""
        user_id = event.data['user_id']
        for person in self.storage_data.values():
            if person[CONF_USER_ID] == user_id:
                await self.async_update_person(
                    person_id=person[CONF_ID],
                    user_id=None
                )
Ejemplo n.º 6
0
class LegacyZWaveMigration:
    """Handle the migration from zwave to ozw and zwave_js."""
    def __init__(self, hass: HomeAssistant) -> None:
        """Set up migration instance."""
        self._hass = hass
        self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
        self._data: dict[str, dict[str, ZWaveMigrationData]] = {}

    async def load_data(self) -> None:
        """Load Z-Wave migration data."""
        stored = cast(dict, await self._store.async_load())
        if stored:
            self._data = stored

    @callback
    def save_data(self, config_entry_id: str, entity_id: str,
                  data: ZWaveMigrationData) -> None:
        """Save Z-Wave migration data."""
        if config_entry_id not in self._data:
            self._data[config_entry_id] = {}
        self._data[config_entry_id][entity_id] = data
        self._store.async_delay_save(self._data_to_save, STORAGE_WRITE_DELAY)

    @callback
    def _data_to_save(self) -> dict[str, dict[str, ZWaveMigrationData]]:
        """Return data to save."""
        return self._data

    @callback
    def add_entity_value(
        self,
        entity_id: str,
        entity_values: ZWaveDeviceEntityValues,
    ) -> None:
        """Add info for one entity and Z-Wave value."""
        ent_reg = async_get_entity_registry(self._hass)
        dev_reg = async_get_device_registry(self._hass)

        node = entity_values.primary.node
        entity_entry = ent_reg.async_get(entity_id)
        assert entity_entry
        device_identifier, _ = node_device_id_and_name(
            node, entity_values.primary.instance)
        device_entry = dev_reg.async_get_device({device_identifier}, set())
        assert device_entry

        # Normalize unit of measurement.
        if unit := entity_entry.unit_of_measurement:
            unit = unit.lower()
        if unit == "":
            unit = None

        data: ZWaveMigrationData = {
            "node_id": node.node_id,
            "node_instance": entity_values.primary.instance,
            "command_class": entity_values.primary.command_class,
            "command_class_label": entity_values.primary.label,
            "value_index": entity_values.primary.index,
            "device_id": device_entry.id,
            "domain": entity_entry.domain,
            "entity_id": entity_id,
            "unique_id": entity_entry.unique_id,
            "unit_of_measurement": unit,
        }

        self.save_data(entity_entry.config_entry_id, entity_id, data)
Ejemplo n.º 7
0
class AccessoryAidStorage:
    """
    Holds a map of entity ID to HomeKit ID.

    Will generate new ID's, ensure they are unique and store them to make sure they
    persist over reboots.
    """
    def __init__(self, hass: HomeAssistant):
        """Create a new entity map store."""
        self.hass = hass
        self.store = Store(hass, AID_MANAGER_STORAGE_VERSION,
                           AID_MANAGER_STORAGE_KEY)
        self.allocations = {}
        self.allocated_aids = set()

        self._entity_registry = None

    async def async_initialize(self):
        """Load the latest AID data."""
        self._entity_registry = (
            await self.hass.helpers.entity_registry.async_get_registry())

        raw_storage = await self.store.async_load()
        if not raw_storage:
            # There is no data about aid allocations yet
            return

        # Remove the UNIQUE_IDS_KEY in 0.112 and later
        # The beta version used UNIQUE_IDS_KEY but
        # since we now have entity ids in the dict
        # we use ALLOCATIONS_KEY but check for
        # UNIQUE_IDS_KEY in case the database has not
        # been upgraded yet
        self.allocations = raw_storage.get(ALLOCATIONS_KEY,
                                           raw_storage.get(UNIQUE_IDS_KEY, {}))
        self.allocated_aids = set(self.allocations.values())

    def get_or_allocate_aid_for_entity_id(self, entity_id: str):
        """Generate a stable aid for an entity id."""
        entity = self._entity_registry.async_get(entity_id)
        if not entity:
            return self._get_or_allocate_aid(None, entity_id)

        sys_unique_id = get_system_unique_id(entity)
        return self._get_or_allocate_aid(sys_unique_id, entity_id)

    def _get_or_allocate_aid(self, unique_id: str, entity_id: str):
        """Allocate (and return) a new aid for an accessory."""
        # Prefer the unique_id over the
        # entitiy_id
        storage_key = unique_id or entity_id

        if storage_key in self.allocations:
            return self.allocations[storage_key]

        for aid in _generate_aids(unique_id, entity_id):
            if aid in INVALID_AIDS:
                continue
            if aid not in self.allocated_aids:
                self.allocations[storage_key] = aid
                self.allocated_aids.add(aid)
                self.async_schedule_save()
                return aid

        raise ValueError(
            f"Unable to generate unique aid allocation for {entity_id} [{unique_id}]"
        )

    def delete_aid(self, storage_key: str):
        """Delete an aid allocation."""
        if storage_key not in self.allocations:
            return

        aid = self.allocations.pop(storage_key)
        self.allocated_aids.discard(aid)
        self.async_schedule_save()

    @callback
    def async_schedule_save(self):
        """Schedule saving the entity map cache."""
        self.store.async_delay_save(self._data_to_save, AID_MANAGER_SAVE_DELAY)

    async def async_save(self):
        """Save the entity map cache."""
        return await self.store.async_save(self._data_to_save())

    @callback
    def _data_to_save(self):
        """Return data of entity map to store in a file."""
        return {ALLOCATIONS_KEY: self.allocations}
Ejemplo n.º 8
0
class ZhaStorage:
    """Class to hold a registry of zha devices."""

    def __init__(self, hass: HomeAssistant) -> None:
        """Initialize the zha device storage."""
        self.hass: HomeAssistant = hass
        self.devices: MutableMapping[str, ZhaDeviceEntry] = {}
        self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)

    @callback
    def async_create_device(self, device: ZhaDeviceType) -> ZhaDeviceEntry:
        """Create a new ZhaDeviceEntry."""
        device_entry: ZhaDeviceEntry = ZhaDeviceEntry(
            name=device.name, ieee=str(device.ieee), last_seen=device.last_seen
        )
        self.devices[device_entry.ieee] = device_entry
        self.async_schedule_save()
        return device_entry

    @callback
    def async_get_or_create_device(self, device: ZhaDeviceType) -> ZhaDeviceEntry:
        """Create a new ZhaDeviceEntry."""
        ieee_str: str = str(device.ieee)
        if ieee_str in self.devices:
            return self.devices[ieee_str]
        return self.async_create_device(device)

    @callback
    def async_create_or_update_device(self, device: ZhaDeviceType) -> ZhaDeviceEntry:
        """Create or update a ZhaDeviceEntry."""
        if str(device.ieee) in self.devices:
            return self.async_update_device(device)
        return self.async_create_device(device)

    @callback
    def async_delete_device(self, device: ZhaDeviceType) -> None:
        """Delete ZhaDeviceEntry."""
        ieee_str: str = str(device.ieee)
        if ieee_str in self.devices:
            del self.devices[ieee_str]
            self.async_schedule_save()

    @callback
    def async_update_device(self, device: ZhaDeviceType) -> ZhaDeviceEntry:
        """Update name of ZhaDeviceEntry."""
        ieee_str: str = str(device.ieee)
        old = self.devices[ieee_str]

        if old is not None and device.last_seen is None:
            return

        changes = {}
        changes["last_seen"] = device.last_seen

        new = self.devices[ieee_str] = attr.evolve(old, **changes)
        self.async_schedule_save()
        return new

    async def async_load(self) -> None:
        """Load the registry of zha device entries."""
        data = await self._store.async_load()

        devices: OrderedDict[str, ZhaDeviceEntry] = OrderedDict()

        if data is not None:
            for device in data["devices"]:
                devices[device["ieee"]] = ZhaDeviceEntry(
                    name=device["name"],
                    ieee=device["ieee"],
                    last_seen=device.get("last_seen"),
                )

        self.devices = devices

    @callback
    def async_schedule_save(self) -> None:
        """Schedule saving the registry of zha devices."""
        self._store.async_delay_save(self._data_to_save, SAVE_DELAY)

    async def async_save(self) -> None:
        """Save the registry of zha devices."""
        await self._store.async_save(self._data_to_save())

    @callback
    def _data_to_save(self) -> dict:
        """Return data for the registry of zha devices to store in a file."""
        data = {}

        data["devices"] = [
            {"name": entry.name, "ieee": entry.ieee, "last_seen": entry.last_seen}
            for entry in self.devices.values()
            if entry.last_seen and (time.time() - entry.last_seen) < TOMBSTONE_LIFETIME
        ]

        return data
Ejemplo n.º 9
0
class RFIDPadHandler:
    def __init__(self, hass, config_entry, mqtt_prefix):
        self.hass = hass
        self.config_entry = config_entry
        self.mqtt_prefix = mqtt_prefix
        self.async_add_devices = {}
        self.devices = {}
        self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
        self._history = []

    async def async_initialize(self):
        try:
            raw_storage = await self.store.async_load()
            self._history = raw_storage["history"]
        except:
            self._history = []

        _LOGGER.debug(f"Initial history: {self._history}")

    @callback
    def _async_schedule_save(self) -> None:
        """Schedule saving the history and tag data"""
        self.store.async_delay_save(self._data_to_save, SAVE_DELAY)

    @callback
    def _data_to_save(self) -> dict:
        """Return history and tag data store in a file."""
        return {
            'history': self._history[-MAX_HISTORY:],
        }

    async def start_discovery(self):
        topic_filter = f"{self.mqtt_prefix}/discovery/#"
        _LOGGER.info(f"Subscribing to MQTT filter {topic_filter}")
        await mqtt.async_subscribe(self.hass,
                                   f"{self.mqtt_prefix}/discovery/#",
                                   self.async_receive_discovery)

    async def async_receive_discovery(self, msg):
        _LOGGER.info(f"Received discovery message: {msg} ({type(msg)})")
        async_add_devices_sensor = self.async_add_devices[SENSOR]

        try:
            config = json.loads(msg.payload)
        except:
            _LOGGER.info(
                f"Cannot parse rfidpad discovery message: {msg.payload}")
            return

        pad = RFIDPad(self.hass, self, config)
        if pad.id in self.devices:
            _LOGGER.info(
                f"Ignoring RFIDPAD {pad.id} ({pad.name}), id already exists")
            # TODO update device configuration and restart device
        else:
            self.devices[pad.id] = pad
            await pad.start()

    async def async_handle_action(self, action):
        """ Called by an RFIDPad when a tag has been scanned """

        self._history.append(action.as_dict())
        _LOGGER.debug(f"Current history: {self._history}")
        self.hass.bus.fire(HISTORY_UPDATED_EVENT, {})
        self._async_schedule_save()

        if not action.tag_valid:
            _LOGGER.warn(f"unknown tag {action.tag} scanned")
            return

        # Fire event, for use by automations
        self.hass.bus.fire(TAG_SCANNED_EVENT, {
            "button": action.button,
            "tag": action.tag,
            "name": action.tag_name
        })

        # Send new status to all boards
        new_status = STATUS_TRANSITIONS[action.button]
        if new_status != None:
            for device in self.devices.values():
                await device.async_update_status(new_status)
Ejemplo n.º 10
0
class LegacyZWaveMigration:
    """Handle the migration from zwave to zwave_js."""
    def __init__(self, hass: HomeAssistant) -> None:
        """Set up migration instance."""
        self._hass = hass
        self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
        self._data: dict[str, dict[str, ZWaveJSMigrationData]] = {}

    async def load_data(self) -> None:
        """Load Z-Wave JS migration data."""
        stored = cast(dict, await self._store.async_load())
        if stored:
            self._data = stored

    @callback
    def save_data(self, config_entry_id: str, entity_id: str,
                  data: ZWaveJSMigrationData) -> None:
        """Save Z-Wave JS migration data."""
        if config_entry_id not in self._data:
            self._data[config_entry_id] = {}
        self._data[config_entry_id][entity_id] = data
        self._store.async_delay_save(self._data_to_save, STORAGE_WRITE_DELAY)

    @callback
    def _data_to_save(self) -> dict[str, dict[str, ZWaveJSMigrationData]]:
        """Return data to save."""
        return self._data

    @callback
    def add_entity_value(
        self,
        config_entry: ConfigEntry,
        entity_id: str,
        discovery_info: ZwaveDiscoveryInfo,
    ) -> None:
        """Add info for one entity and Z-Wave JS value."""
        ent_reg = async_get_entity_registry(self._hass)
        dev_reg = async_get_device_registry(self._hass)

        node = discovery_info.node
        primary_value = discovery_info.primary_value
        entity_entry = ent_reg.async_get(entity_id)
        assert entity_entry
        device_identifier = get_device_id(node.client, node)
        device_entry = dev_reg.async_get_device({device_identifier}, set())
        assert device_entry

        # Normalize unit of measurement.
        if unit := entity_entry.unit_of_measurement:
            _unit = UNIT_LEGACY_MIGRATION_MAP.get(unit, unit)
            unit = _unit.lower()
        if unit == "":
            unit = None

        data: ZWaveJSMigrationData = {
            "node_id": node.node_id,
            "endpoint_index": node.index,
            "command_class": primary_value.command_class,
            "value_property_name": primary_value.property_name,
            "value_property_key_name": primary_value.property_key_name,
            "value_id": primary_value.value_id,
            "device_id": device_entry.id,
            "domain": entity_entry.domain,
            "entity_id": entity_id,
            "unique_id": entity_entry.unique_id,
            "unit_of_measurement": unit,
        }

        self.save_data(config_entry.entry_id, entity_id, data)
Ejemplo n.º 11
0
class LockHistory:
    """Manage lock history."""
    def __init__(self, hass: HomeAssistantType, component: EntityComponent,
                 lock_node_id, config_tags, config_ozw_log):
        """Initialize lock history storage."""
        self.hass = hass
        self.component = component
        self.node_id = lock_node_id
        self.config_tags = config_tags
        self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
        self.ozw_log = config_ozw_log
        self.ozw_file_offset = 0
        self.ozw_file_inode = None
        self._used_tags = {}  # indexed by tag index number
        self._history = []

    def get_user_by_code(self, usercode_string):
        """ Find the user name for a given user code from the configuration.
	Return None if user code not found """

        if not self.config_tags:
            return None

        for tag in self.config_tags:
            if tag[CONF_USER_CODE] == usercode_string:
                return tag[CONF_NAME]

        return None

    # USERCODE_TYPE_BLANK: usercode is empty, or
    # not a string, or consists of only \0 characters
    def parse_usercode(self, usercode):
        if not isinstance(usercode, str):
            return (None, USERCODE_TYPE_BLANK)
        all_blank = True
        all_digit = True
        num_digits = 0
        reading_digits = True

        for c in usercode:
            if c != '\0':
                all_blank = False
            if reading_digits:
                if c >= '0' and c <= '9':
                    num_digits += 1
                elif c == '\0':
                    reading_digits = False
                else:
                    all_digit = False
            if not reading_digits and c != '\0':
                all_digit = False

        if all_blank:
            return (None, USERCODE_TYPE_BLANK)
        if all_digit:
            return (usercode[0:num_digits], USERCODE_TYPE_PIN)
        tag_string = " ".join(["{:02x}".format(ord(c)) for c in usercode])
        return (tag_string, USERCODE_TYPE_TAG)

    async def get_last_tag_from_ozw_log(self):
        """ Parse the openzwave log file te determine the last
            tag used. Return (tag_number, 5|6).
            Both tag_number and event_code may be None
            if no event was found """

        import aiofiles
        import aiofiles.os

        s = await aiofiles.os.stat(self.ozw_log)
        _LOGGER.debug("current inode: {}; last inode: {}".format(
            s.st_ino, self.ozw_file_inode))
        if s.st_ino != self.ozw_file_inode:
            _LOGGER.info("New OZW log file detected")
            self.ozw_file_offset = 0
            self.ozw_file_inode = s.st_ino

        p = re.compile(
            "Node{:03}.*Received:.*0x06, 0x0(?P<event>\\d), 0x01, 0x(?P<tag>[0-9a-fA-F][0-9a-fA-F]), 0x[0-9a-fA-F][0-9a-fA-F]"
            .format(self.node_id))

        last_event = None
        last_tag = None
        async with aiofiles.open(self.ozw_log, mode='r') as f:
            await f.seek(self.ozw_file_offset)
            i = 0
            while True:
                line = await f.readline()
                if len(line) == 0:
                    break
                self.ozw_file_offset = await f.tell()
                line = line.strip()
                i += 1
                m = p.search(line)
                if not m:
                    continue
                last_event = m.group('event')
                last_tag = int(m.group('tag'), 16)
                _LOGGER.debug("MATCH {}: event {}, tag {}".format(
                    i, last_event, last_tag))
            _LOGGER.debug("Last line read: {}".format(i))

        return last_tag, last_event

    async def async_initialize(self):
        """Get the usercode data."""

        try:
            raw_storage = await self.store.async_load()
            self._history = raw_storage["history"]
        except:
            self._history = []

        async def zwave_ready(event):
            network = self.hass.data[zwave_const.DATA_NETWORK]
            lock_node = network.nodes[self.node_id]

            raw_usercodes = {}
            entities = []
            for value in lock_node.get_values(
                    class_id=zwave_const.COMMAND_CLASS_USER_CODE).values():
                if value.index == 0:
                    _LOGGER.debug("Ignoring user code #0")
                    continue
                user_code = value.data
                #_LOGGER.info("Usercode at slot %s is: %s", value.index, value.data)
                if not isinstance(value.data, str):
                    continue
                usercode_string, usercode_type = self.parse_usercode(
                    value.data)
                if usercode_type != USERCODE_TYPE_BLANK:
                    _LOGGER.debug("Adding usercode at slot %s: %s",
                                  value.index, usercode_string)
                    name = self.get_user_by_code(usercode_string)
                    if not name:
                        _LOGGER.info("Usercode %s has unknown name",
                                     usercode_string)
                        name = "unknown"
                    self._used_tags[value.index] = {
                        CONF_INDEX: value.index,
                        CONF_NAME: name,
                        CONF_USER_CODE: user_code,
                    }

        async def async_access_control_changed(entity_id, old_state,
                                               new_state):
            #print("Access control changed: entity {}, old {}, new {}".format(entity_id, old_state, new_state))
            _LOGGER.info("Access control changed")
            if new_state.state == "6":
                _LOGGER.info("New state is 6 (HOME)")
                state_string = "Home"
            elif new_state.state == "5":
                _LOGGER.info("New state is 5 (AWAY)")
                state_string = "Away"
            else:
                _LOGGER.info("Unknown new state, ignoring")
                return

            last_tag, last_event = await self.get_last_tag_from_ozw_log()

            if not last_event:
                _LOGGER.info("No tag event found in OZW log")
                return

            if last_event != new_state.state:
                _LOGGER.info(
                    "Last event in OZW log not equal to access control event")
                return

            used_tag = self._used_tags[last_tag]
            _LOGGER.info(
                "Adding to history: used tag {} ({}) for new state {}".format(
                    last_tag, used_tag[CONF_NAME], state_string))
            self._history.append({
                "tagno":
                last_tag,
                "name":
                used_tag[CONF_NAME],
                "user_code":
                used_tag[CONF_USER_CODE],
                "state":
                state_string,
                "date":
                dt_util.now().strftime("%d/%m/%Y %H:%M:%S"),
                "timestamp":
                dt_util.as_timestamp(dt_util.now()),
            })

            _LOGGER.debug("Current history: {}".format(self._history))
            self.hass.bus.fire(HISTORY_UPDATED_EVENT, {})
            self._async_schedule_save()

        # Listen for when example_component_my_cool_event is fired
        self.hass.bus.async_listen('zwave.network_ready', zwave_ready)
        homeassistant.helpers.event.async_track_state_change(
            self.hass,
            'sensor.alarm_keypad_access_control',
            action=async_access_control_changed)

        _LOGGER.info("async_initialize finished")

    @callback
    def _async_schedule_save(self) -> None:
        """Schedule saving the area registry."""
        self.store.async_delay_save(self._data_to_save, SAVE_DELAY)

    @callback
    def _data_to_save(self) -> dict:
        """Return data of area registry to store in a file."""
        return {'history': self._history[-MAX_HISTORY:]}
Ejemplo n.º 12
0
class EntityMapStorage:
    """
    Holds a cache of entity structure data from a paired HomeKit device.

    HomeKit has a cacheable entity map that describes how an IP or BLE
    endpoint is structured. This object holds the latest copy of that data.

    An endpoint is made of accessories, services and characteristics. It is
    safe to cache this data until the c# discovery data changes.

    Caching this data means we can add HomeKit devices to HA immediately at
    start even if discovery hasn't seen them yet or they are out of range. It
    is also important for BLE devices - accessing the entity structure is
    very slow for these devices.
    """

    def __init__(self, hass):
        """Create a new entity map store."""
        self.hass = hass
        self.store = Store(
            hass,
            ENTITY_MAP_STORAGE_VERSION,
            ENTITY_MAP_STORAGE_KEY
        )
        self.storage_data = {}

    async def async_initialize(self):
        """Get the pairing cache data."""
        raw_storage = await self.store.async_load()
        if not raw_storage:
            # There is no cached data about HomeKit devices yet
            return

        self.storage_data = raw_storage.get('pairings', {})

    def get_map(self, homekit_id):
        """Get a pairing cache item."""
        return self.storage_data.get(homekit_id)

    def async_create_or_update_map(self, homekit_id, config_num, accessories):
        """Create a new pairing cache."""
        data = {
            'config_num': config_num,
            'accessories': accessories,
        }
        self.storage_data[homekit_id] = data
        self._async_schedule_save()
        return data

    def async_delete_map(self, homekit_id):
        """Delete pairing cache."""
        if homekit_id not in self.storage_data:
            return

        self.storage_data.pop(homekit_id)
        self._async_schedule_save()

    @callback
    def _async_schedule_save(self):
        """Schedule saving the entity map cache."""
        self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY)

    @callback
    def _data_to_save(self):
        """Return data of entity map to store in a file."""
        return {
            'pairings': self.storage_data,
        }