micrOSDevToolKit 2.9.4__py3-none-any.whl → 2.9.7__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.

Files changed (57) hide show
  1. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +19 -19
  2. micrOS/source/Config.py +6 -6
  3. micrOS/source/LM_pacman.py +248 -0
  4. micrOS/source/LM_rest.py +0 -1
  5. micrOS/source/LM_robustness.py +2 -2
  6. micrOS/source/LM_system.py +4 -16
  7. micrOS/source/LM_telegram.py +95 -61
  8. micrOS/source/Logger.py +41 -15
  9. micrOS/source/Notify.py +4 -3
  10. micrOS/source/Server.py +5 -2
  11. micrOS/source/Shell.py +2 -2
  12. micrOS/source/Web.py +22 -23
  13. micrOS/source/microIO.py +1 -2
  14. micrOS/source/reset.py +5 -1
  15. micrOS/source/urequests.py +12 -4
  16. {micrOSDevToolKit-2.9.4.data → micrOSDevToolKit-2.9.7.data}/scripts/devToolKit.py +3 -2
  17. {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.7.dist-info}/METADATA +1 -1
  18. {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.7.dist-info}/RECORD +55 -52
  19. toolkit/Gateway.py +2 -2
  20. toolkit/LM_to_compile.dat +1 -1
  21. toolkit/dashboard_apps/SystemTest.py +12 -11
  22. toolkit/index.html +4 -4
  23. toolkit/lib/macroScript.py +6 -0
  24. toolkit/lib/micrOSClient.py +35 -8
  25. toolkit/lib/micrOSClientHistory.py +122 -0
  26. toolkit/micrOSlint.py +1 -1
  27. toolkit/simulator_lib/__pycache__/machine.cpython-312.pyc +0 -0
  28. toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
  29. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  30. toolkit/simulator_lib/__pycache__/ussl.cpython-312.pyc +0 -0
  31. toolkit/simulator_lib/machine.py +4 -0
  32. toolkit/simulator_lib/node_config.json +1 -1
  33. toolkit/simulator_lib/uasyncio.py +7 -1
  34. toolkit/simulator_lib/uos.py +138 -0
  35. toolkit/socketClient.py +2 -2
  36. toolkit/workspace/precompiled/Config.mpy +0 -0
  37. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  38. toolkit/workspace/precompiled/LM_pacman.mpy +0 -0
  39. toolkit/workspace/precompiled/LM_rest.mpy +0 -0
  40. toolkit/workspace/precompiled/LM_robustness.py +2 -2
  41. toolkit/workspace/precompiled/LM_system.mpy +0 -0
  42. toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
  43. toolkit/workspace/precompiled/Logger.mpy +0 -0
  44. toolkit/workspace/precompiled/Notify.mpy +0 -0
  45. toolkit/workspace/precompiled/Server.mpy +0 -0
  46. toolkit/workspace/precompiled/Shell.mpy +0 -0
  47. toolkit/workspace/precompiled/Web.mpy +0 -0
  48. toolkit/workspace/precompiled/_mpy.version +1 -1
  49. toolkit/workspace/precompiled/microIO.mpy +0 -0
  50. toolkit/workspace/precompiled/node_config.json +1 -1
  51. toolkit/workspace/precompiled/reset.mpy +0 -0
  52. toolkit/workspace/precompiled/urequests.mpy +0 -0
  53. micrOS/source/LM_lmpacman.py +0 -126
  54. toolkit/workspace/precompiled/LM_lmpacman.mpy +0 -0
  55. {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.7.dist-info}/LICENSE +0 -0
  56. {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.7.dist-info}/WHEEL +0 -0
  57. {micrOSDevToolKit-2.9.4.dist-info → micrOSDevToolKit-2.9.7.dist-info}/top_level.txt +0 -0
@@ -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
- def _update_chat_ids():
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
- # --------------------- FUNCTION MAIN ------------------------ #
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
- _, resp_json = urequests.get(url, jsonify=True, sock_size=128)
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'] = _update_chat_ids()
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.get_msg()
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"\tREC&EVAL: {verdict}")
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
- async def __task():
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 msg receiver loop - automatic LM execution
352
+ Telegram BOT (repl) - ReadEvalPrintLoop for Load Modules
320
353
  - Only executes module (function) if the module is already loaded
321
- on the endpoint / micrOS node
354
+ :param period: polling period in sec, default: 3
322
355
  """
323
356
  if TELEGRAM_OBJ is None:
324
357
  return "Network unavailable."
325
- state = micro_task(tag='telegram._loop', task=__task())
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(_data, _f_name, _limit, f_mode='r+'):
28
- _limit = 30 if _limit > 30 else _limit
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(_f_name, f_mode) as f:
34
- _data = f"{ts} {_data}\n"
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 >= _limit:
43
- lines = lines[-_limit:]
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(lines_str)
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(data, f_name, limit)
74
+ _logger()
54
75
  except:
55
76
  try:
56
77
  # There is no file - create 'a+'
57
- _logger(data, f_name, limit, 'a+')
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 N{len(Notify._SUBSCRIBERS)} ({exit_code})"
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.REST_ENDPOINTS[endpoint] = callback
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.close()
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.4-0'
28
+ MICROS_VERSION = '2.9.7-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, .jped) - generic file server feature
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
- REST_ENDPOINTS = {}
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/1.1"
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 shell json converter...
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.REST_ENDPOINTS.keys())) > 0:
119
- resp_schema['result']['usr_endpoints'] = tuple(WebEngine.REST_ENDPOINTS)
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.REST_ENDPOINTS:
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: image/jpeg | text/html | text/plain - data: raw
133
- # task: multipart/x-mixed-replace | multipart/form-data - data: dict=callback,content-type
134
- # content-type: image/jpeg | audio/l16;*
135
- dtype, data = WebEngine.REST_ENDPOINTS[url]()
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
- f"HTTP/1.1 200 OK\r\nContent-Type: {dtype}; boundary=\"micrOS_boundary\"\r\n\r\n").encode(
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
- task.create(callback=self.stream(data['callback'], task, data['content-type']),
148
- tag=f"web.stream_{self.client.client_id.replace('W', '')}")
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, encode=None)
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 os import listdir
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
@@ -1,5 +1,9 @@
1
- from os import listdir
1
+ """
2
+ Easy reset for webrepl terminal
3
+ """
4
+
2
5
  from time import sleep
6
+ from uos import listdir
3
7
  from machine import soft_reset, reset
4
8
 
5
9
  print('Device reboot now, boot micrOSloader...')
@@ -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 as e:
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
- body = f'[ERR] arequest failed: {e}'
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.20.0', 'matplotlib'])
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__)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: micrOSDevToolKit
3
- Version: 2.9.4
3
+ Version: 2.9.7
4
4
  Summary: Development and deployment environment for micrOS, the diy micropython automation OS (IoT)
5
5
  Home-page: https://github.com/BxNxM/micrOS
6
6
  Author: Marcell Ban