micrOSDevToolKit 2.9.4__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/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 +41 -15
- micrOS/source/Notify.py +4 -3
- micrOS/source/Server.py +5 -2
- micrOS/source/Shell.py +2 -2
- 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.4.data → micrOSDevToolKit-2.9.6.data}/scripts/devToolKit.py +3 -2
- {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.6.dist-info}/METADATA +1 -1
- {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.6.dist-info}/RECORD +54 -51
- toolkit/Gateway.py +2 -2
- toolkit/dashboard_apps/SystemTest.py +12 -11
- toolkit/index.html +4 -4
- toolkit/lib/macroScript.py +6 -0
- toolkit/lib/micrOSClient.py +35 -8
- 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 +2 -2
- 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.4.dist-info → micrOSDevToolKit-2.9.6.dist-info}/LICENSE +0 -0
- {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.6.dist-info}/WHEEL +0 -0
- {micrOSDevToolKit-2.9.4.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,12 +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 os import listdir, remove
|
|
9
8
|
from re import match
|
|
9
|
+
from uos import listdir, remove, stat, mkdir, getcwd
|
|
10
10
|
|
|
11
11
|
#############################################
|
|
12
12
|
# LOGGING WITH DATA ROTATION #
|
|
13
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
|
|
14
36
|
|
|
15
37
|
|
|
16
38
|
def logger(data, f_name, limit):
|
|
@@ -24,14 +46,15 @@ def logger(data, f_name, limit):
|
|
|
24
46
|
return write verdict - true / false
|
|
25
47
|
INFO: hardcoded max data number = 30
|
|
26
48
|
"""
|
|
27
|
-
def _logger(
|
|
28
|
-
|
|
49
|
+
def _logger(f_mode='r+'):
|
|
50
|
+
nonlocal data, f_name, limit
|
|
51
|
+
limit = min(limit, 30) # Hardcoded max data line = 30
|
|
29
52
|
# [1] GET TIME STUMP
|
|
30
53
|
ts_buff = [str(k) for k in localtime()]
|
|
31
54
|
ts = ".".join(ts_buff[0:3]) + "-" + ":".join(ts_buff[3:6])
|
|
32
55
|
# [2] OPEN FILE - WRITE DATA WITH TS
|
|
33
|
-
with open(
|
|
34
|
-
_data = f"{ts} {
|
|
56
|
+
with open(f_name, f_mode) as f:
|
|
57
|
+
_data = f"{ts} {data}\n"
|
|
35
58
|
# read file lines and filter by time stump chunks (hack for replace truncate)
|
|
36
59
|
lines = [_l for _l in f.readlines() if '-' in _l and '.' in _l]
|
|
37
60
|
# get file params
|
|
@@ -39,22 +62,20 @@ def logger(data, f_name, limit):
|
|
|
39
62
|
lines.append(_data)
|
|
40
63
|
f.seek(0)
|
|
41
64
|
# line data rotate
|
|
42
|
-
if lines_len >=
|
|
43
|
-
lines = lines[-
|
|
44
|
-
lines_str = ''.join(lines)
|
|
45
|
-
else:
|
|
46
|
-
lines_str = ''.join(lines)
|
|
65
|
+
if lines_len >= limit:
|
|
66
|
+
lines = lines[-limit:]
|
|
47
67
|
# write file
|
|
48
|
-
f.write(
|
|
68
|
+
f.write(''.join(lines))
|
|
49
69
|
|
|
70
|
+
f_name = f"{LOG_FOLDER}/{f_name}"
|
|
50
71
|
# Run logger
|
|
51
72
|
try:
|
|
52
73
|
# There is file - append 'r+'
|
|
53
|
-
_logger(
|
|
74
|
+
_logger()
|
|
54
75
|
except:
|
|
55
76
|
try:
|
|
56
77
|
# There is no file - create 'a+'
|
|
57
|
-
_logger(
|
|
78
|
+
_logger('a+')
|
|
58
79
|
except:
|
|
59
80
|
return False
|
|
60
81
|
return True
|
|
@@ -65,6 +86,7 @@ def log_get(f_name, msgobj=None):
|
|
|
65
86
|
Get and stream (ver osocket/stdout) .log file's content and count "critical" errors
|
|
66
87
|
- critical error tag in log line: [ERR]
|
|
67
88
|
"""
|
|
89
|
+
f_name = f"{LOG_FOLDER}/{f_name}"
|
|
68
90
|
err_cnt = 0
|
|
69
91
|
try:
|
|
70
92
|
if msgobj is not None:
|
|
@@ -85,7 +107,7 @@ def log_get(f_name, msgobj=None):
|
|
|
85
107
|
|
|
86
108
|
def syslog(data=None, msgobj=None):
|
|
87
109
|
if data is None:
|
|
88
|
-
err_cnt = sum([log_get(f, msgobj) for f in listdir() if f.endswith(".sys.log")])
|
|
110
|
+
err_cnt = sum([log_get(f, msgobj) for f in listdir(LOG_FOLDER) if f.endswith(".sys.log")])
|
|
89
111
|
return err_cnt
|
|
90
112
|
|
|
91
113
|
_match = match(r"^\[([^\[\]]+)\]", data)
|
|
@@ -95,8 +117,12 @@ def syslog(data=None, msgobj=None):
|
|
|
95
117
|
|
|
96
118
|
|
|
97
119
|
def log_clean(msgobj=None):
|
|
98
|
-
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')]
|
|
99
121
|
for _del in to_del:
|
|
122
|
+
_del = f"{LOG_FOLDER}/{_del}"
|
|
100
123
|
if msgobj is not None:
|
|
101
124
|
msgobj(f" Delete: {_del}")
|
|
102
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
|
@@ -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):
|
|
@@ -413,4 +413,7 @@ class Server:
|
|
|
413
413
|
|
|
414
414
|
def __del__(self):
|
|
415
415
|
Client.console("[ socket server ] <<destructor>>")
|
|
416
|
-
self.server
|
|
416
|
+
if self.server:
|
|
417
|
+
self.server.close()
|
|
418
|
+
if self.web:
|
|
419
|
+
self.web.close()
|
micrOS/source/Shell.py
CHANGED
|
@@ -11,8 +11,8 @@ Designed by Marcell Ban aka BxNxM
|
|
|
11
11
|
#################################################################
|
|
12
12
|
# IMPORTS #
|
|
13
13
|
#################################################################
|
|
14
|
-
from os import listdir
|
|
15
14
|
from sys import modules
|
|
15
|
+
from uos import listdir
|
|
16
16
|
from machine import reset as hard_reset, soft_reset
|
|
17
17
|
from Config import cfgget, cfgput
|
|
18
18
|
from Tasks import lm_exec
|
|
@@ -25,7 +25,7 @@ from Debug import errlog_add
|
|
|
25
25
|
|
|
26
26
|
class Shell:
|
|
27
27
|
__slots__ = ['__devfid', '__auth_mode', '__hwuid', '__auth_ok', '__conf_mode']
|
|
28
|
-
MICROS_VERSION = '2.9.
|
|
28
|
+
MICROS_VERSION = '2.9.6-0'
|
|
29
29
|
|
|
30
30
|
def __init__(self):
|
|
31
31
|
"""
|
micrOS/source/Web.py
CHANGED
|
@@ -5,7 +5,7 @@ Built-in-function:
|
|
|
5
5
|
- response
|
|
6
6
|
- landing page: index.html
|
|
7
7
|
- rest/ - call load modules, e.x.: system/top
|
|
8
|
-
- file response (.html, .css, .js, .
|
|
8
|
+
- file response (.html, .css, .js, .jpeg) - generic file server feature
|
|
9
9
|
- "virtual" endpoints - to reply from script on a defined endpoint
|
|
10
10
|
- stream - stream data (jpeg) function
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ from Config import cfgget
|
|
|
21
21
|
|
|
22
22
|
class WebEngine:
|
|
23
23
|
__slots__ = ["client"]
|
|
24
|
-
|
|
24
|
+
ENDPOINTS = {}
|
|
25
25
|
AUTH = cfgget('auth')
|
|
26
26
|
VERSION = "n/a"
|
|
27
27
|
REQ200 = "HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len}\r\n\r\n{data}"
|
|
@@ -37,7 +37,8 @@ class WebEngine:
|
|
|
37
37
|
"""File dynamic Content-Type handling"""
|
|
38
38
|
content_types = {".html": "text/html",
|
|
39
39
|
".css": "text/css",
|
|
40
|
-
".js": "application/javascript"
|
|
40
|
+
".js": "application/javascript",
|
|
41
|
+
".jpeg": "image/jpeg"}
|
|
41
42
|
# Extract the file extension
|
|
42
43
|
ext = path.rsplit('.', 1)[-1]
|
|
43
44
|
# Return the content type based on the file extension
|
|
@@ -49,7 +50,7 @@ class WebEngine:
|
|
|
49
50
|
_method, url, _version = request.split('\n')[0].split()
|
|
50
51
|
# Protocol validation
|
|
51
52
|
if _method != "GET" and _version.startswith('HTTP'):
|
|
52
|
-
_err = "Bad Request: not GET HTTP
|
|
53
|
+
_err = f"Bad Request: not GET HTTP but {_version}"
|
|
53
54
|
await self.client.a_send(self.REQ400.format(len=len(_err), data=_err))
|
|
54
55
|
return
|
|
55
56
|
|
|
@@ -109,46 +110,42 @@ class WebEngine:
|
|
|
109
110
|
else:
|
|
110
111
|
state, out = lm_exec(cmd, jsonify=True)
|
|
111
112
|
try:
|
|
112
|
-
resp_schema['result'] = loads(out) # Load again ... hack for embedded
|
|
113
|
+
resp_schema['result'] = loads(out) # Load again ... hack for embedded json converter...
|
|
113
114
|
except:
|
|
114
115
|
resp_schema['result'] = out
|
|
115
116
|
resp_schema['state'] = state
|
|
116
117
|
else:
|
|
117
118
|
resp_schema['result'] = {"micrOS": WebEngine.VERSION, 'node': cfgget('devfid'), 'auth': WebEngine.AUTH}
|
|
118
|
-
if len(tuple(WebEngine.
|
|
119
|
-
resp_schema['result']['usr_endpoints'] = tuple(WebEngine.
|
|
119
|
+
if len(tuple(WebEngine.ENDPOINTS.keys())) > 0:
|
|
120
|
+
resp_schema['result']['usr_endpoints'] = tuple(WebEngine.ENDPOINTS)
|
|
120
121
|
resp_schema['state'] = True
|
|
121
122
|
response = dumps(resp_schema)
|
|
122
123
|
return WebEngine.REQ200.format(dtype='text/html', len=len(response), data=response)
|
|
123
124
|
|
|
124
125
|
async def endpoints(self, url):
|
|
125
126
|
url = url[1:] # Cut first / char
|
|
126
|
-
if url in WebEngine.
|
|
127
|
+
if url in WebEngine.ENDPOINTS:
|
|
127
128
|
console_write(f"[WebCli] endpoint: {url}")
|
|
128
129
|
# Registered endpoint was found - exec callback
|
|
129
130
|
try:
|
|
130
131
|
# RESOLVE ENDPOINT CALLBACK
|
|
131
132
|
# dtype:
|
|
132
|
-
# one-shot:
|
|
133
|
-
# task: multipart/x-mixed-replace | multipart/form-data - data: dict
|
|
134
|
-
#
|
|
135
|
-
dtype, data = WebEngine.
|
|
133
|
+
# one-shot (simple MIME types): image/jpeg | text/html | text/plain - data: raw
|
|
134
|
+
# task (streaming MIME types): multipart/x-mixed-replace | multipart/form-data - data: dict{callback,content-type}
|
|
135
|
+
# content-type: image/jpeg | audio/l16;*
|
|
136
|
+
dtype, data = WebEngine.ENDPOINTS[url]()
|
|
136
137
|
if dtype == 'image/jpeg':
|
|
137
|
-
resp = f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len(data)}\r\n\r\n".encode(
|
|
138
|
-
'utf8') + data
|
|
138
|
+
resp = f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len(data)}\r\n\r\n".encode('utf8') + data
|
|
139
139
|
await self.client.a_send(resp, encode=None)
|
|
140
140
|
elif dtype in ('multipart/x-mixed-replace', 'multipart/form-data'):
|
|
141
|
-
headers =
|
|
142
|
-
|
|
143
|
-
'utf-8')
|
|
144
|
-
await self.client.a_send(headers, encode=None)
|
|
141
|
+
headers = f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}; boundary=\"micrOS_boundary\"\r\n\r\n"
|
|
142
|
+
await self.client.a_send(headers)
|
|
145
143
|
# Start Native stream async task
|
|
146
144
|
task = NativeTask()
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
tag=f"web.stream_{self.client.client_id.replace('W', '')}"
|
|
146
|
+
task.create(callback=self.stream(data['callback'], task, data['content-type']), tag=tag)
|
|
149
147
|
else: # dtype: text/html or text/plain
|
|
150
|
-
await self.client.a_send(
|
|
151
|
-
f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}\r\nContent-Length:{len(data)}\r\n\r\n{data}")
|
|
148
|
+
await self.client.a_send(WebEngine.REQ200.format(dtype=dtype, len=len(data), data=data))
|
|
152
149
|
except Exception as e:
|
|
153
150
|
await self.client.a_send(self.REQ404.format(len=len(str(e)), data=e))
|
|
154
151
|
errlog_add(f"[ERR] WebCli endpoints {url}: {e}")
|
|
@@ -159,6 +156,8 @@ class WebEngine:
|
|
|
159
156
|
"""
|
|
160
157
|
Async stream method
|
|
161
158
|
:param callback: sync or async function callback (auto-detect) WARNING: works for functions only (not methods!)
|
|
159
|
+
:param task: NativeTask instance (that the stream runs in)
|
|
160
|
+
:param content_type: image/jpeg | audio/l16;*
|
|
162
161
|
"""
|
|
163
162
|
is_coroutine = 'generator' in str(type(callback)) # async function callback auto-detect
|
|
164
163
|
with task:
|
|
@@ -175,6 +174,6 @@ class WebEngine:
|
|
|
175
174
|
# Gracefully terminate the stream
|
|
176
175
|
if self.client.connected:
|
|
177
176
|
closing_boundary = '\r\n--micrOS_boundary--\r\n'
|
|
178
|
-
await self.client.a_send(closing_boundary
|
|
177
|
+
await self.client.a_send(closing_boundary)
|
|
179
178
|
await self.client.close()
|
|
180
179
|
task.out = 'Finished stream'
|
micrOS/source/microIO.py
CHANGED
|
@@ -9,7 +9,7 @@ Designed by Marcell Ban aka BxNxM
|
|
|
9
9
|
# IMPORTS #
|
|
10
10
|
#################################################################
|
|
11
11
|
from sys import platform
|
|
12
|
-
from
|
|
12
|
+
from uos import listdir, uname
|
|
13
13
|
from Logger import syslog
|
|
14
14
|
|
|
15
15
|
#################################################################
|
|
@@ -29,7 +29,6 @@ def detect_platform():
|
|
|
29
29
|
Unified platform detection for micrOS
|
|
30
30
|
"""
|
|
31
31
|
if 'esp32' in platform:
|
|
32
|
-
from os import uname
|
|
33
32
|
board = str(uname()[-1]).lower()
|
|
34
33
|
if 'tinypico' in board:
|
|
35
34
|
return 'tinypico' # esp32 family - tinypico
|
micrOS/source/reset.py
CHANGED
micrOS/source/urequests.py
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module implements an optimized version of requests module
|
|
3
|
+
for async and sync http get and post requests.
|
|
4
|
+
|
|
5
|
+
Designed by Marcell Ban aka BxNxM GitHub
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from json import loads, dumps
|
|
1
9
|
from usocket import socket, getaddrinfo
|
|
2
10
|
try:
|
|
3
|
-
from ussl import wrap_socket # Legacy micropython ssl usage
|
|
11
|
+
from ussl import wrap_socket # Legacy micropython ssl usage (+simulator mode)
|
|
4
12
|
except ImportError:
|
|
5
13
|
from ssl import wrap_socket # From micropython 1.23...
|
|
6
|
-
from json import loads, dumps
|
|
7
14
|
from Debug import errlog_add
|
|
8
15
|
import uasyncio as asyncio
|
|
9
16
|
|
|
@@ -39,7 +46,7 @@ def _chunked(_body):
|
|
|
39
46
|
chunk_size_str = _body[:line_end]
|
|
40
47
|
try:
|
|
41
48
|
chunk_size = int(chunk_size_str, 16)
|
|
42
|
-
except ValueError
|
|
49
|
+
except ValueError:
|
|
43
50
|
chunk_size = 0
|
|
44
51
|
|
|
45
52
|
# Check chunk size
|
|
@@ -213,7 +220,8 @@ async def arequest(method, url, data=None, json=None, headers=None, sock_size=25
|
|
|
213
220
|
status_code, body = _parse_response(response)
|
|
214
221
|
except Exception as e:
|
|
215
222
|
status_code = 500
|
|
216
|
-
|
|
223
|
+
# https://github.com/micropython/micropython/blob/8785645a952c03315dbf93667b5f7c7eec49762f/cc3200/simplelink/include/device.h
|
|
224
|
+
body = "[WARN] arequest ASSOC_REJECT" if "-104" == str(e) else f"[ERR] arequest failed: {e}"
|
|
217
225
|
errlog_add(body)
|
|
218
226
|
finally:
|
|
219
227
|
if writer:
|
|
@@ -10,8 +10,9 @@ if len(sys.argv) > 1 and sys.argv[1] in ['light', '--light']:
|
|
|
10
10
|
else:
|
|
11
11
|
# INSTALL OPTIONAL DEPENDENCIES - PIP HACK
|
|
12
12
|
from toolkit.lib import pip_package_installer as pip_install
|
|
13
|
-
pip_install.install_optional_dependencies(['PyQt5', 'opencv-python', 'PyAudio', 'mpy-cross==1.
|
|
14
|
-
|
|
13
|
+
pip_install.install_optional_dependencies(['PyQt5', 'opencv-python', 'PyAudio', 'mpy-cross==1.24.1.post2', 'matplotlib'])
|
|
14
|
+
if sys.platform.startswith("win"):
|
|
15
|
+
pip_install.install_optional_dependencies(['pyreadline3'])
|
|
15
16
|
|
|
16
17
|
# NORMAL CODE ...
|
|
17
18
|
MYPATH = os.path.dirname(__file__)
|