micrOSDevToolKit 2.9.1__py3-none-any.whl → 2.9.6__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.
Potentially problematic release.
This version of micrOSDevToolKit might be problematic. Click here for more details.
- micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +19 -19
- micrOS/source/Common.py +1 -1
- micrOS/source/Config.py +6 -6
- micrOS/source/LM_pacman.py +248 -0
- micrOS/source/LM_rest.py +0 -1
- micrOS/source/LM_robustness.py +2 -2
- micrOS/source/LM_system.py +4 -16
- micrOS/source/LM_telegram.py +95 -61
- micrOS/source/Logger.py +51 -17
- micrOS/source/Notify.py +4 -3
- micrOS/source/Server.py +33 -39
- micrOS/source/Shell.py +72 -74
- micrOS/source/Web.py +22 -23
- micrOS/source/microIO.py +1 -2
- micrOS/source/reset.py +5 -1
- micrOS/source/urequests.py +12 -4
- {micrOSDevToolKit-2.9.1.data → micrOSDevToolKit-2.9.6.data}/scripts/devToolKit.py +3 -2
- {micrOSDevToolKit-2.9.1.dist-info → micrOSDevToolKit-2.9.6.dist-info}/METADATA +1 -1
- {micrOSDevToolKit-2.9.1.dist-info → micrOSDevToolKit-2.9.6.dist-info}/RECORD +57 -54
- toolkit/Gateway.py +2 -2
- toolkit/dashboard_apps/SystemTest.py +14 -12
- toolkit/index.html +4 -4
- toolkit/lib/TerminalColors.py +4 -0
- toolkit/lib/macroScript.py +6 -0
- toolkit/lib/micrOSClient.py +48 -21
- toolkit/lib/micrOSClientHistory.py +122 -0
- toolkit/micrOSlint.py +1 -1
- toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
- toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
- toolkit/simulator_lib/machine.py +4 -0
- toolkit/simulator_lib/node_config.json +1 -1
- toolkit/simulator_lib/uasyncio.py +7 -1
- toolkit/simulator_lib/uos.py +138 -0
- toolkit/socketClient.py +4 -4
- toolkit/workspace/precompiled/Common.mpy +0 -0
- toolkit/workspace/precompiled/Config.mpy +0 -0
- toolkit/workspace/precompiled/Espnow.mpy +0 -0
- toolkit/workspace/precompiled/LM_pacman.py +248 -0
- toolkit/workspace/precompiled/LM_rest.mpy +0 -0
- toolkit/workspace/precompiled/LM_robustness.py +2 -2
- toolkit/workspace/precompiled/LM_system.mpy +0 -0
- toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
- toolkit/workspace/precompiled/Logger.mpy +0 -0
- toolkit/workspace/precompiled/Notify.mpy +0 -0
- toolkit/workspace/precompiled/Server.mpy +0 -0
- toolkit/workspace/precompiled/Shell.mpy +0 -0
- toolkit/workspace/precompiled/Web.mpy +0 -0
- toolkit/workspace/precompiled/_mpy.version +1 -1
- toolkit/workspace/precompiled/microIO.mpy +0 -0
- toolkit/workspace/precompiled/node_config.json +1 -1
- toolkit/workspace/precompiled/reset.mpy +0 -0
- toolkit/workspace/precompiled/urequests.mpy +0 -0
- micrOS/source/LM_lmpacman.py +0 -126
- toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → micrOSDevToolKit-2.9.6.dist-info}/LICENSE +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → micrOSDevToolKit-2.9.6.dist-info}/WHEEL +0 -0
- {micrOSDevToolKit-2.9.1.dist-info → micrOSDevToolKit-2.9.6.dist-info}/top_level.txt +0 -0
micrOS/source/LM_telegram.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from sys import modules
|
|
2
|
-
import uasyncio as asyncio
|
|
3
2
|
import urequests
|
|
4
3
|
from Notify import Notify
|
|
5
4
|
from Config import cfgget
|
|
@@ -15,6 +14,10 @@ class Telegram(Notify):
|
|
|
15
14
|
_API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
|
|
16
15
|
_IN_MSG_ID = None
|
|
17
16
|
|
|
17
|
+
def __init__(self):
|
|
18
|
+
# Subscribe to the notification system - provide send_msg method (over self)
|
|
19
|
+
super().add_subscriber(self)
|
|
20
|
+
|
|
18
21
|
@staticmethod
|
|
19
22
|
def __id_cache(mode):
|
|
20
23
|
"""
|
|
@@ -97,36 +100,60 @@ class Telegram(Notify):
|
|
|
97
100
|
verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
|
|
98
101
|
return verdict
|
|
99
102
|
|
|
103
|
+
@staticmethod
|
|
104
|
+
def __update_chat_ids(resp_json):
|
|
105
|
+
"""
|
|
106
|
+
Update known chat_id-s and cache them
|
|
107
|
+
- return active chat_id frm resp_json
|
|
108
|
+
"""
|
|
109
|
+
console_write("[NTFY GET] update chatIDs")
|
|
110
|
+
_cid = None
|
|
111
|
+
if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
|
|
112
|
+
_cid = resp_json["result"][-1]["message"]["chat"]["id"]
|
|
113
|
+
# LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
|
|
114
|
+
if len(Telegram._CHAT_IDS) < 4:
|
|
115
|
+
_ids = len(Telegram._CHAT_IDS)
|
|
116
|
+
Telegram._CHAT_IDS.add(_cid)
|
|
117
|
+
if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
|
|
118
|
+
Telegram.__id_cache('s')
|
|
119
|
+
else:
|
|
120
|
+
Telegram.__id_cache('r')
|
|
121
|
+
if len(Telegram._CHAT_IDS) == 0:
|
|
122
|
+
error_message = resp_json.get("description", "Unknown error")
|
|
123
|
+
raise Exception(f"Error retrieving chat ID: {error_message}")
|
|
124
|
+
return _cid
|
|
125
|
+
|
|
100
126
|
@staticmethod
|
|
101
127
|
def get_msg():
|
|
102
128
|
"""
|
|
103
129
|
Get the last message from the Telegram chat.
|
|
104
130
|
RETURN None when telegram bot token is missing
|
|
105
131
|
"""
|
|
132
|
+
console_write("[NTFY] GET MESSAGE")
|
|
133
|
+
bot_token = Telegram.__bot_token()
|
|
134
|
+
if bot_token is None:
|
|
135
|
+
return None
|
|
136
|
+
response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
|
|
137
|
+
url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
|
|
138
|
+
console_write(f"\t1/2[GET] request: {url}")
|
|
106
139
|
|
|
107
|
-
|
|
108
|
-
"""
|
|
109
|
-
Update known chat_id-s and cache them
|
|
110
|
-
- return active chat_id frm resp_json
|
|
111
|
-
"""
|
|
112
|
-
console_write("[NTFY GET] update chatIDs")
|
|
113
|
-
_cid = None
|
|
114
|
-
if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
|
|
115
|
-
_cid = resp_json["result"][-1]["message"]["chat"]["id"]
|
|
116
|
-
# LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
|
|
117
|
-
if len(Telegram._CHAT_IDS) < 4:
|
|
118
|
-
_ids = len(Telegram._CHAT_IDS)
|
|
119
|
-
Telegram._CHAT_IDS.add(_cid)
|
|
120
|
-
if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
|
|
121
|
-
Telegram.__id_cache('s')
|
|
122
|
-
else:
|
|
123
|
-
Telegram.__id_cache('r')
|
|
124
|
-
if len(Telegram._CHAT_IDS) == 0:
|
|
125
|
-
error_message = resp_json.get("description", "Unknown error")
|
|
126
|
-
raise Exception(f"Error retrieving chat ID: {error_message}")
|
|
127
|
-
return _cid
|
|
140
|
+
_, resp_json = urequests.get(url, jsonify=True, sock_size=128)
|
|
128
141
|
|
|
129
|
-
|
|
142
|
+
if len(resp_json["result"]) > 0:
|
|
143
|
+
response['c_id'] = Telegram.__update_chat_ids(resp_json)
|
|
144
|
+
resp = resp_json["result"][-1]["message"]
|
|
145
|
+
response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get(
|
|
146
|
+
'username', None) is None else resp['chat']['username']
|
|
147
|
+
response['text'], response['m_id'] = resp['text'], resp['message_id']
|
|
148
|
+
console_write(f"\t2/2[GET] response: {response}")
|
|
149
|
+
return response
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
async def aget_msg():
|
|
153
|
+
"""
|
|
154
|
+
Async: Get the last message from the Telegram chat.
|
|
155
|
+
RETURN None when telegram bot token is missing
|
|
156
|
+
"""
|
|
130
157
|
console_write("[NTFY] GET MESSAGE")
|
|
131
158
|
bot_token = Telegram.__bot_token()
|
|
132
159
|
if bot_token is None:
|
|
@@ -134,9 +161,11 @@ class Telegram(Notify):
|
|
|
134
161
|
response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
|
|
135
162
|
url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
|
|
136
163
|
console_write(f"\t1/2[GET] request: {url}")
|
|
137
|
-
|
|
164
|
+
|
|
165
|
+
_, resp_json = await urequests.aget(url, jsonify=True, sock_size=128)
|
|
166
|
+
|
|
138
167
|
if len(resp_json["result"]) > 0:
|
|
139
|
-
response['c_id'] =
|
|
168
|
+
response['c_id'] = Telegram.__update_chat_ids(resp_json)
|
|
140
169
|
resp = resp_json["result"][-1]["message"]
|
|
141
170
|
response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get(
|
|
142
171
|
'username', None) is None else resp['chat']['username']
|
|
@@ -145,16 +174,13 @@ class Telegram(Notify):
|
|
|
145
174
|
return response
|
|
146
175
|
|
|
147
176
|
@staticmethod
|
|
148
|
-
def receive_eval():
|
|
177
|
+
async def receive_eval():
|
|
149
178
|
"""
|
|
150
179
|
READ - VALIDATE - EXECUTE - REPLY LOOP
|
|
151
180
|
- can be used in async loop
|
|
152
181
|
RETURN None when telegram bot token is missing
|
|
153
182
|
"""
|
|
154
|
-
|
|
155
|
-
console_write("[NTFY] REC&EVAL sequence")
|
|
156
|
-
|
|
157
|
-
# Return data structure template
|
|
183
|
+
console_write("[NTFY] EVAL sequence")
|
|
158
184
|
verdict = None
|
|
159
185
|
|
|
160
186
|
def lm_execute(cmd_args):
|
|
@@ -168,8 +194,8 @@ class Telegram(Notify):
|
|
|
168
194
|
Telegram._IN_MSG_ID = m_id
|
|
169
195
|
|
|
170
196
|
# -------------------------- FUNCTION MAIN -------------------------- #
|
|
171
|
-
# Poll telegram chat
|
|
172
|
-
data = Telegram.
|
|
197
|
+
# Async Poll telegram chat
|
|
198
|
+
data = await Telegram.aget_msg()
|
|
173
199
|
if data is None:
|
|
174
200
|
return data
|
|
175
201
|
# Get msg, msg_id, chat_id as main input data source
|
|
@@ -197,12 +223,42 @@ class Telegram(Notify):
|
|
|
197
223
|
verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
|
|
198
224
|
else:
|
|
199
225
|
verdict = Telegram.notifications()
|
|
226
|
+
# Send is still synchronous (OK)
|
|
200
227
|
Telegram.send_msg(verdict, reply_to=m_id)
|
|
201
228
|
else:
|
|
202
229
|
verdict = "[UP] NoExec"
|
|
203
|
-
console_write(f"\
|
|
230
|
+
console_write(f"\tBOT: {verdict}")
|
|
204
231
|
return verdict
|
|
205
232
|
|
|
233
|
+
@staticmethod
|
|
234
|
+
async def bot_repl(tag, period=3):
|
|
235
|
+
"""
|
|
236
|
+
BOT - ReceiveEvalPrintLoop
|
|
237
|
+
:param tag: task tag (access)
|
|
238
|
+
:param period: polling period in sec, default: 3
|
|
239
|
+
"""
|
|
240
|
+
cancel_cnt = 0
|
|
241
|
+
period = period if period > 0 else 1
|
|
242
|
+
period_ms = period * 1000
|
|
243
|
+
with micro_task(tag=tag) as my_task:
|
|
244
|
+
my_task.out = "[UP] Running"
|
|
245
|
+
while True:
|
|
246
|
+
# Normal task period
|
|
247
|
+
await my_task.feed(sleep_ms=period_ms)
|
|
248
|
+
try:
|
|
249
|
+
v = await Telegram.receive_eval()
|
|
250
|
+
my_task.out = "Missing bot token" if v is None else f"{v} ({period}s)"
|
|
251
|
+
cancel_cnt = 0
|
|
252
|
+
except Exception as e:
|
|
253
|
+
my_task.out = str(e)
|
|
254
|
+
# Auto scale - blocking nature - in case of serial failures (5) - hibernate task (increase async sleep)
|
|
255
|
+
cancel_cnt += 1
|
|
256
|
+
if cancel_cnt > 5:
|
|
257
|
+
my_task.out = f"[DOWN] {e} (wait 1min)"
|
|
258
|
+
cancel_cnt = 5
|
|
259
|
+
# SLOW DOWN - hibernate task
|
|
260
|
+
await my_task.feed(sleep_ms=60_000)
|
|
261
|
+
|
|
206
262
|
@staticmethod
|
|
207
263
|
def set_commands():
|
|
208
264
|
"""
|
|
@@ -237,7 +293,6 @@ def __init():
|
|
|
237
293
|
_sta_available = True if ifconfig()[0] == "STA" else False
|
|
238
294
|
if _sta_available:
|
|
239
295
|
TELEGRAM_OBJ = Telegram()
|
|
240
|
-
Notify.add_subscriber(TELEGRAM_OBJ)
|
|
241
296
|
else:
|
|
242
297
|
syslog("No STA: cannot init telegram")
|
|
243
298
|
|
|
@@ -292,37 +347,16 @@ def receive():
|
|
|
292
347
|
return "Missing telegram bot token" if verdict is None else verdict
|
|
293
348
|
|
|
294
349
|
|
|
295
|
-
|
|
296
|
-
cancel_cnt = 0
|
|
297
|
-
with micro_task(tag='telegram._loop') as my_task:
|
|
298
|
-
my_task.out = "[UP] Running"
|
|
299
|
-
while True:
|
|
300
|
-
# Normal task period
|
|
301
|
-
await asyncio.sleep(5)
|
|
302
|
-
try:
|
|
303
|
-
v = TELEGRAM_OBJ.receive_eval()
|
|
304
|
-
my_task.out = "Missing bot token" if v is None else v
|
|
305
|
-
cancel_cnt = 0
|
|
306
|
-
except Exception as e:
|
|
307
|
-
my_task.out = str(e)
|
|
308
|
-
# Auto scale - blocking nature - in case of serial failures (5) - hibernate task (increase async sleep)
|
|
309
|
-
cancel_cnt += 1
|
|
310
|
-
if cancel_cnt > 5:
|
|
311
|
-
my_task.out = f"[DOWN] {e}"
|
|
312
|
-
cancel_cnt = 5
|
|
313
|
-
# SLOW DOWN - hibernate task
|
|
314
|
-
asyncio.sleep(115)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def receiver_loop():
|
|
350
|
+
def receiver_loop(period=3):
|
|
318
351
|
"""
|
|
319
|
-
Telegram
|
|
352
|
+
Telegram BOT (repl) - ReadEvalPrintLoop for Load Modules
|
|
320
353
|
- Only executes module (function) if the module is already loaded
|
|
321
|
-
|
|
354
|
+
:param period: polling period in sec, default: 3
|
|
322
355
|
"""
|
|
323
356
|
if TELEGRAM_OBJ is None:
|
|
324
357
|
return "Network unavailable."
|
|
325
|
-
|
|
358
|
+
tag = 'telegram.bot_repl'
|
|
359
|
+
state = micro_task(tag=tag, task=TELEGRAM_OBJ.bot_repl(tag=tag, period=period))
|
|
326
360
|
return "Starting" if state else "Already running"
|
|
327
361
|
|
|
328
362
|
|
|
@@ -335,6 +369,6 @@ def help(widgets=False):
|
|
|
335
369
|
"""
|
|
336
370
|
return ('send "text"',
|
|
337
371
|
'receive',
|
|
338
|
-
'receiver_loop',
|
|
372
|
+
'receiver_loop period=3',
|
|
339
373
|
'notify "message"',
|
|
340
374
|
'load', 'INFO: Send & Receive messages with Telegram bot')
|
micrOS/source/Logger.py
CHANGED
|
@@ -5,11 +5,34 @@ Module is responsible for System and User logging
|
|
|
5
5
|
Designed by Marcell Ban aka BxNxM
|
|
6
6
|
"""
|
|
7
7
|
from time import localtime
|
|
8
|
-
from
|
|
8
|
+
from re import match
|
|
9
|
+
from uos import listdir, remove, stat, mkdir, getcwd
|
|
9
10
|
|
|
10
11
|
#############################################
|
|
11
12
|
# LOGGING WITH DATA ROTATION #
|
|
12
13
|
#############################################
|
|
14
|
+
LOG_FOLDER = None
|
|
15
|
+
|
|
16
|
+
def _init_logger():
|
|
17
|
+
""" Init /logs folder """
|
|
18
|
+
global LOG_FOLDER
|
|
19
|
+
if LOG_FOLDER is None:
|
|
20
|
+
LOG_FOLDER = f"{getcwd()}logs"
|
|
21
|
+
do_create = True
|
|
22
|
+
try:
|
|
23
|
+
if stat(LOG_FOLDER)[0] & 0x4000:
|
|
24
|
+
# Dir exists - skip create
|
|
25
|
+
do_create = False
|
|
26
|
+
except:
|
|
27
|
+
pass
|
|
28
|
+
if do_create:
|
|
29
|
+
try:
|
|
30
|
+
mkdir(LOG_FOLDER)
|
|
31
|
+
syslog(f"[BOOT] log dir {LOG_FOLDER} init")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
LOG_FOLDER = getcwd()
|
|
34
|
+
syslog(f"[BOOT] log dir {LOG_FOLDER} fallback: {e}")
|
|
35
|
+
return LOG_FOLDER
|
|
13
36
|
|
|
14
37
|
|
|
15
38
|
def logger(data, f_name, limit):
|
|
@@ -23,14 +46,15 @@ def logger(data, f_name, limit):
|
|
|
23
46
|
return write verdict - true / false
|
|
24
47
|
INFO: hardcoded max data number = 30
|
|
25
48
|
"""
|
|
26
|
-
def _logger(
|
|
27
|
-
|
|
49
|
+
def _logger(f_mode='r+'):
|
|
50
|
+
nonlocal data, f_name, limit
|
|
51
|
+
limit = min(limit, 30) # Hardcoded max data line = 30
|
|
28
52
|
# [1] GET TIME STUMP
|
|
29
53
|
ts_buff = [str(k) for k in localtime()]
|
|
30
54
|
ts = ".".join(ts_buff[0:3]) + "-" + ":".join(ts_buff[3:6])
|
|
31
55
|
# [2] OPEN FILE - WRITE DATA WITH TS
|
|
32
|
-
with open(
|
|
33
|
-
_data = f"{ts} {
|
|
56
|
+
with open(f_name, f_mode) as f:
|
|
57
|
+
_data = f"{ts} {data}\n"
|
|
34
58
|
# read file lines and filter by time stump chunks (hack for replace truncate)
|
|
35
59
|
lines = [_l for _l in f.readlines() if '-' in _l and '.' in _l]
|
|
36
60
|
# get file params
|
|
@@ -38,22 +62,20 @@ def logger(data, f_name, limit):
|
|
|
38
62
|
lines.append(_data)
|
|
39
63
|
f.seek(0)
|
|
40
64
|
# line data rotate
|
|
41
|
-
if lines_len >=
|
|
42
|
-
lines = lines[-
|
|
43
|
-
lines_str = ''.join(lines)
|
|
44
|
-
else:
|
|
45
|
-
lines_str = ''.join(lines)
|
|
65
|
+
if lines_len >= limit:
|
|
66
|
+
lines = lines[-limit:]
|
|
46
67
|
# write file
|
|
47
|
-
f.write(
|
|
68
|
+
f.write(''.join(lines))
|
|
48
69
|
|
|
70
|
+
f_name = f"{LOG_FOLDER}/{f_name}"
|
|
49
71
|
# Run logger
|
|
50
72
|
try:
|
|
51
73
|
# There is file - append 'r+'
|
|
52
|
-
_logger(
|
|
74
|
+
_logger()
|
|
53
75
|
except:
|
|
54
76
|
try:
|
|
55
77
|
# There is no file - create 'a+'
|
|
56
|
-
_logger(
|
|
78
|
+
_logger('a+')
|
|
57
79
|
except:
|
|
58
80
|
return False
|
|
59
81
|
return True
|
|
@@ -64,8 +86,11 @@ def log_get(f_name, msgobj=None):
|
|
|
64
86
|
Get and stream (ver osocket/stdout) .log file's content and count "critical" errors
|
|
65
87
|
- critical error tag in log line: [ERR]
|
|
66
88
|
"""
|
|
89
|
+
f_name = f"{LOG_FOLDER}/{f_name}"
|
|
67
90
|
err_cnt = 0
|
|
68
91
|
try:
|
|
92
|
+
if msgobj is not None:
|
|
93
|
+
msgobj(f_name)
|
|
69
94
|
with open(f_name, 'r') as f:
|
|
70
95
|
eline = f.readline().strip()
|
|
71
96
|
while eline:
|
|
@@ -73,7 +98,7 @@ def log_get(f_name, msgobj=None):
|
|
|
73
98
|
err_cnt += 1 if "[ERR]" in eline else 0
|
|
74
99
|
# GIVE BACK .log file contents
|
|
75
100
|
if msgobj is not None:
|
|
76
|
-
msgobj(eline)
|
|
101
|
+
msgobj(f"\t{eline}")
|
|
77
102
|
eline = f.readline().strip()
|
|
78
103
|
except:
|
|
79
104
|
pass
|
|
@@ -82,13 +107,22 @@ def log_get(f_name, msgobj=None):
|
|
|
82
107
|
|
|
83
108
|
def syslog(data=None, msgobj=None):
|
|
84
109
|
if data is None:
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
err_cnt = sum([log_get(f, msgobj) for f in listdir(LOG_FOLDER) if f.endswith(".sys.log")])
|
|
111
|
+
return err_cnt
|
|
112
|
+
|
|
113
|
+
_match = match(r"^\[([^\[\]]+)\]", data)
|
|
114
|
+
log_lvl = _match.group(1).lower() if _match else 'user'
|
|
115
|
+
f_name = f"{log_lvl}.sys.log" if log_lvl in ("err", "warn", "boot") else 'user.sys.log'
|
|
116
|
+
return logger(data, f_name, limit=4)
|
|
87
117
|
|
|
88
118
|
|
|
89
119
|
def log_clean(msgobj=None):
|
|
90
|
-
to_del = [file for file in listdir() if file.endswith('.log')]
|
|
120
|
+
to_del = [file for file in listdir(LOG_FOLDER) if file.endswith('.log')]
|
|
91
121
|
for _del in to_del:
|
|
122
|
+
_del = f"{LOG_FOLDER}/{_del}"
|
|
92
123
|
if msgobj is not None:
|
|
93
124
|
msgobj(f" Delete: {_del}")
|
|
94
125
|
remove(_del)
|
|
126
|
+
|
|
127
|
+
# Init log folder at module load
|
|
128
|
+
_init_logger()
|
micrOS/source/Notify.py
CHANGED
|
@@ -35,16 +35,17 @@ class Notify:
|
|
|
35
35
|
@staticmethod
|
|
36
36
|
def message(text, reply_to=None, chat_id=None):
|
|
37
37
|
"""
|
|
38
|
-
Send message to all subscribers
|
|
38
|
+
Send message to all subscribers - Notify agents
|
|
39
39
|
"""
|
|
40
40
|
exit_code = 0
|
|
41
41
|
for s in Notify._SUBSCRIBERS:
|
|
42
42
|
try:
|
|
43
|
+
# !!! SUBSCRIBER HAS TO DEFINE send_msg(text, reply_to, chat_id) method !!!
|
|
43
44
|
s.send_msg(text, reply_to, chat_id)
|
|
44
45
|
except Exception as e:
|
|
45
46
|
errlog_add(f"[ERR] Notify: {e}")
|
|
46
47
|
exit_code+=1
|
|
47
|
-
return f"Sent
|
|
48
|
+
return f"Sent for {len(Notify._SUBSCRIBERS)} client(s), errors: ({exit_code})"
|
|
48
49
|
|
|
49
50
|
@staticmethod
|
|
50
51
|
def notifications(state=None):
|
|
@@ -66,7 +67,7 @@ class Notify:
|
|
|
66
67
|
|
|
67
68
|
@staticmethod
|
|
68
69
|
def lm_execute(cmd_args):
|
|
69
|
-
"""Executor with basic access handling"""
|
|
70
|
+
"""Load Module Executor with basic access handling"""
|
|
70
71
|
if lm_is_loaded(cmd_args[0]):
|
|
71
72
|
try:
|
|
72
73
|
_, out = lm_exec(cmd_args)
|
micrOS/source/Server.py
CHANGED
|
@@ -114,7 +114,7 @@ class Client:
|
|
|
114
114
|
console_write("[Client] NoCon: response>dev/nul")
|
|
115
115
|
|
|
116
116
|
def send(self, response):
|
|
117
|
-
# Implement in child class - synchronous send method
|
|
117
|
+
# Implement in child class - synchronous send (all) method
|
|
118
118
|
pass
|
|
119
119
|
|
|
120
120
|
async def close(self):
|
|
@@ -182,7 +182,7 @@ class WebCli(Client, WebEngine):
|
|
|
182
182
|
if cfgput('webui', True): # SET webui to True
|
|
183
183
|
from machine import reset
|
|
184
184
|
reset() # HARD RESET (REBOOT)
|
|
185
|
-
WebEngine.
|
|
185
|
+
WebEngine.ENDPOINTS[endpoint] = callback
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
class ShellCli(Client, Shell):
|
|
@@ -196,7 +196,9 @@ class ShellCli(Client, Shell):
|
|
|
196
196
|
|
|
197
197
|
def send(self, response):
|
|
198
198
|
"""
|
|
199
|
-
|
|
199
|
+
Synchronous send function (with drain task)
|
|
200
|
+
- not used in Shell or ShellCli
|
|
201
|
+
- Note: it is a support function for synchronous scenarios: Server.reply_all
|
|
200
202
|
"""
|
|
201
203
|
if self.connected:
|
|
202
204
|
if self.prompt() != response:
|
|
@@ -219,8 +221,7 @@ class ShellCli(Client, Shell):
|
|
|
219
221
|
|
|
220
222
|
async def __wait_for_drain(self):
|
|
221
223
|
"""
|
|
222
|
-
Handle drain serialization
|
|
223
|
-
- solve output data duplicate
|
|
224
|
+
Handle drain serialization - for synchronous send function
|
|
224
225
|
"""
|
|
225
226
|
try:
|
|
226
227
|
# send write buffer
|
|
@@ -233,55 +234,45 @@ class ShellCli(Client, Shell):
|
|
|
233
234
|
# set drain free
|
|
234
235
|
self.drain_event.set() # set drain free (True)
|
|
235
236
|
|
|
236
|
-
async def
|
|
237
|
-
Client.console(f"[ShellCli] Close connection {self.client_id}")
|
|
238
|
-
self.send("Bye!\n")
|
|
239
|
-
# Reset shell state machine
|
|
240
|
-
self.reset()
|
|
241
|
-
await asyncio.sleep_ms(50)
|
|
242
|
-
# Used from Client parent class
|
|
243
|
-
await super().close()
|
|
244
|
-
|
|
245
|
-
async def __shell_cmd(self, request):
|
|
237
|
+
async def a_send(self, response, encode='utf8'):
|
|
246
238
|
"""
|
|
247
|
-
|
|
239
|
+
Async send for Shell (new line + prompt$)
|
|
248
240
|
"""
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
state = self.shell(request)
|
|
254
|
-
if state:
|
|
255
|
-
return False # exit_loop
|
|
256
|
-
return True # exit_loop : Close session when shell returns False (auth Failed, etc.)
|
|
257
|
-
except Exception as e:
|
|
258
|
-
Client.console(f"[ShellCli] Shell exception: {e}")
|
|
259
|
-
if "ECONNRESET" in str(e):
|
|
260
|
-
return True # exit_loop
|
|
261
|
-
self.send("[HA] Critical error - disconnect & hard reset")
|
|
262
|
-
errlog_add("[ERR] Socket critical error - reboot")
|
|
263
|
-
self.reboot()
|
|
241
|
+
if self.prompt() != response:
|
|
242
|
+
# [format] Add new line if not prompt
|
|
243
|
+
response = f"{response}\n"
|
|
244
|
+
await super().a_send(response, encode)
|
|
264
245
|
|
|
265
246
|
async def run_shell(self):
|
|
266
247
|
# Update server task output
|
|
267
248
|
Manager().server_task_msg(','.join(list(Client.ACTIVE_CLIS)))
|
|
268
249
|
# Init prompt
|
|
269
|
-
self.
|
|
250
|
+
await self.a_send(self.prompt())
|
|
270
251
|
# Run async connection handling
|
|
252
|
+
_exit = False
|
|
271
253
|
while self.connected:
|
|
272
254
|
try:
|
|
273
255
|
# Read request msg from client
|
|
274
256
|
state, request = await self.read()
|
|
275
257
|
if state:
|
|
276
258
|
break
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
259
|
+
# Run micrOS shell with request string
|
|
260
|
+
Client.console("[ShellCli] --- #Run shell")
|
|
261
|
+
# Shell -> True (OK) or False (NOK) -> NOK->Close session (auth Failed, etc.)
|
|
262
|
+
_exit = not await self.shell(request)
|
|
281
263
|
except Exception as e:
|
|
282
|
-
errlog_add(f"[ERR]
|
|
264
|
+
errlog_add(f"[ERR] Shell client: {e}")
|
|
265
|
+
if "ECONNRESET" in str(e):
|
|
266
|
+
_exit = True # exit_loop
|
|
267
|
+
else:
|
|
268
|
+
await self.a_send("[HA] Critical error - disconnect & hard reset")
|
|
269
|
+
errlog_add("[ERR] Socket critical error - reboot")
|
|
270
|
+
await self.reboot()
|
|
271
|
+
if _exit:
|
|
272
|
+
collect()
|
|
283
273
|
break
|
|
284
274
|
# Close connection
|
|
275
|
+
await self.a_send("Bye!")
|
|
285
276
|
await self.close()
|
|
286
277
|
|
|
287
278
|
|
|
@@ -418,8 +409,11 @@ class Server:
|
|
|
418
409
|
"""
|
|
419
410
|
for _, cli in Client.ACTIVE_CLIS.items():
|
|
420
411
|
if cli.connected:
|
|
421
|
-
cli.send(msg)
|
|
412
|
+
cli.send(f"~~~ {msg}")
|
|
422
413
|
|
|
423
414
|
def __del__(self):
|
|
424
415
|
Client.console("[ socket server ] <<destructor>>")
|
|
425
|
-
self.server
|
|
416
|
+
if self.server:
|
|
417
|
+
self.server.close()
|
|
418
|
+
if self.web:
|
|
419
|
+
self.web.close()
|