micrOSDevToolKit 2.8.7__py3-none-any.whl → 2.9.1__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.
Files changed (42) hide show
  1. micrOS/release_info/micrOS_ReleaseInfo/system_analysis_sum.json +22 -22
  2. micrOS/source/Common.py +10 -15
  3. micrOS/source/Config.py +20 -1
  4. micrOS/source/LM_buzzer.py +15 -7
  5. micrOS/source/LM_genIO.py +1 -1
  6. micrOS/source/LM_neoeffects.py +22 -7
  7. micrOS/source/LM_neopixel.py +1 -1
  8. micrOS/source/LM_ph_sensor.py +2 -2
  9. micrOS/source/LM_presence.py +1 -1
  10. micrOS/source/LM_system.py +14 -3
  11. micrOS/source/LM_telegram.py +226 -15
  12. micrOS/source/Logger.py +6 -0
  13. micrOS/source/Notify.py +46 -217
  14. micrOS/source/Shell.py +4 -3
  15. micrOS/source/Types.py +6 -2
  16. micrOS/source/Web.py +16 -3
  17. micrOS/source/micrOSloader.py +16 -13
  18. {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/METADATA +20 -7
  19. {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/RECORD +42 -42
  20. toolkit/DevEnvUSB.py +2 -1
  21. toolkit/MicrOSDevEnv.py +8 -5
  22. toolkit/workspace/precompiled/Common.mpy +0 -0
  23. toolkit/workspace/precompiled/Config.mpy +0 -0
  24. toolkit/workspace/precompiled/LM_buzzer.mpy +0 -0
  25. toolkit/workspace/precompiled/LM_genIO.mpy +0 -0
  26. toolkit/workspace/precompiled/LM_neoeffects.mpy +0 -0
  27. toolkit/workspace/precompiled/LM_neopixel.mpy +0 -0
  28. toolkit/workspace/precompiled/LM_ph_sensor.py +2 -2
  29. toolkit/workspace/precompiled/LM_presence.mpy +0 -0
  30. toolkit/workspace/precompiled/LM_system.mpy +0 -0
  31. toolkit/workspace/precompiled/LM_telegram.mpy +0 -0
  32. toolkit/workspace/precompiled/Logger.mpy +0 -0
  33. toolkit/workspace/precompiled/Notify.mpy +0 -0
  34. toolkit/workspace/precompiled/Shell.mpy +0 -0
  35. toolkit/workspace/precompiled/Types.mpy +0 -0
  36. toolkit/workspace/precompiled/Web.mpy +0 -0
  37. toolkit/workspace/precompiled/micrOSloader.mpy +0 -0
  38. toolkit/workspace/precompiled/node_config.json +1 -1
  39. {micrOSDevToolKit-2.8.7.data → micrOSDevToolKit-2.9.1.data}/scripts/devToolKit.py +0 -0
  40. {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/LICENSE +0 -0
  41. {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/WHEEL +0 -0
  42. {micrOSDevToolKit-2.8.7.dist-info → micrOSDevToolKit-2.9.1.dist-info}/top_level.txt +0 -0
@@ -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
 
micrOS/source/Notify.py CHANGED
@@ -1,247 +1,76 @@
1
- from sys import modules
2
- import urequests
1
+ """
2
+ Module is responsible for Notification handling
3
+ Common:
4
+ - notifications None/True/False
5
+ - notify 'text'
6
+ - lm_execute
7
+ Supported notification subscribers (add_subscriber)
8
+ - LM_telegram
9
+ Designed by Marcell Ban aka BxNxM
10
+ """
11
+
3
12
  from Config import cfgget
4
13
  from Tasks import lm_exec, lm_is_loaded
5
- from Debug import console_write
14
+ from Debug import errlog_add
6
15
 
7
16
  #########################################
8
17
  # micrOS Notifications #
9
18
  # with Telegram Class #
10
19
  #########################################
11
-
12
- class Telegram:
13
- # Telegram bot token and chat ID
14
- # https://core.telegram.org/bots/api
15
- GLOBAL_NOTIFY = True # Enable Global notifications
16
- _TOKEN = None # Telegram token
17
- _CHAT_IDS = set() # Telegram bot chat IDs - multi group support - persistent caching
18
- _API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
19
- _DEVFID = cfgget('devfid') # For reply message (pre text)
20
- _IN_MSG_ID = None
20
+ class Notify:
21
+ GLOBAL_NOTIFY = True # Enable Global notifications
22
+ _DEVFID = cfgget('devfid') # For reply message (pre text)
23
+ _SUBSCRIBERS = set() # Store set of notification objects: send_msg
21
24
 
22
25
  @staticmethod
23
- def __id_cache(mode):
26
+ def add_subscriber(instance):
24
27
  """
25
- pds - persistent data structure
26
- modes:
27
- r - recover, s - save
28
+ Add Notification agent like: Telegram
28
29
  """
29
- if mode == 's':
30
- # SAVE CACHE
31
- console_write("[NTFY] Save chatIDs cache...")
32
- with open('telegram.pds', 'w') as f:
33
- f.write(','.join([str(k) for k in Telegram._CHAT_IDS]))
34
- return
35
- try:
36
- # RESTORE CACHE
37
- console_write("[NTFY] Restore chatIDs cache...")
38
- with open('telegram.pds', 'r') as f:
39
- # set() comprehension
40
- Telegram._CHAT_IDS = {int(k) for k in f.read().strip().split(',')}
41
- except:
42
- pass
43
-
44
- @staticmethod
45
- def __bot_token():
46
- """Get bot token"""
47
- if Telegram._TOKEN is None:
48
- token = cfgget('telegram')
49
- if token is None or token == 'n/a':
50
- return None
51
- Telegram._TOKEN = token
52
- return Telegram._TOKEN
30
+ if isinstance(instance, Notify):
31
+ Notify._SUBSCRIBERS.add(instance)
32
+ return True
33
+ raise Exception("Subscribe error, Notify parent missing")
53
34
 
54
35
  @staticmethod
55
- def send_msg(text, reply_to=None, chat_id=None):
36
+ def message(text, reply_to=None, chat_id=None):
56
37
  """
57
- Send a message to the Telegram chat by chat_id
58
- :param text: text to send
59
- :param reply_to: reply to specific message, if None, simple reply
60
- :param chat_id: chat_id to reply on, if None, reply to all known
61
- RETURN None when telegram bot token is missing
38
+ Send message to all subscribers
62
39
  """
63
- def _send(chid):
64
- """Send message to chat_id (chid)"""
65
- data = {"chat_id": chid, "text": f"{Telegram._DEVFID}⚙️\n{text}"}
66
- if isinstance(reply_to, int):
67
- data['reply_to_message_id'] = reply_to
68
- Telegram._IN_MSG_ID = reply_to
69
- _, _resp = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True, sock_size=128)
70
- console_write(f"\tSend message:\n{data}\nresponse:\n{_resp}")
71
- return _resp
72
-
73
- def _get_chat_ids():
74
- """Return chat ID or None (in case of no token or cannot get ID)"""
75
- if len(Telegram._CHAT_IDS) == 0:
76
- Telegram.get_msg() # It will update the Telegram.CHAT_IDS
77
- console_write(f"\tGet chatIDs: {Telegram._CHAT_IDS}")
78
- return Telegram._CHAT_IDS
79
-
80
- # --------------------- FUNCTION MAIN ------------------------ #
81
- console_write("[NTFY] SEND MESSAGE")
82
- # Check bot token
83
- bot_token = Telegram.__bot_token()
84
- if bot_token is None:
85
- return None
86
- url = f"https://api.telegram.org/bot{bot_token}/sendMessage{Telegram._API_PARAMS}"
87
-
88
- verdict = ""
89
- # Reply to ALL (notification) - chat_id was not provided
90
- if chat_id is None:
91
- console_write("\tREPLY ALL")
92
- for _chat_id in _get_chat_ids():
93
- resp_json = _send(chid=_chat_id)
94
- verdict += f'Sent{_chat_id};' if resp_json['ok'] else str(resp_json)
95
- else:
96
- console_write(f"\tREPLY TO {chat_id}")
97
- # Direct reply to chat_id
98
- resp_json = _send(chid=chat_id)
99
- verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
100
- return verdict
40
+ exit_code = 0
41
+ for s in Notify._SUBSCRIBERS:
42
+ try:
43
+ s.send_msg(text, reply_to, chat_id)
44
+ except Exception as e:
45
+ errlog_add(f"[ERR] Notify: {e}")
46
+ exit_code+=1
47
+ return f"Sent N{len(Notify._SUBSCRIBERS)} ({exit_code})"
101
48
 
102
49
  @staticmethod
103
50
  def notifications(state=None):
104
51
  """
105
52
  Setter for disable/enable notification messages
106
53
  """
107
- if state is not None:
108
- Telegram.GLOBAL_NOTIFY = state
109
- return f"Notifications: {'enabled' if Telegram.GLOBAL_NOTIFY else 'disabled'}"
110
-
54
+ if isinstance(state, bool):
55
+ Notify.GLOBAL_NOTIFY = state
56
+ return f"Notifications: {'enabled' if Notify.GLOBAL_NOTIFY else 'disabled'}"
111
57
 
112
58
  @staticmethod
113
59
  def notify(text, reply_to=None, chat_id=None):
114
60
  """
115
61
  Notification sender
116
62
  """
117
- if Telegram.GLOBAL_NOTIFY:
118
- return Telegram.send_msg(text, reply_to, chat_id)
63
+ if Notify.GLOBAL_NOTIFY:
64
+ return Notify.message(text, reply_to, chat_id)
119
65
  return "Notifications disabled"
120
66
 
121
67
  @staticmethod
122
- def get_msg():
123
- """
124
- Get the last message from the Telegram chat.
125
- RETURN None when telegram bot token is missing
126
- """
127
-
128
- def _update_chat_ids():
129
- """
130
- Update known chat_id-s and cache them
131
- - return active chat_id frm resp_json
132
- """
133
- console_write("[NTFY GET] update chatIDs")
134
- _cid = None
135
- if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
136
- _cid = resp_json["result"][-1]["message"]["chat"]["id"]
137
- # LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
138
- if len(Telegram._CHAT_IDS) < 4:
139
- _ids = len(Telegram._CHAT_IDS)
140
- Telegram._CHAT_IDS.add(_cid)
141
- if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
142
- Telegram.__id_cache('s')
143
- else:
144
- Telegram.__id_cache('r')
145
- if len(Telegram._CHAT_IDS) == 0:
146
- error_message = resp_json.get("description", "Unknown error")
147
- raise Exception(f"Error retrieving chat ID: {error_message}")
148
- return _cid
149
-
150
- # --------------------- FUNCTION MAIN ------------------------ #
151
- console_write("[NTFY] GET MESSAGE")
152
- bot_token = Telegram.__bot_token()
153
- if bot_token is None:
154
- return None
155
- response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
156
- url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
157
- console_write(f"\t1/2[GET] request: {url}")
158
- _, resp_json = urequests.get(url, jsonify=True, sock_size=128)
159
- if len(resp_json["result"]) > 0:
160
- response['c_id'] = _update_chat_ids()
161
- resp = resp_json["result"][-1]["message"]
162
- response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get('username', None) is None else resp['chat']['username']
163
- response['text'], response['m_id'] = resp['text'], resp['message_id']
164
- console_write(f"\t2/2[GET] response: {response}")
165
- return response
166
-
167
- @staticmethod
168
- def receive_eval():
169
- """
170
- READ - VALIDATE - EXECUTE - REPLY LOOP
171
- - can be used in async loop
172
- RETURN None when telegram bot token is missing
173
- """
174
-
175
- console_write("[NTFY] REC&EVAL sequence")
176
-
177
- # Return data structure template
178
- verdict = None
179
-
180
- def lm_execute(cmd_args):
181
- nonlocal verdict
182
- if lm_is_loaded(cmd_args[0]):
183
- verdict = f'[UP] Exec: {" ".join(cmd_args[0])}'
184
- try:
185
- _, out = lm_exec(cmd_args)
186
- except Exception as e:
187
- out = str(e)
188
- Telegram.send_msg(out, reply_to=m_id)
189
- else:
190
- verdict = f'[UP] NoAccess: {cmd_args[0]}'
191
- Telegram._IN_MSG_ID = m_id
192
-
193
- # -------------------------- FUNCTION MAIN -------------------------- #
194
- # Poll telegram chat
195
- data = Telegram.get_msg()
196
- if data is None:
197
- return data
198
- # Get msg, msg_id, chat_id as main input data source
199
- msg_in, m_id, c_id = data['text'], data['m_id'], data['c_id']
200
- if msg_in is not None and m_id != Telegram._IN_MSG_ID:
201
- # replace single/double quotation to apostrophe (str syntax for repl interpretation)
202
- msg_in = msg_in.replace('‘', "'").replace('’', "'").replace('“', '"').replace('”', '"')
203
- if msg_in.startswith('/ping'):
204
- # Parse loaded modules
205
- _loaded_mods = [lm.replace('LM_', '') for lm in modules if lm.startswith('LM_')] + ['task']
206
- Telegram.send_msg(', '.join(_loaded_mods), reply_to=m_id, chat_id=c_id)
207
- elif msg_in.startswith('/cmd_select'):
208
- cmd_lm = msg_in.strip().split()[1:]
209
- # [Compare] cmd selected device param with DEVFID (device/prompt name)
210
- if cmd_lm[0] in Telegram._DEVFID:
211
- lm_execute(cmd_lm[1:])
212
- else:
213
- verdict = f'[UP] NoSelected: {cmd_lm[0]}'
214
- elif msg_in.startswith('/cmd'):
215
- cmd_lm = msg_in.strip().split()[1:]
216
- lm_execute(cmd_lm)
217
- elif msg_in.startswith('/notify'):
218
- param = msg_in.strip().split()[1:]
219
- if len(param) > 0:
220
- verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
221
- else:
222
- verdict = Telegram.notifications()
223
- Telegram.send_msg(verdict, reply_to=m_id)
224
- else:
225
- verdict = "[UP] NoExec"
226
- console_write(f"\tREC&EVAL: {verdict}")
227
- return verdict
228
-
229
- @staticmethod
230
- def set_commands():
231
- """
232
- Set Custom Commands to the Telegram chat.
233
- RETURN None when telegram bot token is missing
234
- """
235
-
236
- console_write("[NTFY] SET DEFAULT COMMANDS")
237
- bot_token = Telegram.__bot_token()
238
- if bot_token is None:
239
- return None
240
- url = f"https://api.telegram.org/bot{bot_token}/setMyCommands{Telegram._API_PARAMS}"
241
- data = {"commands": [{"command": "ping", "description": "Ping All endpoints: list of active modules."},
242
- {"command": "notify", "description": "Enable/Disable notifications: on or off"},
243
- {"command": "cmd", "description": "Run active module function on all devices."},
244
- {"command": "cmd_select", "description": "Same as cmd, only first param must be device name."},
245
- ]}
246
- _, resp_json = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True, sock_size=128)
247
- return 'Custom commands was set' if resp_json['ok'] else str(resp_json)
68
+ def lm_execute(cmd_args):
69
+ """Executor with basic access handling"""
70
+ if lm_is_loaded(cmd_args[0]):
71
+ try:
72
+ _, out = lm_exec(cmd_args)
73
+ except Exception as e:
74
+ out = str(e)
75
+ return True, out
76
+ return False, cmd_args[0]
micrOS/source/Shell.py CHANGED
@@ -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.8.4-0'
28
+ MICROS_VERSION = '2.9.1-0'
29
29
 
30
30
  def __init__(self):
31
31
  """
@@ -264,8 +264,9 @@ class Shell:
264
264
  line = "micrOSisTheBest"
265
265
  while line:
266
266
  line = f.readline()
267
- if line.strip().startswith('def') and '_' not in line and 'self' not in line:
268
- msg_obj(f" {' ' * len(lm_name)}{line.replace('def ', '').split('(')[0]}")
267
+ ldata = line.strip()
268
+ if ldata.startswith('def ') and not ldata.split()[1].startswith("_") and 'self' not in ldata:
269
+ msg_obj(f" {' ' * len(lm_name)}{ldata.replace('def ', '').split('(')[0]}")
269
270
  except Exception as e:
270
271
  msg_obj(f"[{lm_path}] SHOW LM PARSER WARNING: {e}")
271
272
  return False
micrOS/source/Types.py CHANGED
@@ -70,8 +70,12 @@ def _generate(type_dict, help_msg):
70
70
  overwrite[value_type] = values
71
71
  valid_params.append(param_str)
72
72
  else:
73
- param_str = f'{p}=:{"range" if "range" in type_dict else "options"}:'
74
- valid_params.append(param_str)
73
+ if p.startswith("&"):
74
+ # Handle special use case - task postfix
75
+ valid_params.append(p)
76
+ else:
77
+ param_str = f'{p}=:{"range" if "range" in type_dict else "options"}:'
78
+ valid_params.append(param_str)
75
79
  type_dict['lm_call'] = f"{func} {' '.join(valid_params)}"
76
80
  return dumps(type_dict | overwrite)
77
81
 
micrOS/source/Web.py CHANGED
@@ -74,9 +74,22 @@ class WebEngine:
74
74
  try:
75
75
  # SEND RESOURCE CONTENT: HTML, JS, CSS
76
76
  with open(resource, 'r') as file:
77
- html = file.read()
78
- await self.client.a_send(self.REQ200.format(dtype=WebEngine.file_type(resource), len=len(html), data=html))
79
- except OSError:
77
+ data = file.read()
78
+ response = self.REQ200.format(dtype=WebEngine.file_type(resource), len=len(data), data=data)
79
+ # Send entire response data
80
+ await self.client.a_send(response)
81
+
82
+ # Send chunks of response data (experimental)
83
+ #response_len, chunk_size, position = len(response), 300, 0
84
+ #while position < response_len:
85
+ # # Calculate the size of the next chunk
86
+ # next_chunk_size = min(chunk_size, response_len - position)
87
+ # chunk = response[position:position + next_chunk_size]
88
+ # await self.client.a_send(chunk)
89
+ # position += next_chunk_size
90
+ except Exception as e:
91
+ if 'memory allocation failed' in str(e):
92
+ errlog_add(f"[ERR] WebCli {resource}: {e}")
80
93
  await self.client.a_send(self.REQ404.format(len=13, data='404 Not Found'))
81
94
  return
82
95
  # INVALID/BAD REQUEST