xaal.lib 0.7.6__py3-none-any.whl → 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xaal/lib/__init__.py +1 -1
- xaal/lib/aioengine.py +155 -131
- xaal/lib/aiohelpers.py +7 -7
- xaal/lib/aionetwork.py +24 -24
- xaal/lib/bindings.py +30 -32
- xaal/lib/cbor.py +15 -10
- xaal/lib/config.py +71 -42
- xaal/lib/core.py +82 -65
- xaal/lib/devices.py +145 -102
- xaal/lib/engine.py +61 -43
- xaal/lib/exceptions.py +24 -8
- xaal/lib/helpers.py +38 -27
- xaal/lib/messages.py +171 -149
- xaal/lib/network.py +23 -20
- xaal/lib/test.py +25 -27
- xaal/lib/tools.py +50 -36
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/METADATA +20 -3
- xaal.lib-0.7.8.dist-info/RECORD +22 -0
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/WHEEL +1 -1
- xaal.lib-0.7.6.dist-info/RECORD +0 -22
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/top_level.txt +0 -0
xaal/lib/bindings.py
CHANGED
|
@@ -2,86 +2,86 @@ import uuid
|
|
|
2
2
|
|
|
3
3
|
from .exceptions import UUIDError
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class UUID:
|
|
6
|
-
def __init__(self
|
|
7
|
-
self.__uuid = uuid.UUID(*args
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
self.__uuid = uuid.UUID(*args, **kwargs)
|
|
8
9
|
|
|
9
10
|
@staticmethod
|
|
10
|
-
def random_base(digit=2):
|
|
11
|
+
def random_base(digit=2) -> 'UUID':
|
|
11
12
|
"""zeros the last digits of a random uuid, usefull w/ you want to forge some addresses
|
|
12
|
-
|
|
13
|
+
two digit is great.
|
|
13
14
|
"""
|
|
14
|
-
if (digit > 0)
|
|
15
|
+
if (digit > 0) and (digit < 13):
|
|
15
16
|
tmp = str(uuid.uuid1())
|
|
16
|
-
st = "%s%s" % (tmp[:-digit],'0'*digit)
|
|
17
|
+
st = "%s%s" % (tmp[:-digit], '0' * digit)
|
|
17
18
|
return UUID(st)
|
|
18
19
|
else:
|
|
19
20
|
raise UUIDError
|
|
20
21
|
|
|
21
22
|
@staticmethod
|
|
22
|
-
def random():
|
|
23
|
+
def random() -> 'UUID':
|
|
23
24
|
tmp = uuid.uuid1().int
|
|
24
25
|
return UUID(int=tmp)
|
|
25
26
|
|
|
26
|
-
def __add__(self,value):
|
|
27
|
+
def __add__(self, value: int) -> 'UUID':
|
|
27
28
|
tmp = self.__uuid.int + value
|
|
28
29
|
return UUID(int=tmp)
|
|
29
30
|
|
|
30
|
-
def __sub__(self,value):
|
|
31
|
+
def __sub__(self, value: int) -> 'UUID':
|
|
31
32
|
tmp = self.__uuid.int - value
|
|
32
33
|
return UUID(int=tmp)
|
|
33
34
|
|
|
34
|
-
def __eq__(self,value):
|
|
35
|
+
def __eq__(self, value) -> bool:
|
|
35
36
|
return self.__uuid == value
|
|
36
37
|
|
|
37
|
-
def __lt__(self, value
|
|
38
|
+
def __lt__(self, value) -> bool:
|
|
38
39
|
return self.__uuid.int < value
|
|
39
40
|
|
|
40
|
-
def __gt__(self, value
|
|
41
|
+
def __gt__(self, value) -> bool:
|
|
41
42
|
return self.__uuid.int > value
|
|
42
43
|
|
|
43
|
-
def __str__(self):
|
|
44
|
+
def __str__(self) -> str:
|
|
44
45
|
return str(self.__uuid)
|
|
45
|
-
|
|
46
|
-
def __repr__(self):
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
47
48
|
return f"UUID('{self.__uuid}')"
|
|
48
49
|
|
|
49
|
-
def __hash__(self):
|
|
50
|
+
def __hash__(self) -> int:
|
|
50
51
|
return self.__uuid.__hash__()
|
|
51
52
|
|
|
52
|
-
def get(self):
|
|
53
|
+
def get(self) -> uuid.UUID:
|
|
53
54
|
return self.__uuid
|
|
54
|
-
|
|
55
|
-
def set(self,value):
|
|
55
|
+
|
|
56
|
+
def set(self, value: uuid.UUID):
|
|
56
57
|
self.__uuid = value
|
|
57
58
|
|
|
58
59
|
@property
|
|
59
|
-
def str(self):
|
|
60
|
+
def str(self) -> str:
|
|
60
61
|
return str(self)
|
|
61
62
|
|
|
62
63
|
@property
|
|
63
|
-
def bytes(self):
|
|
64
|
+
def bytes(self) -> bytes:
|
|
64
65
|
return self.__uuid.bytes
|
|
65
66
|
|
|
66
67
|
|
|
67
|
-
|
|
68
68
|
class URL:
|
|
69
|
-
def __init__(self,value):
|
|
69
|
+
def __init__(self, value):
|
|
70
70
|
self.__url = value
|
|
71
71
|
|
|
72
|
-
def __eq__(self,value):
|
|
72
|
+
def __eq__(self, value):
|
|
73
73
|
return self.__url == value
|
|
74
74
|
|
|
75
75
|
def __str__(self):
|
|
76
76
|
return str(self.__url)
|
|
77
|
-
|
|
78
|
-
def __repr__(self): # pragma: no cover
|
|
79
|
-
return f"URL('{self.__url}')"
|
|
80
77
|
|
|
81
|
-
def
|
|
78
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
79
|
+
return f"URL('{self.__url}')"
|
|
80
|
+
|
|
81
|
+
def set(self, value: str):
|
|
82
82
|
self.__url = value
|
|
83
83
|
|
|
84
|
-
def get(self):
|
|
84
|
+
def get(self) -> str:
|
|
85
85
|
return self.__url
|
|
86
86
|
|
|
87
87
|
@property
|
|
@@ -93,6 +93,4 @@ class URL:
|
|
|
93
93
|
return self.__url
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
classes = [UUID,URL]
|
|
98
|
-
|
|
96
|
+
classes = [UUID, URL]
|
xaal/lib/cbor.py
CHANGED
|
@@ -16,25 +16,30 @@ def tag_hook(decoder, tag, shareable_index=None):
|
|
|
16
16
|
return bindings.URL(tag.value)
|
|
17
17
|
return tag
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
def default_encoder(encoder, value):
|
|
20
|
-
if isinstance(value,bindings.UUID):
|
|
21
|
+
if isinstance(value, bindings.UUID):
|
|
21
22
|
encoder.encode(CBORTag(37, value.bytes))
|
|
22
23
|
|
|
23
|
-
if isinstance(value,bindings.URL):
|
|
24
|
+
if isinstance(value, bindings.URL):
|
|
24
25
|
encoder.encode(CBORTag(32, value.bytes))
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
def dumps(obj, **kwargs):
|
|
27
|
-
return cbor2.dumps(obj,default=default_encoder
|
|
29
|
+
return cbor2.dumps(obj, default=default_encoder, **kwargs)
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
def loads(payload, **kwargs):
|
|
30
|
-
#return cbor2.loads(payload,tag_hook=tag_hook,**kwargs)
|
|
31
|
-
return _loads(payload,tag_hook=tag_hook
|
|
33
|
+
# return cbor2.loads(payload,tag_hook=tag_hook,**kwargs)
|
|
34
|
+
return _loads(payload, tag_hook=tag_hook, **kwargs)
|
|
35
|
+
|
|
32
36
|
|
|
33
37
|
def _loads(s, **kwargs):
|
|
34
38
|
with BytesIO(s) as fp:
|
|
35
39
|
return CBORDecoder(fp, **kwargs).decode()
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
|
|
42
|
+
# class CustomDecoder(CBORDecoder):pass
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
def cleanup(obj):
|
|
@@ -45,14 +50,14 @@ def cleanup(obj):
|
|
|
45
50
|
Warning: This operate in-place changes.
|
|
46
51
|
Warning: This won't work for tags in dict keys.
|
|
47
52
|
"""
|
|
48
|
-
if isinstance(obj,list):
|
|
49
|
-
for i in range(0,len(obj)):
|
|
53
|
+
if isinstance(obj, list):
|
|
54
|
+
for i in range(0, len(obj)):
|
|
50
55
|
obj[i] = cleanup(obj[i])
|
|
51
56
|
return obj
|
|
52
57
|
|
|
53
|
-
if isinstance(obj,dict):
|
|
58
|
+
if isinstance(obj, dict):
|
|
54
59
|
for k in obj.keys():
|
|
55
|
-
obj.update({k:cleanup(obj[k])})
|
|
60
|
+
obj.update({k: cleanup(obj[k])})
|
|
56
61
|
return obj
|
|
57
62
|
|
|
58
63
|
if type(obj) in bindings.classes:
|
xaal/lib/config.py
CHANGED
|
@@ -1,53 +1,82 @@
|
|
|
1
1
|
|
|
2
2
|
# Default configuration
|
|
3
|
+
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
5
6
|
import binascii
|
|
6
7
|
from configobj import ConfigObj
|
|
7
8
|
|
|
8
|
-
self = sys.modules[__name__]
|
|
9
|
-
|
|
10
9
|
# Default settings
|
|
11
|
-
DEF_ADDR = '224.0.29.200'
|
|
12
|
-
DEF_PORT = 1236
|
|
13
|
-
DEF_HOPS = 10
|
|
14
|
-
DEF_ALIVE_TIMER = 100
|
|
15
|
-
DEF_CIPHER_WINDOW = 60 * 2
|
|
16
|
-
DEF_QUEUE_SIZE = 10
|
|
17
|
-
DEF_LOG_LEVEL = 'DEBUG'
|
|
18
|
-
DEF_LOG_PATH
|
|
19
|
-
|
|
20
|
-
# TBD : Move this stuff
|
|
10
|
+
DEF_ADDR = '224.0.29.200'
|
|
11
|
+
DEF_PORT = 1236
|
|
12
|
+
DEF_HOPS = 10
|
|
13
|
+
DEF_ALIVE_TIMER = 100
|
|
14
|
+
DEF_CIPHER_WINDOW = 60 * 2
|
|
15
|
+
DEF_QUEUE_SIZE = 10
|
|
16
|
+
DEF_LOG_LEVEL = 'DEBUG'
|
|
17
|
+
DEF_LOG_PATH = '/var/log/xaal'
|
|
18
|
+
|
|
21
19
|
STACK_VERSION = 7
|
|
22
20
|
|
|
21
|
+
class Config:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.conf_dir = os.environ.get('XAAL_CONF_DIR', os.path.expanduser("~/.xaal"))
|
|
24
|
+
self.address = DEF_ADDR
|
|
25
|
+
self.port = DEF_PORT
|
|
26
|
+
self.hops = DEF_HOPS
|
|
27
|
+
self.alive_timer = DEF_ALIVE_TIMER
|
|
28
|
+
self.cipher_window = DEF_CIPHER_WINDOW
|
|
29
|
+
self.queue_size = DEF_QUEUE_SIZE
|
|
30
|
+
self.log_level = DEF_LOG_LEVEL
|
|
31
|
+
self.log_path = DEF_LOG_PATH
|
|
32
|
+
self.key = b''
|
|
33
|
+
self.STACK_VERSION = STACK_VERSION
|
|
34
|
+
|
|
35
|
+
def load(self, name='xaal.ini'):
|
|
36
|
+
filename = os.path.join(self.conf_dir, name)
|
|
37
|
+
if not os.path.isfile(filename):
|
|
38
|
+
raise FileNotFoundError(f"Unable to load xAAL config file [{filename}]")
|
|
39
|
+
|
|
40
|
+
cfg = ConfigObj(filename)
|
|
41
|
+
self.address = self.safe_string(cfg.get('address'), DEF_ADDR)
|
|
42
|
+
self.port = self.safe_int(cfg.get('port'), DEF_PORT)
|
|
43
|
+
self.hops = self.safe_int(cfg.get('hops'), DEF_HOPS)
|
|
44
|
+
self.alive_timer = self.safe_int(cfg.get('alive_timer'), DEF_ALIVE_TIMER)
|
|
45
|
+
self.cipher_window = self.safe_int(cfg.get('cipher_window'), DEF_CIPHER_WINDOW)
|
|
46
|
+
self.queue_size = self.safe_int(cfg.get('queue_size'), DEF_QUEUE_SIZE)
|
|
47
|
+
self.log_level = self.safe_string(cfg.get('log_level'), DEF_LOG_LEVEL)
|
|
48
|
+
self.log_path = self.safe_string(cfg.get('log_path'), DEF_LOG_PATH)
|
|
49
|
+
key = cfg.get('key', None)
|
|
50
|
+
if key and type(key) is str:
|
|
51
|
+
self.key = binascii.unhexlify(key.encode('utf-8'))
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(f"Key not set in config file [{filename}]")
|
|
54
|
+
|
|
55
|
+
## Helper functions
|
|
56
|
+
# Pylint enforce to sanity check the input. In fact, ConfigObj can do the job without issue
|
|
57
|
+
# but Pytlint assume cfg.get can return None (even w/ default set), so it warm about wrong
|
|
58
|
+
# type in all config setting. By doing this I insure that the value is of the right type.
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def safe_int(value, default):
|
|
62
|
+
try:
|
|
63
|
+
return int(value)
|
|
64
|
+
except (ValueError, TypeError):
|
|
65
|
+
return default
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def safe_string(value, default):
|
|
69
|
+
if value is None:
|
|
70
|
+
return default
|
|
71
|
+
try:
|
|
72
|
+
return str(value)
|
|
73
|
+
except (ValueError, TypeError):
|
|
74
|
+
return default
|
|
75
|
+
|
|
23
76
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def load_config(name='xaal.ini'):
|
|
31
|
-
filename = os.path.join(self.conf_dir, name)
|
|
32
|
-
if not os.path.isfile(filename):
|
|
33
|
-
print("Unable to load xAAL config file [%s]" % filename)
|
|
34
|
-
sys.exit(-1)
|
|
35
|
-
|
|
36
|
-
cfg = ConfigObj(filename)
|
|
37
|
-
self.address = cfg.get('address',DEF_ADDR)
|
|
38
|
-
self.port = int(cfg.get('port',DEF_PORT))
|
|
39
|
-
self.hops = int(cfg.get('hops',DEF_HOPS))
|
|
40
|
-
self.alive_timer = int(cfg.get('alive_timer',DEF_ALIVE_TIMER))
|
|
41
|
-
self.cipher_window = int(cfg.get('ciper_window',DEF_CIPHER_WINDOW))
|
|
42
|
-
self.queue_size = int(cfg.get('queue_size',DEF_QUEUE_SIZE))
|
|
43
|
-
self.log_level = cfg.get('log_level',DEF_LOG_LEVEL)
|
|
44
|
-
self.log_path = cfg.get('log_path',DEF_LOG_PATH)
|
|
45
|
-
key = cfg.get('key',None)
|
|
46
|
-
|
|
47
|
-
if key:
|
|
48
|
-
self.key = binascii.unhexlify(key.encode('utf-8'))
|
|
49
|
-
else:
|
|
50
|
-
print("Please set key in config file [%s]" % filename)
|
|
51
|
-
self.key = None
|
|
52
|
-
|
|
53
|
-
load_config()
|
|
77
|
+
config = Config()
|
|
78
|
+
try:
|
|
79
|
+
config.load()
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(e)
|
|
82
|
+
sys.exit(-1)
|
xaal/lib/core.py
CHANGED
|
@@ -18,26 +18,50 @@
|
|
|
18
18
|
# along with xAAL. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
#
|
|
20
20
|
|
|
21
|
-
from .messages import MessageType, MessageAction, MessageFactory, ALIVE_ADDR
|
|
22
|
-
from .exceptions import *
|
|
23
|
-
|
|
24
|
-
import time
|
|
25
21
|
import inspect
|
|
26
|
-
|
|
27
22
|
import logging
|
|
23
|
+
import time
|
|
24
|
+
import typing
|
|
25
|
+
from typing import Any, Awaitable, Optional, List, Callable, Union
|
|
26
|
+
|
|
27
|
+
from .exceptions import EngineError, XAALError
|
|
28
|
+
from .messages import ALIVE_ADDR, MessageAction, MessageFactory, MessageType
|
|
29
|
+
|
|
30
|
+
if typing.TYPE_CHECKING:
|
|
31
|
+
from .devices import Device, Attribute
|
|
32
|
+
from .messages import Message
|
|
33
|
+
|
|
34
|
+
# Function type w/ no argument, and no return (Timer, Hooks)
|
|
35
|
+
FuncT = Union[Callable[[], None], Callable[[], Awaitable[None]]]
|
|
36
|
+
|
|
37
|
+
# Function type w/ message as argument (subscribers), no return
|
|
38
|
+
SubFuncT = Union[Callable[['Message'], None], Callable[['Message'], Awaitable[None]]]
|
|
39
|
+
|
|
28
40
|
logger = logging.getLogger(__name__)
|
|
29
41
|
|
|
30
|
-
class EngineMixin(object):
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
#####################################################
|
|
44
|
+
# Timer class
|
|
45
|
+
#####################################################
|
|
46
|
+
class Timer(object):
|
|
47
|
+
def __init__(self, func: FuncT, period: int, counter: int):
|
|
48
|
+
# Timer function should a Callable[[],None], but it can be a coroutine too
|
|
49
|
+
self.func = func
|
|
50
|
+
self.period = period
|
|
51
|
+
self.counter = counter
|
|
52
|
+
self.deadline = time.time() + period
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class EngineMixin(object):
|
|
56
|
+
__slots__ = ['devices', 'timers', 'subscribers', 'msg_filter', '_attributesChange', 'network', 'msg_factory']
|
|
33
57
|
|
|
34
|
-
def __init__(self,address,port,hops,key):
|
|
35
|
-
self.devices = []
|
|
36
|
-
self.timers = []
|
|
37
|
-
self.subscribers = []
|
|
38
|
-
self.msg_filter = None
|
|
58
|
+
def __init__(self, address: str, port: int, hops: int, key: bytes):
|
|
59
|
+
self.devices:list[Device] = [] # list of devices / use (un)register_devices()
|
|
60
|
+
self.timers: list[Timer] = [] # functions to call periodic
|
|
61
|
+
self.subscribers: list[SubFuncT] = [] # message receive workflow
|
|
62
|
+
self.msg_filter = None # message filter
|
|
39
63
|
|
|
40
|
-
self._attributesChange = []
|
|
64
|
+
self._attributesChange = [] # list of XAALAttributes instances
|
|
41
65
|
|
|
42
66
|
# network connector
|
|
43
67
|
self.network = None
|
|
@@ -49,21 +73,21 @@ class EngineMixin(object):
|
|
|
49
73
|
#####################################################
|
|
50
74
|
# Devices management
|
|
51
75
|
#####################################################
|
|
52
|
-
def add_device(self, dev):
|
|
53
|
-
"""register a new device
|
|
76
|
+
def add_device(self, dev: 'Device'):
|
|
77
|
+
"""register a new device"""
|
|
54
78
|
if dev not in self.devices:
|
|
55
79
|
self.devices.append(dev)
|
|
56
80
|
dev.engine = self
|
|
57
81
|
if self.is_running():
|
|
58
82
|
self.send_alive(dev)
|
|
59
83
|
|
|
60
|
-
def add_devices(self, devs):
|
|
84
|
+
def add_devices(self, devs: List['Device']):
|
|
61
85
|
"""register new devices"""
|
|
62
86
|
for dev in devs:
|
|
63
87
|
self.add_device(dev)
|
|
64
88
|
|
|
65
|
-
def remove_device(self, dev):
|
|
66
|
-
"""unregister a device
|
|
89
|
+
def remove_device(self, dev: 'Device'):
|
|
90
|
+
"""unregister a device"""
|
|
67
91
|
dev.engine = None
|
|
68
92
|
# Remove dev from devices list
|
|
69
93
|
self.devices.remove(dev)
|
|
@@ -72,54 +96,53 @@ class EngineMixin(object):
|
|
|
72
96
|
# xAAL messages Tx handling
|
|
73
97
|
#####################################################
|
|
74
98
|
# Fifo for msg to send
|
|
75
|
-
def queue_msg(self, msg):
|
|
99
|
+
def queue_msg(self, msg: bytes):
|
|
76
100
|
logger.critical("To be implemented queue_msg: %s", msg)
|
|
77
101
|
|
|
78
|
-
def send_request(self,dev,targets,action,body = None):
|
|
102
|
+
def send_request(self, dev: 'Device', targets: list, action: str, body: Optional[dict] = None):
|
|
79
103
|
"""queue a new request"""
|
|
80
104
|
msg = self.msg_factory.build_msg(dev, targets, MessageType.REQUEST, action, body)
|
|
81
105
|
self.queue_msg(msg)
|
|
82
106
|
|
|
83
|
-
def send_reply(self, dev, targets, action, body=None):
|
|
107
|
+
def send_reply(self, dev: 'Device', targets: list, action: str, body: Optional[dict] = None):
|
|
84
108
|
"""queue a new reply"""
|
|
85
109
|
msg = self.msg_factory.build_msg(dev, targets, MessageType.REPLY, action, body)
|
|
86
110
|
self.queue_msg(msg)
|
|
87
111
|
|
|
88
|
-
def send_error(self, dev, errcode, description=None):
|
|
112
|
+
def send_error(self, dev: 'Device', errcode: int, description: Optional[str] = None):
|
|
89
113
|
"""queue a error message"""
|
|
90
114
|
msg = self.msg_factory.build_error_msg(dev, errcode, description)
|
|
91
115
|
self.queue_msg(msg)
|
|
92
116
|
|
|
93
|
-
def send_get_description(self, dev, targets):
|
|
117
|
+
def send_get_description(self, dev: 'Device', targets: list):
|
|
94
118
|
"""queue a get_description request"""
|
|
95
119
|
self.send_request(dev, targets, MessageAction.GET_DESCRIPTION.value)
|
|
96
120
|
|
|
97
|
-
def send_get_attributes(self, dev, targets):
|
|
121
|
+
def send_get_attributes(self, dev: 'Device', targets: list):
|
|
98
122
|
"""queue a get_attributes request"""
|
|
99
123
|
self.send_request(dev, targets, MessageAction.GET_ATTRIBUTES.value)
|
|
100
124
|
|
|
101
|
-
def send_notification(self, dev, action, body=None):
|
|
125
|
+
def send_notification(self, dev: 'Device', action: str, body: Optional[dict] = None):
|
|
102
126
|
"""queue a notificaton"""
|
|
103
|
-
msg = self.msg_factory.build_msg(dev, [], MessageType.NOTIFY, action,body)
|
|
127
|
+
msg = self.msg_factory.build_msg(dev, [], MessageType.NOTIFY, action, body)
|
|
104
128
|
self.queue_msg(msg)
|
|
105
129
|
|
|
106
|
-
def send_alive(self, dev):
|
|
130
|
+
def send_alive(self, dev: 'Device'):
|
|
107
131
|
"""Send a Alive message for a given device"""
|
|
108
132
|
timeout = dev.get_timeout()
|
|
109
133
|
msg = self.msg_factory.build_alive_for(dev, timeout)
|
|
110
134
|
self.queue_msg(msg)
|
|
111
135
|
dev.update_alive()
|
|
112
136
|
|
|
113
|
-
def send_is_alive(self, dev, targets=[ALIVE_ADDR,], dev_types=["any.any",]):
|
|
137
|
+
def send_is_alive(self, dev: 'Device', targets: list = [ALIVE_ADDR,], dev_types: list = ["any.any", ] ):
|
|
114
138
|
"""Send a is_alive message, w/ dev_types filtering"""
|
|
115
139
|
body = {'dev_types': dev_types}
|
|
116
|
-
self.send_request(dev,targets, MessageAction.IS_ALIVE.value, body)
|
|
117
|
-
|
|
140
|
+
self.send_request(dev, targets, MessageAction.IS_ALIVE.value, body)
|
|
118
141
|
|
|
119
142
|
#####################################################
|
|
120
143
|
# Messages filtering
|
|
121
144
|
#####################################################
|
|
122
|
-
def enable_msg_filter(self, func=None):
|
|
145
|
+
def enable_msg_filter(self, func: Optional[Callable[['Message'], bool]] = None):
|
|
123
146
|
"""enable message filter"""
|
|
124
147
|
self.msg_filter = func or self.default_msg_filter
|
|
125
148
|
|
|
@@ -127,7 +150,7 @@ class EngineMixin(object):
|
|
|
127
150
|
"""disable message filter"""
|
|
128
151
|
self.msg_filter = None
|
|
129
152
|
|
|
130
|
-
def default_msg_filter(self, msg):
|
|
153
|
+
def default_msg_filter(self, msg: 'Message'):
|
|
131
154
|
"""
|
|
132
155
|
Filter messages:
|
|
133
156
|
- check if message has alive request address
|
|
@@ -150,17 +173,17 @@ class EngineMixin(object):
|
|
|
150
173
|
"""Periodic sending alive messages"""
|
|
151
174
|
now = time.time()
|
|
152
175
|
for dev in self.devices:
|
|
153
|
-
if dev.next_alive < now
|
|
176
|
+
if dev.next_alive < now:
|
|
154
177
|
self.send_alive(dev)
|
|
155
178
|
|
|
156
179
|
#####################################################
|
|
157
180
|
# xAAL attributes changes
|
|
158
181
|
#####################################################
|
|
159
|
-
def add_attributes_change(self, attr):
|
|
182
|
+
def add_attributes_change(self, attr: 'Attribute'):
|
|
160
183
|
"""add a new attribute change to the list"""
|
|
161
184
|
self._attributesChange.append(attr)
|
|
162
185
|
|
|
163
|
-
def get_attributes_change(self):
|
|
186
|
+
def get_attributes_change(self) -> List['Attribute']:
|
|
164
187
|
"""return the pending attributes changes list"""
|
|
165
188
|
return self._attributesChange
|
|
166
189
|
|
|
@@ -180,17 +203,18 @@ class EngineMixin(object):
|
|
|
180
203
|
#####################################################
|
|
181
204
|
# xAAL messages subscribers
|
|
182
205
|
#####################################################
|
|
183
|
-
def subscribe(self,func):
|
|
206
|
+
def subscribe(self, func: SubFuncT):
|
|
207
|
+
# func should be a Callable[[Message],None], but it can be a coroutine too
|
|
184
208
|
self.subscribers.append(func)
|
|
185
209
|
|
|
186
|
-
def unsubscribe(self,func):
|
|
210
|
+
def unsubscribe(self, func: SubFuncT):
|
|
187
211
|
self.subscribers.remove(func)
|
|
188
212
|
|
|
189
213
|
#####################################################
|
|
190
214
|
# timers
|
|
191
215
|
#####################################################
|
|
192
|
-
def add_timer(self, func, period,counter
|
|
193
|
-
"""
|
|
216
|
+
def add_timer(self, func: FuncT, period: int, counter: int = -1):
|
|
217
|
+
"""
|
|
194
218
|
func: function to call
|
|
195
219
|
period: period in second
|
|
196
220
|
counter: number of repeat, -1 => always
|
|
@@ -201,7 +225,7 @@ class EngineMixin(object):
|
|
|
201
225
|
self.timers.append(t)
|
|
202
226
|
return t
|
|
203
227
|
|
|
204
|
-
def remove_timer(self, timer):
|
|
228
|
+
def remove_timer(self, timer: Timer):
|
|
205
229
|
"""remove a given timer from the list"""
|
|
206
230
|
self.timers.remove(timer)
|
|
207
231
|
|
|
@@ -220,23 +244,15 @@ class EngineMixin(object):
|
|
|
220
244
|
def run(self):
|
|
221
245
|
logger.critical("To be implemented run")
|
|
222
246
|
|
|
223
|
-
def is_running(self):
|
|
247
|
+
def is_running(self) -> bool:
|
|
224
248
|
logger.critical("To be implemented is_running")
|
|
249
|
+
return False
|
|
225
250
|
|
|
226
|
-
#####################################################
|
|
227
|
-
# Timer class
|
|
228
|
-
#####################################################
|
|
229
|
-
class Timer(object):
|
|
230
|
-
def __init__(self, func, period, counter):
|
|
231
|
-
self.func = func
|
|
232
|
-
self.period = period
|
|
233
|
-
self.counter = counter
|
|
234
|
-
self.deadline = time.time() + period
|
|
235
251
|
|
|
236
252
|
#####################################################
|
|
237
253
|
# Usefull functions to Engine developpers
|
|
238
254
|
#####################################################
|
|
239
|
-
def filter_msg_for_devices(msg, devices):
|
|
255
|
+
def filter_msg_for_devices(msg: 'Message', devices: List['Device']) -> List['Device']:
|
|
240
256
|
"""
|
|
241
257
|
loop throught the devices, to find which are expected w/ the msg
|
|
242
258
|
- Filter on dev_types for is_alive broadcast request.
|
|
@@ -245,28 +261,29 @@ def filter_msg_for_devices(msg, devices):
|
|
|
245
261
|
results = []
|
|
246
262
|
if msg.is_request_isalive() and (ALIVE_ADDR in msg.targets):
|
|
247
263
|
# if we receive a broadcast is_alive request, we reply
|
|
248
|
-
# with filtering on dev_tyes.
|
|
264
|
+
# with filtering on dev_tyes.
|
|
249
265
|
if 'dev_types' in msg.body.keys():
|
|
250
266
|
dev_types = msg.body['dev_types']
|
|
251
|
-
if
|
|
267
|
+
if "any.any" in dev_types:
|
|
252
268
|
results = devices
|
|
253
269
|
else:
|
|
254
270
|
for dev in devices:
|
|
255
|
-
any_subtype = dev.dev_type.split(
|
|
271
|
+
any_subtype = dev.dev_type.split(".")[0] + ".any"
|
|
256
272
|
if dev.dev_type in dev_types:
|
|
257
273
|
results.append(dev)
|
|
258
274
|
elif any_subtype in dev_types:
|
|
259
275
|
results.append(dev)
|
|
260
276
|
else:
|
|
261
277
|
# this is a normal request, only filter on device address
|
|
262
|
-
# note: direct is_alive are treated like normal request
|
|
278
|
+
# note: direct is_alive are treated like normal request
|
|
263
279
|
# so dev_types filtering is discarded
|
|
264
280
|
for dev in devices:
|
|
265
281
|
if dev.address in msg.targets:
|
|
266
282
|
results.append(dev)
|
|
267
283
|
return results
|
|
268
284
|
|
|
269
|
-
|
|
285
|
+
|
|
286
|
+
def search_action(msg: 'Message', device: 'Device'):
|
|
270
287
|
"""
|
|
271
288
|
Extract an action (match with methods) from a msg on the device.
|
|
272
289
|
Return:
|
|
@@ -279,6 +296,7 @@ def search_action(msg, device):
|
|
|
279
296
|
params = {}
|
|
280
297
|
result = None
|
|
281
298
|
if msg.action in methods.keys():
|
|
299
|
+
assert msg.action
|
|
282
300
|
method = methods[msg.action]
|
|
283
301
|
body_params = None
|
|
284
302
|
if msg.body:
|
|
@@ -286,23 +304,22 @@ def search_action(msg, device):
|
|
|
286
304
|
body_params = msg.body
|
|
287
305
|
|
|
288
306
|
for k in body_params:
|
|
289
|
-
temp =
|
|
307
|
+
temp = "_%s" % k
|
|
290
308
|
if temp in method_params:
|
|
291
|
-
params.update({temp:body_params[k]})
|
|
309
|
+
params.update({temp: body_params[k]})
|
|
292
310
|
else:
|
|
293
|
-
logger.warning("Wrong method parameter [%s] for action %s" %(k, msg.action))
|
|
294
|
-
result =
|
|
311
|
+
logger.warning("Wrong method parameter [%s] for action %s" % (k, msg.action))
|
|
312
|
+
result = (method, params)
|
|
295
313
|
else:
|
|
296
|
-
raise XAALError("Method %s not found on device %s" % (msg.action,device))
|
|
314
|
+
raise XAALError("Method %s not found on device %s" % (msg.action, device))
|
|
297
315
|
return result
|
|
298
316
|
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
|
|
318
|
+
def get_args_method(method: Any) -> List[str]:
|
|
319
|
+
"""return the list on arguments for a given python method"""
|
|
301
320
|
spec = inspect.getfullargspec(method)
|
|
302
321
|
try:
|
|
303
322
|
spec.args.remove('self')
|
|
304
323
|
except Exception:
|
|
305
324
|
pass
|
|
306
325
|
return spec.args
|
|
307
|
-
|
|
308
|
-
|