diff --git a/TODO.rst b/TODO.rst index c39d4976f9b1abdc681e711620998f6198f5e89b..9d3ca706989aa1c075fb9a73bd50413cddcce7d4 100644 --- a/TODO.rst +++ b/TODO.rst @@ -2,22 +2,22 @@ List of things for to be done ============================= +Gateways +-------- +* Rewrite the NetAtmo (they trashed the old API) + +* Switch to async API for Yeelight (this should be a good example) + +* Fix button sending wrong notification (aqara, edisio) Apps ---- - +* Fix rest/dashbard/fakeinput sharing the same template global. This stuff break xaal-pkgrun Lib --- - +* Fix xaal.monitor refresh_rate + * Add a default command line parser: The idea is to provide a way to override default config - -* Look at device management - -* fix message.dump() - -* delay nc.connect() .. - -* add groupID .. diff --git a/apps/conky/pyproject.toml b/apps/conky/pyproject.toml index 05207ce65228724f5d12b4cf321e0e1188f087f5..72fc4b9b9e4f0d09c78541484be0f4f651ab4c7a 100644 --- a/apps/conky/pyproject.toml +++ b/apps/conky/pyproject.toml @@ -13,3 +13,14 @@ dependencies = ["xaal.lib", "xaal.monitor"] [tool.setuptools.packages.find] include = ["xaal.conky"] + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/dashboard/pyproject.toml b/apps/dashboard/pyproject.toml index 12a1f9cd75cfc8c28538142453be9f2b6f2e35a0..13f6d60e42f98db614bbf7ab575803a9e7901eb7 100644 --- a/apps/dashboard/pyproject.toml +++ b/apps/dashboard/pyproject.toml @@ -29,3 +29,14 @@ include = ["xaal.dashboard"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/apps/dashboard/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/apps/dashboard" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/dashboard/xaal/dashboard/app.py b/apps/dashboard/xaal/dashboard/app.py index 141d239cbabc88d44be64069f7c8d292c6dcc3a2..0f0352862eedc1131033f670f6021822d7c4528e 100644 --- a/apps/dashboard/xaal/dashboard/app.py +++ b/apps/dashboard/xaal/dashboard/app.py @@ -1,4 +1,3 @@ -from email.policy import default from gevent import monkey; monkey.patch_all(thread=False) from gevent.pywsgi import WSGIServer from geventwebsocket.handler import WebSocketHandler @@ -47,7 +46,7 @@ def run(): def stop(): global server - server.stop() + server.stop() # pyright: ignore def main(): # This apps can run with gevent alone (without asyncio) diff --git a/apps/dashboard/xaal/dashboard/core/xaal_core.py b/apps/dashboard/xaal/dashboard/core/xaal_core.py index f6af9c9b90e513bb6afc863f61e944bd1e47c7f7..b266495c7494d778e87e8df0109a965443a0a779 100644 --- a/apps/dashboard/xaal/dashboard/core/xaal_core.py +++ b/apps/dashboard/xaal/dashboard/core/xaal_core.py @@ -1,4 +1,4 @@ -from xaal.lib import tools,Engine,Device,helpers +from xaal.lib import tools,Device,helpers from xaal.monitor import Monitor from . import sio @@ -80,4 +80,4 @@ def update_kv(addr,kv): body = {'device':dev.address,'map':kv} print(body) send_request(db_server,'update_keys_values',body) - dev.set_db(kv) \ No newline at end of file + dev.set_db(kv) diff --git a/apps/fuse/pyproject.toml b/apps/fuse/pyproject.toml index 0a5ebc23a68393d67f765546774396f72b807e75..7154300c39a6714f679c31e0da89ed92119914a6 100644 --- a/apps/fuse/pyproject.toml +++ b/apps/fuse/pyproject.toml @@ -21,3 +21,14 @@ include = ["xaal.fuse"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/apps/fuse/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/apps/fuse" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/homeassistant/xaal/bridge.py b/apps/homeassistant/xaal/bridge.py index f8b3d6cf5adcf7ea54abd17b2066294924d55e37..6994eb7ff5812e71b39d9375b704d96d7f02c299 100644 --- a/apps/homeassistant/xaal/bridge.py +++ b/apps/homeassistant/xaal/bridge.py @@ -1,32 +1,31 @@ import asyncio import functools +import logging +from typing import Any, Dict, List -from typing import Dict, List, Any - -from .const import DOMAIN -from . import utils - -from homeassistant.core import HomeAssistant, Event -from homeassistant.helpers.entity import Entity, DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers import device_registry -from homeassistant.helpers.device_registry import DeviceEntry, EVENT_DEVICE_REGISTRY_UPDATED -from homeassistant.helpers import entity_registry +from homeassistant.core import Event, HomeAssistant +from homeassistant.helpers import device_registry, entity_registry +from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED, DeviceEntry +from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED -from xaal.lib import AsyncEngine, tools, Device, Message, bindings +from xaal.lib import AsyncEngine, Device, Message, bindings, tools from xaal.monitor import Monitor, Notification from xaal.monitor.monitor import Device as MonitorDevice -import logging +from . import utils +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) UNSUPPORTED_TYPES = ['cli', 'hmi', 'logger'] -UPDATE_NOTIFICATIONS = [Notification.attribute_change, - Notification.description_change, - Notification.metadata_change] +UPDATE_NOTIFICATIONS = [ + Notification.attribute_change, + Notification.description_change, + Notification.metadata_change, +] class XAALEntity(Entity): @@ -43,7 +42,7 @@ class XAALEntity(Entity): # Life cycle ##################################################### def setup(self): - """ Use setup to tweak a entity at creation """ + """Use setup to tweak a entity at creation""" pass async def async_added_to_hass(self) -> None: @@ -58,7 +57,7 @@ class XAALEntity(Entity): 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. + to the new MonitorDevice. """ self._dev = device self._attr_available = True @@ -78,7 +77,7 @@ class XAALEntity(Entity): "manufacturer": dev.description.get("vendor_id"), "sw_version": dev.description.get("version"), "hw_version": dev.description.get("hw_id"), - "suggested_area": dev.db.get("location") + "suggested_area": dev.db.get("location"), } ##################################################### @@ -86,10 +85,14 @@ class XAALEntity(Entity): ##################################################### 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) + self._bridge.send_request( + [self._dev.address], + action, + body, + ) def get_attribute(self, name: str, default: Dict[str, Any] = None) -> Any: - """ return a attribute for xAAL device""" + """return a attribute for xAAL device""" return self._dev.attributes.get(name, default) @property @@ -100,7 +103,7 @@ class XAALEntity(Entity): # Entity properties ##################################################### def short_type(self) -> str: - """ return a fake device class for entity that doesn't have one """ + """return a fake device class for entity that doesn't have one""" # this apply for light, button # NOTE: I don't know why some HASS entities don't have a device class return self._dev.dev_type.split('.')[0] @@ -140,7 +143,7 @@ class EntityFactory(object): self._binding = binding def build_entities(self, device: MonitorDevice) -> bool: - """ return True if this factory managed to build some entities""" + """return True if this factory managed to build some entities""" result = [] for b_type in self._binding.keys(): if device.dev_type.startswith(b_type): @@ -154,11 +157,12 @@ 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) bridge.add_factory(factory) @@ -169,6 +173,9 @@ def async_setup_factory(hass: HomeAssistant, def filter_msg(msg: Message) -> bool: + if msg.dev_type is None: + # This should not happen + return False m_type = msg.dev_type.split('.')[0] if m_type in UNSUPPORTED_TYPES: return False @@ -176,7 +183,6 @@ def filter_msg(msg: Message) -> bool: class Bridge(object): - def __init__(self, hass: HomeAssistant, db_server) -> None: """Init xAAL bridge.""" self.hass = hass @@ -207,7 +213,7 @@ class Bridge(object): return self._mon.boot_finished async def wait_is_ready(self) -> bool: - """Wait the monitor to received all devices infos """ + """Wait the monitor to received all devices infos""" while 1: if self._mon.boot_finished: return True @@ -233,7 +239,8 @@ class Bridge(object): 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) + if self._mon.db_server: + self.send_request([self._mon.db_server], 'update_keys_values', body) ##################################################### # xAAL Monitor events & callbacks @@ -305,10 +312,10 @@ class Bridge(object): except KeyError: # device already auto-washed or # and old entity - _LOGGER.warn(f"Unknow entity w/ addr {addr}") + _LOGGER.warning(f"Unknow entity w/ addr {addr}") def get_entities(self, addr: bindings.UUID) -> List[XAALEntity] | None: - """ return entities for a given xAAL address""" + """return entities for a given xAAL address""" return self._entities.get(addr) def get_entity_by_id(self, entity_id: str) -> XAALEntity | None: @@ -342,7 +349,7 @@ class Bridge(object): 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 """ + """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) @@ -350,7 +357,7 @@ class Bridge(object): # Factories ##################################################### def add_factory(self, factory: EntityFactory): - """ register a new platform factory""" + """register a new platform factory""" self._factories.append(factory) def remove_factory(self, factory: EntityFactory): diff --git a/apps/homeassistant/xaal/const.py b/apps/homeassistant/xaal/const.py index fa474cf388523775aba0bba2fd4259a3ab480a86..4cbb11ab57c5eb9ca072060b6634acd0ae1083fa 100644 --- a/apps/homeassistant/xaal/const.py +++ b/apps/homeassistant/xaal/const.py @@ -4,12 +4,15 @@ import voluptuous as vol DOMAIN = "xaal" CONF_DB_SERVER = "db_server" -XAAL_TTS_SCHEMA = vol.Schema({ - vol.Optional("title"): cv.template, - vol.Required("message"): cv.template, -}) - -XAAL_TTS_SCHEMA = vol.Schema({ - vol.Required("message"): cv.template, -}) +XAAL_TTS_SCHEMA = vol.Schema( + { + vol.Optional("title"): cv.template, + vol.Required("message"): cv.template, + } +) +XAAL_TTS_SCHEMA = vol.Schema( + { + vol.Required("message"): cv.template, + } +) diff --git a/apps/homeassistant/xaal/light.py b/apps/homeassistant/xaal/light.py index 0958f7d053bb95f647801ac8baff34b033d75f7d..49c24354c215832cd2790442e53d9e203f89636f 100644 --- a/apps/homeassistant/xaal/light.py +++ b/apps/homeassistant/xaal/light.py @@ -1,18 +1,24 @@ import logging +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP_KELVIN, + ATTR_HS_COLOR, + ColorMode, + LightEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.components.light import LightEntity, ColorMode, ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_COLOR_TEMP_KELVIN 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) @@ -47,7 +53,7 @@ class Lamp(XAALEntity, LightEntity): def hs_color(self) -> tuple[float, float] | None: hsv = self.get_attribute('hsv') if hsv: - return (hsv[0], hsv[1]*100) + return (hsv[0], hsv[1] * 100) @property def color_temp_kelvin(self) -> int | None: diff --git a/apps/homeassistant/xaal/pyproject.toml b/apps/homeassistant/xaal/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..f855e0fd6c7218a0c2346bc3ea017a23f6aab219 --- /dev/null +++ b/apps/homeassistant/xaal/pyproject.toml @@ -0,0 +1,9 @@ +[tool.ruff] +line-length = 110 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 110 +skip-string-normalization = true diff --git a/apps/legacytools/pyproject.toml b/apps/legacytools/pyproject.toml index 62703cc28a94df4e5249cfb0bbca6516aef10d7f..b9c4bfad70dff19d3cd61a1aa18975ed9d62c599 100644 --- a/apps/legacytools/pyproject.toml +++ b/apps/legacytools/pyproject.toml @@ -31,3 +31,14 @@ include = ["xaal.legacytools"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/apps/legacytools/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/apps/legacytools" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/legacytools/xaal/legacytools/dumper.py b/apps/legacytools/xaal/legacytools/dumper.py index f4b0058d4507ac27f258b94b04fd3270509e5bc7..4a305b0cba9d1b6712fce3e9fcbcc03332e142b7 100644 --- a/apps/legacytools/xaal/legacytools/dumper.py +++ b/apps/legacytools/xaal/legacytools/dumper.py @@ -18,13 +18,15 @@ # -from xaal.lib import Engine,helpers +from xaal.lib import Engine, helpers helpers.set_console_title("xaal-dumper") + def display(msg): msg.dump() + def main(): try: eng = Engine() diff --git a/apps/legacytools/xaal/legacytools/info.py b/apps/legacytools/xaal/legacytools/info.py index 1b90663f5643dff63c4eff1b07f3e51885762bdc..66605428cf7a407e20213e6a5df0fcb1db8b38ba 100644 --- a/apps/legacytools/xaal/legacytools/info.py +++ b/apps/legacytools/xaal/legacytools/info.py @@ -17,26 +17,25 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from xaal.lib import Engine, Device -from xaal.lib import tools - +import pprint import sys import time -import pprint + +from xaal.lib import Device, Engine, tools from .ansi2 import term + def usage(): print("xaal-info xxxx-xxxx-xxxx : display information about a given device") class InfoDumper: - - def __init__(self,engine): + def __init__(self, engine): self.eng = engine # new fake device addr = tools.get_random_uuid() - dev = Device("cli.experimental",addr) + dev = Device("cli.experimental", addr) dev.vendor_id = "IHSEV" dev.product_id = "xAAL InfoDumper" self.eng.add_device(dev) @@ -44,9 +43,8 @@ class InfoDumper: print(f"xAAL Info dumper [{addr}]") self.dev = dev - def query(self,addr): - """ send getDescription & getAttributes and wait for reply""" - + def query(self, addr): + """send getDescription & getAttributes and wait for reply""" self.target = addr self.msgCnt = 0 self.timer = 0 @@ -63,12 +61,13 @@ class InfoDumper: if self.timer > 30: print("TimeOut...") break - if self.msgCnt > 1:break + if self.msgCnt > 1: + break self.timer += 1 print('\n') - def parse_answer(self,msg): - """ message parser """ + def parse_answer(self, msg): + """message parser""" if msg.is_reply(): if self.dev.address in msg.targets: if self.target == msg.source: @@ -91,7 +90,7 @@ def main(): eng.start() dev = InfoDumper(eng) dev.query(addr) - print("Time : %0.3f sec" % (time.time() -t0)) + print("Time : %0.3f sec" % (time.time() - t0)) else: print("Invalid addr") diff --git a/apps/legacytools/xaal/legacytools/inspector.py b/apps/legacytools/xaal/legacytools/inspector.py index 7c1ff99dbc2b21704ec97e0aa13612fc924ddb93..df32eae2f64244ab1747654fcec03e329a7cf0ff 100644 --- a/apps/legacytools/xaal/legacytools/inspector.py +++ b/apps/legacytools/xaal/legacytools/inspector.py @@ -1,17 +1,22 @@ -from xaal.lib import NetworkConnector,config,cbor,core +from xaal.lib import NetworkConnector, cbor, config -class ParseError(Exception):pass -def incr(value,max_value,increment=1): +class ParseError(Exception): + pass + + +def incr(value, max_value, increment=1): tmp = value + increment if tmp > max_value: raise ParseError("Unable to go forward, issue w/ packet lenght ?") return tmp + def hexdump(data): - print("HEX:",end="") + print("HEX:", end="") for k in data: - print("0x%x" % k,end=", ") + print("0x%x" % k, end=", ") + def parse(data): i = 0 @@ -21,51 +26,52 @@ def parse(data): header_size = data[i] if header_size != 0x85: raise ParseError("Wrong array in header: 0x%x" % header_size) - print("Array w/ size=5 (0x85) : 0x%x" %header_size) + print("Array w/ size=5 (0x85) : 0x%x" % header_size) - i = incr(i,size) + i = incr(i, size) ver = data[i] if ver != 7: raise ParseError("Wrong packet version: 0x%x" % ver) print("Version: 0%x" % ver) - i = incr(i,size) + i = incr(i, size) ts0_size = data[i] - if ts0_size not in [0x1a,0x1b]: + if ts0_size not in [0x1A, 0x1B]: raise ParseError("Wrong timestamp part0 size: 0x%x" % ts0_size) - print("TS0 size (0x1a or 0x1b): 0x%x" % ts0_size ) - if ts0_size == 0x1a: - i=incr(i,size,5) - ts0 = list(data[i-4:i]) - print("TS0: ",end="") + print("TS0 size (0x1a or 0x1b): 0x%x" % ts0_size) + if ts0_size == 0x1A: + i = incr(i, size, 5) + ts0 = list(data[i - 4 : i]) + print("TS0: ", end="") hexdump(ts0) - print("=> %s" % cbor.loads(bytes(data[i-5:i]))) + print("=> %s" % cbor.loads(bytes(data[i - 5 : i]))) - if ts0_size == 0x1b: - i=incr(i,size,9) - ts0 = list(data[i-8:i]) - print("TS0: ",end="") + if ts0_size == 0x1B: + i = incr(i, size, 9) + ts0 = list(data[i - 8 : i]) + print("TS0: ", end="") hexdump(ts0) - print("=> %s" % cbor.loads(bytes(data[i-9:i]))) + print("=> %s" % cbor.loads(bytes(data[i - 9 : i]))) ts1_size = data[i] - if ts1_size != 0x1a: + if ts1_size != 0x1A: raise ParseError("Wrong timestamp part1 size: 0x%x" % ts1_size) - print("TS1 size (0x1a): 0x%x" % ts0_size ) - i = incr(i,size,5) - ts1 = list(data[i-4:i]) - print("TS1: ",end="") + print("TS1 size (0x1a): 0x%x" % ts0_size) + i = incr(i, size, 5) + ts1 = list(data[i - 4 : i]) + print("TS1: ", end="") hexdump(ts1) - print("=> %s" % cbor.loads(bytes(data[i-5:i]))) + print("=> %s" % cbor.loads(bytes(data[i - 5 : i]))) target_size = data[i] - hexdump(data[i:i+10]) - #print("0x%x" % target_size) + hexdump(data[i : i + 10]) + # print("0x%x" % target_size) print() + def main(): - nc = NetworkConnector(config.address,config.port,config.hops) + nc = NetworkConnector(config.address, config.port, config.hops) while 1: data = nc.get_data() if data: @@ -74,9 +80,10 @@ def main(): except ParseError as e: print("ERROR ==> %s" % e) + if __name__ == '__main__': try: main() except KeyboardInterrupt: print("Bye...") - \ No newline at end of file + diff --git a/apps/legacytools/xaal/legacytools/isalive.py b/apps/legacytools/xaal/legacytools/isalive.py index 341985cacd7942abc2c5fbd4edadfa136cf65860..efade13c7209e0bf982a3154f829660b1d3808fd 100644 --- a/apps/legacytools/xaal/legacytools/isalive.py +++ b/apps/legacytools/xaal/legacytools/isalive.py @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -from xaal.lib import Engine, Device, tools, config,helpers +from xaal.lib import Engine, Device, tools, helpers import sys import time @@ -26,28 +26,33 @@ import logging helpers.setup_console_logger() logger = logging.getLogger("xaal-isalive") -class Scanner: - def __init__(self,engine): +class Scanner: + def __init__(self, engine): self.eng = engine # new fake device - self.dev = Device("cli.experimental",tools.get_random_uuid()) + self.dev = Device("cli.experimental", tools.get_random_uuid()) self.eng.add_device(self.dev) self.eng.subscribe(self.parse_answer) - def query(self,dev_type): + def query(self, dev_type): if not tools.is_valid_dev_type(dev_type): logger.warning("dev_type not valid [%s]" % dev_type) return self.dev_type = dev_type self.seen = [] - logger.info("[%s] searching [%s]" % (self.dev.address,self.dev_type)) - self.eng.send_is_alive(self.dev,dev_types=[self.dev_type,]) + logger.info("[%s] searching [%s]" % (self.dev.address, self.dev_type)) + self.eng.send_is_alive( + self.dev, + dev_types=[ + self.dev_type, + ], + ) - print("="*70) + print("=" * 70) self.loop() - print("="*70) + print("=" * 70) print("Found %d devices" % len(self.seen)) def loop(self): @@ -57,15 +62,15 @@ class Scanner: if time.time() > (t0 + 2): break - def parse_answer(self,msg): + def parse_answer(self, msg): if msg.is_alive(): # hidding myself if msg.source == self.dev.address: return - #it is really for us ? + # it is really for us ? if self.dev_type != 'any.any': - (target_dev_type,target_devsubtype) = self.dev_type.split('.') - (msg_dev_type,msg_devsubtype) = msg.dev_type.split('.') + (target_dev_type, target_devsubtype) = self.dev_type.split('.') + (msg_dev_type, msg_devsubtype) = msg.dev_type.split('.') if msg_dev_type != target_dev_type: return if target_devsubtype != 'any' and target_devsubtype != msg_devsubtype: @@ -73,13 +78,12 @@ class Scanner: if msg.source in self.seen: return # everything is Ok :) - print("%s : %s" % (msg.source,msg.dev_type)) + print("%s : %s" % (msg.source, msg.dev_type)) self.seen.append(msg.source) - def run(): - """ run the isalive scanner from cmdline""" + """run the isalive scanner from cmdline""" eng = Engine() eng.disable_msg_filter() @@ -91,17 +95,19 @@ def run(): scan.query(dev_type) -def search(engine,dev_type='any.any'): - """ send request and return list of xaal-addr""" +def search(engine, dev_type='any.any'): + """send request and return list of xaal-addr""" scan = Scanner(engine) scan.query(dev_type) return scan.seen + def main(): try: run() except KeyboardInterrupt: print("Bye bye") + if __name__ == '__main__': main() diff --git a/apps/legacytools/xaal/legacytools/keygen.py b/apps/legacytools/xaal/legacytools/keygen.py index 22c2acb3f07ea0c13d7a9336b996f2d396011908..d4707cb8c4b69362b542727a4e02370d7a4d663b 100644 --- a/apps/legacytools/xaal/legacytools/keygen.py +++ b/apps/legacytools/xaal/legacytools/keygen.py @@ -1,22 +1,19 @@ -""" Tool to build a key pass for xAAL config file""" - -from __future__ import print_function - -from six.moves import input +"""Tool to build a key pass for xAAL config file""" +import binascii from xaal.lib import tools -import binascii + def main(): try: temp = input("Please enter your passphrase: ") - key = tools.pass2key(temp) + key = tools.pass2key(temp) print("Cut & Paste this key in your xAAL config-file") - print("key=%s"% binascii.hexlify(key).decode('utf-8')) + print("key=%s" % binascii.hexlify(key).decode('utf-8')) except KeyboardInterrupt: print("Bye Bye..") - + if __name__ == '__main__': main() diff --git a/apps/legacytools/xaal/legacytools/log.py b/apps/legacytools/xaal/legacytools/log.py index ad3ec6011108e8563ada703c7b8303ae644516d1..5d5fc601a85a64c776b21b8c70a9c5fa325c8ed2 100644 --- a/apps/legacytools/xaal/legacytools/log.py +++ b/apps/legacytools/xaal/legacytools/log.py @@ -1,15 +1,16 @@ -""" dumb script that display attributes change the xAAL bus""" +"""dumb script that display attributes change the xAAL bus""" -from xaal.lib import Engine,helpers,Message +from xaal.lib import Engine, helpers import time helpers.set_console_title("xaal-log") + def print_evt(msg): if msg.is_alive(): return if msg.is_notify(): - print("%s %s %s %s %s" % (time.ctime(),msg.source,msg.dev_type,msg.action,msg.body)) + print("%s %s %s %s %s" % (time.ctime(), msg.source, msg.dev_type, msg.action, msg.body)) def main(): @@ -21,5 +22,6 @@ def main(): except KeyboardInterrupt: print("ByeBye..") + if __name__ == '__main__': main() diff --git a/apps/legacytools/xaal/legacytools/pkgrun.py b/apps/legacytools/xaal/legacytools/pkgrun.py index e3e12f1204c8e60084aba8df4c0442e869600d61..065960ec19536d1bbe6bb22e12568e9567ce2a6e 100755 --- a/apps/legacytools/xaal/legacytools/pkgrun.py +++ b/apps/legacytools/xaal/legacytools/pkgrun.py @@ -1,6 +1,7 @@ #!/usr/bin/env python try: - from gevent import monkey;monkey.patch_all(thread=False) + from gevent import monkey + monkey.patch_all(thread=False) except ModuleNotFoundError: pass @@ -23,23 +24,25 @@ def load_pkgs(eng): try: mod = importlib.import_module(xaal_mod) except ModuleNotFoundError: - logger.critical("Unable to load module: %s" %xaal_mod ) + logger.critical("Unable to load module: %s" % xaal_mod) continue - - if hasattr(mod,'setup') == False: + + if not hasattr(mod, 'setup'): logger.critical("Unable to setup %s" % xaal_mod) continue mod.setup(eng) + def run(): # some init stuffs helpers.setup_console_logger() - #helpers.setup_file_logger(MY_NAME) + # helpers.setup_file_logger(MY_NAME) # Start the engine eng = Engine() eng.start() load_pkgs(eng) - eng.run() + eng.run() + def main(): try: diff --git a/apps/legacytools/xaal/legacytools/querydb.py b/apps/legacytools/xaal/legacytools/querydb.py index 0476a00e0f12109b02050b20b74eec92e383f841..67ce6eb6ba8c2bcc15743a92937ea6d69200991a 100644 --- a/apps/legacytools/xaal/legacytools/querydb.py +++ b/apps/legacytools/xaal/legacytools/querydb.py @@ -17,41 +17,39 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from xaal.lib import Engine, Device, MessageType -from xaal.lib import tools -from . import isalive - - import sys -import json import time +from xaal.lib import Device, Engine, MessageType, tools + +from . import isalive from .ansi2 import term + def usage(): print("xaal-querydb xxxx-xxxx-xxxx : display metadata for a given device") class QueryDB: - - def __init__(self,engine,db_servers): + def __init__(self, engine, db_servers): self.eng = engine self.db_servers = db_servers # new fake device self.addr = tools.get_random_uuid() - self.dev = Device("cli.experimental",self.addr) + self.dev = Device("cli.experimental", self.addr) self.eng.add_device(self.dev) - self.eng.add_rx_handler(self.parse_answer) - - print("xAAL DB query [%s]" % self.addr) + self.eng.subscribe(self.parse_answer) + print("xAAL DB query [%s]" % self.addr) - def query(self,addr): + def query(self, addr): self.timer = 0 mf = self.eng.msg_factory - body = {'device': addr,} - msg = mf.build_msg(self.dev,self.db_servers, MessageType.REQUEST,'get_keys_values',body) + body = { + 'device': addr, + } + msg = mf.build_msg(self.dev, self.db_servers, MessageType.REQUEST, 'get_keys_values', body) self.eng.queue_msg(msg) while 1: @@ -62,12 +60,12 @@ class QueryDB: self.timer += 1 print('\n') - def parse_answer(self,msg): - """ message parser """ + def parse_answer(self, msg): + """message parser""" if msg.is_reply(): if (self.addr in msg.targets) and (msg.action == 'get_keys_values'): term('yellow') - print("%s => " % msg.source,end='') + print("%s => " % msg.source, end='') print(msg.body) term() @@ -79,13 +77,13 @@ def main(): t0 = time.time() eng = Engine() eng.start() - db_servers = isalive.search(eng,'metadatadb.basic') + db_servers = isalive.search(eng, 'metadatadb.basic') if len(db_servers) == 0: print("No metadb server found") return - dev = QueryDB(eng,db_servers) + dev = QueryDB(eng, db_servers) dev.query(addr) - print("Time : %0.3f sec" % (time.time() -t0)) + print("Time : %0.3f sec" % (time.time() - t0)) else: print("Invalid addr") diff --git a/apps/legacytools/xaal/legacytools/tail.py b/apps/legacytools/xaal/legacytools/tail.py index da94c0e319a3f1a6c8fbe8e47ce05468ecca5d58..6985e5dda00e3e3adddb81d6f36af90f5ffb802c 100644 --- a/apps/legacytools/xaal/legacytools/tail.py +++ b/apps/legacytools/xaal/legacytools/tail.py @@ -17,16 +17,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -from xaal.lib import Engine,MessageType -from xaal.lib import tools +import shutil +import sys + +from xaal.lib import Engine, MessageType, tools from .ansi2 import term -import sys -import shutil level = 0 -HIDE_ACTION=['get_attributes','get_description','get_keys_values','is_alive'] +HIDE_ACTION = ['get_attributes', 'get_description', 'get_keys_values', 'is_alive'] def type_to_string(mtype): @@ -37,20 +37,23 @@ def type_to_string(mtype): def display(msg): term('yellow') - if (msg.action in HIDE_ACTION and level==2): + if msg.action in HIDE_ACTION and level == 2: return - + if msg.is_reply(): - if level > 2: return + if level > 2: + return term('red') if msg.is_request(): - if level > 3: return + if level > 3: + return term('green') if msg.is_notify(): if msg.is_alive(): - if level > 0: return + if level > 0: + return term('grey') if msg.is_attributes_change(): term('cyan') @@ -59,11 +62,12 @@ def display(msg): tmp = shutil.get_terminal_size()[0] - (8 + 20 + 36 + 20 + 16 + 9) if tmp < 50: tmp = 50 - BODY_FORMAT = '%-50.'+str(tmp)+'s' - FORMAT = '%-8.08s=> %-18.18s %-36.36s (%-20.20s) %-16.16s '+BODY_FORMAT - res = FORMAT % (type_to_string(msg.msg_type),msg.action,msg.source,msg.dev_type,targets,msg.body) + BODY_FORMAT = '%-50.' + str(tmp) + 's' + FORMAT = '%-8.08s=> %-18.18s %-36.36s (%-20.20s) %-16.16s ' + BODY_FORMAT + res = FORMAT % (type_to_string(msg.msg_type), msg.action, msg.source, msg.dev_type, targets, msg.body) print(res) + def usage(): print("%s : monitor xAAL network w/ tail format" % sys.argv[0]) print(" usage : %s log-level" % sys.argv[0]) @@ -85,8 +89,8 @@ def main(): eng.start() term('@@') - FORMAT = '%-8.08s=> %-18.18s %-36.36s (%-20.20s) %-16.16s %-50.50s' - print(FORMAT % ('type','action','source','dev_type','targets','body')) + FORMAT = '%-8.08s=> %-18.18s %-36.36s (%-20.20s) %-16.16s %-50.50s' + print(FORMAT % ('type', 'action', 'source', 'dev_type', 'targets', 'body')) try: eng.run() except KeyboardInterrupt: @@ -94,5 +98,6 @@ def main(): else: usage() + if __name__ == '__main__': main() diff --git a/apps/legacytools/xaal/legacytools/uuidgen.py b/apps/legacytools/xaal/legacytools/uuidgen.py index 94c70e381e99d935b4997dbf3f5d905331de5cf3..ca4c9255c35551f7b5c4281eb60bcbd9e4a8060f 100644 --- a/apps/legacytools/xaal/legacytools/uuidgen.py +++ b/apps/legacytools/xaal/legacytools/uuidgen.py @@ -7,19 +7,20 @@ def main(): if len(sys.argv) > 1: value = sys.argv[1] uuid = tools.get_uuid(value) - if uuid == None: - uuid=tools.get_random_uuid() + if uuid is None: + uuid = tools.get_random_uuid() print("TXT: %s" % uuid) - print("HEX: ",end="") + print("HEX: ", end="") for b in uuid.bytes: - print("0x%x" % b,end=',') + print("0x%x" % b, end=',') print() - print("INT: ",end="") + print("INT: ", end="") for b in uuid.bytes: - print("%d" % b,end=',') + print("%d" % b, end=',') print() + if __name__ == '__main__': main() diff --git a/apps/legacytools/xaal/legacytools/walker.py b/apps/legacytools/xaal/legacytools/walker.py index 5ff082dc9a5dd94cf7523438e232f13f78e2db44..a1f8b31de3a94989db28f9697dd3201e5d2e6156 100644 --- a/apps/legacytools/xaal/legacytools/walker.py +++ b/apps/legacytools/xaal/legacytools/walker.py @@ -9,13 +9,12 @@ import time import xaal.lib - from . import info from . import isalive def main(): - """ search for alive devices and gather informations about this device""" + """search for alive devices and gather informations about this device""" eng = xaal.lib.Engine() eng.disable_msg_filter() @@ -23,13 +22,14 @@ def main(): devtype = 'any.any' if len(sys.argv) == 2: devtype = sys.argv[1] - devs = isalive.search(eng,devtype) + devs = isalive.search(eng, devtype) print() dumper = info.InfoDumper(eng) t0 = time.time() for k in devs: dumper.query(k) - print("Time : %0.3f sec (%d devices)" % (time.time() -t0,len(devs))) + print("Time : %0.3f sec (%d devices)" % (time.time() - t0, len(devs))) + -if __name__=='__main__': +if __name__ == '__main__': main() diff --git a/apps/rest/pyproject.toml b/apps/rest/pyproject.toml index 1a3f62a92971c8c2c9a41354f3d8426d03f1dfdb..12efd4a3f01bdd9289bb8b0e0f6764d89eeb5471 100644 --- a/apps/rest/pyproject.toml +++ b/apps/rest/pyproject.toml @@ -25,3 +25,14 @@ include = ["xaal.rest"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/apps/rest/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/apps/rest" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/tools/pyproject.toml b/apps/tools/pyproject.toml index bbad4f788b483ed83f021521a23bc8424f7109e5..f22105553d791af369940e63b71a90f47a2cfdc9 100644 --- a/apps/tools/pyproject.toml +++ b/apps/tools/pyproject.toml @@ -33,3 +33,14 @@ include = ["xaal.tools"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/apps/tools/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/apps/tools" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/apps/tools/xaal/tools/toolbox.py b/apps/tools/xaal/tools/toolbox.py index 3293894677008843590b7669ce5c8b6aa6364968..81284ab11e45eec6298691856160cf0ab78d8324 100644 --- a/apps/tools/xaal/tools/toolbox.py +++ b/apps/tools/xaal/tools/toolbox.py @@ -18,7 +18,7 @@ if sys.argv[0].endswith('pkgrun'): # xAAL import from xaal.lib import AsyncEngine, Device, tools, helpers, config -from xaal.lib.messages import MessageType +from xaal.lib.messages import Message, MessageType # General python import import asyncio @@ -43,18 +43,7 @@ DB_DEV_TYPE = "metadatadb.basic" class Colors(enum.Enum): DEFAULT = fore.WHITE - # ALIVE = fore.LIGHT_GRAY - # ATTRIBUTS = fore.LIGHT_YELLOW - # REQUEST = fore.LIGHT_RED - # IS_ALIVE = fore.LIGHT_MAGENTA - # REPLY = fore.LIGHT_CYAN - # NOTIFY = fore.LIGHT_GREEN - # DEV_TYPE = fore.LIGHT_BLUE - # ADDR = fore.LIGHT_RED - # INFO = fore.CYAN - # DB = fore.SPRING_GREEN_1 - - ALIVE = fore.LIGHT_GRAY + ALIVE = fore.LIGHT_GRAY ATTRIBUTS = fore.YELLOW REQUEST = fore.RED IS_ALIVE = fore.MAGENTA @@ -148,7 +137,7 @@ class DeviceInfo(object): r.append([k, v]) # attributes - if len(self.attributes) > 0: + if self.attributes and len(self.attributes) > 0: # tabulate has no minimal width so used this trick r.append(['-'*22, '-'*46]) r.append(['Attributes', '']) @@ -172,9 +161,6 @@ class DeviceInfo(object): class ToolboxHelper(object): def __init__(self) -> None: self.name = None # cmdline name - self.parser = None - self.engine = None # toolbox engine - self.device = None # current device self.devices = [] # devices list (alive / walker) # idle detector / force exit self.exit_event = asyncio.Event() @@ -238,7 +224,10 @@ class ToolboxHelper(object): # command line parsing ##################################################### def setup_msg_parser(self): - self.engine.subscribe(self.parse_msg) + # match the subscribe API (no return value) + def handle(msg:Message): + self.parse_msg(msg) + self.engine.subscribe(handle) def parse(self): self.options, self.args = self.parser.parse_args() @@ -311,7 +300,7 @@ class ToolboxHelper(object): print(color_value, end='') msg.dump() if color: - print(style.RESET, end='') + print(style.RESET, end='') # pyright: ignore def color_for_msg(self, msg): color_value = Colors.DEFAULT @@ -420,8 +409,9 @@ class ToolboxHelper(object): def run_until_timeout(self, timeout=3): """ run the engine until timeout """ - self.engine.add_timer(self.quit, timeout) - self.engine.run() + if self.engine: + self.engine.add_timer(self.quit, timeout) + self.engine.run() def run_until_idle(self): self.engine.new_task(self.idle_detector()) @@ -448,7 +438,7 @@ class ToolboxHelper(object): def colorize(color, text): - return f"{color}{text}{style.RESET}" + return f"{color}{text}{style.RESET}" # pyright: ignore def now(): @@ -671,7 +661,7 @@ def log(): color_value = Colors.REPLY if color: - dump = f"{Colors.DEFAULT}{time.ctime()} {Colors.ADDR}{msg.source} {Colors.DEV_TYPE}{msg.dev_type}\t{color_value}{msg.action} {msg.body}{style.RESET}" + dump = f"{Colors.DEFAULT}{time.ctime()} {Colors.ADDR}{msg.source} {Colors.DEV_TYPE}{msg.dev_type}\t{color_value}{msg.action} {msg.body}{style.RESET}" # pyright: ignore else: dump = f"{time.ctime()} {msg.source} {msg.dev_type}\t{msg.action} {msg.body}" print(dump) @@ -979,7 +969,7 @@ def shell(): print("$ pip install ipython\n") exit(1) - # for a unknown reason, watchdog_task raise a RuntimeError: + # for a unknown reason, watchdog_task raise a RuntimeError: # "Event loop is closed" on the shell exit. To avoid issue # we need to stop all tasks. async def on_stop(): @@ -1012,4 +1002,3 @@ def shell(): print("* Ending Engine") eng.shutdown() print("* Bye bye") - diff --git a/core/metadb/pyproject.toml b/core/metadb/pyproject.toml index 8bdc8df9677d0b4646c2671ba0a6c44f02c6563c..11d1e071aa2c6e4c13f82ff87d19ffb6911a17a2 100644 --- a/core/metadb/pyproject.toml +++ b/core/metadb/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.metadb"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/core/metadb/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/core/metadb" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/assistants/Alexa/pyproject.toml b/devices/assistants/Alexa/pyproject.toml index 3c3adf0a8f72df1ad2a381db1f71a125df5bdd2d..87509c13b171227383b5e29afb6db0423e1721eb 100644 --- a/devices/assistants/Alexa/pyproject.toml +++ b/devices/assistants/Alexa/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.alexa"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/assistants/Alexa/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/assistants/Alexa" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/emulations/Fauxmo/pyproject.toml b/devices/emulations/Fauxmo/pyproject.toml index 9ceffa6d50467155f883e0d2f001d7b505e79178..860c965b7f5d9abcf561576d5829de9c11d47ff4 100644 --- a/devices/emulations/Fauxmo/pyproject.toml +++ b/devices/emulations/Fauxmo/pyproject.toml @@ -19,3 +19,14 @@ include = ["xaal.fauxmo"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/emulations/Fauxmo/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/emulations/Fauxmo" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/loggers/influxdb/pyproject.toml b/devices/loggers/influxdb/pyproject.toml index 382ea7221aa3c7ea965aef154530c775e15c949d..d32153a530576af6b6d0d8e4b83e2299a0aec6e2 100644 --- a/devices/loggers/influxdb/pyproject.toml +++ b/devices/loggers/influxdb/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.influxdb"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/loggers/influxdb/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/loggers/influxdb" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/loggers/mqtt/pyproject.toml b/devices/loggers/mqtt/pyproject.toml index 0dd15cef7ab132b0083a8fc03eeb12a78a128706..ef7cdb95ecac6c578125ec3feeda429e14f42f25 100644 --- a/devices/loggers/mqtt/pyproject.toml +++ b/devices/loggers/mqtt/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.mqttlogger"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/loggers/mqtt/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/loggers/mqtt" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/loggers/warp10/pyproject.toml b/devices/loggers/warp10/pyproject.toml index 4544dfb466255b366ecda9f79d47d07b61c17857..ae1aaee3f058a78b33c81e2bf67724d4be27985a 100644 --- a/devices/loggers/warp10/pyproject.toml +++ b/devices/loggers/warp10/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.warp10"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/loggers/warp10/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/loggers/warp10" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/notifications/gtk-notify/pyproject.toml b/devices/notifications/gtk-notify/pyproject.toml index 550c0edc7f0e6be0a83703f3a0f59d0ca1f5c1d5..1bf8dbf1d4c2c5d0a49fc756a8fcbbe4eebb712c 100644 --- a/devices/notifications/gtk-notify/pyproject.toml +++ b/devices/notifications/gtk-notify/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.gtknotify"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/notifications/gtk-notify/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/notifications/gtk-notify" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/notifications/pushbullet/pyproject.toml b/devices/notifications/pushbullet/pyproject.toml index b4830396280d9fed9eeadfb3834fdf41eb759e6d..c66c4603748c8a9308832efdce26304e32c9ea0d 100644 --- a/devices/notifications/pushbullet/pyproject.toml +++ b/devices/notifications/pushbullet/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.pushbullet"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/notifications/pushbullet/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/notifications/pushbullet" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/Aqara/pyproject.toml b/devices/protocols/Aqara/pyproject.toml index 80fbeea625f7d2732008965dd221070ba8f15921..3d6c7aed07d4eaf27ef751b6da9a7a889eca3466 100644 --- a/devices/protocols/Aqara/pyproject.toml +++ b/devices/protocols/Aqara/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.aqara"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Aqara/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Aqara" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/ESPHome/pyproject.toml b/devices/protocols/ESPHome/pyproject.toml index cc6399f1af9d36c8c9187ead3c82706be7d75425..345d9da2af9553e575c221558fc2295371ab2bae 100644 --- a/devices/protocols/ESPHome/pyproject.toml +++ b/devices/protocols/ESPHome/pyproject.toml @@ -19,3 +19,14 @@ include = ["xaal.esphome"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/ESPHome/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/ESPHome" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/ESPHome/xaal/esphome/bindings.py b/devices/protocols/ESPHome/xaal/esphome/bindings.py index 4b8a8a20532d4aa93b205ed0e2169a5064dda6a7..30f91475a02892f8ee3a464d721792145529e858 100644 --- a/devices/protocols/ESPHome/xaal/esphome/bindings.py +++ b/devices/protocols/ESPHome/xaal/esphome/bindings.py @@ -1,12 +1,10 @@ -from xaal.lib import tools,Device -from xaal.schemas import devices - -from pprint import pprint -from aioesphomeapi import APIClient, APIConnectionError,model - import asyncio import logging +from aioesphomeapi import APIClient, APIConnectionError, model + +from xaal.lib import Device, tools +from xaal.schemas import devices logging.getLogger('aioesphomeapi').setLevel(logging.INFO) logger = logging.getLogger(__name__) @@ -18,7 +16,9 @@ class ESPDevice: self.port = cfg.get('port',6053) self.key = cfg.get('key',None) self.passwd = cfg.get('passwd',None) - self.base_addr = tools.get_uuid(cfg.get('base_addr')) + addr = tools.get_uuid(cfg.get('base_addr')) + assert addr, "Invalid base_addr" + self.base_addr = addr self.embedded = [] self.disconnected = asyncio.Event() @@ -91,10 +91,12 @@ def find_device_class(info): if type_ == model.LightInfo: return Lamp elif type_ == model.SwitchInfo: - return PowerRelay + return PowerRelay elif type_ == model.SensorInfo: - if info.device_class == 'signal_strength': return WifiMeter - if info.device_class == 'power': return PowerMeter + if info.device_class == 'signal_strength': + return WifiMeter + if info.device_class == 'power': + return PowerMeter elif type_ == model.BinarySensorInfo: return Contact @@ -121,6 +123,7 @@ class EntityMixin(object): return self.info.key def setup_device_description(self): + assert self.dev, "Device not setup" self.dev.vendor_id = 'ESPHome' self.dev.product_id = self.info.unique_id self.dev.hw_id = self.info.key diff --git a/devices/protocols/Edisio/pyproject.toml b/devices/protocols/Edisio/pyproject.toml index 36a00811e7e58f1c359535fb1d90dd305d36402c..b70a9dc9ce902dc86331d2f1fe6e59a17386815d 100644 --- a/devices/protocols/Edisio/pyproject.toml +++ b/devices/protocols/Edisio/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.edisio"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Edisio/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Edisio" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/HQ433/pyproject.toml b/devices/protocols/HQ433/pyproject.toml index 86fe9fc498ee9123865e7c2ebcc9adca48785377..98dc34e17bc94091de342dae729a1e3ebce59b31 100644 --- a/devices/protocols/HQ433/pyproject.toml +++ b/devices/protocols/HQ433/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.hq433"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/HQ433/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/HQ433" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/HomeKit/pyproject.toml b/devices/protocols/HomeKit/pyproject.toml index 17ed221f71ef24c71cf6482db336eca49264ca20..52e826fa3e1965284447ffa26e044d6338d34711 100644 --- a/devices/protocols/HomeKit/pyproject.toml +++ b/devices/protocols/HomeKit/pyproject.toml @@ -19,3 +19,14 @@ include = ["xaal.homekit"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/HomeKit/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/HomeKit" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/IPX-800/pyproject.toml b/devices/protocols/IPX-800/pyproject.toml index eddfc7d559646133474daa916a4eac285e7e3d36..9aa5d867d20d79ecc3603187dd0f5e40e7ca29b9 100644 --- a/devices/protocols/IPX-800/pyproject.toml +++ b/devices/protocols/IPX-800/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.ipx800"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/IPX-800/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/IPX-800" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/KNX/pyproject.toml b/devices/protocols/KNX/pyproject.toml index 228750b78a8cd5c4f71ea13e67484759c36c0196..53b72d51684921c46a5a68b4a5973fb415814ff2 100644 --- a/devices/protocols/KNX/pyproject.toml +++ b/devices/protocols/KNX/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.knx"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/KNX/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/KNX" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/Meross/pyproject.toml b/devices/protocols/Meross/pyproject.toml index 764aceff6f103faccfdc8e9d757e16c400c73280..9d621bb4ca3c0fc3ac94a2a2d79c30facbaa39b5 100644 --- a/devices/protocols/Meross/pyproject.toml +++ b/devices/protocols/Meross/pyproject.toml @@ -9,7 +9,7 @@ authors = [ license = { text = "GPL License" } classifiers = ["Programming Language :: Python", "Topic :: Home Automation"] keywords = ["xaal", "meross"] -dependencies = ["xaal.lib", "xaal.schemas", "meross_iot==0.4.4.4"] +dependencies = ["xaal.lib", "xaal.schemas", "meross_iot==0.4.7.3"] [tool.setuptools.packages.find] include = ["xaal.meross"] @@ -18,3 +18,14 @@ include = ["xaal.meross"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Meross/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Meross" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/Netatmo/pyproject.toml b/devices/protocols/Netatmo/pyproject.toml index 417a9ff3eb9b0f53efaf880a25fe50c08648aea0..b2b4d5bf2e30ecf3bab50d81c95c4d0c945fe157 100644 --- a/devices/protocols/Netatmo/pyproject.toml +++ b/devices/protocols/Netatmo/pyproject.toml @@ -17,3 +17,14 @@ include = ["xaal.netatmo"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Netatmo/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Netatmo" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/SensFloor/pyproject.toml b/devices/protocols/SensFloor/pyproject.toml index 24b2617c47c7c3b63eaa8071f70dcc2901c19a2c..039a7fd290a25d2ea77bafe6cd8a8a5543b7749a 100644 --- a/devices/protocols/SensFloor/pyproject.toml +++ b/devices/protocols/SensFloor/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.sensfloor"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/SensFloor/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/SensFloor" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/SensFloor/xaal/sensfloor/gw.py b/devices/protocols/SensFloor/xaal/sensfloor/gw.py index 1f4802804e9876134c5d978d91f08f2ff46da854..8e6af3c20c843ed9ddbdf458691e6ab0a5fe8e76 100644 --- a/devices/protocols/SensFloor/xaal/sensfloor/gw.py +++ b/devices/protocols/SensFloor/xaal/sensfloor/gw.py @@ -1,8 +1,7 @@ -from xaal.lib import tools,Device,AsyncEngine +from xaal.lib import tools from xaal.schemas import devices -import asyncio import socketio import atexit import logging @@ -16,39 +15,38 @@ logger = logging.getLogger(PACKAGE_NAME) class GW(object): - def __init__(self,engine): + def __init__(self, engine): self.engine = engine self.devices = {} atexit.register(self._exit) self.config() self.setup() - + def config(self): cfg = tools.load_cfg(PACKAGE_NAME) if not cfg: - cfg= tools.new_cfg(PACKAGE_NAME) - cfg['config']['url']='http://floor1.enstb.org:8000/' + cfg = tools.new_cfg(PACKAGE_NAME) + cfg['config']['url'] = 'http://floor1.enstb.org:8000/' cfg['devices'] = {} - logger.warn("Created an empty config file") + logger.warning("Created an empty config file") cfg.write() self.cfg = cfg def setup(self): # socketio config - self.sio = socketio.AsyncClient(engineio_logger=True,ssl_verify=False) - self.sio.on('alarms-detected',self.on_alarm) - # xaal gateway + self.sio = socketio.AsyncClient(engineio_logger=True, ssl_verify=False) + self.sio.on('alarms-detected', self.on_alarm) + # xaal gateway addr = tools.get_uuid(self.cfg['config']['addr']) self.gw = devices.gateway(addr) self.engine.add_device(self.gw) for k in self.cfg['devices']: cfg = self.cfg['devices'][k] - dev = self.add_device(k,cfg['type'],tools.get_uuid(cfg['addr'])) + dev = self.add_device(k, cfg['type'], tools.get_uuid(cfg['addr'])) self.engine.add_device(dev) - - def add_device(self,idx,al_type,addr=None): + def add_device(self, idx, al_type, addr=None): if not addr: addr = tools.get_random_uuid() dev = None @@ -56,44 +54,42 @@ class GW(object): dev = devices.falldetector(addr) if al_type == 'presence': dev = devices.motion(addr) - if dev==None: - return + if dev is None: + return logger.debug(f"New device {addr} {idx}") dev.vendor_id = 'Future Shape' - dev.product_id='SensFloor' - dev.info='zone index: %s' % idx + dev.product_id = 'SensFloor' + dev.info = 'zone index: %s' % idx - self.devices.update({idx:dev}) + self.devices.update({idx: dev}) self.engine.add_device(dev) return dev - - def get_device(self,idx,al_type): - dev = self.devices.get(idx,None) + def get_device(self, idx, al_type): + dev = self.devices.get(idx, None) return dev - - def on_alarm(self,data): + def on_alarm(self, data): active = [] for k in data: - #print(k) + # print(k) idx = str(k['index']) al_type = k['type'] - state = k['state'] - dev = self.get_device(idx,al_type) - if dev == None: - dev = self.add_device(idx,al_type) - self.cfg['devices'][str(idx)] = {'addr':dev.address,'type':al_type} - - if dev.dev_type=='motion.basic': + # state = k['state'] + dev = self.get_device(idx, al_type) + if dev is None: + dev = self.add_device(idx, al_type) + self.cfg['devices'][str(idx)] = {'addr': dev.address, 'type': al_type} + + if dev.dev_type == 'motion.basic': dev.attributes['presence'] = True active.append(dev) - #print(active) + # print(active) for dev in self.devices.values(): - if dev in active:continue - if dev.dev_type=='motion.basic': - dev.attributes['presence'] = False - + if dev in active: + continue + if dev.dev_type == 'motion.basic': + dev.attributes['presence'] = False def _exit(self): cfg = tools.load_cfg(PACKAGE_NAME) @@ -101,21 +97,24 @@ class GW(object): logger.info('Saving configuration file') self.cfg.write() -# async def xaal_task(self): -# await self.engine.run() + # async def xaal_task(self): + # await self.engine.run() async def sio_task(self): url = self.cfg['config']['url'] - await self.sio.connect(url)#transports='polling') + await self.sio.connect(url) # transports='polling') await self.sio.wait() def stop(): - import pdb;pdb.set_trace() + import pdb + + pdb.set_trace() def setup(eng): gw = GW(eng) eng.on_stop(stop) eng.new_task(gw.sio_task()) - return True \ No newline at end of file + return True + diff --git a/devices/protocols/Tuya/pyproject.toml b/devices/protocols/Tuya/pyproject.toml index 025af4f6f99c0ea2a1d74bf1bd724948693bebb9..3d450d68aad13e7fcd0468230123913276a4441b 100644 --- a/devices/protocols/Tuya/pyproject.toml +++ b/devices/protocols/Tuya/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.tuya"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Tuya/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Tuya" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/Yeelight/pyproject.toml b/devices/protocols/Yeelight/pyproject.toml index ca6818099772a37f37314a239765625313f66ac7..e45c0bbc98eaed130d402aa4df3c588e1dd178ec 100644 --- a/devices/protocols/Yeelight/pyproject.toml +++ b/devices/protocols/Yeelight/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.yeelight"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/Yeelight/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/Yeelight" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/ZWave/pyproject.toml b/devices/protocols/ZWave/pyproject.toml index 60522c3ed6cca15b4d36e83720a7126f99872c3c..4dd1c99fb5df943b4b7b67a515c030cb353b4a4c 100644 --- a/devices/protocols/ZWave/pyproject.toml +++ b/devices/protocols/ZWave/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.zwave"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/ZWave/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/ZWave" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/protocols/bugOne/pyproject.toml b/devices/protocols/bugOne/pyproject.toml index 4f1b17eb039da7f4f7a4fee994fbfce3570838bd..5883bcf378cf491e4ae13891932dc6b3ac1ae9f3 100644 --- a/devices/protocols/bugOne/pyproject.toml +++ b/devices/protocols/bugOne/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.bugone"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/protocols/bugOne/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/protocols/bugOne" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/sensors/HTU21D/pyproject.toml b/devices/sensors/HTU21D/pyproject.toml index 077a1adc9bac96696fe27eea42c513d268c9c619..a0f7daba3f0bb439c4b611e05046381a576bdce9 100644 --- a/devices/sensors/HTU21D/pyproject.toml +++ b/devices/sensors/HTU21D/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.htu21d"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/sensors/HTU21D/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/sensors/HTU21D" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/sensors/lm_sensors/pyproject.toml b/devices/sensors/lm_sensors/pyproject.toml index 7833328adbbb52db28f0fd2c1ecaa6b4e6d9f0ee..d311a322f19a79467c2a652478dfa40bb50816d3 100644 --- a/devices/sensors/lm_sensors/pyproject.toml +++ b/devices/sensors/lm_sensors/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.lmsensors"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/sensors/lm_sensors/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/sensors/lm_sensors" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/tests/dummy/pyproject.toml b/devices/tests/dummy/pyproject.toml index 43d57f5262fc07c97629b0aee84f9e692dbbca82..ca05cb7943a001cb3f6cf83d7c37277bfce1b48c 100644 --- a/devices/tests/dummy/pyproject.toml +++ b/devices/tests/dummy/pyproject.toml @@ -18,3 +18,14 @@ include = ["xaal.dummy"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/tests/dummy/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/tests/dummy" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/tests/dummy/xaal/dummy/asyncbot.py b/devices/tests/dummy/xaal/dummy/asyncbot.py index 2403e805aa9751fbc6db131c5122a44832b13756..4d1e06b1361d7b20e7804cd54a42897e17e94b19 100644 --- a/devices/tests/dummy/xaal/dummy/asyncbot.py +++ b/devices/tests/dummy/xaal/dummy/asyncbot.py @@ -1,8 +1,10 @@ -from xaal.lib import AsyncEngine,Device,tools -from xaal.schemas import devices +import asyncio import functools +import random +import sys -import random,sys,asyncio +from xaal.lib import AsyncEngine, tools +from xaal.schemas import devices def usage(): @@ -28,22 +30,23 @@ def main(): async def run(): while 1: print(' => turn_on') - eng.send_request(dev,[target,],'turn_on') + eng.send_request(dev, [target,], 'turn_on') await asyncio.sleep(5) print(' => turn_off') - eng.send_request(dev,[target,],'turn_off') + eng.send_request(dev, [target,], 'turn_off') await asyncio.sleep(5) - brightness = random.randint(0,100) + brightness = random.randint(0, 100) print(' => set_brightness %i' % brightness) - eng.send_request(dev,[target,],'set_brightness',{'brightness': brightness}) + eng.send_request(dev , [target,], 'set_brightness', {'brightness': brightness}) await asyncio.sleep(5) eng.on_start(run) - eng.on_stop(functools.partial(print,"Bye Bye")) + eng.on_stop(functools.partial(print, "Bye Bye")) eng.run() + if __name__ == '__main__': try: main() diff --git a/devices/tests/dummy/xaal/dummy/lamp.py b/devices/tests/dummy/xaal/dummy/lamp.py index c5ee9c64f8b2748dfdba4e63cb8e43ce48ae1d34..0a1d8c4fbadad042dbbb8aea3f342a9ab6c93163 100644 --- a/devices/tests/dummy/xaal/dummy/lamp.py +++ b/devices/tests/dummy/xaal/dummy/lamp.py @@ -3,7 +3,7 @@ from xaal.lib import Device,Engine,tools import sys def main(addr): - if addr == None: + if addr is None: addr = tools.get_random_uuid() dev = Device("lamp.dimmer",addr) dev.product_id = 'Dummy Dimming Lamp' diff --git a/devices/tests/dummy/xaal/dummy/lamp_minimal.py b/devices/tests/dummy/xaal/dummy/lamp_minimal.py index 065824b1e2ad6dc998de88a7e1dd9067515a44c5..9e39ed20d065fbe865254f35b8751865068f893b 100644 --- a/devices/tests/dummy/xaal/dummy/lamp_minimal.py +++ b/devices/tests/dummy/xaal/dummy/lamp_minimal.py @@ -1,4 +1,4 @@ -from xaal.schemas import devices +from xaal.schemas import devices from xaal.lib import helpers, tools import sys @@ -36,5 +36,5 @@ def main(engine): return True -if __name__ =='__main__': +if __name__ == '__main__': helpers.run_async_package('lamp_minimal', main) diff --git a/devices/tests/fakeinput/pyproject.toml b/devices/tests/fakeinput/pyproject.toml index dfec889362d748e34b325f3896f9732ada249aec..ac3b7ed13a1a2758c3f369e561edfc41bdad0e3c 100644 --- a/devices/tests/fakeinput/pyproject.toml +++ b/devices/tests/fakeinput/pyproject.toml @@ -25,3 +25,14 @@ include = ["xaal.fakeinput"] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/devices/tests/fakeinput/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/devices/tests/fakeinput" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/weather/OpenWeatherMap/pyproject.toml b/devices/weather/OpenWeatherMap/pyproject.toml index 20e459ae31872a62ec144205f0b4c126b99161a6..73f7f5a76e1ac13e81bca0c87a74eeeb8b804056 100644 --- a/devices/weather/OpenWeatherMap/pyproject.toml +++ b/devices/weather/OpenWeatherMap/pyproject.toml @@ -17,3 +17,14 @@ dependencies = ["xaal.lib", "xaal.schemas", "pyowm==2.10.0"] [tool.setuptools.packages.find] include = ["xaal.owm"] + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/devices/weather/OpenWeatherMap/xaal/owm/gw.py b/devices/weather/OpenWeatherMap/xaal/owm/gw.py index 9434e88a40cd049fdc99c0cb695b04ffa2e78949..cb663804e37ca7de2e33deda59f81c9b025257a3 100644 --- a/devices/weather/OpenWeatherMap/xaal/owm/gw.py +++ b/devices/weather/OpenWeatherMap/xaal/owm/gw.py @@ -1,73 +1,78 @@ -import platform import logging +import platform import pyowm from pyowm.exceptions import OWMError -from xaal.lib import tools -from xaal.schemas import devices -from xaal.lib import helpers +from xaal.lib import helpers, tools +from xaal.schemas import devices PACKAGE_NAME = "xaal.owm" -RATE = 300 # update every 5 min +RATE = 300 # update every 5 min API_KEY = '3a5989bac31472cd41d69e92838bd454' logger = logging.getLogger(PACKAGE_NAME) + def setup_dev(dev): - dev.vendor_id = "IHSEV" + dev.vendor_id = "IHSEV" dev.product_id = "OpenWeatherMap" - dev.info = "%s@%s" % (PACKAGE_NAME,platform.node()) - dev.url = "https://www.openweathermap.org" - dev.version = 0.3 + dev.info = "%s@%s" % (PACKAGE_NAME, platform.node()) + dev.url = "https://www.openweathermap.org" + dev.version = 0.3 return dev + class GW: - def __init__(self,engine): + def __init__(self, engine): self.eng = engine engine.on_stop(self.save_config) cfg = tools.load_cfg(PACKAGE_NAME) - if cfg == None: + if cfg is None: logger.info('New config file') cfg = tools.new_cfg(PACKAGE_NAME) cfg['config']['base_addr'] = str(tools.get_random_base_uuid()) self.cfg = cfg - self.setup() self.update() def setup(self): - """ create devices, register ..""" + """create devices, register ..""" cfg = self.cfg['config'] base_addr = tools.get_uuid(cfg['base_addr']) + if base_addr is None: + logger.error('Invalid base_addr') + return # devices - d1 = devices.thermometer(base_addr +0) - d2 = devices.hygrometer(base_addr +1) - d3 = devices.barometer(base_addr +2) - d4 = devices.windgauge(base_addr +3) + d1 = devices.thermometer(base_addr + 0) + d2 = devices.hygrometer(base_addr + 1) + d3 = devices.barometer(base_addr + 2) + d4 = devices.windgauge(base_addr + 3) d4.unsupported_attributes.append('gust_angle') - d4.del_attribute(d4.get_attribute('gust_angle')) - self.devs = [d1,d2,d3,d4] + gust = d4.get_attribute('gust_angle') + if gust: + d4.del_attribute(gust) + self.devs = [d1, d2, d3, d4] # gw gw = devices.gateway(tools.get_uuid(cfg['addr'])) gw.attributes['embedded'] = [dev.address for dev in self.devs] - group = base_addr + 0xff - for dev in (self.devs + [gw,]): + group = base_addr + 0xFF + for dev in self.devs + [gw,]: setup_dev(dev) if dev != gw: dev.group_id = group self.eng.add_devices(self.devs + [gw,]) # OWM stuff - self.eng.add_timer(self.update,RATE) + self.eng.add_timer(self.update, RATE) # API Key - api_key = cfg.get('api_key',None) + api_key = cfg.get('api_key', None) if not api_key: cfg['api_key'] = api_key = API_KEY - # Place - self.place = cfg.get('place',None) + # Place + self.place = cfg.get('place', None) if not self.place: cfg['place'] = self.place = 'Brest,FR' # We are ready @@ -78,18 +83,19 @@ class GW: try: self._update() except OWMError as e: - logger.warn(e) + logger.warning(e) def _update(self): weather = self.owm.weather_at_place(self.place).get_weather() - self.devs[0].attributes['temperature'] = round(weather.get_temperature(unit='celsius').get('temp',None),1) - self.devs[1].attributes['humidity'] = weather.get_humidity() - self.devs[2].attributes['pressure'] = weather.get_pressure().get('press',None) - wind = weather.get_wind().get('speed',None) - if wind: wind = round(wind * 3600 / 1000, 1) # m/s => km/h + self.devs[0].attributes['temperature'] = round(weather.get_temperature(unit='celsius').get('temp', None), 1) + self.devs[1].attributes['humidity'] = weather.get_humidity() + self.devs[2].attributes['pressure'] = weather.get_pressure().get('press', None) + wind = weather.get_wind().get('speed', None) + if wind: + wind = round(wind * 3600 / 1000, 1) # m/s => km/h self.devs[3].attributes['wind_strength'] = wind - self.devs[3].attributes['wind_angle'] = weather.get_wind().get('deg',None) - self.devs[3].attributes['gust_strength'] = weather.get_wind().get('gust',None) + self.devs[3].attributes['wind_angle'] = weather.get_wind().get('deg', None) + self.devs[3].attributes['gust_strength'] = weather.get_wind().get('gust', None) def save_config(self): cfg = tools.load_cfg(PACKAGE_NAME) @@ -97,6 +103,7 @@ class GW: logger.info('Saving configuration file') self.cfg.write() + def setup(engine): gw = GW(engine) return True diff --git a/libs/lib/examples/func_args.py b/libs/lib/examples/func_args.py index 8c42f58a2db59d4dbedbc1bea441399be14e48f7..764b4d35439fc08175f9663b238fcd3aaec4b85c 100644 --- a/libs/lib/examples/func_args.py +++ b/libs/lib/examples/func_args.py @@ -1,7 +1,6 @@ from xaal.lib import core,helpers -from xaal.lib import core -from xaal.aiolib.core import spawn +from xaal.lib import aiohelpers import asyncio import time @@ -16,7 +15,7 @@ def foo(arg0,arg1): async def goo(arg0,arg1): print(f"{arg0} {arg1}") -@spawn +@aiohelpers.spawn def bar(arg0,arg1): print(f"{arg0} {arg1}") logger.warning('sleep') diff --git a/libs/lib/pyproject.toml b/libs/lib/pyproject.toml index 60d6dde43d1ed80f3e6582563bfb2ee441659cd4..88cad2e506688b6df3c81545e5573f486573b92e 100644 --- a/libs/lib/pyproject.toml +++ b/libs/lib/pyproject.toml @@ -29,3 +29,14 @@ dependencies = [ Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/libs/lib/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/libs/lib" + + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true diff --git a/libs/lib/tests/Makefile b/libs/lib/tests/Makefile index 380804280f112136e1533b668456657151cbb9a9..c7df599044f1f9a1e2ad181161a0930ec4b13df7 100644 --- a/libs/lib/tests/Makefile +++ b/libs/lib/tests/Makefile @@ -1,5 +1,5 @@ -default:cover +default:test test: python -m unittest -v @@ -11,4 +11,4 @@ pytest: cover: cd ../xaal/lib/;coverage run --source=. -m unittest discover -v -b ../../tests/; coverage html -d /tmp/coverage - \ No newline at end of file + diff --git a/libs/lib/tests/test_engine.py b/libs/lib/tests/test_engine.py index cb24f0dd59fdbb910c80f53e1f3111061207e3e0..7f43fe28c51e6661a9ba44c61043acf9275278eb 100644 --- a/libs/lib/tests/test_engine.py +++ b/libs/lib/tests/test_engine.py @@ -59,10 +59,10 @@ class TestEngine(unittest.TestCase): target = Device("test.basic", tools.get_random_uuid()) def action_1(): - return "action_1" + return {"value":"action_1"} def action_2(_value=None): - return "action_%s" % _value + return {"value":"action_%s" % _value} def action_3(): raise Exception @@ -77,12 +77,12 @@ class TestEngine(unittest.TestCase): # simple test method msg.action = "action_1" result = engine.run_action(msg, target) - self.assertEqual(result, "action_1") + self.assertEqual(result, {"value":"action_1"}) # test with value msg.action = "action_2" msg.body = {"value": "2"} result = engine.run_action(msg, target) - self.assertEqual(result, "action_2") + self.assertEqual(result, {"value":"action_2"}) # Exception in method msg.action = "action_3" with self.assertRaises(engine.XAALError): diff --git a/libs/lib/tests/test_message.py b/libs/lib/tests/test_message.py index 693aed02427e41265553bb095e74d3d45dab9261..35e8b90fd3ca429065da2d8069789a600188a276 100644 --- a/libs/lib/tests/test_message.py +++ b/libs/lib/tests/test_message.py @@ -174,13 +174,15 @@ class TestMessageFactory(unittest.TestCase): mf = test_factory() # too young msg = test_message() - msg.timestamp[0] = msg.timestamp[0] + 60*5 + target = (msg.timestamp[0] + 60*5, msg.timestamp[1]) + msg.timestamp = target data = mf.encode_msg(msg) with self.assertRaises(MessageParserError): mf.decode_msg(data) # too old msg.timestamp = messages.build_timestamp() - msg.timestamp[0] = msg.timestamp[0] - 60*5 + target = (msg.timestamp[0] - 60*5, msg.timestamp[1]) + msg.timestamp = target data = mf.encode_msg(msg) with self.assertRaises(MessageParserError): mf.decode_msg(data) @@ -214,4 +216,4 @@ class TestMessageFactory(unittest.TestCase): mf.decode_msg(data) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/libs/lib/xaal/lib/__init__.py b/libs/lib/xaal/lib/__init__.py index d341539d838f24244d25248a6c2ab9bf9507cc13..7178c8fa9ca5c4e1455dc2f7653bf0adf829cfe2 100644 --- a/libs/lib/xaal/lib/__init__.py +++ b/libs/lib/xaal/lib/__init__.py @@ -3,8 +3,8 @@ # Load main class & modules. +from .config import config from . import tools -from . import config from . import bindings from . import aiohelpers as helpers diff --git a/libs/lib/xaal/lib/aioengine.py b/libs/lib/xaal/lib/aioengine.py index 62b880cdb11b451d7f20e6cc9ef9e32bff756daa..888359dfaa8a383b6b4eb0945acd47ef420396c7 100644 --- a/libs/lib/xaal/lib/aioengine.py +++ b/libs/lib/xaal/lib/aioengine.py @@ -1,45 +1,80 @@ import asyncio - -from . import core -from . import config -from . import tools -from .messages import MessageParserError -from .aionetwork import AsyncNetworkConnector -from .exceptions import XAALError, CallbackError - +import logging +import signal +import sys import time +import typing from enum import Enum +from pprint import pprint +from typing import Any, Optional +from uuid import UUID + import aioconsole -import signal -import sys from tabulate import tabulate -from pprint import pprint +from .config import config +from . import core +from .aionetwork import AsyncNetworkConnector +from .exceptions import CallbackError, XAALError +from .messages import MessageParserError + +if typing.TYPE_CHECKING: + from .devices import Device + from .messages import Message -import logging logger = logging.getLogger(__name__) -class AsyncEngine(core.EngineMixin): - __slots__ = ['__txFifo','_loop','_tasks','_hooks','_watchdog_task','_kill_counter','running_event','watchdog_event','started_event'] +##################################################### +# Hooks +##################################################### +class HookType(Enum): + start = 0 + stop = 1 + - def __init__(self, address=config.address, port=config.port, hops=config.hops, key=config.key): - core.EngineMixin.__init__(self,address,port,hops,key) +class Hook(object): + __slots__ = ['type', 'func', 'args', 'kwargs'] + + def __init__(self, type_:HookType, func: core.FuncT, *args, **kwargs): + # func has to be a callable, but it can be a coroutine or a function + self.type = type_ + self.func = func + self.args = args + self.kwargs = kwargs - self.__txFifo = asyncio.Queue() # tx msg fifo - self._loop = None # event loop - self._hooks = [] # hooks - self._tasks = [] # tasks - self._watchdog_task = None # watchdog task - self._kill_counter = 0 # watchdog counter - self.started_event = asyncio.Event() # engine started event - self.running_event = asyncio.Event() # engine running event - self.watchdog_event = asyncio.Event() # watchdog event +class AsyncEngine(core.EngineMixin): + __slots__ = [ + '__txFifo', + '_loop', + '_tasks', + '_hooks', + '_watchdog_task', + '_kill_counter', + 'running_event', + 'watchdog_event', + 'started_event', + ] + + def __init__( + self, address: str = config.address, port: int = config.port, hops: int = config.hops, key: bytes = config.key): + core.EngineMixin.__init__(self, address, port, hops, key) + + self.__txFifo = asyncio.Queue() # tx msg fifo + self._loop = None # event loop + self._hooks = [] # hooks + self._tasks = [] # tasks + self._watchdog_task = None # watchdog task + self._kill_counter = 0 # watchdog counter + + self.started_event = asyncio.Event() # engine started event + self.running_event = asyncio.Event() # engine running event + self.watchdog_event = asyncio.Event() # watchdog event signal.signal(signal.SIGTERM, self.sigkill_handler) signal.signal(signal.SIGINT, self.sigkill_handler) - + # message receive workflow self.subscribe(self.handle_request) # start network @@ -48,20 +83,20 @@ class AsyncEngine(core.EngineMixin): ##################################################### # Hooks ##################################################### - def on_start(self,func,*args,**kwargs): - hook = Hook(HookType.start,func,*args,**kwargs) + def on_start(self, func: core.FuncT, *args, **kwargs): + hook = Hook(HookType.start, func, *args, **kwargs) self._hooks.append(hook) - - def on_stop(self,func,*args,**kwargs): - hook = Hook(HookType.stop,func,*args,**kwargs) + + def on_stop(self, func: core.FuncT, *args, **kwargs): + hook = Hook(HookType.stop, func, *args, **kwargs) self._hooks.append(hook) - async def run_hooks(self,hook_type): - hooks = list(filter(lambda hook: hook.type==hook_type,self._hooks)) - if len(hooks)!=0: + async def run_hooks(self, hook_type: HookType): + hooks = list(filter(lambda hook: hook.type == hook_type, self._hooks)) + if len(hooks) != 0: logger.debug(f"Launching {hook_type} hooks") for h in hooks: - await run_func(h.func,*h.args,**h.kwargs) + await run_func(h.func, *h.args, **h.kwargs) ##################################################### # timers @@ -77,7 +112,7 @@ class AsyncEngine(core.EngineMixin): except CallbackError as e: logger.error(e.description) if t.counter != -1: - t.counter-= 1 + t.counter -= 1 if t.counter == 0: expire_list.append(t) t.deadline = now + t.period @@ -88,20 +123,20 @@ class AsyncEngine(core.EngineMixin): ##################################################### # msg send / receive ##################################################### - def queue_msg(self, msg): + def queue_msg(self, msg: bytes): """queue a message""" self.__txFifo.put_nowait(msg) - def send_msg(self, msg): + def send_msg(self, msg: bytes): """Send an encoded message to the bus, use queue_msg instead""" self.network.send(msg) - async def receive_msg(self): + async def receive_msg(self) -> Optional['Message']: """return new received message or None""" data = await self.network.get_data() if data: try: - msg = self.msg_factory.decode_msg(data,self.msg_filter) + msg = self.msg_factory.decode_msg(data, self.msg_filter) except MessageParserError as e: logger.warning(e) msg = None @@ -113,27 +148,31 @@ class AsyncEngine(core.EngineMixin): msg = await self.receive_msg() if msg: for func in self.subscribers: - await run_func(func,msg) + await run_func(func, msg) self.process_attributes_change() - def handle_request(self, msg): + def handle_request(self, msg: 'Message'): """Filter msg for devices according default xAAL API then process the request for each targets identied in the engine """ if not msg.is_request(): return + targets = core.filter_msg_for_devices(msg, self.devices) for target in targets: - if msg.action == 'is_alive': + if msg.is_request_isalive(): self.send_alive(target) else: self.new_task(self.handle_action_request(msg, target)) - async def handle_action_request(self, msg, target): + async def handle_action_request(self, msg: 'Message', target: 'Device'): + if msg.action is None: + return # should not happen, but pyright need this check + try: result = await run_action(msg, target) - if result is None: - self.send_reply(dev=target,targets=[msg.source],action=msg.action,body=result) + if result is not None and type(result) is dict: + self.send_reply(dev=target, targets=[msg.source], action=msg.action, body=result) except CallbackError as e: self.send_error(target, e.code, e.description) except XAALError as e: @@ -142,25 +181,25 @@ class AsyncEngine(core.EngineMixin): ##################################################### # Asyncio loop & Tasks ##################################################### - def get_loop(self): - if self._loop == None: - logger.debug('New event loop') + def get_loop(self) -> asyncio.AbstractEventLoop: + if self._loop is None: + logger.debug("New event loop") self._loop = asyncio.get_event_loop() return self._loop - def new_task(self, coro, name=None): + def new_task(self, coro: Any, name: Optional[str] = None) -> asyncio.Task: # we maintain a task list, to be able to stop/start the engine # on demand. needed by HASS - task = self.get_loop().create_task(coro,name=name) + task = self.get_loop().create_task(coro, name=name) self._tasks.append(task) task.add_done_callback(self.task_callback) return task - def task_callback(self, task): + def task_callback(self, task: asyncio.Task): # called when a task ended self._tasks.remove(task) - def all_tasks(self): + def all_tasks(self) -> typing.List[asyncio.Task]: return self._tasks async def boot_task(self): @@ -194,17 +233,17 @@ class AsyncEngine(core.EngineMixin): async def watchdog_task(self): await self.watchdog_event.wait() await self.stop() - logger.info('Exit') - + logger.info("Exit") + ##################################################### # start / stop / shutdown ##################################################### - def is_running(self): + def is_running(self) -> bool: return self.running_event.is_set() def start(self): if self.is_running(): - logger.warning('Engine already started') + logger.warning("Engine already started") return self.started_event.set() self.new_task(self.boot_task(), name='Boot') @@ -218,10 +257,10 @@ class AsyncEngine(core.EngineMixin): if self.process_alives in [t.func for t in self.timers]: return # process alives every 10 seconds - self.add_timer(self.process_alives,10) + self.add_timer(self.process_alives, 10) - async def stop(self): - logger.info('Stopping engine') + async def stop(self): # pyright: ignore + logger.info("Stopping engine") await self.run_hooks(HookType.stop) self.running_event.clear() self.started_event.clear() @@ -232,68 +271,67 @@ class AsyncEngine(core.EngineMixin): await asyncio.sleep(0.1) def sigkill_handler(self, signal, frame): - print("", end = "\r") #remove the uggly ^C + print("", end="\r") # remove the uggly ^C if not self.is_running(): - logger.warning('Engine already stopped') + logger.warning("Engine already stopped") self._kill_counter = 1 - self._kill_counter +=1 + self._kill_counter += 1 self.shutdown() if self._kill_counter > 1: - logger.warning('Force quit') + logger.warning("Force quit") sys.exit(-1) else: - logger.warning('Kill requested') + logger.warning("Kill requested") def shutdown(self): self.watchdog_event.set() - + def run(self): if not self.started_event.is_set(): self.start() if self._watchdog_task is None: # start the watchdog task - self._watchdog_task = self.new_task(self.watchdog_task(), name='Watchdog task') + self._watchdog_task = self.new_task(self.watchdog_task(), name="Watchdog task") self.get_loop().run_until_complete(self._watchdog_task) else: - logger.warning('Engine already running') + logger.warning("Engine already running") ##################################################### # Debugging tools ##################################################### def dump_timers(self): - headers = ['Func','Period','Counter','Deadline'] + headers = ['Func', 'Period', 'Counter', 'Deadline'] rows = [] now = time.time() for t in self.timers: - remain = round(t.deadline-now,1) - rows.append([str(t.func),t.period,t.counter,remain]) - print('= Timers') - print(tabulate(rows, headers=headers, tablefmt="fancy_grid")) - + remain = round(t.deadline - now, 1) + rows.append([str(t.func), t.period, t.counter, remain]) + print("= Timers") + print(tabulate(rows, headers=headers, tablefmt='fancy_grid')) def dump_tasks(self): - headers = ["Name","Coro","Loop ID"] + headers = ['Name', 'Coro', 'Loop ID'] rows = [] for t in self.all_tasks(): - rows.append([t.get_name(),str(t.get_coro()),id(t.get_loop())]) - print('= Tasks') - print(tabulate(rows, headers=headers, tablefmt="fancy_grid")) + rows.append([t.get_name(), str(t.get_coro()), id(t.get_loop())]) + print("= Tasks") + print(tabulate(rows, headers=headers, tablefmt='fancy_grid')) def dump_devices(self): - headers = ["addr","dev_type","info"] + headers = ['addr', 'dev_type', 'info'] rows = [] for d in self.devices: - rows.append([d.address,d.dev_type,d.info]) - print('= Devices') - print(tabulate(rows, headers=headers, tablefmt="fancy_grid")) + rows.append([d.address, d.dev_type, d.info]) + print("= Devices") + print(tabulate(rows, headers=headers, tablefmt='fancy_grid')) def dump_hooks(self): - headers = ["Type","Hook"] + headers = ['Type', 'Hook'] rows = [] for h in self._hooks: - rows.append([h.type,str(h.func)]) - print('= Hooks') - print(tabulate(rows, headers=headers, tablefmt="fancy_grid")) + rows.append([h.type, str(h.func)]) + print("= Hooks") + print(tabulate(rows, headers=headers, tablefmt='fancy_grid')) def dump(self): self.dump_devices() @@ -301,8 +339,8 @@ class AsyncEngine(core.EngineMixin): self.dump_timers() self.dump_hooks() - def get_device(self,uuid): - uuid = tools.get_uuid(uuid) + def get_device(self, uuid: UUID) -> Optional['Device']: + # TODO:Check if this method is usefull for dev in self.devices: if dev.address == uuid: return dev @@ -312,26 +350,26 @@ class AsyncEngine(core.EngineMixin): ##################################################### # Utilities functions ##################################################### -async def run_func(func,*args,**kwargs): - """run a function or a coroutine function """ +async def run_func(func, *args, **kwargs): + """run a function or a coroutine function""" if asyncio.iscoroutinefunction(func): - return await func(*args,**kwargs) + return await func(*args, **kwargs) else: - return func(*args,**kwargs) + return func(*args, **kwargs) -async def run_action(msg,device): - """ +async def run_action(msg: 'Message', device: 'Device'): + """ Extract an action & launch it Return: - action result - None if no result - Notes: + Notes: - If an exception raised, it's logged, and raise an XAALError. - Same API as legacy Engine, but accept coroutine functions """ - method,params = core.search_action(msg,device) + method, params = core.search_action(msg, device) result = None try: if asyncio.iscoroutinefunction(method): @@ -344,45 +382,31 @@ async def run_action(msg,device): return result -##################################################### -# Hooks -##################################################### -class HookType(Enum): - start = 0 - stop = 1 - -class Hook(object): - __slots__ = ['type','func','args','kwargs'] - def __init__(self, type_, func, *args, **kwargs): - self.type = type_ - self.func = func - self.args = args - self.kwargs = kwargs - - ##################################################### # Debugging console ##################################################### -async def console(locals=locals(), port=None): +async def console(locals=locals(), port: Optional[int] = None): """launch a console to enable remote engine inspection""" if port is None: # let's find a free port if not specified def find_free_port(): import socketserver - with socketserver.TCPServer(("localhost", 0), None) as s: + with socketserver.TCPServer(('localhost', 0), None) as s: # pyright: ignore pyright reject the None here return s.server_address[1] + port = find_free_port() - - logger.debug(f'starting debug console on port {port}') - sys.ps1 = '[xAAL] >>> ' - banner = '=' * 78 +"\nxAAL remote console\n" + '=' *78 - locals.update({'pprint':pprint}) + + logger.debug(f"starting debug console on port {port}") + sys.ps1 = "[xAAL] >>> " + banner = "=" * 78 + "\nxAAL remote console\n" + "=" * 78 + locals.update({'pprint': pprint}) def factory(streams): return aioconsole.AsynchronousConsole(locals=locals, streams=streams) + # start the console try: # debian with ipv6 disabled still state that localhost is ::1, which broke aioconsole - await aioconsole.start_interactive_server(host='127.0.0.1', port=port,factory=factory,banner=banner) + await aioconsole.start_interactive_server(host="127.0.0.1", port=port, factory=factory, banner=banner) # pyright: ignore except OSError: - logger.warning('Unable to run console') + logger.warning("Unable to run console") diff --git a/libs/lib/xaal/lib/aiohelpers.py b/libs/lib/xaal/lib/aiohelpers.py index 51c40841617b8961116fed8b4858a5b801570192..cd85e247d1af233c73a0842a686b594784b582aa 100644 --- a/libs/lib/xaal/lib/aiohelpers.py +++ b/libs/lib/xaal/lib/aiohelpers.py @@ -22,12 +22,12 @@ def run_async_package(pkg_name, pkg_setup, console_log=True, file_log=False): setup_console_logger() if file_log: setup_file_logger(pkg_name) - - from .aioengine import AsyncEngine + + from .aioengine import AsyncEngine eng = AsyncEngine() eng.start() logger = logging.getLogger(pkg_name) - logger.info('starting xaal package: %s'% pkg_name ) + logger.info("starting xaal package: %s"% pkg_name ) result = pkg_setup(eng) if result is not True: logger.critical("something goes wrong with package: %s" % pkg_name) diff --git a/libs/lib/xaal/lib/aionetwork.py b/libs/lib/xaal/lib/aionetwork.py index 26e1b7fafecd4c35644a6605bc6d5cec7a072ef3..1423f3a48e1dad49aea37efe15fb2c1e485b17c3 100644 --- a/libs/lib/xaal/lib/aionetwork.py +++ b/libs/lib/xaal/lib/aionetwork.py @@ -1,14 +1,13 @@ import asyncio -import struct -import socket - import logging +import socket +import struct logger = logging.getLogger(__name__) class AsyncNetworkConnector(object): - def __init__(self, addr, port, hops, bind_addr="0.0.0.0"): + def __init__(self, addr: str, port: int, hops: int, bind_addr="0.0.0.0"): self.addr = addr self.port = port self.hops = hops @@ -30,25 +29,23 @@ class AsyncNetworkConnector(object): try: # Linux + MacOS + BSD sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except: + except Exception: # Windows sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((self.bind_addr, self.port)) - mreq = struct.pack( - "=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr) - ) + mreq = struct.pack("=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr)) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 10) sock.setblocking(False) return sock - def send(self, data): + def send(self, data: bytes): self.protocol.datagram_send(data, self.addr, self.port) - def receive(self, data): + def receive(self, data: bytes): self._rx_queue.put_nowait(data) - async def get_data(self): + async def get_data(self) -> bytes: return await self._rx_queue.get() diff --git a/libs/lib/xaal/lib/bindings.py b/libs/lib/xaal/lib/bindings.py index 0dd2ad1ead7394b0b1319c707c2e7a1a580ba76f..42e6b8dda3d1aa4a497a23753f929a600705ea32 100644 --- a/libs/lib/xaal/lib/bindings.py +++ b/libs/lib/xaal/lib/bindings.py @@ -8,60 +8,60 @@ class UUID: self.__uuid = uuid.UUID(*args, **kwargs) @staticmethod - def random_base(digit=2): + def random_base(digit=2) -> 'UUID': """zeros the last digits of a random uuid, usefull w/ you want to forge some addresses two digit is great. """ if (digit > 0) and (digit < 13): tmp = str(uuid.uuid1()) - st = "%s%s" % (tmp[:-digit], "0" * digit) + st = "%s%s" % (tmp[:-digit], '0' * digit) return UUID(st) else: raise UUIDError @staticmethod - def random(): + def random() -> 'UUID': tmp = uuid.uuid1().int return UUID(int=tmp) - def __add__(self, value): + def __add__(self, value: int) -> 'UUID': tmp = self.__uuid.int + value return UUID(int=tmp) - def __sub__(self, value): + def __sub__(self, value: int) -> 'UUID': tmp = self.__uuid.int - value return UUID(int=tmp) - def __eq__(self, value): + def __eq__(self, value) -> bool: return self.__uuid == value - def __lt__(self, value): + def __lt__(self, value) -> bool: return self.__uuid.int < value - def __gt__(self, value): + def __gt__(self, value) -> bool: return self.__uuid.int > value - def __str__(self): + def __str__(self) -> str: return str(self.__uuid) - def __repr__(self): # pragma: no cover + def __repr__(self) -> str: # pragma: no cover return f"UUID('{self.__uuid}')" - def __hash__(self): + def __hash__(self) -> int: return self.__uuid.__hash__() - def get(self): + def get(self) -> uuid.UUID: return self.__uuid - def set(self, value): + def set(self, value: uuid.UUID): self.__uuid = value @property - def str(self): + def str(self) -> str: return str(self) @property - def bytes(self): + def bytes(self) -> bytes: return self.__uuid.bytes @@ -75,13 +75,13 @@ class URL: def __str__(self): return str(self.__url) - def __repr__(self): # pragma: no cover + def __repr__(self) -> str: # pragma: no cover return f"URL('{self.__url}')" - def set(self, value): + def set(self, value: str): self.__url = value - def get(self): + def get(self) -> str: return self.__url @property diff --git a/libs/lib/xaal/lib/cbor.py b/libs/lib/xaal/lib/cbor.py index cf2d622d676087337d8e68a3189b9a176d3960b0..991c3cbac93f9644cdb77cf5fa0cacab039ddf86 100644 --- a/libs/lib/xaal/lib/cbor.py +++ b/libs/lib/xaal/lib/cbor.py @@ -16,25 +16,30 @@ def tag_hook(decoder, tag, shareable_index=None): return bindings.URL(tag.value) return tag + def default_encoder(encoder, value): - if isinstance(value,bindings.UUID): + if isinstance(value, bindings.UUID): encoder.encode(CBORTag(37, value.bytes)) - if isinstance(value,bindings.URL): + if isinstance(value, bindings.URL): encoder.encode(CBORTag(32, value.bytes)) + def dumps(obj, **kwargs): - return cbor2.dumps(obj,default=default_encoder,**kwargs) + return cbor2.dumps(obj, default=default_encoder, **kwargs) + def loads(payload, **kwargs): - #return cbor2.loads(payload,tag_hook=tag_hook,**kwargs) - return _loads(payload,tag_hook=tag_hook,**kwargs) + # return cbor2.loads(payload,tag_hook=tag_hook,**kwargs) + return _loads(payload, tag_hook=tag_hook, **kwargs) + def _loads(s, **kwargs): with BytesIO(s) as fp: return CBORDecoder(fp, **kwargs).decode() -#class CustomDecoder(CBORDecoder):pass + +# class CustomDecoder(CBORDecoder):pass def cleanup(obj): @@ -45,14 +50,14 @@ def cleanup(obj): Warning: This operate in-place changes. Warning: This won't work for tags in dict keys. """ - if isinstance(obj,list): - for i in range(0,len(obj)): + if isinstance(obj, list): + for i in range(0, len(obj)): obj[i] = cleanup(obj[i]) return obj - if isinstance(obj,dict): + if isinstance(obj, dict): for k in obj.keys(): - obj.update({k:cleanup(obj[k])}) + obj.update({k: cleanup(obj[k])}) return obj if type(obj) in bindings.classes: diff --git a/libs/lib/xaal/lib/config.py b/libs/lib/xaal/lib/config.py index 9a4cf6f396746f49866314b71ae49806a7319276..4cf2af919358a777ae85c84360acb8d37a0120ab 100644 --- a/libs/lib/xaal/lib/config.py +++ b/libs/lib/xaal/lib/config.py @@ -1,53 +1,82 @@ # Default configuration + import os import sys import binascii from configobj import ConfigObj -self = sys.modules[__name__] - # Default settings -DEF_ADDR = '224.0.29.200' # mcast address -DEF_PORT = 1236 # mcast port -DEF_HOPS = 10 # mcast hop -DEF_ALIVE_TIMER = 100 # Time between two alive msg -DEF_CIPHER_WINDOW = 60 * 2 # Time Window in seconds to avoid replay attacks -DEF_QUEUE_SIZE = 10 # How many packet we can send in one loop -DEF_LOG_LEVEL = 'DEBUG' # should be INFO|DEBUG|None -DEF_LOG_PATH = '/var/log/xaal' # where log are - -# TBD : Move this stuff +DEF_ADDR = '224.0.29.200' +DEF_PORT = 1236 +DEF_HOPS = 10 +DEF_ALIVE_TIMER = 100 +DEF_CIPHER_WINDOW = 60 * 2 +DEF_QUEUE_SIZE = 10 +DEF_LOG_LEVEL = 'DEBUG' +DEF_LOG_PATH = '/var/log/xaal' + STACK_VERSION = 7 +class Config: + def __init__(self): + self.conf_dir = os.environ.get('XAAL_CONF_DIR', os.path.expanduser("~/.xaal")) + self.address = DEF_ADDR + self.port = DEF_PORT + self.hops = DEF_HOPS + self.alive_timer = DEF_ALIVE_TIMER + self.cipher_window = DEF_CIPHER_WINDOW + self.queue_size = DEF_QUEUE_SIZE + self.log_level = DEF_LOG_LEVEL + self.log_path = DEF_LOG_PATH + self.key = b'' + self.STACK_VERSION = STACK_VERSION + + def load(self, name='xaal.ini'): + filename = os.path.join(self.conf_dir, name) + if not os.path.isfile(filename): + raise FileNotFoundError(f"Unable to load xAAL config file [{filename}]") + + cfg = ConfigObj(filename) + self.address = self.safe_string(cfg.get('address'), DEF_ADDR) + self.port = self.safe_int(cfg.get('port'), DEF_PORT) + self.hops = self.safe_int(cfg.get('hops'), DEF_HOPS) + self.alive_timer = self.safe_int(cfg.get('alive_timer'), DEF_ALIVE_TIMER) + self.cipher_window = self.safe_int(cfg.get('cipher_window'), DEF_CIPHER_WINDOW) + self.queue_size = self.safe_int(cfg.get('queue_size'), DEF_QUEUE_SIZE) + self.log_level = self.safe_string(cfg.get('log_level'), DEF_LOG_LEVEL) + self.log_path = self.safe_string(cfg.get('log_path'), DEF_LOG_PATH) + key = cfg.get('key', None) + if key and type(key) is str: + self.key = binascii.unhexlify(key.encode('utf-8')) + else: + raise ValueError(f"Key not set in config file [{filename}]") + + ## Helper functions + # Pylint enforce to sanity check the input. In fact, ConfigObj can do the job without issue + # but Pytlint assume cfg.get can return None (even w/ default set), so it warm about wrong + # type in all config setting. By doing this I insure that the value is of the right type. + + @staticmethod + def safe_int(value, default): + try: + return int(value) + except (ValueError, TypeError): + return default + + @staticmethod + def safe_string(value, default): + if value is None: + return default + try: + return str(value) + except (ValueError, TypeError): + return default + -if 'XAAL_CONF_DIR' in os.environ: - self.conf_dir = os.environ['XAAL_CONF_DIR'] -else: - self.conf_dir = os.path.expanduser("~") + '/.xaal' - - -def load_config(name='xaal.ini'): - filename = os.path.join(self.conf_dir, name) - if not os.path.isfile(filename): - print("Unable to load xAAL config file [%s]" % filename) - sys.exit(-1) - - cfg = ConfigObj(filename) - self.address = cfg.get('address',DEF_ADDR) - self.port = int(cfg.get('port',DEF_PORT)) - self.hops = int(cfg.get('hops',DEF_HOPS)) - self.alive_timer = int(cfg.get('alive_timer',DEF_ALIVE_TIMER)) - self.cipher_window = int(cfg.get('ciper_window',DEF_CIPHER_WINDOW)) - self.queue_size = int(cfg.get('queue_size',DEF_QUEUE_SIZE)) - self.log_level = cfg.get('log_level',DEF_LOG_LEVEL) - self.log_path = cfg.get('log_path',DEF_LOG_PATH) - key = cfg.get('key',None) - - if key: - self.key = binascii.unhexlify(key.encode('utf-8')) - else: - print("Please set key in config file [%s]" % filename) - self.key = None - -load_config() +config = Config() +try: + config.load() +except Exception as e: + print(e) + sys.exit(-1) diff --git a/libs/lib/xaal/lib/core.py b/libs/lib/xaal/lib/core.py index ad867335fb0449ee9dd0aad0fd96969c32894406..bcfb49932b55920420a557122b1a15dd297f7002 100644 --- a/libs/lib/xaal/lib/core.py +++ b/libs/lib/xaal/lib/core.py @@ -18,26 +18,50 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # -from .messages import MessageType, MessageAction, MessageFactory, ALIVE_ADDR -from .exceptions import EngineError, XAALError - -import time import inspect - import logging +import time +import typing +from typing import Any, Awaitable, Optional, List, Callable, Union + +from .exceptions import EngineError, XAALError +from .messages import ALIVE_ADDR, MessageAction, MessageFactory, MessageType + +if typing.TYPE_CHECKING: + from .devices import Device, Attribute + from .messages import Message + +# Function type w/ no argument, and no return (Timer, Hooks) +FuncT = Union[Callable[[], None], Callable[[], Awaitable[None]]] + +# Function type w/ message as argument (subscribers), no return +SubFuncT = Union[Callable[['Message'], None], Callable[['Message'], Awaitable[None]]] + logger = logging.getLogger(__name__) -class EngineMixin(object): - __slots__ = ['devices','timers','subscribers','msg_filter','_attributesChange','network','msg_factory'] +##################################################### +# Timer class +##################################################### +class Timer(object): + def __init__(self, func: FuncT, period: int, counter: int): + # Timer function should a Callable[[],None], but it can be a coroutine too + self.func = func + self.period = period + self.counter = counter + self.deadline = time.time() + period - def __init__(self,address,port,hops,key): - self.devices = [] # list of devices / use (un)register_devices() - self.timers = [] # functions to call periodic - self.subscribers = [] # message receive workflow - self.msg_filter = None # message filter - self._attributesChange = [] # list of XAALAttributes instances +class EngineMixin(object): + __slots__ = ['devices', 'timers', 'subscribers', 'msg_filter', '_attributesChange', 'network', 'msg_factory'] + + def __init__(self, address: str, port: int, hops: int, key: bytes): + self.devices:list[Device] = [] # list of devices / use (un)register_devices() + self.timers: list[Timer] = [] # functions to call periodic + self.subscribers: list[SubFuncT] = [] # message receive workflow + self.msg_filter = None # message filter + + self._attributesChange = [] # list of XAALAttributes instances # network connector self.network = None @@ -49,21 +73,21 @@ class EngineMixin(object): ##################################################### # Devices management ##################################################### - def add_device(self, dev): - """register a new device """ + def add_device(self, dev: 'Device'): + """register a new device""" if dev not in self.devices: self.devices.append(dev) dev.engine = self if self.is_running(): self.send_alive(dev) - def add_devices(self, devs): + def add_devices(self, devs: List['Device']): """register new devices""" for dev in devs: self.add_device(dev) - def remove_device(self, dev): - """unregister a device """ + def remove_device(self, dev: 'Device'): + """unregister a device""" dev.engine = None # Remove dev from devices list self.devices.remove(dev) @@ -72,54 +96,53 @@ class EngineMixin(object): # xAAL messages Tx handling ##################################################### # Fifo for msg to send - def queue_msg(self, msg): + def queue_msg(self, msg: bytes): logger.critical("To be implemented queue_msg: %s", msg) - def send_request(self, dev, targets, action, body=None): + def send_request(self, dev: 'Device', targets: list, action: str, body: Optional[dict] = None): """queue a new request""" msg = self.msg_factory.build_msg(dev, targets, MessageType.REQUEST, action, body) self.queue_msg(msg) - def send_reply(self, dev, targets, action, body=None): + def send_reply(self, dev: 'Device', targets: list, action: str, body: Optional[dict] = None): """queue a new reply""" msg = self.msg_factory.build_msg(dev, targets, MessageType.REPLY, action, body) self.queue_msg(msg) - def send_error(self, dev, errcode, description=None): + def send_error(self, dev: 'Device', errcode: int, description: Optional[str] = None): """queue a error message""" msg = self.msg_factory.build_error_msg(dev, errcode, description) self.queue_msg(msg) - def send_get_description(self, dev, targets): + def send_get_description(self, dev: 'Device', targets: list): """queue a get_description request""" self.send_request(dev, targets, MessageAction.GET_DESCRIPTION.value) - def send_get_attributes(self, dev, targets): + def send_get_attributes(self, dev: 'Device', targets: list): """queue a get_attributes request""" self.send_request(dev, targets, MessageAction.GET_ATTRIBUTES.value) - def send_notification(self, dev, action, body=None): + def send_notification(self, dev: 'Device', action: str, body: Optional[dict] = None): """queue a notificaton""" - msg = self.msg_factory.build_msg(dev, [], MessageType.NOTIFY, action,body) + msg = self.msg_factory.build_msg(dev, [], MessageType.NOTIFY, action, body) self.queue_msg(msg) - def send_alive(self, dev): + def send_alive(self, dev: 'Device'): """Send a Alive message for a given device""" timeout = dev.get_timeout() msg = self.msg_factory.build_alive_for(dev, timeout) self.queue_msg(msg) dev.update_alive() - def send_is_alive(self, dev, targets=[ALIVE_ADDR,], dev_types=["any.any",]): + def send_is_alive(self, dev: 'Device', targets: list = [ALIVE_ADDR,], dev_types: list = ["any.any", ] ): """Send a is_alive message, w/ dev_types filtering""" body = {'dev_types': dev_types} - self.send_request(dev,targets, MessageAction.IS_ALIVE.value, body) - + self.send_request(dev, targets, MessageAction.IS_ALIVE.value, body) ##################################################### # Messages filtering ##################################################### - def enable_msg_filter(self, func=None): + def enable_msg_filter(self, func: Optional[Callable[['Message'], bool]] = None): """enable message filter""" self.msg_filter = func or self.default_msg_filter @@ -127,7 +150,7 @@ class EngineMixin(object): """disable message filter""" self.msg_filter = None - def default_msg_filter(self, msg): + def default_msg_filter(self, msg: 'Message'): """ Filter messages: - check if message has alive request address @@ -150,17 +173,17 @@ class EngineMixin(object): """Periodic sending alive messages""" now = time.time() for dev in self.devices: - if dev.next_alive < now : + if dev.next_alive < now: self.send_alive(dev) ##################################################### # xAAL attributes changes ##################################################### - def add_attributes_change(self, attr): + def add_attributes_change(self, attr: 'Attribute'): """add a new attribute change to the list""" self._attributesChange.append(attr) - def get_attributes_change(self): + def get_attributes_change(self) -> List['Attribute']: """return the pending attributes changes list""" return self._attributesChange @@ -180,17 +203,18 @@ class EngineMixin(object): ##################################################### # xAAL messages subscribers ##################################################### - def subscribe(self,func): + def subscribe(self, func: SubFuncT): + # func should be a Callable[[Message],None], but it can be a coroutine too self.subscribers.append(func) - def unsubscribe(self,func): + def unsubscribe(self, func: SubFuncT): self.subscribers.remove(func) ##################################################### # timers ##################################################### - def add_timer(self, func, period,counter=-1): - """ + def add_timer(self, func: FuncT, period: int, counter: int = -1): + """ func: function to call period: period in second counter: number of repeat, -1 => always @@ -201,7 +225,7 @@ class EngineMixin(object): self.timers.append(t) return t - def remove_timer(self, timer): + def remove_timer(self, timer: Timer): """remove a given timer from the list""" self.timers.remove(timer) @@ -220,24 +244,15 @@ class EngineMixin(object): def run(self): logger.critical("To be implemented run") - def is_running(self): + def is_running(self) -> bool: logger.critical("To be implemented is_running") return False -##################################################### -# Timer class -##################################################### -class Timer(object): - def __init__(self, func, period, counter): - self.func = func - self.period = period - self.counter = counter - self.deadline = time.time() + period ##################################################### # Usefull functions to Engine developpers ##################################################### -def filter_msg_for_devices(msg, devices): +def filter_msg_for_devices(msg: 'Message', devices: List['Device']) -> List['Device']: """ loop throught the devices, to find which are expected w/ the msg - Filter on dev_types for is_alive broadcast request. @@ -246,28 +261,29 @@ def filter_msg_for_devices(msg, devices): results = [] if msg.is_request_isalive() and (ALIVE_ADDR in msg.targets): # if we receive a broadcast is_alive request, we reply - # with filtering on dev_tyes. + # with filtering on dev_tyes. if 'dev_types' in msg.body.keys(): dev_types = msg.body['dev_types'] - if 'any.any' in dev_types: + if "any.any" in dev_types: results = devices else: for dev in devices: - any_subtype = dev.dev_type.split('.')[0] + '.any' + any_subtype = dev.dev_type.split(".")[0] + ".any" if dev.dev_type in dev_types: results.append(dev) elif any_subtype in dev_types: results.append(dev) else: # this is a normal request, only filter on device address - # note: direct is_alive are treated like normal request + # note: direct is_alive are treated like normal request # so dev_types filtering is discarded for dev in devices: if dev.address in msg.targets: results.append(dev) return results -def search_action(msg, device): + +def search_action(msg: 'Message', device: 'Device'): """ Extract an action (match with methods) from a msg on the device. Return: @@ -280,6 +296,7 @@ def search_action(msg, device): params = {} result = None if msg.action in methods.keys(): + assert msg.action method = methods[msg.action] body_params = None if msg.body: @@ -287,23 +304,22 @@ def search_action(msg, device): body_params = msg.body for k in body_params: - temp = '_%s' %k + temp = "_%s" % k if temp in method_params: - params.update({temp:body_params[k]}) + params.update({temp: body_params[k]}) else: - logger.warning("Wrong method parameter [%s] for action %s" %(k, msg.action)) - result = (method,params) + logger.warning("Wrong method parameter [%s] for action %s" % (k, msg.action)) + result = (method, params) else: - raise XAALError("Method %s not found on device %s" % (msg.action,device)) + raise XAALError("Method %s not found on device %s" % (msg.action, device)) return result -def get_args_method(method): - """ return the list on arguments for a given python method """ + +def get_args_method(method: Any) -> List[str]: + """return the list on arguments for a given python method""" spec = inspect.getfullargspec(method) try: spec.args.remove('self') except Exception: pass return spec.args - - diff --git a/libs/lib/xaal/lib/devices.py b/libs/lib/xaal/lib/devices.py index d9c8ca3a764ce3e326d9d5207c9d53c2e127e11c..424f6d129e4ba5e7640d7ad5eed5cadeeb8d70ed 100644 --- a/libs/lib/xaal/lib/devices.py +++ b/libs/lib/xaal/lib/devices.py @@ -18,40 +18,48 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # +import logging import time +import typing +from typing import Any, Optional, Union, Callable, Awaitable + +from tabulate import tabulate -from . import config -from . import tools -from . import bindings +from . import bindings, config, tools from .exceptions import DeviceError -from tabulate import tabulate -import logging +if typing.TYPE_CHECKING: + from .aioengine import AsyncEngine + from .engine import Engine + from .core import EngineMixin + +# Funtion types with any arguments and return a dict or None (device methods) +MethodT = Union[Callable[..., Union[dict, None]], Callable[..., Awaitable[Union[dict, None]]]] + logger = logging.getLogger(__name__) class Attribute(object): - - def __init__(self, name, dev=None, default=None): + def __init__(self, name, dev: Optional['Device'] = None, default: Any = None): self.name = name self.default = default - self.device = dev + self.device: Optional['Device'] = dev self.__value = default @property - def value(self): + def value(self) -> Any: return self.__value @value.setter - def value(self, value): + def value(self, value: Any): if value != self.__value and self.device: eng = self.device.engine if eng: eng.add_attributes_change(self) - logger.debug("Attr change %s %s=%s" % (self.device.address,self.name,value)) + logger.debug("Attr change %s %s=%s" % (self.device.address, self.name, value)) self.__value = value - def __repr__(self): # pragma: no cover + def __repr__(self) -> str: # pragma: no cover return f"<{self.__module__}.Attribute {self.name} at 0x{id(self):x}>" @@ -59,85 +67,106 @@ class Attributes(list): """Devices owns a attributes list. This list also have dict-like access""" def __getitem__(self, value): - if isinstance(value,int): - return list.__getitem__(self,value) + if isinstance(value, int): + return list.__getitem__(self, value) for k in self: - if (value == k.name): + if value == k.name: return k.value raise KeyError(value) def __setitem__(self, name, value): - if isinstance(name,int): - return list.__setitem__(self,name,value) + if isinstance(name, int): + return list.__setitem__(self, name, value) for k in self: - if (name == k.name): + if name == k.name: k.value = value return raise KeyError(name) -class Device(object): - - __slots__ = ['__dev_type','__address','group_id', - 'vendor_id','product_id','hw_id', - '__version','__url','schema','info', - 'unsupported_attributes','unsupported_methods','unsupported_notifications', - 'alive_period','next_alive', - '__attributes','methods','engine'] - def __init__(self, dev_type, addr=None, engine=None): +class Device(object): + __slots__ = [ + '__dev_type', + '__address', + 'group_id', + 'vendor_id', + 'product_id', + 'hw_id', + '__version', + '__url', + 'schema', + 'info', + 'unsupported_attributes', + 'unsupported_methods', + 'unsupported_notifications', + 'alive_period', + 'next_alive', + '__attributes', + 'methods', + 'engine', + ] + + def __init__( + self, + dev_type: str, + addr: Optional[bindings.UUID] = None, + engine: Union['AsyncEngine', 'Engine', 'EngineMixin', None] = None, + ): # xAAL internal attributes for a device - self.dev_type = dev_type # xaal dev_type - self.address = addr # xaal addr - self.group_id = None # group devices - self.vendor_id = None # vendor ID ie : ACME - self.product_id = None # product ID - self.hw_id = None # hardware info - self.__version = None # product release - self.__url = None # product URL - self.schema = None # schema URL - self.info = None # additionnal info + self.dev_type = dev_type # xaal dev_type + self.address = addr # xaal addr + self.group_id: Optional[bindings.UUID] = None # group devices + self.vendor_id: Optional[str] = None # vendor ID ie : ACME + self.product_id: Optional[str] = None # product ID + self.hw_id: Optional[str] = None # hardware info + self.version = None # product release + self.url = None # product URL + self.schema: Optional[str] = None # schema URL + self.info: Optional[str] = None # additionnal info # Unsupported stuffs self.unsupported_attributes = [] self.unsupported_methods = [] self.unsupported_notifications = [] # Alive management - self.alive_period = config.alive_timer # time in sec between two alive + self.alive_period = config.alive_timer # time in sec between two alive self.next_alive = 0 # Default attributes & methods self.__attributes = Attributes() - self.methods = {'get_attributes' : self._get_attributes, - 'get_description': self._get_description } + self.methods: dict[str, MethodT] = { + 'get_attributes': self._get_attributes, + 'get_description': self._get_description, + } self.engine = engine @property - def dev_type(self): + def dev_type(self) -> str: return self.__dev_type @dev_type.setter - def dev_type(self, value): + def dev_type(self, value: str): if not tools.is_valid_dev_type(value): raise DeviceError(f"The dev_type {value} is not valid") self.__dev_type = value @property - def version(self): + def version(self) -> Optional[str]: return self.__version @version.setter - def version(self, value): - #Â version must be a string + def version(self, value: Any): + # version must be a string if value: self.__version = "%s" % value else: self.__version = None @property - def address(self): + def address(self) -> Optional[bindings.UUID]: return self.__address @address.setter - def address(self, value): + def address(self, value: Optional[bindings.UUID]): if value is None: self.__address = None return @@ -146,61 +175,65 @@ class Device(object): self.__address = value @property - def url(self): + def url(self) -> Optional[bindings.URL]: return self.__url @url.setter - def url(self, value): + def url(self, value: Optional[str]): if value is None: self.__url = None else: self.__url = bindings.URL(value) # attributes - def new_attribute(self, name, default=None): - attr = Attribute(name,self,default) + def new_attribute(self, name: str, default: Any = None): + attr = Attribute(name, self, default) self.add_attribute(attr) return attr - def add_attribute(self, attr): + def add_attribute(self, attr: Attribute): if attr: self.__attributes.append(attr) attr.device = self - def del_attribute(self, attr): + def del_attribute(self, attr: Attribute): if attr: attr.device = None self.__attributes.remove(attr) - def get_attribute(self, name): + def get_attribute(self, name: str) -> Optional[Attribute]: for attr in self.__attributes: if attr.name == name: return attr return None @property - def attributes(self): + def attributes(self) -> Attributes: return self.__attributes @attributes.setter - def attributes(self, values): - if isinstance(values,Attributes): + def attributes(self, values: Attributes): + if isinstance(values, Attributes): self.__attributes = values else: raise DeviceError("Invalid attributes list, use class Attributes)") - def add_method(self, name, func): - self.methods.update({name:func}) + def add_method(self, name: str, func: MethodT): + self.methods.update({name: func}) + + def del_method(self, name: str): + if name in self.methods: + del self.methods[name] - def get_methods(self): + def get_methods(self) -> dict[str, MethodT]: return self.methods def update_alive(self): - """ update the alive timimg""" + """update the alive timimg""" self.next_alive = time.time() + self.alive_period - def get_timeout(self): - """ return Alive timeout used for isAlive msg""" + def get_timeout(self) -> int: + """return Alive timeout used for isAlive msg""" return 2 * self.alive_period ##################################################### @@ -210,34 +243,33 @@ class Device(object): print("= Device: %s" % self) # info & description r = [] - r.append(['dev_type',self.dev_type]) - r.append(['address',self.address]) - for k,v in self._get_description().items(): - r.append([k,v]) - print(tabulate(r,tablefmt="fancy_grid")) + r.append(['dev_type', self.dev_type]) + r.append(['address', self.address]) + for k, v in self._get_description().items(): + r.append([k, v]) + print(tabulate(r, tablefmt='fancy_grid')) # attributes if len(self._get_attributes()) > 0: r = [] - for k,v in self._get_attributes().items(): - r.append([k,str(v)]) - print(tabulate(r,tablefmt="fancy_grid")) + for k, v in self._get_attributes().items(): + r.append([k, str(v)]) + print(tabulate(r, tablefmt='fancy_grid')) # methods if len(self.methods) > 0: r = [] - for k,v in self.methods.items(): - r.append([k,v.__name__]) - print(tabulate(r,tablefmt="fancy_grid")) - + for k, v in self.methods.items(): + r.append([k, v.__name__]) + print(tabulate(r, tablefmt='fancy_grid')) - def __repr__(self): + def __repr__(self) -> str: return f"<xaal.Device {id(self):x} {self.address} {self.dev_type}>" ##################################################### # default public methods ##################################################### - def _get_description(self): + def _get_description(self) -> dict: result = {} if self.vendor_id: result['vendor_id'] = self.vendor_id @@ -290,7 +322,7 @@ class Device(object): result.update({attr.name: attr.value}) return result - def send_notification(self, notification, body={}): - """ queue an notification, this is just a method helper """ + def send_notification(self, notification: str, body: dict = {}): + """queue an notification, this is just a method helper""" if self.engine: - self.engine.send_notification(self,notification,body) + self.engine.send_notification(self, notification, body) diff --git a/libs/lib/xaal/lib/engine.py b/libs/lib/xaal/lib/engine.py index 67875d4afacea6dc4ee9cfe3fa2f555761f3eb77..1a5fc608ecc862d7ece1ffc030091e11b53ae6cb 100644 --- a/libs/lib/xaal/lib/engine.py +++ b/libs/lib/xaal/lib/engine.py @@ -18,33 +18,44 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # -from . import core -from .network import NetworkConnector -from .exceptions import MessageParserError, CallbackError, XAALError -from . import config - -import time import collections +import logging +import time +import typing from enum import Enum +from typing import Optional + + + +from .config import config +from . import core +from .exceptions import CallbackError, MessageParserError, XAALError +from .network import NetworkConnector + +if typing.TYPE_CHECKING: + from .devices import Device + from .messages import Message + -import logging logger = logging.getLogger(__name__) -class EngineState(Enum): - started = 1 - running = 2 - halted = 3 -class Engine(core.EngineMixin): +class EngineState(Enum): + started = 1 + running = 2 + halted = 3 - __slots__ = ['__last_timer','__txFifo','state','network'] - def __init__(self,address=config.address,port=config.port,hops=config.hops,key=config.key): - core.EngineMixin.__init__(self,address,port,hops,key) +class Engine(core.EngineMixin): + __slots__ = ['__last_timer', '__txFifo', 'state', 'network'] - self.__last_timer = 0 # last timer check - self.__txFifo = collections.deque() # tx msg fifo + def __init__( + self, address: str = config.address, port: int = config.port, hops: int = config.hops, key: bytes = config.key + ): + core.EngineMixin.__init__(self, address, port, hops, key) + self.__last_timer = 0 # last timer check + self.__txFifo = collections.deque() # tx msg fifo # message receive workflow self.subscribe(self.handle_request) # ready to go @@ -56,16 +67,16 @@ class Engine(core.EngineMixin): # xAAL messages Tx handling ##################################################### # Fifo for msg to send - def queue_msg(self, msg): + def queue_msg(self, msg: bytes): """queue an encoded / cyphered message""" self.__txFifo.append(msg) - def send_msg(self, msg): + def send_msg(self, msg: bytes): """Send an encoded message to the bus, use queue_msg instead""" self.network.send(msg) def process_tx_msg(self): - """ Process (send) message in tx queue called from the loop()""" + """Process (send) message in tx queue called from the loop()""" cnt = 0 while self.__txFifo: temp = self.__txFifo.popleft() @@ -79,13 +90,13 @@ class Engine(core.EngineMixin): ##################################################### # xAAL messages subscribers ##################################################### - def receive_msg(self): + def receive_msg(self) -> Optional['Message']: """return new received message or None""" result = None data = self.network.get_data() if data: try: - msg = self.msg_factory.decode_msg(data,self.msg_filter) + msg = self.msg_factory.decode_msg(data, self.msg_filter) except MessageParserError as e: logger.warning(e) msg = None @@ -99,35 +110,37 @@ class Engine(core.EngineMixin): for func in self.subscribers: func(msg) self.process_attributes_change() - - def handle_request(self, msg): + + def handle_request(self, msg: 'Message'): """ Filter msg for devices according default xAAL API then process the request for each targets identied in the engine """ if not msg.is_request(): - return - + return + targets = core.filter_msg_for_devices(msg, self.devices) for target in targets: - if msg.action == 'is_alive': + if msg.is_request_isalive(): self.send_alive(target) else: self.handle_action_request(msg, target) - def handle_action_request(self, msg, target): + def handle_action_request(self, msg: 'Message', target: 'Device'): """ Run method (xAAL exposed method) on device: - - None is returned if device method do not return anything - result is returned if device method gives a response - Errors are raised if an error occured: * Internal error * error returned on the xAAL bus """ + if msg.action is None: + return # should not happen, but pyright need this check + try: result = run_action(msg, target) - if result is not None: - self.send_reply(dev=target,targets=[msg.source],action=msg.action,body=result) + if result: + self.send_reply(dev=target, targets=[msg.source], action=msg.action, body=result) except CallbackError as e: self.send_error(target, e.code, e.description) except XAALError as e: @@ -139,8 +152,8 @@ class Engine(core.EngineMixin): def process_timers(self): """Process all timers to find out which ones should be run""" expire_list = [] - - if len(self.timers)!=0 : + + if len(self.timers) != 0: now = time.time() # little hack to avoid to check timer to often. # w/ this enable timer precision is bad, but far enougth @@ -154,14 +167,14 @@ class Engine(core.EngineMixin): except CallbackError as e: logger.error(e.description) if t.counter != -1: - t.counter-= 1 + t.counter -= 1 if t.counter == 0: expire_list.append(t) t.deadline = now + t.period # delete expired timers for t in expire_list: self.remove_timer(t) - + self.__last_timer = now ##################################################### @@ -188,7 +201,7 @@ class Engine(core.EngineMixin): def start(self): """Start the core engine: send queue alive msg""" - if self.state in [EngineState.started,EngineState.running]: + if self.state in [EngineState.started, EngineState.running]: return self.network.connect() for dev in self.devices: @@ -207,13 +220,14 @@ class Engine(core.EngineMixin): while self.state == EngineState.running: self.loop() - def is_running(self): + def is_running(self) -> bool: if self.state == EngineState.running: return True return False -def run_action(msg,device): - """ + +def run_action(msg: 'Message', device: 'Device') -> Optional[dict]: + """ Extract an action & launch it Return: - action result @@ -221,12 +235,15 @@ def run_action(msg,device): Note: If an exception raised, it's logged, and raise an XAALError. """ - method,params = core.search_action(msg,device) + method, params = core.search_action(msg, device) result = None try: result = method(**params) except Exception as e: logger.error(e) - raise XAALError("Error in method:%s params:%s" % (msg.action,params)) + raise XAALError("Error in method:%s params:%s" % (msg.action, params)) + # Here result should be None or a dict, and we need to enforce that. This will cause issue + # in send_reply otherwise. + if result is not None and not isinstance(result, dict): + raise XAALError("Method %s should return a dict or None" % msg.action) return result - diff --git a/libs/lib/xaal/lib/exceptions.py b/libs/lib/xaal/lib/exceptions.py index ec8f0b4b1b5c2e4ea6b51ed63e094e4bffdc7b0a..e958eed0f4a128357832cffa0fd5053dcca0396c 100644 --- a/libs/lib/xaal/lib/exceptions.py +++ b/libs/lib/xaal/lib/exceptions.py @@ -26,11 +26,11 @@ class UUIDError(Exception): pass __all__ = [ - "DeviceError", - "EngineError", - "XAALError", - "CallbackError", - "MessageParserError", - "MessageError", - "UUIDError", + 'DeviceError', + 'EngineError', + 'XAALError', + 'CallbackError', + 'MessageParserError', + 'MessageError', + 'UUIDError', ] diff --git a/libs/lib/xaal/lib/helpers.py b/libs/lib/xaal/lib/helpers.py index d2b4849604cb3f93bc2c381c991363930f47c70a..9e0d57202cc950c649974e502c391e78b7fa402c 100644 --- a/libs/lib/xaal/lib/helpers.py +++ b/libs/lib/xaal/lib/helpers.py @@ -1,4 +1,4 @@ -""" +""" This file contains some helpers functions. This functions aren't used in the lib itself but can be usefull for xaal packages developpers """ @@ -7,18 +7,23 @@ import logging import logging.handlers import os import time +from typing import Any, Optional + import coloredlogs from decorator import decorator -from . import config +from .config import config + def singleton(class_): - instances = {} - def getinstance(*args, **kwargs): - if class_ not in instances: - instances[class_] = class_(*args, **kwargs) - return instances[class_] - return getinstance + instances = {} + + def getinstance(*args, **kwargs): + if class_ not in instances: + instances[class_] = class_(*args, **kwargs) + return instances[class_] + + return getinstance @decorator @@ -27,22 +32,25 @@ def timeit(method, *args, **kwargs): ts = time.time() result = method(*args, **kwargs) te = time.time() - logger.debug('%r (%r, %r) %2.6f sec' % (method.__name__, args, kwargs, te-ts)) + logger.debug("%r (%r, %r) %2.6f sec" % (method.__name__, args, kwargs, te - ts)) return result -def set_console_title(value): + +def set_console_title(value: str): # set xterm title - print("\x1B]0;xAAL => %s\x07" % value, end='\r') + print("\x1b]0;xAAL => %s\x07" % value, end="\r") -def setup_console_logger(level=config.log_level): - fmt = '%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s' - #fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s' - coloredlogs.install(level=level,fmt=fmt) -def setup_file_logger(name,level=config.log_level, filename = None): - filename = filename or os.path.join(config.log_path,'%s.log' % name) - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') - handler = logging.handlers.RotatingFileHandler(filename, 'a', 10000, 1, 'utf8') +def setup_console_logger(level: str = config.log_level): + fmt = "%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s" + # fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s' + coloredlogs.install(level=level, fmt=fmt) + + +def setup_file_logger(name: str, level: str = config.log_level, filename: Optional[str] = None): + filename = filename or os.path.join(config.log_path, "%s.log" % name) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s") + handler = logging.handlers.RotatingFileHandler(filename, "a", 10000, 1, "utf8") handler.setLevel(level) handler.setFormatter(formatter) # register the new handler @@ -50,22 +58,24 @@ def setup_file_logger(name,level=config.log_level, filename = None): logger.root.addHandler(handler) logger.root.setLevel('DEBUG') + # --------------------------------------------------------------------------- -# TBD: We should merge this stuffs, and add support for default config file -# and commnand line parsing. -# +# TBD: We should merge this stuffs, and add support for default config file +# and commnand line parsing. +# # Default arguments console_log and file_log are (and should) never be used. # --------------------------------------------------------------------------- -def run_package(pkg_name, pkg_setup, console_log = True, file_log=False): +def run_package(pkg_name: str, pkg_setup: Any, console_log: bool = True, file_log: bool = False): if console_log: set_console_title(pkg_name) setup_console_logger() if file_log: setup_file_logger(pkg_name) logger = logging.getLogger(pkg_name) - logger.info('starting xaal package: %s'% pkg_name ) + logger.info("starting xaal package: %s" % pkg_name) from .engine import Engine + eng = Engine() result = pkg_setup(eng) @@ -77,4 +87,5 @@ def run_package(pkg_name, pkg_setup, console_log = True, file_log=False): eng.shutdown() logger.info("exit") -__all__ = ['singleton','timeit','set_console_title','setup_console_logger','setup_file_logger','run_package'] + +__all__ = ['singleton', 'timeit', 'set_console_title', 'setup_console_logger', 'setup_file_logger', 'run_package'] diff --git a/libs/lib/xaal/lib/messages.py b/libs/lib/xaal/lib/messages.py index 343e0a0c15d6dc422eaa59dd55c7f9349847f44d..a706492740c4272b12d5b2f59110a4afb5336cff 100644 --- a/libs/lib/xaal/lib/messages.py +++ b/libs/lib/xaal/lib/messages.py @@ -18,26 +18,139 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # -from . import tools -from . import config +import datetime +import logging +import pprint +import struct +import typing +from enum import Enum +from typing import Any, Optional + +import pysodium +from tabulate import tabulate + +from . import cbor, tools from .bindings import UUID +from .config import config from .exceptions import MessageError, MessageParserError -from . import cbor -from enum import Enum -from tabulate import tabulate -import pprint -import datetime -import pysodium -import struct -import sys +if typing.TYPE_CHECKING: + from .devices import Device + -import logging logger = logging.getLogger(__name__) ALIVE_ADDR = UUID("00000000-0000-0000-0000-000000000000") +class MessageType(Enum): + NOTIFY = 0 + REQUEST = 1 + REPLY = 2 + + +class MessageAction(Enum): + ALIVE = 'alive' + IS_ALIVE = 'is_alive' + ATTRIBUTES_CHANGE = 'attributes_change' + GET_ATTRIBUTES = 'get_attributes' + GET_DESCRIPTION = 'get_description' + + +class Message(object): + """Message object used for incomming & outgoint message""" + + __slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets'] + + def __init__(self): + self.version = config.STACK_VERSION # message API version + self.__targets = [] # target property + self.timestamp: tuple = () # message timestamp + self.source: Optional[UUID] = None # message source + self.dev_type: Optional[str] = None # message dev_type + self.msg_type: Optional[MessageType] = None # message type + self.action: Optional[str] = None # message action + self.body = {} # message body + + @property + def targets(self) -> list: + return self.__targets + + @targets.setter + def targets(self, values: list): + if not isinstance(values, list): + raise MessageError("Expected a list for targetsList, got %s" % (type(values),)) + for uid in values: + if not tools.is_valid_address(uid): + raise MessageError("Bad target addr: %s" % uid) + self.__targets = values + + def targets_as_string(self) -> list: + return [str(k) for k in self.targets] + + def dump(self): + r = [] + r.append(['version', self.version]) + r.append(['targets', str(self.targets)]) + r.append(['timestamp', str(self.timestamp)]) + r.append(['source', self.source]) + r.append(['dev_type', self.dev_type]) + r.append(['msg_type', MessageType(self.msg_type)]) + r.append(['action', self.action]) + if self.body: + tmp = "" + for k, v in self.body.items(): + k = k + ":" + v = pprint.pformat(v, width=55) + tmp = tmp + "- %-12s %s\n" % (k, v) + # tmp = tmp.strip() + r.append(['body', tmp]) + print(tabulate(r, headers=['Field', 'Value'], tablefmt='psql')) + + def __repr__(self) -> str: + return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>" + + def is_request(self) -> bool: + if MessageType(self.msg_type) == MessageType.REQUEST: + return True + return False + + def is_reply(self) -> bool: + if MessageType(self.msg_type) == MessageType.REPLY: + return True + return False + + def is_notify(self) -> bool: + if MessageType(self.msg_type) == MessageType.NOTIFY: + return True + return False + + def is_alive(self) -> bool: + if self.is_notify() and self.action == MessageAction.ALIVE.value: + return True + return False + + def is_request_isalive(self) -> bool: + if self.is_request() and self.action == MessageAction.IS_ALIVE.value: + return True + return False + + def is_attributes_change(self) -> bool: + if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value: + return True + return False + + def is_get_attribute_reply(self) -> bool: + if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value: + return True + return False + + def is_get_description_reply(self) -> bool: + if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value: + return True + return False + + class MessageFactory(object): """Message Factory: - Build xAAL message @@ -45,9 +158,10 @@ class MessageFactory(object): - Serialize/Deserialize data in CBOR""" def __init__(self, cipher_key): - self.cipher_key = cipher_key # key encode / decode message built from passphrase + # key encode / decode message built from passphrase + self.cipher_key = cipher_key - def encode_msg(self, msg): + def encode_msg(self, msg: Message) -> bytes: """Apply security layer and return encode MSG in CBOR :param msg: xAAL msg instance :type msg: Message @@ -65,6 +179,10 @@ class MessageFactory(object): # Format payload & ciphering buf = [] + if not msg.source: + raise MessageError("No source address in message") + if not msg.msg_type: + raise MessageError("No msg_type in message") buf.append(msg.source.bytes) buf.append(msg.dev_type) buf.append(msg.msg_type.value) @@ -73,8 +191,8 @@ class MessageFactory(object): buf.append(msg.body) clear = cbor.dumps(buf) # Additionnal Data == cbor serialization of the targets array - ad = result[3] - nonce = build_nonce(msg.timestamp) + ad = result[3] + nonce = build_nonce(msg.timestamp) payload = pysodium.crypto_aead_chacha20poly1305_ietf_encrypt(clear, ad, nonce, self.cipher_key) # Final CBOR serialization @@ -82,7 +200,7 @@ class MessageFactory(object): pkt = cbor.dumps(result) return pkt - def decode_msg(self, data, filter_func=None): + def decode_msg(self, data: bytes, filter_func: Any = None) -> Optional[Message]: """Decode incoming CBOR data and De-Ciphering :param data: data received from the multicast bus :type data: cbor @@ -94,17 +212,17 @@ class MessageFactory(object): # Decode cbor incoming data try: data_rx = cbor.loads(data) - except: + except Exception: raise MessageParserError("Unable to parse CBOR data") # Instanciate Message, parse the security layer msg = Message() try: - msg.version = data_rx[0] - msg_time = data_rx[1] - targets = cbor.loads(data_rx[3]) - msg.targets = [UUID(bytes=t) for t in targets] - msg.timestamp = [data_rx[1], data_rx[2]] + msg.version = data_rx[0] + msg_time = data_rx[1] + targets = cbor.loads(data_rx[3]) + msg.targets = [UUID(bytes=t) for t in targets] + msg.timestamp = (data_rx[1], data_rx[2]) except IndexError: raise MessageParserError("Bad Message, wrong fields") @@ -116,7 +234,7 @@ class MessageFactory(object): # Replay attack, window fixed to CIPHER_WINDOW in seconds now = build_timestamp()[0] # test done only on seconds ... if msg_time < (now - config.cipher_window): - raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time) ) + raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time)) if msg_time > (now + config.cipher_window): raise MessageParserError("Potential replay attack, message too young: %d sec" % round(now - msg_time)) @@ -132,19 +250,19 @@ class MessageFactory(object): nonce = build_nonce(msg.timestamp) try: clear = pysodium.crypto_aead_chacha20poly1305_ietf_decrypt(ciph, ad, nonce, self.cipher_key) - except : + except Exception: raise MessageParserError("Unable to decrypt msg") # Decode application layer (payload) try: payload = cbor.loads(clear) - except: + except Exception: raise MessageParserError("Unable to parse CBOR data in payload after decrypt") try: - msg.source = UUID(bytes=payload[0]) + msg.source = UUID(bytes=payload[0]) msg.dev_type = payload[1] msg.msg_type = payload[2] - msg.action = payload[3] + msg.action = payload[3] except IndexError: raise MessageParserError("Unable to parse payload headers") if len(payload) == 5: @@ -160,7 +278,14 @@ class MessageFactory(object): ##################################################### # MSG builder ##################################################### - def build_msg(self, dev=None, targets=[], msg_type=None, action=None, body=None): + def build_msg( + self, + dev: Optional['Device'] = None, + targets: list = [], + msg_type: Optional[MessageType] = None, + action: Optional[str] = None, + body: Optional[dict] = None, + ): """the build method takes in parameters : -A device -The list of targets of the message @@ -187,150 +312,47 @@ class MessageFactory(object): data = self.encode_msg(message) return data - def build_alive_for(self, dev, timeout=0): + def build_alive_for(self, dev: 'Device', timeout: int = 0) -> bytes: """Build Alive message for a given device timeout = 0 is the minimum value """ body = {} body['timeout'] = timeout - message = self.build_msg(dev=dev, targets=[], msg_type=MessageType.NOTIFY, action=MessageAction.ALIVE.value, body=body) + message = self.build_msg( + dev=dev, targets=[], msg_type=MessageType.NOTIFY, action=MessageAction.ALIVE.value, body=body + ) return message - def build_error_msg(self, dev, errcode, description=None): + def build_error_msg(self, dev: 'Device', errcode: int, description: Optional[str] = None): """Build a Error message""" message = Message() body = {} body['code'] = errcode if description: body['description'] = description - message = self.build_msg(dev, [], MessageType.NOTIFY, "error", body) + message = self.build_msg(dev, [], MessageType.NOTIFY, 'error', body) return message -class MessageType(Enum): - NOTIFY = 0 - REQUEST = 1 - REPLY = 2 - - -class MessageAction(Enum): - ALIVE = "alive" - IS_ALIVE = "is_alive" - ATTRIBUTES_CHANGE = "attributes_change" - GET_ATTRIBUTES = "get_attributes" - GET_DESCRIPTION = "get_description" - - -class Message(object): - """Message object used for incomming & outgoint message""" - - __slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets'] - - def __init__(self): - self.version = config.STACK_VERSION # message API version - self.__targets = [] # target property - self.timestamp = None # message timestamp - self.source = None # message source - self.dev_type = None # message dev_type - self.msg_type = None # message type - self.action = None # message action - self.body = {} # message body - - @property - def targets(self): - return self.__targets - - @targets.setter - def targets(self, values): - if not isinstance(values, list): - raise MessageError("Expected a list for targetsList, got %s" % (type(values),)) - for uid in values: - if not tools.is_valid_address(uid): - raise MessageError("Bad target addr: %s" % uid) - self.__targets = values - - def targets_as_string(self): - return [str(k) for k in self.targets] - - def dump(self): - r = [] - r.append(["version", self.version]) - r.append(["targets", str(self.targets)]) - r.append(["timestamp", str(self.timestamp)]) - r.append(["source", self.source]) - r.append(["dev_type", self.dev_type]) - r.append(["msg_type", MessageType(self.msg_type)]) - r.append(["action", self.action]) - if self.body: - tmp="" - for k,v in self.body.items(): - k = k + ':' - v = pprint.pformat(v,width=55) - tmp = tmp+"- %-12s %s\n" % (k,v) - #tmp = tmp.strip() - r.append(["body", tmp]) - print(tabulate(r, headers=["Fied", "Value"], tablefmt="psql")) - - def __repr__(self): - return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>" - - def is_request(self): - if MessageType(self.msg_type) == MessageType.REQUEST: - return True - return False - - def is_reply(self): - if MessageType(self.msg_type) == MessageType.REPLY: - return True - return False - - def is_notify(self): - if MessageType(self.msg_type) == MessageType.NOTIFY: - return True - return False - - def is_alive(self): - if self.is_notify() and self.action == MessageAction.ALIVE.value: - return True - return False - - def is_request_isalive(self): - if self.is_request() and self.action == MessageAction.IS_ALIVE.value: - return True - return False - - def is_attributes_change(self): - if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value: - return True - return False - - def is_get_attribute_reply(self): - if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value: - return True - return False - - def is_get_description_reply(self): - if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value: - return True - return False - - -def build_nonce(data): - """ Big-Endian, time in seconds and time in microseconds """ - nonce = struct.pack('>QL', data[0], data[1]) +def build_nonce(data: tuple) -> bytes: + """Big-Endian, time in seconds and time in microseconds""" + nonce = struct.pack(">QL", data[0], data[1]) return nonce -def build_timestamp(): +def build_timestamp() -> tuple: """Return array [seconds since epoch, microseconds since last seconds] Time = UTC+0000""" epoch = datetime.datetime.fromtimestamp(0, datetime.UTC) timestamp = datetime.datetime.now(datetime.UTC) - epoch - return _packtimestamp(timestamp.total_seconds(), timestamp.microseconds) + return (int(timestamp.total_seconds()), int(timestamp.microseconds)) + +## This stuff below is for Py2/Py3 compatibility. In the current state of xAAL, we only use +# Py3. This code is here for archive purpose and could be removed in the future. # for better performance, I choose to use this trick to fix the change in size for Py3. # only test once. -if sys.version_info.major == 2: - _packtimestamp = lambda t1,t2: [long(t1),int(t2)] -else: - _packtimestamp = lambda t1,t2: [int(t1),int(t2)] +# if sys.version_info.major == 2: +# _packtimestamp = lambda t1, t2: (long(t1), int(t2)) # pyright: ignore +# else: +# _packtimestamp = lambda t1, t2: (int(t1), int(t2)) diff --git a/libs/lib/xaal/lib/network.py b/libs/lib/xaal/lib/network.py index d90dd309ce39ae66351da525f39e81760ebd6132..30fc21678a3591080b7564de77ad0b666765ea19 100644 --- a/libs/lib/xaal/lib/network.py +++ b/libs/lib/xaal/lib/network.py @@ -18,25 +18,26 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # +import logging +import select import socket import struct -import select -import logging import time from enum import Enum +from typing import Optional logger = logging.getLogger(__name__) class NetworkState(Enum): disconnected = 0 - connected = 1 + connected = 1 class NetworkConnector(object): UDP_MAX_SIZE = 65507 - def __init__(self, addr, port, hops,bind_addr='0.0.0.0'): + def __init__(self, addr: str, port: int, hops: int, bind_addr="0.0.0.0"): self.addr = addr self.port = port self.hops = hops @@ -52,14 +53,14 @@ class NetworkConnector(object): def __connect(self): logger.info("Connecting to %s:%s" % (self.addr, self.port)) - self.__sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP) + self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) # #formac os ??? - #self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.__sock.bind((self.bind_addr, self.port)) - mreq = struct.pack("=4s4s",socket.inet_aton(self.addr),socket.inet_aton(self.bind_addr)) - self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq) - self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_TTL,self.hops) + mreq = struct.pack("=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr)) + self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, self.hops) self.state = NetworkState.connected def disconnect(self): @@ -70,17 +71,17 @@ class NetworkConnector(object): def is_connected(self): return self.state == NetworkState.connected - def receive(self): + def receive(self) -> bytes: packt = self.__sock.recv(self.UDP_MAX_SIZE) return packt - def __get_data(self): - r = select.select([self.__sock, ], [], [], 0.02) + def __get_data(self) -> Optional[bytes]: + r = select.select([self.__sock,], [], [], 0.02) if r[0]: return self.receive() return None - def get_data(self): + def get_data(self) -> Optional[bytes]: if not self.is_connected(): self.connect() try: @@ -88,7 +89,7 @@ class NetworkConnector(object): except Exception as e: self.network_error(e) - def send(self,data): + def send(self, data: bytes): if not self.is_connected(): self.connect() try: @@ -96,7 +97,7 @@ class NetworkConnector(object): except Exception as e: self.network_error(e) - def network_error(self, msg): + def network_error(self, ex: Exception): self.disconnect() - logger.info("Network error, reconnect..%s" % msg) + logger.info("Network error, reconnect..%s" % ex.__str__()) time.sleep(5) diff --git a/libs/lib/xaal/lib/test.py b/libs/lib/xaal/lib/test.py index 9e9857cf0269d2b24615f6348c9a091921a28b76..d93002495e57d8270df7b653c897e89215591392 100644 --- a/libs/lib/xaal/lib/test.py +++ b/libs/lib/xaal/lib/test.py @@ -7,8 +7,8 @@ from xaal.lib import tools import logging -ADDR="b8bec7ca-f955-11e6-9031-82ed25e6aaaa" -ADDR=tools.get_random_uuid() +ADDR='b8bec7ca-f955-11e6-9031-82ed25e6aaaa' +ADDR=tools.get_random_uuid() def dump_hex(name,data): print("%s : " % name,end='=') @@ -23,30 +23,30 @@ def test_pysodium(): from xaal.lib import messages import pysodium - payload = "FooBar".encode("utf-8") + payload = 'FooBar'.encode("utf-8") ad = '[]' data = messages.build_timestamp() nonce = messages.build_nonce(data) key = tools.pass2key("My Friend Goo") - - dump_hex("Payload",payload) - dump_hex("Key",key) + + dump_hex('Payload',payload) + dump_hex('Key',key) ciph = pysodium.crypto_aead_chacha20poly1305_encrypt(payload, ad, nonce, key) - dump_hex("Ciph",ciph) - + dump_hex('Ciph',ciph) + pjson = pysodium.crypto_aead_chacha20poly1305_decrypt(ciph, ad, nonce, key) print(pjson) - + def test_device(): from xaal.lib.devices import Device addr = ADDR dev = Device("foo.basic",addr) - dev.vendor_id = "ACME" + dev.vendor_id = 'ACME' dev.url="http://acmefactory.blogspot.fr/" - dev.hw_id = "0x201" + dev.hw_id = '0x201' print(dev.getDescription()) return dev @@ -59,7 +59,7 @@ def test_msg(): def test_encode(): from xaal.lib.messages import MessageFactory - key = tools.pass2key("FooBar") + key = tools.pass2key('FooBar') factory = MessageFactory(key) m2 = factory.build_msg() print(factory.decode_msg(m2)) @@ -68,10 +68,10 @@ def test_log(): logger = logging.getLogger(__name__) logger.info("This is an INFO msg") logger.debug("This is an DEBUG msg") - + def test_engine(): - from xaal.lib.core import Engine + from xaal.lib.engine import Engine engine = Engine() engine.run() @@ -87,7 +87,7 @@ def test_crypto_decoding_error(): dev = xaal.lib.Device("test.basic",addr) eng = xaal.lib.Engine() eng.add_devices([dev,]) - eng.msg_factory.cipher_key = tools.pass2key("FakeKey") + eng.msg_factory.cipher_key = tools.pass2key('FakeKey') eng.start() eng.loop() @@ -95,14 +95,14 @@ def test_crypto_decoding_error(): def test_attr(): dev = xaal.lib.Device("test.basic",ADDR) dev.url = "http://linux.org" - dev.vendor_id = "ACME" + dev.vendor_id = 'ACME' dev.product_id = "Full Fake Device" - dev.info = "FooBar" - dev.hw_id = "ffd0001" + dev.info = 'FooBar' + dev.hw_id = 'ffd0001' #dev.alive_period = 100 - attr0 = dev.new_attribute("attr0",10) - attr1 = dev.new_attribute("attr1",False) + attr0 = dev.new_attribute('attr0',10) + attr1 = dev.new_attribute('attr1',False) eng = xaal.lib.Engine() eng.add_devices([dev,]) @@ -111,12 +111,12 @@ def test_attr(): attr0.value = attr0.value + 1 eng.add_timer(update,60) - + #eng.loop() eng.run() - - + + def run(): logger = tools.get_logger(__name__,logging.DEBUG,"%s.log" % __name__) @@ -128,13 +128,11 @@ def run(): #test_alive() #test_crypto_decoding_error() test_attr() - - + + if __name__ == '__main__': try: run() except KeyboardInterrupt: print("Bye bye...") - - diff --git a/libs/lib/xaal/lib/tools.py b/libs/lib/xaal/lib/tools.py index ccd5e0b30c74a60e84aa6e2aa06de1eab8cd591a..40650b464301708f1aa4427684465f394a291e83 100644 --- a/libs/lib/xaal/lib/tools.py +++ b/libs/lib/xaal/lib/tools.py @@ -18,101 +18,114 @@ # along with xAAL. If not, see <http://www.gnu.org/licenses/>. # +import functools import os import re +import sys +from typing import Optional, Union import pysodium - -import sys -import functools from configobj import ConfigObj -from . import config +from .config import config from .bindings import UUID -XAAL_DEVTYPE_PATTERN = '^[a-zA-Z][a-zA-Z0-9_-]*\\.[a-zA-Z][a-zA-Z0-9_-]*$' +XAAL_DEVTYPE_PATTERN = "^[a-zA-Z][a-zA-Z0-9_-]*\\.[a-zA-Z][a-zA-Z0-9_-]*$" -def get_cfg_filename(name, cfg_dir=config.conf_dir): - if name.startswith('xaal.'): + +def get_cfg_filename(name: str, cfg_dir: str = config.conf_dir) -> str: + if name.startswith("xaal."): name = name[5:] - filename = '%s.ini' % name + filename = "%s.ini" % name if not os.path.isdir(cfg_dir): print("Your configuration directory doesn't exist: [%s]" % cfg_dir) return os.path.join(cfg_dir, filename) -def load_cfg_file(filename): - """ load .ini file and return it as dict""" + +def load_cfg_file(filename: str) -> Optional[ConfigObj]: + """load .ini file and return it as dict""" if os.path.isfile(filename): - return ConfigObj(filename,indent_type=' ',encoding="utf8") + return ConfigObj(filename, indent_type=" ", encoding='utf8') return None -def load_cfg(app_name): + +def load_cfg(app_name: str) -> Optional[ConfigObj]: filename = get_cfg_filename(app_name) return load_cfg_file(filename) -def load_cfg_or_die(app_name): + +def load_cfg_or_die(app_name: str) -> ConfigObj: cfg = load_cfg(app_name) if not cfg: print("Unable to load config file %s" % get_cfg_filename(app_name)) sys.exit(-1) return cfg -def new_cfg(app_name): + +def new_cfg(app_name: str) -> ConfigObj: filename = get_cfg_filename(app_name) - cfg = ConfigObj(filename,indent_type=' ') - cfg['config'] = {} - cfg['config']['addr']=get_random_uuid().str + cfg = ConfigObj(filename, indent_type=" ") + cfg['config'] = {'addr' : get_random_uuid().str} return cfg -def get_random_uuid(): + +def get_random_uuid() -> UUID: return UUID.random() -def get_random_base_uuid(digit=2): + +def get_random_base_uuid(digit=2) -> UUID: return UUID.random_base(digit) -def get_uuid(val): - if isinstance(val,UUID): + +def get_uuid(val: Union[UUID, str]) -> Optional[UUID]: + if isinstance(val, UUID): return val - if isinstance(val,str): + if isinstance(val, str): return str_to_uuid(val) return None -def str_to_uuid(val): - """ return an xAAL address for a given string""" + +def str_to_uuid(val: str) -> Optional[UUID]: + """return an xAAL address for a given string""" try: return UUID(val) except ValueError: return None -def bytes_to_uuid(val): + +def bytes_to_uuid(val: bytes) -> Optional[UUID]: try: return UUID(bytes=val) except ValueError: return None -def is_valid_uuid(val): - return isinstance(val,UUID) -def is_valid_address(val): +def is_valid_uuid(val: UUID) -> bool: + return isinstance(val, UUID) + + +def is_valid_address(val: UUID) -> bool: return is_valid_uuid(val) + @functools.lru_cache(maxsize=128) -def is_valid_dev_type(val): - if not isinstance(val,str): +def is_valid_dev_type(val: str) -> bool: + if not isinstance(val, str): return False - if re.match(XAAL_DEVTYPE_PATTERN,val): - return True + if re.match(XAAL_DEVTYPE_PATTERN, val): + return True return False -def pass2key(passphrase): + +def pass2key(passphrase: str) -> bytes: """Generate key from passphrase using libsodium crypto_pwhash_scryptsalsa208sha256 func salt: buffer of zeros opslimit: crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE memlimit: crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE """ - buf = passphrase.encode('utf-8') - KEY_BYTES = pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES #32 + buf = passphrase.encode("utf-8") + KEY_BYTES = pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES # 32 # this should be: # salt = bytes(KEY_BYTES) # but due to bytes() stupid stuff in py2 we need this awfull stuff @@ -122,8 +135,9 @@ def pass2key(passphrase): key = pysodium.crypto_pwhash_scryptsalsa208sha256(KEY_BYTES, buf, salt, opslimit, memlimit) return key + @functools.lru_cache(maxsize=128) -def reduce_addr(addr): +def reduce_addr(addr: UUID) -> str: """return a string based addred without all digits""" tmp = addr.str return tmp[:5] + '..' + tmp[-5:] diff --git a/libs/monitor/pyproject.toml b/libs/monitor/pyproject.toml index b657358447e55dc73ba564509a42daa90e51f6af..8532a5eafd23061625df2bdf7b39db80ef8ea1d2 100644 --- a/libs/monitor/pyproject.toml +++ b/libs/monitor/pyproject.toml @@ -19,3 +19,14 @@ dependencies = ['xaal.lib'] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/libs/monitor/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/libs/monitor" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/libs/monitor/xaal/monitor/monitor.py b/libs/monitor/xaal/monitor/monitor.py index 0b42ce9b6c21a536b5baa5df7e17bb8442e00813..d50ce8bc7c3d040810be34f75f30b6ab21de0874 100644 --- a/libs/monitor/xaal/monitor/monitor.py +++ b/libs/monitor/xaal/monitor/monitor.py @@ -7,6 +7,7 @@ from xaal.lib import Message import logging + logger = logging.getLogger(__name__) # how often we force refresh the devices attributes/description/keyvalues @@ -22,23 +23,24 @@ def now(): class TimedDict(dict): def __init__(self, refresh_rate=REFRESH_RATE, data={}): - dict.__init__(self,data) + dict.__init__(self, data) self.last_update = 0 self.next_update = 0 + self.refresh_rate = refresh_rate def updated(self): self.last_update = now() - self.next_update = self.last_update + REFRESH_RATE + random.randint(-30, 30) + self.next_update = self.last_update + self.refresh_rate + random.randint(-30, 30) def __setitem__(self, key, item): - super().__setitem__(key,item) + super().__setitem__(key, item) self.updated() - def update(self, dict_): - changed = False if self.last_update !=0 else True - if dict_!= self: + def update(self, args, **kwargs): + changed = False if self.last_update != 0 else True + if args != self: changed = True - super().update(dict_) + super().update(args) self.updated() return changed @@ -52,15 +54,15 @@ class Device: self.short_address = tools.reduce_addr(addr) self.dev_type = dev_type # device cache - self.attributes = TimedDict(refresh_rate=REFRESH_RATE) - self.description = TimedDict(refresh_rate=REFRESH_RATE*3) - self.db = TimedDict(refresh_rate=REFRESH_RATE*3) + self.attributes = TimedDict(refresh_rate=REFRESH_RATE) + self.description = TimedDict(refresh_rate=REFRESH_RATE * 3) + self.db = TimedDict(refresh_rate=REFRESH_RATE * 3) # Alive management self.last_alive = now() self.next_alive = 0 def update_attributes(self, data): - """ rude update attributes. Return true if updated""" + """rude update attributes. Return true if updated""" # really not the best comparaison, but we just need a flag return self.attributes.update(data) @@ -72,10 +74,10 @@ class Device: """ return self.description.update(data) - def update_db(self,data): + def update_db(self, data): return self.db.update(data) - def set_db(self,data): + def set_db(self, data): purge = [] for k in data: if data[k] is None: @@ -90,15 +92,15 @@ class Device: return True return False - def alive(self,value): + def alive(self, value): self.last_alive = int(time.time()) self.next_alive = self.last_alive + value - def get_kv(self,key): - return self.db.get(key,None) + def get_kv(self, key): + return self.db.get(key, None) def dump(self): - print("*** %s %s **" % (self.address,self.dev_type)) + print("*** %s %s **" % (self.address, self.dev_type)) print(" Description : %s" % self.description) print(" Attributes : %s" % self.attributes) print() @@ -106,19 +108,21 @@ class Device: @property def display_name(self): result = tools.reduce_addr(self.address) - result = self.db.get('nickname',result) - result = self.db.get('name',result) + result = self.db.get('nickname', result) + result = self.db.get('name', result) return result + class Devices: - """ Device List for monitoring """ + """Device List for monitoring""" + def __init__(self): self.__devs = {} self.__list_cache = None - def add(self,addr,dev_type): - dev = Device(addr,dev_type) - self.__devs.update({addr : dev}) + def add(self, addr, dev_type): + dev = Device(addr, dev_type) + self.__devs.update({addr: dev}) self.__list_cache = None return dev @@ -128,9 +132,9 @@ class Devices: def get(self): if not self.__list_cache: - #print("Refresh cache") + # print("Refresh cache") res = list(self.__devs.values()) - res.sort(key = lambda d: d.dev_type) + res.sort(key=lambda d: d.dev_type) self.__list_cache = res return self.__list_cache @@ -143,7 +147,7 @@ class Devices: def get_with_group(self, addr): r = [] for d in self.get(): - if addr==d.description.get('group_id', None): + if addr == d.description.get('group_id', None): r.append(d) return r @@ -164,7 +168,7 @@ class Devices: def get_with_key_value(self, key, value): r = [] for d in self.get(): - if (key in d.db) and (d.db[key]==value): + if (key in d.db) and (d.db[key] == value): r.append(d) return r @@ -176,13 +180,13 @@ class Devices: return None def get_dev_types(self): - """ return the list of distinct dev_types""" - l = [] + """return the list of distinct dev_types""" + ll = [] for dev in self.__devs.values(): - if dev.dev_type not in l: - l.append(dev.dev_type) - l.sort() - return l + if dev.dev_type not in ll: + ll.append(dev.dev_type) + ll.sort() + return ll def __len__(self): return len(self.__devs) @@ -199,7 +203,7 @@ class Devices: return key in self.__devs def auto_wash(self): - now_ =now() + now_ = now() result = [] for dev in self.get(): if dev.next_alive < now_: @@ -209,22 +213,23 @@ class Devices: def dump(self): for d in self.get(): - print("%s %s" % (d.address,d.dev_type)) + print("%s %s" % (d.address, d.dev_type)) class Notification(Enum): - new_device = 0 - drop_device = 1 # sending drop_device notif is not implemented yet, - attribute_change = 2 - description_change = 3 - metadata_change = 4 + new_device = 0 + drop_device = 1 # sending drop_device notif is not implemented yet, + attribute_change = 2 + description_change = 3 + metadata_change = 4 class Monitor: """ use this class to monitor a xAAL network """ - def __init__(self,device,filter_func=None,db_server=None): + + def __init__(self, device, filter_func=None, db_server=None): self.dev = device self.engine = device.engine self.db_server = db_server @@ -240,8 +245,8 @@ class Monitor: self.engine.disable_msg_filter() # only send isAlive message every 2 expirations self.send_is_alive() - self.engine.add_timer(self.refresh_alives,REFRESH_TIMER) - # delete expired device every 10s + self.engine.add_timer(self.refresh_alives, REFRESH_TIMER) + # delete expired device every 10s self.engine.add_timer(self.auto_wash, AUTOWASH_TIMER) # wait x seconds for the first isAlive answers before the initial crawl self.refresh_timer = self.engine.add_timer(self.refresh_devices, BOOT_TIMER) @@ -252,23 +257,25 @@ class Monitor: return if msg.source not in self.devices: dev = self.add_device(msg) - self.notify(Notification.new_device,dev) + self.notify(Notification.new_device, dev) dev = self.devices.get_with_addr(msg.source) + if not dev: + return if msg.is_alive(): - dev.alive(msg.body.get('timeout', config.DEF_ALIVE_TIMER)) + dev.alive(msg.body.get('timeout', config.alive_timer)) elif msg.is_request_isalive(): self.last_isalive = now() elif msg.is_attributes_change() or msg.is_get_attribute_reply(): if dev.update_attributes(msg.body): - self.notify(Notification.attribute_change,dev) + self.notify(Notification.attribute_change, dev) elif msg.is_get_description_reply(): if dev.update_description(msg.body): - self.notify(Notification.description_change,dev) + self.notify(Notification.description_change, dev) elif self.is_from_metadb(msg): addr = msg.body.get('device') @@ -280,7 +287,7 @@ class Monitor: if self.is_update_metadb(msg): changed = target.update_db(msg.body['map']) if changed: - self.notify(Notification.metadata_change,target) + self.notify(Notification.metadata_change, target) def subscribe(self, func): self.subscribers.append(func) @@ -290,11 +297,11 @@ class Monitor: def notify(self, ev_type, device): for s in self.subscribers: - #logger.warning(f"{s} {ev_type}") - s(ev_type,device) + # logger.warning(f"{s} {ev_type}") + s(ev_type, device) def add_device(self, msg): - return self.devices.add(msg.source,msg.dev_type) + return self.devices.add(msg.source, msg.dev_type) def auto_wash(self): """call the Auto-wash on devices List""" @@ -308,11 +315,11 @@ class Monitor: self.last_isalive = now() def refresh_alives(self): - """ every REFRESH we check if need to send a isAlive""" + """every REFRESH we check if need to send a isAlive""" tmp = self.last_isalive + config.alive_timer * 2 if tmp < now(): self.send_is_alive() - + def refresh_devices(self): now_ = now() cnt = 0 @@ -321,40 +328,57 @@ class Monitor: if dev.description.next_update < now_: self.request_description(dev.address) dev.description.next_update = now_ + REFRESH_RATE - cnt = cnt +1 + cnt = cnt + 1 # metadata if self.db_server and dev.db.next_update < now_: self.request_metadb(dev.address) dev.db.next_update = now_ + REFRESH_RATE - cnt = cnt +1 + cnt = cnt + 1 # attributes if dev.attributes.next_update < now_: self.request_attributes(dev.address) dev.attributes.next_update = now_ + REFRESH_RATE - cnt = cnt +1 + cnt = cnt + 1 if cnt > 40: break # switch to normal timer after boot - if not self.boot_finished and cnt == 0 and len(self.devices)!=0: + if not self.boot_finished and cnt == 0 and len(self.devices) != 0: self.refresh_timer.period = REFRESH_TIMER logger.debug("Switching to slow refresh timer") self.boot_finished = True - elif cnt!=0: - logger.debug("request queued: %d" % cnt ) + elif cnt != 0: + logger.debug("request queued: %d" % cnt) def request_metadb(self, addr): if self.db_server: - self.engine.send_request(self.dev, [self.db_server,], 'get_keys_values', {'device':addr}) + self.engine.send_request( + self.dev, + [ + self.db_server, + ], + 'get_keys_values', + {'device': addr}, + ) def request_attributes(self, addr): - self.engine.send_get_attributes(self.dev,[addr,]) + self.engine.send_get_attributes( + self.dev, + [ + addr, + ], + ) def request_description(self, addr): - self.engine.send_get_description(self.dev,[addr,]) + self.engine.send_get_description( + self.dev, + [ + addr, + ], + ) def is_from_metadb(self, msg): - if (msg.is_notify() or msg.is_reply()) and msg.source == self.db_server : + if (msg.is_notify() or msg.is_reply()) and msg.source == self.db_server: return True return False @@ -370,4 +394,7 @@ class Monitor: def debug_timers(self): for dev in self.devices: - print("%s\t%s\t%d\t%d\t%d" % (dev.address,dev.dev_type,dev.description.last_update,dev.db.last_update,dev.attributes.last_update)) + print( + "%s\t%s\t%d\t%d\t%d" + % (dev.address, dev.dev_type, dev.description.last_update, dev.db.last_update, dev.attributes.last_update) + ) diff --git a/libs/schemas/pyproject.toml b/libs/schemas/pyproject.toml index 70c938ce8267598ab5380ea87009a40935e73524..a81c758fbe715b7d68242c66ca5578c7099dfefd 100644 --- a/libs/schemas/pyproject.toml +++ b/libs/schemas/pyproject.toml @@ -20,3 +20,14 @@ dependencies = ['xaal.lib'] Homepage = "https://recherche.imt-atlantique.fr/xaal/" Documentation = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/entry/code/Python/branches/0.7/libs/schemas/README.rst" Source = "https://redmine.imt-atlantique.fr/projects/xaal/repository/xaal/show/code/Python/branches/0.7/libs/schemas" + +[tool.ruff] +line-length = 122 + +[tool.ruff.format] +quote-style = "preserve" + +[tool.black] +line-length = 122 +skip-string-normalization = true + diff --git a/libs/schemas/xaal/schemas/devices.py b/libs/schemas/xaal/schemas/devices.py index c207820b25736ff87589f738988e05cce68dd2ea..78c33b6a1781568c634c639f517090f3ac5bab84 100644 --- a/libs/schemas/xaal/schemas/devices.py +++ b/libs/schemas/xaal/schemas/devices.py @@ -1,18 +1,22 @@ """ -Autogenerated devices skeletons. This file is generated from the json schemas +Autogenerated devices skeletons. This file is generated from the json schemas at https://redmine.telecom-bretagne.eu/svn/xaal/schemas/branches/schemas-0.7 This python module is updated frequently according to schemas """ - -from xaal.lib import Device,tools import logging +from typing import Optional + +from xaal.lib import Device, tools +from xaal.lib.bindings import UUID + logger = logging.getLogger(__name__) + #===================================================================== -def barometer(addr=None): +def barometer(addr: Optional[UUID] = None) -> Device: """Simple barometer""" addr = addr or tools.get_random_uuid() dev = Device('barometer.basic',addr) @@ -23,14 +27,14 @@ def barometer(addr=None): return dev #===================================================================== -def basic(addr=None): +def basic(addr: Optional[UUID] = None) -> Device: """Generic schema for any devices""" addr = addr or tools.get_random_uuid() dev = Device('basic.basic',addr) return dev #===================================================================== -def battery(addr=None): +def battery(addr: Optional[UUID] = None) -> Device: """Report on state of a battery""" addr = addr or tools.get_random_uuid() dev = Device('battery.basic',addr) @@ -43,14 +47,14 @@ def battery(addr=None): return dev #===================================================================== -def button(addr=None): +def button(addr: Optional[UUID] = None) -> Device: """Simple button device""" addr = addr or tools.get_random_uuid() dev = Device('button.basic',addr) return dev #===================================================================== -def button_remote(addr=None): +def button_remote(addr: Optional[UUID] = None) -> Device: """Simple remote device with several buttons""" addr = addr or tools.get_random_uuid() dev = Device('button.remote',addr) @@ -65,7 +69,7 @@ def button_remote(addr=None): return dev #===================================================================== -def cache(addr=None): +def cache(addr: Optional[UUID] = None) -> Device: """Simple cache that can be queried about attributes of devices""" addr = addr or tools.get_random_uuid() dev = Device('cache.basic',addr) @@ -80,7 +84,7 @@ def cache(addr=None): return dev #===================================================================== -def co2meter(addr=None): +def co2meter(addr: Optional[UUID] = None) -> Device: """Simple CO2 meter""" addr = addr or tools.get_random_uuid() dev = Device('co2meter.basic',addr) @@ -91,7 +95,7 @@ def co2meter(addr=None): return dev #===================================================================== -def contact(addr=None): +def contact(addr: Optional[UUID] = None) -> Device: """Simple contact-sensor device; e.g. door or window opening sensor""" addr = addr or tools.get_random_uuid() dev = Device('contact.basic',addr) @@ -102,7 +106,7 @@ def contact(addr=None): return dev #===================================================================== -def door(addr=None): +def door(addr: Optional[UUID] = None) -> Device: """Simple door device""" addr = addr or tools.get_random_uuid() dev = Device('door.basic',addr) @@ -126,7 +130,7 @@ def door(addr=None): return dev #===================================================================== -def falldetector(addr=None): +def falldetector(addr: Optional[UUID] = None) -> Device: """Simple fall detection device""" addr = addr or tools.get_random_uuid() dev = Device('falldetector.basic',addr) @@ -137,7 +141,7 @@ def falldetector(addr=None): return dev #===================================================================== -def gateway(addr=None): +def gateway(addr: Optional[UUID] = None) -> Device: """Simple gateway that manage physical devices""" addr = addr or tools.get_random_uuid() dev = Device('gateway.basic',addr) @@ -150,14 +154,14 @@ def gateway(addr=None): return dev #===================================================================== -def hmi(addr=None): +def hmi(addr: Optional[UUID] = None) -> Device: """Basic Human Machine Interface""" addr = addr or tools.get_random_uuid() dev = Device('hmi.basic',addr) return dev #===================================================================== -def hygrometer(addr=None): +def hygrometer(addr: Optional[UUID] = None) -> Device: """Simple hygrometer""" addr = addr or tools.get_random_uuid() dev = Device('hygrometer.basic',addr) @@ -168,7 +172,7 @@ def hygrometer(addr=None): return dev #===================================================================== -def lamp(addr=None): +def lamp(addr: Optional[UUID] = None) -> Device: """Simple lamp""" addr = addr or tools.get_random_uuid() dev = Device('lamp.basic',addr) @@ -192,7 +196,7 @@ def lamp(addr=None): return dev #===================================================================== -def lamp_color(addr=None): +def lamp_color(addr: Optional[UUID] = None) -> Device: """Color-changing lamp""" addr = addr or tools.get_random_uuid() dev = Device('lamp.color',addr) @@ -256,7 +260,7 @@ def lamp_color(addr=None): return dev #===================================================================== -def lamp_dimmer(addr=None): +def lamp_dimmer(addr: Optional[UUID] = None) -> Device: """Simple dimmable lamp""" addr = addr or tools.get_random_uuid() dev = Device('lamp.dimmer',addr) @@ -294,7 +298,7 @@ def lamp_dimmer(addr=None): return dev #===================================================================== -def lamp_toggle(addr=None): +def lamp_toggle(addr: Optional[UUID] = None) -> Device: """Simple lamp with toggle function - Note that a toggle function may leads to undefined state due to its stateful nature; its usage should be avoided.""" addr = addr or tools.get_random_uuid() dev = Device('lamp.toggle',addr) @@ -323,7 +327,7 @@ def lamp_toggle(addr=None): return dev #===================================================================== -def lightgauge(addr=None): +def lightgauge(addr: Optional[UUID] = None) -> Device: """Simple light gauge""" addr = addr or tools.get_random_uuid() dev = Device('lightgauge.basic',addr) @@ -334,7 +338,7 @@ def lightgauge(addr=None): return dev #===================================================================== -def linkquality(addr=None): +def linkquality(addr: Optional[UUID] = None) -> Device: """Report on quality of a transmission link""" addr = addr or tools.get_random_uuid() dev = Device('linkquality.basic',addr) @@ -347,7 +351,7 @@ def linkquality(addr=None): return dev #===================================================================== -def luxmeter(addr=None): +def luxmeter(addr: Optional[UUID] = None) -> Device: """Simple luxmeter""" addr = addr or tools.get_random_uuid() dev = Device('luxmeter.basic',addr) @@ -358,7 +362,7 @@ def luxmeter(addr=None): return dev #===================================================================== -def metadatadb(addr=None): +def metadatadb(addr: Optional[UUID] = None) -> Device: """Simple metatdata database to manage tags associated with devices""" addr = addr or tools.get_random_uuid() dev = Device('metadatadb.basic',addr) @@ -388,7 +392,7 @@ def metadatadb(addr=None): return dev #===================================================================== -def motion(addr=None): +def motion(addr: Optional[UUID] = None) -> Device: """Simple motion detector device""" addr = addr or tools.get_random_uuid() dev = Device('motion.basic',addr) @@ -399,7 +403,7 @@ def motion(addr=None): return dev #===================================================================== -def powermeter(addr=None): +def powermeter(addr: Optional[UUID] = None) -> Device: """Simple powermeter""" addr = addr or tools.get_random_uuid() dev = Device('powermeter.basic',addr) @@ -414,7 +418,7 @@ def powermeter(addr=None): return dev #===================================================================== -def powerrelay(addr=None): +def powerrelay(addr: Optional[UUID] = None) -> Device: """Simple power relay device""" addr = addr or tools.get_random_uuid() dev = Device('powerrelay.basic',addr) @@ -438,7 +442,7 @@ def powerrelay(addr=None): return dev #===================================================================== -def powerrelay_toggle(addr=None): +def powerrelay_toggle(addr: Optional[UUID] = None) -> Device: """Power relay with toggle function - Note that a toggle function may leads to undefined state due to its stateful nature; its usage should be avoided.""" addr = addr or tools.get_random_uuid() dev = Device('powerrelay.toggle',addr) @@ -467,7 +471,7 @@ def powerrelay_toggle(addr=None): return dev #===================================================================== -def raingauge(addr=None): +def raingauge(addr: Optional[UUID] = None) -> Device: """Simple rain gauge""" addr = addr or tools.get_random_uuid() dev = Device('raingauge.basic',addr) @@ -480,7 +484,7 @@ def raingauge(addr=None): return dev #===================================================================== -def scale(addr=None): +def scale(addr: Optional[UUID] = None) -> Device: """Simple scale""" addr = addr or tools.get_random_uuid() dev = Device('scale.basic',addr) @@ -491,7 +495,7 @@ def scale(addr=None): return dev #===================================================================== -def scenario(addr=None): +def scenario(addr: Optional[UUID] = None) -> Device: """Simple Scenario""" addr = addr or tools.get_random_uuid() dev = Device('scenario.basic',addr) @@ -527,7 +531,7 @@ def scenario(addr=None): return dev #===================================================================== -def shutter(addr=None): +def shutter(addr: Optional[UUID] = None) -> Device: """Simple shutter""" addr = addr or tools.get_random_uuid() dev = Device('shutter.basic',addr) @@ -556,7 +560,7 @@ def shutter(addr=None): return dev #===================================================================== -def shutter_position(addr=None): +def shutter_position(addr: Optional[UUID] = None) -> Device: """Shutter with a position managment""" addr = addr or tools.get_random_uuid() dev = Device('shutter.position',addr) @@ -592,7 +596,7 @@ def shutter_position(addr=None): return dev #===================================================================== -def soundmeter(addr=None): +def soundmeter(addr: Optional[UUID] = None) -> Device: """Simple soundmeter""" addr = addr or tools.get_random_uuid() dev = Device('soundmeter.basic',addr) @@ -603,7 +607,7 @@ def soundmeter(addr=None): return dev #===================================================================== -def switch(addr=None): +def switch(addr: Optional[UUID] = None) -> Device: """Simple switch button device""" addr = addr or tools.get_random_uuid() dev = Device('switch.basic',addr) @@ -614,7 +618,7 @@ def switch(addr=None): return dev #===================================================================== -def thermometer(addr=None): +def thermometer(addr: Optional[UUID] = None) -> Device: """Simple thermometer""" addr = addr or tools.get_random_uuid() dev = Device('thermometer.basic',addr) @@ -625,7 +629,7 @@ def thermometer(addr=None): return dev #===================================================================== -def tts(addr=None): +def tts(addr: Optional[UUID] = None) -> Device: """Text-To-Speech device""" addr = addr or tools.get_random_uuid() dev = Device('tts.basic',addr) @@ -640,7 +644,7 @@ def tts(addr=None): return dev #===================================================================== -def windgauge(addr=None): +def windgauge(addr: Optional[UUID] = None) -> Device: """Simple wind gauge""" addr = addr or tools.get_random_uuid() dev = Device('windgauge.basic',addr) @@ -657,7 +661,7 @@ def windgauge(addr=None): return dev #===================================================================== -def window(addr=None): +def window(addr: Optional[UUID] = None) -> Device: """Simple window device""" addr = addr or tools.get_random_uuid() dev = Device('window.basic',addr) @@ -681,7 +685,7 @@ def window(addr=None): return dev #===================================================================== -def worktop(addr=None): +def worktop(addr: Optional[UUID] = None) -> Device: """Simple worktop""" addr = addr or tools.get_random_uuid() dev = Device('worktop.basic',addr) diff --git a/libs/schemas/xaal/schemas/devices_py.mako b/libs/schemas/xaal/schemas/devices_py.mako index a2c58f4f5f3e369e0345b0a6540643285cf103ed..1423d3ea00985cb70fb864b39ba839629cfa2b24 100644 --- a/libs/schemas/xaal/schemas/devices_py.mako +++ b/libs/schemas/xaal/schemas/devices_py.mako @@ -1,5 +1,5 @@ #===================================================================== -def ${name}(addr=None): +def ${name}(addr: Optional[UUID] = None) -> Device: """${doc}""" addr = addr or tools.get_random_uuid() dev = Device('${devtype}',addr) @@ -39,9 +39,9 @@ def ${name}(addr=None): %> def default_${meth}(${args}): """${methods[meth]['description']}""" % if len(args) == 0: - logger.info("default_${meth}()") + logger.warning("default_${meth}()") % else: - logger.info("default_${meth}(${buf})" % (${print_keys})) + logger.warning("default_${meth}(${buf})" % (${print_keys})) % endif % endfor diff --git a/libs/schemas/xaal/schemas/head_py.txt b/libs/schemas/xaal/schemas/head_py.txt index 90fc65059358432cd5c5eae0bd7bf4704d0e9a8b..6137b3691adcd0c0c012450455145d6949cd8eb0 100644 --- a/libs/schemas/xaal/schemas/head_py.txt +++ b/libs/schemas/xaal/schemas/head_py.txt @@ -5,8 +5,12 @@ at https://redmine.telecom-bretagne.eu/svn/xaal/schemas/branches/schemas-0.7 This python module is updated frequently according to schemas """ - -from xaal.lib import Device,tools import logging +from typing import Optional + +from xaal.lib import Device, tools +from xaal.lib.bindings import UUID + logger = logging.getLogger(__name__) +