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__)
 
+