micrOSDevToolKit 2.8.6__py3-none-any.whl → 2.9.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 (104) hide show
  1. env/driver_cp210x/.DS_Store +0 -0
  2. env/driver_cp210x/macOS_VCP_Driver/SiLabsUSBDriverDisk.dmg +0 -0
  3. env/driver_cp210x/macOS_VCP_Driver/macOS_VCP_Driver_Release_Notes.txt +0 -0
  4. micrOS/micropython/esp32-20241129-v1.24.1.bin +0 -0
  5. micrOS/micropython/esp32s3-20241129-v1.24.1.bin +0 -0
  6. micrOS/micropython/esp32s3_spiram_oct-20241129-v1.24.1.bin +0 -0
  7. micrOS/micropython/rpi-pico-w-20241129-v1.24.1.uf2 +0 -0
  8. micrOS/micropython/tinypico-20241129-v1.24.1.bin +0 -0
  9. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +40 -40
  10. micrOS/source/Common.py +12 -17
  11. micrOS/source/Config.py +20 -1
  12. micrOS/source/Hooks.py +25 -15
  13. micrOS/source/LM_buzzer.py +16 -9
  14. micrOS/source/LM_cct.py +2 -3
  15. micrOS/source/LM_dimmer.py +1 -2
  16. micrOS/source/LM_distance.py +2 -4
  17. micrOS/source/LM_genIO.py +1 -1
  18. micrOS/source/LM_i2s_mic.py +4 -4
  19. micrOS/source/LM_keychain.py +2 -3
  20. micrOS/source/LM_light_sensor.py +1 -2
  21. micrOS/source/LM_neoeffects.py +22 -7
  22. micrOS/source/LM_neopixel.py +2 -3
  23. micrOS/source/LM_oledui.py +3 -4
  24. micrOS/source/LM_pet_feeder.py +3 -4
  25. micrOS/source/LM_ph_sensor.py +2 -2
  26. micrOS/source/LM_presence.py +2 -3
  27. micrOS/source/LM_rest.py +52 -9
  28. micrOS/source/LM_rgb.py +1 -2
  29. micrOS/source/LM_roboarm.py +1 -2
  30. micrOS/source/LM_sound_event.py +2 -3
  31. micrOS/source/LM_system.py +14 -3
  32. micrOS/source/LM_telegram.py +226 -15
  33. micrOS/source/Logger.py +6 -0
  34. micrOS/source/Notify.py +46 -218
  35. micrOS/source/Scheduler.py +7 -4
  36. micrOS/source/Shell.py +2 -2
  37. micrOS/source/Tasks.py +106 -98
  38. micrOS/source/Types.py +6 -2
  39. micrOS/source/Web.py +32 -8
  40. micrOS/source/dashboard.html +4 -4
  41. micrOS/source/micrOS.py +5 -10
  42. micrOS/source/micrOSloader.py +16 -18
  43. micrOS/source/uapi.js +5 -6
  44. micrOS/source/urequests.py +171 -85
  45. {micrOSDevToolKit-2.8.6.dist-info → micrOSDevToolKit-2.9.0.dist-info}/METADATA +2 -2
  46. {micrOSDevToolKit-2.8.6.dist-info → micrOSDevToolKit-2.9.0.dist-info}/RECORD +95 -97
  47. toolkit/DevEnvUSB.py +1 -6
  48. toolkit/MicrOSDevEnv.py +8 -5
  49. toolkit/simulator_lib/__pycache__/simulator.cpython-312.pyc +0 -0
  50. toolkit/simulator_lib/__pycache__/uasyncio.cpython-312.pyc +0 -0
  51. toolkit/simulator_lib/__pycache__/usocket.cpython-312.pyc +0 -0
  52. toolkit/simulator_lib/simulator.py +14 -0
  53. toolkit/simulator_lib/uasyncio.py +2 -2
  54. toolkit/simulator_lib/usocket.py +5 -1
  55. toolkit/workspace/precompiled/Common.mpy +0 -0
  56. toolkit/workspace/precompiled/Config.mpy +0 -0
  57. toolkit/workspace/precompiled/Hooks.mpy +0 -0
  58. toolkit/workspace/precompiled/LM_buzzer.mpy +0 -0
  59. toolkit/workspace/precompiled/LM_cct.mpy +0 -0
  60. toolkit/workspace/precompiled/LM_dimmer.mpy +0 -0
  61. toolkit/workspace/precompiled/LM_distance.mpy +0 -0
  62. toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
  63. toolkit/workspace/precompiled/LM_i2s_mic.mpy +0 -0
  64. toolkit/workspace/precompiled/LM_keychain.mpy +0 -0
  65. toolkit/workspace/precompiled/LM_light_sensor.mpy +0 -0
  66. toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
  67. toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
  68. toolkit/workspace/precompiled/LM_oledui.mpy +0 -0
  69. toolkit/workspace/precompiled/LM_pet_feeder.py +3 -4
  70. toolkit/workspace/precompiled/LM_ph_sensor.py +2 -2
  71. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  72. toolkit/workspace/precompiled/LM_rest.mpy +0 -0
  73. toolkit/workspace/precompiled/LM_rgb.mpy +0 -0
  74. toolkit/workspace/precompiled/LM_roboarm.mpy +0 -0
  75. toolkit/workspace/precompiled/LM_sound_event.mpy +0 -0
  76. toolkit/workspace/precompiled/LM_system.mpy +0 -0
  77. toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
  78. toolkit/workspace/precompiled/Logger.mpy +0 -0
  79. toolkit/workspace/precompiled/Notify.mpy +0 -0
  80. toolkit/workspace/precompiled/Scheduler.mpy +0 -0
  81. toolkit/workspace/precompiled/Shell.mpy +0 -0
  82. toolkit/workspace/precompiled/Tasks.mpy +0 -0
  83. toolkit/workspace/precompiled/Types.mpy +0 -0
  84. toolkit/workspace/precompiled/Web.mpy +0 -0
  85. toolkit/workspace/precompiled/dashboard.html +4 -4
  86. toolkit/workspace/precompiled/micrOS.mpy +0 -0
  87. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  88. toolkit/workspace/precompiled/node_config.json +1 -1
  89. toolkit/workspace/precompiled/uapi.js +5 -6
  90. toolkit/workspace/precompiled/urequests.mpy +0 -0
  91. env/driver_cp210x/CH34XSER_MAC/CH34X_DRV_INSTALL_INSTRUCTIONS.pdf +0 -0
  92. env/driver_cp210x/CH34XSER_MAC/CH34xVCPDriver.pkg +0 -0
  93. micrOS/micropython/esp32-20231005-v1.21.0.bin +0 -0
  94. micrOS/micropython/esp32-GENERIC-20240602-v1.23.0.bin +0 -0
  95. micrOS/micropython/rpi-pico-w-20240602-v1.23.0.uf2 +0 -0
  96. micrOS/micropython/tinypico-20231005-v1.21.0.bin +0 -0
  97. micrOS/micropython/tinypico_usbc-UM-20240105-v1.22.1.bin +0 -0
  98. /micrOS/micropython/{esp32c3-GENERIC-20240222-v1.22.2.bin → esp32c3-20240222-v1.22.2.bin} +0 -0
  99. /micrOS/micropython/{esp32s2-GENERIC-20240602-v1.23.0.bin → esp32s2-20240602-v1.23.0.bin} +0 -0
  100. /micrOS/micropython/{esp32s3-GENERIC-20240105-v1.22.1.bin → esp32s3-20240105-v1.22.1.bin} +0 -0
  101. {micrOSDevToolKit-2.8.6.data → micrOSDevToolKit-2.9.0.data}/scripts/devToolKit.py +0 -0
  102. {micrOSDevToolKit-2.8.6.dist-info → micrOSDevToolKit-2.9.0.dist-info}/LICENSE +0 -0
  103. {micrOSDevToolKit-2.8.6.dist-info → micrOSDevToolKit-2.9.0.dist-info}/WHEEL +0 -0
  104. {micrOSDevToolKit-2.8.6.dist-info → micrOSDevToolKit-2.9.0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,7 @@
1
1
  from LM_neopixel import load, segment, Data, status, pinmap as pm
2
2
  from random import randint
3
+ from Types import resolve
4
+ from Common import manage_task
3
5
 
4
6
 
5
7
  #################################
@@ -285,6 +287,13 @@ def fire(r=None, g=None, b=None, ledcnt=24):
285
287
  effect.draw(cgen, shift=False)
286
288
  return 'fire R{}:G{}:B{} N:{}'.format(r, g, b, effect.pix_cnt)
287
289
 
290
+
291
+ def stop_effects():
292
+ """
293
+ Stop all running (neo)effects tasks
294
+ """
295
+ return manage_task("neoeffects.*", "kill")
296
+
288
297
  #######################
289
298
  # LM helper functions #
290
299
  #######################
@@ -307,10 +316,16 @@ def help(widgets=False):
307
316
  (widgets=False) list of functions implemented by this application
308
317
  (widgets=True) list of widget json for UI generation
309
318
  """
310
- return 'meteor r=<0-255> g=<0-255> b=<0-255> shift=True ledcnt=24',\
311
- 'cycle r g b shift=True ledcnt=24',\
312
- 'rainbow step=1 br=<5-100> ledcnt=24',\
313
- 'fire r=None g=None b=None ledcnt=24',\
314
- 'shader size=4 offset=0 shift=True ledcnt=24',\
315
- 'random max_val=255',\
316
- 'color r g b', 'pinmap'
319
+ return resolve(('meteor r=<0-255> g=<0-255> b=<0-255> shift=True ledcnt=24',
320
+ 'BUTTON meteor &&',
321
+ 'cycle r g b shift=True ledcnt=24',
322
+ 'BUTTON cycle &&50',
323
+ 'rainbow step=1 br=<5-100> ledcnt=24 &&',
324
+ 'BUTTON rainbow br=50 &&',
325
+ 'fire r=None g=None b=None ledcnt=24',
326
+ 'BUTTON fire &&200',
327
+ 'BUTTON stop_effects',
328
+ 'shader size=4 offset=0 shift=True ledcnt=24',
329
+ 'random max_val=255',
330
+ 'pinmap',
331
+ 'COLOR color r=<0-255-10> g b'), widgets=widgets)
@@ -3,7 +3,6 @@ from machine import Pin
3
3
  from sys import platform
4
4
  from utime import sleep_ms
5
5
  from Common import transition_gen, micro_task
6
- import uasyncio as asyncio
7
6
  from microIO import resolve_pin, pinmap_search
8
7
  from random import randint
9
8
  from Types import resolve
@@ -269,7 +268,7 @@ def transition(r=None, g=None, b=None, sec=1.0, wake=False):
269
268
  Data.DCACHE[1] = g_val if g_val > 5 else 5 # SAVE VALUE TO CACHE > 5 ! because toggle
270
269
  Data.DCACHE[2] = b_val if b_val > 5 else 5 # SAVE VALUE TO CACHE > 5 ! because toggle
271
270
  my_task.out = f"Dimming: R:{r_val} G:{g_val} B:{b_val}"
272
- await asyncio.sleep_ms(ms_period)
271
+ await my_task.feed(sleep_ms=ms_period)
273
272
  if Data.DCACHE[3] == 1 or wake:
274
273
  __state_machine(r=r_val, g=g_val, b=b_val)
275
274
  my_task.out = f"Dimming DONE: R:{r_val} G:{g_val} B:{b_val}"
@@ -346,7 +345,7 @@ def help(widgets=False):
346
345
  'BUTTON toggle state=<True,False> smooth=True',
347
346
  'load ledcnt=24',
348
347
  'SLIDER brightness percent=<0-100> smooth=True wake=True',
349
- 'COLOR segment r g b s=<0-n>',
348
+ 'segment r g b s=<0-n>',
350
349
  'transition r g b sec=1.0 wake=False',
351
350
  'BUTTON random smooth=True max_val=254',
352
351
  'status',
@@ -1,5 +1,4 @@
1
1
  from utime import localtime, ticks_ms, ticks_diff, sleep_ms
2
- import uasyncio as asyncio
3
2
  from Common import syslog, micro_task, manage_task, exec_cmd
4
3
  from Types import resolve
5
4
  # Core modules
@@ -106,7 +105,7 @@ class Frame(BaseFrame):
106
105
  my_task.out = 'paused' if self.paused else f'refresh: {period_ms} ms'
107
106
  s = self.paused
108
107
  if self.paused:
109
- await asyncio.sleep_ms(period_ms) # extra wait in paused mode
108
+ await my_task.feed(sleep_ms=period_ms) # extra wait in paused mode
110
109
  else:
111
110
  # Draw/Refresh frame
112
111
  self.draw()
@@ -115,7 +114,7 @@ class Frame(BaseFrame):
115
114
  if self._fast_refresh:
116
115
  self._fast_refresh = False
117
116
  break
118
- await asyncio.sleep_ms(micro_sleep_ms)
117
+ await my_task.feed(sleep_ms=micro_sleep_ms)
119
118
 
120
119
  def clb_refresh(self):
121
120
  """Fast reload app loop callbacks"""
@@ -486,7 +485,7 @@ class ScreenSaver(BaseFrame):
486
485
  # Store data in task cache (task show mytask)
487
486
  my_task.out = f'GameOfLife: {counter}'
488
487
  # Async sleep - feed event loop
489
- await asyncio.sleep_ms(period_ms)
488
+ await my_task.feed(sleep_ms=period_ms)
490
489
  my_task.out = f'GameOfLife stopped: {counter}'
491
490
 
492
491
  def run(self, fps=10):
@@ -1,4 +1,3 @@
1
- import uasyncio as asyncio
2
1
  from LM_servo import sduty, deinit, pinmap as servo_pinmap
3
2
  from LM_stepper import step, pinmap as stepper_pinmap
4
3
  from Common import micro_task
@@ -16,13 +15,13 @@ async def __portion_task(portion, posmin, posmax):
16
15
  # [1]Run pos fill up
17
16
  for pos in range(posmin, posmax):
18
17
  sduty(pos)
19
- await asyncio.sleep_ms(15)
18
+ await my_task.feed(sleep_ms=15)
20
19
  # [2]Wait between fill up / food out
21
- await asyncio.sleep_ms(500)
20
+ await my_task.feed(sleep_ms=500)
22
21
  # [3]Run pos food out
23
22
  for pos in range(posmax, posmin, -1):
24
23
  sduty(pos)
25
- await asyncio.sleep_ms(20)
24
+ await my_task.feed(sleep_ms=20)
26
25
  my_task.out = "{}/{} serving".format(p+1, portion)
27
26
  deinit()
28
27
  my_task.out += ": {} task done".format(Data.TASK_TAG)
@@ -12,7 +12,7 @@ ADC.ATTN_11DB — the full range voltage: 3.3V
12
12
 
13
13
 
14
14
  def __measure(samples=10):
15
- adc_obj = SmartADC.get_singleton(resolve_pin('ph'))
15
+ adc_obj = SmartADC.get_instance(resolve_pin('ph'))
16
16
  mbuf = 0
17
17
  for k in range(0, samples):
18
18
  mbuf += adc_obj.get()['volt']
@@ -22,7 +22,7 @@ def __measure(samples=10):
22
22
 
23
23
 
24
24
  def measure():
25
- data = SmartADC.get_singleton(resolve_pin('ph')).get()
25
+ data = SmartADC.get_instance(resolve_pin('ph')).get()
26
26
  return "ADC data: {}\nAVG V: {}".format(data, __measure())
27
27
 
28
28
 
@@ -1,6 +1,5 @@
1
1
  from microIO import resolve_pin, pinmap_search
2
2
  from Common import SmartADC, micro_task, notify, syslog
3
- import uasyncio as asyncio
4
3
  from utime import ticks_ms
5
4
  try:
6
5
  import LM_intercon as InterCon
@@ -97,7 +96,7 @@ async def __task(ms_period, buff_size):
97
96
 
98
97
  if Data.MIC_TYPE == Data.MIC_TYPES['ADC']:
99
98
  # Create ADC object
100
- Data.MIC_ADC = SmartADC.get_singleton(resolve_pin('mic'))
99
+ Data.MIC_ADC = SmartADC.get_instance(resolve_pin('mic'))
101
100
  elif Data.MIC_TYPE == Data.MIC_TYPES['I2S']:
102
101
  if Data.I2S_MIC is None:
103
102
  import LM_i2s_mic
@@ -115,7 +114,7 @@ async def __task(ms_period, buff_size):
115
114
  my_task.out = f"{int(Data.OFF_EV_TIMER)-1} sec until off event"
116
115
  Data.OFF_EV_TIMER -= round(ms_period / 1000, 3)
117
116
  # Async sleep - feed event loop
118
- await asyncio.sleep_ms(ms_period)
117
+ await my_task.feed(sleep_ms=ms_period)
119
118
 
120
119
  # RUN OFF CALLBACK (local + remote)
121
120
  __exec_local_callbacks(Data.OFF_CALLBACKS)
micrOS/source/LM_rest.py CHANGED
@@ -1,4 +1,8 @@
1
+ import uasyncio as asyncio
1
2
  from urequests import get as http_get
3
+ from urequests import aget as http_aget
4
+ from Common import micro_task
5
+
2
6
 
3
7
  class Rest:
4
8
  GATEWAY_HOST = None
@@ -13,23 +17,60 @@ def load(gateway_url=None):
13
17
  if Rest.GATEWAY_HOST is None:
14
18
  return 'Missing Gateway url'
15
19
  return Rest.GATEWAY_HOST
16
- Rest.GATEWAY_HOST = gateway_url
17
- return f'Set gateway url: {gateway_url}'
20
+ if gateway_url.startswith("http"):
21
+ Rest.GATEWAY_HOST = gateway_url
22
+ else:
23
+ return f"URL have to starts with http/https"
24
+ return f'Gateway url: {gateway_url}'
18
25
 
19
26
 
20
- def url(subdomain):
21
- """
22
- Execute rest call with given subdomain
23
- :param subdomain: command parameters of url, like: /webhooks/template
24
- """
27
+ def _subdomain(subdomain):
25
28
  if Rest.GATEWAY_HOST is None:
26
- return 'Missing Gateway url'
29
+ raise Exception('Missing Gateway url')
27
30
  if not subdomain.startswith('/'):
28
31
  subdomain = '/' + subdomain
29
32
  domain = f'{Rest.GATEWAY_HOST}{subdomain}'
33
+ return domain
34
+
35
+
36
+ def url(subdomain):
37
+ """
38
+ Execute rest call with given subdomain / url
39
+ :param subdomain: url parameter, http(s) full url or gateway subdomain like: /webhooks/template
40
+ """
41
+ if subdomain.startswith('http'):
42
+ domain = subdomain
43
+ else:
44
+ domain = _subdomain(subdomain)
30
45
  status_code, response = http_get(domain, jsonify=True)
31
46
  return {'status': status_code, 'response': response}
32
47
 
48
+
49
+ async def __task(subdomain, tag):
50
+ with micro_task(tag=tag) as my_task:
51
+ my_task.out = f"GET {subdomain}"
52
+ if subdomain.startswith('http'):
53
+ domain = subdomain
54
+ else:
55
+ domain = _subdomain(subdomain)
56
+ status_code, response = await http_aget(domain, jsonify=True)
57
+ my_task.out = f'status: {status_code}, response: {response}'
58
+ return {'status': status_code, 'response': response}
59
+
60
+
61
+ def aurl(subdomain):
62
+ """
63
+ Execute async rest call with given subdomain / url
64
+ :param subdomain: url parameter, http(s) full url or gateway subdomain like: /webhooks/template
65
+ """
66
+ # [!] ASYNC TASK CREATION [1*] with async task callback + taskID (TAG) handling
67
+ tag = "rest." + subdomain.replace("http://", '').replace("https://", '')
68
+ if len(tag) > 50:
69
+ tag = tag[0:50]
70
+ state = micro_task(tag=tag, task=__task(subdomain, tag))
71
+ return f"Starting" if state else f"Already running"
72
+
73
+
33
74
  def help(widgets=False):
34
75
  """
35
76
  [i] micrOS LM naming convention - built-in help message
@@ -37,4 +78,6 @@ def help(widgets=False):
37
78
  (widgets=False) list of functions implemented by this application
38
79
  (widgets=True) list of widget json for UI generation
39
80
  """
40
- return 'load gateway_url=<http://gateway.local:5000>', 'url subdomain=</webhooks/template>'
81
+ return ('load gateway_url=<http://gateway.local:5000>',
82
+ 'url subdomain=</webhooks/template>',
83
+ 'aurl subdomain=</webhooks/template>')
micrOS/source/LM_rgb.py CHANGED
@@ -4,7 +4,6 @@
4
4
  from machine import Pin, PWM
5
5
  from sys import platform
6
6
  from Common import transition_gen, micro_task
7
- import uasyncio as asyncio
8
7
  from utime import sleep_ms
9
8
  from microIO import resolve_pin, pinmap_search
10
9
  from random import randint
@@ -240,7 +239,7 @@ def transition(r=None, g=None, b=None, sec=1.0, wake=False):
240
239
  Data.RGB_CACHE[1] = _g if _g > 5 else 5 # SAVE VALUE TO CACHE > 5 ! because toggle
241
240
  Data.RGB_CACHE[2] = _b if _b > 5 else 5 # SAVE VALUE TO CACHE > 5 ! because toggle
242
241
  my_task.out = f"Dimming: R:{_r} G:{_g} B:{_b}"
243
- await asyncio.sleep_ms(ms_period)
242
+ await my_task.feed(sleep_ms=ms_period)
244
243
  if Data.RGB_CACHE[3] == 1 or wake:
245
244
  __state_machine(_r, _g, _b)
246
245
  my_task.out = f"Dimming DONE: R:{_r} G:{_g} B:{_b}"
@@ -3,7 +3,6 @@ from random import randint
3
3
  import LM_servo as servo
4
4
  from LM_switch import set_state, pinmap as switch_pinmap
5
5
  from Common import transition, micro_task
6
- import uasyncio as asyncio
7
6
  from Types import resolve
8
7
 
9
8
 
@@ -179,7 +178,7 @@ async def _play(args, deinit, delay):
179
178
  if task is not None:
180
179
  task.out = "Roboarm X:{} Y:{}".format(x, y)
181
180
  # Async wait between steps
182
- await asyncio.sleep_ms(delay)
181
+ await task.feed(sleep_ms=delay)
183
182
  if deinit:
184
183
  servo.deinit()
185
184
  # OFF LASER
@@ -37,7 +37,6 @@ Terminal ready
37
37
  """
38
38
 
39
39
  import LM_i2s_mic
40
- import uasyncio as asyncio
41
40
  import json
42
41
 
43
42
  from microIO import pinmap_search
@@ -647,7 +646,7 @@ async def __control_task(capture_duration_ms,
647
646
  :param pause_duration_ms: int - duration of pause frames to distinguish events
648
647
  :param event_buffer_length: int - number of events to store at once
649
648
  """
650
- with micro_task(tag=Data.CONTROL_TASK_TAG):
649
+ with micro_task(tag=Data.CONTROL_TASK_TAG) as my_task:
651
650
  samples = []
652
651
  frame_size = int((frame_size_ms/1000)*LM_i2s_mic.Data.SAMPLING_RATE)
653
652
  pause_duration = int(pause_duration_ms/frame_size_ms)
@@ -674,7 +673,7 @@ async def __control_task(capture_duration_ms,
674
673
 
675
674
  # Wait for new samples to be taken
676
675
  ms_period = int(len(new_samples)/LM_i2s_mic.Data.SAMPLING_RATE)
677
- await asyncio.sleep_ms(ms_period)
676
+ await my_task.feed(sleep_ms=ms_period)
678
677
  Data.EVENTS = Data.EVENTS[-event_buffer_length:]
679
678
  except Exception as e:
680
679
  console(f'[ERR] sound_event: {e}')
@@ -7,6 +7,7 @@ from Network import get_mac, ifconfig as network_config
7
7
  from Time import ntp_time, set_time, uptime
8
8
  from Tasks import Manager
9
9
  from Types import resolve
10
+ from Notify import Notify
10
11
 
11
12
  try:
12
13
  from gc import mem_free, mem_alloc, collect
@@ -177,15 +178,16 @@ def list_stations():
177
178
  return [("NoAP", '')]
178
179
 
179
180
 
180
- def pinmap(key='builtin'):
181
+ def pinmap(keys='builtin irq1 irq2 irq3 irq4'):
181
182
  """
182
183
  Get Logical pin by key runtime
183
- :param key str: logical pin name to resolve
184
+ :param keys str: logical pin name or names to resolve
184
185
  :return dict: key map
185
186
  """
186
187
  from microIO import pinmap_search, pinmap_info
187
188
  map = pinmap_info()
188
- map[key] = pinmap_search(key)[key]
189
+ keys = keys.split()
190
+ map["search"] = pinmap_search(keys)
189
191
  return map
190
192
 
191
193
  @socket_stream
@@ -232,6 +234,14 @@ def urequests_host_cache():
232
234
  return host_cache()
233
235
 
234
236
 
237
+ def notifications(enable=None):
238
+ """
239
+ Global notifications control for micrOS
240
+ :param enable: True: Enable notifications / False: Disable notifications
241
+ return: state verdict
242
+ """
243
+ return Notify.notifications(state=enable)
244
+
235
245
  #######################
236
246
  # LM helper functions #
237
247
  #######################
@@ -246,5 +256,6 @@ def help(widgets=False):
246
256
  return resolve(('info', 'TEXTBOX top', 'gclean', 'heartbeat', 'clock',
247
257
  'setclock year month mday hour min sec',
248
258
  'ntp', 'rssi', 'list_stations', 'pinmap key="dhtpin"/None', 'alarms clean=False',
259
+ 'notifications enable=<None,True,False>',
249
260
  'sun refresh=False', 'ifconfig', 'memory_usage',
250
261
  'disk_usage', 'dat_dump', 'urequests_host_cache'), widgets=widgets)
@@ -1,7 +1,229 @@
1
+ from sys import modules
1
2
  import uasyncio as asyncio
2
- from Notify import Telegram
3
- from Common import micro_task, syslog
4
- from Network import ifconfig
3
+ import urequests
4
+ from Notify import Notify
5
+ from Config import cfgget
6
+ from Common import micro_task, syslog, console_write
7
+ from LM_system import ifconfig
8
+
9
+
10
+ class Telegram(Notify):
11
+ # Telegram bot token and chat ID
12
+ # https://core.telegram.org/bots/api
13
+ _TOKEN = None # Telegram token
14
+ _CHAT_IDS = set() # Telegram bot chat IDs - multi group support - persistent caching
15
+ _API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
16
+ _IN_MSG_ID = None
17
+
18
+ @staticmethod
19
+ def __id_cache(mode):
20
+ """
21
+ pds - persistent data structure
22
+ modes:
23
+ r - recover, s - save
24
+ """
25
+ if mode == 's':
26
+ # SAVE CACHE
27
+ console_write("[NTFY] Save chatIDs cache...")
28
+ with open('telegram.pds', 'w') as f:
29
+ f.write(','.join([str(k) for k in Telegram._CHAT_IDS]))
30
+ return
31
+ try:
32
+ # RESTORE CACHE
33
+ console_write("[NTFY] Restore chatIDs cache...")
34
+ with open('telegram.pds', 'r') as f:
35
+ # set() comprehension
36
+ Telegram._CHAT_IDS = {int(k) for k in f.read().strip().split(',')}
37
+ except:
38
+ pass
39
+
40
+ @staticmethod
41
+ def __bot_token():
42
+ """Get bot token"""
43
+ if Telegram._TOKEN is None:
44
+ token = cfgget('telegram')
45
+ if token is None or token == 'n/a':
46
+ return None
47
+ Telegram._TOKEN = token
48
+ return Telegram._TOKEN
49
+
50
+ @staticmethod
51
+ def send_msg(text, reply_to=None, chat_id=None):
52
+ """
53
+ Send a message to the Telegram chat by chat_id
54
+ :param text: text to send
55
+ :param reply_to: reply to specific message, if None, simple reply
56
+ :param chat_id: chat_id to reply on, if None, reply to all known
57
+ RETURN None when telegram bot token is missing
58
+ """
59
+
60
+ def _send(chid):
61
+ """Send message to chat_id (chid)"""
62
+ data = {"chat_id": chid, "text": f"{Telegram._DEVFID}⚙️\n{text}"}
63
+ if isinstance(reply_to, int):
64
+ data['reply_to_message_id'] = reply_to
65
+ Telegram._IN_MSG_ID = reply_to
66
+ _, _resp = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True,
67
+ sock_size=128)
68
+ console_write(f"\tSend message:\n{data}\nresponse:\n{_resp}")
69
+ return _resp
70
+
71
+ def _get_chat_ids():
72
+ """Return chat ID or None (in case of no token or cannot get ID)"""
73
+ if len(Telegram._CHAT_IDS) == 0:
74
+ Telegram.get_msg() # It will update the Telegram.CHAT_IDS
75
+ console_write(f"\tGet chatIDs: {Telegram._CHAT_IDS}")
76
+ return Telegram._CHAT_IDS
77
+
78
+ # --------------------- FUNCTION MAIN ------------------------ #
79
+ console_write("[NTFY] SEND MESSAGE")
80
+ # Check bot token
81
+ bot_token = Telegram.__bot_token()
82
+ if bot_token is None:
83
+ return None
84
+ url = f"https://api.telegram.org/bot{bot_token}/sendMessage{Telegram._API_PARAMS}"
85
+
86
+ verdict = ""
87
+ # Reply to ALL (notification) - chat_id was not provided
88
+ if chat_id is None:
89
+ console_write("\tREPLY ALL")
90
+ for _chat_id in _get_chat_ids():
91
+ resp_json = _send(chid=_chat_id)
92
+ verdict += f'Sent{_chat_id};' if resp_json['ok'] else str(resp_json)
93
+ else:
94
+ console_write(f"\tREPLY TO {chat_id}")
95
+ # Direct reply to chat_id
96
+ resp_json = _send(chid=chat_id)
97
+ verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
98
+ return verdict
99
+
100
+ @staticmethod
101
+ def get_msg():
102
+ """
103
+ Get the last message from the Telegram chat.
104
+ RETURN None when telegram bot token is missing
105
+ """
106
+
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
128
+
129
+ # --------------------- FUNCTION MAIN ------------------------ #
130
+ console_write("[NTFY] GET MESSAGE")
131
+ bot_token = Telegram.__bot_token()
132
+ if bot_token is None:
133
+ return None
134
+ response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
135
+ url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
136
+ console_write(f"\t1/2[GET] request: {url}")
137
+ _, resp_json = urequests.get(url, jsonify=True, sock_size=128)
138
+ if len(resp_json["result"]) > 0:
139
+ response['c_id'] = _update_chat_ids()
140
+ resp = resp_json["result"][-1]["message"]
141
+ response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get(
142
+ 'username', None) is None else resp['chat']['username']
143
+ response['text'], response['m_id'] = resp['text'], resp['message_id']
144
+ console_write(f"\t2/2[GET] response: {response}")
145
+ return response
146
+
147
+ @staticmethod
148
+ def receive_eval():
149
+ """
150
+ READ - VALIDATE - EXECUTE - REPLY LOOP
151
+ - can be used in async loop
152
+ RETURN None when telegram bot token is missing
153
+ """
154
+
155
+ console_write("[NTFY] REC&EVAL sequence")
156
+
157
+ # Return data structure template
158
+ verdict = None
159
+
160
+ def lm_execute(cmd_args):
161
+ nonlocal verdict, m_id
162
+ access, output = Telegram.lm_execute(cmd_args)
163
+ if access:
164
+ verdict = f'[UP] Exec: {" ".join(cmd_args[0])}'
165
+ Telegram.send_msg(output, reply_to=m_id)
166
+ else:
167
+ verdict = f'[UP] NoAccess: {cmd_args[0]}'
168
+ Telegram._IN_MSG_ID = m_id
169
+
170
+ # -------------------------- FUNCTION MAIN -------------------------- #
171
+ # Poll telegram chat
172
+ data = Telegram.get_msg()
173
+ if data is None:
174
+ return data
175
+ # Get msg, msg_id, chat_id as main input data source
176
+ msg_in, m_id, c_id = data['text'], data['m_id'], data['c_id']
177
+ if msg_in is not None and m_id != Telegram._IN_MSG_ID:
178
+ # replace single/double quotation to apostrophe (str syntax for repl interpretation)
179
+ msg_in = msg_in.replace('‘', "'").replace('’', "'").replace('“', '"').replace('”', '"')
180
+ if msg_in.startswith('/ping'):
181
+ # Parse loaded modules
182
+ _loaded_mods = [lm.replace('LM_', '') for lm in modules if lm.startswith('LM_')] + ['task']
183
+ Telegram.send_msg(', '.join(_loaded_mods), reply_to=m_id, chat_id=c_id)
184
+ elif msg_in.startswith('/cmd_select'):
185
+ cmd_lm = msg_in.strip().split()[1:]
186
+ # [Compare] cmd selected device param with DEVFID (device/prompt name)
187
+ if cmd_lm[0] in Telegram._DEVFID:
188
+ lm_execute(cmd_lm[1:])
189
+ else:
190
+ verdict = f'[UP] NoSelected: {cmd_lm[0]}'
191
+ elif msg_in.startswith('/cmd'):
192
+ cmd_lm = msg_in.strip().split()[1:]
193
+ lm_execute(cmd_lm)
194
+ elif msg_in.startswith('/notify'):
195
+ param = msg_in.strip().split()[1:]
196
+ if len(param) > 0:
197
+ verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
198
+ else:
199
+ verdict = Telegram.notifications()
200
+ Telegram.send_msg(verdict, reply_to=m_id)
201
+ else:
202
+ verdict = "[UP] NoExec"
203
+ console_write(f"\tREC&EVAL: {verdict}")
204
+ return verdict
205
+
206
+ @staticmethod
207
+ def set_commands():
208
+ """
209
+ Set Custom Commands to the Telegram chat.
210
+ RETURN None when telegram bot token is missing
211
+ """
212
+
213
+ console_write("[NTFY] SET DEFAULT COMMANDS")
214
+ bot_token = Telegram.__bot_token()
215
+ if bot_token is None:
216
+ return None
217
+ url = f"https://api.telegram.org/bot{bot_token}/setMyCommands{Telegram._API_PARAMS}"
218
+ data = {"commands": [{"command": "ping", "description": "Ping All endpoints: list of active modules."},
219
+ {"command": "notify", "description": "Enable/Disable notifications: on or off"},
220
+ {"command": "cmd", "description": "Run active module function on all devices."},
221
+ {"command": "cmd_select",
222
+ "description": "Same as cmd, only first param must be device name."},
223
+ ]}
224
+ _, resp_json = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True,
225
+ sock_size=128)
226
+ return 'Custom commands was set' if resp_json['ok'] else str(resp_json)
5
227
 
6
228
  #########################################
7
229
  # micrOS Notifications #
@@ -15,6 +237,7 @@ def __init():
15
237
  _sta_available = True if ifconfig()[0] == "STA" else False
16
238
  if _sta_available:
17
239
  TELEGRAM_OBJ = Telegram()
240
+ Notify.add_subscriber(TELEGRAM_OBJ)
18
241
  else:
19
242
  syslog("No STA: cannot init telegram")
20
243
 
@@ -103,17 +326,6 @@ def receiver_loop():
103
326
  return "Starting" if state else "Already running"
104
327
 
105
328
 
106
- def notifications(enable=None):
107
- """
108
- Global notifications control for micrOS
109
- :param enable: True: Enable notifications / False: Disable notifications
110
- return: state verdict
111
- """
112
- if enable is None:
113
- enable = not Telegram.GLOBAL_NOTIFY
114
- return Telegram.notifications(state=enable)
115
-
116
-
117
329
  def help(widgets=False):
118
330
  """
119
331
  [i] micrOS LM naming convention - built-in help message
@@ -125,5 +337,4 @@ def help(widgets=False):
125
337
  'receive',
126
338
  'receiver_loop',
127
339
  'notify "message"',
128
- 'notifications enable=False',
129
340
  'load', 'INFO: Send & Receive messages with Telegram bot')
micrOS/source/Logger.py CHANGED
@@ -1,3 +1,9 @@
1
+ """
2
+ Module is responsible for System and User logging
3
+ - built-in log rotation
4
+
5
+ Designed by Marcell Ban aka BxNxM
6
+ """
1
7
  from time import localtime
2
8
  from os import listdir, remove
3
9