Skip to content
Snippets Groups Projects
Commit 8204e5ab authored by jkerdreu's avatar jkerdreu
Browse files

- Lot of refactoring

- Added support for auto-washing device !! 
=> All features added ! need a bit more tweaks but no new features!

git-svn-id: https://redmine.imt-atlantique.fr/svn/xaal/code/Python/branches/0.7@3036 b32b6428-25c9-4566-ad07-03861ab6144f
parent 8b025728
Branches
No related tags found
No related merge requests found
......@@ -23,6 +23,10 @@ import logging
_LOGGER = logging.getLogger(__name__)
UNSUPPORTED_TYPES = ['cli', 'hmi', 'logger']
UPDATE_NOTIFICATIONS = [Notification.attribute_change,
Notification.description_change,
Notification.metadata_change]
class XAALEntity(Entity):
_attr_has_entity_name = True
......@@ -35,7 +39,7 @@ class XAALEntity(Entity):
self.setup()
#####################################################
# Init
# Life cycle
#####################################################
def setup(self):
""" Use setup to tweak a entity at creation """
......@@ -45,17 +49,26 @@ class XAALEntity(Entity):
"""call by HASS when entity is ready"""
self._attr_available = True
def auto_wash(self) -> None:
"""Monitor device has been auto washed, so entity isn't available"""
# Note: we don't release the _dev weakref because we still need
# it. If the device is back, it will be release w/ the fix_device()
self._attr_available = False
def fix_device(self, device: MonitorDevice) -> None:
"""The xAAL device is back after an auto-wash, so we need to switch,
to the new MonitorDevice.
"""
self._dev = device
self._attr_available = True
#####################################################
# HASS Device
#####################################################
@property
def device_info(self) -> DeviceInfo | None:
dev = self._dev
group_id = dev.description.get('group_id')
if group_id:
ident = "grp:" + str(group_id)
else:
ident = "dev:" + str(dev.address)
ident = self._bridge.device_to_ident(dev)
name = dev.db.get("ha_dev_name", ident)
return {
"identifiers": {(DOMAIN, ident)},
......@@ -177,55 +190,6 @@ class Bridge(object):
hass.bus.async_listen(EVENT_DEVICE_REGISTRY_UPDATED, self.device_registry_updated)
hass.bus.async_listen(EVENT_ENTITY_REGISTRY_UPDATED, self.entity_registry_updated)
async def device_registry_updated(self, event: Event):
if event.data.get('action') != 'update':
return
_LOGGER.warning(event.data)
device_id = event.data.get('device_id')
dr = device_registry.async_get(self.hass)
device_entry = dr.async_get(device_id)
idents = list(device_entry.identifiers)
print(idents)
if idents[0][0] != DOMAIN:
return
addrs = self.ident_to_address(idents[0][1])
kv = {'ha_dev_name': device_entry.name_by_user}
for addr in addrs:
body = {'device': addr, 'map': kv}
self.ha_update_db(body)
async def entity_registry_updated(self, event: Event):
if event.data.get('action') != 'update':
return
_LOGGER.warning(event.data)
# ugly bugfix HASS sync issue, we need to wait registry to be up to date.
await asyncio.sleep(0.1)
entity_id = event.data.get('entity_id')
entity = self.get_entity_by_id(entity_id)
if entity:
name = entity.registry_entry.name
if (name is None) and (entity._dev.db.get('ha_name') is None):
# HASS and DB can be out of sync, so we push db even if everything looks
# fine, except if there is no data
return
kv = {'ha_name': name}
body = {'device': entity.address, 'map': kv}
self.ha_update_db(body)
else:
_LOGGER.info(f"Unable to find entiy {entity_id}")
def get_entity_by_id(self, entity_id: str) -> XAALEntity | None:
# This is cleary not a best way to find out entity by id, but
# HASS doesn't seems to provide a direct way
for addr, entities in self._entities.items():
for entity in entities:
if entity.entity_id == entity_id:
return entity
return None
#####################################################
# Engine & Hooks
#####################################################
......@@ -249,6 +213,70 @@ class Bridge(object):
await asyncio.sleep(0.2)
return False
#####################################################
# xAAL stuffs
#####################################################
def setup_device(self) -> Device:
"""setup a new device need by the Monitor"""
dev = schemas.hmi()
dev.dev_type = 'hmi.hass'
dev.vendor_id = 'IMT Atlantique'
dev.product_id = 'xAAL to HASS Brigde'
# never use this terrible hack to gain access to brigde throught aioconsole
# dev.new_attribute('bridge', self)
self._eng.add_device(dev)
return dev
def send_request(self, targets: List[bindings.UUID], action: str, body: Dict[str, Any] | None = None):
"""send a xAAL request (queueing it)"""
self._eng.send_request(self._dev, targets, action, body)
def ha_update_db(self, body: dict):
self.send_request([self._mon.db_server], 'update_keys_values', body)
#####################################################
# xAAL Monitor events & callbacks
#####################################################
def monitor_event(self, notif: Notification, dev: MonitorDevice):
entities = self.get_entities(dev.address)
# update entities if found
if entities:
# First we need to check if this device isn't an old auto-washed one
if notif in UPDATE_NOTIFICATIONS:
for ent in entities:
if ent._dev != dev and dev.is_ready():
ent.fix_device(dev)
_LOGGER.debug(f"Recovering {ent}")
# is it a change
if notif in UPDATE_NOTIFICATIONS:
for ent in entities:
if ent.available:
ent.schedule_update_ha_state()
return
# is it a dropped device
if notif in [Notification.drop_device]:
for ent in entities:
_LOGGER.debug(f"Entity {ent} isn't available right now")
ent.auto_wash()
ent.schedule_update_ha_state()
return
# Not found, so it's a new entity
if entities is None and dev.is_ready():
self.build_entities(dev)
def monitor_notification(self, msg: Message):
# right now the monitor doesn't send event on notification, so the bridge deals w/
# both monitor events & messages.
if (not msg.is_notify()) or msg.is_alive() or msg.is_attributes_change():
return
entities = self.get_entities(msg.source)
if entities:
for ent in entities:
if hasattr(ent, 'handle_notification'):
msg.dump()
ent.handle_notification(msg)
#####################################################
# Entities
#####################################################
......@@ -263,23 +291,41 @@ class Bridge(object):
self.warm_once(f"Unable to find entity for {dev.address} {dev.dev_type} ")
def add_entities(self, addr: bindings.UUID, entities: list[XAALEntity]) -> None:
"""register a some entities (called from factories)"""
# _LOGGER.debug(f"new Entities: {addr} {entities}")
"""register some entities (called from factories)"""
_LOGGER.debug(f"new Entities: {addr} {entities}")
self._entities.update({addr: entities})
def remove_entities(self, addr: bindings.UUID) -> None:
"""remove entities for a given xAAL address"""
_LOGGER.debug(f"Removing entities: {addr}")
self._entities.pop(addr)
self._mon.devices.remove(addr)
# if device not already auto-washed remove it
if self._mon.devices.get_with_addr(addr):
self._mon.devices.remove(addr)
def get_entities(self, addr: bindings.UUID) -> list[XAALEntity] | None:
""" return entities for a given xAAL address"""
return self._entities.get(addr)
def ha_remove_device(self, ident: str) -> None:
""" User asked to remove an HA device, we need to find out the entites """
for addr in self.ident_to_address(ident):
self.remove_entities(addr)
def get_entity_by_id(self, entity_id: str) -> XAALEntity | None:
"""return a entity for a given entity_id"""
# This is cleary not a best way to find out entity by id, but
# HASS doesn't seems to provide a direct way
for addr, entities in self._entities.items():
for entity in entities:
if entity.entity_id == entity_id:
return entity
return None
#####################################################
# HA devices
#####################################################
def device_to_ident(self, device: MonitorDevice) -> str:
group_id = device.description.get('group_id')
if group_id:
return "grp:" + str(group_id)
else:
return "dev:" + str(device.address)
def ident_to_address(self, ident: str) -> list[bindings.UUID]:
tmp = ident.split(':')
......@@ -291,6 +337,11 @@ class Bridge(object):
# it's a group
return [dev.address for dev in self._mon.devices.get_with_group(addr)]
def ha_remove_device(self, ident: str) -> None:
""" User asked to remove an HA device, we need to find out the entites """
for addr in self.ident_to_address(ident):
self.remove_entities(addr)
#####################################################
# Factories
#####################################################
......@@ -302,52 +353,48 @@ class Bridge(object):
self._factories.remove(factory)
#####################################################
# xAAL
# HA Event listening
#####################################################
def setup_device(self) -> Device:
"""setup a new device need by the Monitor"""
dev = schemas.hmi()
dev.dev_type = 'hmi.hass'
dev.vendor_id = 'IMT Atlantique'
dev.product_id = 'xAAL to HASS Brigde'
# never use this terrible hack to gain access to brigde throught aioconsole
# dev.new_attribute('bridge',self)
self._eng.add_device(dev)
return dev
def send_request(self, targets: List[bindings.UUID], action: str, body: Dict[str, Any] | None = None):
"""send a xAAL request (queueing it)"""
self._eng.send_request(self._dev, targets, action, body)
async def device_registry_updated(self, event: Event):
"""store the new device name in DB if changed"""
if event.data.get('action') != 'update':
return
_LOGGER.info(event.data)
device_id = event.data.get('device_id')
dr = device_registry.async_get(self.hass)
device_entry = dr.async_get(device_id)
idents = list(device_entry.identifiers)
print(idents)
if idents[0][0] != DOMAIN:
return
def ha_update_db(self, body: dict):
self.send_request([self._mon.db_server], 'update_keys_values', body)
addrs = self.ident_to_address(idents[0][1])
kv = {'ha_dev_name': device_entry.name_by_user}
for addr in addrs:
body = {'device': addr, 'map': kv}
self.ha_update_db(body)
#####################################################
# Monitor events & callbacks
#####################################################
def monitor_event(self, notif: Notification, dev: MonitorDevice):
entities = self.get_entities(dev.address)
# update entities if found
if entities and (notif in [Notification.attribute_change, Notification.description_change, Notification.metadata_change]):
for ent in entities:
if ent.available:
ent.schedule_update_ha_state()
async def entity_registry_updated(self, event: Event):
"""store the new entity name in DB if changed"""
if event.data.get('action') != 'update':
return
# Not found, so it's a new entity
if entities is None and dev.is_ready():
self.build_entities(dev)
_LOGGER.info(event.data)
# ugly bugfix HASS sync issue, we need to wait registry to be up to date.
await asyncio.sleep(0.1)
entity_id = event.data.get('entity_id')
entity = self.get_entity_by_id(entity_id)
def monitor_notification(self, msg: Message):
# right now the monitor doesn't send event on notification, so the bridge deals w/
# both monitor events & messages.
if (not msg.is_notify()) or msg.is_alive() or msg.is_attributes_change():
return
entities = self.get_entities(msg.source)
if entities:
for ent in entities:
if hasattr(ent, 'handle_notification'):
msg.dump()
ent.handle_notification(msg)
if entity:
name = entity.registry_entry.name
if (name is None) and (entity._dev.db.get('ha_name') is None):
# HASS and DB can be out of sync, so we push db even if everything looks
# fine, except if there is no data
return
kv = {'ha_name': name}
body = {'device': entity.address, 'map': kv}
self.ha_update_db(body)
else:
_LOGGER.info(f"Unable to find entiy {entity_id}")
#####################################################
# Miscs
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment