micrOSDevToolKit 2.20.0__py3-none-any.whl → 2.21.0__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 (34) hide show
  1. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +19 -15
  2. micrOS/source/Config.py +5 -2
  3. micrOS/source/Espnow.py +99 -40
  4. micrOS/source/Files.py +50 -23
  5. micrOS/source/InterConnect.py +9 -3
  6. micrOS/source/Pacman.py +141 -0
  7. micrOS/source/Shell.py +1 -1
  8. micrOS/source/Tasks.py +4 -1
  9. micrOS/source/modules/LM_oled_ui.py +39 -41
  10. micrOS/source/modules/LM_oledui.py +56 -85
  11. micrOS/source/modules/LM_pacman.py +36 -44
  12. micrOS/source/urequests.py +1 -1
  13. {microsdevtoolkit-2.20.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/METADATA +9 -6
  14. {microsdevtoolkit-2.20.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/RECORD +34 -32
  15. toolkit/DevEnvOTA.py +2 -2
  16. toolkit/DevEnvUSB.py +1 -1
  17. toolkit/lib/micrOSClient.py +37 -15
  18. toolkit/lib/micrOSClientHistory.py +35 -1
  19. toolkit/simulator_lib/__pycache__/uos.cpython-312.pyc +0 -0
  20. toolkit/simulator_lib/uos.py +1 -0
  21. toolkit/workspace/precompiled/Config.mpy +0 -0
  22. toolkit/workspace/precompiled/Espnow.mpy +0 -0
  23. toolkit/workspace/precompiled/Files.mpy +0 -0
  24. toolkit/workspace/precompiled/InterConnect.mpy +0 -0
  25. toolkit/workspace/precompiled/Pacman.mpy +0 -0
  26. toolkit/workspace/precompiled/Shell.mpy +0 -0
  27. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  28. toolkit/workspace/precompiled/modules/LM_oled_ui.mpy +0 -0
  29. toolkit/workspace/precompiled/modules/LM_oledui.mpy +0 -0
  30. toolkit/workspace/precompiled/modules/LM_pacman.mpy +0 -0
  31. {microsdevtoolkit-2.20.0.data → microsdevtoolkit-2.21.0.data}/scripts/devToolKit.py +0 -0
  32. {microsdevtoolkit-2.20.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/WHEEL +0 -0
  33. {microsdevtoolkit-2.20.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/licenses/LICENSE +0 -0
  34. {microsdevtoolkit-2.20.0.dist-info → microsdevtoolkit-2.21.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,141 @@
1
+ from mip import install
2
+ from Files import OSPath, path_join, is_file
3
+ from Debug import syslog
4
+
5
+
6
+ # ---------------------------------------------------------------------
7
+ # Utility helpers
8
+ # ---------------------------------------------------------------------
9
+
10
+ def _normalize_source(ref):
11
+ """
12
+ Normalize GitHub URLs or shorthand for mip compatibility.
13
+ Converts:
14
+ - https://github.com/user/repo/blob/branch/path/file.py → https://raw.githubusercontent.com/user/repo/branch/path/file.py
15
+ - https://github.com/user/repo/tree/branch/path → github:user/repo/path
16
+ Returns (normalized_ref, branch)
17
+ """
18
+ try:
19
+ ref = ref.strip().rstrip("/")
20
+ # Already in github: shorthand
21
+ if ref.startswith("github:"):
22
+ return ref, None
23
+
24
+ if ref.startswith("https://"):
25
+ ref = ref.replace("https://", "")
26
+ if ref.startswith("github.com"):
27
+ # Folder (tree) case → github:user/repo/path
28
+ if "/tree/" in ref:
29
+ print("[mip-normalize] detected GitHub tree folder link")
30
+ parts = ref.split("/")
31
+ user, repo = parts[1], parts[2]
32
+ branch = parts[4]
33
+ path = "/".join(parts[5:])
34
+ github_ref = f"github:{user}/{repo}/{path}".rstrip("/")
35
+ return github_ref, branch
36
+
37
+ # File (blob) case → raw.githubusercontent.com
38
+ if "/blob/" in ref:
39
+ print("[mip-normalize] detected GitHub blob file link")
40
+ url_base = "https://raw.githubusercontent.com/"
41
+ ref = ref.replace("github.com/", url_base).replace("/blob", "")
42
+ return ref, None
43
+
44
+ # Direct GitHub file (no blob/tree) → github:user/repo/path
45
+ if ref.count("/") >= 2:
46
+ print("[mip-normalize] direct GitHub path (no blob/tree)")
47
+ parts = ref.split("/")
48
+ user, repo = parts[1], parts[2]
49
+ path = "/".join(parts[3:])
50
+ github_ref = f"github:{user}/{repo}/{path}".rstrip("/")
51
+ return github_ref, None
52
+
53
+ print("[mip-normalize] unchanged")
54
+ return ref, None
55
+
56
+ except Exception as e:
57
+ syslog(f"[ERR][pacman] normalize failed: {ref}: {e}")
58
+ print(f"[normalize][ERROR] {e}")
59
+ return str(ref), None
60
+
61
+
62
+ # ---------------------------------------------------------------------
63
+ # Core installer
64
+ # ---------------------------------------------------------------------
65
+
66
+ def _install_any(ref, target=None):
67
+ """Internal wrapper with consistent error handling and debug output."""
68
+ verdict = f"[mip] Installing: {ref}\n"
69
+ try:
70
+ ref, branch = _normalize_source(ref)
71
+ kwargs = {}
72
+ if branch:
73
+ kwargs["version"] = branch
74
+ kwargs["target"] = target or OSPath.LIB
75
+ verdict = f"[mip] Installing: {ref} {kwargs}\n"
76
+ # MIP Install
77
+ install(ref, **kwargs)
78
+ verdict += f" ✓ Installed successfully under {kwargs["target"]}"
79
+ except Exception as e:
80
+ err = f" ✗ Failed to install '{ref}': {e}"
81
+ syslog(f"[ERR][pacman] {err}")
82
+ verdict += err
83
+ return verdict
84
+
85
+
86
+ # ---------------------------------------------------------------------
87
+ # Public install functions
88
+ # ---------------------------------------------------------------------
89
+
90
+ def install_requirements(source="requirements.txt"):
91
+ """Install from a requirements.txt file under /config."""
92
+ verdict = f"[mip] Installing from requirements file: {source}\n"
93
+ try:
94
+ source_path = path_join(OSPath.CONFIG, source)
95
+ verdict = f"[mip] Installing from requirements file: {source_path}\n"
96
+ if is_file(source_path):
97
+ install(source_path)
98
+ verdict += " ✓ All listed packages processed"
99
+ else:
100
+ err = f" ✗ {source_path} not exists"
101
+ syslog(f"[ERR][pacman] {err}")
102
+ verdict += err
103
+ except Exception as e:
104
+ err = f" ✗ Failed to process {source}: {e}"
105
+ syslog(f"[ERR][pacman] {err}")
106
+ verdict += err
107
+ return verdict
108
+
109
+
110
+ # ---------------------------------------------------------------------
111
+ # Unified entry point
112
+ # ---------------------------------------------------------------------
113
+
114
+ def download(ref):
115
+ """
116
+ Unified mip-based downloader for micrOS.
117
+ Automatically detects:
118
+ - requirements.txt files (local or remote)
119
+ - Single-file load modules (LM_/IO_ names or URLs)
120
+ - GitHub or raw URLs (tree/blob/github:)
121
+ - Official MicroPython packages
122
+ """
123
+
124
+ if not ref:
125
+ return "[mip] Nothing to download (empty input)"
126
+
127
+ # 1. requirements.txt
128
+ if ref == "requirements.txt":
129
+ return install_requirements(ref)
130
+
131
+ if "github" in ref:
132
+ # 2. LM_/IO_ load modules → /modules
133
+ if ref.endswith("py") and ("LM_" in ref or "IO_" in ref):
134
+ return _install_any(ref, target=OSPath.MODULES)
135
+
136
+ # 3. GitHub or raw URLs → /lib
137
+ if ref.startswith("http") or ref.startswith("github"):
138
+ return _install_any(ref, target=OSPath.LIB)
139
+
140
+ # 4. Fallback: official micropython package → /lib
141
+ return _install_any(ref, target=OSPath.LIB)
micrOS/source/Shell.py CHANGED
@@ -25,7 +25,7 @@ from Debug import syslog
25
25
 
26
26
  class Shell:
27
27
  __slots__ = ['__devfid', '__auth_mode', '__hwuid', '__auth_ok', '__conf_mode']
28
- MICROS_VERSION = '2.20.0-0'
28
+ MICROS_VERSION = '2.21.0-0'
29
29
 
30
30
  def __init__(self):
31
31
  """
micrOS/source/Tasks.py CHANGED
@@ -10,6 +10,7 @@ Designed by Marcell Ban aka BxNxM
10
10
  #################################################################
11
11
  from sys import modules
12
12
  from json import dumps
13
+ from re import match
13
14
  import uasyncio as asyncio
14
15
  from micropython import schedule
15
16
  from utime import ticks_ms, ticks_diff
@@ -458,8 +459,10 @@ def exec_builtins(func):
458
459
  # ... >json - command output format option
459
460
  # ... >>node01.local - intercon: command execution on remote device by hostname/IP address
460
461
  arg_list, json_flag = (arg_list[:-1], True) if arg_list[-1] == '>json' else (arg_list, False)
461
- arg_list, intercon_target = (arg_list[:-1], arg_list[-1].replace(">>", "")) if arg_list[-1].startswith('>>') else (arg_list, None)
462
462
  json_flag = jsonify if isinstance(jsonify, bool) else json_flag
463
+ arg_list, intercon_target = ((arg_list[:-1], arg_list[-1].replace(">>", ""))
464
+ if match(r'^>>[A-Za-z0-9._-]+$', arg_list[-1])
465
+ else (arg_list, None))
463
466
 
464
467
  # INTERCONNECT
465
468
  if intercon_target:
@@ -1,3 +1,9 @@
1
+ """
2
+ micrOS simple OLED UI (irq or single task based refresh)
3
+ - with page generation
4
+ Designed by Marcell Ban aka BxNxM
5
+ """
6
+
1
7
  from Config import cfgget
2
8
  from utime import localtime
3
9
  from network import WLAN, STA_IF
@@ -236,8 +242,8 @@ class PageUI:
236
242
  else:
237
243
  self.page_callback_list[self.active_page]() # <== Execute page functions
238
244
  except Exception as e:
239
- PageUI.PAGE_UI_OBJ.show_msg = f"Err: {e}" # Show page error in msgbox
240
245
  syslog(f"oled_ui render error: {e}")
246
+ PageUI.PAGE_UI_OBJ.show_msg = f"Err: {e}" # Show page error in msgbox
241
247
  PageUI.DISPLAY.show()
242
248
  self.__power_save()
243
249
  else:
@@ -332,16 +338,17 @@ class PageUI:
332
338
  #####################################
333
339
  # PAGE GENERATORS #
334
340
  #####################################
335
- def intercon_page(self, host:str, cmd:list, run=False):
341
+ def intercon_page(self, cmd:str, run=False):
336
342
  """Generic interconnect page core - create multiple page with it"""
337
343
  posx, posy = 5, 12
338
344
 
339
345
  def _button():
346
+ nonlocal host, _cmd
340
347
  # BUTTON CALLBACK - INTERCONNECT execution
341
348
  self.open_intercons.append(host)
342
349
  try:
343
350
  # Send CMD to other device & show result
344
- state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
351
+ state, data_meta = exec_cmd(_cmd, jsonify=True)
345
352
  if state:
346
353
  self.cmd_task_tag = list(data_meta.keys())[0]
347
354
  verdict = list(data_meta.values())[0]
@@ -354,21 +361,24 @@ class PageUI:
354
361
  self.open_intercons.remove(host)
355
362
 
356
363
  # Check open host connection
357
- if host in self.open_intercons:
358
- return
364
+ _cmd = cmd.strip().split()
365
+ host = _cmd[-1].replace(">", "").replace("&", "")
359
366
  # Draw host + cmd details
360
- PageUI.DISPLAY.text(host, 0, posy)
361
- PageUI.DISPLAY.text(' '.join(cmd), posx, posy+10)
367
+ PageUI.DISPLAY.text(' '.join(_cmd[0:-1]), 0, posy)
368
+ PageUI.DISPLAY.text(_cmd[-1], posx, posy+10)
369
+ self._cmd_text(posx, posy + 10)
370
+
362
371
  # Update display output with retrieved task result (by TaskID)
363
372
  if self.cmd_task_tag is not None:
364
373
  task_buffer = manage_task(self.cmd_task_tag, 'show').replace(' ', '')
365
374
  if task_buffer is not None and len(task_buffer) > 0:
366
375
  # Set display out to task buffered data
367
376
  self.cmd_out = task_buffer
368
- # Kill task - clean
369
- manage_task(self.cmd_task_tag, 'kill')
370
- # data gathered - remove tag - skip re-read
371
- self.cmd_task_tag = None
377
+ if not manage_task(self.cmd_task_tag, 'isbusy'):
378
+ # Kill task - clean
379
+ manage_task(self.cmd_task_tag, 'kill')
380
+ # data gathered - remove tag - skip re-read
381
+ self.cmd_task_tag = None
372
382
  # Show self.cmd_out value on display
373
383
  self._cmd_text(posx, posy+10)
374
384
  # Run button event at page init
@@ -378,7 +388,7 @@ class PageUI:
378
388
  # Set button press callback (+draw button)
379
389
  self.set_press_callback(_button)
380
390
 
381
- def cmd_call_page(self, cmd, run=False):
391
+ def cmd_call_page(self, cmd:str, run=False):
382
392
  """Generic LoadModule execution page core - create multiple page with it"""
383
393
  posx, posy = 5, 12
384
394
 
@@ -429,8 +439,14 @@ def _intercon_cache(line_limit=3):
429
439
  cache = hosts()["intercon"]
430
440
  if sum([1 for _ in cache]) > 0:
431
441
  for key, val in cache.items():
432
- key = key.split('.')[0]
433
- val = '.'.join(val.split('.')[-2:])
442
+ if '.' in key:
443
+ # IP splitting
444
+ key = key.split('.')[0]
445
+ val = '.'.join(val.split('.')[-2:])
446
+ else:
447
+ # MAC splitting
448
+ key = key.split(':')[0]
449
+ val = ':'.join(val.split(':')[-2:])
434
450
  PageUI.DISPLAY.text(f" {val} {key}", 0, line_start+(line_cnt*10))
435
451
  line_cnt += 1
436
452
  if line_cnt > line_limit:
@@ -514,8 +530,7 @@ def msgbox(msg='micrOS msg'):
514
530
  PageUI.PAGE_UI_OBJ.render_page()
515
531
  return 'Show msg'
516
532
 
517
-
518
- def intercon_genpage(cmd:str=None, run=False):
533
+ def genpage(cmd:str=None, run=False):
519
534
  """
520
535
  Create intercon pages dynamically :)
521
536
  - based on cmd value.
@@ -523,31 +538,15 @@ def intercon_genpage(cmd:str=None, run=False):
523
538
  :param run: run button event at page init: True/False
524
539
  :return: page creation verdict
525
540
  """
526
- raw = cmd.split()
527
- host = raw[0]
528
- cmd = raw[1:]
529
- try:
530
- # Create page for intercon command
531
- PageUI.PAGE_UI_OBJ.add_page(lambda: PageUI.PAGE_UI_OBJ.intercon_page(host, cmd, run=run))
532
- except Exception as e:
533
- syslog(f'[ERR] intercon_genpage: {e}')
534
- return str(e)
535
- return True
536
-
537
-
538
- def cmd_genpage(cmd:str=None, run=False):
539
- """
540
- Create load module execution pages dynamically :)
541
- - based on cmd value: load_module function (args)
542
- :param cmd: 'load_module function (args)' string
543
- :param run: run button event at page init: True/False
544
- :return: page creation verdict
545
- """
546
541
  try:
547
- # Create page for intercon command
548
- PageUI.PAGE_UI_OBJ.add_page(lambda: PageUI.PAGE_UI_OBJ.cmd_call_page(cmd, run=run))
542
+ if ">>" in cmd or "&" in cmd:
543
+ # Create page for intercon/task background command
544
+ PageUI.PAGE_UI_OBJ.add_page(lambda: PageUI.PAGE_UI_OBJ.intercon_page(cmd, run=run))
545
+ else:
546
+ # Create page for realtime command
547
+ PageUI.PAGE_UI_OBJ.add_page(lambda: PageUI.PAGE_UI_OBJ.cmd_call_page(cmd, run=run))
549
548
  except Exception as e:
550
- syslog(f'[ERR] cmd_genpage: {e}')
549
+ syslog(f'[ERR] genpage: {e}')
551
550
  return str(e)
552
551
  return True
553
552
 
@@ -598,6 +597,5 @@ def help(widgets=False):
598
597
  'draw',
599
598
  'BUTTON control cmd=<prev,press,next,on,off>',
600
599
  'msgbox "msg"',
601
- 'intercon_genpage "host cmd" run=False',
602
- 'cmd_genpage "cmd" run=False',
600
+ 'genpage "cmd" run=False',
603
601
  'pinmap'), widgets=widgets)
@@ -1,3 +1,8 @@
1
+ """
2
+ micrOS multitask OLED UI
3
+ - with page generation
4
+ Designed by Marcell Ban aka BxNxM
5
+ """
1
6
  from utime import localtime, ticks_ms, ticks_diff, sleep_ms
2
7
  from Common import syslog, micro_task, manage_task, exec_cmd
3
8
  from Types import resolve
@@ -738,79 +743,59 @@ class PageUI:
738
743
  :param y: frame y
739
744
  """
740
745
  x, y = x+2, y+4
741
- def _execute(display, w, h, x, y):
742
- nonlocal cmd
743
- try:
744
- cmd_list = cmd.strip().split()
745
- # Send CMD to other device & show result
746
- state, out = exec_cmd(cmd_list)
747
- cmd_out = out.strip()
748
- except Exception as e:
749
- cmd_out = str(e)
750
- self.app_frame.press_output = cmd_out
751
- PageUI.write_lines(cmd_out, display, x, y + 15)
752
-
753
- display.text(cmd, x, y)
754
- if run:
755
- _execute(display, w, h, x, y)
756
- else:
757
- self._press_indicator(display, w, h, x, y)
758
- PageUI.write_lines(self.app_frame.press_output, display, x, y + 15)
759
- # Return "press" callback, mandatory input parameters: display, w, h, x, y
760
- return {"press": _execute}
761
- return
762
746
 
747
+ def _display_output():
748
+ nonlocal x, y, display
749
+ if self._cmd_task_tag is None:
750
+ # Display cached data
751
+ PageUI.write_lines(self.app_frame.press_output, display, x, y + 20)
752
+ return
753
+ task_buffer = manage_task(self._cmd_task_tag, 'show').replace(' ', '')
754
+ if task_buffer is not None and len(task_buffer) > 0:
755
+ # Update display out to task buffered data
756
+ self.app_frame.press_output = task_buffer
757
+ if not manage_task(self._cmd_task_tag, 'isbusy'):
758
+ # data gathered - remove tag - skip re-read
759
+ self._cmd_task_tag = None
760
+ # Display task cached data
761
+ PageUI.write_lines(self.app_frame.press_output, display, x, y + 20)
763
762
 
764
- def intercon_exec_page(self, host, cmd, run, display, w, h, x, y):
765
- """
766
- :param host: hostname or IP address of a device
767
- :param cmd: load module string command
768
- :param run: auto-run command (every page refresh)
769
- :param display: display instance
770
- :param h: frame h
771
- :param w: frame w
772
- :param x: frame x
773
- :param y: frame y
774
- """
775
- x, y = x+2, y+4
776
763
  def _execute(display, w, h, x, y):
777
- nonlocal host, cmd, run
778
- # Check open host connection
764
+ nonlocal cmd, run
779
765
  try:
780
- # Send CMD to other device & show result
781
- state, data_meta = exec_cmd(cmd + [f">>{host}"], jsonify=True)
782
- if state:
783
- self._cmd_task_tag = list(data_meta.keys())[0]
784
- verdict = list(data_meta.values())[0]
785
- if "Already running" in verdict and not run:
786
- self.app_frame.press_output = verdict # Otherwise the task start output not relevant on UI
766
+ cmd_list = cmd.strip().split()
767
+ # TASK mode: background execution, intercon: >> OR task: &
768
+ if '>>' in cmd_list[-1] or '&' in cmd_list[-1]:
769
+ # BACKGROUND: EXECUTE COMMAND
770
+ state, out = exec_cmd(cmd_list, jsonify=True) if self._cmd_task_tag is None else (False, "skip...")
771
+ if state:
772
+ self._cmd_task_tag = list(out.keys())[0]
773
+ buffer = manage_task(self._cmd_task_tag, 'show').replace(' ', '')
774
+ if buffer is not None and len(buffer) > 0:
775
+ self.app_frame.press_output = buffer
787
776
  else:
788
- self.app_frame.press_output = f"Error: {data_meta}"
777
+ # REALTIME mode: get command execution result
778
+ state, out = exec_cmd(cmd_list, jsonify=True)
779
+ self.app_frame.press_output = str(out)
789
780
  except Exception as e:
790
781
  self.app_frame.press_output = str(e)
782
+ # Print and cache output to display
783
+ PageUI.write_lines(self.app_frame.press_output, display, x, y+20)
791
784
 
792
- def _read_buffer():
793
- # Read command output from async buffer
794
- if self._cmd_task_tag is not None:
795
- task_buffer = manage_task(self._cmd_task_tag, 'show').replace(' ', '')
796
- if task_buffer is not None and len(task_buffer) > 0:
797
- # Set display out to task buffered data
798
- self.app_frame.press_output = task_buffer
799
- # data gathered - remove tag - skip re-read
800
- self._cmd_task_tag = None
801
- PageUI.write_lines(self.app_frame.press_output, display, x, y + 20, line_limit=2)
802
-
803
- PageUI.write_lines(f"{host.split(".")[0]}:{' '.join(cmd)}", display, x, y, line_limit=2)
785
+ # Write command header line and buffered output
786
+ PageUI.write_lines(cmd, display, x, y, line_limit=2)
787
+ _display_output()
788
+ # RUN command
804
789
  if run:
805
- if self._cmd_task_tag is None:
806
- _execute(display, w, h, x, y)
807
- _read_buffer()
808
- return
809
- _read_buffer()
790
+ # Automatic Execution Mode (in page refresh time)
791
+ _execute(display, w, h, x, y)
792
+ return None
793
+ # Button Press Execution Mode (callback)
810
794
  self._press_indicator(display, w, h, x, y)
811
795
  # Return "press" callback, mandatory input parameters: display, w, h, x, y
812
796
  return {"press": _execute}
813
797
 
798
+
814
799
  #################################################################################
815
800
  # Page function #
816
801
  #################################################################################
@@ -833,8 +818,14 @@ def _intercon_nodes_page(display, w, h, x, y):
833
818
  cache = hosts()["intercon"]
834
819
  if sum([1 for _ in cache]) > 0:
835
820
  for key, val in cache.items():
836
- key = key.split('.')[0]
837
- val = '.'.join(val.split('.')[-2:])
821
+ if '.' in key:
822
+ # IP splitting
823
+ key = key.split('.')[0]
824
+ val = '.'.join(val.split('.')[-2:])
825
+ else:
826
+ # MAC splitting
827
+ key = key.split(':')[0]
828
+ val = ':'.join(val.split(':')[-2:])
838
829
  display.text(f" {val} {key}", x, line_start + (line_cnt * 10))
839
830
  line_cnt += 1
840
831
  if line_cnt > line_limit:
@@ -900,7 +891,7 @@ def cursor(x, y):
900
891
  return "Set cursor position"
901
892
 
902
893
 
903
- def cmd_genpage(cmd=None, run=False):
894
+ def genpage(cmd=None, run=False):
904
895
  """
905
896
  Create load module execution pages dynamically :)
906
897
  - based on cmd value: load_module function (args)
@@ -915,30 +906,11 @@ def cmd_genpage(cmd=None, run=False):
915
906
  # Create page for intercon command
916
907
  PageUI.INSTANCE.add_page(lambda display, w, h, x, y: PageUI.INSTANCE.lm_exec_page(cmd, run, display, w, h, x, y))
917
908
  except Exception as e:
918
- syslog(f'[ERR] cmd_genpage: {e}')
909
+ syslog(f'[ERR] genpage: {e}')
919
910
  return str(e)
920
911
  return True
921
912
 
922
913
 
923
- def intercon_genpage(cmd=None, run=False):
924
- """
925
- Create intercon pages dynamically :)
926
- - based on cmd value.
927
- :param cmd: 'host hello' or 'host system clock'
928
- :param run: run button event at page init: True/False
929
- :return: page creation verdict
930
- """
931
- raw = cmd.split()
932
- host = raw[0]
933
- cmd = raw[1:]
934
- try:
935
- # Create page for intercon command
936
- PageUI.INSTANCE.add_page(lambda display, w, h, x, y: PageUI.INSTANCE.intercon_exec_page(host, cmd, run, display, w, h, x, y))
937
- except Exception as e:
938
- syslog(f'[ERR] intercon_genpage: {e}')
939
- return str(e)
940
- return True
941
-
942
914
  def add_page(page_callback):
943
915
  """
944
916
  [LM] Create page from load module with callback function
@@ -963,6 +935,5 @@ def help(widgets=False):
963
935
  "BUTTON control cmd=<prev,press,next,on,off>",
964
936
  "BUTTON debug", "cursor x y",
965
937
  "popup msg='text'", "cancel_popup",
966
- "cmd_genpage cmd='system clock'",
967
- "intercon_genpage 'host cmd' run=False"),
938
+ "genpage cmd='system clock'"),
968
939
  widgets=widgets)
@@ -1,6 +1,6 @@
1
1
  from sys import modules
2
2
  from Common import socket_stream
3
- from Files import is_protected, list_fs, ilist_fs, remove_fs, OSPath, path_join
3
+ from Files import is_protected, list_fs, ilist_fs, remove_file, remove_dir, OSPath, path_join
4
4
 
5
5
 
6
6
  #############################################
@@ -31,13 +31,22 @@ def ls(path="/", content='*', raw=False, select='*', core=False):
31
31
  return lines
32
32
 
33
33
 
34
- def rm(path, allow_dir=False):
34
+ def rm(path, force=False):
35
35
  """
36
36
  Linux like rm command - delete app resources and folders
37
37
  :param path: app resource name/path, ex.: LM_robustness.py
38
- :param allow_dir: enable directory deletion, default: False
38
+ :param force: bypasses protection check - sudo mode
39
39
  """
40
- return remove_fs(path, allow_dir)
40
+ return remove_file(path, force)
41
+
42
+
43
+ def rmdir(path, force=False):
44
+ """
45
+ Linux like rmdir command for directory deletion
46
+ :param path: app resource folder path, ex.: /lib/myapp
47
+ :param force: bypasses protection check - sudo mode
48
+ """
49
+ return remove_dir(path, force)
41
50
 
42
51
 
43
52
  def dirtree(path="/", raw=False, core=False):
@@ -64,44 +73,26 @@ def cat(path):
64
73
  return content
65
74
 
66
75
 
67
- def download(url=None, package=None):
76
+ def download(ref=None):
68
77
  """
69
- [BETA] Load Module downloader with mip
70
- :param url: github url path, ex. BxNxM/micrOS/master/toolkit/workspace/precompiled/LM_robustness.py
71
- :param package: mip package name or raw url (hack)
78
+ Unified mip-based downloader for micrOS.
79
+ Automatically detects:
80
+ 1. Official MicroPython packages (from https://micropython.org/pi/v2)
81
+ Example: pacman download "umqtt.simple"
82
+ 2. Single-file load modules (LM_/IO_ names or URLs)
83
+ Example: pacman download "https://github.com/BxNxM/micrOS/blob/master/toolkit/workspace/precompiled/modules/LM_rgb.mpy"
84
+ pacman download "github.com/BxNxM/micrOS/blob/master/toolkit/workspace/precompiled/modules/LM_rgb.mpy"
85
+ 3. GitHub packages (folders via tree/blob URLs or github: form)
86
+ Example: pacman download "github:peterhinch/micropython-mqtt"
87
+ pacman download "https://github.com/peterhinch/micropython-mqtt/tree/master"
88
+ pacman download "https://github.com/peterhinch/micropython-mqtt/blob/master/package.json"
89
+ pacman download "https://github.com/peterhinch/micropython-mqtt"
90
+ [NOK] pacman download "https://github.com/basanovase/sim7600/tree/main/sim7600" -> Package not found: github:basanovase/sim7600/package.json
91
+ 4. Install from local /config/requirements.txt file
92
+ Example: pacman download "requirements.txt"
72
93
  """
73
- def _install(target=None):
74
- nonlocal url, verdict
75
- try:
76
- verdict += f"Install {url}\n"
77
- if target is None:
78
- install(url) # Default download: /lib
79
- else:
80
- install(url, target=target) # Custom target
81
- verdict += "\n|- Done"
82
- except Exception as e:
83
- verdict += f"|- Cannot install: {url}\n{e}"
84
- return verdict
85
-
86
- from mip import install
87
- verdict = ""
88
- if url is None and package is None:
89
- return "Nothing to download, url=None package=None"
90
- if package is None:
91
- verdict += "Install from GitHub URL"
92
- base_url = "https://raw.githubusercontent.com/"
93
- file_name = url.split("/")[-1]
94
- if not(file_name.endswith("py") and file_name.startswith("LM_")):
95
- return "Invalid file name in url ending, hint: /LM_*.mpy or /LM_*.py"
96
- # Convert GitHub URL to raw content URL
97
- if "github.com" in url and "blob" in url:
98
- url = url.replace("https://github.com/", base_url).replace("/blob", "")
99
- else:
100
- url = f"{base_url}{url}"
101
- return _install(target=OSPath.MODULES) # Install module from Github URL
102
- url = package
103
- return _install() # Install official package
104
-
94
+ from Pacman import download as pm_download
95
+ return pm_download(ref)
105
96
 
106
97
  def del_duplicates(migrate=True):
107
98
  """
@@ -119,7 +110,7 @@ def del_duplicates(migrate=True):
119
110
  if m in py and m != 'main':
120
111
  to_delete = f'{m}.py'
121
112
  try:
122
- verdict = remove_fs(path_join(modules_path, to_delete))
113
+ verdict = remove_file(path_join(modules_path, to_delete))
123
114
  except:
124
115
  verdict = "n/a"
125
116
  state = False
@@ -130,7 +121,7 @@ def del_duplicates(migrate=True):
130
121
  def _migrate_from_root(_rf):
131
122
  nonlocal _deleted, files
132
123
  if _rf in files:
133
- remove_fs(path_join(OSPath._ROOT, _rf))
124
+ remove_file(path_join(OSPath._ROOT, _rf))
134
125
  if _rf in ("LM_pacman.mpy", "LM_system.mpy"):
135
126
  # Delete protected LMs from root
136
127
  remove(path_join(OSPath._ROOT, _rf))
@@ -188,7 +179,7 @@ def cachedump(delete=None, msgobj=None):
188
179
  # Remove given cache file
189
180
  try:
190
181
  delete_cache = path_join(data_dir, f"{delete}.cache")
191
- verdict = remove_fs(delete_cache)
182
+ verdict = remove_file(delete_cache)
192
183
  return f'{delete_cache} delete done.: {verdict}'
193
184
  except:
194
185
  return f'{delete}.cache not exists'
@@ -252,7 +243,7 @@ def delmod(mod):
252
243
  else:
253
244
  return f'Invalid {mod}, must ends with .py or .mpy'
254
245
  try:
255
- return remove_fs(path_join(OSPath.MODULES, to_remove))
246
+ return remove_file(path_join(OSPath.MODULES, to_remove))
256
247
  except Exception as e:
257
248
  return f'Cannot delete: {mod}: {e}'
258
249
 
@@ -286,5 +277,6 @@ def help(widgets=False):
286
277
  'micros_checksum',
287
278
  'ls path="/" content="*/f/d" select="*/LM/IO"',
288
279
  'rm <path>',
280
+ 'rmdir <path>',
289
281
  'dirtree path="/"',
290
282
  'makedir <path>')
@@ -282,7 +282,7 @@ async def apost(url, data=None, json=None, headers:dict=None, sock_size=256, jso
282
282
  return await arequest('POST', url, data=data, json=json, headers=headers, sock_size=sock_size, jsonify=jsonify)
283
283
 
284
284
 
285
- def host_cache():
285
+ def host_cache() -> dict:
286
286
  """
287
287
  Return address cache
288
288
  """