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
micrOS/source/Notify.py CHANGED
@@ -1,248 +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
-
13
- class Telegram:
14
- # Telegram bot token and chat ID
15
- # https://core.telegram.org/bots/api
16
- GLOBAL_NOTIFY = True # Enable Global notifications
17
- _TOKEN = None # Telegram token
18
- _CHAT_IDS = set() # Telegram bot chat IDs - multi group support - persistent caching
19
- _API_PARAMS = "?offset=-1&limit=1&timeout=2" # Generic API params - optimization
20
- _DEVFID = cfgget('devfid') # For reply message (pre text)
21
- _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
22
24
 
23
25
  @staticmethod
24
- def __id_cache(mode):
26
+ def add_subscriber(instance):
25
27
  """
26
- pds - persistent data structure
27
- modes:
28
- r - recover, s - save
28
+ Add Notification agent like: Telegram
29
29
  """
30
- if mode == 's':
31
- # SAVE CACHE
32
- console_write("[NTFY] Save chatIDs cache...")
33
- with open('telegram.pds', 'w') as f:
34
- f.write(','.join([str(k) for k in Telegram._CHAT_IDS]))
35
- return
36
- try:
37
- # RESTORE CACHE
38
- console_write("[NTFY] Restore chatIDs cache...")
39
- with open('telegram.pds', 'r') as f:
40
- # set() comprehension
41
- Telegram._CHAT_IDS = {int(k) for k in f.read().strip().split(',')}
42
- except:
43
- pass
30
+ if isinstance(instance, Notify):
31
+ Notify._SUBSCRIBERS.add(instance)
32
+ return True
33
+ raise Exception("Subscribe error, Notify parent missing")
44
34
 
45
35
  @staticmethod
46
- def __bot_token():
47
- """Get bot token"""
48
- if Telegram._TOKEN is None:
49
- token = cfgget('telegram')
50
- if token is None or token == 'n/a':
51
- return None
52
- Telegram._TOKEN = token
53
- return Telegram._TOKEN
54
-
55
- @staticmethod
56
- def send_msg(text, reply_to=None, chat_id=None):
36
+ def message(text, reply_to=None, chat_id=None):
57
37
  """
58
- Send a message to the Telegram chat by chat_id
59
- :param text: text to send
60
- :param reply_to: reply to specific message, if None, simple reply
61
- :param chat_id: chat_id to reply on, if None, reply to all known
62
- RETURN None when telegram bot token is missing
38
+ Send message to all subscribers
63
39
  """
64
- def _send(chid):
65
- """Send message to chat_id (chid)"""
66
- data = {"chat_id": chid, "text": f"{Telegram._DEVFID}⚙️\n{text}"}
67
- if isinstance(reply_to, int):
68
- data['reply_to_message_id'] = reply_to
69
- Telegram._IN_MSG_ID = reply_to
70
- _, _resp = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True, sock_size=128)
71
- console_write(f"\tSend message:\n{data}\nresponse:\n{_resp}")
72
- return _resp
73
-
74
- def _get_chat_ids():
75
- """Return chat ID or None (in case of no token or cannot get ID)"""
76
- if len(Telegram._CHAT_IDS) == 0:
77
- Telegram.get_msg() # It will update the Telegram.CHAT_IDS
78
- console_write(f"\tGet chatIDs: {Telegram._CHAT_IDS}")
79
- return Telegram._CHAT_IDS
80
-
81
- # --------------------- FUNCTION MAIN ------------------------ #
82
- console_write("[NTFY] SEND MESSAGE")
83
- # Check bot token
84
- bot_token = Telegram.__bot_token()
85
- if bot_token is None:
86
- return None
87
- url = f"https://api.telegram.org/bot{bot_token}/sendMessage{Telegram._API_PARAMS}"
88
-
89
- verdict = ""
90
- # Reply to ALL (notification) - chat_id was not provided
91
- if chat_id is None:
92
- console_write("\tREPLY ALL")
93
- for _chat_id in _get_chat_ids():
94
- resp_json = _send(chid=_chat_id)
95
- verdict += f'Sent{_chat_id};' if resp_json['ok'] else str(resp_json)
96
- else:
97
- console_write(f"\tREPLY TO {chat_id}")
98
- # Direct reply to chat_id
99
- resp_json = _send(chid=chat_id)
100
- verdict = f'Sent{chat_id}' if resp_json['ok'] else str(resp_json)
101
- 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})"
102
48
 
103
49
  @staticmethod
104
50
  def notifications(state=None):
105
51
  """
106
52
  Setter for disable/enable notification messages
107
53
  """
108
- if state is not None:
109
- Telegram.GLOBAL_NOTIFY = state
110
- return f"Notifications: {'enabled' if Telegram.GLOBAL_NOTIFY else 'disabled'}"
111
-
54
+ if isinstance(state, bool):
55
+ Notify.GLOBAL_NOTIFY = state
56
+ return f"Notifications: {'enabled' if Notify.GLOBAL_NOTIFY else 'disabled'}"
112
57
 
113
58
  @staticmethod
114
59
  def notify(text, reply_to=None, chat_id=None):
115
60
  """
116
61
  Notification sender
117
62
  """
118
- if Telegram.GLOBAL_NOTIFY:
119
- return Telegram.send_msg(text, reply_to, chat_id)
63
+ if Notify.GLOBAL_NOTIFY:
64
+ return Notify.message(text, reply_to, chat_id)
120
65
  return "Notifications disabled"
121
66
 
122
67
  @staticmethod
123
- def get_msg():
124
- """
125
- Get the last message from the Telegram chat.
126
- RETURN None when telegram bot token is missing
127
- """
128
-
129
- def _update_chat_ids():
130
- """
131
- Update known chat_id-s and cache them
132
- - return active chat_id frm resp_json
133
- """
134
- console_write("[NTFY GET] update chatIDs")
135
- _cid = None
136
- if resp_json.get("ok", None) and len(resp_json["result"]) > 0:
137
- _cid = resp_json["result"][-1]["message"]["chat"]["id"]
138
- # LIMIT Telegram._CHAT_IDS NOTIFICATION CACHE TO 3 IDs
139
- if len(Telegram._CHAT_IDS) < 4:
140
- _ids = len(Telegram._CHAT_IDS)
141
- Telegram._CHAT_IDS.add(_cid)
142
- if len(Telegram._CHAT_IDS) - _ids > 0: # optimized save (slow storage access)
143
- Telegram.__id_cache('s')
144
- else:
145
- Telegram.__id_cache('r')
146
- if len(Telegram._CHAT_IDS) == 0:
147
- error_message = resp_json.get("description", "Unknown error")
148
- raise Exception(f"Error retrieving chat ID: {error_message}")
149
- return _cid
150
-
151
- # --------------------- FUNCTION MAIN ------------------------ #
152
- console_write("[NTFY] GET MESSAGE")
153
- bot_token = Telegram.__bot_token()
154
- if bot_token is None:
155
- return None
156
- response = {'sender': None, 'text': None, 'm_id': -1, 'c_id': None}
157
- url = f"https://api.telegram.org/bot{bot_token}/getUpdates{Telegram._API_PARAMS}"
158
- console_write(f"\t1/2[GET] request: {url}")
159
- _, resp_json = urequests.get(url, jsonify=True, sock_size=128)
160
- if len(resp_json["result"]) > 0:
161
- response['c_id'] = _update_chat_ids()
162
- resp = resp_json["result"][-1]["message"]
163
- response['sender'] = f"{resp['chat']['first_name']}{resp['chat']['last_name']}" if resp['chat'].get('username', None) is None else resp['chat']['username']
164
- response['text'], response['m_id'] = resp['text'], resp['message_id']
165
- console_write(f"\t2/2[GET] response: {response}")
166
- return response
167
-
168
- @staticmethod
169
- def receive_eval():
170
- """
171
- READ - VALIDATE - EXECUTE - REPLY LOOP
172
- - can be used in async loop
173
- RETURN None when telegram bot token is missing
174
- """
175
-
176
- console_write("[NTFY] REC&EVAL sequence")
177
-
178
- # Return data structure template
179
- verdict = None
180
-
181
- def lm_execute(cmd_args):
182
- nonlocal verdict
183
- if lm_is_loaded(cmd_args[0]):
184
- verdict = f'[UP] Exec: {" ".join(cmd_args[0])}'
185
- try:
186
- _, out = lm_exec(cmd_args)
187
- except Exception as e:
188
- out = str(e)
189
- Telegram.send_msg(out, reply_to=m_id)
190
- else:
191
- verdict = f'[UP] NoAccess: {cmd_args[0]}'
192
- Telegram._IN_MSG_ID = m_id
193
-
194
- # -------------------------- FUNCTION MAIN -------------------------- #
195
- # Poll telegram chat
196
- data = Telegram.get_msg()
197
- if data is None:
198
- return data
199
- # Get msg, msg_id, chat_id as main input data source
200
- msg_in, m_id, c_id = data['text'], data['m_id'], data['c_id']
201
- if msg_in is not None and m_id != Telegram._IN_MSG_ID:
202
- # replace single/double quotation to apostrophe (str syntax for repl interpretation)
203
- msg_in = msg_in.replace('‘', "'").replace('’', "'").replace('“', '"').replace('”', '"')
204
- if msg_in.startswith('/ping'):
205
- # Parse loaded modules
206
- _loaded_mods = [lm.replace('LM_', '') for lm in modules if lm.startswith('LM_')] + ['task']
207
- Telegram.send_msg(', '.join(_loaded_mods), reply_to=m_id, chat_id=c_id)
208
- elif msg_in.startswith('/cmd_select'):
209
- cmd_lm = msg_in.strip().split()[1:]
210
- # [Compare] cmd selected device param with DEVFID (device/prompt name)
211
- if cmd_lm[0] in Telegram._DEVFID:
212
- lm_execute(cmd_lm[1:])
213
- else:
214
- verdict = f'[UP] NoSelected: {cmd_lm[0]}'
215
- elif msg_in.startswith('/cmd'):
216
- cmd_lm = msg_in.strip().split()[1:]
217
- lm_execute(cmd_lm)
218
- elif msg_in.startswith('/notify'):
219
- param = msg_in.strip().split()[1:]
220
- if len(param) > 0:
221
- verdict = Telegram.notifications(not param[0].strip().lower() in ("disable", "off", 'false'))
222
- else:
223
- verdict = Telegram.notifications()
224
- Telegram.send_msg(verdict, reply_to=m_id)
225
- else:
226
- verdict = "[UP] NoExec"
227
- console_write(f"\tREC&EVAL: {verdict}")
228
- return verdict
229
-
230
- @staticmethod
231
- def set_commands():
232
- """
233
- Set Custom Commands to the Telegram chat.
234
- RETURN None when telegram bot token is missing
235
- """
236
-
237
- console_write("[NTFY] SET DEFAULT COMMANDS")
238
- bot_token = Telegram.__bot_token()
239
- if bot_token is None:
240
- return None
241
- url = f"https://api.telegram.org/bot{bot_token}/setMyCommands{Telegram._API_PARAMS}"
242
- data = {"commands": [{"command": "ping", "description": "Ping All endpoints: list of active modules."},
243
- {"command": "notify", "description": "Enable/Disable notifications: on or off"},
244
- {"command": "cmd", "description": "Run active module function on all devices."},
245
- {"command": "cmd_select", "description": "Same as cmd, only first param must be device name."},
246
- ]}
247
- _, resp_json = urequests.post(url, headers={"Content-Type": "application/json"}, json=data, jsonify=True, sock_size=128)
248
- 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]
@@ -1,6 +1,6 @@
1
1
  from time import localtime
2
2
  from re import compile
3
- from Tasks import exec_lm_core_schedule
3
+ from Tasks import exec_lm_pipe_schedule
4
4
  from Debug import console_write, errlog_add
5
5
  from Time import Sun, suntime, ntp_time
6
6
 
@@ -179,7 +179,7 @@ def __scheduler_trigger(cron_time_now, crontask, deltasec=2):
179
179
  lm_state = False
180
180
  if isinstance(crontask[1], str):
181
181
  # [1] Execute Load Module as a string (user LMs)
182
- lm_state = exec_lm_core_schedule(crontask[1].split())
182
+ lm_state = exec_lm_pipe_schedule(crontask[1])
183
183
  else:
184
184
  try:
185
185
  # [2] Execute function reference (built-in functions)
@@ -207,6 +207,7 @@ def deserialize_raw_input(cron_data):
207
207
  Scheduler/Cron input string format
208
208
  :param cron_data: raw cron tasks, time based task execution input (bytearray)
209
209
  example: WD:H:M:S!LM func;WD:H:M:S!LM func; ...
210
+ multi command example: WD:H:M:S!LM func;LM func2;; WD:H:M:S!LM func;; ...
210
211
 
211
212
  time_tag: timestamp / time-tag aka suntime
212
213
  timestamp: WD:H:M:S
@@ -218,8 +219,10 @@ def deserialize_raw_input(cron_data):
218
219
  Returns tuple: (("WD:H:M:S", 'LM FUNC'), ("WD:H:M:S", 'LM FUNC'), ...)
219
220
  """
220
221
  try:
221
- # Parse and create return - convert cron_data (bytearray) to string
222
- return (tuple(cron.split('!')) for cron in str(cron_data, 'utf-8').split(';'))
222
+ # Parse and create return
223
+ cd = str(cron_data, 'utf-8') # convert cron_data (bytearray) to string
224
+ sep = ';;' if ';;' in cd else ';' # support multi command with ;;
225
+ return (tuple(cron.split('!')) for cron in cd.split(sep))
223
226
  except Exception as e:
224
227
  errlog_add(f"[ERR] cron deserialize - syntax error: {e}")
225
228
  return ()
micrOS/source/Shell.py CHANGED
@@ -2,7 +2,7 @@
2
2
  Module is responsible for shell like environment
3
3
  dedicated to micrOS framework.
4
4
  Built-in-function:
5
- - Shell wrapper for safe InterpreterCore interface
5
+ - Shell wrapper for lm_exec interface
6
6
  - Configuration handling interface - state machine handling
7
7
  - Help (runtime) message generation
8
8
 
@@ -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.1-0'
28
+ MICROS_VERSION = '2.9.0-0'
29
29
 
30
30
  def __init__(self):
31
31
  """