otampy 2.0.0rc1__tar.gz

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 (34) hide show
  1. otampy-2.0.0rc1/PKG-INFO +389 -0
  2. otampy-2.0.0rc1/README.md +373 -0
  3. otampy-2.0.0rc1/pyproject.toml +114 -0
  4. otampy-2.0.0rc1/src/otampy/__init__.py +8 -0
  5. otampy-2.0.0rc1/src/otampy/cli.py +2074 -0
  6. otampy-2.0.0rc1/src/otampy/deploy.py +767 -0
  7. otampy-2.0.0rc1/src/otampy/device/.vscode/extensions.json +8 -0
  8. otampy-2.0.0rc1/src/otampy/device/.vscode/settings.json +15 -0
  9. otampy-2.0.0rc1/src/otampy/device/README.md +106 -0
  10. otampy-2.0.0rc1/src/otampy/device/examples/boot.py +42 -0
  11. otampy-2.0.0rc1/src/otampy/device/examples/main.py +114 -0
  12. otampy-2.0.0rc1/src/otampy/device/examples/minimal_ping.py +17 -0
  13. otampy-2.0.0rc1/src/otampy/device/examples/ota-config.example.py +18 -0
  14. otampy-2.0.0rc1/src/otampy/device/examples/shared_uart_telemetry.py +34 -0
  15. otampy-2.0.0rc1/src/otampy/device/lib/Blink.py +15 -0
  16. otampy-2.0.0rc1/src/otampy/device/lib/otampy/__init__.py +10 -0
  17. otampy-2.0.0rc1/src/otampy/device/lib/otampy/boot.py +348 -0
  18. otampy-2.0.0rc1/src/otampy/device/lib/otampy/core.py +58 -0
  19. otampy-2.0.0rc1/src/otampy/device/lib/otampy/filecopy.py +188 -0
  20. otampy-2.0.0rc1/src/otampy/device/lib/otampy/manager.py +294 -0
  21. otampy-2.0.0rc1/src/otampy/device/lib/otampy/ota.py +55 -0
  22. otampy-2.0.0rc1/src/otampy/device/tests/conftest.py +68 -0
  23. otampy-2.0.0rc1/src/otampy/device/tests/shared.py +29 -0
  24. otampy-2.0.0rc1/src/otampy/device/tests/test_ota_boot.py +207 -0
  25. otampy-2.0.0rc1/src/otampy/device/tests/test_ota_core.py +136 -0
  26. otampy-2.0.0rc1/src/otampy/device/tests/test_ota_facade.py +84 -0
  27. otampy-2.0.0rc1/src/otampy/device/tests/test_ota_manager.py +580 -0
  28. otampy-2.0.0rc1/src/otampy/device/tools/footprint_boot.py +103 -0
  29. otampy-2.0.0rc1/src/otampy/device/tools/footprint_fs.py +69 -0
  30. otampy-2.0.0rc1/src/otampy/device/tools/footprint_stress.py +493 -0
  31. otampy-2.0.0rc1/src/otampy/device/tools/footprint_update.py +147 -0
  32. otampy-2.0.0rc1/src/otampy/device/tools/performance_timer.py +44 -0
  33. otampy-2.0.0rc1/src/otampy/utils/__init__.py +0 -0
  34. otampy-2.0.0rc1/src/otampy/utils/logging_utils.py +77 -0
@@ -0,0 +1,389 @@
1
+ Metadata-Version: 2.4
2
+ Name: otampy
3
+ Version: 2.0.0rc1
4
+ Summary: Serial OTA application firmware update solution for MicroPython
5
+ Author: Simon R. Lincoln
6
+ Author-email: Simon R. Lincoln <oss@codeability.co.uk>
7
+ License-Expression: SUL-1.0
8
+ Requires-Dist: pyserial>=3.5
9
+ Requires-Dist: click>=8.0.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: urst-mpy>=1.0.0,<2.0.0
12
+ Requires-Python: >=3.12
13
+ Project-URL: Homepage, https://github.com/simonl65/otampy
14
+ Project-URL: Bug Tracker, https://github.com/simonl65/otampy/issues
15
+ Description-Content-Type: text/markdown
16
+
17
+ # OTAmpy — Over-The-Air Update Suite for MicroPython
18
+
19
+ ![Static Badge](<https://img.shields.io/badge/status-release_candidate_(v2.0.0_rc1)-orange?logoColor=orange>)
20
+
21
+ [![License: SUL-1.0](https://img.shields.io/badge/license-SUL--1.0-blue.svg)](LICENSE.md)
22
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
23
+ [![PyPI version](https://img.shields.io/pypi/v/otampy.svg)](https://pypi.org/project/otampy/)
24
+ [![Python](https://img.shields.io/pypi/pyversions/otampy.svg)](https://pypi.org/project/otampy/)
25
+ [![CI](https://github.com/simonl65/otampy/actions/workflows/ci.yml/badge.svg)](https://github.com/simonl65/otampy/actions/workflows/ci.yml)
26
+
27
+ OTAmpy is a lightweight Over-The-Air (OTA) file and system management suite for MicroPython microcontrollers (Raspberry Pi Pico, ESP32, ESP8266, and compatible boards). It provides robust remote firmware updates and device control over wireless serial modules (such as XBee) or any transparent UART connection.
28
+
29
+ OTAmpy is built on top of the **Universal Reliable Serial Transport (URST)** protocol, which guarantees error-free framing, integrity checking, and automatic recovery even over noisy or high-latency wireless links.
30
+
31
+ **DISCLAIMER** This is not intended as professional grade OTA. Use at your own risk.
32
+
33
+ ---
34
+
35
+ ## Features
36
+
37
+ - **Transactional OTA firmware updates** — files are staged, SHA-256 verified, and atomically committed; a failed transfer leaves the running firmware untouched.
38
+ - **Interactive file management** — upload (`cp`, `upd`), view (`cat`), or remove (`rm`) files on the device.
39
+ - **Recovery protection** — `rm` refuses to delete boot, main, configuration, OTAmpy, or URST files needed for future wireless maintenance; no force option exists.
40
+ - **Remote reboot and reset** — trigger a hard reboot (`rb`) or MicroPython soft reset (`sr`) over the air.
41
+ - **Diagnostic commands** — `ping` health checks and `mem` RAM/flash queries.
42
+ - **Port management** — `ports` lists and selects adapters; `OTAMPY_PORT` and persistent `~/.config/otampy/config.json` settings avoid repeating `--port` on every command.
43
+ - **Target-matched bytecode deployment** — `--bytecode` compiles OTAmpy and URST to `.mpy` using the connected device's exact `.mpy` format and small-int width.
44
+ - **Fail-safe CLI** — destructive commands display a confirmation prompt before contacting the device.
45
+
46
+ ---
47
+
48
+ ## Repository Structure
49
+
50
+ ```
51
+ otampy/
52
+ ├── src/otampy/ # Host CLI package (Python ≥ 3.12)
53
+ │ ├── cli.py # Click-based command-line interface
54
+ │ ├── deploy.py # Deploy command implementation
55
+ │ └── device/ # MicroPython device library (bundled in releases)
56
+ │ ├── lib/otampy/ # Device-side OTA library
57
+ │ └── examples/ # Example boot.py, main.py, config files
58
+ ├── docs/ # Deployment, release, architecture, and protocol guides
59
+ ├── tests/ # Host-side pytest suite
60
+ └── scripts/ # Automated release gate
61
+ ```
62
+
63
+ The published package contains both the CLI and a read-only copy of the device library. The canonical device source lives in `src/otampy/device/`; see the [deployment guide](docs/deployment.md) for how it is bundled.
64
+
65
+ ---
66
+
67
+ ## Installation
68
+
69
+ Assumes your project will be using UV
70
+
71
+ ```bash
72
+ uv add otampy
73
+ otampy init
74
+ # Edit ota-config.py
75
+ ```
76
+
77
+ During development you can use `pipx install git+https://github.com/simonl65/otampy.git@develop --force` to install the latest development version.
78
+
79
+ `init` creates `boot.py`, `main.py`, and `ota-config.py` in your project at the location (`device-dir`) of your choosing. Edit `ota-config.py` to set the UART pins, baud rate, and timeout for your board. `init` will not overwrite existing files but will prompt you; use `--force` only when intentionally replacing all three.
80
+
81
+ Preview then perform the initial USB deployment:
82
+
83
+ > **WARNING**
84
+ > `deploy` erases the device filesystem before copying files. Back up any application data and configuration first.
85
+
86
+ ```bash
87
+ # Edit ota-config.py before deployment
88
+ otampy deploy --port /dev/ttyACM0 --dry-run
89
+ otampy deploy --port /dev/ttyACM0
90
+ ```
91
+
92
+ The installed package contains the versioned OTAmpy device library and the templates used by `init`. Your project owns `boot.py`, `main.py`, and `ota-config.py`; upgrading the package does not overwrite them.
93
+
94
+ ### Developer installation from this repository
95
+
96
+ Ensure [uv](https://github.com/astral-sh/uv) is installed, then:
97
+
98
+ ```bash
99
+ cd /path/to/otampy
100
+ uv sync
101
+ uv tool install -e .
102
+ ```
103
+
104
+ ### Device library setup (repository checkout)
105
+
106
+ 1. **Copy** `src/otampy/device/examples/config.example.py` to `src/otampy/device/examples/ota-config.py` (do not rename) and set the UART pins and baud rate for your board.
107
+ 2. Deploy the device library, example scripts, and URST to your device:
108
+
109
+ ```bash
110
+ otampy deploy --port /dev/ttyACM0
111
+ ```
112
+
113
+ 3. Optionally add `--with-logger` to install the [`log-to-file`](https://github.com/simonl65/log-to-file) development logger. The default profile runs silently via `NullLogger`.
114
+ 4. For a smaller, target-matched deployment, install `mpy-cross` and pass `--bytecode`. OTAmpy queries the connected firmware's `.mpy` format before erasing anything.
115
+
116
+ ```bash
117
+ uv tool install mpy-cross
118
+ otampy deploy --port /dev/ttyACM0 --bytecode
119
+ ```
120
+
121
+ 5. Call `ota.poll()` inside your application main loop to enable background OTA listening.
122
+
123
+ See the [deployment guide](docs/deployment.md) and [device integration guide](src/otampy/device/README.md) for the complete set of deployment options, logger injection, and integration examples.
124
+
125
+ ---
126
+
127
+ ## Usage
128
+
129
+ All commands follow this pattern:
130
+
131
+ ```bash
132
+ otampy [global-options] <command> [command-options]
133
+ ```
134
+
135
+ ### Global options
136
+
137
+ | Option | Description |
138
+ | ------------------- | --------------------------------------------------------------------- |
139
+ | `-p`, `--port PORT` | Select the OTA UART adapter (e.g. `/dev/ttyUSB0`, `COM3`). |
140
+ | `-b`, `--baud RATE` | Set the baud rate. |
141
+ | `--log-level LEVEL` | Host CLI logging: `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`. |
142
+
143
+ The default log level is `ERROR`. When `--log-level` is supplied, the CLI offers to retain the setting permanently (`p`), for the current shell session (`s`), or only for the current command (`c`).
144
+
145
+ Permanent port and log-level settings are stored together in `~/.config/otampy/config.json`:
146
+
147
+ ```json
148
+ {
149
+ "default_port": "/dev/ttyUSB0",
150
+ "log_level": "DEBUG"
151
+ }
152
+ ```
153
+
154
+ Session-only selections use shell-specific files in the operating system's temporary directory (normally `/tmp` on Linux) and do not alter the permanent configuration. `OTAMPY_PORT` and `OTAMPY_LOG_LEVEL` environment variables override saved settings.
155
+
156
+ ### Commands
157
+
158
+ | Command | Arguments | Description |
159
+ | ------------ | --------------------- | ------------------------------------------------------------------- |
160
+ | `cat` | `file` | Print a file from the device. |
161
+ | `cp` | `source[:dest] [...]` | Copy files or folders to the device without rebooting. |
162
+ | `deploy` | _(see below)_ | Erase and deploy the full device library over USB. |
163
+ | `device-dir` | — | Show or manage the saved project directory for deploy. |
164
+ | `init` | `[directory]` | Scaffold `boot.py`, `main.py`, and `ota-config.py`. |
165
+ | `log-level` | | Show or manage the saved CLI log level. |
166
+ | `ls` | `[path]` | List device directory contents. |
167
+ | `mem` | — | Query device RAM and flash utilisation. |
168
+ | `ping` | — | Connection health check - should receive PONG. |
169
+ | `ports` | — | List available serial adapters; mark and store a selection. |
170
+ | `rb` | — | Hard reboot the device (with confirmation). |
171
+ | `rm` | `path [...]` | Remove paths from the device (with confirmation - not recoverable). |
172
+ | `sr` | — | MicroPython soft reset (with confirmation). |
173
+ | `upd` | `[source[:dest] ...]` | Transactional OTA firmware update.<sup>1</sup> |
174
+
175
+ <sup>1</sup> Updates take place after the device has rebooted; the update process is handled by `boot.py`. With no sources specified, `upd` selects `main.py` and all Python files under `lib/` in the current directory.
176
+
177
+ ### Deployment Options
178
+
179
+ | Option | Effect |
180
+ | --------------------- | --------------------------------------------------------- |
181
+ | `-p`, `--port` | Select the USB/serial device used by `mpremote`. |
182
+ | `--device-dir` | Select the directory containing device/ (`boot.py`, etc.) |
183
+ | `--with-logger` | Install the optional `log-to-file` package. |
184
+ | `--bytecode`, `--mpy` | Compile OTAmpy and URST into target-matched `.mpy` files. |
185
+ | `--mpy-cross` | Select the `mpy-cross` executable or command. |
186
+ | `--no-mip` | Install neither URST nor the optional logger. |
187
+ | `--no-reset` | Leave the board without a final reset. |
188
+ | `--dry-run` | Print the complete `mpremote` command without running it. |
189
+ | `--mpremote` | Use a specific `mpremote` executable. |
190
+
191
+ ### Common examples
192
+
193
+ Select a port for subsequent commands (avoids repeating `--port`):
194
+
195
+ ```bash
196
+ otampy ports
197
+ ```
198
+
199
+ NOTE: The following commands assume you've set the port.
200
+
201
+ Check device connection health:
202
+
203
+ ```bash
204
+ otampy ping
205
+ ```
206
+
207
+ List device files:
208
+
209
+ ```bash
210
+ otampy ls
211
+ otampy ls /lib
212
+ ```
213
+
214
+ Query device memory and storage:
215
+
216
+ ```bash
217
+ otampy mem
218
+ ```
219
+
220
+ Copy files or folders to the device (without reboot):
221
+
222
+ ```bash
223
+ otampy cp settings.json:config/settings.json
224
+ otampy cp assets:assets/
225
+ otampy cp 'device/lib/*:lib/'
226
+ ```
227
+
228
+ `cp` accepts multiple sources, folders, and local wildcard patterns (`*`, `?`, `[]`, `**`). Folder contents are copied recursively; empty folders are not created. Files are streamed to checksum-verified staging files and committed individually while the device continues running. Copies targeting root `/boot.py` or `/main.py` produce a reminder that the replacement will take effect on the next restart.
229
+
230
+ Remove files or directories (recovery paths are protected):
231
+
232
+ ```bash
233
+ otampy rm old.py config.old
234
+ otampy rm 'lib/plugins/*.py'
235
+ ```
236
+
237
+ Quote wildcards to prevent the host shell from expanding them locally (e.g., `otampy rm '*'`). Prefix an argument with `:` (e.g., `:notes.txt`) or use `--literal-remote-paths` if the filename also exists locally to prevent protection/verification aborts. Removing a non-empty directory requires a confirmation prompt.
238
+
239
+ To preserve remote recovery, `rm` cannot remove root `/boot.py`, `/main.py`, `/ota-config.py`, anything under `/lib/otampy` or `/lib/urst`, or an ancestor such as `/lib` or `/`.
240
+
241
+ Trigger an OTA firmware update (defaults to `main.py` and all `lib/` Python files):
242
+
243
+ ```bash
244
+ otampy upd
245
+ ```
246
+
247
+ Update specific files or mapped paths:
248
+
249
+ ```bash
250
+ otampy upd main.py ota-config.py
251
+ otampy upd 'device/lib/something/*.py:lib/something/'
252
+ ```
253
+
254
+ Directories include all Python files recursively. Local `*`, `?`, `[]`, and `**` patterns are supported. A pattern matching multiple files must map to a destination ending in `/`. If any source or pattern has no matches, the update stops before contacting the device.
255
+
256
+ Enable **host-side** diagnostics for a single command:
257
+
258
+ ```bash
259
+ otampy --log-level DEBUG ping
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Device Integration
265
+
266
+ Call `ota.poll()` in your application loop:
267
+
268
+ ```python
269
+ from otampy import OTA
270
+ from machine import UART
271
+
272
+ uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
273
+ ota = OTA(uart, config=config)
274
+
275
+ while True:
276
+ ota.poll()
277
+ # ... application logic
278
+ ```
279
+
280
+ **Important!** - If your device needs to be set to a safe state before a reboot/reset you can add a callback function to the `ota.poll()` call:
281
+
282
+ ```python
283
+ def prepare_for_shutdown():
284
+ # ... application safety logic
285
+
286
+ while True:
287
+ ota.poll(callback=prepare_for_shutdown)
288
+ # ... application logic
289
+ ```
290
+
291
+ This will invoke `prepare_for_shutdown()` before continuing to reboot/reset.
292
+
293
+ Inject any logger that implements `debug`, `info`, `warning`, `error`, and `critical`:
294
+
295
+ ```python
296
+ from log_to_file import Logger
297
+ from otampy import OTA
298
+
299
+ logger = Logger("/logs/ota.log", "some identifier", level="DEBUG")
300
+ ota = OTA(uart, config=config, logger=logger)
301
+ ```
302
+
303
+ When no logger is provided, `OTA` uses an allocation-light `NullLogger`. `NullLogger` is also importable directly for applications that want an unconditional logger variable:
304
+
305
+ ```python
306
+ from otampy import NullLogger
307
+ logger = NullLogger()
308
+ ```
309
+
310
+ See the [device integration guide](src/otampy/device/README.md) for the full configuration reference.
311
+
312
+ ---
313
+
314
+ ## Contributing
315
+
316
+ Contributions are welcome. Please follow the process below to keep the codebase consistent and the release gate green.
317
+
318
+ ### Getting started
319
+
320
+ 1. Fork the repository and create a feature branch from `develop`:
321
+
322
+ ```bash
323
+ git checkout develop
324
+ git checkout -b feature/your-feature-name
325
+ ```
326
+
327
+ 2. Install the development dependencies:
328
+
329
+ ```bash
330
+ uv sync --group dev
331
+ ```
332
+
333
+ 3. Make your changes. Match the existing code style; the project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting.
334
+
335
+ ### Running tests
336
+
337
+ ```bash
338
+ uv run pytest
339
+ ```
340
+
341
+ The test suite requires no connected hardware. Coverage is reported automatically.
342
+
343
+ Run the linter:
344
+
345
+ ```bash
346
+ uv run ruff check .
347
+ uv run ruff format --check .
348
+ ```
349
+
350
+ ### Submitting a pull request
351
+
352
+ - Target the `develop` branch, not `main`.
353
+ - Ensure `ruff check` and `pytest` both pass before opening a PR.
354
+ - Describe what the change does, why it is needed, and how it was tested.
355
+ - For changes to the device library, note any impact on flash or RAM footprint.
356
+ - Keep commits focused and write clear, semantic and scoped commit messages (e.g. `feat(core): add ability to walk on water`).
357
+
358
+ ### Reporting issues
359
+
360
+ Open a GitHub issue and include:
361
+
362
+ - A minimal reproduction case.
363
+ - The host OS, Python version, and OTAmpy version (`otampy --version`).
364
+ - The target board, MicroPython version, and transport (XBee model, direct UART, etc.).
365
+ - Any relevant host CLI and/or device logs (For CLI use: `--log-level DEBUG`. For device set `LOG_LEVEL="DEBUG"` in `ota-config.py`).
366
+
367
+ ### Release process
368
+
369
+ Releases are cut by maintainers using the automated gate described in the [release guide](docs/releasing.md). Do not build publishable artifacts manually.
370
+
371
+ ---
372
+
373
+ ## Documentation
374
+
375
+ | Document | Description |
376
+ | ------------------------------------------------------- | ---------------------------------------------------------------- |
377
+ | [Deployment guide](docs/deployment.md) | All `deploy` options, logging profiles, and bytecode deployment. |
378
+ | [Device integration guide](src/otampy/device/README.md) | Device-side setup, logger injection, and runtime file copy. |
379
+ | [Protocol specification](docs/protocol.md) | URST framing, packet types, and error recovery. |
380
+ | [Architecture overview](docs/architecture.md) | Component relationships and design decisions. |
381
+ | [Release guide](docs/releasing.md) | Versioning, the automated release gate, and publishing. |
382
+
383
+ ---
384
+
385
+ ## License
386
+
387
+ OTAmpy is released under the [Sustainable Use License](LICENSE.md).
388
+
389
+ Non-commercial and internal business use is permitted free of charge. Commercial redistribution or monetisation requires a separate commercial licence. Contact [oss@codeability.co.uk](mailto:oss@codeability.co.uk) for commercial licensing enquiries.