xaal.lib 0.7.7__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 +139 -96
- 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.7.dist-info → xaal.lib-0.7.8.dist-info}/METADATA +2 -2
- xaal.lib-0.7.8.dist-info/RECORD +22 -0
- {xaal.lib-0.7.7.dist-info → xaal.lib-0.7.8.dist-info}/WHEEL +1 -1
- xaal.lib-0.7.7.dist-info/RECORD +0 -22
- {xaal.lib-0.7.7.dist-info → xaal.lib-0.7.8.dist-info}/top_level.txt +0 -0
xaal/lib/__init__.py
CHANGED
xaal/lib/aioengine.py
CHANGED
|
@@ -1,45 +1,80 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from . import tools
|
|
6
|
-
from .messages import MessageParserError
|
|
7
|
-
from .aionetwork import AsyncNetworkConnector
|
|
8
|
-
from .exceptions import *
|
|
9
|
-
|
|
2
|
+
import logging
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
10
5
|
import time
|
|
6
|
+
import typing
|
|
11
7
|
from enum import Enum
|
|
8
|
+
from pprint import pprint
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
12
|
import aioconsole
|
|
13
|
-
import signal
|
|
14
|
-
import sys
|
|
15
13
|
from tabulate import tabulate
|
|
16
|
-
from pprint import pprint
|
|
17
14
|
|
|
15
|
+
from .config import config
|
|
16
|
+
from . import core
|
|
17
|
+
from .aionetwork import AsyncNetworkConnector
|
|
18
|
+
from .exceptions import CallbackError, XAALError
|
|
19
|
+
from .messages import MessageParserError
|
|
20
|
+
|
|
21
|
+
if typing.TYPE_CHECKING:
|
|
22
|
+
from .devices import Device
|
|
23
|
+
from .messages import Message
|
|
18
24
|
|
|
19
|
-
import logging
|
|
20
25
|
logger = logging.getLogger(__name__)
|
|
21
26
|
|
|
22
|
-
class AsyncEngine(core.EngineMixin):
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
#####################################################
|
|
29
|
+
# Hooks
|
|
30
|
+
#####################################################
|
|
31
|
+
class HookType(Enum):
|
|
32
|
+
start = 0
|
|
33
|
+
stop = 1
|
|
34
|
+
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
class Hook(object):
|
|
37
|
+
__slots__ = ['type', 'func', 'args', 'kwargs']
|
|
38
|
+
|
|
39
|
+
def __init__(self, type_:HookType, func: core.FuncT, *args, **kwargs):
|
|
40
|
+
# func has to be a callable, but it can be a coroutine or a function
|
|
41
|
+
self.type = type_
|
|
42
|
+
self.func = func
|
|
43
|
+
self.args = args
|
|
44
|
+
self.kwargs = kwargs
|
|
28
45
|
|
|
29
|
-
self.__txFifo = asyncio.Queue() # tx msg fifo
|
|
30
|
-
self._loop = None # event loop
|
|
31
|
-
self._hooks = [] # hooks
|
|
32
|
-
self._tasks = [] # tasks
|
|
33
|
-
self._watchdog_task = None # watchdog task
|
|
34
|
-
self._kill_counter = 0 # watchdog counter
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
class AsyncEngine(core.EngineMixin):
|
|
48
|
+
__slots__ = [
|
|
49
|
+
'__txFifo',
|
|
50
|
+
'_loop',
|
|
51
|
+
'_tasks',
|
|
52
|
+
'_hooks',
|
|
53
|
+
'_watchdog_task',
|
|
54
|
+
'_kill_counter',
|
|
55
|
+
'running_event',
|
|
56
|
+
'watchdog_event',
|
|
57
|
+
'started_event',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self, address: str = config.address, port: int = config.port, hops: int = config.hops, key: bytes = config.key):
|
|
62
|
+
core.EngineMixin.__init__(self, address, port, hops, key)
|
|
63
|
+
|
|
64
|
+
self.__txFifo = asyncio.Queue() # tx msg fifo
|
|
65
|
+
self._loop = None # event loop
|
|
66
|
+
self._hooks = [] # hooks
|
|
67
|
+
self._tasks = [] # tasks
|
|
68
|
+
self._watchdog_task = None # watchdog task
|
|
69
|
+
self._kill_counter = 0 # watchdog counter
|
|
70
|
+
|
|
71
|
+
self.started_event = asyncio.Event() # engine started event
|
|
72
|
+
self.running_event = asyncio.Event() # engine running event
|
|
73
|
+
self.watchdog_event = asyncio.Event() # watchdog event
|
|
39
74
|
|
|
40
75
|
signal.signal(signal.SIGTERM, self.sigkill_handler)
|
|
41
76
|
signal.signal(signal.SIGINT, self.sigkill_handler)
|
|
42
|
-
|
|
77
|
+
|
|
43
78
|
# message receive workflow
|
|
44
79
|
self.subscribe(self.handle_request)
|
|
45
80
|
# start network
|
|
@@ -48,20 +83,20 @@ class AsyncEngine(core.EngineMixin):
|
|
|
48
83
|
#####################################################
|
|
49
84
|
# Hooks
|
|
50
85
|
#####################################################
|
|
51
|
-
def on_start(self,func
|
|
52
|
-
hook = Hook(HookType.start,func
|
|
86
|
+
def on_start(self, func: core.FuncT, *args, **kwargs):
|
|
87
|
+
hook = Hook(HookType.start, func, *args, **kwargs)
|
|
53
88
|
self._hooks.append(hook)
|
|
54
|
-
|
|
55
|
-
def on_stop(self,func
|
|
56
|
-
hook = Hook(HookType.stop,func
|
|
89
|
+
|
|
90
|
+
def on_stop(self, func: core.FuncT, *args, **kwargs):
|
|
91
|
+
hook = Hook(HookType.stop, func, *args, **kwargs)
|
|
57
92
|
self._hooks.append(hook)
|
|
58
93
|
|
|
59
|
-
async def run_hooks(self,hook_type):
|
|
60
|
-
hooks = list(filter(lambda hook: hook.type==hook_type,self._hooks))
|
|
61
|
-
if len(hooks)!=0:
|
|
94
|
+
async def run_hooks(self, hook_type: HookType):
|
|
95
|
+
hooks = list(filter(lambda hook: hook.type == hook_type, self._hooks))
|
|
96
|
+
if len(hooks) != 0:
|
|
62
97
|
logger.debug(f"Launching {hook_type} hooks")
|
|
63
98
|
for h in hooks:
|
|
64
|
-
await run_func(h.func
|
|
99
|
+
await run_func(h.func, *h.args, **h.kwargs)
|
|
65
100
|
|
|
66
101
|
#####################################################
|
|
67
102
|
# timers
|
|
@@ -77,7 +112,7 @@ class AsyncEngine(core.EngineMixin):
|
|
|
77
112
|
except CallbackError as e:
|
|
78
113
|
logger.error(e.description)
|
|
79
114
|
if t.counter != -1:
|
|
80
|
-
t.counter-= 1
|
|
115
|
+
t.counter -= 1
|
|
81
116
|
if t.counter == 0:
|
|
82
117
|
expire_list.append(t)
|
|
83
118
|
t.deadline = now + t.period
|
|
@@ -88,20 +123,20 @@ class AsyncEngine(core.EngineMixin):
|
|
|
88
123
|
#####################################################
|
|
89
124
|
# msg send / receive
|
|
90
125
|
#####################################################
|
|
91
|
-
def queue_msg(self, msg):
|
|
126
|
+
def queue_msg(self, msg: bytes):
|
|
92
127
|
"""queue a message"""
|
|
93
128
|
self.__txFifo.put_nowait(msg)
|
|
94
129
|
|
|
95
|
-
def send_msg(self, msg):
|
|
130
|
+
def send_msg(self, msg: bytes):
|
|
96
131
|
"""Send an encoded message to the bus, use queue_msg instead"""
|
|
97
132
|
self.network.send(msg)
|
|
98
133
|
|
|
99
|
-
async def receive_msg(self):
|
|
134
|
+
async def receive_msg(self) -> Optional['Message']:
|
|
100
135
|
"""return new received message or None"""
|
|
101
136
|
data = await self.network.get_data()
|
|
102
137
|
if data:
|
|
103
138
|
try:
|
|
104
|
-
msg = self.msg_factory.decode_msg(data,self.msg_filter)
|
|
139
|
+
msg = self.msg_factory.decode_msg(data, self.msg_filter)
|
|
105
140
|
except MessageParserError as e:
|
|
106
141
|
logger.warning(e)
|
|
107
142
|
msg = None
|
|
@@ -113,27 +148,31 @@ class AsyncEngine(core.EngineMixin):
|
|
|
113
148
|
msg = await self.receive_msg()
|
|
114
149
|
if msg:
|
|
115
150
|
for func in self.subscribers:
|
|
116
|
-
await run_func(func,msg)
|
|
151
|
+
await run_func(func, msg)
|
|
117
152
|
self.process_attributes_change()
|
|
118
153
|
|
|
119
|
-
def handle_request(self, msg):
|
|
154
|
+
def handle_request(self, msg: 'Message'):
|
|
120
155
|
"""Filter msg for devices according default xAAL API then process the
|
|
121
156
|
request for each targets identied in the engine
|
|
122
157
|
"""
|
|
123
158
|
if not msg.is_request():
|
|
124
159
|
return
|
|
160
|
+
|
|
125
161
|
targets = core.filter_msg_for_devices(msg, self.devices)
|
|
126
162
|
for target in targets:
|
|
127
|
-
if msg.
|
|
163
|
+
if msg.is_request_isalive():
|
|
128
164
|
self.send_alive(target)
|
|
129
165
|
else:
|
|
130
166
|
self.new_task(self.handle_action_request(msg, target))
|
|
131
167
|
|
|
132
|
-
async def handle_action_request(self, msg, target):
|
|
168
|
+
async def handle_action_request(self, msg: 'Message', target: 'Device'):
|
|
169
|
+
if msg.action is None:
|
|
170
|
+
return # should not happen, but pyright need this check
|
|
171
|
+
|
|
133
172
|
try:
|
|
134
173
|
result = await run_action(msg, target)
|
|
135
|
-
if result
|
|
136
|
-
self.send_reply(dev=target,targets=[msg.source],action=msg.action,body=result)
|
|
174
|
+
if result is not None and type(result) is dict:
|
|
175
|
+
self.send_reply(dev=target, targets=[msg.source], action=msg.action, body=result)
|
|
137
176
|
except CallbackError as e:
|
|
138
177
|
self.send_error(target, e.code, e.description)
|
|
139
178
|
except XAALError as e:
|
|
@@ -142,25 +181,25 @@ class AsyncEngine(core.EngineMixin):
|
|
|
142
181
|
#####################################################
|
|
143
182
|
# Asyncio loop & Tasks
|
|
144
183
|
#####################################################
|
|
145
|
-
def get_loop(self):
|
|
146
|
-
if self._loop
|
|
147
|
-
logger.debug(
|
|
184
|
+
def get_loop(self) -> asyncio.AbstractEventLoop:
|
|
185
|
+
if self._loop is None:
|
|
186
|
+
logger.debug("New event loop")
|
|
148
187
|
self._loop = asyncio.get_event_loop()
|
|
149
188
|
return self._loop
|
|
150
189
|
|
|
151
|
-
def new_task(self,coro,name=None):
|
|
190
|
+
def new_task(self, coro: Any, name: Optional[str] = None) -> asyncio.Task:
|
|
152
191
|
# we maintain a task list, to be able to stop/start the engine
|
|
153
192
|
# on demand. needed by HASS
|
|
154
|
-
task = self.get_loop().create_task(coro,name=name)
|
|
193
|
+
task = self.get_loop().create_task(coro, name=name)
|
|
155
194
|
self._tasks.append(task)
|
|
156
195
|
task.add_done_callback(self.task_callback)
|
|
157
196
|
return task
|
|
158
197
|
|
|
159
|
-
def task_callback(self, task):
|
|
198
|
+
def task_callback(self, task: asyncio.Task):
|
|
160
199
|
# called when a task ended
|
|
161
200
|
self._tasks.remove(task)
|
|
162
201
|
|
|
163
|
-
def all_tasks(self):
|
|
202
|
+
def all_tasks(self) -> typing.List[asyncio.Task]:
|
|
164
203
|
return self._tasks
|
|
165
204
|
|
|
166
205
|
async def boot_task(self):
|
|
@@ -194,106 +233,105 @@ class AsyncEngine(core.EngineMixin):
|
|
|
194
233
|
async def watchdog_task(self):
|
|
195
234
|
await self.watchdog_event.wait()
|
|
196
235
|
await self.stop()
|
|
197
|
-
logger.info(
|
|
198
|
-
|
|
236
|
+
logger.info("Exit")
|
|
237
|
+
|
|
199
238
|
#####################################################
|
|
200
239
|
# start / stop / shutdown
|
|
201
240
|
#####################################################
|
|
202
|
-
def is_running(self):
|
|
241
|
+
def is_running(self) -> bool:
|
|
203
242
|
return self.running_event.is_set()
|
|
204
243
|
|
|
205
244
|
def start(self):
|
|
206
245
|
if self.is_running():
|
|
207
|
-
logger.warning(
|
|
246
|
+
logger.warning("Engine already started")
|
|
208
247
|
return
|
|
209
248
|
self.started_event.set()
|
|
210
|
-
self.new_task(self.boot_task(),name='Boot')
|
|
211
|
-
self.new_task(self.receive_task(),name='RecvQ')
|
|
212
|
-
self.new_task(self.send_task(),name='SendQ')
|
|
213
|
-
self.new_task(self.timer_task(),name='Timers')
|
|
214
|
-
self.new_task(console(locals()),name='Console')
|
|
249
|
+
self.new_task(self.boot_task(), name='Boot')
|
|
250
|
+
self.new_task(self.receive_task(), name='RecvQ')
|
|
251
|
+
self.new_task(self.send_task(), name='SendQ')
|
|
252
|
+
self.new_task(self.timer_task(), name='Timers')
|
|
253
|
+
self.new_task(console(locals()), name='Console')
|
|
215
254
|
|
|
216
255
|
def setup_alives_timer(self):
|
|
217
256
|
# needed on stop-start sequence
|
|
218
257
|
if self.process_alives in [t.func for t in self.timers]:
|
|
219
258
|
return
|
|
220
259
|
# process alives every 10 seconds
|
|
221
|
-
self.add_timer(self.process_alives,10)
|
|
260
|
+
self.add_timer(self.process_alives, 10)
|
|
222
261
|
|
|
223
|
-
async def stop(self):
|
|
224
|
-
logger.info(
|
|
262
|
+
async def stop(self): # pyright: ignore
|
|
263
|
+
logger.info("Stopping engine")
|
|
225
264
|
await self.run_hooks(HookType.stop)
|
|
226
265
|
self.running_event.clear()
|
|
227
266
|
self.started_event.clear()
|
|
228
267
|
# cancel all tasks
|
|
229
268
|
for task in self.all_tasks():
|
|
230
|
-
if task!=self._watchdog_task:
|
|
269
|
+
if task != self._watchdog_task:
|
|
231
270
|
task.cancel()
|
|
232
271
|
await asyncio.sleep(0.1)
|
|
233
272
|
|
|
234
|
-
def sigkill_handler(self,signal,frame):
|
|
235
|
-
print("", end
|
|
273
|
+
def sigkill_handler(self, signal, frame):
|
|
274
|
+
print("", end="\r") # remove the uggly ^C
|
|
236
275
|
if not self.is_running():
|
|
237
|
-
logger.warning(
|
|
276
|
+
logger.warning("Engine already stopped")
|
|
238
277
|
self._kill_counter = 1
|
|
239
|
-
self._kill_counter +=1
|
|
278
|
+
self._kill_counter += 1
|
|
240
279
|
self.shutdown()
|
|
241
280
|
if self._kill_counter > 1:
|
|
242
|
-
logger.warning(
|
|
281
|
+
logger.warning("Force quit")
|
|
243
282
|
sys.exit(-1)
|
|
244
283
|
else:
|
|
245
|
-
logger.warning(
|
|
284
|
+
logger.warning("Kill requested")
|
|
246
285
|
|
|
247
286
|
def shutdown(self):
|
|
248
287
|
self.watchdog_event.set()
|
|
249
|
-
|
|
288
|
+
|
|
250
289
|
def run(self):
|
|
251
290
|
if not self.started_event.is_set():
|
|
252
291
|
self.start()
|
|
253
|
-
if self._watchdog_task
|
|
292
|
+
if self._watchdog_task is None:
|
|
254
293
|
# start the watchdog task
|
|
255
|
-
self._watchdog_task = self.new_task(self.watchdog_task(),name=
|
|
294
|
+
self._watchdog_task = self.new_task(self.watchdog_task(), name="Watchdog task")
|
|
256
295
|
self.get_loop().run_until_complete(self._watchdog_task)
|
|
257
296
|
else:
|
|
258
|
-
logger.warning(
|
|
297
|
+
logger.warning("Engine already running")
|
|
259
298
|
|
|
260
299
|
#####################################################
|
|
261
300
|
# Debugging tools
|
|
262
301
|
#####################################################
|
|
263
302
|
def dump_timers(self):
|
|
264
|
-
headers = ['Func','Period','Counter','Deadline']
|
|
303
|
+
headers = ['Func', 'Period', 'Counter', 'Deadline']
|
|
265
304
|
rows = []
|
|
266
305
|
now = time.time()
|
|
267
306
|
for t in self.timers:
|
|
268
|
-
remain = round(t.deadline-now,1)
|
|
269
|
-
rows.append([str(t.func),t.period,t.counter,remain])
|
|
270
|
-
print(
|
|
271
|
-
print(tabulate(rows,headers=headers,tablefmt=
|
|
272
|
-
|
|
307
|
+
remain = round(t.deadline - now, 1)
|
|
308
|
+
rows.append([str(t.func), t.period, t.counter, remain])
|
|
309
|
+
print("= Timers")
|
|
310
|
+
print(tabulate(rows, headers=headers, tablefmt='fancy_grid'))
|
|
273
311
|
|
|
274
312
|
def dump_tasks(self):
|
|
275
|
-
headers = [
|
|
313
|
+
headers = ['Name', 'Coro', 'Loop ID']
|
|
276
314
|
rows = []
|
|
277
315
|
for t in self.all_tasks():
|
|
278
|
-
rows.append([t.get_name(),str(t.get_coro()),id(t.get_loop())])
|
|
279
|
-
print(
|
|
280
|
-
print(tabulate(rows,headers=headers,tablefmt=
|
|
316
|
+
rows.append([t.get_name(), str(t.get_coro()), id(t.get_loop())])
|
|
317
|
+
print("= Tasks")
|
|
318
|
+
print(tabulate(rows, headers=headers, tablefmt='fancy_grid'))
|
|
281
319
|
|
|
282
320
|
def dump_devices(self):
|
|
283
|
-
headers = [
|
|
321
|
+
headers = ['addr', 'dev_type', 'info']
|
|
284
322
|
rows = []
|
|
285
323
|
for d in self.devices:
|
|
286
|
-
rows.append([d.address,d.dev_type,d.info])
|
|
287
|
-
print(
|
|
288
|
-
print(tabulate(rows,headers=headers,tablefmt=
|
|
324
|
+
rows.append([d.address, d.dev_type, d.info])
|
|
325
|
+
print("= Devices")
|
|
326
|
+
print(tabulate(rows, headers=headers, tablefmt='fancy_grid'))
|
|
289
327
|
|
|
290
328
|
def dump_hooks(self):
|
|
291
|
-
headers = [
|
|
329
|
+
headers = ['Type', 'Hook']
|
|
292
330
|
rows = []
|
|
293
331
|
for h in self._hooks:
|
|
294
|
-
rows.append([h.type,str(h.func)])
|
|
295
|
-
print(
|
|
296
|
-
print(tabulate(rows,headers=headers,tablefmt=
|
|
332
|
+
rows.append([h.type, str(h.func)])
|
|
333
|
+
print("= Hooks")
|
|
334
|
+
print(tabulate(rows, headers=headers, tablefmt='fancy_grid'))
|
|
297
335
|
|
|
298
336
|
def dump(self):
|
|
299
337
|
self.dump_devices()
|
|
@@ -301,8 +339,8 @@ class AsyncEngine(core.EngineMixin):
|
|
|
301
339
|
self.dump_timers()
|
|
302
340
|
self.dump_hooks()
|
|
303
341
|
|
|
304
|
-
def get_device(self,uuid):
|
|
305
|
-
|
|
342
|
+
def get_device(self, uuid: UUID) -> Optional['Device']:
|
|
343
|
+
# TODO:Check if this method is usefull
|
|
306
344
|
for dev in self.devices:
|
|
307
345
|
if dev.address == uuid:
|
|
308
346
|
return dev
|
|
@@ -312,26 +350,26 @@ class AsyncEngine(core.EngineMixin):
|
|
|
312
350
|
#####################################################
|
|
313
351
|
# Utilities functions
|
|
314
352
|
#####################################################
|
|
315
|
-
async def run_func(func
|
|
316
|
-
"""run a function or a coroutine function
|
|
353
|
+
async def run_func(func, *args, **kwargs):
|
|
354
|
+
"""run a function or a coroutine function"""
|
|
317
355
|
if asyncio.iscoroutinefunction(func):
|
|
318
|
-
return await func(*args
|
|
356
|
+
return await func(*args, **kwargs)
|
|
319
357
|
else:
|
|
320
|
-
return func(*args
|
|
358
|
+
return func(*args, **kwargs)
|
|
321
359
|
|
|
322
360
|
|
|
323
|
-
async def run_action(msg,device):
|
|
324
|
-
"""
|
|
361
|
+
async def run_action(msg: 'Message', device: 'Device'):
|
|
362
|
+
"""
|
|
325
363
|
Extract an action & launch it
|
|
326
364
|
Return:
|
|
327
365
|
- action result
|
|
328
366
|
- None if no result
|
|
329
367
|
|
|
330
|
-
Notes:
|
|
368
|
+
Notes:
|
|
331
369
|
- If an exception raised, it's logged, and raise an XAALError.
|
|
332
370
|
- Same API as legacy Engine, but accept coroutine functions
|
|
333
371
|
"""
|
|
334
|
-
method,params = core.search_action(msg,device)
|
|
372
|
+
method, params = core.search_action(msg, device)
|
|
335
373
|
result = None
|
|
336
374
|
try:
|
|
337
375
|
if asyncio.iscoroutinefunction(method):
|
|
@@ -340,49 +378,35 @@ async def run_action(msg,device):
|
|
|
340
378
|
result = method(**params)
|
|
341
379
|
except Exception as e:
|
|
342
380
|
logger.error(e)
|
|
343
|
-
raise XAALError("Error in method:%s params:%s" % (msg.action,params))
|
|
381
|
+
raise XAALError("Error in method:%s params:%s" % (msg.action, params))
|
|
344
382
|
return result
|
|
345
383
|
|
|
346
384
|
|
|
347
|
-
#####################################################
|
|
348
|
-
# Hooks
|
|
349
|
-
#####################################################
|
|
350
|
-
class HookType(Enum):
|
|
351
|
-
start = 0
|
|
352
|
-
stop = 1
|
|
353
|
-
|
|
354
|
-
class Hook(object):
|
|
355
|
-
__slots__ = ['type','func','args','kwargs']
|
|
356
|
-
def __init__(self,type_,func,*args,**kwargs):
|
|
357
|
-
self.type = type_
|
|
358
|
-
self.func = func
|
|
359
|
-
self.args = args
|
|
360
|
-
self.kwargs = kwargs
|
|
361
|
-
|
|
362
|
-
|
|
363
385
|
#####################################################
|
|
364
386
|
# Debugging console
|
|
365
387
|
#####################################################
|
|
366
|
-
async def console(locals=locals(),port=None):
|
|
388
|
+
async def console(locals=locals(), port: Optional[int] = None):
|
|
367
389
|
"""launch a console to enable remote engine inspection"""
|
|
368
|
-
if port
|
|
390
|
+
if port is None:
|
|
369
391
|
# let's find a free port if not specified
|
|
370
392
|
def find_free_port():
|
|
371
393
|
import socketserver
|
|
372
|
-
with socketserver.TCPServer((
|
|
394
|
+
with socketserver.TCPServer(('localhost', 0), None) as s: # pyright: ignore pyright reject the None here
|
|
373
395
|
return s.server_address[1]
|
|
396
|
+
|
|
374
397
|
port = find_free_port()
|
|
375
|
-
|
|
376
|
-
logger.debug(f
|
|
377
|
-
sys.ps1 =
|
|
378
|
-
banner =
|
|
379
|
-
locals.update({'pprint':pprint})
|
|
398
|
+
|
|
399
|
+
logger.debug(f"starting debug console on port {port}")
|
|
400
|
+
sys.ps1 = "[xAAL] >>> "
|
|
401
|
+
banner = "=" * 78 + "\nxAAL remote console\n" + "=" * 78
|
|
402
|
+
locals.update({'pprint': pprint})
|
|
380
403
|
|
|
381
404
|
def factory(streams):
|
|
382
405
|
return aioconsole.AsynchronousConsole(locals=locals, streams=streams)
|
|
406
|
+
|
|
383
407
|
# start the console
|
|
384
408
|
try:
|
|
385
409
|
# debian with ipv6 disabled still state that localhost is ::1, which broke aioconsole
|
|
386
|
-
await aioconsole.start_interactive_server(host=
|
|
410
|
+
await aioconsole.start_interactive_server(host="127.0.0.1", port=port, factory=factory, banner=banner) # pyright: ignore
|
|
387
411
|
except OSError:
|
|
388
|
-
logger.warning(
|
|
412
|
+
logger.warning("Unable to run console")
|
xaal/lib/aiohelpers.py
CHANGED
|
@@ -4,8 +4,8 @@ import asyncio
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
@decorator
|
|
7
|
-
def spawn(func
|
|
8
|
-
return asyncio.get_event_loop().run_in_executor(None,func
|
|
7
|
+
def spawn(func, *args, **kwargs):
|
|
8
|
+
return asyncio.get_event_loop().run_in_executor(None, func, *args, **kwargs)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def static_vars(**kwargs_):
|
|
@@ -16,20 +16,20 @@ def static_vars(**kwargs_):
|
|
|
16
16
|
return decorate
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def run_async_package(pkg_name,pkg_setup,console_log
|
|
19
|
+
def run_async_package(pkg_name, pkg_setup, console_log=True, file_log=False):
|
|
20
20
|
if console_log:
|
|
21
21
|
set_console_title(pkg_name)
|
|
22
22
|
setup_console_logger()
|
|
23
23
|
if file_log:
|
|
24
24
|
setup_file_logger(pkg_name)
|
|
25
|
-
|
|
26
|
-
from .aioengine import AsyncEngine
|
|
25
|
+
|
|
26
|
+
from .aioengine import AsyncEngine
|
|
27
27
|
eng = AsyncEngine()
|
|
28
28
|
eng.start()
|
|
29
29
|
logger = logging.getLogger(pkg_name)
|
|
30
|
-
logger.info(
|
|
30
|
+
logger.info("starting xaal package: %s"% pkg_name )
|
|
31
31
|
result = pkg_setup(eng)
|
|
32
|
-
if result
|
|
32
|
+
if result is not True:
|
|
33
33
|
logger.critical("something goes wrong with package: %s" % pkg_name)
|
|
34
34
|
try:
|
|
35
35
|
eng.run()
|
xaal/lib/aionetwork.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import struct
|
|
3
|
-
import socket
|
|
4
|
-
|
|
5
2
|
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import struct
|
|
6
5
|
|
|
7
6
|
logger = logging.getLogger(__name__)
|
|
8
7
|
|
|
9
|
-
class AsyncNetworkConnector(object):
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
class AsyncNetworkConnector(object):
|
|
10
|
+
def __init__(self, addr: str, port: int, hops: int, bind_addr="0.0.0.0"):
|
|
12
11
|
self.addr = addr
|
|
13
12
|
self.port = port
|
|
14
13
|
self.hops = hops
|
|
@@ -18,57 +17,58 @@ class AsyncNetworkConnector(object):
|
|
|
18
17
|
async def connect(self):
|
|
19
18
|
loop = asyncio.get_running_loop()
|
|
20
19
|
on_con_lost = loop.create_future()
|
|
21
|
-
self.transport, self.protocol = await loop.create_datagram_endpoint(
|
|
22
|
-
lambda: XAALServerProtocol(on_con_lost,self.receive), sock
|
|
20
|
+
self.transport, self.protocol = await loop.create_datagram_endpoint(
|
|
21
|
+
lambda: XAALServerProtocol(on_con_lost, self.receive), sock=self.new_sock()
|
|
22
|
+
)
|
|
23
23
|
# In some conditions (containers), transport is connected but IGMP is delayed (up to 10ms)
|
|
24
24
|
# so we need to wait for IGMP to be really sent.
|
|
25
25
|
await asyncio.sleep(0.05)
|
|
26
26
|
|
|
27
27
|
def new_sock(self):
|
|
28
|
-
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
|
|
28
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
29
29
|
try:
|
|
30
30
|
# Linux + MacOS + BSD
|
|
31
31
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
32
|
-
except:
|
|
32
|
+
except Exception:
|
|
33
33
|
# Windows
|
|
34
34
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
35
35
|
sock.bind((self.bind_addr, self.port))
|
|
36
|
-
mreq = struct.pack("=4s4s",socket.inet_aton(self.addr),socket.inet_aton(self.bind_addr))
|
|
37
|
-
sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq)
|
|
38
|
-
sock.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_TTL,10)
|
|
36
|
+
mreq = struct.pack("=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr))
|
|
37
|
+
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
38
|
+
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 10)
|
|
39
39
|
sock.setblocking(False)
|
|
40
40
|
return sock
|
|
41
41
|
|
|
42
|
-
def send(self,data):
|
|
43
|
-
self.protocol.datagram_send(data,self.addr,self.port)
|
|
42
|
+
def send(self, data: bytes):
|
|
43
|
+
self.protocol.datagram_send(data, self.addr, self.port)
|
|
44
44
|
|
|
45
|
-
def receive(self,data):
|
|
45
|
+
def receive(self, data: bytes):
|
|
46
46
|
self._rx_queue.put_nowait(data)
|
|
47
47
|
|
|
48
|
-
async def get_data(self):
|
|
48
|
+
async def get_data(self) -> bytes:
|
|
49
49
|
return await self._rx_queue.get()
|
|
50
50
|
|
|
51
|
+
|
|
51
52
|
class XAALServerProtocol(asyncio.Protocol):
|
|
52
|
-
def __init__(self,on_con_lost,on_dtg_recv):
|
|
53
|
+
def __init__(self, on_con_lost, on_dtg_recv):
|
|
53
54
|
self.on_con_lost = on_con_lost
|
|
54
55
|
self.on_dtg_recv = on_dtg_recv
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
def connection_made(self, transport):
|
|
57
58
|
logger.info("xAAL network connected")
|
|
58
59
|
self.transport = transport
|
|
59
60
|
|
|
60
61
|
def error_received(self, exc):
|
|
61
|
-
print(
|
|
62
|
+
print("Error received:", exc)
|
|
62
63
|
logger.warning(f"Error received: {exc}")
|
|
63
64
|
|
|
64
65
|
def connection_lost(self, exc):
|
|
65
66
|
logger.info(f"Connexion closed: {exc}")
|
|
66
67
|
self.on_con_lost.set_result(True)
|
|
67
|
-
|
|
68
|
-
def datagram_send(self,data,ip,port):
|
|
69
|
-
self.transport.sendto(data,(ip,port))
|
|
68
|
+
|
|
69
|
+
def datagram_send(self, data, ip, port):
|
|
70
|
+
self.transport.sendto(data, (ip, port))
|
|
70
71
|
|
|
71
72
|
def datagram_received(self, data, addr):
|
|
72
|
-
#print(f"pkt from {addr}")
|
|
73
|
+
# print(f"pkt from {addr}")
|
|
73
74
|
self.on_dtg_recv(data)
|
|
74
|
-
|