Skip to content
Snippets Groups Projects
Commit 6c6bbce7 authored by jkerdreu's avatar jkerdreu
Browse files

- Maxi Lint

- Added state_class on sensors to be able to statistics..

git-svn-id: https://redmine.imt-atlantique.fr/svn/xaal/code/Python/branches/0.7@3033 b32b6428-25c9-4566-ad07-03861ab6144f
parent 95e1fcb4
Branches
No related tags found
No related merge requests found
......@@ -15,10 +15,10 @@ _LOGGER = logging.getLogger(__name__)
# https://www.home-assistant.io/integrations/binary_sensor/
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'motion.' : [Motion],
'contact.': [Contact],
......@@ -32,10 +32,10 @@ class XAALBinarySensorEntity(XAALEntity, BinarySensorEntity):
@property
def state(self) -> Literal["on", "off"] | None:
try:
attr = getattr(self,'_xaal_attribute')
attr = getattr(self, '_xaal_attribute')
value = self.get_attribute(attr)
return STATE_ON if value else STATE_OFF
except:
except AttributeError:
return None
......@@ -70,3 +70,4 @@ class Button(XAALBinarySensorEntity):
self.click_event('single')
if msg.action == 'double_click':
self.click_event('double')
import asyncio
import functools
from html import entities
from turtle import pd, up
from typing import Dict, List, Any, Type
from uuid import UUID
from typing import Dict, List, Any
from .const import DOMAIN
......@@ -23,7 +21,7 @@ from xaal.monitor.monitor import Device as MonitorDevice
import logging
_LOGGER = logging.getLogger(__name__)
UNSUPPORTED_TYPES = ['cli','hmi','logger']
UNSUPPORTED_TYPES = ['cli', 'hmi', 'logger']
class XAALEntity(Entity):
......@@ -51,14 +49,14 @@ class XAALEntity(Entity):
# HASS Device
#####################################################
@property
def device_info(self) -> DeviceInfo | None :
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)
name = dev.db.get("ha_dev_name",ident)
name = dev.db.get("ha_dev_name", ident)
return {
"identifiers": {(DOMAIN, ident)},
"name": name,
......@@ -72,17 +70,18 @@ class XAALEntity(Entity):
#####################################################
# xAAL helpers
#####################################################
def send_request(self, action: str, body: Dict[str, Any] | None =None) -> None:
def send_request(self, action: str, body: Dict[str, Any] | None = None) -> None:
_LOGGER.debug(f"{self} {action} {body}")
self._bridge.send_request([self._dev.address, ], action, body)
def get_attribute(self, name: str, default: Dict[str, Any] =None) -> Any:
def get_attribute(self, name: str, default: Dict[str, Any] = None) -> Any:
""" return a attribute for xAAL device"""
return self._dev.attributes.get(name, default)
@property
def address(self):
return self._dev.address
#####################################################
# Entity properties
#####################################################
......@@ -99,20 +98,20 @@ class XAALEntity(Entity):
if dev_name and db_name:
db_name = db_name.removeprefix(dev_name)
force_name = getattr(self,'_force_name',None)
force_name = getattr(self, '_force_name', None)
name = db_name or force_name or self.device_class or self.short_type()
#print(f"{dev_name} =>{db_name} => {name}")
# print(f"{dev_name} =>{db_name} => {name}")
return name.capitalize()
@property
def unique_id(self) -> str:
addr = str(self._dev.address).replace('-', '_')
if hasattr(self,'_xaal_attribute'):
if hasattr(self, '_xaal_attribute'):
return f"xaal.{addr}.{self._xaal_attribute}"
return f"xaal.{addr}"
@property
def device_registry_entry(self) -> DeviceEntry|None:
def device_registry_entry(self) -> DeviceEntry | None:
device_id = self.registry_entry.device_id
dr = device_registry.async_get(self.hass)
return dr.async_get(device_id)
......@@ -141,11 +140,10 @@ class EntityFactory(object):
return False
def async_setup_factory(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
binding: dict ) -> None:
def async_setup_factory(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
binding: dict) -> None:
bridge: Bridge = hass.data[DOMAIN][config_entry.entry_id]
factory = EntityFactory(bridge, async_add_entities, binding)
......@@ -176,30 +174,30 @@ class Bridge(object):
self._eng.start()
self._entities = {}
self._factories = []
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_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':
if event.data.get('action') != 'update':
return
_LOGGER.warning(event.data)
#import pdb;pdb.set_trace()
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)
if idents[0][0]!= DOMAIN:
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}
body = {'device': addr, 'map': kv}
self.ha_update_db(body)
async def entity_registry_updated(self, event: Event):
if event.data.get('action') != 'update':
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.
......@@ -209,17 +207,16 @@ class Bridge(object):
if entity:
name = entity.registry_entry.name
if (name == None) and (entity._dev.db.get('ha_name') == None):
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}
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
......@@ -255,19 +252,19 @@ class Bridge(object):
#####################################################
# Entities
#####################################################
def build_entities(self,dev: MonitorDevice) -> None:
def build_entities(self, dev: MonitorDevice) -> None:
"""search factories to build a new entities"""
cnt = 0
for fact in self._factories:
r = fact.build_entities(dev)
if r:
cnt = cnt + 1
if cnt==0:
if cnt == 0:
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}")
# _LOGGER.debug(f"new Entities: {addr} {entities}")
_LOGGER.debug(f"new Entities: {addr} {entities}")
self._entities.update({addr: entities})
......@@ -289,11 +286,10 @@ class Bridge(object):
addr = tools.get_uuid(tmp[1])
# is it a xAAL device, if so remove it's address
if tmp[0] == 'dev':
return [addr,]
return [addr]
else:
# it's a group
return [dev.address for dev in self._mon.devices.get_with_group(addr)]
#####################################################
# Factories
......@@ -315,11 +311,11 @@ class Bridge(object):
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)
# 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):
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)
......@@ -337,7 +333,7 @@ class Bridge(object):
if ent.available:
ent.schedule_update_ha_state()
return
# Not found, so it's a new entity
# Not found, so it's a new entity
if entities is None and dev.is_ready():
self.build_entities(dev)
......@@ -347,7 +343,7 @@ class Bridge(object):
if (not msg.is_notify()) or msg.is_alive() or msg.is_attributes_change():
return
entities = self.get_entities(msg.source)
if entities :
if entities:
for ent in entities:
if hasattr(ent, 'handle_notification'):
msg.dump()
......@@ -359,3 +355,4 @@ class Bridge(object):
@functools.lru_cache(maxsize=128)
def warm_once(self, msg: str):
_LOGGER.warning(msg)
......@@ -15,6 +15,7 @@ DATA_SCHEMA = vol.Schema(
}
)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
......@@ -34,3 +35,4 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown"
# else we show the empty form
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA, errors=errors)
......@@ -12,3 +12,4 @@ XAAL_TTS_SCHEMA = vol.Schema({
XAAL_TTS_SCHEMA = vol.Schema({
vol.Required("message"): cv.template,
})
......@@ -11,22 +11,20 @@ from .bridge import XAALEntity, async_setup_factory
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
binding = { 'shutter.position' : [ShutterPosition],
'shutter.' : [Shutter,] }
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'shutter.position': [ShutterPosition],
'shutter.' : [Shutter, ]}
return async_setup_factory(hass, config_entry, async_add_entities, binding)
class Shutter(XAALEntity, CoverEntity):
_attr_device_class= CoverDeviceClass.SHUTTER
_attr_device_class = CoverDeviceClass.SHUTTER
@property
def supported_features(self) -> int:
return CoverEntityFeature.OPEN|CoverEntityFeature.CLOSE|CoverEntityFeature.STOP
return CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
@property
def is_closed(self) -> bool | None:
......@@ -41,11 +39,12 @@ class Shutter(XAALEntity, CoverEntity):
def stop_cover(self, **kwargs: Any) -> None:
self.send_request('stop')
class ShutterPosition(Shutter):
@property
def supported_features(self) -> int:
return super().supported_features|CoverEntityFeature.SET_POSITION
return super().supported_features | CoverEntityFeature.SET_POSITION
@property
def is_closed(self) -> bool | None:
......@@ -58,7 +57,7 @@ class ShutterPosition(Shutter):
if self.get_attribute('action') == 'down':
return True
return False
@property
def is_opening(self) -> bool | None:
if self.get_attribute('action') == 'up':
......@@ -71,5 +70,6 @@ class ShutterPosition(Shutter):
def set_cover_position(self, **kwargs: Any) -> None:
position = kwargs.get(ATTR_POSITION, None)
if position != None:
self.send_request('set_position',{'position':position})
if position is not None:
self.send_request('set_position', {'position': position})
......@@ -11,11 +11,10 @@ from .bridge import XAALEntity, async_setup_factory
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'lamp.' : [Lamp]}
return async_setup_factory(hass, config_entry, async_add_entities, binding)
......@@ -42,10 +41,9 @@ class Lamp(XAALEntity, LightEntity):
@property
def brightness(self) -> int | None:
brightness = self.get_attribute('brightness',0)
brightness = self.get_attribute('brightness', 0)
return round(255 * (int(brightness) / 100))
@property
def hs_color(self) -> tuple[float, float] | None:
hsv = self.get_attribute('hsv')
......@@ -89,3 +87,4 @@ class Lamp(XAALEntity, LightEntity):
def turn_off(self, **kwargs) -> None:
self.send_request('turn_off')
......@@ -4,7 +4,7 @@ from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, STATE_CLASS_MEASUREMENT
from homeassistant import const
from .bridge import XAALEntity, async_setup_factory
......@@ -15,10 +15,9 @@ from . import utils
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback ) -> None:
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'thermometer.' : [Thermometer ],
'hygrometer.' : [Hygrometer],
'barometer.' : [Barometer],
......@@ -37,10 +36,11 @@ async def async_setup_entry(
class XAALSensorEntity(XAALEntity, SensorEntity):
_attr_state_class = STATE_CLASS_MEASUREMENT
@property
def native_value(self) -> Any:
target = getattr(self,'_xaal_attribute')
target = getattr(self, '_xaal_attribute')
return self.get_attribute(target)
......@@ -73,11 +73,13 @@ class PowerMeter(XAALSensorEntity):
_attr_native_unit_of_measurement = const.POWER_WATT
_xaal_attribute = 'power'
class CurrentMeter(XAALSensorEntity):
_attr_device_class = SensorDeviceClass.CURRENT
_attr_native_unit_of_measurement = const.ELECTRIC_CURRENT_AMPERE
_xaal_attribute = 'current'
class VoltMeter(XAALSensorEntity):
_attr_device_class = SensorDeviceClass.VOLTAGE
_attr_native_unit_of_measurement = const.ELECTRIC_POTENTIAL_VOLT
......@@ -85,7 +87,7 @@ class VoltMeter(XAALSensorEntity):
class WifiMeter(XAALSensorEntity):
_attr_device_class= SensorDeviceClass.SIGNAL_STRENGTH
_attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
_attr_native_unit_of_measurement = const.SIGNAL_STRENGTH_DECIBELS
_xaal_attribute = 'rssi'
......@@ -100,7 +102,7 @@ class CO2Meter(XAALSensorEntity):
_attr_device_class = SensorDeviceClass.CO2
_attr_native_unit_of_measurement = const.CONCENTRATION_PARTS_PER_MILLION
_xaal_attribute = 'co2'
class SoundMeter(XAALSensorEntity):
_attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
......@@ -121,7 +123,7 @@ class Gateway(XAALSensorEntity):
@property
def name(self) -> str | None:
return self._dev.description.get('product_id','gateway')
return self._dev.description.get('product_id', 'gateway')
class TTS(XAALEntity):
......@@ -129,12 +131,13 @@ class TTS(XAALEntity):
def setup(self):
name = utils.str_to_id(self.name)
self._bridge.hass.services.async_register("notify",name, self.say, XAAL_TTS_SCHEMA)
self._bridge.hass.services.async_register("notify", name, self.say, XAAL_TTS_SCHEMA)
def say(self, service):
msg = service.data['message'].template
self.send_request('say',{'msg':msg})
self.send_request('say', {'msg': msg})
@property
def name(self) -> str | None:
return self._dev.description.get('info','tts')
\ No newline at end of file
return self._dev.description.get('info', 'tts')
......@@ -4,7 +4,7 @@ from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.siren import SirenEntity, SirenEntityFeature, ATTR_DURATION
from homeassistant.components.siren import SirenEntity, SirenEntityFeature # , ATTR_DURATION
from .bridge import XAALEntity, async_setup_factory
......@@ -12,11 +12,10 @@ from .bridge import XAALEntity, async_setup_factory
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'siren.': [Siren]}
return async_setup_factory(hass, config_entry, async_add_entities, binding)
......@@ -25,7 +24,7 @@ class Siren(XAALEntity, SirenEntity):
@property
def supported_features(self) -> int:
return SirenEntityFeature.TURN_ON|SirenEntityFeature.TURN_OFF|SirenEntityFeature.DURATION
return SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF | SirenEntityFeature.DURATION
def turn_on(self, **kwargs: Any) -> None:
self.send_request('play')
......
......@@ -10,12 +10,11 @@ from .bridge import XAALEntity, async_setup_factory
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback ) -> None:
async def async_setup_entry(hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
binding = {'powerrelay.' : [PowerRelay]}
binding = {'powerrelay.': [PowerRelay]}
return async_setup_factory(hass, config_entry, async_add_entities, binding)
......@@ -32,3 +31,4 @@ class PowerRelay(XAALEntity, SwitchEntity):
def turn_off(self, **kwargs) -> None:
self.send_request('turn_off')
def str_to_id(value: str):
return value.translate ({ord(c): "_" for c in "!@#$%^&*()[]{};:,./<>?\|`~-=_+ "})
\ No newline at end of file
return value.translate ({ord(c): "_" for c in "!@#$%^&*()[]{};:,./<>?\|`~-=_+ "})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment