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