{"id":16281286,"url":"https://github.com/carglglz/asyncmd","last_synced_at":"2025-03-20T01:34:00.109Z","repository":{"id":177104139,"uuid":"594140331","full_name":"Carglglz/asyncmd","owner":"Carglglz","description":"Tools for MicroPython Async Development","archived":false,"fork":false,"pushed_at":"2024-03-20T01:24:31.000Z","size":1418,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-17T21:35:36.218Z","etag":null,"topics":["async","asyncio","debugging","htop","logging","micropython","mqtt","profiling","systemd"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Carglglz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-01-27T17:38:01.000Z","updated_at":"2024-08-07T10:16:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"20dc00da-c743-4b7c-9f0e-7c53a8f19203","html_url":"https://github.com/Carglglz/asyncmd","commit_stats":null,"previous_names":["carglglz/asyncmd"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Carglglz%2Fasyncmd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Carglglz%2Fasyncmd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Carglglz%2Fasyncmd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Carglglz%2Fasyncmd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Carglglz","download_url":"https://codeload.github.com/Carglglz/asyncmd/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221733355,"owners_count":16871856,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","asyncio","debugging","htop","logging","micropython","mqtt","profiling","systemd"],"created_at":"2024-10-10T19:06:20.860Z","updated_at":"2024-10-27T21:07:04.546Z","avatar_url":"https://github.com/Carglglz.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Tools for MicroPython Async Development\n\nInspired by [aiorepl](https://github.com/micropython/micropython-lib/tree/master/micropython/aiorepl),\n**asyncmd** is an *asyncio* based set of tools to help with the development of asynchronous applications implemented in MicroPython.\n\n*Asyncio* is ideal for running multiple tasks concurrently[^1], however an easy way to \n**interactively** inspect running tasks in the event loop was not available\nuntil the introduction of [aiorepl](https://github.com/micropython/micropython-lib/tree/master/micropython/aiorepl),\nan asynchronous MicroPython REPL.\n\nThis set of tools builds upon this *aiorepl* capacity to interact with tasks running in the event loop.\n\n**asyncmd** is intended to be flexible and extensible i.e. minimum requirement is `aioctl.py`\nand then every script builds upon *aioctl* functionality.\n\n#### Features\n\n* Create async **traceable tasks** that can be controlled, tracked, managed, debugged or profiled --\u003e `aioctl.py`\n\n\n    Create a task that can be stopped or restarted, inspect its result/return value, \n    internal error, traceback message, know how long it has been running or how long it has been done.\n    Also allows to add a custom name/id to the task, or run a callback function when the task is stopped\n    or throws an error. \n    \n    e.g. ``● hello_task: status: running since 2023-03-16 00:00:19; 4 s ago``\n\n* Asynchronous **RAM logging** --\u003e `aiolog.py`\n    \n    Create a \"ring buffer stream\" to log tasks output indefinitely. It \n    allocates a predefined amount of RAM and rotates automatically so it never allocates\n    more RAM than the one predefined. \n    Also allows to interactively inspect its content the same way as using `cat`, `cat | grep` or `tail -F `, `tail -F | grep`.\n    \n    e.g. `2023-03-16 00:00:19 [pyb] [INFO] [hello_task] LED 3 toggled!`\n\n\n* Asynchronous **scheduling** --\u003e `aioschedule.py`\n\n    Create a task and run it in `s` seconds (*from event loop start or task creation*) or at a time `(timetuple)` and repeat every `n` seconds. \n    This has a \"scheduler task loop\" that checks every second its schedule and runs a scheduled task when the time is due.\n\n    e.g. \n    \n    ```\n    ● world_task: status: done @ 2023-03-16 00:12:44; 48 s ago --\u003e result:\n    ┗━► schedule: last @ 2023-03-16 00:12:39 --\u003e next in 37 s  @ 2023-03-16 00:13:16\n    ```\n\n\n* Asynchronous **services**[^2] --\u003e `aioservice.py`, `aioclass.py`, `aioservices/services`, `services.config`\n\n    Create a service that can have one or more tasks, install/list/get status of services, load/unload, config services \n    as enabled or disabled, config service main task args and keyword args, get the traceback of services that failed to load, init/boot services \n    following a priority depending of another service dependency...\n\n    e.g. \n    ```\n    [ OK ] Service: hello_core.service from ./aioservices/services/hello_core_service.py loaded\n    [ OK ] Service: hello_low.service from ./aioservices/services/hello_low_service.py loaded\n    [ OK ] Service: world.service from ./aioservices/services/world_service.mpy loaded\n    [ OK ] Service: hello.service from ./aioservices/services/hello_service.mpy loaded\n    [ OK ] Service: watcher.service from ./aioservices/services/watcher_service.mpy loaded\n    [ ERROR ] Service: dofail.service from ./aioservices/services/dofail_service.mpy not loaded: Error: ZeroDivisionError\n    ```\n    \n    Finally to inspect a task or service enabling debug mode in `aioctl` gives the full service/task status\n    \n    ```\n    ● hello.service - Hello example runner v1.0\n        Loaded: Service: hello.service from ./aioservices/services/hello_service.mpy\n        Active: (active) running since 2023-03-16 00:35:00; 00:02:31 ago\n        Type: runtime.service\n        Docs: https://github.com/Carglglz/asyncmd/blob/main/README.md\n        Stats:     Temp: 25.79095 C   Loop info: exec time: 5 ms; # loops: 186\n        Task: \u003cTaskctl object at 20003010\u003e\n        ┗━► args: (Service: hello.service from ./aioservices/services/hello_service.mpy, 3, 10)\n        ┗━► kwargs: { 'on_error': \u003cbound_method\u003e,\n                      '_id': 'hello.service',\n                      'log': \u003cLogger object at 20008010\u003e,\n                      'on_stop': \u003cbound_method\u003e }\n    2023-03-16 00:35:10 [pyb] [INFO] [hello.service] LED 3 toggled!\n    2023-03-16 00:35:20 [pyb] [INFO] [hello.service] LED 2 toggled!\n    ```\n\n\n## Install\n\n### Manual\nFor `aioctl.py`, `aioschedule.py`, `aiolog.py`, `aioservice.py` and `aioclass.py` just upload the scripts to the device[^3]\n\n\nFor `aioservices/services` make the directories first and then upload  `aioservices/services/__init__.py` (or directly sync `aioservices`)\n\nThen to install a service upload it to this directory.\n\n### Using MIP\nSee [MIP](https://docs.micropython.org/en/latest/reference/packages.html)\n\n*Note that Network-capable boards must be already connected to WiFi/LAN and have internet access* \n\nTo install `asyncmd` using `mip`\n```\n\u003e\u003e\u003e import mip\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd\", target=\".\")\n```\n\nFor simple installation .i.e only `aioctl.py`\n\n```\n\u003e\u003e\u003e import mip\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd/aioctl.py\")\n```\n\nTo install services using `mip`\n\n```\n\u003e\u003e\u003e import mip\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd/services\", target=\".\")\n\n# or only network (core network, wpa_supplicant)\n\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd/services/network\", target=\".\")\n\n# or develop (watcher, mip, unittest)\n\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd/services/develop\", target=\".\")\n\n# or ble \n\n\u003e\u003e\u003e mip.install(\"github:Carglglz/asyncmd/services/ble\", target=\".\")\n```\n\n\n\nNote that this set of tools (with exception of `aioservices/services`) can be frozen in the firmware too which will be the best option for saving memory. To freeze services, see `frz_services`. To learn more about freezing packages/modules see\n[MicroPython manifest files](https://docs.micropython.org/en/latest/reference/manifest.html#manifest)\n\n\n## Example\nThis basic example demonstrates how to use `@aioctl.aiotask` decorator to create\na traceable task\n\ne.g. `async_blink.py`\n\n```python\nfrom machine import Pin\nimport uasyncio as asyncio\nimport aiorepl\nimport aioctl\n\n# Define a task\n\n@aioctl.aiotask\nasync def blink(pin, sleep=5):\n    led = Pin(pin, Pin.OUT)\n    while True:\n        led.on()\n        await asyncio.sleep_ms(500)\n        led.off()\n        await asyncio.sleep(sleep)\n\n\nasync def main():\n    print(\"Starting tasks...\")\n    # Add tasks\n    aioctl.add(blink, 2, sleep=5)\n    aioctl.add(aiorepl.task, name=\"repl\")\n    # await tasks\n    await asyncio.gather(*aioctl.tasks())\n\n\nasyncio.run(main())\n```\nTo run, copy and paste in paste mode or upload the script to the device then\n```\n\u003e\u003e\u003e import async_blink\nStarting tasks...\nStarting asyncio REPL\n--\u003e import aioctl\n--\u003e aioctl.status()\n● repl: status: running since 2023-03-16 11:27:11; 38 s ago\n● blink: status: running since 2023-03-16 11:27:11; 38 s ago\n\n# Enable aioctl debug mode\n--\u003e aioctl.debug()\ndebug mode: True\n--\u003e aioctl.status()\n● repl: status: running since 2023-03-16 11:27:11; 00:01:01 ago\n    Task: \u003cTaskctl object at 2000c9d0\u003e\n    ┗━► args: ()\n    ┗━► kwargs: {}\n● blink: status: running since 2023-03-16 11:27:11; 00:01:01 ago\n    Task: \u003cTaskctl object at 2000c7d0\u003e\n    ┗━► args: (2,)\n    ┗━► kwargs: { 'sleep': 5 }\n\n# Stop blink task\n--\u003e aioctl.stop(\"blink\")\nTrue\n--\u003e aioctl.status()\n● repl: status: running since 2023-03-16 11:27:11; 00:01:25 ago\n    Task: \u003cTaskctl object at 2000c9d0\u003e\n    ┗━► args: ()\n    ┗━► kwargs: {}\n● blink: status: stopped @ 2023-03-16 11:28:33; 3 s ago --\u003e result:\n    Task: \u003cTaskctl object at 2000c7d0\u003e\n    ┗━► runtime: 00:01:22\n    ┗━► args: (2,)\n    ┗━► kwargs: { 'sleep': 5 }\n\n# Change sleep kwarg\n--\u003e aioctl.group().tasks[\"blink\"].kwargs.update(sleep=3)\n\n# Start again\n--\u003e aioctl.start(\"blink\")\nTrue\n--\u003e aioctl.status()\n● repl: status: running since 2023-03-16 11:27:11; 00:06:35 ago\n    Task: \u003cTaskctl object at 2000c9d0\u003e\n    ┗━► args: ()\n    ┗━► kwargs: {}\n● blink: status: running since 2023-03-16 11:33:43; 3 s ago\n    Task: \u003cTaskctl object at 20016110\u003e\n    ┗━► args: (2,)\n    ┗━► kwargs: { 'sleep': 3 }\n\n# Add another blink task\n--\u003e aioctl.add(blink, 3, sleep=6)\n--\u003e aioctl.status()\n● blink@1: status: running since 2023-03-16 11:40:56; 11 s ago\n    Task: \u003cTaskctl object at 20015350\u003e\n    ┗━► args: (3,)\n    ┗━► kwargs: { 'sleep': 6 }\n● repl: status: running since 2023-03-16 11:27:11; 00:13:56 ago\n    Task: \u003cTaskctl object at 2000c9d0\u003e\n    ┗━► args: ()\n    ┗━► kwargs: {}\n● blink: status: running since 2023-03-16 11:37:48; 00:03:19 ago\n    Task: \u003cTaskctl object at 20010070\u003e\n    ┗━► args: (2,)\n    ┗━► kwargs: { 'sleep': 3 }\n\n```\n\nSee more [examples](examples/) to know how to add *\"async\" logging*,\ncallbacks, debugging errors, get results, scheduling and finally some [examples](lib-aioservices/) of `aioservice`\nimplementation.\n\n## Docs\n\n### [aioctl](docs/aioctl.md)\n\n\n\n### [aiolog](docs/aiolog.md)\n\n\n### [aioschedule](docs/aioschedule.md)\n\n\n### [aioservice](docs/aioservice.md)\n\n\n### [aioservices](docs/aioservices.md)\n\n\n### [app](app/)\n\n### [async_modules](async_modules/)\n\n    \n\n## Examples\n\nSet of examples of increasing complexity to show the capabilities\nof these tools.\n\n- aiotasks --\u003e [examples](examples)\n\n- aioservices --\u003e [example-aioservices](lib-aioservices)\n\n\n#### Use cases for **aioctl**\n\n - Building block for *asyncio* based applications that makes tasks\n  traceable, manageable and easily debugged\n\n#### Use cases for **aioservice**\n\n - Debug, develop and manage complex *asyncio* applications\n - Create reproducible/repeatable builds that can be fine tuned for each device using config files\n - Long term running and modular applications that need to be resilient and remotely:\n    - monitored\n    - debugged\n    - updated\n\n\n## Tutorial\n\nSee a tutorial for `unix` port in [develop example](develop/unix) using *MQTT*, *asyncmd* CLI and the following services: \n    \n- `aiomqtt.service` --\u003e control/debug and get/check OTA updates notifications over MQTT \n- `aiomqtt_sensor_bme280.service` --\u003e sensor (simulated) reports Temperature,Humidity,Pressure over MQTT  \n- `mip.service` --\u003e check and update mip installable packages\n- `unittest.service` --\u003e check and run new tests regularly.\n- `watcher.service` --\u003e watch services tasks to add resiliency and enable watchdog to secure\n  device continuous operation with minimum downtime\n\nSee a tutorial for `esp32` port in [develop example](develop/esp32) using *MQTT*, *asyncmd* CLI and the following services: \n    \n- `aiomqtt.service` --\u003e control/debug and get/check OTA updates notifications over MQTT \n- `aiomqtt_sensor_bme280.service` --\u003e sensor (simulated) reports Temperature,Humidity,Pressure over MQTT  \n- `ota.service` --\u003e async OTA firmware upgrades using `asyncmd` CLI\n- `network.service` --\u003e enable WiFi connection and WebREPL (optional)\n- `wpa_supplicant.service` --\u003e check WiFi connection and reconnects if disconnected\n- `watcher.service` --\u003e watch services tasks to add resiliency and enable watchdog to secure\n  device continuous operation with minimum downtime\n\n## asyncmd CLI\n\n### [asyncmd cli](cli/)\n\n\n## Tested on ports:\n\n- esp32 (WROOM ESP32-D0WDQ6 revision v1.0)\n- stm32 (pyboard) (STM32F405RG)\n- unix (see [develop/unix](develop/unix))\n\n### Notes\n\n[^1]: *Runnnig*\n*multiple tasks concurrently where timing precision is only needed to be held up to a certain degree\nwhich can vary with the number of tasks running , the amount of time they\ntake to run and how frequent they are scheduled*\n\n[^2]: *Inspiration comes from Linux [systemd](https://github.com/systemd/systemd) specially `systemctl` and `journalctl`.*\n\n[^3]: *Better if compiled to `.mpy` using `mpy-cross` to save memory, see [mpy-cross](https://docs.micropython.org/en/latest/reference/glossary.html#term-cross-compiler)*\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarglglz%2Fasyncmd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcarglglz%2Fasyncmd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarglglz%2Fasyncmd/lists"}