From 2a97e87c16f1d5f2278d3db0d2f9342acc9de80d Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 7 Apr 2026 14:53:02 -0600 Subject: [PATCH] replace repo contents with redirect to donglora org --- .gitignore | 2 - ANSWERS.md | 257 --- CHANGELOG.md | 71 - CONTRIBUTING.md | 54 - DESIGN.md | 108 - LICENSE | 21 - README.md | 225 +- clients/python/.gitignore | 1 - clients/python/README.md | 31 - clients/python/donglora/__init__.py | 381 ---- clients/python/mise.toml | 4 - clients/python/pyproject.toml | 25 - clients/python/uv.lock | 606 ----- clients/rust/.gitignore | 1 - clients/rust/CHANGELOG.md | 27 - clients/rust/CLAUDE_TODO.md | 86 - clients/rust/Cargo.lock | 326 --- clients/rust/Cargo.toml | 21 - clients/rust/LICENSE | 21 - clients/rust/README.md | 37 - clients/rust/justfile | 11 - clients/rust/src/client.rs | 222 -- clients/rust/src/codec.rs | 330 --- clients/rust/src/connect.rs | 174 -- clients/rust/src/discovery.rs | 70 - clients/rust/src/lib.rs | 45 - clients/rust/src/protocol.rs | 693 ------ clients/rust/src/transport.rs | 228 -- demo.jpg | Bin 743138 -> 0 bytes examples/.gitignore | 2 - examples/README.md | 21 - examples/all_commands.py | 50 - examples/justfile | 42 - examples/lora_bridge.py | 174 -- examples/meshcore/README.md | 30 - examples/meshcore/ai_bot.py | 1424 ------------ examples/meshcore/channels.csv | 281 --- examples/meshcore/corpus/build_hand.py | 649 ------ examples/meshcore/corpus/generate.py | 333 --- examples/meshcore/corpus/hand/ack_basic.json | 22 - .../meshcore/corpus/hand/ack_with_path.json | 26 - .../meshcore/corpus/hand/advert_basic.json | 31 - .../corpus/hand/advert_emoji_name.json | 32 - .../hand/advert_feat_flags_as_name.json | 32 - .../meshcore/corpus/hand/advert_location.json | 34 - .../corpus/hand/advert_no_appdata.json | 25 - .../meshcore/corpus/hand/advert_sensor.json | 30 - .../corpus/hand/advert_truncated.json | 20 - .../corpus/hand/advert_with_path.json | 35 - .../corpus/hand/anon_req_tc_fallback.json | 30 - .../meshcore/corpus/hand/bad_path_len.json | 15 - .../hand/bad_path_len_reserved_hash_size.json | 16 - .../corpus/hand/control_discover_req.json | 23 - .../meshcore/corpus/hand/empty_packet.json | 14 - .../meshcore/corpus/hand/grp_txt_basic.json | 26 - .../meshcore/corpus/hand/multipart_ack.json | 24 - .../meshcore/corpus/hand/noise_packet.json | 14 - .../meshcore/corpus/hand/path_3byte_hash.json | 29 - .../meshcore/corpus/hand/path_4byte_hash.json | 17 - .../corpus/hand/req_transport_direct.json | 33 - .../hand/response_corrupted_pathlen_1.json | 11 - .../hand/response_corrupted_pathlen_2.json | 11 - .../corpus/hand/response_valid_1hop.json | 14 - .../corpus/hand/response_valid_2hop.json | 14 - .../meshcore/corpus/hand/too_short_1byte.json | 14 - .../corpus/hand/too_short_2bytes.json | 14 - .../corpus/hand/trace_4byte_hash.json | 24 - .../meshcore/corpus/hand/trace_basic.json | 23 - .../corpus/hand/txt_msg_direct_2b_hash.json | 30 - .../corpus/hand/txt_msg_flood_with_loops.json | 33 - examples/meshcore/corpus/schema.json | 128 -- examples/meshcore/corpus/validate.py | 207 -- examples/meshcore/meshcore_rx.py | 931 -------- examples/meshcore/telemetry_monitor.py | 1143 ---------- examples/mise.toml | 4 - examples/ping_pong.py | 58 - examples/pyproject.toml | 19 - examples/simple_rx.py | 38 - examples/simple_tx.py | 32 - examples/uv.lock | 630 ----- firmware/.cargo/config.toml | 13 - firmware/.gitignore | 5 - firmware/Cargo.lock | 2017 ----------------- firmware/Cargo.toml | 127 -- firmware/PROTOCOL.md | 291 --- firmware/README.md | 61 - firmware/TODO.md | 4 - firmware/build.rs | 121 - firmware/justfile | 211 -- firmware/ld/nrf52840-memory.x | 16 - firmware/mise.toml | 9 - firmware/src/board/PORTING.md | 96 - firmware/src/board/esp32s3.rs | 98 - firmware/src/board/heltec_v3.rs | 97 - firmware/src/board/heltec_v3_uart.rs | 93 - firmware/src/board/heltec_v4.rs | 90 - firmware/src/board/mod.rs.j2 | 16 - firmware/src/board/rak_wisblock_4631.rs | 144 -- firmware/src/board/traits.rs | 61 - firmware/src/board/wio_tracker_l1.rs | 147 -- firmware/src/channel.rs | 67 - firmware/src/display.rs | 631 ------ firmware/src/driver/mod.rs | 5 - firmware/src/driver/sh1106.rs | 132 -- firmware/src/driver/simple_led.rs | 20 - firmware/src/hal/esp32s3.rs | 82 - firmware/src/hal/mod.rs | 10 - firmware/src/hal/nrf52840.rs | 82 - firmware/src/host/framing.rs | 124 - firmware/src/host/mod.rs | 16 - firmware/src/host/uart.rs | 75 - firmware/src/host/usb.rs | 137 -- firmware/src/main.rs | 102 - firmware/src/protocol.rs | 707 ------ firmware/src/radio.rs | 386 ---- mux/python/.gitignore | 1 - mux/python/README.md | 26 - mux/python/donglora_mux/__init__.py | 517 ----- mux/python/donglora_mux/__main__.py | 3 - mux/python/justfile | 15 - mux/python/mise.toml | 4 - mux/python/pyproject.toml | 22 - mux/python/uv.lock | 64 - mux/rust/.gitignore | 1 - mux/rust/CHANGELOG.md | 29 - mux/rust/Cargo.lock | 940 -------- mux/rust/Cargo.toml | 26 - mux/rust/LICENSE | 21 - mux/rust/README.md | 29 - mux/rust/justfile | 19 - mux/rust/src/daemon.rs | 651 ------ mux/rust/src/intercept.rs | 291 --- mux/rust/src/main.rs | 115 - mux/rust/src/session.rs | 57 - 134 files changed, 14 insertions(+), 20100 deletions(-) delete mode 100644 .gitignore delete mode 100644 ANSWERS.md delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.md delete mode 100644 DESIGN.md delete mode 100644 LICENSE delete mode 100644 clients/python/.gitignore delete mode 100644 clients/python/README.md delete mode 100644 clients/python/donglora/__init__.py delete mode 100644 clients/python/mise.toml delete mode 100644 clients/python/pyproject.toml delete mode 100644 clients/python/uv.lock delete mode 100644 clients/rust/.gitignore delete mode 100644 clients/rust/CHANGELOG.md delete mode 100644 clients/rust/CLAUDE_TODO.md delete mode 100644 clients/rust/Cargo.lock delete mode 100644 clients/rust/Cargo.toml delete mode 100644 clients/rust/LICENSE delete mode 100644 clients/rust/README.md delete mode 100644 clients/rust/justfile delete mode 100644 clients/rust/src/client.rs delete mode 100644 clients/rust/src/codec.rs delete mode 100644 clients/rust/src/connect.rs delete mode 100644 clients/rust/src/discovery.rs delete mode 100644 clients/rust/src/lib.rs delete mode 100644 clients/rust/src/protocol.rs delete mode 100644 clients/rust/src/transport.rs delete mode 100644 demo.jpg delete mode 100644 examples/.gitignore delete mode 100644 examples/README.md delete mode 100755 examples/all_commands.py delete mode 100644 examples/justfile delete mode 100755 examples/lora_bridge.py delete mode 100644 examples/meshcore/README.md delete mode 100644 examples/meshcore/ai_bot.py delete mode 100644 examples/meshcore/channels.csv delete mode 100755 examples/meshcore/corpus/build_hand.py delete mode 100755 examples/meshcore/corpus/generate.py delete mode 100644 examples/meshcore/corpus/hand/ack_basic.json delete mode 100644 examples/meshcore/corpus/hand/ack_with_path.json delete mode 100644 examples/meshcore/corpus/hand/advert_basic.json delete mode 100644 examples/meshcore/corpus/hand/advert_emoji_name.json delete mode 100644 examples/meshcore/corpus/hand/advert_feat_flags_as_name.json delete mode 100644 examples/meshcore/corpus/hand/advert_location.json delete mode 100644 examples/meshcore/corpus/hand/advert_no_appdata.json delete mode 100644 examples/meshcore/corpus/hand/advert_sensor.json delete mode 100644 examples/meshcore/corpus/hand/advert_truncated.json delete mode 100644 examples/meshcore/corpus/hand/advert_with_path.json delete mode 100644 examples/meshcore/corpus/hand/anon_req_tc_fallback.json delete mode 100644 examples/meshcore/corpus/hand/bad_path_len.json delete mode 100644 examples/meshcore/corpus/hand/bad_path_len_reserved_hash_size.json delete mode 100644 examples/meshcore/corpus/hand/control_discover_req.json delete mode 100644 examples/meshcore/corpus/hand/empty_packet.json delete mode 100644 examples/meshcore/corpus/hand/grp_txt_basic.json delete mode 100644 examples/meshcore/corpus/hand/multipart_ack.json delete mode 100644 examples/meshcore/corpus/hand/noise_packet.json delete mode 100644 examples/meshcore/corpus/hand/path_3byte_hash.json delete mode 100644 examples/meshcore/corpus/hand/path_4byte_hash.json delete mode 100644 examples/meshcore/corpus/hand/req_transport_direct.json delete mode 100644 examples/meshcore/corpus/hand/response_corrupted_pathlen_1.json delete mode 100644 examples/meshcore/corpus/hand/response_corrupted_pathlen_2.json delete mode 100644 examples/meshcore/corpus/hand/response_valid_1hop.json delete mode 100644 examples/meshcore/corpus/hand/response_valid_2hop.json delete mode 100644 examples/meshcore/corpus/hand/too_short_1byte.json delete mode 100644 examples/meshcore/corpus/hand/too_short_2bytes.json delete mode 100644 examples/meshcore/corpus/hand/trace_4byte_hash.json delete mode 100644 examples/meshcore/corpus/hand/trace_basic.json delete mode 100644 examples/meshcore/corpus/hand/txt_msg_direct_2b_hash.json delete mode 100644 examples/meshcore/corpus/hand/txt_msg_flood_with_loops.json delete mode 100644 examples/meshcore/corpus/schema.json delete mode 100755 examples/meshcore/corpus/validate.py delete mode 100755 examples/meshcore/meshcore_rx.py delete mode 100644 examples/meshcore/telemetry_monitor.py delete mode 100644 examples/mise.toml delete mode 100755 examples/ping_pong.py delete mode 100644 examples/pyproject.toml delete mode 100755 examples/simple_rx.py delete mode 100755 examples/simple_tx.py delete mode 100644 examples/uv.lock delete mode 100644 firmware/.cargo/config.toml delete mode 100644 firmware/.gitignore delete mode 100644 firmware/Cargo.lock delete mode 100644 firmware/Cargo.toml delete mode 100644 firmware/PROTOCOL.md delete mode 100644 firmware/README.md delete mode 100644 firmware/TODO.md delete mode 100644 firmware/build.rs delete mode 100644 firmware/justfile delete mode 100644 firmware/ld/nrf52840-memory.x delete mode 100644 firmware/mise.toml delete mode 100644 firmware/src/board/PORTING.md delete mode 100644 firmware/src/board/esp32s3.rs delete mode 100644 firmware/src/board/heltec_v3.rs delete mode 100644 firmware/src/board/heltec_v3_uart.rs delete mode 100644 firmware/src/board/heltec_v4.rs delete mode 100644 firmware/src/board/mod.rs.j2 delete mode 100644 firmware/src/board/rak_wisblock_4631.rs delete mode 100644 firmware/src/board/traits.rs delete mode 100644 firmware/src/board/wio_tracker_l1.rs delete mode 100644 firmware/src/channel.rs delete mode 100644 firmware/src/display.rs delete mode 100644 firmware/src/driver/mod.rs delete mode 100644 firmware/src/driver/sh1106.rs delete mode 100644 firmware/src/driver/simple_led.rs delete mode 100644 firmware/src/hal/esp32s3.rs delete mode 100644 firmware/src/hal/mod.rs delete mode 100644 firmware/src/hal/nrf52840.rs delete mode 100644 firmware/src/host/framing.rs delete mode 100644 firmware/src/host/mod.rs delete mode 100644 firmware/src/host/uart.rs delete mode 100644 firmware/src/host/usb.rs delete mode 100644 firmware/src/main.rs delete mode 100644 firmware/src/protocol.rs delete mode 100644 firmware/src/radio.rs delete mode 100644 mux/python/.gitignore delete mode 100644 mux/python/README.md delete mode 100644 mux/python/donglora_mux/__init__.py delete mode 100644 mux/python/donglora_mux/__main__.py delete mode 100644 mux/python/justfile delete mode 100644 mux/python/mise.toml delete mode 100644 mux/python/pyproject.toml delete mode 100644 mux/python/uv.lock delete mode 100644 mux/rust/.gitignore delete mode 100644 mux/rust/CHANGELOG.md delete mode 100644 mux/rust/Cargo.lock delete mode 100644 mux/rust/Cargo.toml delete mode 100644 mux/rust/LICENSE delete mode 100644 mux/rust/README.md delete mode 100644 mux/rust/justfile delete mode 100644 mux/rust/src/daemon.rs delete mode 100644 mux/rust/src/intercept.rs delete mode 100644 mux/rust/src/main.rs delete mode 100644 mux/rust/src/session.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index af4d558a..00000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -__pycache__/ -.hypothesis/ diff --git a/ANSWERS.md b/ANSWERS.md deleted file mode 100644 index 8a0a60b8..00000000 --- a/ANSWERS.md +++ /dev/null @@ -1,257 +0,0 @@ -## Module Naming & Structure - - 1. The unified host communication module — do you prefer comm/, host/, or something else? - -host/ - - 2. Should protocol.rs stay as a single file, or split into protocol/types.rs + protocol/framing.rs (absorbing protocol_io.rs)? - -Split it apart logically - - 3. Or should the framing code live inside the comm/ module since it's only used there? - -You decide what makes the most sense based on our first principles - - 4. The radio/ directory currently has just task.rs (and presumably a mod.rs). Should it stay a directory or flatten to radio.rs? - -Flatten. No directory should ever have just one (or even 2?) things in it! - - 5. Same question for display/ — it has task.rs, render.rs, and the driver. Keep as directory? - -Nope. See previous question. - - 6. Should there be a board/drivers/ subdirectory, or should the SH1106 driver just stay at the board level (e.g. board/sh1106.rs)? - -Somehow, things like drivers need to be clearly separated from boards, both by -filename+location and by how the code is structured (abstractions, etc) - - 7. The channel.rs file is standalone — is that fine, or should inter-task communication be grouped differently? - -I guess fine? - - Board Definition Contract - - 8. into_parts() is currently an inherent method, not on the trait. Should it be added to LoRaBoard (requires associated types), or is the implicit contract fine? - -Nothing ever implicit. This is rust. - - 9. Should we add a spawn_host_task() function to each board, or keep that concern in the comm/ module with internal cfg? - -If it is going to vary by hardware, then yeah I think we have to push it down -into the board abstraction level. Which sucks, but at least so far seems to be -how it works. I frankly still don't believe that the Heltec V3 cannot be made to -look like a proper ttyACM0 device! I'm not giving up on that shit yet!!! - - 10. Should boards export a single BoardParts struct (with radio, comm, display fields) instead of returning a tuple? - -struct > tuple. - - 11. Should LoRaBoard gain associated constants like const HAS_DISPLAY: bool or const COMM_TYPE: CommType? - -I don't know. You can have a HTV4 with an OLED or without one. Does anything -above the board abstraction layer need to know this??? - - 12. Is the current LoRaBoard trait the right level of abstraction? Should it grow (more methods), shrink (fewer), or stay the same? - -You decide. - - 13. Should mac_address() remain on the trait, or become a field in a parts struct? - -I think it can fit nicely in the struct. If that's a pattern we're choosing, be -consistent. - - 14. The into_parts() currently consumes self (the Board). Is this the right ownership model? - -I guess? Sounds rusty. - -## Chip-Family Helpers - - 15. For nrf52840.rs — should it be a single file or a directory (nrf52840/mod.rs + submodules)? - -Maybe a directory? But how are we separating components from board -definitions??? I don't think we should just have one directory full of random -directories for MCUs, displays, UARTS, and boards all plopped in there. That -sounds **disorganized**, not **organized**. - - 16. The nRF52840 bind_interrupts! macro — should it live in the helper or per-board? (Boards might use different peripherals.) - -If you're **very** confident we'll need to customize it per-board, then go ahead -and break it out. If not, leave it generic across all boards. - - 17. The mac_address() unsafe FICR block is identical in both nRF boards. Definitely extract to helper? - -Yes. - - 18. Both ESP32-S3 boards have identical Vext GPIO36 and display reset GPIO21 logic. Should this be in esp32s3.rs or per-board? - -If you're confident this **will need to be board specific** before long, break -it out. Otherwise conslidate. - - 19. Should chip family helpers define ALL possible init functions (USB, UART, etc.) even if not all boards use them, or only what's actually used? - -Good question. I'm not sure. Best advice from you? - - 20. Should esp32s3.rs be split into submodules (esp32s3/radio.rs, esp32s3/usb.rs, esp32s3/display.rs)? - -Uhhhh.. and ESP32 is an MCU, not a radio, not a USB, not a display. GET THE -ABSTRACTIONS AND SEPARATION RIGHT!!!!!!!!!!!!!!!!!!!!! - - 21. If we add an nrf52840.rs, should the existing esp32s3.rs be renamed for consistency (both are chip-family helpers but esp32s3 is named after the chip while nrf52840 would - be named after the chip too — good)? - -Make the naming scheme consistent. - -## Display - - 22. Should the board provide an already-initialized display (DisplayParts { display: DisplayDriver, mac }) or raw parts with a separate create_display() async function? - -Board should provide the means to its consumer to init display, radio, etc. It -shouldn't preemptively init stuff. - - 23. Since into_parts() is sync but display init is async — does the create_display() approach seem right? - -I don't know. What's your advice? - - 24. Do we need an AsyncDisplay trait (for flush + brightness), or is relying on the concrete type's methods via duck-typing sufficient? - -What do you think? - - 25. Both drivers implement DrawTarget — rendering is already generic. The only non-generic part is flush() and init(). Is pushing init/flush into the board layer the right call? - -I think so. - - 26. The SH1106 driver (display/sh1106.rs) — is it a hardware driver that belongs in board/drivers/, or display infrastructure that should stay in display/? - -Think of the abstractions. Imagine you're just looking at this project for the -first time. Where would you look for a display driver??? drivers/? Something else? - - 27. The display init retry logic (try twice, give up) is identical for both drivers. Should this be a common wrapper? - -I think that retry is stupid. Let the client retry. - - 28. Display brightness: SSD1306 uses Brightness::BRIGHTEST, SH1106 uses 0xFF. Should create_display() handle this internally? - -Make it generic across displays. - - 29. Currently display errors are silently ignored (let _ = display.flush().await). Is this intentional and should remain? - -Seems like it should be elegantly handled and propagated up the abstraction layers. - -## Host Communication - - 30. USB has DTR disconnect detection that sends DisplayCommand::Reset/On. UART has no equivalent. Is it OK for UART boards to simply not have this feature? - -I mean, do we have a choice? I **really** don't want to support these UART -boards. And I'm still refusing to believe that the Heltec V3's USB hardware -cannot be made to look like a proper ttyACM. People use these MCUs and boards to -create keyboards/HID and other things after all! So my REAL preference is to NOT -support these boards. That said, we have to support the Heltec V3. So, if we -can't do it properly, then I guess we don't have a choice here. - - 31. The USB task runs join(usb_dev.run(), protocol_loop(...)) — UART just loops. Is having structurally different task bodies behind the same host_task name acceptable? - -Consistency is important to me. I don't know the right answer here. But be -consistent and smart. - - 32. Should comm/mod.rs compile ONLY the active transport, or compile both and let the linker dead-code-eliminate? - -Umm if there's a nice way to do it, then sure. But no leaky abstractions! Use -traits and abstractions and layers so we compile the right thing for a given configuration. - - 33. The USB task's COBS accumulation is inline and the UART task uses FrameAccumulator. I've verified they're semantically identical. Can I just replace the USB inline code with FrameAccumulator? - -Sure. Is "FrameAccumulator" the right name for this though? - - 34. The USB task's route_command (inline, lines 206-238) is byte-for-byte identical to protocol_io::route_command. Can I just delete the inline one? - -Yes. - - 35. MAX_FRAME is defined in both usb/task.rs (line 16) and protocol_io.rs (line 12). Should there be one canonical definition? - -Yes. - -## Build System - - 36. How should build.rs discover chip-family helpers? Options: (a) scan board files for use super::, (b) naming convention, (c) explicit list in build.rs, (d) marker comments in board files? - -I like build.rs scanning for board files. I just want them all in a logical -directory tree. - - 37. The Jinja template approach — keep it, or is there something better? - -I'm pretty happy with it. You tell me: is there something better??? - - 38. The justfile _info helper uses a hardcoded if/elif chain. Should this become data-driven (scan a file or Cargo.toml)? - -Yes. - - 39. Should the justfile board list at line 15 be auto-derived from Cargo.toml features? - -Maybe. What if it's derived from the source files it finds? - - 40. build.rs excludes mod, traits, esp32s3 from board discovery. With nrf52840.rs added, the exclusion list grows. Is a naming convention better (e.g. - all-lowercase-with-underscores = board, CamelCase or chip-family-names = helper)? - -"Exclustion lists" are evil. Fix the abstractions and design so we don't have to -deal with this shit. - -## Future-Proofing - - 41. Do ALL future boards use SX1262, or could there be SX1276/SX1280/other radios? - -There will be other radios. - - 42. Could a future board have NO display at all? (Currently all boards return Some(DisplayParts) or have optional display.) - -Yes, many boards will have no display. - - 43. Could a future board use SPI display instead of I2C? - -Probably. - - 44. Could a future board use a completely different MCU family (STM32, RP2040)? - -Yes. In fact I have both STM and RP2040 boards sitting here now. - - 45. Is there a realistic scenario where a board needs both USB AND UART? - -No. FUCK UART. - - 46. Could a future board need additional tasks beyond radio/comm/display (e.g. LED, sensor)? - -I am thinking of adding an RGB LED interface down into the boards. Then if the -host has turned on the display, we'd blink the LED green anytime we receive and -red any time we transmit a packet. - -And these boards often have lots of other hardware. Wi-Fi, BLE, sensors, -whatever. DongLoRa is really just about the radio though so I'm not prioritizing -supporting these, beyond the RGB LED thing I mentioned. - -## Migration & Testing - - 47. Should this be one atomic refactor, or phased (dedup first, then reorg, then display)? I propose phased — each step independently compilable and testable. - -Probably phased. Use jj to checkpoint as you go along. When you're happy with a -phase, "jj commit -m 'WIP: phase description'" (jj commit is the same as a jj -describe + jj new) - - 48. Are there any in-flight changes on other branches/bookmarks that would conflict? - -Nope. We just finished getting the Heltec V3 to work, which are the last few -"WIP" commits. - - 49. The protocol tests (just test) use DONGLORA_HOST_TEST=1 to skip build.rs board logic. Will the reorg affect this? - -You tell me. What I do know is testing is extremely important. I'd like -comprehensive tests, property tests, mutation testing, fuzz testing. Basically -throw everything we have at it. - - 50. After the reorg, do you want a CI-enforceable rule like "no #[cfg(feature = \"board_name\")] outside board/ and comm/mod.rs"? - -That sounds great. Any mechanisms to enforce our abstractions and first -principles are very welcome. - - -BTW, NO unsafe code unless it is ABSOLUTELY REQUIRED. I know we're doing -embedded stuff. There are probably a few unsafe things we have to do. But always -research for ways to eliminate unsafe code. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1438fd8c..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,71 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/), -and this project adheres to [Semantic Versioning](https://semver.org/). - -## [0.2.0] - 2026-04-07 - -### Added - -- Wio Tracker L1 board support (nRF52840 + SX1262, SH1106 OLED) -- LED blink on packet activity: brief flash on RX and TX when display is on - - RX brightness scales with SNR (stronger signal = brighter) - - Works on all boards with a user LED (Heltec V3/V4, Wio Tracker) -- Native USB CDC-ACM support for Heltec V3 (requires hardware mod: solder R29/R3) -- `heltec_v3_uart` feature for stock (unmodified) V3 boards via CP2102 bridge -- `RgbLed` trait and `SimpleLed` driver for GPIO-based LEDs -- `LoRaBoard` trait with full associated types (`RadioParts`, `CommParts`, - `DisplayParts`, `DisplayDriver`, `LedDriver`) and `BoardParts` struct - -### Changed - -- Major firmware architecture reorganization: - - `hal/` module for MCU-specific primitives (ESP32-S3, nRF52840) - - `driver/` module for hardware peripheral drivers (SH1106, SimpleLed) - - `host/` module for unified USB/UART communication with internal cfg dispatch - - `board/` contains only board definitions — no MCU helpers or drivers mixed in - - Board files are declarative wiring diagrams calling hal primitives -- `run()` function has zero board-specific conditional compilation -- Display init moved to board layer (`create_display()`) — display task has no cfg -- Flattened `radio/` and `display/` directories (no single-file directories) -- `build.rs` auto-discovers boards by content (implements `LoRaBoard`) and - helper modules by `use super::` imports — no exclusion lists -- `check` and `clippy` commands now use `--release` profile -- Deduplicated USB/UART protocol framing (shared `CobsDecoder` + `route_command`) -- Removed `ErrorCode::CrcError` (SX1262 silently drops bad-CRC packets) - -### Fixed - -- Display stuck on splash screen for UART boards (disconnected flag started true) - -## [0.1.0] - 2026-03-15 - -### Added - -- Transparent LoRa radio over USB CDC-ACM (VID `1209`, PID `5741`) -- COBS-framed fixed-size LE binary protocol (9 commands, 7 responses) -- Radio configuration: frequency, bandwidth, spreading factor, coding rate, TX power -- Continuous RX mode with RSSI/SNR per packet -- Single-shot TX with optional per-packet config override -- SSD1306 OLED display support with status dashboard - - Radio state, frequency, modulation parameters - - Packet counters (RX/TX) - - RSSI sparkline (1-minute history, TX shown as dotted bars) - - Combined splash/waiting screen on disconnect -- Auto-detect USB host connect/disconnect via DTR -- Config validation against per-board hardware limits -- `TX_POWER_MAX` sentinel (`-128`): auto-use the board's maximum TX power -- Board support: Heltec V3, Heltec V4, RAK WisBlock 4631 -- Python host library (`clients/python/`) with `pip install` and `donglora-mux` CLI -- USB multiplexer for sharing one dongle with multiple applications -- Two-way LoRa bridge over TCP (`examples/lora_bridge.py`) - -### Technical - -- Embassy async runtime (no threads, CPU sleeps when idle) -- Zero-config: boots and waits for host commands -- Protocol-agnostic: firmware has no opinion about what you transmit -- Tasks never panic: all errors reported to host or logged -- Board abstraction: one `.rs` file + one Cargo feature per board diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 43b8bc19..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,54 +0,0 @@ -# Contributing to DongLoRa - -## Adding Board Support - -DongLoRa is designed to make adding new boards easy: - -1. Create `firmware/src/board/your_board.rs` implementing the `init()` function -2. Add a Cargo feature in `firmware/Cargo.toml` with the board's dependencies -3. Add the board's feature/target/chip to the `justfile` boards list -4. `firmware/build.rs` auto-discovers the new file and generates the module selector - -See existing board files (`heltec_v3.rs`, `rak_wisblock_4631.rs`) as templates. -Each board file owns all hardware init and returns peripheral bundles that the -common tasks consume. No shared board code — each board is self-contained. - -## Code Style - -- **Clippy-clean.** `just clippy ` must pass with no warnings. -- **No panics in tasks.** Use `match`/`if let`/`warn!` — never `.unwrap()` in async task code. Board init (runs once at startup) may use `.expect("reason")`. -- **Display rendering uses `.ok()`.** Drawing is best-effort; display errors are not recoverable. -- **Every `let _ =` gets an inline comment** explaining why the result is discarded. - -## Client Libraries - -Client libraries exist in [Python](clients/python/) and [Rust](clients/rust/). -To add a library in another language: - -1. Create a directory with the language's standard project layout -2. Implement the wire protocol from [firmware/PROTOCOL.md](firmware/PROTOCOL.md) -3. Include device discovery (USB VID `1209`, PID `5741`) -4. Include COBS framing, command encoding, and response decoding -5. Add a mux client (connect to the mux daemon's Unix socket or TCP) - -The protocol is intentionally simple — 8 commands, 7 responses, fixed-size LE. -A minimal client library should be implementable in a weekend. - -## Building and Testing - -```sh -cd firmware -just check-all # Compile-check all boards -just build-all # Build release firmware for all boards -just clippy # Lint a specific board -just test # Host-side protocol unit tests -``` - -Tool versions are pinned in each project's `mise.toml`. -Tools are installed automatically on first run. - -## Commits - -- [Conventional Commits](https://www.conventionalcommits.org/) format -- Logically grouped (one concern per commit) -- We use [jj](https://martinvonz.github.io/jj/) for version control diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 1f18ea10..00000000 --- a/DESIGN.md +++ /dev/null @@ -1,108 +0,0 @@ -# Design Principles - -These are the non-negotiable first principles of this project. Every design decision, -naming choice, module boundary, and abstraction is evaluated against them. When in -doubt, choose simplicity and clarity over cleverness. - -## What This Is - -A transparent LoRa radio over USB. Plug the dongle into your PC, talk to any LoRa -radio within range. That's it. - -## What This Is Not - -Not a mesh network node. Not a Meshtastic device. Not a protocol implementation. Not -an application framework. This firmware has no opinion about what you do with your -radio — it just gives you clean access to it. - -## Principles - -1. **Single purpose.** Transparent LoRa radio over USB. No mesh, no routing, no - application logic. Ever. - -2. **Protocol-agnostic.** The firmware is a dumb pipe. It exposes clean LoRa - parameters (frequency, bandwidth, spreading factor, coding rate, sync word, TX - power) and nothing else. No raw register access, no chip-specific leakage. - -3. **Host-driven.** The firmware makes zero autonomous decisions. The radio idles on - boot until the host commands it. No persistence across reboots. The host is always - in control. - -4. **Zero-config.** Boots, enumerates USB, waits for commands. The host plugs in and - goes. - -5. **Robustness over features.** Tasks never panic. Errors are reported to the host. - The radio self-recovers. Ship fewer features, but make them bulletproof. - -6. **Minimal footprint.** Avoid heavy dependencies. Keep the binary small. Every - dependency must justify its inclusion. - -7. **Auto-detect peripherals.** If the board has a display, keyboard, or sensors, - use them. No user configuration required. - -8. **World-class Rust.** Fully leverage the type system for safety. Strict `no_std`. - Always clippy-clean. Thoughtfully designed — everything has an obvious home. - Anyone who reads this code should immediately think *"this is nice."* - -9. **Easy board support.** Adding a new board means dropping a `.rs` file and adding - a Cargo feature. Nothing else changes. - -10. **Support all common LoRa boards.** This project supports every common LoRa - board, and even some uncommon ones. The architecture is designed to accommodate - them cleanly. If the code isn't structured for a new board, fix the code. - -11. **Minimal overhead.** The dongle should add as little latency as possible - between the radio and the host. No unnecessary copies, no blocking operations - in the radio path, no computation that isn't strictly needed. - -12. **Standard toolchain.** No custom Rust forks or special SDKs. Use stock `rustup` - channels (stable, nightly). Xtensa targets use nightly + `-Zbuild-std`; Cortex-M - and RISC-V targets use stable. Boards that need the `esp` toolchain (via `espup`) - are supported but gracefully skipped when the toolchain isn't installed. - -## Architecture - -``` -Host PC - │ USB CDC-ACM (COBS-framed fixed-size LE) - ▼ -usb_task ──Command──► radio_task ──► SX1262 - ◄──Response── │ - StatusWatch - ▼ - display_task (optional) ──► OLED/TFT -``` - -Each task exclusively owns its peripheral. No shared state except typed channels and -a watch signal. Pure actor model. - -## Peripheral Model - -Boards can have optional peripherals beyond the radio and USB. The board's -`into_parts()` returns `Option` slots for each, and the corresponding task is -only spawned when present. - -| Peripheral | Slot | Task | Examples | -|---|---|---|---| -| Display | `Option` | `display_task` | SSD1306 OLED, ST7789 TFT | -| Input | `Option` | `input_task` | T-Deck keyboard, buttons | -| Telemetry | `Option` | `telemetry_task` | Battery voltage, temperature | - -**Board files are always separate.** A T-Deck is `t_deck.rs`, not a variant of -Heltec V3. Each board owns its own init code and pin mappings. Shared logic between -boards that use the same MCU can be factored into helper modules if it earns its -keep, but never prematurely. - -## Technical Decisions - -| Decision | Choice | Why | -|---|---|---| -| USB framing | COBS | Compact, `no_std`, zero-overhead frame delimiting | -| Serialization | Fixed-size LE | Every field at a known offset. No varints, no surprises | -| Async runtime | Embassy | De facto standard for async embedded Rust | -| Display | Radio status dashboard | Freq, BW, SF, packet counts, RSSI/SNR | -| Boot behavior | Idle until host commands | Host-driven principle | -| Persistence | None | Always start fresh, true dumb pipe | -| Board codegen | Jinja2 via build.rs | Auto-discovers boards from filesystem | -| Rust toolchain | Stock `rustup` (stable/nightly) | No forks; `espup` for Xtensa boards only | -| Firmware output | `firmware/builds/donglora-{board}-{profile}.elf` | Readable names, not buried in `target/` | diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a4ad6f14..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 3fa7bc76..4848cffd 100644 --- a/README.md +++ b/README.md @@ -1,214 +1,17 @@ -# DongLoRa +# DongLoRa has moved -**/ˈdɒŋ.ɡəl.ɔːr.ə/** — a [portmanteau](https://en.wikipedia.org/wiki/Portmanteau) of **dongle** and **LoRa**. +This repository has been split into individual repos under the +[**donglora** GitHub organization](https://github.com/donglora). -Transparent LoRa radio over USB. Flash the firmware onto a supported -board, plug it in, and send/receive LoRa packets from your computer. -The firmware handles the radio; your code decides what to do with it. +| What | New location | +|------|--------------| +| Firmware | [donglora/firmware](https://github.com/donglora/firmware) | +| Rust client library | [donglora/client-rs](https://github.com/donglora/client-rs) | +| Python client library | [donglora/client-py](https://github.com/donglora/client-py) | +| Rust mux daemon | [donglora/mux-rs](https://github.com/donglora/mux-rs) | +| Python mux daemon | [donglora/mux-py](https://github.com/donglora/mux-py) | +| MeshCore bridge | [donglora/bridge](https://github.com/donglora/bridge) | +| MeshCore AI bot | [donglora/ai-bot](https://github.com/donglora/ai-bot) | +| Example scripts | [donglora/examples](https://github.com/donglora/examples) | -Any LoRa board becomes a reusable peripheral that works with any host — -a laptop, a desktop, a Raspberry Pi, a server. The built-in multiplexer -lets multiple applications share one dongle at the same time, so you can -run a MeshCore bot, a packet analyzer, and a bridge all on the same -radio without dedicated hardware for each. - -![photograph of firmware running on Heltec V4](./demo.jpg) - -## How It Works - -``` -Your code ──► USB serial ──► firmware ──► LoRa radio - -Your code ──► client library ──► USB serial ──► firmware ──► LoRa radio - -Your code ──► client library ──┐ -Your code ──► client library ──┤ - ... ├──► mux daemon ──► USB serial ──► firmware ──► LoRa radio -Your code ──► client library ──┤ -Your code ──► client library ──┘ -``` - -Four components, each self-contained with its own dependencies and tooling: - -| Component | What it is | Language | -|-----------|-----------|----------| -| **[firmware/](firmware/)** | Embedded firmware flashed onto a LoRa board. Speaks a simple [binary protocol](firmware/PROTOCOL.md) over USB. | Rust | -| **[clients/](clients/)** | Client libraries that handle device discovery, COBS framing, and protocol encoding/decoding. This is what your code imports. | [Python](clients/python/), [Rust](clients/rust/) | -| **[mux/](mux/)** | Optional daemon that lets multiple applications share one dongle. Owns the USB port, exposes a Unix socket (and optional TCP). | [Python](mux/python/), [Rust](mux/rust/) | -| **[examples/](examples/)** | Ready-to-run scripts demonstrating the client library — receive, transmit, bridge, MeshCore decoder, AI bot. | Python | - -The client library can talk directly to the firmware over USB, or connect -through the mux when multiple applications need the same dongle. - -## Quick Start - -1. Get a [supported board](#supported-boards) -2. `cd firmware && just flash heltec_v4` -3. `cd examples && just rx` -4. See packets - -## Supported Boards - -| Board | MCU | Radio | Display | -|-------|-----|-------|---------| -| Heltec V3 | ESP32-S3 | SX1262 | SSD1306 OLED | -| Heltec V4 | ESP32-S3 | SX1262 | SSD1315 OLED | -| RAK WisBlock 4631 | nRF52840 | SX1262 | SSD1306 OLED (optional) | - -## Examples - -Dependencies are handled automatically: - -```sh -cd examples -just rx # receive packets -just tx # transmit a packet -just ping-pong --role tx # two-dongle ping-pong demo -just test-commands # exercise all DongLoRa commands -just bridge --mode server # LoRa bridge over TCP -just meshcore # MeshCore packet decoder -just orac # MeshCore AI bot (needs ANTHROPIC_API_KEY) -just telemetry # MeshCore repeater telemetry monitor -``` - -## Multiplexer - -To share one dongle across multiple applications, run the mux daemon: - -```sh -# Python -cd mux/python -just run # start the mux daemon -just verbose # start with verbose logging -just run --tcp 5741 --port /dev/ttyACM0 # with explicit options - -# Rust -cd mux/rust -just run # start the mux daemon -just verbose # start with verbose logging -just run -- --tcp 5741 --port /dev/ttyACM0 # with explicit options -``` - -The mux owns the USB serial port exclusively and exposes a Unix socket -(and optional TCP) speaking the same protocol. Client libraries connect -through it transparently. - -| Script | Description | -|--------|-------------| -| [`simple_rx.py`](examples/simple_rx.py) | Configure radio, receive and print packets | -| [`simple_tx.py`](examples/simple_tx.py) | Transmit a single packet | -| [`ping_pong.py`](examples/ping_pong.py) | Two-dongle demo (`--role tx` / `--role rx`) | -| [`all_commands.py`](examples/all_commands.py) | Exercise all 9 commands | -| [`lora_bridge.py`](examples/lora_bridge.py) | Two-way LoRa bridge over TCP (works over Tailscale, WireGuard, etc.) | -| [`meshcore/`](examples/meshcore/) | Full MeshCore packet decoder, AI bot, telemetry monitor | - -## Device Permissions (Linux) - -Your user needs read/write access to the serial device (`/dev/ttyACM*` or -`/dev/ttyUSB*`). The cleanest fix is a udev rule: - -```sh -# /etc/udev/rules.d/99-donglora.rules -SUBSYSTEM=="tty", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5741", MODE="0666" -``` - -Then reload: - -```sh -sudo udevadm control --reload-rules && sudo udevadm trigger -``` - -Without this, you'll get permission errors when opening the serial port. - -## Building - -Requires [just](https://github.com/casey/just) and [mise](https://mise.jdx.dev/). -Everything else is installed automatically on first run. - -```sh -cd firmware -just build heltec_v4 # build a specific board -just build-all # build firmware for all boards -just flash heltec_v4 # build + flash -just check-all # compile-check only -just clippy heltec_v4 # lint -just test # host-side protocol unit tests -``` - -## Roadmap - -### Now: Solidify the Foundation - -- [ ] Test on more boards (Heltec V2, T-Beam Supreme, T-Echo, RAK 3112) -- [ ] Python client library on PyPI — the existing library, properly packaged -- [ ] CI for all boards — automated build/clippy/size-check on every commit -- [ ] Protocol documentation polish - -### Next: Cross-Language Client Libraries - -The protocol is 8 commands over COBS-framed LE. Any language that can -open a serial port can speak DongLoRa. - -- [x] **Rust** crate — async, tokio-native, zero-copy COBS -- [ ] **Go** module — goroutine-friendly, cross-platform -- [ ] **C** library — `libdonglora` for FFI from any language -- [ ] **Ruby** gem — simple protocol, weekend gem, new community -- [ ] **Python** on PyPI — type hints and async support - -### Then: Infrastructure and Tooling - -- [x] Cross-platform mux daemon rewritten in Rust (single static binary, no Python required) -- [ ] `donglora-ctl` CLI — ping, configure, receive, transmit, scan frequencies. Think `ip link` for LoRa. -- [ ] Protocol versioning — version handshake so client libraries can detect firmware capabilities -- [ ] Firmware OTA over USB — field-upgradeable without a flash tool - -### Future: Things That Would Be Amazing - -All host-driven, firmware stays dumb. Every item passes the pipe test. - -- [ ] **Spectrum analyzer** — host sweeps frequencies, radio reports RSSI at each step. Instant RF site survey tool. -- [ ] **Multi-dongle coordination** — one host, multiple dongles on different frequencies. Frequency-hopping, parallel monitoring, diversity reception — all host-driven. -- [ ] **LoRa packet capture** — PCAP-NG with a LoRa link type. Wireshark dissectors. Record, replay, analyze LoRa traffic with standard tools. -- [ ] **Time-synchronized RX** — "start RX at T, stop at T+N" for precise duty-cycle control and TDMA-style protocols. Firmware just follows the schedule. -- [ ] **Raw IQ streaming** — SDR-lite for LoRa. Demodulation and signal analysis on the host. -- [ ] **Mesh protocol test harness** — host-side framework for testing any mesh protocol over real radios. Inject packets, measure latency, simulate topology. The dongle is just the radio — test logic lives entirely on the host. - -*Have an idea? Open an issue. The protocol is stable and the firmware is -intentionally boring — the interesting stuff happens on the host.* - -
-Board Roadmap - -### Next Up - -| Board | MCU | Radio | -|-------|-----|-------| -| Heltec V2 | ESP32 | SX1276 | -| Heltec Wireless Tracker | ESP32-S3 | SX1262 | -| Heltec Vision Master E213/E290 | ESP32-S3 | SX1262 | -| LilyGo T-Beam (SX1262) | ESP32 | SX1262 | -| LilyGo T-Beam Supreme | ESP32-S3 | SX1262 | -| LilyGo T-Echo | nRF52840 | SX1262 | -| LilyGo T3 S3 | ESP32-S3 | SX1262 | -| Seeed Studio Xiao nRF52 WIO | nRF52840 | SX1262 | - -### Future - -| Board | MCU | Radio | -|-------|-----|-------| -| Elecrow ThinkNode M1-M6 | various | various | -| Heltec Wireless Paper / MeshPocket / T114 | various | SX1262 | -| LilyGo T-Deck / T-Echo Lite / LoRa32 | various | various | -| RAK WisBlock 3112 / WisMesh variants | various | SX1262 | -| RPI Pico 2040 + WaveShare SX1262 | RP2040 | SX1262 | -| Seeed Studio SenseCAP / Xiao variants | various | SX1262 | - -
- -## Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md). - -## License - -[MIT](LICENSE) — Stephen Waits +This repo is archived and will not receive further updates. diff --git a/clients/python/.gitignore b/clients/python/.gitignore deleted file mode 100644 index 21d0b898..00000000 --- a/clients/python/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.venv/ diff --git a/clients/python/README.md b/clients/python/README.md deleted file mode 100644 index 77756c27..00000000 --- a/clients/python/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# DongLoRa Python Client - -Python client library for talking to a DongLoRa device — either directly -over USB or through the [mux daemon](../../mux/). - -## What's in Here - -- `donglora/` — the library: device discovery, COBS framing, protocol - encoding/decoding, mux client support -- Installable as `donglora` via pip/uv - -## Usage - -```python -import donglora as dl - -ser = dl.connect() -dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG) -dl.send(ser, "StartRx") -while True: - pkt = dl.recv(ser) - if pkt: - print(pkt["rssi"], pkt["payload"].hex()) -``` - -## Dependencies - -- `cobs` — COBS framing -- `pyserial` — USB serial communication - -Optional extras: `meshcore` (crypto), `orac` (AI bot). diff --git a/clients/python/donglora/__init__.py b/clients/python/donglora/__init__.py deleted file mode 100644 index bb58efad..00000000 --- a/clients/python/donglora/__init__.py +++ /dev/null @@ -1,381 +0,0 @@ -"""DongLoRa host library — connect, configure, send/receive LoRa packets. - -Implements the DongLoRa USB protocol (COBS-framed fixed-size LE). -See firmware/PROTOCOL.md for the full specification. -""" - -import collections -import glob -import os -import socket -import struct -import subprocess - -import serial -import time -from cobs import cobs - -# ── USB device discovery ────────────────────────────────────────── - -USB_VID_PID = "1209:5741" - -# Known USB-UART bridge VID:PIDs found on some board revisions. -BRIDGE_VID_PIDS = {"10c4:ea60", "1a86:55d4", "1a86:7523", "0403:6001"} - - -def find_port() -> str | None: - """Find the DongLoRa serial port by USB VID:PID. - - Checks for native USB CDC-ACM first (1209:5741), then falls back to - known USB-UART bridge chips found on some board revisions. - """ - bridge_match = None - for path in sorted(glob.glob("/dev/ttyACM*")) + sorted(glob.glob("/dev/ttyUSB*")): - try: - result = subprocess.run( - ["udevadm", "info", "--query=property", f"--name={path}"], - capture_output=True, text=True, timeout=2, - ) - props = dict( - line.split("=", 1) for line in result.stdout.splitlines() if "=" in line - ) - vid = props.get("ID_VENDOR_ID", "").lower() - pid = props.get("ID_MODEL_ID", "").lower() - vid_pid = f"{vid}:{pid}" - if vid_pid == USB_VID_PID: - return path - if bridge_match is None and vid_pid in BRIDGE_VID_PIDS: - bridge_match = path - except Exception: - continue - if bridge_match is not None: - return bridge_match - ports = sorted(glob.glob("/dev/ttyACM*")) + sorted(glob.glob("/dev/ttyUSB*")) - return ports[0] if ports else None - - -def wait_for_device() -> str: - """Block until a DongLoRa device appears.""" - print("Waiting for DongLoRa...", end="", flush=True) - while True: - port = find_port() - if port: - print(f" found {port}") - time.sleep(0.3) - return port - print(".", end="", flush=True) - time.sleep(0.5) - - -# ── COBS framing ───────────────────────────────────────────────── - -def cobs_encode(data: bytes) -> bytes: - return cobs.encode(data) + b"\x00" - - -def read_frame(ser: serial.Serial) -> bytes | None: - """Read one COBS frame. Returns decoded bytes or None on timeout.""" - buf = b"" - while True: - b = ser.read(1) - if not b: - return None - if b == b"\x00": - break - buf += b - if not buf: - return None - try: - return cobs.decode(buf) - except cobs.DecodeError: - return None - - -# ── Fixed-size LE encoding ──────────────────────────────────────── - -def encode_config(cfg: dict) -> bytes: - """Encode a RadioConfig to 13 fixed-size LE bytes.""" - return struct.pack(" bytes: - """Encode a command to fixed-size LE bytes.""" - tags = { - "Ping": 0, "GetConfig": 1, "SetConfig": 2, "StartRx": 3, - "StopRx": 4, "Transmit": 5, "DisplayOn": 6, "DisplayOff": 7, - "GetMac": 8, - } - out = bytes([tags[cmd]]) - if cmd == "SetConfig": - out += encode_config(kwargs["config"]) - elif cmd == "Transmit": - config = kwargs.get("config") - if config is None: - out += b"\x00" - else: - out += b"\x01" + encode_config(config) - payload = kwargs["payload"] - out += struct.pack(" dict: - """Decode a fixed-size LE response.""" - if not data: - return {"type": "Empty"} - tag = data[0] - rest = data[1:] - if tag == 0: - return {"type": "Pong"} - elif tag == 1: - if len(rest) >= 13: - freq_hz, bw, sf, cr, sync_word, pwr, preamble_len, cad = struct.unpack_from("= 6: - mac = ":".join(f"{b:02X}" for b in rest[:6]) - return {"type": "MacAddress", "mac": mac} - return {"type": "MacAddress", "raw": rest.hex()} - else: - return {"type": f"Unknown({tag})"} - - -# ── High-level helpers ─────────────────────────────────────────── - -# Buffer for RxPacket frames encountered while waiting for solicited responses. -# These are real received LoRa data — don't discard them. -_rx_queue: collections.deque = collections.deque(maxlen=256) - - -def send(ser: serial.Serial, cmd: str, **kwargs) -> dict: - """Send a command and return the solicited response. - - Any unsolicited RxPacket frames encountered while waiting are buffered - in the module-level receive queue (retrievable via recv/drain_rx). - """ - ser.write(cobs_encode(encode_command(cmd, **kwargs))) - ser.flush() - for _ in range(50): # safety bound — don't loop forever - data = read_frame(ser) - if data is None: - return {"type": "Timeout"} - resp = decode_response(data) - if resp["type"] == "RxPacket": - _rx_queue.append(resp) - continue - return resp - return {"type": "Timeout"} - - -def recv(ser: serial.Serial) -> dict | None: - """Return the next RxPacket from the buffer or the wire. - - Returns None on timeout (no packet available). - """ - if _rx_queue: - return _rx_queue.popleft() - data = read_frame(ser) - if data is None: - return None - resp = decode_response(data) - if resp["type"] == "RxPacket": - return resp - return None - - -def drain_rx(ser: serial.Serial) -> list[dict]: - """Drain all buffered and pending RxPacket frames.""" - packets = list(_rx_queue) - _rx_queue.clear() - old_timeout = ser.timeout - ser.timeout = 0.01 - while True: - data = read_frame(ser) - if data is None: - break - resp = decode_response(data) - if resp["type"] == "RxPacket": - packets.append(resp) - ser.timeout = old_timeout - return packets - - -# ── Mux client support ────────────────────────────────────────── - -class MuxConnection: - """Drop-in replacement for serial.Serial that talks to the mux daemon.""" - - def __init__(self, sock: socket.socket, timeout: float = 2.0): - self._sock = sock - self._timeout = timeout - self._sock.settimeout(timeout) - - @property - def timeout(self) -> float: - return self._timeout - - @timeout.setter - def timeout(self, value: float) -> None: - self._timeout = value - self._sock.settimeout(value) - - def read(self, n: int = 1) -> bytes: - try: - data = self._sock.recv(n) - if not data: - raise ConnectionError("mux disconnected") - return data - except socket.timeout: - return b"" - - def write(self, data: bytes) -> int: - self._sock.sendall(data) - return len(data) - - def flush(self) -> None: - pass - - def reset_input_buffer(self) -> None: - pass - - def close(self) -> None: - self._sock.close() - - -def _mux_socket_path() -> str | None: - """Resolve the mux socket path, or None if no socket exists.""" - env = os.environ.get("DONGLORA_MUX") - if env: - return env if os.path.exists(env) else None - xdg = os.environ.get("XDG_RUNTIME_DIR") - if xdg: - p = os.path.join(xdg, "donglora", "mux.sock") - if os.path.exists(p): - return p - p = "/tmp/donglora-mux.sock" - return p if os.path.exists(p) else None - - -def mux_connect(path: str | None = None, timeout: float = 2.0) -> MuxConnection: - """Connect to the DongLoRa mux daemon via Unix socket.""" - if path is None: - path = _mux_socket_path() - if path is None: - raise FileNotFoundError("No mux socket found") - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(path) - return MuxConnection(sock, timeout) - - -def mux_tcp_connect(host: str, port: int, timeout: float = 2.0) -> MuxConnection: - """Connect to the DongLoRa mux daemon via TCP.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((host, port)) - return MuxConnection(sock, timeout) - - -_mux_mode: tuple | None = None # ("unix",) or ("tcp", host, port) once sticky - - -def _reconnect_mux(timeout: float) -> MuxConnection: - """Retry the mux connection until it comes back.""" - assert _mux_mode is not None - print("Waiting for mux...", end="", flush=True) - while True: - try: - if _mux_mode[0] == "unix": - return mux_connect(timeout=timeout) - else: - return mux_tcp_connect(_mux_mode[1], _mux_mode[2], timeout) - except (FileNotFoundError, ConnectionRefusedError, OSError): - print(".", end="", flush=True) - time.sleep(1) - - -def connect(port: str | None = None, timeout: float = 2.0) -> serial.Serial | MuxConnection: - """Open a connection to a DongLoRa device. - - Priority: DONGLORA_MUX_TCP env var → Unix socket mux → direct USB serial. - Once connected via mux, subsequent calls only retry mux (never steals USB). - """ - global _mux_mode - - # Sticky mux — if we connected via mux before, only retry mux - if _mux_mode is not None and port is None: - return _reconnect_mux(timeout) - - if port is None: - # Try local Unix socket mux - sock_path = _mux_socket_path() - if sock_path is not None: - try: - conn = mux_connect(sock_path, timeout=timeout) - _mux_mode = ("unix",) - return conn - except (ConnectionRefusedError, OSError): - pass # stale socket — fall through - - # Try TCP mux (e.g. remote access over Tailscale) - tcp = os.environ.get("DONGLORA_MUX_TCP") - if tcp: - try: - host, _, p = tcp.rpartition(":") - host = host or "localhost" - conn = mux_tcp_connect(host, int(p), timeout) - _mux_mode = ("tcp", host, int(p)) - return conn - except (ConnectionRefusedError, OSError): - pass # mux not reachable — fall through - - # No mux available — direct USB serial - if port is None: - port = wait_for_device() - ser = serial.Serial(port, baudrate=115200, timeout=timeout) - ser.reset_input_buffer() - return ser - - -# Sentinel: set TX power to the board's maximum -TX_POWER_MAX = -128 # i8::MIN on the wire - -# Sentinel: use firmware default preamble length (16 symbols) -PREAMBLE_DEFAULT = 0 - -# Default radio config: 915 MHz, 125 kHz BW, SF7, CR 4/5, max power, default preamble -DEFAULT_CONFIG = { - "freq_hz": 915_000_000, - "bw": 7, # 125 kHz - "sf": 7, - "cr": 5, # CR 4/5 - "sync_word": 0x1424, - "tx_power_dbm": TX_POWER_MAX, - "preamble_len": PREAMBLE_DEFAULT, -} diff --git a/clients/python/mise.toml b/clients/python/mise.toml deleted file mode 100644 index a245b1c0..00000000 --- a/clients/python/mise.toml +++ /dev/null @@ -1,4 +0,0 @@ -[tools] -python = "3.14" -uv = "latest" -just = "latest" diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml deleted file mode 100644 index 488621be..00000000 --- a/clients/python/pyproject.toml +++ /dev/null @@ -1,25 +0,0 @@ -[project] -name = "donglora" -version = "0.1.0" -description = "DongLoRa host library — talk LoRa from Python over USB" -license = "MIT" -requires-python = ">=3.10" -authors = [{ name = "Stephen Waits" }] -keywords = ["lora", "radio", "usb", "embedded"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Topic :: Communications :: Ham Radio", - "Topic :: System :: Hardware", -] -dependencies = [ - "cobs", - "pyserial", -] - -[project.optional-dependencies] -meshcore = ["pycryptodome", "pynacl"] -orac = ["pycryptodome", "pynacl", "anthropic"] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/clients/python/uv.lock b/clients/python/uv.lock deleted file mode 100644 index 6e68fded..00000000 --- a/clients/python/uv.lock +++ /dev/null @@ -1,606 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anthropic" -version = "0.88.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "docstring-parser" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/68/565f13059c0a6a6fd5f96f306f2a0fb478a0e1174ec18a4df16b5fac9379/anthropic-0.88.0.tar.gz", hash = "sha256:f4c7f6863d08c869913516f08d658fe53caaf8bcc4fbea3218df343d2a876c58", size = 596654, upload-time = "2026-04-01T19:59:05.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ac/68f646998160c9f2e6f9353a31dd87292ef02b915b455aaf70a52a059a75/anthropic-0.88.0-py3-none-any.whl", hash = "sha256:71898b32332bc75d9739bc10095288d40a29605da6d00da2fe832b1aa036552f", size = 478338, upload-time = "2026-04-01T19:59:03.832Z" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "cobs" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/ef/ea149311227a4fc3160cc885fce06da7c7d76782a308ef070b8065c69953/cobs-1.2.2.tar.gz", hash = "sha256:dbdd5e32111d72786f83d0c269215dcd6ac629b1ac1962c6878221f3b2ca98da", size = 14582, upload-time = "2025-07-20T01:08:35.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/e0/9574590639a624a9a84c89a569f36343523a306fff2ffddc9468a2b9b05d/cobs-1.2.2-cp312-cp312-win32.whl", hash = "sha256:3afe0cec5d91164f84dcf35b471949aa36285664d21ee984350e39d485707d11", size = 26400, upload-time = "2025-07-20T01:08:30.288Z" }, - { url = "https://files.pythonhosted.org/packages/d3/4f/5418d9decdc4cf27a814fc189bc4fa44d216d37b871d633654f37ed331f8/cobs-1.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:4da6048f6e16cbd6c8ec5e95e8520e226248ffb8d31e9d6fc5e0888b3b65c54b", size = 27776, upload-time = "2025-07-20T01:08:31.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/df/7f07559954d53d09680aa4f5c72db999e7643253e2ca4e431023fe5c1c40/cobs-1.2.2-cp313-cp313-win32.whl", hash = "sha256:a8fdf6ca70247e7271caea6f2f6f9c0343f5e872e2fd47e21f71831fa9ababa8", size = 26401, upload-time = "2025-07-20T01:08:32.508Z" }, - { url = "https://files.pythonhosted.org/packages/0c/00/5b063105ff567bb7fb72c3cd3e96116a3889381fa2c0e7747497391dbecc/cobs-1.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe3f7194d5fbbc02e8fb03d26ad626a33fa0d420b50d66ed60ac4bf516322d8e", size = 27774, upload-time = "2025-07-20T01:08:33.692Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/de1e889c26fcfc9ae6852b8f99e744e6690a3baae99f63e2b9fffdd21710/cobs-1.2.2-cp314-cp314-win32.whl", hash = "sha256:bcf08cb0b68c1824a87cfa89e0684d5a3eaee9b9e08b02ce4a54d5589536f01c", size = 26779, upload-time = "2026-03-09T02:18:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/b6/64/390e226843cc83066acc60257e0e71ebdbce6dd915ec4f3d34101b9ac31b/cobs-1.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:1f27b7a553d809487287e4ed88540b2c30ed9c9bb596173bfcf549e705af7bf0", size = 28205, upload-time = "2026-03-09T02:18:20.193Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - -[[package]] -name = "docstring-parser" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, -] - -[[package]] -name = "donglora" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "cobs" }, - { name = "pyserial" }, -] - -[package.optional-dependencies] -meshcore = [ - { name = "pycryptodome" }, - { name = "pynacl" }, -] -orac = [ - { name = "anthropic" }, - { name = "pycryptodome" }, - { name = "pynacl" }, -] - -[package.metadata] -requires-dist = [ - { name = "anthropic", marker = "extra == 'orac'" }, - { name = "cobs" }, - { name = "pycryptodome", marker = "extra == 'meshcore'" }, - { name = "pycryptodome", marker = "extra == 'orac'" }, - { name = "pynacl", marker = "extra == 'meshcore'" }, - { name = "pynacl", marker = "extra == 'orac'" }, - { name = "pyserial" }, -] -provides-extras = ["meshcore", "orac"] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "jiter" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, - { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, - { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, - { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, - { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, - { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, - { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, - { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, - { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, - { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, - { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, - { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, - { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, - { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, - { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, - { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, - { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, - { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, - { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, - { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, - { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, - { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, - { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, - { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, - { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, - { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pycryptodome" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, - { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, - { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, - { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, - { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, - { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, - { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, - { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, - { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, - { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, - { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pynacl" -version = "1.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, - { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, - { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, - { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, - { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, - { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, - { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, - { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, - { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, - { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, - { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, - { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, - { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, -] - -[[package]] -name = "pyserial" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] diff --git a/clients/rust/.gitignore b/clients/rust/.gitignore deleted file mode 100644 index ea8c4bf7..00000000 --- a/clients/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md deleted file mode 100644 index cef926af..00000000 --- a/clients/rust/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Changelog - -## 0.1.0 — 2026-04-06 - -Initial release. - -### Features - -- High-level `Client` with send/recv and command helpers (ping, set_config, - start_rx, stop_rx, transmit, get_config, get_mac, display_on, display_off) -- COBS wire framing via `ucobs` (matches firmware implementation) -- Auto-detection connection: TCP mux, Unix socket mux, direct USB serial -- Bounded RX packet buffering (256 packets, FIFO eviction) -- `FrameReader` accumulator for streaming byte sources -- USB device discovery by VID:PID with blocking wait - -### Resilience - -- Cross-platform timeout handling: `TimedOut` (Windows) and `WouldBlock` - (Linux/macOS) both treated as clean timeouts in `read_frame` -- `EINTR`/`Interrupted` signals retried automatically in `read_frame` -- `drain_rx` always restores the original timeout, even on I/O errors -- TCP mux connections use `connect_timeout` (bounded by caller's timeout) -- Mux sockets set both read and write timeouts -- `SerialTransport` tracks timeout accurately for save/restore -- Unexpected unsolicited frames logged via `tracing::warn` instead of silently - discarded diff --git a/clients/rust/CLAUDE_TODO.md b/clients/rust/CLAUDE_TODO.md deleted file mode 100644 index 7fa5ebc3..00000000 --- a/clients/rust/CLAUDE_TODO.md +++ /dev/null @@ -1,86 +0,0 @@ -# Add non-blocking `connect` variant - -## Problem - -`connect(None, timeout)` in `src/connect.rs` has a three-step fallback: - -1. `DONGLORA_MUX_TCP` env var -> TCP mux -2. Unix socket mux (if socket file exists) -3. Direct USB serial via `find_port().unwrap_or_else(wait_for_device)` - -Step 3 calls `wait_for_device()` which polls `find_port()` every 500ms -**indefinitely** until a USB device appears. This makes `connect(None, ...)` -unsuitable for callers that need to retry with backoff or report disconnected -status (e.g. donglora-bridge's reconnect loop). - -Today, donglora-bridge works around this by manually reimplementing the -fallback chain: - -```rust -if let Ok(c) = connect_mux_auto(timeout) { - (c, "mux".to_string()) -} else { - let port_path = find_port() - .ok_or_else(|| anyhow!("no mux or USB dongle found"))?; - let c = connect(Some(&port_path), timeout)?; - (c, shorten_path(&port_path)) -} -``` - -This duplicates logic from `connect()` (steps 1-2 via `connect_mux_auto`, step -3 minus the blocking wait) and won't pick up any future changes to the -`connect()` fallback order. - -## Proposed solution - -Add a `try_connect` (or similar) that runs the same mux -> serial fallback as -`connect(None, timeout)` but replaces `wait_for_device()` with a single -`find_port()` call, returning an error immediately if no device is found: - -```rust -/// Like [`connect`] but returns an error instead of blocking when no USB -/// device is present. Suitable for callers with their own retry logic. -pub fn try_connect(timeout: Duration) -> Result> { - // Steps 1-2: identical to connect() — try TCP mux, then Unix socket mux - - // Step 3: non-blocking USB scan - let port_path = discovery::find_port() - .ok_or_else(|| anyhow!("no DongLoRa device found (no mux, no USB device)"))?; - let transport = SerialTransport::open(&port_path, timeout)?; - Ok(Client::new(AnyTransport::Serial(transport))) -} -``` - -This would let donglora-bridge collapse its connection code to: - -```rust -let (mut client, device) = if let Some(port) = port { - let c = connect(Some(port), timeout)?; - (c, shorten_path(port)) -} else { - let c = try_connect(timeout)?; - let device = match c.transport() { - AnyTransport::Mux(_) => "mux".to_string(), - AnyTransport::Serial(_) => find_port() - .map(|p| shorten_path(&p)) - .unwrap_or_else(|| "serial".to_string()), - }; - (c, device) -}; -``` - -### Device name from transport - -The bridge also needs to know *what* it connected to for display purposes. Two -options: - -1. Match on `AnyTransport` variant after connecting (shown above) -- works but - requires a redundant `find_port()` call for the display name since - `SerialTransport` has private fields. -2. Have `try_connect` return the port path alongside the client, e.g. - `-> Result<(Client, ConnectionInfo)>` where `ConnectionInfo` - is an enum like `Mux { socket_path }` | `Serial { port_path }`. Cleaner - but a larger API change. - -Either works. Option 2 would also benefit `connect()` callers who want to log -what they connected to. diff --git a/clients/rust/Cargo.lock b/clients/rust/Cargo.lock deleted file mode 100644 index 039bb419..00000000 --- a/clients/rust/Cargo.lock +++ /dev/null @@ -1,326 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "donglora-client" -version = "0.1.0" -dependencies = [ - "anyhow", - "serialport", - "tracing", - "ucobs", -] - -[[package]] -name = "io-kit-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" -dependencies = [ - "core-foundation-sys", - "mach2", -] - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[package]] -name = "libudev" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" -dependencies = [ - "libc", - "libudev-sys", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "mach2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serialport" -version = "4.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "core-foundation", - "core-foundation-sys", - "io-kit-sys", - "libudev", - "mach2", - "nix", - "scopeguard", - "unescaper", - "windows-sys", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "ucobs" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fbab511bafd13c109aff41f67d4c4426bf919a6670647e69f0d2af95ac01d" - -[[package]] -name = "unescaper" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/clients/rust/Cargo.toml b/clients/rust/Cargo.toml deleted file mode 100644 index ee6579ce..00000000 --- a/clients/rust/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "donglora-client" -version = "0.1.0" -edition = "2024" -authors = ["Stephen Waits "] -license = "MIT" -description = "DongLoRa host library — connect, configure, send/receive LoRa packets" -repository = "https://github.com/swaits/donglora" -keywords = ["lora", "radio", "usb", "serial"] -categories = ["hardware-support"] - -[dependencies] -anyhow = "1" -ucobs = "0.3" -serialport = "4" -tracing = "0.1" - -[lints.clippy] -unwrap_used = "deny" -expect_used = "deny" -panic = "deny" diff --git a/clients/rust/LICENSE b/clients/rust/LICENSE deleted file mode 100644 index a4ad6f14..00000000 --- a/clients/rust/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/clients/rust/README.md b/clients/rust/README.md deleted file mode 100644 index d84defb5..00000000 --- a/clients/rust/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# DongLoRa Rust Client - -Rust client library for talking to a DongLoRa device — either directly -over USB or through the [mux daemon](../../mux/). - -## What's in Here - -- `src/protocol.rs` — wire protocol types (`RadioConfig`, `Command`, `Response`, `ErrorCode`) -- `src/codec.rs` — COBS framing, frame accumulator -- `src/discovery.rs` — USB VID:PID device discovery -- `src/transport.rs` — serial and mux socket transports -- `src/client.rs` — high-level `Client` with send/recv -- `src/connect.rs` — auto-detection (mux socket, TCP, direct USB) - -## Usage - -```rust -use donglora_client::*; - -let mut client = connect_default()?; -client.ping()?; -client.set_config(RadioConfig::default())?; -client.start_rx()?; - -loop { - if let Some(Response::RxPacket { rssi, snr, payload }) = client.recv()? { - println!("RX rssi={rssi} snr={snr} len={}", payload.len()); - } -} -``` - -## Dependencies - -- `ucobs` — COBS framing (same implementation as the firmware) -- `serialport` — USB serial communication -- `anyhow` — error handling -- `tracing` — logging diff --git a/clients/rust/justfile b/clients/rust/justfile deleted file mode 100644 index e0e33239..00000000 --- a/clients/rust/justfile +++ /dev/null @@ -1,11 +0,0 @@ -set shell := ["bash", "-c"] - -default: check - -# Run clippy and tests -check: - cargo clippy -- -D warnings && cargo test - -# Build release -build: - cargo build --release diff --git a/clients/rust/src/client.rs b/clients/rust/src/client.rs deleted file mode 100644 index c9142b95..00000000 --- a/clients/rust/src/client.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! High-level DongLoRa client. -//! -//! The [`Client`] wraps a transport and provides ergonomic send/recv methods -//! that handle the command/response discipline (buffering unsolicited RxPackets -//! while waiting for solicited responses). - -use std::collections::VecDeque; - -use crate::codec::{encode_frame, read_frame}; -use crate::protocol::{Command, ErrorCode, RadioConfig, Response}; -use crate::transport::Transport; - -/// Maximum number of buffered RxPackets before oldest are dropped. -const RX_BUFFER_CAP: usize = 256; - -/// Maximum frames to read while waiting for a solicited response before giving up. -const MAX_UNSOLICITED_BEFORE_TIMEOUT: usize = 50; - -/// High-level DongLoRa client, generic over transport. -/// -/// Works with any [`Transport`]: direct USB serial, Unix socket mux, or TCP mux. -pub struct Client { - transport: T, - rx_buffer: VecDeque, -} - -impl Client { - /// Create a new client wrapping the given transport. - pub fn new(transport: T) -> Self { - Self { - transport, - rx_buffer: VecDeque::with_capacity(64), - } - } - - /// Send a command and wait for the solicited response. - /// - /// Any unsolicited `RxPacket` frames encountered while waiting are buffered - /// and retrievable via [`recv`](Self::recv) or [`drain_rx`](Self::drain_rx). - pub fn send(&mut self, cmd: Command) -> anyhow::Result { - let frame = encode_frame(&cmd.to_bytes()); - std::io::Write::write_all(&mut self.transport, &frame)?; - std::io::Write::flush(&mut self.transport)?; - - for _ in 0..MAX_UNSOLICITED_BEFORE_TIMEOUT { - let data = read_frame(&mut self.transport)? - .ok_or_else(|| anyhow::anyhow!("timeout waiting for response"))?; - let resp = Response::from_bytes(&data) - .ok_or_else(|| { - let hex: Vec = data.iter().map(|b| format!("{b:02x}")).collect(); - anyhow::anyhow!("malformed response ({} bytes): [{}]", data.len(), hex.join(" ")) - })?; - if resp.is_rx_packet() { - self.buffer_rx(resp); - continue; - } - return Ok(resp); - } - anyhow::bail!("no solicited response after {MAX_UNSOLICITED_BEFORE_TIMEOUT} frames") - } - - /// Return the next RxPacket from the buffer or the wire. - /// - /// Returns `Ok(None)` on timeout (no packet available). - pub fn recv(&mut self) -> anyhow::Result> { - if let Some(pkt) = self.rx_buffer.pop_front() { - return Ok(Some(pkt)); - } - let Some(data) = read_frame(&mut self.transport)? else { - return Ok(None); - }; - let resp = Response::from_bytes(&data) - .ok_or_else(|| anyhow::anyhow!("malformed response ({} bytes)", data.len()))?; - if resp.is_rx_packet() { - Ok(Some(resp)) - } else { - tracing::warn!("recv: discarding unexpected unsolicited response: {resp:?}"); - Ok(None) - } - } - - /// Drain all buffered and pending RxPacket frames. - /// - /// Temporarily reduces the read timeout to quickly drain any frames still - /// in flight, then restores the original timeout. - pub fn drain_rx(&mut self) -> anyhow::Result> { - let mut packets: Vec = self.rx_buffer.drain(..).collect(); - - let old_timeout = self.transport.timeout(); - self.transport - .set_timeout(std::time::Duration::from_millis(10))?; - - let result: anyhow::Result<()> = (|| { - loop { - let Some(data) = read_frame(&mut self.transport)? else { - return Ok(()); - }; - if let Some(resp) = Response::from_bytes(&data) - && resp.is_rx_packet() - { - packets.push(resp); - } - } - })(); - - // Always restore timeout, even if the loop errored - self.transport.set_timeout(old_timeout)?; - result?; - Ok(packets) - } - - /// Send a Ping and verify the Pong response. - pub fn ping(&mut self) -> anyhow::Result<()> { - match self.send(Command::Ping)? { - Response::Pong => Ok(()), - other => anyhow::bail!("unexpected response to Ping: {other:?}"), - } - } - - /// Set the radio configuration. - pub fn set_config(&mut self, config: RadioConfig) -> anyhow::Result<()> { - match self.send(Command::SetConfig(config))? { - Response::Ok => Ok(()), - Response::Error(code) => anyhow::bail!("SetConfig failed: {code}"), - other => anyhow::bail!("unexpected response to SetConfig: {other:?}"), - } - } - - /// Start receiving LoRa packets. - pub fn start_rx(&mut self) -> anyhow::Result<()> { - match self.send(Command::StartRx)? { - Response::Ok => Ok(()), - Response::Error(code) => anyhow::bail!("StartRx failed: {code}"), - other => anyhow::bail!("unexpected response to StartRx: {other:?}"), - } - } - - /// Stop receiving LoRa packets. - pub fn stop_rx(&mut self) -> anyhow::Result<()> { - match self.send(Command::StopRx)? { - Response::Ok => Ok(()), - Response::Error(code) => anyhow::bail!("StopRx failed: {code}"), - other => anyhow::bail!("unexpected response to StopRx: {other:?}"), - } - } - - /// Transmit a LoRa packet. - pub fn transmit( - &mut self, - payload: &[u8], - config: Option, - ) -> anyhow::Result<()> { - let cmd = Command::Transmit { - config, - payload: payload.to_vec(), - }; - match self.send(cmd)? { - Response::TxDone => Ok(()), - Response::Error(ErrorCode::TxTimeout) => anyhow::bail!("transmit timed out"), - Response::Error(code) => anyhow::bail!("Transmit failed: {code}"), - other => anyhow::bail!("unexpected response to Transmit: {other:?}"), - } - } - - /// Get the board's MAC address. - pub fn get_mac(&mut self) -> anyhow::Result<[u8; 6]> { - match self.send(Command::GetMac)? { - Response::MacAddress(mac) => Ok(mac), - Response::Error(code) => anyhow::bail!("GetMac failed: {code}"), - other => anyhow::bail!("unexpected response to GetMac: {other:?}"), - } - } - - /// Get the current radio configuration from the device. - pub fn get_config(&mut self) -> anyhow::Result { - match self.send(Command::GetConfig)? { - Response::Config(cfg) => Ok(cfg), - Response::Error(code) => anyhow::bail!("GetConfig failed: {code}"), - other => anyhow::bail!("unexpected response to GetConfig: {other:?}"), - } - } - - /// Turn on the display (if present). - pub fn display_on(&mut self) -> anyhow::Result<()> { - match self.send(Command::DisplayOn)? { - Response::Ok => Ok(()), - Response::Error(code) => anyhow::bail!("DisplayOn failed: {code}"), - other => anyhow::bail!("unexpected response to DisplayOn: {other:?}"), - } - } - - /// Turn off the display (if present). - pub fn display_off(&mut self) -> anyhow::Result<()> { - match self.send(Command::DisplayOff)? { - Response::Ok => Ok(()), - Response::Error(code) => anyhow::bail!("DisplayOff failed: {code}"), - other => anyhow::bail!("unexpected response to DisplayOff: {other:?}"), - } - } - - /// Consume the client and return the inner transport. - pub fn into_inner(self) -> T { - self.transport - } - - /// Get a reference to the inner transport. - pub fn transport(&self) -> &T { - &self.transport - } - - /// Get a mutable reference to the inner transport. - pub fn transport_mut(&mut self) -> &mut T { - &mut self.transport - } - - fn buffer_rx(&mut self, resp: Response) { - if self.rx_buffer.len() >= RX_BUFFER_CAP { - self.rx_buffer.pop_front(); // drop oldest - } - self.rx_buffer.push_back(resp); - } -} diff --git a/clients/rust/src/codec.rs b/clients/rust/src/codec.rs deleted file mode 100644 index fcdcfdea..00000000 --- a/clients/rust/src/codec.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! COBS framing: encode/decode frames and accumulate partial reads. -//! -//! Each frame is COBS-encoded and terminated with a `0x00` sentinel byte. -//! The sentinel never appears in the encoded data, so it unambiguously -//! marks frame boundaries. -//! -//! Uses the [`ucobs`] crate — the same COBS implementation as the firmware. - -use tracing::warn; - -/// COBS-encode data and append the `0x00` sentinel. -pub fn encode_frame(data: &[u8]) -> Vec { - let max_encoded = ucobs::max_encoded_len(data.len()); - let mut buf = vec![0u8; max_encoded]; - let n = match ucobs::encode(data, &mut buf) { - Some(n) => n, - None => return vec![0x00], // empty/failed encode → just sentinel - }; - buf.truncate(n); - buf.push(0x00); - buf -} - -/// COBS-decode a frame (without the sentinel). Returns `None` on decode error. -pub fn decode_frame(encoded: &[u8]) -> Option> { - if encoded.is_empty() { - return None; - } - let mut buf = vec![0u8; encoded.len()]; - let n = ucobs::decode(encoded, &mut buf)?; - buf.truncate(n); - Some(buf) -} - -/// Stateful COBS frame accumulator. -/// -/// Feed raw byte chunks (from serial reads, socket reads, etc.) and extract -/// complete decoded frames. Handles partial reads, multiple frames per chunk, -/// and empty inter-frame gaps gracefully. -/// -/// Used by both the sync client (`read_frame`) and the async mux to accumulate -/// data from serial and client socket streams. -pub struct FrameReader { - buf: Vec, -} - -impl FrameReader { - /// Create a new empty frame reader. - pub fn new() -> Self { - Self { - buf: Vec::with_capacity(512), - } - } - - /// Feed raw bytes and return all complete decoded frames. - /// - /// COBS decode errors are logged and the bad frame is skipped. - /// Empty inter-frame gaps (consecutive `0x00` bytes) are ignored. - pub fn feed(&mut self, data: &[u8]) -> Vec> { - self.buf.extend_from_slice(data); - - let mut frames = Vec::new(); - while let Some(sentinel_pos) = self.buf.iter().position(|&b| b == 0x00) { - let encoded = &self.buf[..sentinel_pos]; - if !encoded.is_empty() { - match decode_frame(encoded) { - Some(decoded) => frames.push(decoded), - None => warn!("bad COBS frame ({} bytes) — skipped", encoded.len()), - } - } - // Remove the consumed bytes including the sentinel - self.buf.drain(..=sentinel_pos); - } - frames - } - - /// Return the number of buffered bytes not yet forming a complete frame. - pub fn buffered(&self) -> usize { - self.buf.len() - } -} - -impl Default for FrameReader { - fn default() -> Self { - Self::new() - } -} - -/// Read one complete COBS frame from a blocking `Read` source. -/// -/// Reads byte-by-byte until a `0x00` sentinel is found, then COBS-decodes. -/// Returns `Ok(None)` on timeout (zero-length read from the underlying source). -/// Returns `Err` on I/O errors or COBS decode failures. -pub fn read_frame(reader: &mut dyn std::io::Read) -> anyhow::Result>> { - let mut buf = Vec::with_capacity(280); - let mut byte = [0u8; 1]; - - loop { - match reader.read(&mut byte) { - Ok(0) => { - // Timeout or EOF - return Ok(None); - } - Ok(_) => { - if byte[0] == 0x00 { - break; - } - buf.push(byte[0]); - } - Err(e) - if matches!( - e.kind(), - std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock - ) => - { - return Ok(None); - } - Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, - Err(e) => return Err(e.into()), - } - } - - if buf.is_empty() { - return Ok(None); - } - - decode_frame(&buf) - .map(Some) - .ok_or_else(|| anyhow::anyhow!("COBS decode error")) -} - -// ── Tests ────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encode_decode_roundtrip() { - let data = b"hello world"; - let encoded = encode_frame(data); - // Encoded should end with 0x00 sentinel - assert_eq!(encoded.last(), Some(&0x00)); - // Encoded should not contain 0x00 except the trailing sentinel - assert!(!encoded[..encoded.len() - 1].contains(&0x00)); - // Decode (without sentinel) - let decoded = decode_frame(&encoded[..encoded.len() - 1]); - assert_eq!(decoded.as_deref(), Some(data.as_slice())); - } - - #[test] - fn encode_decode_empty() { - // COBS encoding of empty data produces just the 0x00 sentinel. - // This is equivalent to an empty inter-frame gap and is correctly - // skipped by both FrameReader and read_frame. - let encoded = encode_frame(b""); - assert_eq!(*encoded.last().unwrap(), 0x00); - } - - #[test] - fn encode_decode_with_zeros() { - let data = &[0x00, 0x01, 0x00, 0x02, 0x00]; - let encoded = encode_frame(data); - let decoded = decode_frame(&encoded[..encoded.len() - 1]); - assert_eq!(decoded.as_deref(), Some(data.as_slice())); - } - - #[test] - fn decode_frame_invalid() { - assert!(decode_frame(&[]).is_none()); - } - - #[test] - fn frame_reader_single_frame() { - let mut reader = FrameReader::new(); - let data = b"test"; - let frame = encode_frame(data); - let frames = reader.feed(&frame); - assert_eq!(frames.len(), 1); - assert_eq!(frames[0], data); - } - - #[test] - fn frame_reader_multiple_frames_at_once() { - let mut reader = FrameReader::new(); - let f1 = encode_frame(b"one"); - let f2 = encode_frame(b"two"); - let mut combined = f1; - combined.extend_from_slice(&f2); - let frames = reader.feed(&combined); - assert_eq!(frames.len(), 2); - assert_eq!(frames[0], b"one"); - assert_eq!(frames[1], b"two"); - } - - #[test] - fn frame_reader_partial_then_complete() { - let mut reader = FrameReader::new(); - let frame = encode_frame(b"split"); - let mid = frame.len() / 2; - - // Feed first half — no complete frames yet - let frames = reader.feed(&frame[..mid]); - assert!(frames.is_empty()); - assert!(reader.buffered() > 0); - - // Feed second half — now we get the frame - let frames = reader.feed(&frame[mid..]); - assert_eq!(frames.len(), 1); - assert_eq!(frames[0], b"split"); - assert_eq!(reader.buffered(), 0); - } - - #[test] - fn frame_reader_empty_gaps() { - let mut reader = FrameReader::new(); - // Multiple sentinels in a row (empty gaps between frames) - let f = encode_frame(b"data"); - let mut input = vec![0x00, 0x00]; // leading empty gaps - input.extend_from_slice(&f); - input.push(0x00); // trailing empty gap - let frames = reader.feed(&input); - assert_eq!(frames.len(), 1); - assert_eq!(frames[0], b"data"); - } - - #[test] - fn frame_reader_bad_cobs_skipped() { - let mut reader = FrameReader::new(); - // Feed a known-bad COBS frame followed by a good one. - // A frame where the first byte claims a run longer than the frame is invalid. - let bad = &[0xFF, 0x01, 0x00]; // 0xFF says next 254 bytes, but only 1 present - let good = encode_frame(b"ok"); - let mut input = bad.to_vec(); - input.extend_from_slice(&good); - let frames = reader.feed(&input); - // Bad frame skipped, good frame decoded - assert_eq!(frames.len(), 1); - assert_eq!(frames[0], b"ok"); - } - - #[test] - fn read_frame_from_bytes() { - let data = b"frame"; - let encoded = encode_frame(data); - let mut cursor = std::io::Cursor::new(encoded); - let result = read_frame(&mut cursor); - assert!(result.is_ok()); - assert_eq!(result.ok().flatten().as_deref(), Some(data.as_slice())); - } - - #[test] - fn read_frame_timeout() { - // Empty reader → returns None (timeout) - let mut cursor = std::io::Cursor::new(Vec::::new()); - let result = read_frame(&mut cursor); - assert!(result.is_ok()); - assert!(result.ok().flatten().is_none()); - } - - #[test] - fn read_frame_empty_gap_then_data() { - let data = b"after_gap"; - let mut input = vec![0x00]; // empty gap - input.extend_from_slice(&encode_frame(data)); - let mut cursor = std::io::Cursor::new(input); - // First read_frame returns None for the empty gap - let r1 = read_frame(&mut cursor); - assert_eq!(r1.ok().flatten(), None); - // Second read returns the actual frame - let r2 = read_frame(&mut cursor); - assert_eq!(r2.ok().flatten().as_deref(), Some(data.as_slice())); - } - - #[test] - fn encode_all_byte_values_roundtrip() { - // Test that all possible byte values survive encode/decode - let data: Vec = (0..=255).collect(); - let encoded = encode_frame(&data); - let decoded = decode_frame(&encoded[..encoded.len() - 1]); - assert_eq!(decoded, Some(data)); - } - - #[test] - fn config_response_cobs_roundtrip() { - // Exact Config response bytes: tag=1, freq=910525000, bw=62.5k, sf=7, - // cr=5, sync=0x3444, tx=22dBm, preamble=16, cad=1 - // Note: preamble high byte is 0x00 — tests COBS with embedded zero. - let data = vec![ - 0x01, 0x48, 0x82, 0x45, 0x36, 0x06, 0x07, 0x05, - 0x44, 0x34, 0x16, 0x10, 0x00, 0x01, - ]; - assert_eq!(data.len(), 14); - let encoded = encode_frame(&data); - let decoded = decode_frame(&encoded[..encoded.len() - 1]); - assert_eq!(decoded, Some(data)); - } - - #[test] - fn config_response_cobs_roundtrip_cad_zero() { - // Same but with cad=0 — two consecutive 0x00 bytes at end. - let data = vec![ - 0x01, 0x48, 0x82, 0x45, 0x36, 0x06, 0x07, 0x05, - 0x44, 0x34, 0x16, 0x10, 0x00, 0x00, - ]; - assert_eq!(data.len(), 14); - let encoded = encode_frame(&data); - let decoded = decode_frame(&encoded[..encoded.len() - 1]); - assert_eq!(decoded, Some(data)); - } - - #[cfg(unix)] - #[test] - fn read_frame_socket_timeout_returns_none() { - use std::os::unix::net::UnixStream; - use std::time::Duration; - - let (mut a, _b) = UnixStream::pair().unwrap(); - a.set_read_timeout(Some(Duration::from_millis(10))) - .unwrap(); - - // No data written to _b, so read on `a` will time out with WouldBlock on Linux. - let result = read_frame(&mut a); - assert!( - result.is_ok(), - "socket timeout should be Ok(None), got: {result:?}" - ); - assert!(result.unwrap().is_none()); - } -} diff --git a/clients/rust/src/connect.rs b/clients/rust/src/connect.rs deleted file mode 100644 index 2b56b866..00000000 --- a/clients/rust/src/connect.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! Connection auto-detection and mux client helpers. -//! -//! The [`connect`] function tries mux connections first (TCP via env var, then -//! Unix socket), falling back to direct USB serial. This matches the Python -//! client's `connect()` behavior. - -use std::time::Duration; - -use tracing::debug; - -use crate::client::Client; -use crate::discovery; -use crate::transport::{AnyTransport, MuxTransport, SerialTransport}; - -/// Default read timeout for connections. -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2); - -/// Resolve the mux socket path in priority order. -/// -/// 1. `$DONGLORA_MUX` environment variable -/// 2. `$XDG_RUNTIME_DIR/donglora/mux.sock` -/// 3. `/tmp/donglora-mux.sock` -pub fn default_socket_path() -> String { - if let Ok(env) = std::env::var("DONGLORA_MUX") { - return env; - } - if let Ok(xdg) = std::env::var("XDG_RUNTIME_DIR") { - return format!("{xdg}/donglora/mux.sock"); - } - "/tmp/donglora-mux.sock".to_string() -} - -/// Find an existing mux socket path, or `None` if no socket file exists. -fn find_mux_socket() -> Option { - if let Ok(env) = std::env::var("DONGLORA_MUX") { - if std::path::Path::new(&env).exists() { - return Some(env); - } - return None; - } - if let Ok(xdg) = std::env::var("XDG_RUNTIME_DIR") { - let p = format!("{xdg}/donglora/mux.sock"); - if std::path::Path::new(&p).exists() { - return Some(p); - } - } - let p = "/tmp/donglora-mux.sock"; - if std::path::Path::new(p).exists() { - return Some(p.to_string()); - } - None -} - -/// Connect to the mux daemon via Unix domain socket. -#[cfg(unix)] -pub fn mux_connect( - path: Option<&str>, - timeout: Duration, -) -> anyhow::Result> { - let path = match path { - Some(p) => p.to_string(), - None => find_mux_socket() - .ok_or_else(|| anyhow::anyhow!("no mux socket found"))?, - }; - let transport = MuxTransport::unix(&path, timeout)?; - Ok(Client::new(transport)) -} - -/// Connect to the mux daemon via TCP. -pub fn mux_tcp_connect( - host: &str, - port: u16, - timeout: Duration, -) -> anyhow::Result> { - let transport = MuxTransport::tcp(host, port, timeout)?; - Ok(Client::new(transport)) -} - -/// Auto-detect and connect to a DongLoRa device. -/// -/// Priority: -/// 1. `DONGLORA_MUX_TCP` env var → TCP mux connection -/// 2. Unix socket mux (if socket file exists) -/// 3. Direct USB serial (auto-detect by VID:PID, blocks until device appears) -/// -/// If `port` is `Some`, skips mux detection and connects directly to that serial port. -pub fn connect( - port: Option<&str>, - timeout: Duration, -) -> anyhow::Result> { - // If explicit port given, go direct - if let Some(port) = port { - debug!("opening serial port {port}"); - let transport = SerialTransport::open(port, timeout)?; - return Ok(Client::new(AnyTransport::Serial(transport))); - } - - // Try TCP mux via environment variable - if let Ok(tcp) = std::env::var("DONGLORA_MUX_TCP") - && let Some(transport) = try_tcp_mux(&tcp, timeout) - { - debug!("connected to TCP mux at {tcp}"); - return Ok(Client::new(AnyTransport::Mux(transport))); - } - - // Try Unix socket mux - #[cfg(unix)] - if let Some(path) = find_mux_socket() { - match MuxTransport::unix(&path, timeout) { - Ok(transport) => { - debug!("connected to mux socket at {path}"); - return Ok(Client::new(AnyTransport::Mux(transport))); - } - Err(_) => { - // Stale socket or connection refused — fall through - debug!("mux socket at {path} not reachable, falling back to USB"); - } - } - } - - // Direct USB serial — auto-detect or wait - let port_path = discovery::find_port() - .unwrap_or_else(discovery::wait_for_device); - debug!("opening serial port {port_path}"); - let transport = SerialTransport::open(&port_path, timeout)?; - Ok(Client::new(AnyTransport::Serial(transport))) -} - -/// Convenience: connect with default timeout. -pub fn connect_default() -> anyhow::Result> { - connect(None, DEFAULT_TIMEOUT) -} - -/// Connect to a mux daemon only (TCP via env var, then Unix socket). -/// -/// Unlike [`connect`], this **never** falls back to direct USB serial. -/// Returns an error if no mux is reachable — the caller can retry with backoff. -/// -/// This is the Rust equivalent of the Python client's "sticky mux" behavior: -/// once you decide to use the mux, you stay on the mux. -pub fn connect_mux_auto(timeout: Duration) -> anyhow::Result> { - // Try TCP mux via environment variable. - if let Ok(tcp) = std::env::var("DONGLORA_MUX_TCP") - && let Some(transport) = try_tcp_mux(&tcp, timeout) - { - debug!("connected to TCP mux at {tcp}"); - return Ok(Client::new(AnyTransport::Mux(transport))); - } - - // Try Unix socket mux. - #[cfg(unix)] - { - let path = find_mux_socket() - .ok_or_else(|| anyhow::anyhow!("no mux socket found"))?; - let transport = MuxTransport::unix(&path, timeout)?; - debug!("connected to mux socket at {path}"); - Ok(Client::new(AnyTransport::Mux(transport))) - } - - #[cfg(not(unix))] - anyhow::bail!("mux-only mode requires Unix socket support or DONGLORA_MUX_TCP") -} - -fn try_tcp_mux(addr: &str, timeout: Duration) -> Option { - let (host, port) = if let Some((h, p)) = addr.rsplit_once(':') { - let host = if h.is_empty() { "localhost" } else { h }; - let port: u16 = p.parse().ok()?; - (host.to_string(), port) - } else { - let port: u16 = addr.parse().ok()?; - ("localhost".to_string(), port) - }; - MuxTransport::tcp(&host, port, timeout).ok() -} diff --git a/clients/rust/src/discovery.rs b/clients/rust/src/discovery.rs deleted file mode 100644 index 0ce8d8dd..00000000 --- a/clients/rust/src/discovery.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! USB device discovery for DongLoRa dongles. -//! -//! Finds the serial port by matching USB VID:PID using the `serialport` crate. - -use std::thread; -use std::time::Duration; - -use tracing::info; - -/// DongLoRa USB Vendor ID. -pub const USB_VID: u16 = 0x1209; - -/// DongLoRa USB Product ID. -pub const USB_PID: u16 = 0x5741; - -/// Known USB-UART bridge VID:PIDs found on some board revisions. -const BRIDGE_VID_PIDS: &[(u16, u16)] = &[ - (0x10C4, 0xEA60), // CP2102 (Silicon Labs) - (0x1A86, 0x55D4), // CH9102 - (0x1A86, 0x7523), // CH340 - (0x0403, 0x6001), // FT232R (FTDI) -]; - -/// Find the DongLoRa serial port by USB VID:PID. -/// -/// Checks for native USB CDC-ACM first (VID:PID 1209:5741), then falls -/// back to known USB-UART bridge chips found on some board revisions. -/// Returns the first matching port path, or `None` if no device is found. -pub fn find_port() -> Option { - let ports = serialport::available_ports().ok()?; - - // Prefer native USB (our custom VID:PID) - if let Some(port) = ports.iter().find(|p| { - matches!( - &p.port_type, - serialport::SerialPortType::UsbPort(info) - if info.vid == USB_VID && info.pid == USB_PID - ) - }) { - return Some(port.port_name.clone()); - } - - // Fall back to known USB-UART bridge chips - ports - .into_iter() - .find(|p| { - matches!( - &p.port_type, - serialport::SerialPortType::UsbPort(info) - if BRIDGE_VID_PIDS.contains(&(info.vid, info.pid)) - ) - }) - .map(|p| p.port_name) -} - -/// Block until a DongLoRa device appears on USB. -/// -/// Polls [`find_port`] every 500ms and returns the port path once found. -pub fn wait_for_device() -> String { - info!("waiting for DongLoRa device..."); - loop { - if let Some(port) = find_port() { - info!("found device at {port}"); - // Brief delay for USB enumeration to settle - thread::sleep(Duration::from_millis(300)); - return port; - } - thread::sleep(Duration::from_millis(500)); - } -} diff --git a/clients/rust/src/lib.rs b/clients/rust/src/lib.rs deleted file mode 100644 index ec67a35c..00000000 --- a/clients/rust/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! DongLoRa host library — connect, configure, send/receive LoRa packets. -//! -//! Implements the DongLoRa USB protocol (COBS-framed fixed-size LE). -//! See `firmware/PROTOCOL.md` for the full specification. -//! -//! # Quick start -//! -//! ```no_run -//! use donglora_client::*; -//! -//! let mut client = connect_default()?; -//! client.ping()?; -//! client.set_config(RadioConfig::default())?; -//! client.start_rx()?; -//! -//! loop { -//! if let Some(Response::RxPacket { rssi, snr, payload }) = client.recv()? { -//! println!("RX rssi={rssi} snr={snr} len={}", payload.len()); -//! } -//! } -//! # Ok::<(), anyhow::Error>(()) -//! ``` - -pub mod client; -pub mod codec; -pub mod connect; -pub mod discovery; -pub mod protocol; -pub mod transport; - -// Flat re-exports for convenience -pub use client::Client; -pub use codec::{decode_frame, encode_frame, read_frame, FrameReader}; -pub use connect::{connect, connect_default, connect_mux_auto, default_socket_path}; -pub use discovery::{find_port, wait_for_device, USB_PID, USB_VID}; -pub use protocol::{ - Bandwidth, Command, ErrorCode, RadioConfig, Response, CMD_TAG_SET_CONFIG, CMD_TAG_START_RX, - CMD_TAG_STOP_RX, ERROR_INVALID_CONFIG, MAX_PAYLOAD, PREAMBLE_DEFAULT, RADIO_CONFIG_SIZE, - RESP_TAG_ERROR, RESP_TAG_OK, RESP_TAG_RX_PACKET, TX_POWER_MAX, -}; -pub use transport::{AnyTransport, MuxTransport, SerialTransport, Transport}; - -#[cfg(unix)] -pub use connect::mux_connect; -pub use connect::mux_tcp_connect; diff --git a/clients/rust/src/protocol.rs b/clients/rust/src/protocol.rs deleted file mode 100644 index a84ed18d..00000000 --- a/clients/rust/src/protocol.rs +++ /dev/null @@ -1,693 +0,0 @@ -//! Wire protocol types and fixed-size little-endian serialization. -//! -//! Mirrors the firmware's `protocol.rs` using std types. Every integer is -//! fixed-width LE. See `firmware/PROTOCOL.md` for the full specification. - -/// Maximum LoRa payload size in bytes. -pub const MAX_PAYLOAD: usize = 256; - -/// RadioConfig wire size (fixed). -pub const RADIO_CONFIG_SIZE: usize = 13; - -/// Sentinel value for `tx_power_dbm`: use the board's maximum TX power. -pub const TX_POWER_MAX: i8 = i8::MIN; // -128 on the wire - -/// Sentinel value for `preamble_len`: use the firmware default (16 symbols). -pub const PREAMBLE_DEFAULT: u16 = 0; - -// ── Tag constants ────────────────────────────────────────────────── - -/// Command tag for SetConfig. -pub const CMD_TAG_SET_CONFIG: u8 = 2; -/// Command tag for StartRx. -pub const CMD_TAG_START_RX: u8 = 3; -/// Command tag for StopRx. -pub const CMD_TAG_STOP_RX: u8 = 4; - -/// Response tag for RxPacket. -pub const RESP_TAG_RX_PACKET: u8 = 2; -/// Response tag for Ok. -pub const RESP_TAG_OK: u8 = 4; -/// Response tag for Error. -pub const RESP_TAG_ERROR: u8 = 5; - -/// Error code for InvalidConfig. -pub const ERROR_INVALID_CONFIG: u8 = 0; - -// ── Bandwidth ────────────────────────────────────────────────────── - -/// LoRa signal bandwidth. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum Bandwidth { - Khz7 = 0, - Khz10 = 1, - Khz15 = 2, - Khz20 = 3, - Khz31 = 4, - Khz41 = 5, - Khz62 = 6, - Khz125 = 7, - Khz250 = 8, - Khz500 = 9, -} - -impl Bandwidth { - /// Convert from raw byte. Returns `None` for invalid values. - pub fn from_u8(v: u8) -> Option { - match v { - 0 => Some(Self::Khz7), - 1 => Some(Self::Khz10), - 2 => Some(Self::Khz15), - 3 => Some(Self::Khz20), - 4 => Some(Self::Khz31), - 5 => Some(Self::Khz41), - 6 => Some(Self::Khz62), - 7 => Some(Self::Khz125), - 8 => Some(Self::Khz250), - 9 => Some(Self::Khz500), - _ => None, - } - } -} - -// ── RadioConfig ──────────────────────────────────────────────────── - -/// Complete LoRa radio configuration. -/// -/// Wire layout (13 bytes, all little-endian): -/// ```text -/// [freq_hz:4] [bw:1] [sf:1] [cr:1] [sync_word:2] [tx_power_dbm:1] [preamble_len:2] [cad:1] -/// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct RadioConfig { - /// Frequency in Hz (150_000_000 - 960_000_000 for SX1262). - pub freq_hz: u32, - pub bw: Bandwidth, - /// Spreading factor (5-12). - pub sf: u8, - /// Coding rate denominator (5-8). E.g. 5 = CR 4/5, 8 = CR 4/8. - pub cr: u8, - pub sync_word: u16, - /// Transmit power in dBm. Set to [`TX_POWER_MAX`] (-128) for the board's maximum. - pub tx_power_dbm: i8, - /// Preamble length in symbols. Set to [`PREAMBLE_DEFAULT`] (0) for firmware default (16). - pub preamble_len: u16, - /// Channel Activity Detection (listen-before-talk). 0 = disabled, non-zero = enabled. - pub cad: u8, -} - -impl RadioConfig { - /// Serialize to fixed-size LE bytes. - pub fn to_bytes(&self) -> [u8; RADIO_CONFIG_SIZE] { - let mut buf = [0u8; RADIO_CONFIG_SIZE]; - buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes()); - buf[4] = self.bw as u8; - buf[5] = self.sf; - buf[6] = self.cr; - buf[7..9].copy_from_slice(&self.sync_word.to_le_bytes()); - buf[9] = self.tx_power_dbm as u8; - buf[10..12].copy_from_slice(&self.preamble_len.to_le_bytes()); - buf[12] = self.cad; - buf - } - - /// Deserialize from fixed-size LE bytes. Returns `None` if too short or invalid. - pub fn from_bytes(buf: &[u8]) -> Option { - if buf.len() < RADIO_CONFIG_SIZE { - return None; - } - Some(Self { - freq_hz: u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), - bw: Bandwidth::from_u8(buf[4])?, - sf: buf[5], - cr: buf[6], - sync_word: u16::from_le_bytes([buf[7], buf[8]]), - tx_power_dbm: buf[9] as i8, - preamble_len: u16::from_le_bytes([buf[10], buf[11]]), - cad: buf[12], - }) - } -} - -/// Default: 915 MHz, 125 kHz BW, SF7, CR 4/5, sync 0x1424, max power, default preamble, CAD on. -impl Default for RadioConfig { - fn default() -> Self { - Self { - freq_hz: 915_000_000, - bw: Bandwidth::Khz125, - sf: 7, - cr: 5, - sync_word: 0x1424, - tx_power_dbm: TX_POWER_MAX, - preamble_len: PREAMBLE_DEFAULT, - cad: 1, - } - } -} - -// ── Command ──────────────────────────────────────────────────────── - -/// Host → firmware commands. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Command { - Ping, - GetConfig, - SetConfig(RadioConfig), - StartRx, - StopRx, - Transmit { - config: Option, - payload: Vec, - }, - DisplayOn, - DisplayOff, - GetMac, -} - -impl Command { - /// Returns the wire tag byte for this command. - pub fn tag(&self) -> u8 { - match self { - Self::Ping => 0, - Self::GetConfig => 1, - Self::SetConfig(_) => 2, - Self::StartRx => 3, - Self::StopRx => 4, - Self::Transmit { .. } => 5, - Self::DisplayOn => 6, - Self::DisplayOff => 7, - Self::GetMac => 8, - } - } - - /// Serialize to fixed-size LE bytes. - pub fn to_bytes(&self) -> Vec { - match self { - Self::Ping => vec![0], - Self::GetConfig => vec![1], - Self::SetConfig(cfg) => { - let mut out = vec![2]; - out.extend_from_slice(&cfg.to_bytes()); - out - } - Self::StartRx => vec![3], - Self::StopRx => vec![4], - Self::Transmit { config, payload } => { - let mut out = vec![5]; - match config { - None => out.push(0), - Some(cfg) => { - out.push(1); - out.extend_from_slice(&cfg.to_bytes()); - } - } - out.extend_from_slice(&(payload.len() as u16).to_le_bytes()); - out.extend_from_slice(payload); - out - } - Self::DisplayOn => vec![6], - Self::DisplayOff => vec![7], - Self::GetMac => vec![8], - } - } - - /// Deserialize from a decoded frame. Returns `None` on malformed input. - pub fn from_bytes(buf: &[u8]) -> Option { - let tag = *buf.first()?; - let rest = &buf[1..]; - match tag { - 0 => Some(Self::Ping), - 1 => Some(Self::GetConfig), - 2 => Some(Self::SetConfig(RadioConfig::from_bytes(rest)?)), - 3 => Some(Self::StartRx), - 4 => Some(Self::StopRx), - 5 => { - if rest.is_empty() { - return None; - } - let (config, pos) = if rest[0] == 0 { - (None, 1) - } else if rest[0] == 1 && rest.len() > RADIO_CONFIG_SIZE { - (Some(RadioConfig::from_bytes(&rest[1..])?), 1 + RADIO_CONFIG_SIZE) - } else { - return None; - }; - if rest.len() < pos + 2 { - return None; - } - let len = u16::from_le_bytes([rest[pos], rest[pos + 1]]) as usize; - let data_start = pos + 2; - if rest.len() < data_start + len || len > MAX_PAYLOAD { - return None; - } - Some(Self::Transmit { - config, - payload: rest[data_start..data_start + len].to_vec(), - }) - } - 6 => Some(Self::DisplayOn), - 7 => Some(Self::DisplayOff), - 8 => Some(Self::GetMac), - _ => None, - } - } -} - -// ── ErrorCode ────────────────────────────────────────────────────── - -/// Error codes reported by the firmware. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum ErrorCode { - InvalidConfig = 0, - RadioBusy = 1, - TxTimeout = 2, - CrcError = 3, - NotConfigured = 4, - NoDisplay = 5, -} - -impl ErrorCode { - /// Convert from raw byte. Returns `None` for unknown codes. - pub fn from_u8(v: u8) -> Option { - match v { - 0 => Some(Self::InvalidConfig), - 1 => Some(Self::RadioBusy), - 2 => Some(Self::TxTimeout), - 3 => Some(Self::CrcError), - 4 => Some(Self::NotConfigured), - 5 => Some(Self::NoDisplay), - _ => None, - } - } -} - -impl std::fmt::Display for ErrorCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InvalidConfig => write!(f, "InvalidConfig"), - Self::RadioBusy => write!(f, "RadioBusy"), - Self::TxTimeout => write!(f, "TxTimeout"), - Self::CrcError => write!(f, "CrcError"), - Self::NotConfigured => write!(f, "NotConfigured"), - Self::NoDisplay => write!(f, "NoDisplay"), - } - } -} - -// ── Response ─────────────────────────────────────────────────────── - -/// Firmware → host responses. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Response { - Pong, - Config(RadioConfig), - RxPacket { - rssi: i16, - snr: i16, - payload: Vec, - }, - TxDone, - Ok, - Error(ErrorCode), - MacAddress([u8; 6]), -} - -impl Response { - /// Returns the wire tag byte for this response. - pub fn tag(&self) -> u8 { - match self { - Self::Pong => 0, - Self::Config(_) => 1, - Self::RxPacket { .. } => 2, - Self::TxDone => 3, - Self::Ok => 4, - Self::Error(_) => 5, - Self::MacAddress(_) => 6, - } - } - - /// Whether this is an unsolicited RxPacket. - pub fn is_rx_packet(&self) -> bool { - matches!(self, Self::RxPacket { .. }) - } - - /// Serialize to fixed-size LE bytes. - pub fn to_bytes(&self) -> Vec { - match self { - Self::Pong => vec![0], - Self::Config(cfg) => { - let mut out = vec![1]; - out.extend_from_slice(&cfg.to_bytes()); - out - } - Self::RxPacket { rssi, snr, payload } => { - let mut out = vec![2]; - out.extend_from_slice(&rssi.to_le_bytes()); - out.extend_from_slice(&snr.to_le_bytes()); - out.extend_from_slice(&(payload.len() as u16).to_le_bytes()); - out.extend_from_slice(payload); - out - } - Self::TxDone => vec![3], - Self::Ok => vec![4], - Self::Error(code) => vec![5, *code as u8], - Self::MacAddress(mac) => { - let mut out = vec![6]; - out.extend_from_slice(mac); - out - } - } - } - - /// Deserialize from a decoded frame. Returns `None` on malformed input. - pub fn from_bytes(buf: &[u8]) -> Option { - let tag = *buf.first()?; - let rest = &buf[1..]; - match tag { - 0 => Some(Self::Pong), - 1 => Some(Self::Config(RadioConfig::from_bytes(rest)?)), - 2 => { - if rest.len() < 6 { - return None; - } - let rssi = i16::from_le_bytes([rest[0], rest[1]]); - let snr = i16::from_le_bytes([rest[2], rest[3]]); - let len = u16::from_le_bytes([rest[4], rest[5]]) as usize; - if rest.len() < 6 + len || len > MAX_PAYLOAD { - return None; - } - Some(Self::RxPacket { - rssi, - snr, - payload: rest[6..6 + len].to_vec(), - }) - } - 3 => Some(Self::TxDone), - 4 => Some(Self::Ok), - 5 => Some(Self::Error(ErrorCode::from_u8(*rest.first()?)?)), - 6 => { - if rest.len() < 6 { - return None; - } - let mut mac = [0u8; 6]; - mac.copy_from_slice(&rest[..6]); - Some(Self::MacAddress(mac)) - } - _ => None, - } - } -} - -// ── Tests ────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn make_config() -> RadioConfig { - RadioConfig { - freq_hz: 915_000_000, - bw: Bandwidth::Khz125, - sf: 7, - cr: 5, - sync_word: 0x3444, - tx_power_dbm: 22, - preamble_len: 16, - cad: 1, - } - } - - // ── RadioConfig ──────────────────────────────────────────────── - - #[test] - fn radio_config_roundtrip() { - let cfg = make_config(); - let bytes = cfg.to_bytes(); - assert_eq!(bytes.len(), RADIO_CONFIG_SIZE); - assert_eq!(RadioConfig::from_bytes(&bytes), Some(cfg)); - } - - #[test] - fn radio_config_default_roundtrip() { - let cfg = RadioConfig::default(); - let bytes = cfg.to_bytes(); - assert_eq!(RadioConfig::from_bytes(&bytes), Some(cfg)); - } - - #[test] - fn radio_config_all_bandwidths() { - for bw_val in 0u8..=9 { - let bw = Bandwidth::from_u8(bw_val); - assert!(bw.is_some(), "bandwidth {bw_val} should be valid"); - let cfg = RadioConfig { bw: bw.unwrap(), ..make_config() }; - let bytes = cfg.to_bytes(); - assert_eq!(RadioConfig::from_bytes(&bytes), Some(cfg)); - } - } - - #[test] - fn radio_config_invalid_bandwidth() { - let mut buf = make_config().to_bytes(); - buf[4] = 255; - assert!(RadioConfig::from_bytes(&buf).is_none()); - } - - #[test] - fn radio_config_negative_power() { - let cfg = RadioConfig { tx_power_dbm: TX_POWER_MAX, ..make_config() }; - let bytes = cfg.to_bytes(); - assert_eq!(RadioConfig::from_bytes(&bytes), Some(cfg)); - } - - #[test] - fn radio_config_short_buffer() { - assert!(RadioConfig::from_bytes(&[0u8; 12]).is_none()); - assert!(RadioConfig::from_bytes(&[]).is_none()); - } - - // ── Command ──────────────────────────────────────────────────── - - #[test] - fn command_simple_roundtrips() { - for cmd in [ - Command::Ping, - Command::GetConfig, - Command::StartRx, - Command::StopRx, - Command::DisplayOn, - Command::DisplayOff, - Command::GetMac, - ] { - let bytes = cmd.to_bytes(); - assert_eq!(Command::from_bytes(&bytes), Some(cmd)); - } - } - - #[test] - fn command_set_config_roundtrip() { - let cmd = Command::SetConfig(make_config()); - let bytes = cmd.to_bytes(); - assert_eq!(Command::from_bytes(&bytes), Some(cmd)); - } - - #[test] - fn command_transmit_no_config_roundtrip() { - let cmd = Command::Transmit { - config: None, - payload: b"hello".to_vec(), - }; - let bytes = cmd.to_bytes(); - assert_eq!(Command::from_bytes(&bytes), Some(cmd)); - } - - #[test] - fn command_transmit_with_config_roundtrip() { - let cmd = Command::Transmit { - config: Some(make_config()), - payload: b"test".to_vec(), - }; - let bytes = cmd.to_bytes(); - assert_eq!(Command::from_bytes(&bytes), Some(cmd)); - } - - #[test] - fn command_transmit_empty_payload() { - let cmd = Command::Transmit { - config: None, - payload: vec![], - }; - let bytes = cmd.to_bytes(); - assert_eq!(Command::from_bytes(&bytes), Some(cmd)); - } - - #[test] - fn command_transmit_truncated() { - assert!(Command::from_bytes(&[5]).is_none()); // tag only - assert!(Command::from_bytes(&[5, 1]).is_none()); // has_config=1, no config - assert!(Command::from_bytes(&[5, 0]).is_none()); // has_config=0, no length - } - - #[test] - fn command_invalid_tag() { - assert!(Command::from_bytes(&[9]).is_none()); - assert!(Command::from_bytes(&[255]).is_none()); - } - - #[test] - fn command_empty_buffer() { - assert!(Command::from_bytes(&[]).is_none()); - } - - #[test] - fn command_tags() { - assert_eq!(Command::Ping.tag(), 0); - assert_eq!(Command::GetConfig.tag(), 1); - assert_eq!(Command::SetConfig(make_config()).tag(), 2); - assert_eq!(Command::StartRx.tag(), 3); - assert_eq!(Command::StopRx.tag(), 4); - assert_eq!(Command::Transmit { config: None, payload: vec![] }.tag(), 5); - assert_eq!(Command::DisplayOn.tag(), 6); - assert_eq!(Command::DisplayOff.tag(), 7); - assert_eq!(Command::GetMac.tag(), 8); - } - - // ── Response ─────────────────────────────────────────────────── - - #[test] - fn response_simple_roundtrips() { - for resp in [Response::Pong, Response::TxDone, Response::Ok] { - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - } - - #[test] - fn response_config_roundtrip() { - let resp = Response::Config(make_config()); - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - - #[test] - fn response_rx_packet_roundtrip() { - let resp = Response::RxPacket { - rssi: -80, - snr: 10, - payload: b"data".to_vec(), - }; - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - - #[test] - fn response_rx_packet_empty_payload() { - let resp = Response::RxPacket { - rssi: -120, - snr: -5, - payload: vec![], - }; - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - - #[test] - fn response_error_codes() { - for code in [ - ErrorCode::InvalidConfig, - ErrorCode::RadioBusy, - ErrorCode::TxTimeout, - ErrorCode::CrcError, - ErrorCode::NotConfigured, - ErrorCode::NoDisplay, - ] { - let resp = Response::Error(code); - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - } - - #[test] - fn response_mac_address_roundtrip() { - let resp = Response::MacAddress([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); - let bytes = resp.to_bytes(); - assert_eq!(Response::from_bytes(&bytes), Some(resp)); - } - - #[test] - fn response_tags() { - assert_eq!(Response::Pong.tag(), 0); - assert_eq!(Response::Config(make_config()).tag(), 1); - assert_eq!( - Response::RxPacket { rssi: 0, snr: 0, payload: vec![] }.tag(), - 2 - ); - assert_eq!(Response::TxDone.tag(), 3); - assert_eq!(Response::Ok.tag(), 4); - assert_eq!(Response::Error(ErrorCode::RadioBusy).tag(), 5); - assert_eq!(Response::MacAddress([0; 6]).tag(), 6); - } - - #[test] - fn response_is_rx_packet() { - assert!(Response::RxPacket { rssi: 0, snr: 0, payload: vec![] }.is_rx_packet()); - assert!(!Response::Ok.is_rx_packet()); - assert!(!Response::Pong.is_rx_packet()); - } - - #[test] - fn response_invalid_tag() { - assert!(Response::from_bytes(&[7]).is_none()); - assert!(Response::from_bytes(&[255]).is_none()); - } - - #[test] - fn response_truncated() { - assert!(Response::from_bytes(&[]).is_none()); - assert!(Response::from_bytes(&[5]).is_none()); // Error with no code - assert!(Response::from_bytes(&[2, 0, 0]).is_none()); // RxPacket too short - assert!(Response::from_bytes(&[6, 0, 0, 0]).is_none()); // MacAddress too short - } - - // ── ErrorCode ────────────────────────────────────────────────── - - #[test] - fn error_code_from_u8() { - for v in 0..=5 { - assert!(ErrorCode::from_u8(v).is_some()); - } - assert!(ErrorCode::from_u8(6).is_none()); - assert!(ErrorCode::from_u8(255).is_none()); - } - - #[test] - fn error_code_display() { - assert_eq!(ErrorCode::InvalidConfig.to_string(), "InvalidConfig"); - assert_eq!(ErrorCode::RadioBusy.to_string(), "RadioBusy"); - } - - // ── Cross-compatibility with firmware encoding ───────────────── - - #[test] - fn firmware_worked_example() { - // From PROTOCOL.md: 915 MHz, 125 kHz BW, SF7, CR 4/5, sync 0x1424, max power - let cfg = RadioConfig { - freq_hz: 915_000_000, - bw: Bandwidth::Khz125, - sf: 7, - cr: 5, - sync_word: 0x1424, - tx_power_dbm: TX_POWER_MAX, - preamble_len: PREAMBLE_DEFAULT, - cad: 1, - }; - let cmd = Command::SetConfig(cfg); - let bytes = cmd.to_bytes(); - // Expected: 02 C0 CA 89 36 07 07 05 24 14 80 00 00 01 - assert_eq!( - bytes, - [0x02, 0xC0, 0xCA, 0x89, 0x36, 0x07, 0x07, 0x05, 0x24, 0x14, 0x80, 0x00, 0x00, 0x01] - ); - } -} diff --git a/clients/rust/src/transport.rs b/clients/rust/src/transport.rs deleted file mode 100644 index 735f8ccc..00000000 --- a/clients/rust/src/transport.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Transport abstraction for byte-stream connections. -//! -//! The [`Transport`] trait unifies serial ports and mux socket connections, -//! allowing the [`Client`](crate::client::Client) to work over any of them. - -use std::io::{self, Read, Write}; -use std::time::Duration; - -/// A byte-stream transport that the client can talk over. -pub trait Transport: Read + Write + Send { - /// Set the read timeout. A read that takes longer returns zero bytes. - fn set_timeout(&mut self, timeout: Duration) -> anyhow::Result<()>; - - /// Get the current read timeout. - fn timeout(&self) -> Duration; -} - -// ── Serial transport ─────────────────────────────────────────────── - -/// Direct USB serial port connection to a DongLoRa dongle. -pub struct SerialTransport { - port: Box, - timeout: Duration, -} - -impl SerialTransport { - /// Open a serial port with the given timeout. - /// - /// Baud rate is irrelevant for USB CDC-ACM but we pass a conventional value. - pub fn open(path: &str, timeout: Duration) -> anyhow::Result { - let port = serialport::new(path, 115_200) - .timeout(timeout) - .open() - .map_err(|e| anyhow::anyhow!("failed to open serial port {path}: {e}"))?; - Ok(Self { port, timeout }) - } - - /// Clear the serial input buffer. - pub fn reset_input_buffer(&self) -> anyhow::Result<()> { - self.port - .clear(serialport::ClearBuffer::Input) - .map_err(|e| anyhow::anyhow!("failed to clear input buffer: {e}")) - } -} - -impl Read for SerialTransport { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.port.read(buf) - } -} - -impl Write for SerialTransport { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.port.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.port.flush() - } -} - -impl Transport for SerialTransport { - fn set_timeout(&mut self, timeout: Duration) -> anyhow::Result<()> { - self.port - .set_timeout(timeout) - .map_err(|e| anyhow::anyhow!("failed to set serial timeout: {e}"))?; - self.timeout = timeout; - Ok(()) - } - - fn timeout(&self) -> Duration { - self.timeout - } -} - -// ── Mux transport ────────────────────────────────────────────────── - -/// Connection to the DongLoRa mux daemon via Unix socket or TCP. -pub struct MuxTransport { - stream: MuxStream, - timeout: Duration, -} - -enum MuxStream { - #[cfg(unix)] - Unix(std::os::unix::net::UnixStream), - Tcp(std::net::TcpStream), -} - -impl MuxTransport { - /// Connect to the mux daemon via Unix domain socket. - #[cfg(unix)] - pub fn unix(path: &str, timeout: Duration) -> anyhow::Result { - let stream = std::os::unix::net::UnixStream::connect(path) - .map_err(|e| anyhow::anyhow!("failed to connect to mux socket {path}: {e}"))?; - stream - .set_read_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set socket read timeout: {e}"))?; - stream - .set_write_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set socket write timeout: {e}"))?; - Ok(Self { - stream: MuxStream::Unix(stream), - timeout, - }) - } - - /// Connect to the mux daemon via TCP. - pub fn tcp(host: &str, port: u16, timeout: Duration) -> anyhow::Result { - use std::net::ToSocketAddrs; - - let addr = format!("{host}:{port}"); - let sock_addr = addr - .to_socket_addrs() - .map_err(|e| anyhow::anyhow!("failed to resolve mux address {addr}: {e}"))? - .next() - .ok_or_else(|| anyhow::anyhow!("no addresses found for {addr}"))?; - let stream = std::net::TcpStream::connect_timeout(&sock_addr, timeout) - .map_err(|e| anyhow::anyhow!("failed to connect to mux at {addr}: {e}"))?; - stream - .set_read_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set TCP read timeout: {e}"))?; - stream - .set_write_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set TCP write timeout: {e}"))?; - Ok(Self { - stream: MuxStream::Tcp(stream), - timeout, - }) - } -} - -impl Read for MuxTransport { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match &mut self.stream { - #[cfg(unix)] - MuxStream::Unix(s) => s.read(buf), - MuxStream::Tcp(s) => s.read(buf), - } - } -} - -impl Write for MuxTransport { - fn write(&mut self, buf: &[u8]) -> io::Result { - match &mut self.stream { - #[cfg(unix)] - MuxStream::Unix(s) => s.write(buf), - MuxStream::Tcp(s) => s.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match &mut self.stream { - #[cfg(unix)] - MuxStream::Unix(s) => s.flush(), - MuxStream::Tcp(s) => s.flush(), - } - } -} - -impl Transport for MuxTransport { - fn set_timeout(&mut self, timeout: Duration) -> anyhow::Result<()> { - self.timeout = timeout; - let result = match &self.stream { - #[cfg(unix)] - MuxStream::Unix(s) => s - .set_read_timeout(Some(timeout)) - .and_then(|()| s.set_write_timeout(Some(timeout))), - MuxStream::Tcp(s) => s - .set_read_timeout(Some(timeout)) - .and_then(|()| s.set_write_timeout(Some(timeout))), - }; - result.map_err(|e| anyhow::anyhow!("failed to set timeout: {e}")) - } - - fn timeout(&self) -> Duration { - self.timeout - } -} - -// ── AnyTransport ─────────────────────────────────────────────────── - -/// Type-erased transport for the [`connect`](crate::connect::connect) return type. -pub enum AnyTransport { - Serial(SerialTransport), - Mux(MuxTransport), -} - -impl Read for AnyTransport { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - Self::Serial(t) => t.read(buf), - Self::Mux(t) => t.read(buf), - } - } -} - -impl Write for AnyTransport { - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - Self::Serial(t) => t.write(buf), - Self::Mux(t) => t.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self { - Self::Serial(t) => t.flush(), - Self::Mux(t) => t.flush(), - } - } -} - -impl Transport for AnyTransport { - fn set_timeout(&mut self, timeout: Duration) -> anyhow::Result<()> { - match self { - Self::Serial(t) => t.set_timeout(timeout), - Self::Mux(t) => t.set_timeout(timeout), - } - } - - fn timeout(&self) -> Duration { - match self { - Self::Serial(t) => t.timeout(), - Self::Mux(t) => t.timeout(), - } - } -} diff --git a/demo.jpg b/demo.jpg deleted file mode 100644 index 811a0b212830273021c20816736e89c45840b21e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 743138 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�m!Isl~51 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�$fQQUc)xjnrroz1hinM{Dq$69tdT#J?$ zB^i0D5@n0&eC6l8Xv~acSuJHR9!7ZLxumXnn224o)qWRzalh~-+)>I5)?|(AlYlEU z+dgi-TgiKDecSN&;#QC1{RZCl#^q!z2IS8weJi$-<~*6h>i+-<(%g8u+7Fj^%qyli zR!hgM3 zd8DDSSo{pSV0Oh8eQ{jQ2?{x+*^_thGgb^ENR_^VsVJQh%xXO@V)%#PhNq(0`R^BO z;8m%{C#^>v%em#U!E9JYNBV*VX={_j;_zc6HN?8G%qV-3M-aUFt3l~ zS{{j}{1VpgE#r28#u-8BUS&$D9vxiDXtuusH6VEdKKQ9mnI3P~CdhBT0P6F_nBt87 zRmoO-hI7T$lCm>2{{VoRo7}uNN#uYD2ZA{MRaCJpJr83CjFmNMv&y_*;oGkZ>oeNU zg$lRJ#~W+t=vRwZk?zsVV>xoknGjk}au1S>$G`soTDs+4Hqh^tbxtlo zp&)_!*P-m$?9#;3o|i5!=UFvlwLV=*HVpKng(#8Ujh~fjnBz$qyt`t!>P7J7D{# z)2(=aSj0^5=bE@^a$gbu0B0?C!Fq4@ji;M^Z6EsGu@x!rjGT(=!&k1JsPr(Ilb2J* zW7H*$$`DkJFnd?p(3-ZV(^7Jt%+j>Jk=tlFK7zEHOH-;X(`_?nOYr`ou3SeAv`)__ z7{z$F#<^c9;^K2JVesyI7l;1ilZowuk1^sh$;g~ngrC2=1wQzuvbk32ygfNN{0=8=DZ@t?}RvYk1_d&gum z*;KZthU;D&)I1-oy@sJ1LoywuSahz%4yV@OvHb6rLnOKRI5gV1?|Veqp`_`;bS&hP zo)mVjd3(DaUMCQt7VdhF!%u;C9}zD1J;&JWSFDooPy4muV!foaN5fXkJ*1wtNc0<@ z*w0UZ`G;41zlCv8z}IZ^9&px|Gj6|ROAvq2byDB&t$Pv6D>bPy&SdsvaQ?_MN;uRY z9)t}3mCW+WshLMOoS(#S-aP%BHO~pgWk6?ybdo-Y@UK#q8c`ivrFn zY6-=YM|M*H*Ns_R-VV|yH^8a)uRUnasm{pO@cpx2$}+rGGOKM48knmi)U+=ML8(LK zM{3s(j(oA4eQ;})%5B{FTvlz!+FF~Cd=z<(f3l|hkNDQn=I(s0Oxel1#g?P^9dnQd zjW@Bb$kK1AU3dn)N8&+x#wE1#LaueDdpeYw6PJ;^shpa?0q83 zsL_t5b(n6xwd+Bil^T4z7MS2w-0iVSX$n6a3iPOHxt6V$H0wL9N@YkFjDUa0tF4Mu z&Ky=gICW#wY(5n0e`Jnd?Ds;^ zM?A4mkMP!~3wwX!?*pvSA-c2M!3s!P))ggDT<)V*on`N<9F5P9b-USdE~_we)pwfG z6Gwe48m-it)gEGHnTyQlCC^&vaOXac2ZDtX*l-P7f$F8HO5 z=O-PjiltRZ^D!ReZg-vw_!Z(G9@BlT z#vw=m#=Qmg zi)_k{Y2Bhl{gb>}1^}@C0Keb;YPA~cqdC8{t1heh`43;&U&U5WxCMvt(_iev?%Y8BVh}oX=Dm7!rwV$XM}f`a z;(34?o|Ady+@q=Kg1E=$UPW5`zUw}V516Sv&bv;!XNTtC*L5{>=qXiFM6L5zr5GL5 z*;-h(^FxVf%)A2zqQ#>EecoqEl931nO(t4)CIC38WuhlcaZ)n7Ceg(}3wMKn<2W)Ebub@q#$1keSnI_WQ9=NwIGZ!YCQ544YPi zosW4(mq3i0**9**2FEvcaU}p(5^+I9j6B%DkN_S>rhb(K|olp@Lad2 z0{JI!ptzbg@n(XJBnq9V0gOU5Q6ttsMkoQZMj2=Vp+F4<#IJL0#%jtaEVwwJ3qih7 zKnyL4X@JqsNb0F#$3?R99npB`YqAuKr6bJzN#biS7F$Vbn`KeQ6;DG4q2k*A0D&aYuG;1${mUHU zu#&lIRw1+3je{ufn#ohJa~{RuZ;X>^8cBhrCz0N%Xl)jLg?vA~SYp^saHG;kbM~J( zzEje*LpOBHvJB@Uf(?ajvGYwCqMWk+^Vm`me%ZrBF;WBNz*@Tw#1L~K28#uWWshkG zwIK|V1c~&kETn*>ohVxfk6iHOr-?Mltgj;yLn@UkipJWRwR7_a_QCy{Zay39vTGV_ z1nD;imK_lP0EJw4GO_Zl#%$*KKIrRRA~m$lNp2Gvra_{Rq`c5lDc{@ei2W&aG+P;V zQJC&fjtH+dvC&BT_x3`SwLKjauPOkpXdOuVBf}P-XJu1cl=eEy#UE$JD$Zk=yMxSX z1XWu`9jE}7H`})upbAf6BXpn!{ejeS1ps7gc7WTFfuCxmS?3zWFBa?y)dApN7H_3_ zJeOaSHEhM4XNNS?Jdfp&aC%V!EO}4IxEj_}Mi?A)uC$C<=3MLo8bI zDurSz_SwY(D@XIFAPNa&c_}BQ0x!d;C|#uUKoQS$I*I_R_Kz}v9}1UjQcmDH;@GY@ zrUhf1)HYR#V2JJ+tk_u4jRwjtB9!a%;TXW2+}WebeqYM3lvfdS&=>CUM<`s$ zyt7qoVx_AYmDf+m%_BFt9>$HZdJ3ejXj+BU#?rN1phqDV(qt`Ng5~(ZxlU;X$PKGR z{0sp|EG&t+IiZD%{v0c9c8q%-mB}VJA7p$X*6lC6I~tY&kw7ETh@-6)ekpjS($2xH z_kwxiwzZC0hsqu#y%!g5%Y_-B=Z?FXHd=y4+fGeGcRDOLRdXaPR2q~+MFv!TQa--W|7PKf)^Du5=vFk6c|we?eNvTbW)D@Fd!lsUQ`}DI3KfLwr2& zYw6_!t_CZeE0me*HkwpWg}@mD(zi&KH&n0&-2L7~ZynBdeADr_MoT1`P6vALgiKpL zT-EjFM`d0)tr|0Oxq)-6+%Y(AD={T<-hrsJ*J&J>Aezon(AE*NKHm5Vpy`jLLnn}} zDPKz9oR2~gJ@((jR*x7302uVF<13oD*yKEI;EO-9%F7z$4@&Hb#Wl;8IHDHgjVQ@8y557 zde+FzmSechQaPC-$_w#ZM9M^@=C(7j6Lg>~K_Uhtib80Jz!y0cW-S|@9hgBX=cQH5 znz`M_dZr*jq?ydom3eXIu^FwA%_!WC8(rhxvYR>iPhj{nXjbidcBe~olB7?ltmgYQ znyANW_UY_$s2?SM*jkUD6yjGC5Y)~A2rj}eV7;yF3Oka8)-8SZwzGV##e)kS*rUC##>K=Ivg zMDmrd&cZ>{{RV1&*4Hwj|YceW&P~62gMH++;K07 zuKxhwIjxdLa>CK=Z<{ZF!g1mhJkH`AzoE>iS9_(vQWO6!A zJwFo9{3lx5jy0>l&aavwdYy>>0O2#&NBqWa{{Zi*u4Gl}b{i*-^~4xD)!_O)Upg!I zolyYs?y;PIY~KF>-@{a@M0}MA6UO?%KQ_6kh5rC%X03g!N{uSF+5RP{d~JB&f5aNG z{{XhFQ0n;|N{j1%#Jg$Yxn00I=BPj3ovAvuC)o76HJ@8@ihs4Q+v_t_dvcuz=3(l7 zD2Qf1KIy&u(@EBoS1`mQHX2jDm_oE_Gv`L@sR%jiFo!RyYMel!OSD>pPRoEL6gM( zBmV%e)~=jxtsZ_C_%GM>I27EFipB{0JS))=^*OGXv(>Q_!;UGoPg1+}#YN7QxYu2) zPUA$jbL=!0jcBEgROE^b9IjNnv{EvMHKa~?I}~hmOO00cC@s*u?$`(iO609t$nh~4 zud)21gZ2?Vd-0!zY<1X~e3jpi;2hVnI((|)Pt?7t7Z7cQf504x5ErzGQw#&Jyf z9CdkAvgmzf;7u0Z8)b$C18RZAd9|Y$95iWrR(d_Yg0b7>N6m`z9e;Yq)#0-KT6a7* z<9COkhflefimDnO_7(EYYtW#sc&}~> z==yZ@A)e=A9lxbzQqeN2K1kfT;f)&SS-wrNGkz81R>WD@`D(a^=6x~nv*EMHb8R&6 z9OMu&z^!>xN0V0)>TZ2Kr)kS)rvPI@SGGG>H?xJ!DB+`1abC?QgtA5lJ-b%%P)9y9 z2&w9inLlbDhCkc3zuGWCzIa9(0+iLXL{P>1C4Q*+2VU@gp`wMklqiMzZ581=#h-0M z7wxSs=0sO&1_}J@x{JPy_7k?F{LGFkvy`4-^VtsvrB^8pm+bET(ejf@Q8a;M#0ewy ztkh0guyS@7)*?gKG?g^YdX!|Ar*Gh0Ygq8?s`^%=EsNtHFGKw)x}MLWtFB)enp6I{<9Qu-Z>2NtwL?fw*K_V1L3k?Tq36Jl9^V*ddAAl8we zlo9l#vCZ_&_?O^21o0u3$p+E)lzP{F6)S3TQ^vY-_fJCj zHSp#ZkuI;}$>7GOoqu%>mNHeoBj}55G8thD<$wUicvTteQ^TsdvMD?}Zd-(3$X2nh z%2(AA9dDw;2UwJka4Ra3Qz|%AiaPFt1;x495{CAxqLL=H3oE0={wMq}(!5)x%NVy$ z?Ko7j%Z^SvQBpN8?w%y8xarjJ-vM|Y8&4S9rQ-hRN&~4Jb?IKs8dBuDJuk1ydNzH% zrr5>ySkm4#NL4^p1GX#7n^Ag>4b);wp+_nEf`w5bIv3c5{5cWn82qbMh!?PAC&N*b z{-ICcD*mv8l5e1^;YiQ?uwVP+Q1Kw}^O0`H!pUzSc%ejZ_eFEm#70_3RC3uja<7ea zh5o{sq=q*Gv0T+)oF`Y69*@-}O!=?JzYg@xYr>LBE#QjnLo&#q17A?+q-RcYgwj4! zy0$hqlR({3h{sMV!o)6CevN~dw5EnMoL3rWT&WOD{$keGB|)JyOLKjxh(|5g9@XMv zX5!B~8HnVKkFh^xj|xL3pLa7NG>kwjK;U~;N(!du=T)TZH?HT@mYx{~1qycHdRCF- zheNK8PwgXfJ6+IGwE3;4K_qSpu*Nb7#w(_mBYOkFeN~^C{{XaafNeYr<0f4@<+q)C-RBAh(>F|r;J;uFqmlrFx)P3Mb6|^ZS9~FzmN^e)8_OHW#0>x#g zz?RT~3UQDzQ#it&=M5@8-0L+@1GMjzkLC6Ps+J;>+;1q0=un^G)0lXTv*-nOM;}cG z_B?wN>7NctC(alH+r42|9Zl?JvP88;PsA?>q7o}gDPYH1$EUzkpF@;px4UBuo~@JAS}C?oITHBvV<(fr}TuT~~( zhGowJsVziT+k=YJsO${eMMX`8Vuj4!mE}!b!cNR}gBeQa`_<-D(UDefl=R;R_;TaK zJ}1(2Ye`H$YLp>$;{v>%e|uBsG1dOeS^7or_u-b6;SUd5>Cjq6%^CpGkT)^zNv}pc ztzDiYf~oGKs2&x!?211DTE@{bd0JvWgyL?%eihQHq-OGjRDT7>?N{xQU)fP5xA2Te zeqbqkWFOgaEe{FWu=$W0=aoaEo>de_FN1ASa;OxKN|iKgJhjChDe=$XWsi(BS+5ZQ z)T0Ln9e)bzru8{+^OXsCp9JgP9hbw}quNfmWh8CK0~PAQSs!JC#rt%lBtU?zb;d`i z;bKmvwt-PY?JQR2?NPY~yCYMT9C)Yf)z7E@0A|03T2`fc_csx&qTGV2Mn-*W=CSpp z^ttfZ%BpfczS8_R3!*DRyHE|I1CP?aZdBBThQP*pmHb)Y?I&2%8rxB~Nh8Jv;ry$% zg(S8##88bDspa1a{0*Y$+McAgFsGWWwg~yrEl$`aJ)5c!;{0g{{WU^ zldTcQ3t99p+xUD%$%JF+UTmXHv(cl0txr~tTg9FO()Ctvv{^*zdX1u$MAG+8TvdA1 zbVtNLA3hU7;N5)O>CpwVm5$NbeTNkA)4u1kl;S5jt897FM#$WBTKYOqL-d+(m98%0 zNvo1N5nYzNs@fRI*&dznt3cE5Zf##yi+oc|5d&}ye7$)$Bj@q8(psN-=w1}F({#Id zY++cV2tHK+RQJokl^H9S+|wvlOd;@vJ&ZoX(I z=PEg`q@jeTI##j6Ur8&Ti)o?U%W9rnq(h7a7yt_7ROwQd#~;-%u!F;I8DLACbgso# zowP(O3JG+HqQDI2J-^7SRcXG3yadMFcxG0$-@W+lny6x6rE3~TE|XfBR{AXQOP}7K z$NvDWU305WOIr~vu1M)Lod!#UAnzIVCcKK%ryhq@EUrwOTJUrc>28h#Cw$ivsT$tx zk9M91>aq7vkpBQ_FACbGpR?={tVnl9EO7vR`4#dQjK7+(3ceyTT)Q7QT4}ptQU_}7 zjUH3dz*#?g9w`kK8rvfj6jPcnE%+1w(3{0UMVmOv zxT%mGhLavtQ~|5AX`l(?jFE~Cab6R$fHQ7jA)13@2Kh0X0Lk+PaY00iNcvP3A$*#d z36LCys3e(Rj%Wd`DI8D)WvBp(O{Rbn>~D4L!Y}!!m*(qo?8@HrJ*3TX!=oM+^~_Hb47yWhR~kW z#5`G$I1~XCk+P0O095jeH3h_uakU6FS18R9ZfwzT9#o*_gusqPEI6hEB39k?q}Ue_ zUksiN*F~@lWClPw3hSp6IZDTo_}k)z--sr46CWohfNP*s{G$r#&SdOb9HR)=o+1?yBZu(w^O+Y}IA3BEB+8KRul$9Tmd76eTa z4%Hw!IkIqS?mLR^BO9o=Tv2+CgEWLHy0-(RG&D@Mszo6dBZG`pB9Y1bM)-55d_eH^ z)|IJ5B)6RiBfm}v^u<(TV>XY;Z`x1xY1TdsYxBt*O>?2Ue(G#zV9x;7mnO%7MCR7y z?KsbBqZZBmGgJ+QZYUCFoy!R#;AXUILul@0MGHOJfVdUo*LFIhet~|)7Rs7X9l)-7 zXG9OMEs{uYQIHb3uAYOL($93hPs};33{2KQ=O9o66s~d86ad?Z(`G`70HwTV8);a9 z&k8ho=|zCexzppAV2aTJ!+5Jf*sZmHO0v(Eel}VCo?y?w%~LUF2jJ~uD+{?IY+#yc zf##pI1hHS-F2m_vX_zy{Z6y0U@_4Ci4Oq_g`q66;OpWDZiehDJ_49eh6oyA-5C95S zKtAvUr*T-$+n30taIrd&Q<|HFfni+dG`0&CZN?jlR|^;2JjDRfaTUJ9m!LzQ-^<1+ zSO-Ly28#h+NY83j7K9sG1)E8AXfd3)ubRbfcTzuJ{{Uo*cD49#Vwl^|*4R_1_YG$T z(evKDj)rHF_-gCyo-w;o(T*#b?lg8gpRctVprXT=-8PMbj;>Q z6K(U{b*?6KEssdhd?A0U*!i}^00&%A1#zthP2ins$nBVXr1hsHYL(81!9NPMUlZ$) zTSnVs$jf_I45wpQ)4BBCm-{(e+gPkGA(4m~^rsqz>WSvQH~0w#lY9$A3J+?g*_4%x zGw`oXMH^vU{VNs=V!hA8>rw#nhCY-OcYh5&6xho;tHT~W#bYB_Ee~Y6_#>j;*cfHC zLU!ZsirSXpQaT?Re$ambr0|xZE#-_q==sh7uUZP|@#@D@&Hfo_Z*h9+eo^V^Lv|tx z$D(Rl6Irfz{GzmrHEhbTuzQ)xmg!S+t|qUb6@i;QD7akWC${qCIBL+rvP2&y4Fb8R zV`?3SYa;H;w{VD};YZ~_U7Wr4481E+70ZQ>iU6Z1-%!vGian;aKqbH~I#U8$Dg2ei z08K5z=71tB8<9{0t>q~oBDm&?iS6GAHTf(p*%`QMac4oL@dgPs89DMFh6`5moFN8sChzZU;Dw4P}=l=kRJ{yC*of|SKMOoe&u%ST@xT>Om5onD)HLCH9`3wWj#nVpS2|l2o=vY zb7-^Jtu=diQCS%6DEVa%WWC!b3K_$o{iVkoX0W-^dPjsjJr%r>F#Ft8us)yoXR944 zA_V+^R(3PF?KWO2g6aYL#r*4dqc}5BopLA_DN`6U2Pt9_ zMFo~)%QzGPw`pz>2hxBoINY=WpDLAV0H<>qD?ki=6EpC61}`0h&3C+)0c1 zha$C(h7LD2G{83VQ8O1C9+&Xm`E0M#(3(`8nawC%`X<*_h3y09_(gkE=b_HwW8}Zu zcHn9nmj{KSQWRi(+R%vRQ7kw8fuS7j8=5wt zS-|(JMok?uo$3vDLRLBDO)=$nCjz=|an6ib#kax8;+xdvmJx1C1VP*mDqLGR-VQ6F zuMU|TMa>+hA5vRbMYLF7<)e^G1eZeGa`BFTIynmZkTgi`pX)~>C+ZO}! z2^PGB+tk%^G5tm*d97n=u2vsWi|AKd2!ZKN#jFfcwTmCw(YkOyl`~(eM9aCgj@^z} z3JLn02rZs7C;_S2oX-gzjp))jVj0ipMVXYasEw>)2-~sEWmia^bm_)0YR5<5J5_0* z@{V?1#+Bz`s}1rr`oTC z!SRH7u|I`+7#+_CF!)c+pNTK!c)P@-(($WoYt7j6afP~sCwd~n#Uu0n%YB~xmGrBO6Q$x9&LAh z4`A??n(=BnewU`h8{XbHQp!HyuQAH*?DT1a7jUQrkEv{PIzR4y;Vbl{|f7NF_Qad4vQP7wE z5*ri$04?ME>o{X%A!VT$o%u zs{7JB8B|HW=el@1K#t1Se9iM7LV;d|N9_I4QK5a!9ZSS9* zD7;4rK3Z_u=qqQ1aaSU&-Bn=Kv^#BoPjs0<3^^FCIWKz0kvue+@_)vkhF{s5b=TOT z5=Or$kE!;nse@dXN3(*f95if?kF~owbX`T#c-k<1SG9S{)U=WHxH{0Tt}@!~OJrAc zUotmww62W}f?>yMRP8e68DsEbyl`8(p za@?Jo2*1k-E&ggZqdj0&*@!QYC7IVHEXG8juz8EyEhU}bB60) z+nqI`6I^xPK z?kN$&SLs4^4x$ro5$x)_zEE@{fhbyD&DDp`Yh~vj<%bG`T zv!*YV8T6v^AG4+cD1{u-8mCDTDntirSCOiPB3Q6giov(cb<;`2xd)-IT9W38 z`Nn$-Nck`JsaIVx)c*hvnZ;*Gd*x*hmfok%HvRKoLkI4kuTXbLLZl7_Q+7Kdh(~;_ zc^H|T7?zSfhr&8J)b9K`_TPRPGGiS81a+?xjRz*~eodWX{gmO#^||-1gK)PNk@7fD z4mxzM*H4lseqUWzMuou}4NOt!)@zlXAMtVgy>`vlwsT)$fc^>cc!y~t)x0q%xY4a# zbSNw5aXM6J^{}e(H3>j^3g*=7e2MH${*HQqS^MW=l>zGZ_`w4dtusi~poe1DvZo!f zQ}3H|VcRW2_Q)_ck6~EJOz)*r4!ad~xS_kVWG#%2qP3&UnT|4aoHS>P{8sR8mw(|K zt9zG16-ie;tF}<))cNXiP1yJrbr9Z72*%Z72h%vOK5J9&vh5K2h*i@V?MWl)<0F~8 zPbyz(mX3t(LnKKu=CWwfI~eyV<&p(WjY{2kq)`N_7uuw&knZ%TV{XAco<0 z;-Q)gGKV;!i4~Y^q%t$f%>Y-qP)=wpNYetd*vOjS{a~q(j;~DtP-3KJv;%Pz2+%~p z;(!(Z0M!}*$i5sLPzN)pslnodXzX*7F^WnGEWf~O{+lAkwzqLXehAOfpA3tNfdeQO2pW+8h|ipuvl!?%FDo_!DAxgFD;HJ zu}IChZQM~|+^-t#G*~W4aS$oR2@;B`xirfiOMnvp09WoAQ zT5YaqDIB%ro5`SMbW(9Z$>WTM_Mk?Uzd?>@0%iFQJJi@4HLSH3w%SpSMOP^#T9&mv z-bVBEtx0e)_sSR?9CxUYxeYA}@$7EaMHt65ie;U(*N&u?-^w__6{?V4$2om>_S7|) z%odMh5m3=_KW6?OpElp=UTQs5dWM%WhgCSOP|l@3=N^<0Ngc+)*NSM(6Qs&%2zy8z zVt^nv;5K?t1(dZ}6R%nTf=w{5N&raUG4-Z~i;+1TP-s|5w&JY-tuE|V=@}dc#gB(N zZ^SPRU0GegkGV+smp!Oxmt*pin@(sVBju5{*CjKq2j4y)(j$0- zvVuqkx(6}({*ARauK-quG0v#l#(Gc&ed+SsGyuqM_a+Sh0|u_d47y_!eQ2;5(dvRW z!y2F=)YdDwdV&b5MV=e+d*Su2xnldiLOKywA`hRX_(eXKA(}ah^T4dr1Ht|{SiYHg z637VPX0+!qJc-oFxw|i=V=91XS-z~srJMR8<=sy zrR|6)YFB&P8RitRNX)CVlmD8}4T?Le9-q>i)pqp_uJbH5qu zN3{`hFwWkBwDzP6cOJzDt!eE{?or<3pr`EsO|Jc_CsRX1uJC_1)|-djkbg??F*}`f zkJsPXbv)08`h1EwAb%>`2z4XaFQD_FJaPX3)~{osn>g(U!?4GDHQO)DSmL?u?-Y%> zEYt>XUX{@lNr`l=1fz-?1V&)qxoRCSxZw^?Hq?7Da}{qb)RGGJs;5YaGsr$LXlpiy zbWm~yb#hqbl#hvFwsqIzIOIKNEy_oA;yr3r(^U^;uR?)^L*^few;yS`mFuc!GB_PD zmbn=`R}(r>^vA**m2EB2wu}K=K@}xp*Tg>&t%PYB6y1;q6y}EpdQa@}aF;i+%EfmO zE6J%F9)ub7L@ByG*&KRSa7RO(k3IOU1QTh}ZO53SitLKH!8Uj-8vHh4mV9y2iA3j8 zE8Y0M3=c19?OC)^k?J1@Za&z2-;~x2@21agH*v>r)O4boG^?@T{{S54wA3_P#gHi> zc>Jr`r1U&$yPp?$B1N+C9njz?bAe7s=S=RrL#W1S1x#t)jL& z9cB=0pN`d$N0RCdvgO7)8r06kPql#EDu`!V3_u{y= zHa&7pTkMvEH#il!i)I{tD!BVb=yEaXQ6ked?7VHSTu3e8NhHtrRhUm}aLWB_x-vLwBgpTvw=73Y z(~~MTwT_#^@T~TkIId=mQZ7wq5=F=9P|>12BjKg4r+YV-V*@@tE1E{BT=$(TMe!b; zZX|fNHaMz@a#|d3jXZU(=?g4!i~*5dk=W#vPoKPF;!CYgMrltbwPaZHyQVTZ+&quo3^WvS{~I<=fE$8Q)Fl93K23(MaWX)Ik9*x8BzuE;r{0wwvV0@Q0kE!asSpv=N1gKR_OkIrm zEPBSD;=9AAsKNWS-3grYf%EUiU20kNNkqMCXCo^fQe;=>70D)Uku|*S3jzgZnXQ?n z_U}3^xUEwinEM}|LF-gx<8!I7gg^sTnev)zeYb(MB)JvRBxjH*SVt|d-{sEJ`BfxCF|9RI z44jfG?2Z=NdYyKctgNw&npP)rTid)u#B{;MbJkwr;o~nAq0)RF)Uf9#{$FCp=UxQ| z`@`e&708vmkGlLQpvU=Fr&Z{E7F2gu+*^;OcvIZ_UWYy6{EGwreJj+#9u_@&r{*uk zTaunL@i6}Yr_)!B-5i*B{_Xz&T}~p#R=9$=>Ag>=qP^5yFc70P-kH$ZUUmtZX__LO z)8HIcqor;@w>cETES}wiu;PNvHL_Wh4Y{n^GiyTCrkvK($F*_DuRf$tBNH1dpGy1= z@Voy2!dYpnH{DhFPB^YMx*wlZ_iDMHWm?JRDiPF*?t`*D95j@P?ikjPcaZTc`D^0m zh_3DYO?Z$_=ogFwU!&#ppy?~4iiRKB$n!XUA?o67k8wYz2EBDcpJUglgQpEn<3e3q zTZLrA@&@&;TCt}C$E!w~)gIIE-@})dmtJ()3H{SwDUYpBmdkUFlarl~QI<7GvtKV4 z%_G^X1sZoelj6O{{7P;YY5CU!`B&6nUm^2Uo0PeZU*O+^hM5J%p(16E-8ii=l@y(W z8(q#8J!44K#1}~%zbWV|$ejk_+6)a<4bH7^(J_-=W2D*iu*IdJq>^ugKGnwKX&&@v z%}nwyi;uTWz5G9zuWFybd@ecOZ1|t!SQ6^e6&(Knda|Jv%zOEKHMz?8N5PYLfz!)9 za5G(arsEwCM;AA38THr0kA@bW3(({8P(9>`8d-7A&{u&*jH^_<&zO|7w^O#!^cRkF zl0-bP#(h02>!Zs~7Dpst`L54Tw6WO=9v6;%tHPZ5neM@ItDbrBLs1$!t-!-=`^}2_ zOtK!+MswGz*unU7sGWaO`(uB{x!`(cyD{t8!pKynmZ!8sWQG&4jz<;ouA@&=&|I@x z9zo*o56g9^m1s+#ZkXb}zYRK4rD+~DdQzsYjtAmD?E9d2$4r^t*=?^ve(qSuLyjxu zvlzqdlZMAi4|_A=jbp;|Xd3O+o8&Z3Rk-x7JUrR=*c#tiO60M@sYS@KOwRtrbr`6DWu{i$#iF(|cev2W6ioa}Esez-< zElw;=M=g(^;P`>63!fz>ZQj-Oj}1+ukCfLs=ZMN>9_WHQS2M!WW(_hS;!hRZeE7M* z{_w2+r%4S)QefX6H9ZISdX#5xO~h7j2~VM_p)RL8u6$7%_1sejA|*dC?OG_PXpPqO7I|)= zZ@PI=Ut5Eqr%hb(;|Eero^vn9`?W)fygxWP5>T^Ad z;irlRi>(B*wq);Kb~2Z-Ug^nBlTPgPMr5^*Zqc^96?wDMx>3~fKZ^n=v0`}(0!P*C8IOA<;ms5=8 zW{Jf)Mpj48y0?h+sb_!qm`TGNE^F)~horh0^3BHgAh+>HiJu7s*H<|H=6I|2SaNyd zTW=6}!3W&F{4-D3Va>VY)X}y0gW}y1-L52p>R96$VA;)KEFKzqSsN>TlbqFmYd42W zohP%AX9oi(iu0>sXwi2^p+^l?vbsKN@z2EVL&aLWw=+!458PAHMR_fr{X8ctyE*MO ze%HC&){%|RQV_Bt)NvUY7!`7YNZD1fr{NC{U2F24ssvmO-9Q!NSC=cX;^HxK*IOS) z_y^%&ipJHpgmH$(Icj)%9MZc!FBgk=h+5~+z75f(v+}Yqsr@V1t7t~YorQzF+3a>V z0j(3Bg1%0qedF$ERsG$JUlHC2t=N(P!LLIL3$y2Q3fdnn>pm8X#@`W^Z7S zc)YxdIAqS|6%LFNDdDO^sqNkzQ+s0+o~kS6u?<2^9^MLsr@5^>0yV{QMt40*)Kn4R z-?Y>g^L#qj1lGxLqS5|F9r3{j99OAH9%cr+Rh$pZ?+7VtW5_#6QhyWqS6mO)?RcZ1 znL?ZoN_|XiVhJPTikpMjjdK~zV&rnjo-6lWJ6D}m+`-FJLe_lDq?~dq=CQ2D68+n= z>JNeQ1-!FEz(3(vmc!aJ^L)-+ld?Xy@V()Ce0J?$PdaKSoE+L$M?*EiMBIB35*MAULg0eQBscXxBFipmE-lm5(PAiS}o+cwgc0Z2~ulZoQLk20B+G zmoq+75nGxbXT1C(_;0EBem^wsjJpi+SxH>^uVmuybLlM{lM06GUUhVRj$ZCr)MW`7 z=e1`>2Tnesgin_KFzXk72l1Vrt!u;*wqHFI^slFcm$I?>om#SuMJ2aWztVmxK|Y%l zziWX@D9Dky2NlH_DW03DI4v1q{8x!ni`z!`&2%g*SiZGaqbWbd=H2q&S)Xt6Yh{Cn zQhv8tbu+F$Gu|uhjK?N=Z5&rjFf^odymeKj%Q|1gePZ_ZHfR0bc|g6t$gL%Sq>z?2 zyJxa#I)|SCC#`&qdR&q|&jE{bg_)7!by=4uwRKT^sQJw4^E2g7+Oh<14M0cV^GD%X zL*qGeo}Q@rhRHT54bWG$2V{QBgy(Ms9l6aW4jm&UVN7} z=f>tZ*A~(~toSqV%6|*OZ#683Wd=A;Z>@Jyrn(=N;^OMJDKp*tD>Fy9ebqR?sKmh| z(#sp9j)~l`uRSB(obHbi_`N!|xdH>7qni5czBi(L_96S{srVVk{3CXr$zMB+U%h9w zg4P87Ak@~v%17O|YLog@;i+@UAg?PP1*m*V)aKMAMY?dH^j<6K77__;c-+@}j)TV< z!-5jxclHF<%MDMeIp;YprL^&evA>z;$Dk&(SZq7!HBx=f!^2)F)SDZGhmYdTaAGi& zdKpU$7hGvJV z8Hl847Z7=3fHPf7H?;srFq~FDgfv#qR12oxK7?E0ju*V6ah-& zAO}hS!PO#NrxY7UW0JhL-scn)LaXIe7CJdI#Y}|6b~zOUjstM5E@@&H`AudnP!Eoj zn+{NhL*`R+Hs40I1uA2BR44GyyDcxKIN0J!k>I8UT>S zSWq-9HrO;2SR))z1kk2ON&rK_pam_)C<8w3FlZVvCx;C{isf)<0Ns&_0IbME(@0kw zy=qxw)`S4xdyZAfQb27%s)bFt=AS7XrKt4y zbw5g-1W#k)I0(TsXad( zQH*+#PU9heidqVsi`4o8KgPA^LK?pmygzVuE&Mybx>yQ)=npaY!=P!ND$*vFIW1VY zIU9%cG&!VKD?dE`Cj2ZmI?S(bBHNSqTQ$upsSA;uOL%fyct9$DD$UeID3j?H2Hx#K z2OmsSx`=$I)<3gfh@jE6D-@e0e9(O{T(R8fPRHp#h31a)Oomv9WpxB&oL5{?ZbZ8G zHf`&Um9Hu?=REF7Sy_W)udO>HCo`Yd^*6Rm^v9|H0PE2CQJ*o)!Q+&MLLX-y^^>Uu z)M=L!$B;-dxb;&W_%S3*B3O1wLjX%+3f zVjO(e5wF$pSMA~4EhC-r?~E*$OmFn4_xc7DO0+wWl#e;mJVj-zZ$!R?NI!&;Qmezc zqUJoC_Kom$wz;4Q<7N9wpS%b^%9N`_T&K(**e`6`Ke{o;73WT>xpp5FqQbv0#b@o% zSen|#8Q%@kk8Xm;W#Rt-4%swo@&owLs-q@~zAf;~Fedny6@Jvt$DL1LT2i>F_QA@H z%@0FF!x~=LIZ@O{;oCVAaX=5yRVto1`*Ifc6*=c5RQoYTv2#@M=8I_-Mciz0*{ORw2e!G& zH^ePA;(U3EeL1DDbC#RMmN*1RF;mJM#k*epaau+MH*?Lt zC2O|BM7t!8R=QLUcF&D$j8_`-w{6wQD4IcWq)BA@N9xA&1WuoFa5{XnD_#JW?hTDPe*!U5OlXcRVv)xQ*^GtPW{OT+S?~ z9kPTSDtS#)vF@J)q*!d^8;3Q`I319ZJzG=NcGoIfmc^^6fS2 z(^f~9QQY}b-tn!kq?J0BHLZ*hcU18Vv!M>yrjfS6;QP(`q7PG3auMoUwEjhdXSk$M z<9ejxOUB1@Nk+sJ+BzDz5+T?tFc<9sXaS9LoYPx@QfI=}fL4zCV^Ba#c3k3uk}1dv zw(162w!9^3z_ZlikQO81YJhA+iC61fl?_qt{{Ri8&Yh0nR!D5m@y@e=z2Ps~Q^j5zoG12!T*3(XT;`@zEcJi*Xpa#1lTJTmwPNLQl?H`7f#m)x z@u!O;nci^mgU@=_CJgY;A9$lv)hF9?AtJM?%_*Z(;hb{HncKiM$rW>X9hZp9TUhR{ zw5($z2Sd~qrX$q<0F8Oo-Ho7+qdo-bmh$ULW<_kN0=Q!r`)$ zV!L3@dFXuE;~xp>@Vv_$2)NETuIMA4N6&sevi{9;zyr4x-34QUqCBqX2_`%YeJfTt zkAqN>$bh^G=aIZllT5t(S}AW@=bN%RPpv;@EgB!NO*t8C59M4=ZoOG^; z1exLfInm?Rgr6v`#B)iXE__AOKe2Av0Klq7XP8~fiH}OgZ0L#|WqQ#v^c?>Hg>&`?;V97JNxS7fOeZ>02TsRQsm1M2?NH z)n-W+1As+YEfO)v7^ibF>B`j#zyP2?7jecqQ$SA~dj)87lE(Nn(JdNwspkMOLo#Nq zm6wyr9q2|(^&bGm3tT4ZhEhF>D_9M#HukMzVI$>lk2Xsdw080SMRp`}o;z=y@_+Dv$zasm7$&TIjF4(4)`Dy))EEj^ z4nh;D$=ZvHk&lB?VDzp>DmLtOU{q$1xf3RrRm$*bobN*(>gdSC)e_A4n=My!o^iQ< zOPHXf*@`xr?Yy3UyIi%OxX&{b&cooe2I@?Y;ku9c*Mo+4n?4&lR%_&a`Qac{e=p=~ z)U32VhbMMB$UbXf>qdc^1aS_k%7{{TLf>)=ldFka$6EdDIX{{R)=ia+b{ zkI>fmvpQQGu8Kk{eQVE#=d*?{&v>9>y%_9z7cwp;J*uK<5|Naevq-UFOp1chcF}%p zP*Kxp(P8_Vbq*y*6~j>+6`^zAJ_C5gX4Wk<$rO#N$K)%Mn@gXYW4+qEmOjJN z)8&tELB>6+rjyY0aCWhF>M@Geo{ZlU6w&hk0LFg`+{@w%w}LidE-+MPzeUR{N7*Z$ zO$0Xu(3+#=9vB4W}UhQ8oip%XDW6i@<iA;NT73%=}NTu(ec>oQKc4seg4s&8E*~v+FQ+_L3x*4cRlK;9{x#D!WX{hngtTU zk<%5|&1QYlT+tnCZYvinmXUF7!i>>&K2gFo#}p<@RxMf2<*^;B3X0}!*sbbMVejP{#FA_dPJ3i7Hyc>1a_ zv=1uy#XPavEMOcqd9S0(l4pyFsmmjg_(R~>^$XcA?v)Cw;Z^Iu9aYkek#w4#r@1DN zX4nEG!1S+{b?RSprBaNE4u@t_9#U-{)GS3y?riAAqs)FP{5I0OPhewPWfFmn$}8Hz z*O%I79Ml$!`KMg?Z-3#E@Wvd(r;G~q?w+Tihs32GL$UaO@j~;%FeQb-D;U7Z>?;XV zjZMy4^ts*7TbJX0k!NMN+y-sEyPDy6Jx+L1lacf9#19rt<84-HK5w%hyBM!phq$bd zUNmDBc?;o>hVyGMi9lse2hCSf=2FH&oNse}1M>jY_4!}CnZ@kk z7@dc~%UL1t%I-hLe=6~_TPn657W*SZ;t=1aGLK4(w@H(UYUj?);p?qKSy+=lGXY;) zDvB`@);78HUxYj(27{zU7nZFtQMs|rd}b>W=RbE~r%6RqbCm<%70>mFSnKvT z)XDKqlMFV8W^39~F)sQYctSBFW0)o>Ja>0Si!4o_Bl@x!WU!Pj-; zqsQ{&037xJV!NqPQ`GRW7@2cgEb(6)d`^98;91$o7il5({uSs#k0Fgk&jYvkP2s50 z&MT=G_sqOzrx#VlSm(px)kcx(4X21xAY!~#SDBvVuqu>pa=s?<^|yyZEQcfxE3Td& zn^T5ZmY*uv@V^@P^7qCp_V6h%`T<_GI%**)#u4U;#oKrnQM<>L8%BC_#Z;h*r;3bL zhF6O}XZ=$8%k8|9tDN^0gR$u0vEI$x@cma!yU?{ZwwH44;2dxTZk?iih8n-O_fJ{) z9r3Mv52@SQT}a<%ko?){j@9SYq3vDI7afC=k@iN3@mEdNESBclWq6yqMR}x)|Ub8t_d9TRqY~E#3(bT@SlXQZLd_yjGs1oRLQ1~KDIGbPAArW1Yfi0 z*2>+N74o>n?;gff)O9xQm^nDEI%lDYl9A${9&P;p00(O^@5h;GApZczO?z~e(coaE z%ZNWNycs6Mxr@HyNB#6_ld=08Wq2=<((ElYE@M8Vrr#-2=5n&M(QU3t%*~XC5?4I6 zJes#txuh3jC(u`ci;cq(D?s{>;D%14XSb#g{)1h4Qa?P*zBB9J3C5z(5!8Q;eGPES zwl^;MjDy8;!*kNC&zZg^*eUU4)SEKErYr8Svb_X(>Z(r6^}hgkD^8C@f@v+(vyI&= zz{XuVWV3>K_k& z8qKRJN{CsT+kr$^Ge17eb54CCeNFJwz>w&W#3udoC|uOyN^-rU9@N=t313$H=je~ zUypwg2Z%hl;&32RKmhYwO(TyFLE=@B<2HUaxQT>A4iosBrA#B#_i11!1_PVps^uGhySpdljXQxVvxXDQRDmiwV7q2d((dLCu3c!vlo-5ibnpAPY zs~m5{4;VqBX!lpoAPEX*2f3`Jqp|ZC%CfI^kBa^!_~PrvH!^wW5GmmSu8MK7?PSA4@8RK~eWnn%#y7SX=hZ5f#W0CB}DAQeCeq@w zPu-2aoL3xcI-8!YY%Fm58CSj{io(`claDM{WH8QhRy@~;<mbelHCu?0ot0cRW`|OSSfxX?yuto>#eR7_S2XoM zM8ZxL6`qXI&<^N<>&1E$-OsJVPfZOAnLiHIspz+JHt9DSmS~DhyA<(?%-LvGBpxd^ zZi=rC+~8Jap)s{iX)%=LaHlnkDH*qyJB}(OY|1P!b5Mqbw_(4U4D4*$MmQpZldxVW zD?s^-JA`FCR26a(%}J7KY0PpeKMaP%Q|-S_JkOIBLy;*pB5tcb{rQ zIc-{9xu9n|BIVko=tWtVZA&c>Mb?;$Dxj~@lNu{AG2_y+Gbcq3oOKk_u*R6NgXvPx zh|q15rAQi49It8s3F52<6S|-VIuq7_8V1z>)tdlK`R9rN1UTtH5~aVb0BA>UC^atI z+94y-fF-zvQAQ{Ni>^%oPXg^zK<-wQbHxP$W&2PBS1=L{0ApRpwty%yj`RTwb>e^{ zahd>b06ZE@OmlIH5F$L4=9tF^Ip9zOrhna@D_EJMlh#S!(y8nW=j0oY<5_|t-c+Az zXk?4?(gEl;&;^B`rxXFdqw0xlMpl45Kf+!*w#r2**`6tKm&|(hk1eJAO35NHKT57r zZq4MS#Th(Nsj0?59>SN_2V$MZvwwBgO}|4>@eC|Rm->WbB6j@hKCr^Ym^H0h z51DR1IzF(%#;vc5b#z~tp{+f;A1MvbjI|lEjfDr&k8Z=1=kL7x!cQ%lkE}4|Io(Uc z^2|P1Pv=tWu;n=2LrS}~Y%byX)_+)IRWntYxs`|?xA)hslxXZOQ3(z|N zPCqPE{b|U)Q!4kt`n}TT<>ZwA0JMkvDqUNLDlsm;8(rK6K2|V4x@h)>jtIhUhnIo= zR2ftK(kdN8K2&wy9q`TWpRvJ`CSHX&tz$a^iajGv@#l*)XrqqP#CO);6q;EJG5eU}~N-@r1GE_4~Z~hR5};Nn+%U6)T)pwXb-ROli^{JfPrhJ|PjGg6AWBPD57#;s$Xfv`@2UzMJ6LSdW;GY*%GGPnj-@J3TkS@uUmp z1Cidk)p2_w&Y`3&wUH49Vk%xD;HflK@a@gkoqrtgw2(-h3h;jdD|#4Q z4%qM$wVU}T7+ILW-YS%CS2^iF4E`lpX=4)16dKM@HjPua*FG%i zV#&7ODdW^qj0klbK7aU?>w$V(&pjav>&wBMarVSQ-j2GfW2yjB!!~_Gr^eqox|Usv4u& zz8~(ih-Z*%G&Xb|AkeIQM}c@a7{Lb>(_x)gfIb;`T_#5IQfK3b!LCV&_N_Nh()73h zg4zgTUbp~pLO}FKlYDLQCWSqOkwYAQXc#18`d3mgXT=^j4JFp$(>2XkQ(Af+rSO+U z52B=<%}q6PNf-Vg*gQI8p3BX3+Q`O772tbwsNaN9k{kSM#>5>|(f237FNIgv2?gfE zjAx~AQd*s@Sstn5%|7Er(`3q!6%T6mDaS+4tD*9Lh;?7J>JFrZ`K!7pNo(u76*lG^UI@1Kt3Dhke zXDg1C%QLP5L2O*8!Rm2bvwWu3K7jq6J{!;C&l73((ZpC5$>O}}zGgj-)7u|{QE9#s zDX=RnSm}<{=uoF~8MEWxioO@T(6xE~W+8bP`q!)M?0NEMo!)qzY^~j9IU#?gI}YcH z>-v}5b%k}{2F*)$1JiVQbEd)c6`0HNACm%rIqT^6JqD!)O|$_ROd0@wJtzUYMF42& z(FP)bH=r0?Py=opFG>KRFEjwaO7TDoC(M)qPs+id2ge^Y7?wqe0Gg)HEi`5{{RZ>xs|SX;zb-%U<%4i+A7A9V4$pyf*EmIB2iW` zvRbvVnY&{#0CQTR5o&2NwP+bY&E>Zhx@SCy}R%2F51C^6SCIvDq%dpMqK^A=rD&W!%R7Wd? zpiU^R!}p(BE@086W&(4pAa|(uP=7;S&1a$M5;*F(!>)xSWrUJ)-C)IVf(@{o+~I*c*$JlbNZ<73VcF?#CK)|O{_XrSga4Rjn3>gJK9ReS$Xrv zUR6JFo{lTuf#jbMbk~S|qo9u@dso+B=NZPw=hf-BPUv3){8qD`*xOvN4;~aBTIa-I zWhq`KyN0UyE@gaD@wKhhoOZU-Zh1*kI#&2fE{)udTED!Dk<$D!(!%Jr^1u4Y7d(A2 zT-e&JyAg+%wX|94_DhR@rF^wVsroh%T9uC{_{kXl!PH6q>i+;SUdR(mqXl?sR11H|T zW(T^Dq7Rl<8la@IZw9uu`=44F{b;=NC< zeP!96WU$VP(275Zc1d;iUob4W!QEFZ3%4=K@m!9@+y4L=LmV4plXErz#b-+d+q(#3 zUd68uY454;mS4KvD-1AAJqKOgnEpHP%qoSvphFCDI@ewbyQ!l%@l>UTo`=f6 z5aCNJ`-s(c1U)O-QRO{)4b+CGHK$rjdff{y4n3-+c6LcZ2WR0g2)*U(W=Qc0_0M{` zaIxd!s>)j*Mff+tvCU`4%BQCouQM5o_Hwc~VL{>}Yn_$9gd>h1j0yLz4tV`;T`Zze zVqW-SI|v$By04(Br;K%7ncH6j?AI|u*Tm4-MnuF7zPwjV@UD8JnvH&48RGsh@jPB4 z@gR;qaNAe&uc*S)`n@oz2i*7X1Zn>OV9_9qjB%RytirE7R?klZPRN^D(Gyc4TkiBV zoeW2^)e&D0%_Al+09e?|_K27FqXZi3`uyjiobb*OxmN!G#t`VzI^DOF7mdfIZBGX4 zE5P&PHx@}3jc!wQXZV5+;P;;2Qy(<|ns~f^`#pq9c;X7os zB@+W2iq4)kQhJ(Jz^F#(`P24*(lq@;!BSip)Lf$-oN{Z|_I}e-p9fM@<*D)3kKx;m zS5#P)HjT$M=GT*tx_!*?T-24%a`=DX`6Zegc$aQY3uDr`*ShZ`(2^{{RVm zKZe&xg$e-0arCa=tf(yy8kHV*soLm%7)=%Y)yD$7`q+e)^P1`a z{6GW0+*qacRmy!;;Bn--McGFHSCfri>CJ(4p{jOzY2rZIp}v z0Q42j^~TPJmdQJk>OLQiTMr|QV}p*BF|BqwxPN66<}Z%F17o_0@9Y*`(G0Lb)cTL> zUZfu~I?Th@1Wz@y5Af5VN(o`!aV6 z)YU1ra!&_^anT-w;n?-PV3>r`G;;NgPY3Hx?D?AS4~LUT`cL5J!mC{-6~4J$`iD`x zaJlrZO4WH!9oKaou6r=>&9vw<82kl!GQ_lY#{}vtm1Xcfo_8!@FzLl<9Au0om9$%o ze`G?cepDO^i9c%Y3Nyluxz{3z3l`#`Q?R9s=5{`6{iD7w+Uc52)s!y3ZIu}mkC;cs z2|voap!rePmP6Xtx$s_vWVW~S$b^-U_%FBdHjKD^tFne}hLeWvouJRBZB z;5E~gyB`mjYR|8{DHhvoV;TPd6JGsxJhd9PaLCQU`D++SBh#w}aULT0ZyvRH*BkM> z1d8?Oo1gb--Uemix&JTM;C1oZ!P6q$l!c$;EOr*TZED%ed>Pq zCcO$$_O8X@s!EK~x$tL-ygM$1;)}a$WzWyspcJtwK_792oVj#nzlHoEJo=PvAP*yB zYGbW=F^@Yrv6!xCJH1b}eie8&D=jJl>K0Iga%0-MDlcS@fpPY;J!eYr>~XMQxZrVI zH8C0<{sSwtu3x{@6H8@{yN)ZKoi|zB_v=gQ(J{5Z6G3Ti&QIMO*KIls`P^13pJU{I zjXx4a#q;cAv=e|0bUVPph7sa7K5*3UuC;sfJFyY+IO&Q|jnA{eW&XwU1)4^TK~e2p zO3^-&oMO$3AYIW_bLl7BHWLNek6QLzwspZkjW|gKKuyRrUhpx-bXH5N;RVSk>&L_Wq%wG+zd__O@-S--NNB40s`~LOG8r<3+pW+;< zw%b0Sm%+bkm0CZQ1~9zfDI>u?E_kj;3qb>63mwE)(qZJIW98I+r@Tk-ss+_&nt$O{7&X;e zwWDL2rA1Rt$G_=N`F84~wS1L%UPsm7W9|@Y8d1A*zmy{wtEE?w-5KI=-0b6Dz{#TO zBJv^v=OhaD7BAZ_R($PD1ED^2&DeZlr^He>m*?qRlEGA#sUE!?LX2!$ZyD+FaAg_v ztp1@@^f!5gVYo-D#jKK%sB|KeqbeG+CtYF+H@+4~B>O_B`FekaTf{1{<&l+KJgG;S z9u@mbd?mK=kAW_*vt8cT3Bm1T^w2!5C z(UJPD0S_u~W$k)h5v%u!iEYW{KR6?#pas zicHYwZ$`}4ETm(~rJpqtHW85Rs6#|b3Q0g&8caS2yU9CkX1&d_} z!lVuBR@|c%kR1-2{-_Gn3)=_>(t$mS&UUe&7Q3n;AI8Fc|6uD*-jTQvyi{r~z$|vvi;dCyYlx z6yzJaRhtimFen1hihv|uItl==ua2UFQrr@ZPy(~>paW>fN&t);8USIAf|DN-VY5KS zUhtey2RV3tV?Y_xWafY%*nQ{$r4JMU-zbL|6quPPjOQJwK#1jCxXm$2VP=Q_LJAEu>X-e%rbuHbmjiSC4ww%&y1 zx!G%Dl$K02X7?O+KU*{}2+86tP8%eTaVG~JmGhOSbKKbX4-9_JGRCUz0%UMA(~99y zY*#fk4}v!Hr7s0s9$7{{U%# zrOLyYw-3PU^iE++`!N3i8hng7h$i?!sX})Bg_|AKhCiJi$T^3n!h6JDp2l(XBm8Q4 z1DLmC@Rg@OHKfV@@f7UCLLEck&D2l2D`s)^6j_J5T+_Z5N>dM~kVmTu6i3X7=lD@D zA0?muJy*ya#TNK1*B|RFV*FeG0Ig5ii0`x&Hv!#b)x`i`tnNU$M@WaVA81O0TuEkNuvwKe3_Me%Te<_A{`@ z$ogV^Eua4YimCp@k7GrMNP2$9aRu5htk4hcK!3)yhD?`0`pOT7#h@3`>m?bvUG{|>#@&j{{XPv%;z@#BSs#LZU7ak%G5*K$FWKu z*ec>cJkJtFhrZi&G<`r$tvu3g)^3mev0B&rJbu_;_+TFi(F*h2O$X{gwsb&16xu$Ab{=~i$fk1n!nQ`s~Y_OIy zaxS0P&%rGo#`{71NBC3vs}g4;;$PS!!xyUZY1h|Qvv%CsUfrpERijkTHP*ji&05kK z4V~l{7#}YDE`Kq{;Z~fYTBe!c{we*OJYV6RN?4l7Wl^7+2go0#Wa`NqstJpr{5{uf z7ZTb^pYICjqb5wKs~Q)&73Hox#Ve0$_vuv7^J-EuZm#YugLTF$yO`#2vCQf>a%wO9 zU&^e_vx4yk@Po@O+)abPpxP+yd{uiX(wnaditnUl zHFM@&XDxp#6Z~B(X_Ao3c#7e%SejX$mEivX4=$%-(iXwv6;VTItE>1P@>y8I!G}Rx z5n~j18&^`;T&V3_%_7n=b!|TX07FB)SqH9bNW@K?UH-2Fd*AvLN(Ow^CX*Kw)X+8b!U) zyo`BdbJm8Lh=z+$^=3~yiv*myJQ^D1#u4Q0CL6ydr&roU_s-h0ui~!C;}${A6l># zXTilL1-PRRipB0|jeRCD6tU+WYGjIMt@v(qHh?ZO?NVr3)rG@HnA@P~nl1zyX0752 zML%V?a=`F<3X&Tk)%A}Z>I>!hGsd6v&5bAtoXy9AyjOphk{KH#o$Z>@yBV}_J|Xa~ zt8Xb67H@?;aahS*(g^i`hK?c7Vu%flI*KU@WuG0~MA`+yu^X|)cWjIfN8mq)rq+B) zu|hMJ{{R~K>_gP_DIc=G2s{C8;r{>$TQ1+6L^H)j6n2@P6#OQduA3}j0NkVwE7yjH zn^_;8{{R_2A8H;H)a0AXA~M;?^sbcbah?<6iDtcmR3|tUT*A5KH&*^l%MSIW!9>rx zx7;h5QZ`3b@SfaTYw)|Sa^XPr_OCvpxl42Pv*7o_xa>S9V$w&3l>;1mfNKhF+norj z@jKxjC4^UsbWf0(lHC}?X_$n_+5-a;V*z$)BTo^lK)Z$+TdvOnX*fZXkDPz8jaa!>_nPUhl(7BTtIG2~{oD5}a! z`eJ}2Q-#SC5;V=mDVfMteZ!ixNm@nCPz=uBYNAPoYRsCkG((IU2xO1)fr@B>gQ2Pc z(PTLEtxQXfzb!OLrKGVADUl<)^4wy#M?8tF?)p}UhRr0uQP#B`#cS0yXo(QL=4z3f^C4S;UYVVXi!m$3 zBHqN&OhpzTl_t$I64X~4Xe#BX?(FKnMUajHTqpCd8v_0Id?tVIS+67XE5QSN zE9Zfk3+Xzv4QMs!+NvyH1s*&6^vQ_HR)kAXN8VGDZVGoyT_g=$9wnw zX1Z}=!@u1>>#I3SC(LAf*O>#_p|O2VTV7$P z9nDxZJ-z8zo0nGa&H|^Dce$~l>PJYKWJf8U)yFz#lV1+3^gUnUPmZV3yiW^7E(E6q z4?-)@p<9wVpOxkq8LUO(kFGRZ`D2nhmE=iX^=BH2#~m7ul(jqPwLS2u+6NWoN{!j{ ziyajbz23hSq$Q;JK=rLD;ahqM<5ZQ87Wk9mc)U$<98sU$#@INoroqwnDDrU@tXlX` zB#T^)zk_X7V-@V#juNLVk8oq=MtQG~S~KaS`QUJ$5G;PzqTM3>;HJH7HA_hOD)f}< zSoq6S)^GId`4J^(j81x2)y4BGpHGFM%S9u1!E)XBn)(BGa?i2C{N9ztH)eRbf_~3a z>(2)dD$*#y7(f8WwRl*>d6{JIQ19(O)C&2@v-J!&z4)Jgv8{{S&wtq1$F z<1;-m2gRN`k!~X6oD*K18Cdl4$)sugEx1@^O~)Bw{VQis%Q^Rv_P2r4bXoOP{#EB< zozdsuqh^lb$L@ZW`_G0}j^2+Y@kG{eyVU8EX(jPpGanJIvWk&Rm&+{m1+3^SMiFBu4u#Y%p40x|D zR*OA+iZ_xxS5CSz+o)c6#w)Ge#Qh3Yl_Nsl-JL#Rwz#TjbF6Q>slTE4Ecl8!7T!JZ`SdHJ1o5}a$4a_M?hi17e2iteT=|Rgk4o!|x)@7- zbZGci;cPrhZ99F%U;+FpWlHiqEMAGT?VpA|B7;b>f=T50UmHi^Uq^<6lq`II@ak%N z&9{hj^BNztB8KUcio$qmm)QDhIEra*UtLDtRLV$%cEPS^N}|%oW5iRjh2kwnCA4&R z05Iw+(85xeGCq45TiQNUe1-9ATa!ldM31J;mH>CJwt{?#Na(_4-0z(oN5Z@N{dh*# zR}B1vSx(T2=VlmxWY4O6GZ0%uUq$A=6BzF%d)Y!#MsJSd4`ANa)t5@sIdg@}Bz$A> zMOtlge0|L0iu_U}oQPGB}OAEQan(d<;Z(pT(j|)D> zxg5%gTU6F9(%rJljD8iZ3_R1&=EY+pQCS}~d|dHETHP3}WWp29+WUMoT8g>BOA6|0 zk3H~isAtwvXNB?!P`zAqJpa+Bpb@U_$)r&PWm z)0F=JuS)Zl7O!K}Sjr2j9p<}hU@nQdWiEc1o$v( z8t0b;JPsMXtB$Q0y-!;gj8Ts&KAi9;!}~2N)+N{&;0)rtn#n}*qYrPQJucoPf*`Bg zfCYJSmGnKD7;Z#Md486bhjGUr#YHM^Z0fC7*+R$8zZZObbflT#ah}!nbQ+@*!=l|; z-p9+nAo1k7mE_MPpS^MhJ*uZyockO$7uiPVv3voy`*50WtZ|CRo0Ryh;io2jHQ>0& zz5Oeb9R0(CE%&Pt4g=51vpsS58U0Ib( zPqkJ?Mdp`tdjUpJ6}tZbg?2`)AkQt~r@B6O_|fpAPq%{N_#q5AU|EWpsamS;I9B$Y1XkVyg}s(J9Mb2FK4;)HEKLW zv^_IP(j?OpE;{3ZUR7GEro)CFbY0|hHrHyaf@{sGNyz#PYS3GScynIA^{Ak@rShL#n)v>$laUK)=pJ|`7LyIA{!!kVNP zHnDkaxj-D6^`!^OEEJR_snnN=#%qq!N7Fgdk}FRnNGn*T&I;3|1H-;C>+$IJbKOBG zS(A`6UvHLEla8qI@YKGt-H#El_?LUDvm;5!uWE7S*&UVe9?c&_d>)+7X&blkC^hoA znzB4P8e$RbGY|A?`3QVR-BwnjdFP7l%;Ea%8Q^^@>F|n{J?VkDYtKj@|ELYlvw#kruoL8UE$b98g@ech@Y|*q)q|QI* zopRKv%#PeHAC=vmD(hCUj4O=$R|3PypHub9bd`=<#~1L)2bg6DRA;U$(ZfTK`K*2^ z*LI2WU&X(VYiBIBHnTG(Fu49Nt#wXT)L1MXY zQe$@>mDddqSt}6RBTsm!pyZwas+7^ejKs#0Jwrn9@>p8My*$g=ND2Vyn)Bs(pB;$A zsK?EpOMD#oJlbqyQMdxbG4!2#|_Nfx4G?F4vaL32wdRQ)So&%j3oy|>7cq* z9c#~UzNgbt#KtPjoLWcDW)qEBXnuM4!v50P?*wELWH|il zwdOU;WU$orC}=iO-bQ3(B}lDr(DX4`gegJD_TPrT0Y{9aiu(8nY?B3IVe>t4)V|31 zikPK{cW2f99Pq`ahi4tNoI*H|0l=kBJi4<=bUE47+JOQL;)(mm(qXMeI+>m!luc~v zq%XcYS5_G~yPiI2h>tFZgnUc!VeqxA!-+C7Iq6=cs@(YqMbBN&p0)n~A4#v?2#Ii~ z0=VT@k=*uSgnXyHd>p$=cs!lBE`qmJeWE;EMKw-VK84bp2!(moRoV5~e3W2Z^G52% z$ED7$Qom$q$m6ASQ@UrFij*1i{{W74$h<#$GqMoGoDtH$K*7V>K^{#gM&})&YM1)$ z{&$_H?u_=Xx`{-Lbt-X*^hd+15o=_NC*H+;PGc*}Lb90Cy^-8%apJ+a?OqKh@1J9h zhwcw9@hr-kOGtWjtn(T^<JY%gQaGz*werCMfKSTALo>!*Kd#h%4 z;C(+T^-=bepSVI`Xp@mr(bSnM`25ax zSpqtH790UufvoG8b5&tIjG54xD6lPt7TSHPN-#Ctd2}#268J6eSlHb%GTEhLL@`Fw zNJE-lDOkUvB`4B@HMuNU#Ro#zk&mFDWY)hm09s_l01m-YfGP$%=mKOQiU58y(t+H9>ObAk zQO#cr%4(V=GKufSNkAw9Q5I?lA(eLXR#?DzpaxtG!kZ-(gczw{$gS7h#8-p45x#WL&NoVv(vp zpby#O!7)n|n#7TgSgPc7HS!MZ_2r@T0w@OnWB&lvU9_Qd2FF)Jfjq)vn!!U*QUE=w z?28OmZpcDxrC_+Y_N3r2+s#iQH(~%isN@T=y9nuuIYp=tKdX6d2%V=qo;CK5!9?W{zYW;j)^Da|#y~ zqKXG0aYrC?1}No7IfUC;@`*{YxTVTzadC=iSV7HIg>c0e3%?m!6JEsKGmCzic$`=a+h!m z??))^6s#+T%B2gE*lecafiUvJ3S6kJYY{b)KMlw|MPTX0n|nr#YtM(;2BjfujWP*X z^(A?!Sa`90Vo0n101A)7t-;Z)`SemM;f6@3h>?w~d;suW{B6HUjSu@L73e~hwlbAY zZO<0?iTfycb?qmYPA>x}Brf{tj1E~d<6np#4SxmSOA#4V$OgKj3!JiNfOxZCS?6F4 zUm`hqj(Wy*mgRFtA~j0qv-m#NAF@Wu25T--(bj7kGF@qsa6avKQbbJod&K%`+UvIw z_RTqpD9J|S1F^+m%}%ew9x81ee6l`T)*#NWPw|bV(_!1oCasdq+iR$<7&KEUQYskI zFg$+tvgwZzC}gb>i#!_dN!uX@G~_}gi%mpOHk!g9i62i?!2pW3>@}n@v=7qrBg#X>s?;hMV6DbH4rlOy?QS!tY@_=6%&xT zxH4xgMXXs(KRR2P43H6;kSa;kRe&f56agRrsieS(<0*kzGekKKc&j9oMU|jdA(Akh zP$IMB915@_+Fgw%1-Oev+Om6^BF(+?BYi4aY|w|r7Z#Yl3!k1p zdJ?#sJ)gn<01@;(Mi`K?!r1;JR-!@N{{X^Up~9*>fLr9@T15<}v>8NnZEh{y0(#a#`y=eOGQN!QzYicV~r82*W?S;%|kCzqc zq;Xb0Y53>iST&t8c*+7mI5@4;P6Oesd%*(QYk=82kSVx|$Bp=t!!2ca&5kK^6?2um zgUan#NPNf8Ke3;}tAC0&Mt~#3Kak;8LR(v-i>9&bxzjh!Let=e7he8c| zW!w*%>!wW4ls-0iF&^!vjAK5P-vfe6Q{{h&9vLuLOiJ_>Oo;A$*{H*^<<*CCT$4w9 zHZJLrs`-A$ishO%KK1>8yg?15liSEuWDmH0wGoMwnfJx+t|fs$bvfd_dP>Fcsf*y1V_dIuMY-o*Ahe2G*tX-XVrqA~KA0o0hmYYD z6iKMVwmv8Za+huU!YDK;8&Kwe6=25{0U(PDKn&SH7NC=61prvVaX=1BZsvd@m~5a5 z<*+CMvo>3b0F@5WKn!V#>BR;mSvT&W3%2d@Py>pC#Q-04; z8zU*Hi4p*E4Qfdwajc9*QIjH?Arn=U0E5!3Or0=2s52$%w9%c0ZO3Y$v0~mWz*eRt z`*#p2q76$Jb`WWiBcZeT@ls&Un?{VQ3{WGaxrv(GP)v^=_=c?&(fWUdS%Wwjq#M3t zP|e^^ztWmD7Au<31+Hn@6_XUv2C7+u9zd-Snz@syp;L<088lb4mj=2Kn=4$#Bx>Cl zHF1zG1!#_T;dDN5BY9~^{*=i{{VP?bDg%M_aAup6y!yY%0I1pxQ~SQGKI0|Q(S$X*LS6O zYjf}V9OsB-^R&(X08?I$7jwhMY<^IDNOS)H3C6sCy?Y`50LP7VV;zwgZ{a^*)tvkv zyHYF3f$q@IohB=q^q|hvM|Te4n%vPlnGFuc!JwNC3US928;*%iXae=L8_p;*NZqux z*aZcanmRdoiqWFE)h;y4h~OlO+%IbH61 zO{m7lnfyudOgdGhkp|r()9V6>y|s?eaqh;psu8v*yzjBj^thyb89+<7}{zR zZJOqO^3eM;;ZKQRxzi(sm>GZ@kF|5uPG>F>$n+zs!;(D8=Jri=K7rI+ryJu>4!zud zbY!v50CcZw1BvC(@axfQ-bct zQ>lFs;9{z4uE*8>3HTA={{RgcC9|*y91L#xjy|Tj-6uDx!3a034>bLtz6Ha5rp~PxW?H8}ZZ6=?850@w1 z5)sXG(xXK_Xrtewm%5Kr%v)QVS=5ZOv&hmMoY$t{nm&>kf2&0LKj301jBq%`c-WZQ zEchJNxxEjmJUrhg80pf!4-~aMT$=GlSHug%w$kKhI9lz?pyg3mGfPRIB`JTYjoEMv@WT$=FlZ`y2m)hlzm9-3!ak#^t#jw|Om z8fT$O$sAwA9}z*LTI99}uWJEC&E1@JsYax%c!sO-lJ?qP^i|3)din>3f?A%Wa>dG; zC|>+^)NgE>6pR9REzM-8Q;WTh*yXJrbFKJ-^H7+X8HsA^i;6v37=LG@G&~-!CBlVo znDL6`t1F{}8=}zqJHmbvopj_l%!NY=@uyNL>U?!%;jv><@W!tTrou*Z&!u!iy(uHj zRHUV=JfGr6io8YP9Ye{E;FTB^--l3_L+o%EcuvL?RfOQ zGwbnR!ahd$nST1Nye%Rd(Fy76oLAq{ES4dHq=vP)MF4~#ar8A3t9Fmbv&v4K+DFsg4Nu+{_cb`lUM6`$ zysmb)9L5K=c#z!tj7G3XfLoxCcNM2IxyYzNLzwelivBQ76H1aepZETPz1$WZ%id>+ zQfjQ65#bsK#Y?kv-fWD(d)KW}nomOofO3u5?cWYHSJxwJl1|$5aY;re3@N&DJyzZ| zczSlPoSd9l_LM2cbk7j@_8DLn=m8SrKZSh;4p)ibRie4tM=kK@z$Pnr^&Pk`_i`(( zPn7d(&A6V^;QK*oAJ6xWE6l~upJQCTE8a&(eJBwg_^&>i)b?>xPC6bn#oC3<{o#>job)G(!Zmr%x5Hp#hu`8ctrY+1k)HPmwqx0b#f_e zcDuzNBYax;QFY<_xug-FB}v-Yz^nF2EsW_`mo@HreS$5y!v#&ebg6Jhw5oGP=cxD* zT$mOu``Z9unz~Wtk>S^)`?KmV0O_72veHQ0TJ$iSN)*1e-8fVQ@4;ey5hQFm(i2DN4p9tx5RjV#Gi=wJ}$fS z5>pfhs^-3<4@NZMq4c;cxlq*2@EpOfy>s5Ms?hO?O6dC+;XCYx&<=%1@~@Q4m$=W* zVWuSZiNE99@UM%9#C^?g;GT2ieN1V3b<6Rbarsx#;bF|r&GA=-Tb>!MniVeVj9^w#pE3FVXNhgzEc$Q2-wKk_;cq8FJcJDAp!!#!qZ?{@ zly6nt>U+X5yHZvyw0Kb(Vm2WF8g-N+dIUg=t8GJ>psxR>x^XE|`*_}wy zib(oz;eWx>utj&MF4f1&7#(Y(hwnB%TN5WeWY41fH>drRWN8qNB>-{2HKkfgBf`>^ zM{~HhP^v46O#12(Nfq@Azap*?xyg*gdp!~7zAgBNrs&p^$#ZfLDE0j-rL`T8m#IQ9LItJrAXR z5%|ShMH0II0Hh+1YB283$G~H0URUaSzK7$u{M@ zgNl|U20V@{IUt(?x6K76ak`RucMVoaM2w1?6>&E4_)-}4J4HbSd7C(^ERARZvUR4( zL9k#{uw$f6gQYYyb3P`M%ZTE%M4U9TZ^@)+ksfLbv0}!?T|(wKVM6Zb01D1iJzEUy9 zYv(B|p3P|3^jhywIRdrzWdIWQSydI ztsJ4s8-+!NF5&A%fLf7-0dc%ys>0A{+9=dTUnsdyi*To-3OPfS58f)Sg2+PY z+~SL%vJ=isVvC@%6VFUzIHKq*hP0{{8U&h0^tww zQFAeqUBeevqTnQYgfettT3Ik3_O%`9*b3J=#8%^#KZOZcT+Gb4@x`^j9!!TG)j`Z> zx5V3cjt#qy#+}RN5>Fmm-VO#o8tSIQnCARRt}OP_g&%oqvIcTKCjFzWg8B08KXBJs z!87yk#TSJ&39-f+ux3RqKT&)K2fZzhn4`V$x5T-0S;px6#1tlM_B|F~jusdCbc&Gd zlWr?y%$|pZ_>W)y)YPPubUi8D%*>AC0CuSZLOC}mprFZC^q>tpEqY6J3Lb(=`nIHQ zTr)ou0fJOJ8p_~35GOsWQ8YGFk%2(Oj@SJw&M{6%h-iB^?7#5> z4+?mZJoz@p-nD~`%^bTQrrrtozh!r(E5SN4h3sphoiis=#QNXH?H<29ri5f`TOl$%DT$nP{M?+-B2t5G0xGz}PynyAP-wOymP}Ak=mB~F z(zhFE0>__<0IM{+xHJJqQMP~}2butovBdy2S3cAjgF)>;3|ym71(4ZXn$)mkk8wZ( z_Mm}Ef;}h!n>tVgikr?Vk&zV9Z0W@?8*S8@XbGi{%pH1F85L43tbOW=Cro1UDYsaw(!w*E9#n{3?*o5cs4v z-{OXIEzdenzH0_*OS~;j#vy)c(=4}Ra$|0lYDA-^vAhyRis_7jlr?}h=C?*@#fEZQ zZEq4L$qD&uc$}gyAB7KaBA!sSv4bEDn$;PXEnkjlB4k_OilPZ2O}GM;$>p(c$_kXO zOMs{_Dcs8BfaZ&pa82E6Oc^lYC^STDyOh?Eb~BVNNv2x;rsN0dnsaBL?A)D?Q?c-J zizww@z}JC_mW+o26KOmUyke&o;rF)m+jl+7|6B7iBAo zD0A5=4wY!u*Dq-iChBGMH(gV11p+F{I0CLJ=+Z9A!X}uv^FLI!kI?#8&EWBptvwHvuNYK`-pBAFSdunBDyYE6YWwUp zSg5O>C0sn4x3oOd+OB3?OE9rfdTvN8EisL5RJd;z`Z@gE2 zXK;RD^2W!C=7iPGZ)W2@W{vL&{34fINhP_${Wz|fRZ%+UE@;`EJ-@@jqq2oV@*hgl zl~ngUTGU#Nk4o^nG(jU_+PLv)JyD(&H6ze1WZMmV+~av4p<$|W(>&ASKALrjB!~~a z#eD_|B^^(Y$Ko5P(mdkp;Pt)o?PMgJbR1VrR;{7WLaS)x{8jL3^Fg*37EyVda&T+T zi%wddRcBL{@zlofw~j7!Ig-{%3b;F1FHv2T(oW?@Qi8MG_KhD_xW0-hw-Xy+itqPi zhYHe9Q`I!74b7B$fh3R0zGkxImWR+$YEZGkd{xpS)Z&IGTz(br<#V;qICcbg<|{hINY8<1dU*BvXr5kT1;M$F6^>KMz^|*qR8i5BfW~_V zc5o2<6G1AKl&;>}Yu0m%N3Byg_L?4>@b|;b;pv2Y$-9rciu3UM)o^ ziG|t2QTsVq@&5oAUTR(`x@BPHPu)nyMSJkWLXtaSI6%R&_+fc)z)Km0JCbOhCvcu; z$)T?Q00?7Pa<94mpZ#j*(&RkZlw|lxau;xjzt*2LS5iNV0wHJvJMCDjm2vLCCL%#@3fRJFr-2wW)tou}N*%A4?UzLoblTIod|9s?2edFpu@d@Q^m{{W+r&3ivtjki9B`%*0H zJRjjpofiH!C{zPjRGeqj^D$VSbLsB_{Ps(nFG~1qdb7~W0Ts=2snujrV<-EjyqXK% zN73TvA7^9coexKkQ}N4&K!18$^{>2}6NbMa0e4_P|RCidD+z;Rzlp$KYt6sStHZhWER z&mJ9O?ouTsSeyWO6^!cNL+P-2QcmpP?Be@G``_j#^sYv(eTEwfspxAMHrtZDfyG;m z(bp*19@G0O>wsM9c5)mOj0(mOvq$Bb>b&I>>+b`}^ZnlZQH_nFd09k#oQ~4|P$SQL zSBFYEpJR=xln)c}r^H=bOumvkY@}*&UsFQ~7PUS#SG=ur#ynH0YC80iO*oZ{9^;Dk zX~Hzl_(F`gXFsH9S2NDYkTS78m2$D@xzVK-lRlOBOQpV*qpWT)Ph(yNF!||KjOC%~ z_QXdQ;a@v7%_HfkNmGT74EV^x=fw8WZal6l>2NW%GsLMrTF0e$TTF_}Mzw(ken4x? zttZULj8$C)@g=<0c5gHXYbecWhnkeE$gf4yS{_M#<85H1f>{{jg;nogQ%eU=dk&PX z3p2}guLJ9zHPwFD@IJzSjMr;WrKQg3VDqw()oomgnRwpF0RT7o| z0PK6BUJ`uw$@H(A#8LK)Dd&8<7?1lL+FGIVrjhZ`Vz}c&lTQiEH`^s1|q(vMkgI@5%s@<{vSi& zizwiMf709y&FNmo2A8&1XT)PG`$uRV_3-A!*y2Xrj`h!uf}^S3mPK=;(e*2IOJnl% z72;B#Gd`m*R;QuhzBKTCkBMTM&h{?;lr(#@-q|2mBF-ez~W|DEX319M?pB(|k;?hd%t#wD^UzK|tYu zD#CSjJZv@_+HPOEvq<4N1L;!05h!EO%gV&C2Zg7V7@v#9D(-F&%gw zg1Dsl+4>e1gQk;cxf~l;1oWi1hEX zSr>(Vb*%99Ii+TL`HdfCNa5!3%2-;lx?r1gn)W3^D^ue>vgWx@3;4nDiXXAOaWcA& zgB8a+CQUpn<*kpG^<75m#5ZixYe@pDYht_@SCvGd2oe}*lj-^nJD z`B!hcE8n3$dtBs(A`oW_2ZA*Todl7R3vw}D)GP8Mx9c>T-1MITcx|*>*qp#r4{E}t zUU;;4_?pqF8y`gYJ;TMESH?~?5S$fVbgPJS_*x@oZ z024v%z5t@YZt2jpN(Lyf8!Hy%6-GsQ4j76o1UtYupryHqub?plSxC_0h|H#-&}_@N zeiRmnBL!O&hKZEt8L23K{%Rsh7{p?!ltu{ND>4mxSpZSTN&s1=6{%!iU~VfS2ImHo zKu;8y9jZWVqjzes7)NYS0Dvi=*sy~mr3Q;)%tviIg^S5A$|$&4jhiNmg@!UwaIq{> z6ylJ{C;=>n!-@dbmP8SL6am5N@cEZ6MOg-5Ql}IYhm*8avXKe-jUtHWUNerA ztWAc%*&Q)Og2NcbU_tJu*a>raE9veuQNnC@@En(cjShEi{H zWRGu~z&4gKDn|g?1}hU)V=Cjpa9ui$fB<==bZB+jAAvOMTU7np=MvqKq0|(G_D#FN>C}gMGY+-=FfT_O=TYbuWr#&-yDB{{VRir_MVStv)E;Hxh+! z(vEPku^+^5-Gd}dew1^ChRyLx8+88wQ0+%J%D?cpVrId&U@CpH9ocLC7M-a~h?_rJ zUfG4&3HY^cynUUI<43k(c0k`4V?*-WIUia*vkS2g#z@HG+5Z4P{=GkJ$+IGFj0$>M zzvuq|*Q467vjzCG{{T*n`PB1BEXfDOkNS2$ogB<8$G63gq2?;pFjLG?f2 zDCP?>mcJ9OWFyUlRr7_Jp#K07nl%`=qns?tukl)3Z5H^b<{qVKJ|>^Wp@U-~mT4YGuE~TcvN6fTNf-8s8Cb zW$5ciFlsn>^7kL>2jx?76{PXC?B|&W(Z#>(7nD(yvY>A&Y{?L|* zai&g2GqmIKuJn1+KQ?@CYSC*qDtctrGBR437CSH$HaV_#>~u#{(7ZYD#(xl4iN(j4 zkWDVfiFBYZz~B+HL_+!hqy5SPa>!bb~o!F#bO2aBNPD- zm{f5<5lJa}0B`^fC@G-N=~|LBu6j@f$N(!++btwKE;*_di2x*4Y_c{*7^loli!q|$ zXB4k-*$&|i7G`3bfP_(I1BN6StpJZ{M&hY3EWN>K@k|PD^ECh_2Q&bx+gB78mYF>O&z3~(N@^*5@v~w^}I$sUwv1&>uo?WavR5=}L2A;d8!);*O zTQrR28y;z{=~2%aZ)yoV{`*cMIa~J z@QX^-CLVRVw>`pqqAG1^z6RX8}BB%FrjMmB| zOLMC5pTeCRM$^EW-K2BWvCUU8hbe1S@IH|@ z7Ho$U0dscR0F2E*3S%xf6afsXM^Qi(h@b&*C;>skb)dwNm}80nZU%Ee2o5V!$p&ij`0VXX~(5f5#av- z6@eOo0-G^Mnt7XBrC`lgz$D(>QY6r}v-5FNa?J$OcCJNI5j~7=6Jxb>#7vu0h|eBC zuEPdoM`?$xlQZ11Eb=kwT?op=Pi$0hD)eDuw6N#5N~B=X3~_>Sio2Pj+)(2fs!W-p z+NKw!E0baHu1_M6H{7ty6jC#9sT^|P;+@W7RWw{^Qzw=>;8RS+y$D+OFMxQaQkBgM zb@LOaS`ABZe&zFyls!&aJ06*#c$XS_#K*C(7ZkK-Jy&C@_$C(O=T$zWAJ)9A6SZUW zT-#Rv0IsLozYiqJLH_Fh0809dLibNoCfy#KqvXwe%$BF-ml+>=zE3?**`F7kXzskfGxU$cf9aZnC|}(b+y*;W7EwiV-0-lm zbC#!gw#cKE$gh8yr(}IBF!cJOytX#&{{Sph6YX27(p^ZN78cPNw~_sga~}Y97263Z z?tJDYdncj9c%R}GzMBKbBM7m^PkQvJ;rU(9JL??svGTXX-yDBvYTjUwNOD5QUX4lG zHa(6-7;|+!E8#bVMy;jVU)=n=x>t)CT1@e$KWS#qrL;{RD|m2lw5dr|)g1IOt}m6E8pf6Vw-Crs-LX+dudrvV|#~TNUjUk(#CKO-)N$TNsEK+y_eFjXSdnxW^q4;hz@1AnE!xk$Eil3Fnl;#t(Wb z)pXU7=SB|@{o~_r5cs0!#E&D$wxaU#MC+Hbw5;Ul;WOV-k1iT{J-Q?E@N0V-H zd*-~yJx?PU%&I#*5%<@CwDp@!w})Um5l1~M(uH1FoLGE6x2a}!si2}902F&r@h7UX zi1S{?q!BH$00t|HbM}Y7Mw+1;5NZ}{sKUs@wP{MPv6hB7nhO;cahivPg{u|-SG4h7 z(kH;t{?#42D;8&}L z%;$zyM=eYOsP0E$@V`&f*Ab8S7As1by-Dt6{g~HM*ZeEnU-Bz2?Kzpep8JY?1#7pD zb!ybt^nTwm|EO^zfLsRr1L3F<5R& zsUsudm&JvWC(`E0nje%_n4Aiz`H|NgTcPfrmu;>#q(B3M*1V}<{gKSG2Z?;7ljv6W zM>0kYOmPy1%;;EK>Q?^%gg(Vv{X+9q^||*K)Sbd_18MIVD+movIO>V+bxQ+ynK#Dc z!-`BQLM?YwP{jO39FB&@4yW-fCYL)-`!=Z+*ZrRwa=1pTtd2+FUBc@c;k-_bllj+P zDe};0;ZeOq=$$?Q5PE?3%*y2&w0= zOjV_!QkBlj;Et~zk>g?WVP+s#CpC}HbDCGG{Li#JJK`~8BxJT#yVX>WwTbdHCh1J? zF12wkoMi+aIIlJots~XMVjQX1!-v9Fn!pp!3dqmRcTW{6dLzf9gOx{e=lmd=d5_Ec zBzt`^S$%eW&qL}JItdqm>@C}LcIc#gQu^%QQ%PW|HEWmP(>}~_suT}FS<0_D#|$K) zElD*EQ7kRl*zg5q2y-*KrX!c#N6y|N*FWJY@tl5M>?`dR^z(Nyra5o`Z(roMEUoI zlAV#OXW-XSkD9+|jV{Xn07jSXQS9=*>sE@hdmleRdnwtTdHXha zYZo)!K_8vC{#BJ)Jk0j1K6u?T>RS(o$$I{COULj}(6c6u*|S4IFY zKDEP$#Qk9RF!%?DpE0*QB3L$_gnl*U!}o0Wt5a1W%Fb5%!Es2e-9fKqV_Q}}QxTJ@ ziuO3K82De!((bJtH$x&7=b$*MkMAANp7w7GE>q&K58nR(!Xe|gwhQ~n?ic~|JXc$d z&sEO!xl#7-!>=1fsp)Cw$g3~|t#Y~!PeYcTBiSuZi&NAd@x*ZzU`HEkdNjSBtcv)j zR$Qf3@L{+jNj_tW+2gr%LwReexshIfhwT3VaTHdO%&obca(N;p_zha7{zhc#QSAqN_aPimCYO95L??s%4AT=JJ&5dJfzP?t~M$>r;|r=o+j~; zJA*?HQ zMx#0DT~VdVeN>f}hM=2jj8x8&2)hdunP6)lW@9-tO49P+UD5XWWi=Qfd_q6apVJiZzY(TzzjSTjc%8!(9u5K?c40Fowvw|A9VS23n7Xd;ln!e%> z-2g%K$n>t&A9VUfZ)SRD?7@8_d`8qFLxAheeEn!HWAmKaFPcZ+ULFmFuJvUDpDI%3 zvDNG*I^m49KS6PB2ZsDo(^YP#xsAM_iZDNcQ1@47lnj zaMbtk_@(U1I@Rl3TQXV@@_GvCr5ii5EKJo%SscBehHkDVEpr|-*L8Z|Sx{OZH#`iw z7ZbqoZpcL#_NaK~L~`1#?o50l6mSRSQn7~bFOjvP>FuYX2Dod~Raxv{s607(?^IWS zu1BSDD0??^zP>$Frs90>@yp^>w}EYCw%)tt1c>v=HF%1=@v-qRqt9CB#P?d)_QtPu zdnfwIT$~Evgf&O%8ActdO|lD%otUovQ|oEP#w0@EG7xDabj*A7p}fOHyuDP#Nl4HL-YG=024Sl9ko4 z$4ZvQjBCqQ&P7RkkwUvz165&~u*4V)QD80Gua?4#0l8u~kwt*pLAEc#ix6d8f2=gX zgeWGZnk~$45D>89vu39joXXXaXhJhVM0m?_Kn~n7QfP*;%G5;yBJb9$Vu6OptVk{- z*iN(o5s{eIrIH3}!U zZlDLD1W*HId@|4mm8?7Cpaw7zJ}3fd7&J5i%IY5^jm238M6b;SA|{tLETl${^HM08 zu_Dj}B{q9vfFhb~)3~fx7X>?1+%8I~_J#xd#0uw>tZQTF{{V)c1-v}k4Yl3nvdcSw zPT^UpU4@SFPX}nW5d@0H6d&%2i6NwH_;*USv>A>xkJw;VGDe9V_LF&OZkxQ(zos)> zt(qN{lclb$0DSRD0h6xyQLS?RRBlEgPMnb`{-0oYfKXkH;wI)Z=%XJI8V> z-Gb2asvj-t;bFTddsMV2Ef47n#gh;0TaoH3#XSS(+vqayk^EG^svu<_D5O3?1P}ew zL28)j;$V3Or(pJBFlOkiX41r`oMw$}%R#X%gR z#guQmnnB8HL#QUuxWy}%VV6@Z!vJQBg^cG$&-%a8qQf4!*^QX|D6q&^aDThS76M-+ zzf2GF^`gQW){-Ael!eG5Y4VRs)g~N;sBM^a6{Ulae35%%iw7XlZrI|1*msd9dIG{N zqZ5GGpjIFDCIdUD3lWQU&uVugkd5PTaf&V+gQAP(28)%-UmU0gi-##*Z96=v;+VM+ zR9kIsT7cw4Pi>aSZ_BJ0T zkPU*s4z<$&CcU>QYIgQo?WS|hA2NJ;)rGaZv9ao;S9#{h_(R89f1i6KsyI2Uo4DeR z4iqrXtKW5VlzkuY_x?a};DC12#cEqMyhM@RX>&z3!O{7?ey+f9Iqa%v>NmmeoeA_A)v zHKH^`(Ov_9Xk^HmS&(uKNer&{3TO+KY~}~AY74n#fNIE+oWCn(fGt`}kEK$5%`97y z)Qr`VQfBR%0BflWKoP9i7&HLWEdsb_2Q`tQG*<$EBx#LmK%Qr>Gyta+k!4vM=025a zSeC|#1}cC^=I0cy7CL_p>d#@wBNdY<>JGTtgo};{AHpgw1oG;dire8(>>bF(E_)P^ z?Hj||mx>E{d&&BoQSAZ5$?;dhtwzxN%5cZhrR0wW|C=`*o=RW9eF>7S38v5SxskQ&QOMVd?@|TOkRo*+RkM3%fK4&Ih$N zXpwHm#Fw$l=Eey>O3jSjxABg=>-(ga{sN}6xh*oTqvM|v*^j*{aoiDI5N2GqIjR07 zYpif?JMsywg%fMS-xl>9F5zBQMjuSjQ$0#Q+AcV)Mk6GV{3 z8V-bjF-_<aRe8*+0pv`WHMB@_vC~<6 zZOlR|h`w>gYogXB<{yfDTOPGMQp7O%6cF_~%?9#WtrIGG3eCHS)Rrb_agHex&O=p% zr00qYDXGU8pg}=PaycZ!&{No9n=Oh8E?V@U3rFV^8a;_3vhHX(-sOdiN5M1*hERrp zD7paD2x~{Ubf5@~M0TJE-d&iWWQyUJiqxQ>2bU-~=A|nMAyR&enxaYBhS5L_2cDDw z#ysY%$Y%3Yg2F6@n5;-!0-6&mw=}^5FX2HiVqO93inB6!a>I%U9La{QOc{GmoCyG+ z4b&rpRE%US0(jzmcvnH|4McQUa**^bUBT&n_GHhNQR#>3>c zU^-I0hho3kn6NZ+7|tV#ur#@hWI;G%O+@E$DmvmpNu-KO=T)S@t!$aB+crx=$CI_V z9jenfq_rfoy?CFm0=&FZx@Vm_j_0lX3zgdD><+B8=iP5Y-Wt&%Jpx2N>4R)O<)=kA}FKNX@Y@gH5| zrNe(Z(z@7KKY^Imu!grF*Pf%P^i*V#mo2U*;=1B?reBFZTxPW8G>W-e?j;8_)JrIIp6m8yR5mE_D-Uhh1xrKB%+E#kMwUTCi;&UG*+l-JfUv&%P_L z@ehP8;4Z5)SQ|0{HS_YK;UnW`g_~Ds`U6hXqFDCi4WhW}(roqc)xCN(NeCYkA}Z%U4KiC{yS?~R!f{@?K#`*Yo??e_}mvfdE$MA@XO<3c*?>k@0-fJQ}==EUX2yX zes??^p}yzPUK`cny0%Z7l@*2|Yjk?}I*vLsXHdR~MUZNCN%q7HrvUp`ojQKap5+PsVO*oF zYjEhW?vUVm*F_2*)tuOzRA?Mdq2v2|wPlUDuT@}NQSunPa; zTD{3}h5i9v_7b16TAX&LiF+R>c$?xo_?yP|)1fii#^FNOuL|+l`X!et5ax;XU&H-r zUKk4e-zdQRE6>J7O&>2+C{=Dpr^T-#h05Z-YFKNYgmV=FU+wt&R&Ni=4>*b*y6T@w z$JoTtX6cfywx_)(Nq0I{Y?&a{6#oDatnCzTvB~uYzfAq=Uf?`h?s<>J?;OLR=(iF^ z>g=k1U&6hdC7^g!rS0pZ@(A=?!&8Hb>UMF~yECLDSTH zLB~Bll-Vm`BGj(*tEITMkff?c2(D>D=a-71&#FCd!Jo8Nr)r4?tBIjs;~hmrebeUZ zWlkx%A5r`%_@!mzT|FX#b#Oo%Sn?{R?;ZteE}ODF$4`2IKRP*IK=!a(lx}q!c+tLKPo`^lQZeyr8{#mQ_ocb@FNd`Y za}wr6{A+^|PEF`(f|85W>6UniA26>i#z)gettPhva|j%=(&Uj|+K__w*-!e!{OS8S zjaQ_L34d?>_5ABMN_~#l;%P3Wgw!A(#y>j6(UI8R&AUw?9r(%mlfKp9m?G+ty66EYQxyA53Mvm z5WzeH=J~P6tSV4!=)>b<1Afy}j^*&A6X{$tqUodE!{cLJ*%xGC8iVazsyyuVYEO|| z&brp2fi|-NU68}G!>^gb5wXZ?+NHGi(lKDmgY>Ub5#&B25nWfE(fRZ7()JiWAyw;JQDdppEQ|f(V;QekbN*Hb< zej6PtNzO8jkD8|!PFFoj&r(E)ZX>05RVg;lx5DG$2wFpqw{8zrq_FZfd94wj@Q~W{ z+`pYatIS{8)JX32D4qgA7^pEf-W_u@>TL@7Bt`9-`P@QRJ=~)c7LqUPckxAN z$2?axD<*rnmLd+Fk@Eil#(#)bT1AcJyZz_STJ#;HkBh=mZl%&b6V#P&yg@C;_n4aO zp&KLh93(96%XY#!-6G%sIjyQ`6X>$a$yjfS?lY%K z6}j75;Nhv&o78iKqI^a1vtL~+;ynIlcC~_kI`u2lyibP1VAL@@-e0NnmENSQsLbin z-#?vchoXJP2{>#ho)3(*FQu-BQ}m;=82I6V4kGJLIE6JcR76uqO9J{9n>}aM|nLttWs*<_7g0U8b<4s?}7v-%cA~rm)YUh+wK7SRL%5L%MeRuFD_L75H zj^9VtkC!t5i6Q7urAJf4tA>^q7Cw*CLN(`_yLK?>Jw0kFJv1=EO7r)QiwH+STw0$| z?lCaF!i_~}0;Fl&RO-f+I27;ZRZo_;g0|Upd-RIo&?eK%kIoq7bq7 zm|A?1v0`gy#_U$@o`iQPmMKwj@|CW>bl_HPmXT84Fp_gn*|J6H4pSJc*{!TqXvPN> zm4PA&!l3EWp`x}iZtixu#Zje-e`+N0L1`CkTXcfC*}2%*=*I30pzTmYeT=O~Pd4%p zM@o{+`7Kq~kyB$tP<=QC>C&iU;g8EWpbL@3o|FNzq^>9fUQQC4Ak3HV8W-Y-@$D_eSq22Fl4t_Im)50} zh{9r#W7)QIo&`G!bCGx}FNqHK?>dj}06|z9BUbb6GB{^u9)1k{1(+T+1%X8vhRZ8)t`upTXU{{SD(>%b?BRIoSn-y&Oa=7oQ)V)rQ! zU+6k@Y9h1U!nX z9Vy(fKxO-)iz7jTATlx@y#E0R9dZiDipl%8ZDAajgS%06OMWOcz6T%zVg zCLf!DM<{b3lFdr-Ma?;iW<93@i-$2RYDe(Y@{w|2iZR6;p>l622;k9T`G}^3V;Q2s z%pyVxb6METYQd95fQ{@Jl0`03$!y>#-!&YdUpCRY^uirom$C z*aaOZuvmlpIC@nM5vO1WA~=)*KysT9MZeOdlHywb0J10o31Ct>Q?M1j$N=KCh!r&?BHm8lEqBv1 zsE>)iYHd^OS2)f(*Imylri}Ri0AJNasN9YLteDZodFqpgBw#S8T9sqgd>7+OJ8Lf~ zJ2*qZEh7* z8g>C1^cTkNlvvK?m>+ne!ye;|3^+t~sEq7e&bfrDOo|ms}A*4TfVIW3>jRf-ztZYP88{X3W1F)D%nQg1(di zPISi^txVe?A1(y~ByN-xBCa~n1c8Pq0y!oib?HFJKHSQ2P?4h#J0q`Jvmp`rG@gSc zwkl*9tjM|SJ_PVBwzFrsyVOY3?mQvz+fTCb3~MF`VmK#?#j&30Kf^r|?%88D>4FC$ zvXeGQn?v|0qoI^JE*RGi%95vWAI1A+Lo0+{7d2z>^LDurF2D^ z%^#ot01^Hk+vvU}xwSZESX5^5iTWq-d%$`RGS*{vHhi!FF?{i{^AjesDwDlS>{j+LZ9mJtCM914<( zx~;mx4h8@e0l@1EzDD+-lN0Wm1UwA$K}&MI+ijqyv799R19<IW!NTSVB0!E8~X@MTX4Kzy?WA&y8X&B;y zUc*~~>sDq-1p6`VK?LS)#`$7t?(He~{X7VLT~ zTv>p9Dv+*#)uEUDvXG$KIJ0!(TiY!J9tSBuS_RuncNilA0>G7Fd z3|8tyNOrReLODa)ryiRk(=sv{UU{|R?`P_EL=3mAr2mCCpeOLZ_ z{{UL*sj5T5tHwtp7+S`2Sty>0pD7Fyq|zE~Snj1&OH$~I3(~1G(Tg(gV_1A}CdM`wnr~kz&nxufj+Io}j)#F)2{f#=N2QP2vi>v9k0<;D z*QE%j)bkb!t^O86Kka99>Oj&aQ|{H>8df%V8k6X7z9jvkwOvkQ<=c5l{nBf)H2jUB zmCpMfTdZn#+WqTKHhDSvS9Bnjhr2@t=)PjCmVpO9HFP*D9*o`eENI>%zwo8tn#SSa zU~)E-kIJ}e(KssM;WzG{ucH3bo+7u5ETJbL0hZ%6%BV%|d3cPzrLPmFwEd>FTY>}G zC;SA{bYPD&>XjR_MvDH>cZ6;>#I+hSm|_)S#*g~M9P8A7mCYR zpEHcZLaIHF!#^DFEiC}En{SprCY;pKhN>|YJu$g1MGEiXyrXwa zS3*`gIpfr^r%Ga;IaT(&YzW0;QVHsO{wfN()cP0Uzr`=Ky}gC}+>8Y#l@E7Q^XzRt zS@NGk=t&*Ph2mx)fz#<;zt$6tj{*^rmCshu<`LVdJYaXPp0BKr(Q?|{u+^9zIX3qe zE+ww)9Jv|eX4QH0N6g<5{v_G>NgfMU4RKzrOhwNd3q>`l^H+;JU3aeCy~OUvcSY@9 zdzJJ)zXgNj{o^@1cv?fanzaMir&sK=wD32LF8md!EYY!L2kx52ttT_V#^9$NR>#qL zSM3L+Uun^_-@1`U&7WF|PIOuFRj{2+n-7lOA9(R4!YA^tL+h3~{;4X9V_H9r9vDCK z5rYJOewWto&=y@NqPC^+qr<|{j5qKZ0O%gG`rEyVrG}H(@jr-v5+l}>`Bw#pO`!2z zP_4{;J_dx>L&a|{oByc>gyq<(d z_Jr`F^wev}bZQ$%EUY*F8au-ZIKR)~Q|u;nM=iuyu0Lrn3$HDxnwNu{C1v&2?4_yx z(|#8a7LNLPk4=b}{d!ca9YSSMyX)La??pROS>%xG1 z(_H@m-z)w!axTVy+0|3A%In{?Hj}COzh{nJxcN^*_=={QdL6W~x4HArjeKAK00~v} z@}7)(XEkmOqv-JXWev@*g})W`ABR`zCA6<4xF9?^;EEJ)qZT6t&l|-04F1_V3>Hzy z@W`@}%xACZ{uP6xsp2dXzK0#-FWWZPRnsJgPP39Jh#_6b^v3{IMwO7ivt;L@K4#Um zyT1_X65QN7ODv?bZsN9rKF0@x`n)yVA%Kpex|XN62$O8bYK0q?(EYo5Qm{!V>;@4h z6j+6+YTHj#QhdJM={i?2lytGsco)VOUJ2Kuy0t8dxGfkw5krlU^L6lXb8<-fUrha^ zt>m*-c64$ESKhO6OzqFq4Y8UVqvsGLg1H^sOVxw=8%ae zemK}jgAa6mqXLV;9I?aHdt8d+<87+{0MFOwOP&QhEnhMZ_+7U6{{UA%=TDvj`n6v$ zZ;lG9^1(U(0D)6;fnLV47(P9Oa2m>guKI^45o+n$%!gb3pe}r%QXefb#^Mk4qA`im z@U?1a&kOjQ@lx}}C(054qqcv-k7S!Y><(U{W&AeygFl4tu_J!v+pS#SqK>Ri1-%ck zJ{kDFKNRTy0BC|u>_5BOx#J~!Gvcw-B}Ze}JRYyP%lEDfZMq(=OqzO~P3vuo@UI${ zjQbp8v=1xsZmgCDdF3O^O84os(fNI8%B1X%m;NsNdbo-?EVS41$vED%t+hS8J`K35 zEl--Y4MKbQncTa|UvU-D3ViJQd>t>VQLvWFJW?Y^zbbMCDbnR+;;E{%wmypZL-DFF z4_ZfMsxz)Q3>P@BBC48qN5kSUN`kbHdD1>JXm^Su8iqIj0MAY+RUB5wlO@XRnW(-o z(;Kd%7ya}rWr>N8v8u)=i?ov5>M?)cLWhY$_7zH6x5P~~Et)tl(6}D7M!J#PPYpRc zGv&XHUMv3DxS3X8GXtWU=}>dn`s@W5(ps4R00g{zd<(3{H0dF=ej6u>%B?wA<;-xL z+P#mid_VE4Mz~1YEkYQP#&E}_c-1LQv*zi;>hIlUj~IMp(sZpROM|Oh$%W!D+?wZ9 z-_9>%2JhF0a3j*Hygv9!gy6F z)}rk6XRZGL!%lcMAl`1UiL_^F^rP9^TM_lDt47a~{{U$J0NNgJ9$%e5No~4Xw+-F# z{{ZW#waqEe`op(F!wH)63{ESul{B?IXyIi}$r{>G3dh>FvFkzxp_}(~H)EwyxmDkF zFKBHVBxcK9?4k ztK9zjFO?_t9+ixseC{F&pTc_Ld}-4VU|zF+b*Hk4AKD&v1 z3ilrxH29ka<-gD4TB=d8%{*0AB(7q3xAv3Kv~4a%j6KZQDlv=$kFWBsImX%}x7u?@ z-qiSa;$Mw*e;wG z9W}BlARd*yj`(PX&N7>ESxO~FMkVB7bAepVn)4Y&iTYMekC;IoTC;LAQY9k;7_8eg zQXBVx;-Sc1(4lu5GMoyIVqHTLeKuKSIHz+(P>LNff#!~5rZTx^A>_&hVsy58{{Vn( zmg2yYGl4-U=lp2`!*bZ-or+B4_i;$jf>#PC0;ilA=p@jCg%lKP>7yoqHOl;-g$SFO z)2v(DlPwt9y|Vd6p_@eFCIUG~tXeE`4>4*DnI_OWcA%0Lk9etOh(QL<+*Jro zy!5CjOki*+v?R#n+y_cSCW7nBB7iTY=H^q>qqZFn_i zM>y=c#RU;K093J%PQ_P>Lo;r!41bMV84NJ-xc98v9m`5K^r*O4hUxH>hKk>4JCls$ zkLg^qosALp=j`F&sWo2*!*?p~TpWIt#OfL&+U4-Ig^~xFFpG}jv2taVq0@AGhT5h) zbm%K4%0$heS~?Vq9@|A`&5E{Df5JI+_i@h$6d)a}_>x^oPFRuHR+j?+$oc7kikpFD zF5Wosp#F7}+@wows4_Rsa5Gx;26Wn1w;V2@u*VezqqnoaTZ?=qF_F@@q!LHXU$viy z{@q~nD)dro=;&;C7=!csUeYnBI{JT!AJVj*09p>EaX=L2KpQjwn~i63t|`g< zD7ajD4351hu*YrZ6j%v<%63s;A@Ut4un%V;c%r~EJ=y6+fSH(~s<^CIELpzoOA8jF zLyYaA1&IOK(Q2+1870Xh?$iZ}`-$i%3lB()oKO}X_8=_*Vp~{@XRc}3EG@;Sm}szA zp2`m?hd87bG}~{NKv>R^fPS^7DHaum<)f4=MA7lqpD0+3(`R})Ld1y}W#rT33lhWv zoKeUYCVM^OG;#%q(%)|esYEPJEym2|tC16T+^ZqPydIQtAiM4(C>`l?7MW4bUm2s2 ztCPZZQ{@?6>6TW&s<0H=9f#VO5~O&=0BYJgh31k2MkzsDRiG)|NFu0c3Px|1w}2TM z-k_@k^Ieh9<*0lU`(5fAuUBdQD(F19qvWkhHkWoYoK{7njlGU&n2a7xJC;^UtC;@g z@O_0(Fw3yVs81eDa=7|a=L0(8(si2+6NB`loCz(oyT^Z$Ct>uXoDQQ(@U868b8g^$ zDxwWoT~|qyOdDiEryORwVqqm?85NpA#Z}0$wPzmH0*j2*NP&uzL$O4l3_$E_Cag~@ z8pmi9ib}*pWSZR?BvpPJr)rW&SQaN0kQ#Q{agsdtpwyAg+x`{b&m=n(U{(y(9Xjn!#fD9VZ88msmmXhCn(ZQ&Y z(lmeJ6f!t&l#uK?-^1qG?ROE3(ve9X)!`^p>?xdZwS*R?uS1&nv8P3IqDa^o0N0}f zl5F`SPPazZXN~yzjc8#PsCd%RMwZwhVuz@tb6*Xww%>@E^UY+@TOQdByGRE-Vx=H# zx#aANZaY<_aag(Fy>bmNR@)zGhw2wCZr0#yl;Vb@ekVoii*QT^Q`iJMvt18?A z4{X%6b1Hh7x*X!yR<>;52NjvKK9th+q&jp&zeN?KQK6f!>gzTzbB{_03%_jAeE!vKoW>}6abEPwn zMy;fnQJS+E9q)m3kF{LJ2eSL58qbEKF)nH&Ix0)mV z(5x0>jtSdauN5T0iiDD_G`CkF7_rsi#ey+eogM<_(!VR0TFNe{lY_*&JzywU2T^ zrk)zX%@L2lS&AM8jXz`Yb%=nv7^X-B!b|22YDl`wD#A6jh^Rwd>tH<$E#2%}1 zM^4;VMM^HqL)yUdxXV+5)m6Iyar`2=nrQUDbr-8MBblOLL?*Z@*HId&N;?-9SZWQh&m`M$qD_QTBGVRkfckEC?)6K@33`dsZ_I~0lckC0}JlT1mAA(Fih zgWxv8TsCKas&UuNA`51NK$3D@P0eBn(Rhn4lTWB7#+}YGFY;I=WJPCVyd`rMHT>h| z00srRG4dwHqQ{M)y@v@YgyQheOg)F)@+dNauTC~&fKt|#?4RY~jd{A`VyPec{gK?9 z`pRGtzWw#V~=TBcZJ)fJ5nhju``U2%*Fni>goLVnZ)R2mmUxlts{&n*y zLDZXRE3ZVW`Saw#kB>M9IQMn}D6WvI*Y<`46+B}Bo?bsJVo|PQ2DF16W=*4~Z;mI= z49RK|Us2jJWG;w9{PD+D^8-FoWiYQPgFf6DgjkcKiimfu$;ziNqb(SNFDuMwYfrqF z6Y!>BG&LrMMp7#chcmg$7wYhc)ILE1O*^TDk6~;lwn)w)(uun3>jL~hu>2;{ZKg_i zeh#OEwAu4YFt;m`xFYpA*Ie4K>8;?9%fr{2gcI*zCH?i~Tww_i{1wXgbtz>MH4eXn z%PU>$PsJL{$5jLHs`o9VNs7ljIw!m}`m@)@))ws?$rqin*QLI6rTI2IM7QX za-N`);CnY1>pX>(*qfjt$RPYcOslyYhl_O~Qs39}(PR3G`5l%vn)4S}?bpfNBy9V5 z)hwikI6AQF3NG-s^5LiOtG?cs)P=vEQfEaU8jNYuDdkZ0AKT)i4ic{^i}obnX;k`I zyZ-}8>x9N}27MHtkf~Akna8FMZjP3~fa5D?jA@QyQ!$S(`Q7sUosxq~h6Xm)q_IMt> z>MbNuz;(PZzrEYXa+tFRJst(xU~1x#=d!Ym#LcNV`t-S&E@Bi`svWFNOym~ml90Nl;gC7^1sp1NX`w0ff22G?h)@I zxm#Ie;+^hcw3l?6$TZW!-(mDg*-kW);~?4-CL82x>iwSPALu>Fo$mW*GAeLRO_>Z% z1Q4?AhCa-Htt8$H;(&8~jb{_O>H~^@{estu+)~RyTXC(l@)wV#@34Q)`?3O3ZJmMIb$6coBCydbHlT&rUBl`2H%ZNI*FDV@zTSdWp^ z`d|~=9Xdfpb#S$sG@sgV;*HWx`g5oHXZz@ePk^LSPaeAcV(!zB2v?5c4HwU+yGlcL zd{;$Hf)o88F`cXnt7y}m?cvV7EK!-Sp2pA);cnKVl?4jVbJ5%A_6G@ZHM1;{>wRqu#G1_H$d+ zQ6uyN7-I2vW}Z$lShnvw-COMr4Yt~u`23VSbCcLOah)xG_?AhJsg+TFO$o1Y7wWVb z5;;*$%kzuseU&|DA2%Goh(sH#Qj?d?h`xEVyH8nMg2hhL87YLP+VJ%7gqu_MWBTDY z9v-?o^$S&)-CNIppb#VaTF(rf5M7cpYK8Vul5|7xg=8Kq4V4u|^8ggi>`CjAw9>gI zIXe@06qs9_cQq3gG9kY8#ZSnXMqXdMxJ7Ebr@=CwLy19?@=+}5ry0-Qg_KD4xg)sh zr?<}m;cg=@qiIa#n;KP};_+N6vLoR;o1Iiy;^r5lUc;b3`$5)f?4cK7XqGy-G?Xy(bm_MKP0El9pg_4sc zMc;X>AT6Xwy_AvN2z+iUQeT%*TSmsgdz%C-0i{xv<%OEG7sg*SvS&v8Xx}Qei%x_U z8}XDd9`L;*%DxyE{erAa(f!Q#HfFSMyp3>j6nEPMQA%D~);8DYXfOOSR^|fI>2w>E zQ{Sf-*yvzkQk81!PC418v+Vj7GHPt(vK30{Qbnz&_>#yZIQR4pKf{25w3>0R_td}2 zLgC?CT{Yt_t@+;pEs2Z;=Jro1wSTK^4d_$9Q_lq?e+^HzIPq}0X6LvS7CzpuANn#M z>)en$NXI}ZBlz=k)ZT=dr_@lSX2w6z_RDX|4~@vKhk2=C8CXXQb?%FfFCj~p=#UD_ zuv&FLTecABB%~wW`~NLh<4+V>_Iweav%`dKrR_?jaerVDz35peWtYHG9p`&i zF1=D$F@0o%@0ku+vbx+)QkKL zLJkY|AMPM(BlEUpvCmo-Yo50%aQDDr@T&I$uNTFdSX+Np2hV0c3e<&Xu!&c?d3m{B zQ}tmjHpzcWC^5^lGmUNR8i-kbeF$mv^b>yfTiy0UUWuz~?f{{xM67pIxyxbY({je1 ziyh1ryL_zPm<0jOA31*#wy%D_R&?66(N7?gpx}0F@@{-Ts1!1uHK8(^+{{{Hy|&bx z0n&NX_Op2Jr?&a}5~2TqPBzDSujr+mgU08^gW@8vq4i#_t7w!*%h+{};oA)!`*LXk z@EBENLvoO9 zOhF>*)b0f0qs#)8CCIOr+9xEdl-+Zq)pf}rygrr>NodJ zn8W7N*!`a>Y*)f0&V7aUQ)B9hU+qTcKO$O5BFe^Fcwv2t=Y55zWzE z9Ajq$_Dy9i1mAuhQUZ?~eY1Ks^WcYIE%t*D@C@Q8LE$s<# z>CF200wuYzv$4N}MMpJ zukj{i$yP@W=*eZtEEJaRu|e7qswrjmDjb&Hi(38g1>! z7o5hAO{LcX>>`RcA{KOv3&0o@j_-iO+L0fReqkQjGlxBMhKn_UF#3yPK)MPcCla!r zX^)2jC1hDJ$I8c;^J?w|GD?IlKC}qJFprk_*JwTtYmU6FPXOqD}#rVLRldQ}^YG#3x`p4i3 zqvQbg+B89y%Y1W)6fx(8LGJ3@kWrL`@8!ZZ^laWMrf+va$Db7-i;>qEmRF{zn1`;Tv&*06FpYG;`w<3gQ*dU_@|3MvUC;-x}V(#|Q+%u8*Y{gS_aE&{UD z=t&hiMUX)9hd}Kl60<&P%6#IdtwSYjVG;WZO3g7?Q^C#^bpCcdXRdWD>5jd5jWwn2 z{6TW5FdE8cx();$Gz_3=T$yo!22B!!V zvfuh81sA{Xu7PE$dk^z$J;kMz>NlQlP7KbZV51VTWJpFBR;RCze_pgJqc_xCO767E z%oCAQC6x+Na@8r89eq4tr=1QT$V53|!D8kVodjelz747{hcW3Se8J%Ji~(6^IOnII zNvU|oP2;Tv2G3C_3r6PSH?ZwXO=J5cx&i(3B)IKwKDK2eh5$xN7>62y1H&dG!3kse zk)Eu6nkT#3yS0h0w30Pvq`Km{h>OC>=O2?^8hR;y31uygxPRq4!20nSL!QRn&aVF$%AbB?%( z;HSCCdXrg>fN~wq@t%42Wc(+)*9HA9jzQab@sRmMKBINV9kJc{5D@>M{WcyN}x zfSdlxPsSztEcTc^v0S<$j^CDj!1U+X()o&SwyVy@fmYuo(}F?7mP1M9*E|L1hVLoT zuV&SHYul?g(;Ox{JoSafYThqf|KtO^Pv|iT9sgPWF`$HIdYW1ln#_%-x9ru-Vm4o?R_i8`m>pu>`^S%ajiB@XS!>K~;%)545UJU zMtp4w(4k+2)}CJ0ydYLSaoLN;`$@tPCD+{c^i-kArcDAStkfuAr_JW>WC~fK@l4V5 zTJU}KQOo1bajQ0?_VtXtQMJEF#nR4qy`;Yo(qPfK&gUEQWzPA$9ah4rkaanWwjhIz zvTlc`AHR=K`B;*pKJ&c64Ep9^mkRdXED5^qS%2$)mDp%SE?U!NgJRfZG5rbogAEia z-9t}B>rMGPD?8plDLZ?YKQx{l3DgR12cgs2$DHAUK90z#i+>;{t+V&rj)+CcJS2_* z7{P0I)!G@MUCHQ?`=*(){1hW6fNar8O0?14~Yl}TG@vzzS@u5H$qTANWXsRQ2GpDz9i zP2!H3Wi0vbxFInr`!N)8{4|Q)m_JvDNzKb-SSY#LvmZo{ZXV0-5_0bFY`wRB9>AE{ z{ndL)&WdZ+OD0>jH7B&(kLP*U@^`mL;i)hEYWg=)KeX@E3+`fdTj$)Xq*lMCw!bna z|8W}g$1`U7ud{<%8eLPlF?!OHJHx%QEUI%r|4{sZw*L+_r* zq7AP7b4Y}yBXg`=kh7tEbfvDfDZi@Kw% zTJXjn>B?s#TSiuQR~wtkY5zbB{nWktr(*TpgSwi8c@5+|pmWa%(x4n( zMCa>53ckHI9pcK$X>bns0^R;9MNvlNUB0`Vf#iOlrZI&uwAkE6D@ElYr0-~l{;Y0V zLKjs%k#(<4G}8Nw@5h3=PWL1caOlPJ;rqAzyj|;GA(J^?Z3Sa5a(vq;tUw+3U00`p zq19$k2hFEYm(5C^@7-OG3b%~a|A8=FRwUlbI~|1ZXt&+5Y~8TGYXsC8F~7f}p^b%Y zNCbR|?s3OY?bijk9tmwRZx=?8xBNvaL;&OGb2_cd7XDjj&cPgoHHQE-w)>TmR5F_V71m7RfZ* zjamZ~eTd$|>d|d7aiyD&x2HxP*+G98-1rVZoLg>#dyYsp6Ck5s^CzLz`a$)op~Ne@ z`=7P8O(Y4SCgtsbh-9Q_JfNnfcSXrh=zeqZT|NBnIVm75=?X|o#*A!#epr9Kr;8*s zf-pZra*KU*IDN{Ul>xzU7YqFZXp`Jd*j?E37#RL_g2~`)l8;1`ohXL56`g#}*y2U_ zi2eh8Du2lOQYR89%pHt5_XJwiGKFaR+7mHgVCD1)iSaX3>aOcg_Q;k=%R+_eS|4OP z=RMmot1^0dQK&~yy2ppAVfadMH!pqnIdoux$Oy-)C&;X)Pb@vyQGw1%)sV=zEZT%4N&h*sXy&y2geN z%$0c@dOY}5cb4|8aIXt;bU7G-1O43l=XmIom+B~X+Z^1No1xqU&%g@R1t09S=vV3; z45rR@Vbu2yHEGdKhv|}Ks$>HQaTlJv%L`5fPE_3uBr^-@NTiX#76w3dl);c=#)Q`2d~yTc$K%DY6!Wych^so*E(0;CYuN1j5*U#(u~>71PiGJ31t&_eAhTaKY5dro^WKw-;u z){ZQ6M9k{?#%(ja?CgX9VVYS4m;WKn-V7i>+U0YXh}RGxihFR zc$tWBTaWeas`pO(St$!S{|uQ;EixcSbv~pIofY<9R?7hq;~^wBTSWoXeXLar!T!Qh zIJD3r(0Td~^sZ+A2x51) zt8HZ(2VIp+wANOaP(LScIV*919=s^K0Ph-{aZ{qIi2)9o1(2chQs|eC-aRy)v9}Jg zGL6onMhaF}zMm@qUtpj`tc+mqP)(>E<1YIkrk;5lFnk;u&tEx*oeTAUKXlgP_~hPw z2U?hTS%t3FXY`NTMF`1iktyH(13dt_ujP60oih$pRz8C%mpg%eT25aDQXvEW?lblN zRi)TRm@}%mx)69|!r4wM%~8WE&UhdgpkPYy?GQj>Z8Q#ucOCWfs) zV?kr7FROe%5BwN=k#Lhh)P{uZC3nI5l~gY*kFIzV_tj>p;f*S0TR%^rW0j%$i!c5G z`6r6NMj+xA9lUu}Y2MvnoB+nXVYz(^oCOa+T^@%Lx)K2kMD!KxHWF3JaR;tsB+5JC z7Q-WQIN1|IVlDgwMNIFWaGF|-?`N&Whdy$JULQaOEM6R##Ucy@4ep34(|Q_d9%5!H zzbcDFR*kJ#^$-Ql?rVtl3Jo&Yu}}`_ew)PGDIeL;QPzBa(K66rLX-Z7g{Dv7pv?j+ zy(G};%@#kzag$<%C;CihtmB5v(B-Sf%|+WcNp3rRU^Vi+fC&W>UzA738 zP#QNcUsW^z12scBOzKeII=m(!GjL$9U~P)B0i*#CL-Ows(*E;GPp!cw%arjgX-J z&c{@m+=`j}HH1G29nIw!G^w7N$u6IRwP!)ANUxs9!rxrlv6D{V1Jr|ZZRqKvph6R< zL*7#biMII890U0k74=Wd?L{(&aPv!xE9;56hivxOWm%O|zsw3NLWe@1=j@>}N&i@y zZ=3kBBLeyB-i8i_G8y0UB;zu`;XRmX@~H7HfGr0{MdS&NSi#MyHf-PJ>O zpP{lu?4ftJA4BYFoONJ`tg7oLKSFjAmw)NKqELJS`IG&~izMO@?T5;Dr@$;*6$PMAb~})XBhd`cU`RHqzH7TJ?)U-HVdI@Ut!K zLbmhx_t4kC>#M7L;YGkVBS0*8OYt1o2k8j>1Nm~5gjY;hzBA0zeT>H25Mu;(l()Z| z{net}g)7&7HQX`4pi`XCKkJS+rjdO@m21hJ_?t=|@Am(JI=Sm9Sl?XU_}T$hgX30! z>!XV%ov#plZtKFkOwmQm7T-0GA3BkU(1rcFxH8%up*E37mKE3omzORj3@$LNV>=RfE_!{{T>aB?)~w@ex0)M?!id7d`_u;{1EQu zNP=yp)FsmRfpT(J_aJHfM5F?#!g^7fUxb#nhp0?YIq(bB@Guf|eCrk^&v{UAxu;pM zHTms4c9G!v!)wObsC&2L{TJ8-?GWQZqsD#>n!=^|>PfreHe;?jRIS%IK*(v=h4=0F zF`CLn_t5u)4|F~J6gN6+Sxd|MCSXm@+{R#Xx1@WO$T(Eu2q z&SYwArJb&=FnPOWq1v4#Op>-&?NHy&QLV#^o5J@=fd8?<8@`?-7B_{zKgL14u zGq1WsN;`$I+k!Pm(43+o^{Xm!X_OX6FS}aY0gHgSRYIErq8n8Os|? zCrqFAG28rne91XiR^CLl#V>~9>isK+D?t7u86OlsMa3bNK2Z0mvFP=HY6XjR;~a&U zFDSp%usz8pm34y5txXo!5IotjvR`yV0#|DB@1!$(uJu zm%C7fbeA@y*V3ZntGI3Uc2=Wi0Kef{4slwX=IHers^&>q&O*2M?SoNjdQ~xw!}<`& zZVrSw&CqWX##7ov7AHo5A^tOJ=Y`SJ33DaiP@Hdum)f=mm@QFz!&7%nCA{cV@u8O_ zH{YSef2H24-Zxy5_Z_VEFRG$fp+aUOS-Pg+mfN*KZ2vmxYvL{J5s{*2VH;e{LbIt{ z-M9JQagvPV`7SrY(|23%G4db(=9kSUJa$)zrU2~)6BB`6y%(a$oB(M)U-HSvU&xM5 zV=_FJrDnW*j!PL_Fd7cj*#pWaMrj5oZ#jfteWq#wV!af&9LpWDg|U>QqsP_J5>ph$ z5+>pcb8&u!J)G>*SgIjFfyc; zw8sm6@|mS1zfH}xYX4|j0>}kV>HDA*KB|YFP9EAUlHKQDI%SljTP@M{N;zTVHlP>f zAl4H?laYFChKB8Pubq4~iprNzHa?Y&z{~%YR8;AzM0F3yPzf>$DQ$%aEO+Az3%C6Rq_m0Ny-P77`4!qzWho!IiMx(Q=pty-0zE-GvdbDD8s zj^VFleQ6y(6{J5QApG?_1FRE2^~m5;gtM-e+6*W*gq~83B|XZrrAC=T;h+CGHi>9jm-gbURZ1F9DLy!{K$8HZazBa!be?~+JFTq;t# zx1IFsOs<8yFW!*a$PZFmbqxW+mU8AFP_i!wT@7V$(mgoB)th& z|AKWFxTja=N6U){H~C`T5XMOtC6P~58QVzJgE@`EGf9&>2kKoaZhrLAcUuFeG2q#> zx)BJE0@IMag`H+VPJTa;b0i<1Z)vy5qKfKSilWvI64pvx?c2yspK6p}HM%HB;MXHg zVS=4mw}zupKIwuZ$Bip~90Q{C&&LzpBGkiRkf1F2?J#myEYK`u@^grMz5`>YGG0bH zCT{CP9R9$Gvuiq1eg`*OOVIvhBCO(50=v~*?kycI=r5&R|Kx|kmFQ?8xTF7dLNvu} z>uT*PuzWvMKSX9bu&*tQ(AOsyZFj{4rp3N9Y5wKp{0-Rn4aC28b#rrhE5 z(i2fR(d!sY4)@6I#Md3fYZ!8EM|F1nyc4i;Z2#0_b?}=$d6l6!St1O-n03sCnVUx7SP9cA<3*|y2O%4Dvd8H zq+zTcc}{;To~7XV(bf|^&e*T`{B>#J*Bc$d9D|~4W$&_;;#v&-PeM&&`Vo9w$+1gE zX#7F94jp{rAtWbDL;?I~ZZ>g@m&8@#RSk@nL?Z7==wiEE!`%6EEKnV|T2HjJNqNcx zi8Nx7E~#s?c5lRVeMxwf-%RwR#3ud^;Rsy4Y5f`7jJAMk%J>cfQx4fKPGKcDXb=!TJvT}bqixSi>@e#G*h|4LUB27W z1>qyw=S5_0c?$ylL>M;&R@%~(0#gb!N<1-Xm-WT%Uu9{pCSGxu2BKj=NiBPb) z_}hmNV*G+C|Jo<(Tq`|XcCHiKCmSiOO{~O)Y8$wrP(@{Gz2*$EbE=gmj-#vLfFF&W zJZl$c{1W8DUycixOkT-Y(8bLdw0MN>je}EkHYR@<+2#F!COxD5UY(5k$d6>X|66vG zkM3gvWRC-ka`YZnU!eOUD2_;L2eAn#nbS2N$>B!vZe@()U6Z)X8n=xYZ{%9hxZ0|z z2lpHJ*Vv1BH_fUV4`%dPxj-OC+-)}ZBurb+Wj<_QRC&};==u*aLG8t+bDUVAL=KlK zBX-~Gn2-Kutnk{NX|HYg=&IvJw5+{c#QzQ#ti)+L4noMyT9E0eX-#{Bl#70+YFG} z{&faka<6lNL|>Pg=m8hTHHgbZjWOFg-i2`g1C1FE?fwHT6gVWm3LvL~M8B0B%{#m( zLg=E1Ahm^`!P`sk0jC$jMdj=k3AJJVxJe+EcZV24;xwE5Y$rvkqtR90;l_M^6I@WuEdwA9$26 zvbE?07c3~a?nH-u%8B*khhUHYIgkFm#7}2^JK7o@r%0<&s%KB+YcvS1`=hLCueldbw~&_RE0Sb zH*^dumqxbVyb2Z|_$zmB5MnoK{PIjfiW~T4Y!WJKRPNICc5634%uqjdZTm2~hqvui zcMDOoGHnAd6D9`>Rw8lAP9Ib0JfHTzI0E-5F91MJ-Wx_n@LJhTilH9grz9uwqr4L3 z1)Yk~vL&lCO7vFA&37TIv$n^V*K=u1FF()uj3?IQPb#66z}+*-mqmU<%!+i!Z0< z7K38Or`cxobRqwl37jU6qzSX@JqD7qsyBw$?%Dzu98H@=9{~Mtj=qit#-grlscMtx zebwb28j5nUTG7Ww#me+g2Mf(BZ#qGzw$%}@!@1eZmuf?wZtSA$tkU?;B^jbo@iP6; zi)#KUWRuqQogR9&XbDnZwiJjI{P9OH)C%R2cg_VFg`Abc*#Jn^Squr+8ZAPoWn4wW z^$~_$Rib{ch)NZJY#WPdNkCc9!d;EWP)_i&AY&dubK*_k6=YW(p_X@* zh+vkF?&E=uy@6n%MIb8_|3F`w(lr4@Dbl6udJYT>Y&>}73Hm>*p|kJTc84nxTEd>m z*I!j#Xnk=-@6~rzwIS^*f*!ZayQs-W-yWNUc&s8sqLJJ%$j=9zn;xh`U0=81=`U~^ z0S*5dT#>yG;d`1m+Gsojb&ScyZuw+ulM@2zO{`vnG*qcbfpH#R%{eYXBf+NJC%#;O zZZ;`q+&gfZ)k5-Qt!yVjj7;BE`9m+SHIF(_$ajmO@nS3jg0uGQhYjt}I za6jZTynx_;cLN*9cWpn3UPyEU8!9;VTs$~ll$|F>&p-LlGXrFq2`D#z>VyGNo?aFb zbGqNJ2LxYj*?Wa2h zu+?bMN)dn)vVZ7}`6jRTJ~?CVMD(?k0QD_bNCgW*#x^>t2))m4fD88-g7%6QH9$8F z7-!kKv|-}@@0A?@6)WS!6Us+24CWzy%1xia%hv#F?xV+HxYkO4-r2*)BDtZ9Iq!Z* zLn%M}$#HF{{(r{~B0ku!V}YE4&lzyDGjXTQvzaNft_CX`?nGkNzAu&W24t3o2%3ys zniMwFnhBD-ljf6|o>*Gvr=PCd_m5t76tuT}Gc^)52|6>2RY^A<9igElE)l771#QX` z@5=sc%6syCEKlusq#ChgA*5linU4YIdgM{aS)h^RH*DT$R33}ywcPv{d$CZ1dD=!A zDeTt+!m}d19>G(i&G1Z5sN=kF6;isQ9l7CtRrTD8xLGJck9{r0ozTUFM3TC{f>xYJ0Yt_4-}^i*!KEKNE1nK9l!)6OY|PZ%!Dw*z%T}uw_Uc=?l^NAui7W zGa3JMC!8zQH1z8q=)3Ylq0t#`(2XHGoS9#R8LU9z^^2sUuDZAt(u+G=GdEZ}?mwF#a{}EI zu2xyM-ow(c?gkK?PDQPTVo0TiUH04XP;Fe~BR#lJ-1Ep}D}>BNj^pta5T%q(Baiao zmc}iO_up+pV%r_-2qjR}vPJvH=X7#&PUp|4lUVX>&I(779@srX>~y#+sqyBiN0b<@ z%>E7kQ|7HzN#vnW7IV4q7uAk zE85!@P;7sqqRbzQrI{3vWiX5oY*6R8P&No}I6nmx%{u+4 zZm9cjiOp{be|Hxf3VLt}=rzC(vQ=Ge0;!XZ^yELvrT&5RI@Rw>BX5=?BoU(#&2ZKXf6u zmC7$V2fiRngdX_sjHI!BoxSHpzVW!UzfGo3(%)VAaY$`LE@ghK>$K>_7HVLdz&%+` z^?o2}Z3D<^dSk>T@+?*-21lC}rxEjft(Uu zOokgeJnwn6d*FU(p_tLg)}sap<@DusIrIEmW*0wVUC|u*VG-h;+M9O4IEOOr z+V6oE35{n%*5tZ`;ybY~YP($5oEiwogb_xtG6yP-snGkKntc;Ww$=dc8IO9Qh5dqz zbY_@@W^B+v_6s76QSM>pJ#8wbkiAleo_wCT@~@*33>9uvika{0lh3D`s40_Qo9#HT zi-=K0bQZMJJ5=n}KQqhFcrs_s6ANBZl4}I+N}sxw;1ca)-qLY8f#G8j0Y|yD19pOv?LzHpLZ1L5*N;jx1FzI2Q#@5yO70$z@jCN?o06$%L&ns0cwK3w zM785fW5Xv#`~OOFhO5qnIbjNC8DFcuecT%M#`dZd^XwoMqY5+T$>R)6q3&`_g4n7! ze9%N|*5&3^RZf+&oxUo4-p$+1Nc(fl;Wq5U<*A9Z-4YpJs#jV84AWo^Vei^{8p=X? z4{0G&Hpe%g0Zl^@Q=6uEn(fiQ)k-Q)2!pgiwm9Wae~yVJb4&r9uCG;qCRldPfQxg* z3m&pieh@P}^63=3kWM?Vq}kCK(&kW5x&A>CRnAl0>Om3+(3r`C!Od;IvW-8Haa4TH z$pSQ6k{;eI#~n0l^c2!jtf)#%yo>-|-a-@ioJ56{MVwP2L|>AX(3dl{G+TxlOi4?V zZp8#Ao+c^_q@9N)cdpxqE4!*D?bjyRP2MdZdJPw5&If&`@r(_p&-LFZPhh=h;-)#T z!8O;$eYgFMZ{=h{<_kvPpY8S1()_xs`+P!989cT>DQCM)8p&yA&+c@1Gr+Dg^B%9- zCar=wTHA8|WOyC4urAEV;5yff3;j;b%__Gy{{0U9*ooV9AUhpvp(HYn$a^b21COw| zlqolV!KR+U{o=Q1^VKiEQ2|+@$V>O$cvU`#=P2#65L=D?@h1LnOf(ARDmq@&<_n2b zrH3INNOj-(J~(`7e+P?&_!!nL)u)%)4J^USW9HIrR5mQ-{#HD49N%~|05G~V-+F4R z98r@1n8?hhFeRsF3ymwMF*qj4!TEE96++gKNw@TajxAdRtD0T!r5uY+@VLPfvqtTD3`4mZmfkpnBC)!%(r*buh6&CLx!-&W zNn@NoKrBLL40y*ahwKqjZ|+7YBZZ<0;Po5K&ilAd|3D@02N+$~?l$3LT9T6siYGPD zZ~6%_j^mw-VG=gz`R*x)mkjv5;;R}BmfhC+xe-ZpFQkIddc!-PXFIe6JKVB1{2@b* zvbA$Orb@=&Z3IGujP?6C6_EcxVjtUBf8<%Ar`k)?0=9p=Brt>Rw|*8=2@jVLpYYte z$Q>=X@Hdy#8o&L4r#p+S*%xzLqB)Wm5>-+DY9RXOE6K#kl;Vk!*l4eumWr3*cMBD1 zlbklkNvJkRM_y(noF}hZE_&4l{Qzh)B)&lqoWA(Y4v!=FkB#-GK)E*KjeoWc)Oj_b z9*AOx;D`2F#96XsZdaILq1z#dBV9O2Eswr}PI=MR2T3}hzx>FIp0oq_t;FsoIWFo69SXDZif7JnE zgdWgMo1&AZm$X&ynfm(E+0mAOAKp2@Uon4X1&@Cr!%`2vx&#C!xX_h)b^HQlYvD+p z((h(K6i%|M!r%-@w0@k3Ci@9I!Pq$T8w6;wM-|DEd=~z5|GSZg;dQ_Ii+m+C$1fwa z+4ntHt%^AdLiA6?WH7aJ*zN@6wg-r)C6;UcF&VVi;tgaU=!Z_NJAIT=nJx6(MaARteIoKN_!ihVnU8Y)@jHrwW7hZi4MvwVG%dVuewSg4$2Em)2ZjS^DYuJC|KcN;u9_l zQKyPTv!f}``?(+qx*!!2YUlI&nX@HSvC>!ALRKZfx}XDqB+}$vHvD?v}Ss7EVEPMCHnz4u4Y4l8q2^O z)?F(4fs%35B0f!pL_L$_5}117_@R?ZqaLgXeej0XxPB04c95w}NCne6R6w~tS!N~g z4fTtJ)BE`#2|UBnNNR0p094X)RIy^N?>yWWl`nm-nPEURIY9Osp|{ijnG5yJf0QD3W581eJ^}_err)A(9^?S2A?(lK(<0=6 z@;q1kfv^JYdFC-IG_&HNN#7Awq)3EbZ~6zyt-PPPQ7eWz>yn)RaGPb{at<-f2T%y6 zKJ&1|j;?QDgCwZSEbaR->$w;!{iLoYXZ-9nVvomVH_o{5G-j=H@!=yP{x?IYB8|8c zorL+&9>5%(ew|_It4YUF!{Y^Zwc~DKaE*1_@@V+S(8iys;6>-p@V%Rgy=#`!B#hu_ zU=ARn7oR=*sdua@7kjE>Lsug6<({Uqj!NuYO`7ZeY2pI&hjaNA;4<6=8?dXDj@I&v zhdKH4*b@%;TPw3qgFmrm%a#R0n?Iq~NJ^Yw(F@ZAY4FN@Tnl_RT!gS!mgKN?tCmY} zT!ygFd1bEJPUUjFbOptDvF8V6j-03v7;$gjvo;_Y%%5MB7Mm$`dBm|#Y)#nlx5S#n zXo89ciO{F^q1S;~kbQmvFK>{f*b;yDx1_wRUAVujiC1w1V+{WR-^jkg!>;fVW17^g z7+iSZNm xbzp?0M2ur2^K3`(3Rdwf-}idw-u8#>>w$K25*nOiGG&4E-Y7bc^w+BX5Uo})%fd#~-jC&XjQo!r)aDDX<&e}C=U{+4$PjRDI4B+sC}Ww%kW zCuq5^P`EQs z;mSM%=N>!v7eS#EUgO{2MPqonNptLH1_g`%ks)z0Z{RfQi(X+V8Dsw0#1wpof;E0v znWZ~aK2|K&YAjol+C!gHB?YO4e7x2}6n^_(+uUCYQ@L|aIEkR71Wk~d^#OZ+QF3%+ zv~{V0OlH~W@cTDn6KWDx-Vj%e+(6QmayI3ruwR0-B~PEWPt(q_R~zv}o-;?1&_2xW zCtEInX3H5)zL?0&vOG$*{hjS=a~MV~hh)DhaWE2ABm&<;)p!ObNCl(s6#29}aIHhA zuL^toaR&Z^)3}Mm3lwapLk&blUo*$Ol?webn4?zP7|*vB7sAQDb>H=Ao{U148@u2R@3*uBf2++FQZ?Qlgey=+o)upA!W zovkZvEHsIf!yoMrH+k#FlA{6_6Bc{Jnjp(s@G^YCY3SA_meSj#baW?a^y(c8PWG#} z1>UZ$b_Ur`7eyyos#a(vG~ZCz8Ve~*`kMbfs7n)2o=_xs;7}6CdOhqd(CcVG09M#vo&@q!>_RWxJpgR^}yLfIyRr$p~)`pIkmJ zb^ha6PZKh^1aIxPpUes82vc}`3NRQ3+C1U1)LDS;KrzeV6p8) z+<{`(7KaVHZp*+6P!(rT9Es7TIaYbI%9r9t?4<`%kD> zm#Wn{Y3hfKvOky!!oKpW@J!H|g-<_dGtDL&V&UUh1>z|XZy4v#auf+i=FLV7Vum(? z&dz#F!)UL6&;$H|+26d2B?84SI9F3+2x?CQq}t?oxZYCn$yg9C&=d}MeTjHKJRzh@ zU2EBz|5#=c8>C|AzuP;F4UshZf~Dsnyz{C4a!JR7bCNyk>BKMxUdn!Qb0BR6&5_AKvY$C&8fQHFg)wa=mMzV>^G$JdQtAU4sZXsA{}|Zf z3B|OfmErOl+Ih97EiTg|v~h*XT_e1k!#EVSml!#)if7+L8bD$gIyUzSyULfr9tv15 zO7p2j7YCZJVT?BO9K?HG8}dFc@E2k*4IMi6~2dM!aS!jVYM}on-^M_m9TbrYGf-q;mRNV@Gb|x zf*j<>+^F!LU50#CK5`ugy79EKwubCZascFG1KQ=DZB(hT{kll`mQ_ZM{hhmwd4kKK zVeW1eYYb1^DoYd=n`+e&$?UMV>yE_M@OS((xknS8sx(1MtknZjlkQQlakQ%y&9BQ8Lyx+gM?sMi`Gw05| z_F8NA$dzxQkeHRK-&+o@<8@vvR4|qFr+Qlzz4hcUsv;M$%HeJw(vmk2%ddE8>ezQE z%b?$}`8ViG+ZKe#0^y}?8YdRpe$=r!%OmU&j1&Ed+7 z#B(N6>L`hSDmvJ!Y^bbld2jM)dK{o`4P|IMk#YW6n}6d4AFjkeGfC$v+<3eFO;8B8 zurV*yLkAPHq>M4%fr+^K1o@l4t)M(8n+s}lp&7kN8b_TFug9oeQ0G6qFcGRa0aR96 z+Z`EvJ1J8(N@k0zG+5)m{4Z7v?WCnRSfOjyo#-_N6Vq|lnbxEK zM(7o>^>0{y8zNLcSDrcki=6Ev6?c zT8@};Kz`T}Ex!XQ-!~Zo<<*dKRD!0dk1|AWH|H6dhf|bXS*_%&$ZaSD-`c#dO6>m2 z^yck;Y=e3ic8-^9(r|)G{&cl_88Jot=n#q;IbeTmEU8X1>{viUlBx>7IW^5}9Q;tx zS1Gn!RXX{{;Dc$;M@AXS_MA(RP?C)w3`6zmqW@$^r>Y@afZXh48Hol~J@_)j7+mqF zd5jz2OldDF*zSZl8AL(j`-Aix4j=AfLP1Uh<8@p`1sLY9P_9h15}+xAwnyBj z#(6UShPMlykz=eaK?ENB@f6lBUA@@1Z(&4}Rgo^Ayy1Ycru^aK1oOx1B!fzKNxz6Z z(`1!L(*r{IbDaNwC@P<8NQ~%0-fMPn)t#zs-A@w9%mK2bnVV5Rx-I6e49!!TQfrQ_ zu5L&hd#rRD-kwf@jueFv@~^mo?|&#CRKcf}bI-0c<4A=Au!DxM_X`IgTL(#p)(o`I zx;a4IpbKpM2QENU1W(Q1GihU^)!y(fVTLu+);IMnVfw~#j0w_hT% zaf(=;p8-y~zNESI9_VL~>@#c!fuyH)Bmh0S0a^I!o({G4mFk|6?f;RWqp5Cx&;q%} zMK@%N$?)GZ&-J>?Q1AtAkYz#w{~7XkYdmBTf@}d<6nFk0C^0|B3H}dd6Bm-;6qW5C zHEa35D@AJ-!*y25JwkoY1_U?%e{&&rf>Qf6V!-k3ZVdnyR9adTTc1=C4Zx*WbniAn ziX}Hk3hX4t1)xEWchUhLKo{)NKh^&p%?AO0J|d=cWML2Vcm%DJk*?8#B1wI}fu4GR zKqW*kBoOm`f{wp5i-S$@T@o@C*^5pVQTbbG2p`X{EAF}+IUCFOPz+=}wc(ZnI186N zvjuw|_*E0$oTg8@7ejsbZbjf|Ab!utEVii_Go`aZ6m9ua{dh-*3{8bNmg1#myz6|j zxTul_Hv<8Y=536nK}g)!?U6{d|4>FXJP_K?`3cfh+=E}sq?}ty zg=9#V*pZX#eT4IQJd&pAI0Q%qwX?nJvU(`dS8RY??cWlR7&SmJR&Mh4}OD|LuP&AGh?8jn8>j8$+z9jCFV{Ux*!3NLyTFDSdU zV1zvJoWF)l8!W;zA;WpMK0qJXSz&0dQ1zEfyEu(QR%MYwdKZM|sJ2U~9LSk<63zRB zW!Uo;QfU6hw-f&nOc-r>n|YDtvv3-<&7LL*T1HF`DLt2Tvrb$dFTmg=m0@zmp?s%= zM31We>d&X4Jbj>xf=5B;=Zpm8JBnT>ORQfbx0j*Vkp8Bhd=NVrIJFQE9=L_Y4EQH2 zJvTuvs@^99Ka}TQ9@GNZqT95KwRW5eWNOD@QsYq(U{d)W5@73TeQqO|bqQ8RWiRYgP2!6%0YgmvszXOOAaW*uqhX(V~9M7WWi`B?R zs1y%9nAw6#f+!NQVQERQ$&8Dle?&$6T~8j(Cs%nTZfE?@IbD22u!stPC|_BtjH{br z2S$?V7n}wFx{Vl%;gIhmh$$!WU*&C&HL)cxqZ%rf(N3UyD;1%JWanI)!djcfs&+64d^t?AO& z)A&W$mZqhhX^%)iS|&9E1HtdGp08OSCb;(*^oPp^U8r;2-hwXplor_h$j<+8z)oL7 z?_UBrk`QcYOzG0BvqI-#Ie-BWeYlT6E`!ix2Ztc7}mp{?gO zI4!LN5#a>hJ%DTs)f!uss+Gkc4c;OWJ{0YBf$rcAiOg1daL^b4sP@nP#d`V)EqXm|uz8)J(9p->9?9C~2Ecx#ZAJhrCys-91tvka1ErqD+x4*l@x z=O3E8lzCKw%gEFDw&y1jh<Y1KW30DQUMj(I7g*vV1^_e^2`nF~ zb(MpmV|ABNPuvokz$p(vcY6*)bTA&N4KI_y!$C5T9XeZ}V{{%`cyC4XcF98pmUztH zk02k3k#*{;M~K7${1IF(QSp`}0EW|f_{sb5Gju?6M2ZLb)xa7K+$5i89Y-AOGmB;J zIUY)1b#yJav>gS!+^ZQC*dVFp|B$gcm0SI;7+@OqD*uO45H=3!i%=T?AeXu|2gFTL zoh6|$@^uTyuxQEhr=HAT}Lzukw6!q?!k6P6F@nM;6Qu+=9-# zpqkJ??_NJW%w{7KOpApkM$JTDd3brllM}K+Hoa(~v-}}}?%A%s43<{r zJn}SQILC&yz(E2~!Tnd!0et*_@>9jSBfUh+>Um95XdKLp@VY%4`5?rDr8cTYA+nYe zY?%;61TbzwD5s9*7Oi?(bjRHhO%z9$p?1)Z>VQUcPiBLRF8;!{>XvxYKDJ^xy(W}h zPV7Gv064sxR~z_CI5-#bhhgn=`#mu`=iR`N55zy%=}wOV;(#eT$13BT51^Qh9>s2yH>A6BaBt7I z9PYh$OXV$e?^_fl{gX|R7P`Q9PL=m>TY~Ut--52L^X`^P9t$2#iW~fsXr&j)wznId zz6m@GS&cc{!do4t9;?}@ila`C@j}d@Dljc@_dLN5VQ|g(r@-yV``W@!fx>_?_6P67 zP7Gv=!R+D~>U342&un-bep?SBD)gIRG1R~@4$qL?xJbi1;FwCMw+F5P#|le?O3ros zB5{MR0>okJUJgkZ18H{(Z2|8B^0-GlTGoum$gIJsdSH_(Tf&Ove)SmHS!7UjVEf!) z+92*9xp^ZOd-65UT0d(c1P9&B>j^y8IcuH>J*E5^+1k9`QW>wl3$0|itpci@HZUBg zDGE0#1=iUUgm~6{j52|JcA6&!*Jw7FKdCf*%^PbovKct`IvJgcS$u4<)V~sZ0BD#O z-!{DkL-Z**bx(K^by#P4W6;Wx_MiY?e1uvw=qM{=-q8K_@`SJ3Ch+A1NFVCi(_(n9 zK+A0nP-uWm1VkU|{xEO3%dAP747BuWt86i>^;S>a%OHSpJq19_05BdI_W15$4tO7O z+jpnH7TX(^)jx0_Yc%py1li)tKnCDHKCt&x;O$jWKH^!Dzh8Sv_P_N!uX5l%>Soy& z6YjGH@f-v87MF#LdB@&nkVW3duOlKGSBEeELures@K@zHCA<=={Tc<`1BJW=G-IV1 zbBA~MrzYVQaC*kc<2ajJe(a&m3WnNTN4>HDxSvKV=;s2b)rei1gOQV(b%332P*Xa5 z{}ihL;XE)4FpwNq&DQV;$tP6=f98B_2&-cU3Il*n9=A0S>OGA9-z+*9COd)?eqz-F zki#zOq~YW21@g~CV3oACei%A6Xz$mrmQlj%-1M1>?YeY zz2tgJ=*On}t<$s7YiFRN7!2}`d?SLdYfXJnM0uCL$`JD`v1@4$JHfoJj0N1BfumVut z|Mgwx7Z^86r=le1eaA-0amQmt)eiz}US){-U;fH(&j_CSMxNi03};rrke^oZGmtTg zN9m}HQBGKulE6+;IZf1?#uD20xTKtGO0>Bzr*PW=q-24Lm5(ocQr#O z8pMP1nYckey0KBd?D69TYB}+;w4m27x_>P}{m77}sU3?=pBgEMYGA*@j!kD8A5YMi z|AL9?>^8s0%AwTdrvs-_sMG;k@nR{%1~Dqv&&BUQ%TXIgU**5FrugQkuKj^W!6*E> zu+K2}1FB#(rr#>Duz2`4e0Gvi6Q#^tzJOJx`FxN{$z2 z0@bR^JYk6#BV1)eET5CZtd$i9C8wN45F^^QTZZ?U{e@Q7+*`e^DB_oW>DMYO7B?@l z(!ae%1yG|xjy56NZ$qLHk^WPvzG#DO1%=16Nffhxy0c#BWa*N5nG}Cy$bgf@6tzT0 z3Kc2|*#DS4!*8}Ds4PcNaM zH8NXN*XkG_fTytbc%xMg?D5wk#hZH8Q%662LGom$KF&KB@}56A2)({qC?D$jep;2J zJX%FC;cU3>@ll4_HNTipHve@%OD5-t1yH?9lTz&!Oh^Rv7|w5$L8VE2UrpK}2_j}* z<1N5ZqT0O=YYnFG+y*=m%QJy#s<8koMG*R0ve;7J|JAWlyhB0?XDMw z0>VoVZxHSNp$meU9(}|qG?Q|JwA>wYC6>%0p<^-=>OaDZymIDB7H#7HLn&J0p(z3g zw?VoJ=xH5)TZWP*cGq50?|+0{SL$aI7g$a7#@|%2A}42+{~RcSOZ@((O{ypkf-jO) z{1{&Oe_ZH1i{~cb?>eJqJ5-n{6m;3<6s?}XZfl?hs0XcDrJp%d6;EZ*3ai}tjO&@N zNR&)u^aJKnQ1=RTL>i1;{+0Hb?X{Qj<$m2U-Ok|CZ7RNb{U1v-fTQR%; z0BPhXQSJM<9xMk}SjiL6Vp;Nt{ioM4#bVuw;Daw}{|%NU&8$}0Um5p}a;ID zu$!Mg!K8F=?uqZ3)~r8a(=5&YTr52qtU*-B=4mQaM>oJ?=O3^&$w6ckf2B%^oy@wQ zh37$%A&ONZ(!MPG-ZNi^o6|52CPR+YBVHz3#d#C(!XU;7C&g)25LIM8UjV3D6Fi(r z+ChSm&P9DjJNKPZ;kHyWFiD_vXIk~G&8hLviS*&L@B~*Zr{>5T_MB=TsShD-;1Cgut!-9>Rc0J#Hmnx@)$z3IxQp# z4b&nPrtdWBqd+GS!6829UXW43l5RHRv}#Uy3< z?~_rjPNqcmB}*Ft>>V^1T7L{Fi(M#aSkY7Fca8ZKUQiz^r8wE>(z9E;vcF&Qo^-Y0$|DlD$`qEarD7*+9RRP-(%Bb>0PMFM;0FGHHD2?D)27)=P7qnox(S%H5Ddo_o=@~Fa*0@uB;zOMN&ZcCLX z^yJh_ne!NUT4U#;L90#EvqqaXIHkxp-Sh@_sLVi>;ZnQ&k{Gw}5>*0EeBO)tGO^5b zEADHldb}uljhQhCgLfcpotJIR0bT~V{pzo?(zf>_36}U1MUv>+0WP)01Z`tw&=SW} z#wqa8p~DQZII;-!o)H{jC^)`On(WC#dL(vV>ITReGQs*$o`V+LbJm}#PtDAl#BvF< z1?k`mr7*V~2raloLWBAo{gn?VuC;dieSEw0j-KaK2L~6_s(GDft_E*FqMSBCiSaSP z_Ie?mfmU#C_bu08)RK@=Q^lGys%CxNwuZ5y`Yh5rNE}*@EYQ4H&KT^>2OpP!5=@J9 z+mQt-A<2yhs7?|9hm;*@*d6Gd;h?Jmhf?j9;~UATx8QRYg^tsvmC1()pbC0N}h`C zP38rVEhA6ZEe}O!E;rbgW1x$qqr=&408T+GyHNsU57&?+@D|I0l4(cpd6DMPyhPd! zHh7FP@T^K~WCe(TdJ;ee6>DldMNj3q5(x%w_dz~Dz(YB)#j@7E$=cf(_sYIXbM}7Z z2{=C`XwKWO9VD8n-5ahkb}BX&A>R=~Hte4qpAF2%9wK;lEIr<9fQ|t!1WVYr zBXET-QeK<`*HP&S0^o~%3E*o8y@*4sO-=f>>#28{iv^c z&GDeu>$v7rLOU4UhK`83f8yW{$h@o_Dj#10f-*25Kv_|*)iY^HaTvX?a`H@#XHuw* zoNsOLdIQTbtb3oGygx}~O8n0lnk@?)Oe=jS&zh(X>L1}MWhHl z6qCvR-hND%6fkg&PYN->SdujN_5b&vb4w`j&@4gN7VA190a&ic-Z|VPu_C)kLaLBIj!^A0V<*x9Zia(qO&pBp*cb6z>@rzZu=@AiU{b% zKGe&E=#;HRBmNqmO16X!T56Z9F~BElyD4@nAKF7y^E~z7k=4NDg0li6*X)1ysz~Zq zejC3G_4(G%-&3L<9Llzk>cIh9u3`zddiWs?%N{bB1|1ha9j6-^#OGosQzGh&ryhRe z!^|27nl;);L=Ya(W-1ql1Kk@ALDEr49+d&1)LlTc^jjP!8ECEeHq;G$F8d%jhpM@dvpfy4C0!ga#QAH~ZFy(55X;Y{o8L3`7-xVa{;}k`a1KWT zNHX?OJd^uQ!Bg}Sw0~O=1Q+d@dOS66_<97km%CTY!*5I!QkxorA{Nz+ex_mEzLP@J ze84sba0{g;XDb?U(3mk)#tk;N4>knDOp4<>9C#W99rsq$xt@w4JiZO%%MxP`86Lub>TsrhJXtykgh_e9}{*3F@aUazLy`+BLKyYKblm((RSKJ8s4@K1@&3-QJOR{6Imv8Uo+`{4fnd1$!VfL^{qG$E|1dnTH zrW&erI1Y?P-!#m_=*pmH=lNYhfg9}cpl>W5DRl5R%}#SJ&-y)nbX$<8?se@!=MX14 z#3nUZ?MG1PyUwChiSGbJe@{39XZCvHc`$r;`(wCqgYFzI>~_|>2ky^~c)xi$=2pN6 zAz{|Ovw0dp=-m29eD7||v(iH}c`T`dd4LL_LK0E8w$cN@Z20t#=jra5J)O3Qn_ zV+~w&PjfQ^`A}m(Y13VrVKoRMs)bp+*tnZ_s87Q-RuG#09<0?pr2;;$?D)t|4P`3} zi2xJxU^zn0JNMEl3z>EW9|?MgO#1>g0ALf>U7RE~KuUT5YY6w9<^3_xM42&vOg+U? z`Y($P675&(2XyHpV!mv?N($>aSwkxB_CFMo7V?iPu_H*#Ib@Ok5ACu1g#plZPiN?E zCoq2OvVZ9@?GhXk%WRNJ0*bg{p2w2~o>y9VClOC2eu$dwNVjxRV4Ixh?wK>XTX9=Z5-U=*vyokh1ZXDM&50*4G6RCQ=HzXgh| zauSashl*%sUn0T&r~VoSOOQT}@!VH~E#I_}y<2hUu--vo3d&-V2~k#h0M%q8cI^=h zR(_w=tb#lM@Nq+dRFIt82_XrfgY0@0Y`y|_v^cD6~^U4igP2=b% zo^BonU48-}K7MBeZ4?${wKiqRY)=n<~B%r3`>v$ek`SQ1JfL2c}aRS z1spFem3jpWk-GxHG>>)6m7cS|o*09OCB5jMkOFhinTqFt+ETClT&5d(Vv zgLwf+M#TreTni}+g>8_s?hggFZShs4doJo&n97Dghf?#l!7*v|>+B2QSEIGY_9^D- zOko9}d%yd1m#3iZS5Rs8h29IgiV?AIwwzqALc*5diQQy*#Lj>r;ruf7ERaqV1g9m7 zBnp*CBBG4Irs-wRWk7jJLBn_c(sWLI&(B_f8KXsgx{Dy-w-e%Osu@eh-Y#AW9Oz7= zYTZsV8x2TlGC3h6!g;w5K(GCjScv1Vpzo>Bcuu+L9jtmCb~OoE&-@Q%xKp}UpIVbW zY%#T6n#J*L$Q}50259am$ufZIirG$^cp|M`Lb(rxIsM*I9i1YDzD`yOj3;XA@F47} z!OfU4FGrTGo*~0c$feBL#+53h`M10kd^rZR3HRj;nv0gT- zGf=KLdVu6XG@2reFeiLXMi0V2`4cZj>{vO+oCOk(#jEAa3DhzOe9aB zDKpZo`~^`WWiU|WnST$VQ-p$jhA^mI>+VL&9hP0Kc=v_kgyjiGy7lQPzhRPYE3U#rmewCNM2!7JBW^N6y(o3M+e^#M5l@Ulwj#|ZY zPH${v1L+~h*QNe;^R=>NJBw|iVLA`z?v+0T4T8vAuK$rben^Wug}tz=aR|@6y4eTT zvtzB4eGixYA`#JAM=1?m9>@Ps-q`j??)xn?iGX-wnNv2sW`N7JviB8MChPb z%zG=-t|b%+eFzxZi11s>?W07oz0GnteQG}8;1#V=Qp$3)WQdgk4}^kR&EE-ldUoXF zR|@96a?I!?7Hu_e%ix{;)v zKwSy#eNA40qxDFh%r!fL*+5i-jE3?3**h3?R z?DYVR(=?Mb_0RQiPS34BUbYNfOF|fZ816C>ORWG0E-Y6aG`V zEv2YZ9ibRkZj{h3`#^M(K}`)XD4Wn&(oO9jcdVLGdoPm2sQhv<6n?WooEyGn6mARU z&&O*gdYP=fA{xY6_|X@5ye~H4=tW;MS;;&>h89=Rj)fNYC86R_A?lx?*=x3D%*^^P zRhSc;Vv<;Xyj!N1<$DxTZ3D+s0vM*3CGHI~jvKgTIS~>OWH^)}mYCedah^7LC3-Jr z{y6u%?7mo|wK;f)cde}EHPeIX*zayjUYy*L*tFe5PD)dTPMpP*`|Xh%VZlRT&hY0` z#yjTWzZ0HwenK&KzF!}u9RsViGq0DdprAF-mMXEU?+^{i)XC76TVuO0Z>wS6Kj=j* z)Y^_P=4xu#a%p6s?N&snV6vblKabL z=XTO~bgk-pDb!!Js!{6rExopG%aa2mfwGG}j8|GY^o#s+I?`cq&`Djjy-74b|7~jvr z?)b(&q}Z-hfzOg>Hb1#XTU*ux1H_yE^?l!FEXrXGwWPIl>g(8Brq$WcV)rl{y1p8D z-sCSbFTQg?olt;W10ON!Cq?CaOqEwSGI(EKs`WKYTzqf&H?{s@?< zbD~XS7^J!qqwLmVAun6cA}OyIPgqw(5{ zBrnsH$-@2{Rn)Ae%&fS#BU^g$HqphOVrN+-6M*v1->L?e&sg~zj%nY{867%*U5Uug zl6HsxgZ&Bi3|dI>BPQad*LN8DFxed0S(%F~YgV1U@gf#gA?uCG&M3cV6`|tk&&!tw z@C5VcObOmAb&Z7=K=hB-jQdaRkMDsB6ybrevkyu!y#%q?#4n`}(s z)*YX|jbXW<&Upmw^R04)P%8A!ONFviIhfL?|9Zk9X`X(zWKeF8L|7o6^SAY!3DK03 zS(%=We3^-zsE{y^&YXB6d|Hc?-TWvLuz}Ji7aVMxk74-CiX)(k>uVH+MVvf*7A4&V zxE2vqDNkgXNLQce5q-2uq2kZPXPRg(UPk~9QlETLZae)G9aHW&oZ5n3bl-WP0%0<;md1F6geTre{v_q16w|AP{9W&=H_6|(Y zF`q41=u4pkC9*MS)xcbQ+T>#2#!Dd(#TsiOfBM9L1`7)1m9zDVWc&bc#VZpk+OHxf zl^EmNxC#`OJ?oIP00360y=j0+UTQmf(OSaQZbKI((HOf;b-g~W_(#5bR&u(&Sc-*u zt^C?^bv?KK<8rp!Ld7^mZMd{m$*l%5d}Z=u!Zaw&zp1soK}SoW?Xk&v0Qsg9&biSM zvB5$}fLzmt&~2c+9F zy}&8*)OVQF%)X%>vz%2+?_-9BKNJm2G(gsu1?YYq{NZ> z6DCfq@CKvkzfzb3TeuU4TJ^l6_wH%idV>su$iKVk@xR~@U<<<*IoB$uiLQ+$37C49 zD5NQuAVBD@G;tUU4E~$K>W<7=f4^UgCKY%MO|Nf>RoH0tgVh~!(@KJ=a)Wfv(b3~! zBqI_v_=Lvkr6D8zzqga>mUoVySBkS39#xn{kRV&1=zR_?d&UeOF`2YlM}fs0jSJ#fMH; z{X0~nxg=5Buwz?)(w597j_U$y%4h#5E)z23-tF@Txfl`&3KxGYohjo5%opHXSwI)B z*+5=M%IEPLHQa7@*heRWSNJGAF1m)_BvcjthL+bl!b)D)U731|{j=;}XPEWx`n&)2 z9)Gs6By-Y4pfKGQ^LfY1uTc$zzt}6ZNNw_SC$RFT_@95aqTGIOwIBbI47t=*mEjzA)G3YPqM?|G z?d`tx)us0?eg9J2M=ok@MN7;{Ls}gxM-j9(VFikLf z9tT_JR2Si|(Q-Ig>0L{83g{DfAJGxf*KoZbRZA)mx*QFtND?*-N*kfpCiH&sT|_m5 zsF)>V3}w#qi=W~=rn`nfrbHH5=kExxpGgJrD!b{?q9NfYUBCvf=4WT@bmR{y8^2I$ zL`aQ)LfS|E47=fZ%Z#g9@$Z#!)_p^E+Qp|K@9)8OZY0AUsS&h@!n8nW!8Yf8&9DDZ z>_Knx_q6Y0uZzU1drxHq_0cwH6jWoV7LUwQnxdkg@0f7)s6MhPVAz zZ6_PG^v`nu(q&LtB4=cGz4;Llwb@fqWO|nL;(>BOXwUaQluxrx`?!8vT-uDwyJExA z=k&Gu)xOn})NSGuM4M834aISPxv7u=K8I!k)w>0Ia}yuQf^u##0bWau;LQ?#6$-9Hai(IYJ)EfoA50o5+GP`Abs@?l(UDfA!D2wT77H zwrgv+IcwFu6!j9t$c1Jo%&v=~b!{}>jHn)(#A;FP;ye9gEqwkC(7l9(uWP#50n68rHGVPUUZLD@Iu}R14pb`idx_upqX9c!NcI8ZD{PN#wlBHKg zF6*TwRw+s(pEOvHmS;^63d{-;c=+lD&Qx~(`d;GhN9L9&`)LtXrJ|+d)|tQJ-J&iN z)AuiHdg>jT$Y0Z92v$*Zbg`hv zZVy_QYnfou7qiwFw<1CC*B(_$C2OJ|lOLNbh}$D`ge4vKc;h%t{Jn-|c{%dmZ{G2N zE^7VZEF!W>LZzLQME;;*vV4|X;y-!HQwW3J)trAXbM481RDO*=(V&Sn@=-JQb>P6z zhbEwux~53ZZJadev zrYadmLjm2t%|YXQOhGcU#Z6q<2agWll@$i<6Jz!qH6kTywp_;`Olf&-7Y%Edmb`fi zUkbdkJ+%dBMT5~|;<{%W?--&-x*@?G&vVZnBX1u0`C+oyOHI>ppI zXysswEPf(u>Ow>RkT+R_LURg%x%R-IYZzU3E{SG(i;;KaK@q!@+^1CugPz}XmBKtL z@{McU>)BO_9>RLF%OKfckq!;%KVQGbejofNKLszXx=oi{RI>~2%=PkPNgwW@61S?B zqnSQ8UOFUR!z>)AM?X}z`;qgLPv?)~=Tz2w>~H@f@*6uXK0IjAJJvSQvI;8kRObA& z`J+fZf;(h0kv@or5PCgNsKD)xE1xOLDKkgp3F-un@OcvHpV`#kpF5$rddLtBvC;Sw z6Kb{-vQmO#QIkZ6&`AYhSt!ZYD_%1A41%bVWEcu4ma#AnSr3LJS21Ezqli!w>==*t z9B)ZFLy1ui=%<`eTF2xSE14L+EqW5q$L}$InwF1Vu4m<};vF5W{%jLq;bg4Ml(^&i ztKuX5;3us2X~NWMxGZNA#!6t{veUPTJ>P3z%PQZThk@`~pj1xN&Dg(R*r%Ubn6(Nj zEhRnS4W~17cp@wmo#^9^+(kCKJnySKoFd z5K|5LT;`${W6ktpJ;^>-l$KOS?L`CSz@)IH6!+y?dyJk&(_;4l%&jGeSlC|hj^F2Z zbgR3jlf8~oE_L&y?76X;m$yoH+K|jJDGmY~CAx$83qf^t!l?mAI>zX$5fc>qAGzIH zI=*y{!-3O=tO=}UUa{z@$t-%ioq8xx4lP4tQavk>2JjA}^{+XceQ;ou!E!|}OAhGb7Uq;EKipZl&+HB@Sd%qN1 zAq@7Hd@l}T(0|TLT_FH(ZMy;&zcs@TGke;aTSki2UD( z{8yy=HmRs~rY*dW(^wKf@xNkp9hL2kH?^|g=;~!Fn1?Z=$|F7ryf*OTobG8?*(!F~LW$9x-L>yFF0aL862sIBuI=oxCz#!&^D)RvRo9u&y{iaJ8SY5y|%4MDf%^Dnjd-^9Pc|p z9*zm>;F{8I$B@G3sdi6%nhN7DMb`cnytvLEIK!nPco|7AMYkN+vdLTo#n#Ycxk-gN zn>QFD(c&UvTx%HK+F)~&GS4=5IhJbaJJg$lMxAhB>^T>2r=XG8cCk*KsC}LCa)%!iXij4<);tbE}%3! z?qPlVvwjSksbR@AUOcveA}zG`!~mz}uMVm)k)Z~u?){L!Cc&SFhC`b3BD<|iS0g$T z$!sZflrU?v86!;m^TlQPdWmy~ZX>^sYp5M3$Q4c~ zC}!i>7TWShpV8;<+@sEzU+V=^Z{;8s_-qF2quw;)+qQh6C8@gz` zI!$s!Plywm>114-9`5BW__?-dj~LZ9DLfw=&X;h2Hm-x2KN<_iTbaWq7*x}{LM=gi zfiCEUT7ky>OU@U~N2(HV@&u3)20fH3V-#{Z$~o*s;chL=PYtwA0q^<;D2P2H;%3Sj zvht~~0)Te`da>1x19+_uhNc=}u9+8Gqs5`ho{PA%)|0I4)bKJ8Q8=It;u*%GbPJ!B zqe~-s@k!Lf+;CN`=L)a?8NJX9nzek!)vV8d*}{CUNmFHmm3^!vqC^}m5FR`?T51N3 zAbIhX?=3b?^so4@vY6?4pzkthpT(C_G4q!Il=4?j>s4~>+{^Bt{h!ITD)L{p z|4U&-Gui_n5G6d(e9U3Q5Q=iB6G|OtkVSQhLUh~gH%fw{702i>{d+uCwAz8rEJ_xO zG>362j+G?BzJC)a(!Qgc)0n{tiOqzg;hBPOq+9_;REO2N6(dPUwn%m92fc+j>ntVR zvfq1yY(;E@YL87O^82U*(gaS_0)1f%<6H}g;A+nA_12M(k zOTE{6u0BjQDkLLW>^Kcp6utx#sMhaA2;c5i5HL!~`AX_DNSM`yFaTu(3#d!gO};5G zy;xdoo14|U@0?Z7l~+qk78RJeVPR6Q zYm9P*@@g8X^;{80?2Lm1eaq0%*I^9vq0!$iwewL<+|r8XVR|e%`eum+4!``|$alV& zFh`A!-T6%u-#b#U3S@P5VzlSaGe=9$BoA3#i*rhv6U>&v8mPY!c4svSC$(Os-+!2@?6tQ=8e-BC>mlL#(r#yS8DY7oYip}|XoI8JE zubizlUE}hU7Q1YiPHnRoVGtW9UN!IYFY}l*6g?Vu*v{_3AkWy;Xj1HxQ&lD)|BE+V zC+liVr=u5tBd`QJG@Yr=7K)@bvbPC!^zTy801sxQZ$he)YNa?finND71s^IOjf{#4 zm!GRlTN}>lhB>AtINJLU4u^W}RYz7VF#+G*17FhBZCoE_Jmo`6u%gAP*JbBYn?Z2Y z%!*q$eagHD3LciKXBFl9krp+!Tlk(W)&EZ7Zk)C7uwrWx*G>;nK63kTKc)TiQ;Dtl znP4x$7|IdqGWWiUfrUM(;JP=v`po`0iytmHmcFVPgyeBWF0ZOU{3zM5aXqZ~+)I_( zcI|ejqL|XgUs1>R8D`uZj80fv?f&sfpUdz1%e2a=LHRXN!3}%b-i*(1+J@}t^s}?_ z?;n+h;vqB_X`WPRQBHq3tv_=V=I(PYX9=MZ{#5mBe0iV#smXl+?WgEGadGmJLoRjh zIl0N0e;h3{1Ho8`>;-6x*q2Blq+*y-b2Ge7)`BM3QaT#Z*LA_&-^-)!Aze8Zm%OaR-FnZo-+RsFUmt2Hmbx8FdLhwqrq3(C|VahtBUpe!>w} z%2h$xEs^SKkg;`Boyn3V-Lc~pqMtT0u&QT0_oyG@V5#rW;Iip#>yZjBVY32^CfkF_ zY?P%nHA}p@6~-Q_W58Qv_0ZkR4v^+WHFdU-w~1ua_FX?0nc?ijR4PNJrgpql*TrIo z#p1I3DuVCgPVHhUa?W&+G2J@Djz+2~j|6MNSA!C{AgtuyL(+ORo~yG1eb0gK=ws(_ zahm4##OCI{aa500;z+@eyp^fZP4g1uzTK)DlQF;3nPQ4iT;O%?2*!BH9r4wg^ z`RG2gC(La6!AP>h**Iv(gKuyKVzzJ1!348{CuG+!9=5b=J9HJHZ_peR(b z9JF-R$xj{m1X1!K-aSeJY3#5?i9v19+)-_KQ9ot!+kj74k7?fqwJCpDF-`jxTp-L% zzIeHGKh{{E9{xV?4l!sZEz3sq{`y_A#;9$KaTSdcK4i67Qo*fnRUKIO8A&Qf8-btm z{gX{CN}aS%nuyimroFMn&N@57#oEv9)~$(Wk}riL*N3~-vZ*361uTt}@@KYuf#^RX zF;nQhu63A~USu8WaWxj09J+NGJ)iw)thX02RhHfP=}vQbU@P20s8gh^;Xjl@`K7wV zA#18UdTAEkA3LkkBNrQeH8i$i4xj!K+%d!sQ%`y2;mfTYIr+D3RpXSd1^CX?8pgBM z9`x@kn0OXL<9fsqc&qARRLR)IlP|A#yoLg`65{Lqdwgqy?*dp-jl4%lW@zy1_q$aq z+y?=M1vpJ?KAvPr9B|5tA!r9u|9So0%X{7hC8H1hOmSN2%oOjyQ?s!FCF0q9&mWds z=D)I@B0v{)?}Un^G+;vfa~aCFyHQRj@!JJnY%Sz#XQ8D23X&?Gv%I60={>%!Toh?Y=Fh=v=GPQm{Xpv)|Qm1P`X3$v0CfTP>$SG1+ua zyA#SK)TQr0n3fJptBFF+lP!KSdq^@S8$F3N4CAJP?Vy#-sffG5-Jo73kBTptTNqg@NF!75lI!{qR4+yRQzwooi1_i;tzy+ z725vt!Yzk}J8MWm$7WROPK(s}H{*Z8n7lKu;=(=F(tijQq-8bK_HdZ#Cb@Mw4}iZ3 zY_%o+)4zy5(mbf!*1UXVYEE3H*t}z@9%s4r29@Czl~p9Qk7BMD1E8$omqn|y=RbW{ zV@5v;MIQnq$JV>ymr#l4x;TsVCLSD9j7N-DL&H&s)*lfrtXrQ4S>4)|xVA|>*>h3s zDRezJP^}2vv%)?+{?A%3iF`XWx>N}cuL|y6g91)I?kkpb7em*=;$;h4bK&Cz-Wt?q zvAm4JVsZ#>G1{CH?B`f-$+HWj@jLsrE^lSkX)H1yZDnu?^O(^5UsdL$Q@D_U0!w zT!`0^5!+uF<)e`aMID}#t47dcH7W=aA=Kv*X9QAkh&Y*BOLN6Tf-b`qib>L=lq_uN zw_$KILxqK8T)enfkF6^X$i*`hej1l8%+qB50Bp$T`P4dPCPpvzwH%~#GI6M>9Mj|u zW>mVD%=@)mu{AQ~lV)>EmL{uWl$Tq2QSOX@>I^ui%@j06HQ|yxQ|4t@&YEHlbImSc z+Qy!fs(DhWIO;2wWTbl5f#QEI+X1^&z|zDzuCw-ooaUQlsGQyX_U@IgMCUUpG?$-t&q<)Bg#um1dd_QZL(R}RSo-Dac9CZs3c|+b4xy@TlWuT5sB8){MfQQXQ?Q11j%a5l!;8R)8s;c=@5!RzP2G5SxpAg}YD%2V8h+4*un=I2@BJAIjflbQBoTt`*vu}dnhIsC8EytMPV|6G#Vo#xbSKzBl z?G|_-1)mxu<~VO9h#Jtj0*87L>zRd(9sp7xdfHXX%n7D5#!M2+j5HN zfyFcB?;S|rZzPZN#c5-h!_yGWGJ0mPk*rS61@hH#&>GezY{=H5R!7fz-sB#At6G5} zJt|ptG0mpts9|FDrI1DGLJJyJa49&dL1U-WY~uNTQHl_bnQY_of!o@c9PQeOuA~{_ zs={SC36eZ_qQoLyD#qi@+%wjy ztz)^Ik1@KDppdsY&u*0FjADNz&k3tLBzn zqwbysYfap?P2yh_8%tJJm5&|GdbHzoaMhKMjlXHH6)onPVce? zgF?Z7lmaLMExDiwmBO_KMEUa*ny7$~+x*=sz_w5)Y54B>wH=1We6vuQMS&(e)qu%U zc%TX;8CwQ`5#ue=f!KUD`+Cq89^WbAfUznuD?nJSbuJxhnJByA=tJre{{X#M4EMba z2iVbl1q8B|vo^&e(zix(Jo{2z^Al19Xwrho)p)6pn4%$2NWm5wfH!oA{ip+|wTyXV z=|CCx%kvrx=3`zadRC@|4?MSzH$4734SMH{|avVl%00$te{JtzU2g7Mm>;<*>vdBN#aXRyYMOVIH^ z0kxpW7SjY~fFwl3gB>eU4VWa@qOK+qI$auZXW z1ZAo71&add*nR4&m3B`J)2aK^Tz6%!w#kpZT3r#S&$Z3{`YwfLMlR(imN=`pT0~~! zo@nL^GH9jENx3?(phG1mG!r#)NFpo+7Bene+L0z{9P%{6;W(>D9No=*3gKd42{g>LLj7xwl_xEapsk3FJ<8)&w3DN$tv;(b^f=x=gom!7Z~gVDbSfG{ z89-LmEmQtlEej7bNn&VBT zucD(}KXCc{4IgJNVu!%JQe7_YO(tyPCm&jjZWorHIz1T4s;9n2!)^y^`8h35r-qSM zFnn7udl!s)8uV~<^*pR~Ye^&XgX4yfG_Q%bVkq{_Zne`b=<4&J1a~r{W zB#q*0Tb;jkhd$Y?F&IfyJuGa}pwah7g)SnB*2Zk`7pLJ|^zbg6S@|4hsnx%k?p_|d zTiam3=L4mD#x9>EkJWOzy!6c96x;|>LH>G*P=_=Rna-+vHhfw8UFnfFhZW(#oJq8C z*FWJ{QGAnV^Kxp5)lK(4NYV6LjZ*9+Aj!FcfCmD-YL)F9AEUbQ!^2bSKZ0KYY&3YH z)U}&+xt;k$alo&XuL_mrq4BtkWMeklK92DBfHZ4Hd%x_PV&!%yF%Ffs9|GyEP7i40 z?v5|xAME#{c-KRgEfaZCVRrG)YTgp&J^0#1tH^xw$4ae;QLzoSljnh+p7mbV!8uuK zc0LjCyIsz)#=FPTx~bNaM}dgNDruz8to{*vAGV4JZ?6=2-BKbtdzw<_XT#!gcW1ol zx+t*QwlFvytCy7{$BZWBbF#1`Mlvgks!aPF6KXn~{{W8=OQFsW7!~N{+EqsuI!DAF zHJlHKP`dCp5s#&PPA-=`4z5Q!37=MY4&FI*n;8f9MR+PMlo~!NYnHM-R@{EzeJjGA z=iboPk{K+K#Jv7hQBFn`XHJsT&HmCsakHSVrAhMK^42Py%ZqggtQ;BEu@s3#%t1|0 zEREHANfl$2URNg=Ju7CkIOA3lY~nsB+{AQ8i5v&?uTuq0w{~#iXAWBud>wE!0UU=e zr=QH$SoG60%Og$+Bhm+zityFXs{PYJwhwAAFy){%qlEfmo#tlYL`dU^9@O2%2vRLa z97EEul$t#lQ>IGlq63PBFL>#cBB?tcH~eSQ)%2ZFbNpcA@UKf3Pnv>f=W@5UX!t|M zr^@jy!tuY*eSejBxCZH;bC<)CPUZgq4|sKDQ*kjEA9wJtW~FzqVHhcd7!cT@W zc$&=J%!Vs?$l5WF%DOPNay}myH;G#8eHq~0Bl|i?<5ggw;PJ^4^(?R`f z=5Zgps&`~g^_5{zE63%R zG4eT^UeTYTnmiH2{$c8PuVLN7$D4!YPg7x}wIdC;n&;)HdtCT*KLV#s zpjR{6#D9cV=}u=Pt58JIUE9yltwfu;Ubz=%`v+Vx>*)K-8(ex=k*oHV{JXeJ*1D#G|^L%b1&8B^w;kH3%7>n-l{cBpZ z`4x|+$>kSJcZx0i-6C~fyHE0?3-KLUtXHyU!r!#S3?$LxbsKRnAMg`hxP{3r4~xTl zkxSJ1FITcZZt~YB(;rIOo6_w2>>ZUAbGi6q@TTj=8iY}*;vz=*Jq38U*X~yg*q}8r`lkvOYwco<+a!Vy`_O0_nHaKDFn)=A6N+;Ujao*G1n>^Oe zM_9~Ke57O=-`o3*9^WhEXVyQmuZ7lM56gbob}|C1_}9!*i`+4kp$l~x1* zV*?+peQcGH(}1&R-@wIi-Q4GUYWX?ILmEJY)irqGFPAI1B9__gOD|K^% zNfO6yy+FlFlPWZm*lgC|z-WZ5$g4?vGv|*U@6vp3u7@AIQ(u3ClBtC}DPAd_>!q*S zB1R*q&3si~b+l=QrOfQjWwl&(t~WE?jvh80_Bj6lx+;CEHu|MLz%1kb=%d=OSW10~ z*3vxv(OJ5T>W&tg=vYf=$6;Bywsyx4O>{DRK?HJJwg?#Jy6869;O4ZcxafT6@lM7K zT3CWE=~p8a>pB-#K0^zMbZ2Othr+)H$z!5hnIkde*Ckk}o{zm=$F%9T(JXj9xaPdt zvV9M)z)+^6W?MFpk=nVvtdCXUD2leR4@yp?nARGKBD}VUrxmR>&RkUrZaN-);%^Vg z3rPa+&uaE5a>-erBU+auyibZgJ^T~ClUKdfte|-<<-yK5u6o!@Ee}Tpjh-H+hs^WY z-f0(R3HK;cMh$uKk1mov`khBoDrAK30E%i>JE>|Ra@fr~opKX)4Q`3uncAG06|yav z61Lh#fdt!4Z4fLp=Wc3;a+I<&iVX^G7*&!+uHe)gm#pukjB(n76w6~(W7dJ(e0Rq@ zR&2WuH=wm<9HXJs3bjLH`7^P*@5MNvZw2DR@%$UzLn>A$}!K*Vt)sU@n37cZ(lV;Yi+gqL3 zsRF|;I#2^Z4BeO0aSPCKoSgN6ad!i zfj|%HGe8pBq2EDqHIhG;li0FoAuQFH(Pf91K*d)FtpGD;&+R}LV_c>yMX^ZdwWxOn z0+`N9<_(3Y13p24Kn&f*07D@akQ4~o^#z(%k`ka$1;zv?6-1bq-|JerDx*|=bNd|l z?rk?qy3@nqLxKm_C;1BV8r0bzSleq?5=22H{{WL&T(mhKa)QQ?%nfc%8y%~h)Z?#9 z51$<=07t6%QJ~vE6txBWacp;>3`3$klLf_Q2J~JMj9TaTu}BVD`oZmIE02^K0JmWf z=uH53J`nQfoxQ)8tbZE!r9$UTXv zkv2E+Fi9R&bYmibe^;3xsH-R1H= zTu=m)O}KT=MF3_^ArC+nt!_}L9Vi1yNb=|cjl}rrRDi6rF{(gFvTihp9dJsZBAOZ~ zyt!(ki2TlYpb4XtbS8i#U_$Xg5b`JiK-|M3fF9@o=|CB`Gi*2m6amM0yHME{2c<(~ z?s>4X{?eZXam{g5VrH87M%g3FADbSOlP>33@XJBbMzMTClqDTP^=10aW)~3v)&zAf{ACa!y#=`@56_rCYWZdl$oKzx{ zvClGM^PZKob~BNc;)vst$<+3)h#WN!mcArgs$Y-?`Qo}7IgVpW8zfu~E0$)lI}2tF z9zE+wiNxo8L|p;&&1gbL8K_7*^H{kXYG$mXe;ig$R~?IS%%Bb`xxvJ>Z89m%Y0PJ1 zQqNT=z;@!P$eSI`k8}3Ba7g17Ns7litdMUXX&iQ~E=x1wx>0~=xd`jLJv1dbBC5q) z=%cxcK5{T9uw2r%yN*TqT8jn8-RyfQ5xEA71s$Z73a-d>WTIIKNa+kv8jsnFh_P3tvJ{mk1hCv@zYJU^CL5dz`)th^siQ% zvN&p=D|}S=oezkdgw8WtGtN&_gRt=oNC@f0bIDxjizjVX8;-45@|xI)+{e*cq@>1; zNJM#Ro0rU8j`bv5=9e*7B7I>kjWq66%JTS{6T!t;?oVOji*YdN+PP{PMW00cB>0yV zjA*66QHqETN42NKi#D?^)2~jI-5H#W;Pvl~X=@`x45QPE>5Ry+;GY!yMgIT^2I-x* zu*DT8bR>@@@Z`DHVHn9X0s7aKH+FQu{R{B2pZGzo19AJW>0K1bsC;+vhAqA1vBkT3cg`NC<54&Z64GBfbQgUpa>+$=qNHB4Ef_U z0e&g4C^aVy=7B&LZEf;F#Q<%_p<||i%DkLL^|j23zibr^*ar#I9ch6^{@?|`=72KL zhm>cf0BVRA0~L@DR1uD70tscqR-hm}Rz-4+ax>PgFy$qUvInJ5tR#xL=76w@pwKa0 zf^?t=REBB*n-~KW0ZQ~?REnlTHoPAZd$d0FfX{N$0ZVVff>}#cj5;-LjO20G*1Vil zftj;m#RZNdrYdA6Ci14Bkv9@(1Aj;RqJTP^KlMZDKpFay;)6Mv4fAp7TACTRwws3( z0SrjUFndq{{Q$*7KtYabz;Iq@0#I?q06j>c2<7SNKoRF6tdg|bl;BVTM9w&%2|+#h z;;f+yjlc>30rjNVn;68fsS#PjeJaW&w>98qE}N%rO| zTG*1ST(;_V5j1xMiej*($>~6>iK3a86=E&Ja#s`-OFX$s3=HRdHBkhxP8bTk3ADSC zu+KwAfy*x94mhI4Tt^&HU~FmwRbmM;R*Megt1lIHG_n+g}SP>UvPEcqDN?E54V`@{_%L*g9(0XOEA(rmT1m#Qy+< z@c5qjuZI@{lV4GXaFvrg@EFI<%X5$LSAf*H~4#@9XX!x~JY z3u|%nDv$vlmEhuPMmJ}~sxEU!xp-O+uv&yCsQjz*{A~H%&(iX>?BHT}&F~!d$o@jM z!c*pn^I6<^>K`Hg)S8{G?UY(@JLY!wKcJ#+geJj?Zg@hKTf~P93?Dek;+KFrwJYXK2*O`j6 zC1ib;UrAJ2l6a=e8f3V_;AB$6OPWa<WeP75E@Rf{i4 zSFwO!yhqMsrK#|@j;;du$Pv|Dhtj_J(o>2)mk$nXG4+?itB*Uw)_wj_{DpkJKA*Fd zk@@~7O33x=E^XK8Unx`KKIaLoBzYgkFCE?ZS5l5EMjv-}4SgP6hlNUal03X*V>$BN z@}u#$#1G|>3?tpE*H~IdEld<+1kK3)H0$dcpR?PO{_7E0{X&y6cx!bhkK<;u8O_z4 zVL$1bkyHJjPoayX)6|ZC+BVM751k;1ht{RwsU4K?zGpY7{BF`U#5=B4ow^MF0QIZU zqe`7UimR5&Wsiry7X_8&?0$0XAL{w6F*K=GA!XHQUsKXy_{kWA7q~(Ait`=@kw3kP zvt?_Kjj_e|ZsW({TgNP>#HESoV_tsH5k-}bK!Ya(h?GI9f;5 z#=Ypfv%r2Wy0wO?S@>he>0d)qo#-TdO*(FM+qv;hv*BrVFBY>8nM<9Z4CH5}c#497 z^y?8QJr8L3dGJo}TbkZ`;4O6Y!)+b?tEyBJj)#MY#?+ks=b`lX6kQ_8y|;(Bb|d zoS1(f>0bT`*)!nt)y&U}{{U@>!uTFX&noI`X<)XL;&~c<&JIH%sxY#P^WI=YvID93q{KDb{m) zo`V&*y-rVB`RPm9p2EU&tB_um(e0i0{o1%cY|cuw{f|?c(mZ~b*ZxVDrFPE_M2JMR_KC!#>IUDil+mO-K(EU_B_n2E8+2Po_9Y$%Rho(c+%oqdpDJH^7a+qgMx0y&gPC&OzW7GMR>qCN=c!@Kuk+ThXDQbFCmFb^4 zd|7oqG*O&<$SdwJEvzx%&dx2K^`f1&P>=Qp@~@G^Yc_V_WnwQA_>=nsPmMze`KQ-T5n!rSN)+dc(U*7OZz1UwV(3@KeRg^y*!%qhcS_> zYokOA?r@^I=}`8ki(45}4K|U<$?>O6N5ensT}v)Pd5uOgZ;je?DY!Tz>sGSsn@1Z} zlW2bu{{Z1Cd|Jo=9jjSjVMSe>^UNwsOP!XN1lKYC`NIQVRcS^K-90QSb@w^nh`KGk zzKbImRe>1BIOe*rbv>)Jjd3`CWA7v34~{+twK~P+#j*q-DP;n&%qVi}6Wq%&j<>ka z3-Wxtuf8kixwSsdi32Am+PYxQwpWnix{#uZ7ldrBQZ-8EJlV}rOlsDVLLN!kOazQ( zgHlf=8-qX*No=dqgL@IW1ztF&(Ih~hN`}PPiICh@Z$j)izs=2L*sLnQG?{lp(ru=Y zT?g8SN}CSjMaaz-3ypiQcpOo3?pBgUZuE&Iwv??&t&-HY=7;Y=oyN#OTF%Dk$+}FF zidF)IjkJo)zF}1Z3do{Z7t3%(1VtTQm#FQJ2a3+;LPPHE_l^w~70PkkWZ=@USetOg zL3SFFb3jQnb5Ov}l00UR6mMk0(rlz*%vtkU8kG=` zaapq2VmHXeLv}XUY|FS*0a`J#K|mUOJb#4%QzOeX0W@<4=|B=Rndv~RF=oq978$cG z$e?7$0Vo0llbQgqgLgfs0o!nK?LY~(fFv9YdQb$`{5YVvnlV2#p2e4KropVnhs@iJ z0~I(F0NwaB0dn2HcUmoqMn<3mNYuu1+MHWir~?haV~PN7%nAUnJYX?WHZl3k);kIWy_dbf%OHAgf#q<#zu`rVf+63GQD7e{ z#!;6X;)?-~JX?ss#wmcdhWVV(9xAX0X0PsMowq~K)_H0zGDuDiU2on??4IW;Bi0! zoVOXE49#QY8O;EBuZZVqC)!UHgo?2yiDe2#Sd71F9J{j;Iiy&)%KFWB|5Pod&T@c*KpI>+`H~#>Hde$k2 z&LhDU>A=OK!G0>}hRaxK5plTHvm|-L%=?P=tW6e$7G2Cq;}x__-o{3~YVuoU(Ibio_ZTQqJ5yVKk>P@}|0BE140SNn-ZJX(N}*HFhA zlemC!=~=1VT&KEO_&s!u0UN6Y$*$^Z=RHji9`Oyqv(=_w;jX@09F9hB_Owk2=m@Lj zAcij%eVZyI+ls3Js-85GNS9=Q(O@<-pB)Qw1g1~tTxl?C@ZvcF zk~kGpMku%8e}=lGnxe)oL~%AbUrOU>>51-|Pr)^7bTjJH9tX$|6;MSOo-z0x6Wfyw z-_P$}gcgS_L+9TZd^Nq%nnGW8D|T?&%=6h|n%T;*>BVHmmBmSAPz`3rw-qm>L)M_x zZZhjpEIQ(qi?HoKR&dxY#TE-1bHvwa@^kd1%4=gH=E8W7`nNxYOI8-d`bLX=cRKk| zHz%pCc_T=k=irZr+P&SX1-bH02Nelewt55~3-z-LZ@(;{bQQ98Ia%l0--m9jH8+i= zEWr1z6pap3#Wr%=>FiE>R-m>xzY4{l5!iy*4RC|?#d1v5Y<`aE*1u=)ouC5?<#eu^ zCRGoNzA~U5ER>Ge{z9$pa-MZoF9jfCE9P3ZVp2 zuo6bwK9s<7;EpI2ksiRb1(5r8-1wlY8(M9)<^c4dEOc>L2;=EMiZ@qYKr28T_2m1d z=CvZkPdbDIRz+DATHxnBs0$(|oV@{LEN*Gos}>gc!&7kc~M86DS*0VRT$6N}dF(OgC zW`P?BghVIdIyRsYK5!@| zM3Jy0W~#>h3wsLtdpcXpsdpc{Kt4(PTU(tx@U9MDMN&jzUpj3;d| z3K$VU4QAu(Ng9k+sz50@H9$m8XaQnBHY!jTp&ctkQZ2@Oyi-O`V`9otww;X2=;IWQ zIib%cKv7X6ifG>O#etUbThP~+Q{p~*8LiLIPlV-L;ogW>{&-@(mj*I`G3Z|>~~Y|vRQkCUlDTNAG!ms-YMmJR97|3o!RkMiM&63E&PwM&P{%+hnh&7 zHAqDq{N5G1Jd|uz&a%1P^@>la)A&*-mt2skf!s+v*ld^2QodjOVR!RjE32 zw01qJ5RN7~oS($64gHr$p7P%VZzKWQy==D`d8kO`mI+dKN0j(J+xt&aTX^FE_W;Kg z_87W~l|E~ZP*YYuh0ydneIC{{C^zjK``70fit?={cG9C7Q7ia^Tx~M~J~-r#l~{Z- ztGS&_bf~){%YGw#T(!}3$MYsexsy91&pZ$CuS@Fck}*@BB1-*Fln$XU#6J~@=a+t8 z3aWS>pQU-zR%c_w^83?g*Ix%bI~-aCv@L~YA9vsUiqGKG^8T+H%_GK2U?bRvosTqIs9nyV9spTPF0 z!1ZkQuYMTEYab<73Y1ynAG9~aI~{++w<{b$u47E0#c|cCtJXbF=LpH`&m)^pB{%75pEEN{TzVVcl?W!ZJST$6EEFhwglqI~bfUw?2*V z_Ly%W5Z$QCt~ym!pRr!y;?jbxxzO5a*18ljJWIE!t_f6~c9T5_r9|`FPmKyS^{{pM zS;`9bXO-GK#Z#*&Q@cG{&hyTQFz7{m-r30wKs=rQ+Oj>q%>MwR52bu< zPl)?GBmK%eALH$lU28Vv4F2!arF~{ruSK2?DqOX$N${7zRu@)pdwB}VSl|O$<15OV z$Q8GUdan5f^>OeWotNyKkU%4m3xobMDed1{14FY z=9OcGBQAKr^{+uzoV7WYCY>2-e9iH1;Qp)d_Cs|DGg%YEpyTo#D|pp%(E7Y4YXu#4 zI88sqS61tUW=Hnow~V#1>SAd+^zQ?CJY6iYDm;>L&}N~@#hmiO+K#5Sn72B^Mv#dP zPB0C2QLU-LI&|nBx8Q$t2MaJ01=*6APa}cw^yqjJ9#j_I>VsYvyqn z*}`0?;HL#!Q>nJ_)H+Dt3J8Got|?-rS4Ldudp%68bK*6Yj0{pp6rKSmrE5-Fe-Xyi23Dnj(8|x;Z|*s_?aa zqG_F#Dg9q!Nc?|mZ3@Q3dDzB14Jl>QlfOfHIE$@L<3+jqO`^FyMSS)qm8E0puoW*8 zl=180cs&MxrFys+=+6T*caiZw?X!Km!M7=%;~$-Tj%82xhkp*Q_K13)CwN}vWYOc> z)MqvHI3@eEkJl<9d;b8PMRz1}G-XL{TPC!yxk|4emq><$jhJ_-uviR-HE3}fMplMx z71V5hApZag#!yJO#WiUjukgR)Pl-Ms!fo{1_BO7-n`s8+&N0SF#~z=`oGN=g&z{ER zO10ciY4C6D5wC0aO$G8nvA>B_gPy~!bW+DfO4mLzwp;A)%#Wac7tbcKCA89!zdW2* zvs$+_E_jsa@<{p{Ot;w`e0_NzRYE_zGkj5P=2%KU*{-Z4z2tc~_+AIX{{R_0VR`WX z0LAc0Wiv$2l0x+2zLIq=tZ$WM^e0N|sln?%w5N!*OHm6+{8=X*Mk|d?DIUCXN_?#P zJN9|;q&n}0E#65K-6!8u*1IJ&HDr8dVT6?nA4llkHL~)=FDMO;2d#YlY#ik86m{Tn z>M-SHi@TV$5w{znKdnVLIzFLNN}-#XSfy}O z^!GL9)Ti~gbazs(^(SJ^t>c?Wbmo*u0)_9|xg&y=H>x`-=04swN5TI9v?q+<@n?#x z;`fr3|g^{{|b6)K6I#{eW2HKrnv?39Xn6D;MyFQ|YB~4g+ox>Fga=V&I^D7v- zuZJPj0W3RLbvo8OYS=5go+a^T!Iq~&zmDkxLeVw}9@W()%^RE)pR$#i@F$G?9NGq{ z=Eck`Nf;jW=hKbkeU=gPM$c4U@ZXK~4G;+JZmuIA?3@8!#33Vhf~6f1-dg-x*JOZh z)-F>Ww}17ksabN*Qq%r(b)!OY<^?BGI_ObmdD!8v^&M9Tws{F^W_%R#B(p!C^ z>PN`WUvH&)xLj{CK61PEGP6C?!ruw3ZPqK1mMUu;Mm?v>b1Djc@;fPXwbP|pQ8tAb zuQ^rrs?3?g*=jcX#n!fVW#n*x58|R2IZ5bgO0Twsk@J4FugT*70EhvQ2a~jaD)@@)-)1;DZgW09cKZy=C;Gg873yWlbE6zf)bI}q zcwFoH=h?8n&FvU!BdqfTlzXyra3d?&EB(|qe_cJ24Ybz$+csO-(s_IsW#w!dILS`us3 zjGK+mN)=RT*!N$9@Vn`2*|W`gSlJ~L=b!H<^F7AWBDR?H74h?F$o&Tm2O0;B{AIYB z-8P{p9Cojx!7If0OkJT~?D?xzd6Mh*W!ZV+zKWD%W88&0S~D^UuDrlmUv7F;xW%*5 zglX9JzkxDuzQJ9io(*whSMJ&Kn5aQZL+L##K0T}A>pL^q%W1ZZzlbn-x&*-X^%c>V zJ5)&N%p&Z3OYwHXYpq9F5e>y*2lTI7Gk)QQ21+fTGu_%Xyv@CF*1ig)z2oli38?C4 z&GPiGMvSyQNg4M)E^E+=Eg08xbBfrt+^FxGNfbAMTBA`*W`i__tB`0mO5}?#tveDr zS$Lwx_bNN)n-L31va)QU-eB!m2uS1h7ilStOY5`b$Fq*h5xZ9zrb zcioKEMXe2cQGvwA=|B#7FmO8x07%^W zPz0mqJPH7w6g#ns0Jx{Or3Ed^7UL~J(6%m=;(?MzF+dQ?xo(w_1^a0K0CULgS}lr4 zJF7}#m9Qy{=Pw)1-#{3z10&{u6Xx5}fEg!B&cHEa#TEb~Yf)e?Dyv2KQD89cB0hQa zu3DQKMEaxlI<-_w*2m1uzsUapjd=AA*lc~Rr_Ld^0G^eEO%7jQ)C9~yha!M1X}1X)k+!G?-&=X)uoMAK##0T@ihw%nIE2yA21c=N$XHMYGYh?or*LD+B%%pvVt-ty!4@&7rS>-yUYKoOT2#&JLy`l%J4SPg>Zc2kb_|Nha)W3cFICv?$szIpd{o9S(OZ9$E2|Pfem=D@VDS-0)e|QfwaeiLxkN4i4JdBt3}dfIM9E zuF7X5_dZql*KOr$M;vqh6|9VHsmJ(k=V2>eeD^wG6Rd(yFnfbknX`#Y#FOIJVCzvi z0)dO*?}ry2HP#`sjT%WMK9%Cssm$ze1bdbD?3sDt-4E?vM(u+qicpJsmm|*P@GPkz zMz_Embgx1O1Kh4Y8;k3tj3zTzAs(~fzk@JZT8Vt1WePh|ryWM*OXH0yO%qO#KiyG} z)K|Ge8XkRCe4pcMk3UnHm!^NMbxT8&nBb(kly3t)Yb3^<%&WJ!ImT(+qI(Td;u{WV zxEIaK$TPT#E(DgEoyEJKE!vBL(AoIEQHcODvHF@|GV{cf-o(iv`_uu}d@Rx$`uGmS z;<-ATqi^EYfpcMP3+&3SKPu=(ayShWU26-7-A?5lMrd-mk?p5@4^W2H32wmkE~+EJIpI&e;Z(vu&7 zu6dg+kI{o{%i()sy+lCQMH4EA!(SK~>7%%wAZcsw1m>qVL7-b&R z0jo8^MfwU>CdVnj4 zY^g5gIsRG#THuh3m1GO|uD|3NV zO~)b~!?gi&+JXjX3yJ1qfk0e}OtGZ)Gdmvy{F0%p!1hZUtiqp4YjO+;4&n zXo^L}ZOSoC89j@((hvnvk!@}bN-&nin4~Wt9+ibhV~S|>e+ojT&6o~Fd6IXT;A0oN zBkhlZQyG36X<+{V3NZa^>oD6NkzyU{9^c`EwmAKV_}7(=>V4)-@Rz(rVf~|IV*d6k z3>^KNx!;V3F~ME@I<}B5RY~u~eIzp}Bf$E!;z)DU~BGh_eb9qH6y@jjYiTrxPu zE5P+9Y-_|rkjGeRYu$Lh+`iJ`cJYemjs{!U zikPPz4ruVb?bN;TT%K05JFjjsGfshgXJESbm|>(?Kl zt!hJ=Ss!mhEQDsHOYqZLTQ3yATZpsIp|2*j4peexct*93$LJ@4wL3WMt>l2K7FFH1 zwRx2(yB`FrIx{^MEoFtj%Yb&{6~XLNXVJqov}`4=qj7U5N+(J%M)YxVts?#1!$WPj z_7%%Y3Qos%CakHz=k7Jwqqk?>yKi6XU2><$&z7l;PebSLi+>W})8V+cLz|gYs{49Z zR4DVKnjZ3UI3I(46Ndi#`%da0UE4g4wdhc#%Ec^0l`FsabLpK6!ptHPAlj#P@^i)zx0hs7yAvq(J{n*>z#kJd$HV34u+>W;{O01S;MBu<{H%0bk>0I9GIIc=Bz! zOsYGI?912hE@g`Nk?_xpbml%Lox=YB%U1ri_wjz|_PAEQs(nfDIG^}OV9#Jbm3-D` zx%u{B{{VcCcCugeam9Q^XJ&nt4nMj_CF5TRroD8I!;mpv&IcFlVPnP2DL-p;Cx-Mv zq1$;Q7|%gQBCoc0JjzjOS7y84B!75{H5eEwPpE8=hBa7VLV2bF<--y-QF~; z{{SzhE45-{2-x6wIaY0yJX7H-Tm2quTbP8X>$m0u0c7AP(#Z+6pPixV98uxo%P-8)iFHBR!W0gG-G%%CI>m$6d z(~`;sMd-fO^L1o!YW8+prFLV`Bs#f&5O|=A^c*J>qymQc{8^sXCwh%ZwaRr zV}~!Mob@>mhaMA~##-Vuxe@N%di=vZtCppHT@O}1o8RRVI^oEvp;(0NhtF`HR&dBv_ef>5um1c=3#k*T>GUEo6_5 z%qi84+CI?uD!7W=cNqS)_VIV3ney~+e|Y*j%0JwXrF_+Qsr8g2`?7|*zvwdm0J~jS zSE@X`e0{_7v-YL73*wzWjta2+>*zI+=;b`L=^j9IUC;(OKPbQeQdFB~va7|Tqwr6S zwGRg0skfA(1M@Fxw44^FiH^d&OtjegKfoUlW$`59Lo`Zu^gXMhjb*bwLm7a0ePVqJ z;SFRxlBBM!v~{j*O$U3U3RNt%7sPsxnyisXG1CII!qVk;Xqu0m9HxU|E}y8i*Ok4G zw@URfGmPQNjA$-X&~3FS#422bZoUy1J_x`awtZIARd)rXI>+~LGO zZ7Uxnd|&v%bZ&06+e?fGo$PfGK#x=o(GSr2I)OIoxc6Bw*$GBY}AD#-bx0VfMQwzy?53EpG#lu7r-8g1apC=I?Gi<>4F3RH_i;6$Ru)H}&m|j3_H8!i z8EwZ;r=@(YSjjW!uvKX~dY1nHciRH7)b9P?Lkq>)8P>GMf;Iq}>%-OLmZzJHqMUCN z=YJD?JiX9v8UFeMo|W~KtHz`c6?4W79p}OQO5PCANb0Ug;QkfCj;7o$iNYl*T=xhr zm7v0v$F+QXskI~TsbXU3^4#G(d*P4xNgyZ*QaWSsuX_hsQr!4F&kpNNw0Vrb1{sNF zGBNsBvm9Izj9o?4jz5EBDJD1LrxeyI9nIrbsyF;2;MefA(#m2grn0M9Rf|_Sr030c zdcKjXz*8I7BdD*OtwLRorpxh4nz2j8TFs@-pDbQtk&dRf!Qtl#r(?GhiH%(m@vn`% zNv7NQyIG1^V{l>#jQVk1`IEFPJRTXjHhk&gHn);yWIna<6IYS!=b$c&aquZHx()%X$`z#Y z)S3eJDm=zRSsI4iVv}NQoJsQ5P1xIF2Q?Qa~uCx z83ks>S~7*CnPhMC)d3Rq#XuIJhbDkEEr#3*2sG_gnTMqSY}>|4)BsHxQ&t9L&T?u* z%xlhS8Br;e08nnXE6%%d=~=UAmaT9TJ?jF9uE^{t0+>QS3ILDh5_?bt;m#-l$its% z0G1~MuUY_-K&3e}0Y#7lL2)Bc%KUVoBx+i&deA|2l|po&3T80PKoYE5N78^Al5A=K zy=+7CKoYX;=7N^wu(45^4u!E{WjLT@i9f9XN#?6}tcWk!zEc&V*raoM_YyTRoWzU+ z8UThutY`wfskCN*D-aJ_fDq6EmCXP%&1K?%AZ6Z7a#YyTC)A&^waQv7>ODnx^$yr< zeNuH&mW*@I))6#03!Pj^a8o%nrUIFXi1X6013jbVWT)?&$R$ESqgtD0OD^KY{X)KE0p;?DZRow8O#Wgg?boIg_%hEYg;4ija^4d zJd0G1_wK{g)>AqlVa+jBAXX<)pES&e@}$6-Z`6TE1xW!s9w-7=QRU~bpbT5CWf_mJ%|Hz$Q1u3w zigQoLts#)D#z>$GAL}p&N&s~0vjJ+zD=Bl-@jw%x83z;rYbdFX)X>pZLK(AEP|~Z= z4A2BDKxzPCP59z~72qSK07TsW6ae*;@Sq3hjtu}x$K=ILz|6cmGI~`3l=6AU1k<<} z`ktC5&fc}lB9WhIqDvv#P}d7ZGt&MQXusKd^}Ozw+QPZiEXQ-~y)N-DEg~b(SEWf= z9P?v!N!l2&JJ#kcmUQJlK=rK_rZPO^;-qZoLw(U&*v%h3>m+@sjN=t7=|%+izh|5S z(yk=Pb>6C}A5qe~B6G9m4~@4=CHN!Oi9HNf=M&*U=TD0Br1d-DvDp~Bjl^~pY1p$I z=T*K)y8OX#KDFm!CTNeoe`BB7`aLq->spLM&X)y29R+exjoDitPI$Y+HfO_^XkkGDp*;V<_AW{SGx7W7~@9r2`pk zabFoU^?fSi*-d*CVtI8>hc&BnKB+qTF(0j6MPnF@v+#0Vw>)OKQ#9Dr9}1(mOopIk z34RfIZ@V-BG=C0K6vLVTu@}SZU^AK!6m?GvMQIVi9CWUgu`J;ANS@MDj+vo?JyYN= zuL;*9-&Mr~wYVF$ z(QHd>n`EG8N2lEw@F+4i8bI*p+O&v{Bg9e_V~#6ph0a!Kdv!G?Wt}w%U`VWrQ9Anw zGY=i<*wVpq1H?u}78wMv;xb5}W$iF}8p`8qP;Z z#ye1CGVW%TIjt7Pb4NLJcCi$qSx!lD(x{|XknX9FF_`1ZAM(QXa>R}l{ zSpib&6O%!l%BS9gLY>0x6>$QDjzKj=A{>!G$&zxtX|fSsL%rxg$ej-A7$Uni*P#O? zOw|%ujkxB386ey!0;IW9KnR4KdsP~iL!26tBscE2wFHkW0^)!mcLTKmKu+PsSwlku-A4Q& z+G}KD^f~*T=l@i-^JBq{J&uHmd5?3)0dQ(DUfhxGI(Gn4#ZBr)1(Mg&S46qo} zGZY#CSlf!ToRAs-bZ@lK1(lF6#Wac>Uy8Jekf}|gb|=}DKC_=+eCMexb>w*oZiB%h*3u=;!SqiJ0}?*TK4eRc{yr))oQAvK2-Rz@sCpb zRMr-fY=m$K?knst_*D&TdzcKZ&R3D*@<(&3T$x;w?OnXeC)aaB0<4cn_;ujR%d3=B zi;g;q=bco1=3$D0S~J_@_-ywr8p2PT#{;0Q7mSP+r^m`}w6r}Z!x~M;g{=2pH_D@$ z@G&)Gij|;WB%`^bbK-kD>voPARJj<%VGJzcE6EzkwvI%29{T#>rGND-Pvu_I$I_=B zRk?%1%X7;;Xhpwo!kCJu$2Kz_4WSs~jB8qKA>VA`i z$3}KWJ+<@LU96)ZjDI@lsTEOZdKGD^%TwQe6a0CfLXsPMt@B&=?~3v(Vbpa#8!*bb z6S6+K@PCf%yisKw_LHlt0mpIm72;Hmr^d>KT5m(rG=@ct9xKeOr6cOH{#r{?Eq?u0 z(owsTxB{yWO{a2m99(SDXU!iL{w%+Mt^UbujV$tKb~{(8Dk@C!FgQ0j?Q`Ynyilc4yu=K9zE{JrV5Hz)GDP%X8~*g<2v9)DDlvjgw>(g8xx46)3M^e z7d#a=jr2dXS}FTXUp6t@74&#{Mw{f3=HctOM#%F|f{|Tln)lhJ`7@9Q<6B}d$@|FP z8#nB3ePNtAsiu+=3}rYI98ql&4ICin|ubkZq~3g_XAfO4b8!L0^-fN~q|{Mue@0 zABAU$lI1z-9+B{(_)UN1``5rt-w=h|O{^4^hQoTi!byqZE2agDmbW)659Vw**%;HD-LaIjP+GTf#bx z{Muv;Pb6cC`Z&f7YJ80S-A=Q@T5Z+VujX8qFt1G4HZr7P3q(|@bL!7aw$mVpKgt4i zub7otGh1?=TjP5@Vr%SJGe;07rF|w6aOBkHsZvkjFm>s*GvVoAmHuFuIO|TlV{Hy< zahE!|@u$QH%oq0o*!hmfuUh$f`bhmV0T{}fS2+0Rc$Xj?j+M)0Guo|5N;WnfBO9ic!?Db2{6`$BsHwLJmycy=c&l&o}WGinj?}%X4isi3eC1aAUNWk}Rf`0}70JrYa`u%*nIL1Pe%NqGg{L+?( z#O67lw7YvBQ}}1X>u(yv5(QP@1sE8wZwph|LsQ}_N_dB%>Rud+>=xS_105^P#XoB+ zqwBJ}QC{(HQ*@OMF^a~DaW%{(8j0BX_v7x71o|YGLmpyuudTqI1`4a8brX;Mn;t7W zi?+ILo15hoc-eDl66@XGbvmC`>3&d9cMNu~lC8{oxq7D^*{I%GUP!Jr%#VI*lUkUX zyfOJ?a5G+o89SdVn9`?h4-oi=|(sPc&0(M#tlswxVxgU zk>y9to;S0X!+O+_SU)U!uWI@wQ=U4WH5csVq0;yZUGWZL%#zHYjO1~Ob!gJ%BBZL* zmEg~I)8#W;p_Oph8LyeBxn76R;iv5*@0LQu4@%G8I+CQi5=JBb=*2_1(FIGfQ&)!E z+c<6)9gS~6$w^q_#zu;c$C2xkwVlWOOWoX)j00a^LMn0n;q$SoqW#oz9z4@LSwDuZ zS5I*ithoh^YUfL#=walP^k=}{KDF2Me-K>@=_G4_PT~(7*T`nFpV>c4!qlYc+o_Xj zs7S~++6Yn;Z@-o-LB&1MlJlu&IF zF`5b@ke%Y7lXpv-fDK7Tgn+OZpafK)3F3qknt&~$063rvmgA8?8`^LO0)RH*i9*l> zcZ#^642y`foK{0d72yWF+kr0M9cGKpOTF4V=&h7Q9r-Xakp+a%cj&95nz~Dze8w8I#B5 zv7icXF2>@3B!*!pS^&4@kE`4GRfP zuF!g7s0x~$`H_wf-KGaCeJdpS^{N44lgv#16#9gAEX8CQbL(D)3*lrl>u(SP_I4t+ zM(Fa(`7rDo)YeiuAY{B_8OIfw(r4W$z!;>!sPSNMDn=wSV~lj53lA~!@z78OM**-% zpa!=+XaZ$TsnZkzS(*1|ClmoEow;#93V<4*9;=?z(i3Ayz~X=-4A~>KQ6hO~6$Ozz zKy;u5Sqxl;pa}%NVe3{wF3c}(C;~m)Py-CSV>Hmv1IsT=^tF}k*~nm%dPE?C{M?Nei-Cnu`j?qWgdO%%>@`&OHkB!OKh=4XO@ zPq{Z5j?#J>&U+a}YGL?#@9a|U_pd(sozT$in^cbG-0kXdM5knEk+txv!^2kb_LpeL zStOlHao8I2YP+*UkJvv1X)$RU6{ImJP!q7nYV>H*u%^o%D_rQ;1J!X}hQ}qjj};nY zg8u*}K|Yl(M4aDJj$oS zxB8lji6OE1Ge5g39GEt_h;dE>zU7 zp;+=dQc)eupY6CYfNHry=RL3LQA^O(Es19rs9G)GN*=(9S~jI8hBa93W6tF~cIjDI zqv@Xqe$x$OB?~HLpkZ6lB_&j>^MTS|2z~X-Y0HqcJb@+6S!=cZ$76N;Zgy|$4 zA<6UfHG=#Jc2~rTmk-7A#5TiAcCRm?r2k>5NuJ z?nI((!jVXkK5>&vm}KiYhz@cp(zrbe@?38m3INTy%O3QGi?&OU0Tlq%jcDOgy(kdT z7Sq6>3oy&Eg#c;Ad<0^(LT54JM%|Js-hw%J^CN+Z%%iB#bm1eYY6oM{bk7be#4%Zd z)SprCu@S3dr3KjIwcBHD6Jv~4j7J}Fa6|R!S&&2~40tt2XxFkIyTw}EWH)x?0%#eM z@-%IVKwYz+Jf746Yj|QP0%-BTqy*)6FCD6w3~@<;5g4rqATp5QO=M)w>%shmpbty2 zx8De$%x&yGTGoqVPCHVUZ0GArAXX$*6o_5GsgN6{L<8jlXD6)8+p3~N)W#PlI5lQP+exxUXaezIMQSFBlBlMLWp)@9yhxD3o2X>mnk2;t zhEBC+Vu3&sMzv=+{j>lP?M}n6s&PzYp|ez(GAu;n0-I#+SlkHdPQ-HoJBKSpgP4qg zpK7Zbb6jH#{U|EqhsjhvVXnLSP!|{n&p=$Y3T`wHn6k0p3Z!D)%kg}mR+}8NWwqsT zQe;@Nw(}dMb5uDdu6N!IPau`;UR_Pk4-mOjkGQ@E9sd9hw9ieg{Ojp3uS4=|Z^W7I zejSVVQSsWiadO=IoRxD#%=oDp{{Vzq>6GB0$i-GyE7VBp%;RKuzk>WhCZnl{S-6ah ziu-!>lwot`Yhs`Up(>Jy+}JF^ypRh5`pNR z6Yxc?;@fTIoDA_?Rb=%(Pcy|z4_15jgW#z2Yc=v<3QCWfyh-9ycDeHPDo&y3eizcA zx3*Vh{qXNz1}dzjD|9`K4LGH#&-_o+C67&LAnM~BSD}^DQlB#>yw&tPok&p*`d)KFf zta)|u!`e}2-hT`(VY$#W_lS%rsD_Hi_dpeHbtSpXsZd1mOB8)7B&5yh zRiy`^JF>`|t0bMy*~&AIG0FJ5PJ>j_W`o|EBUjds#li0;poFu=*_Td2l*vyQG3 zq_$?I$Hh4AK3iD>=cWyHQlks|$j9pPhf`m|l1<_X^Kj&;>?>SMqeedB9BHQYJw`nm zD6H_R0^^a6weqgKr9Dr3>Pb@PN6xzKg7|ylGb}^*dxHQg?Xd0EqIolZ*W5kp!>q4r zgdW}N(Uar0UG)juMnvt!n^9!0lXfmD%m#sVJ)qmrRc8K+H3WYO8|Ka>6RIxmxPQZGph) zR;sxi6>xB+El-laZk2{_h0{pUsg^0!hQ}hVO|!Emo2iK9TOR@G#2E4`t|#fsV#pn9 za!77Z$^{5ap(E!auH~{XyGb%c3QZ|6H*{%2k4oa5TQS_ZaUcUV!-%fza@A*7@RIRU z#K@!6b6+7TUb;R@G}0-bb^I1T>hbOUYxG)A_XzozYgGDIP8orJ59MAJeb2hXuicb& z!TyaP{{UpyMi}$4%iTXbe`@Hn<1H~y3%u9T($vo?S~JXV7+b7pJCEsIjoBW=m%fho z;m^ax))VcMkCXsbyw_6h$IoK%O(uI+gMJvkrEm|G`-S$YbLBo|;-yjQd*+qkt4li= zl@K9OjAEbG{{Ux95JCt7y)UU= zmpVD8EqPAu^w@4;R|+{9Dt^%)8np;zlTEgQOE}x_ zF&M7-&y@Qdn+XZ-e9|>3bqj`Pa@jkpN_wAj8VZ`w(Tdp~R{Gc!#oL7yBtH|t+u)yFVw zX64<-74Ajyqu#RX#)#BL?H;HQP%Lse95esfe#p*`r6p z{t${RJgOM=u3Sbj?GxO=P;*+M*Nb$B#+4ye7z>KW30cZ^J8I!!PgCIEk9u8`c!uR# zH4=GYvx@D<(sGT_?BFXwzE*tgHLAo;!QQ@d6m``4XxVH~xwa;~2rg5tE1C1fvb0BB zHz-Z_s!r5I?N(FVrsU>;i5QQiKtO9`P-q*8YG@f8g||?~kmQpcYa5g~xmp0DDxCD7 zutQSV4p+lf0ccC|(iDcV^sJgEn25ek2WnOhVGc8z$(;iAN{}LC$4ZtoM}xYcqA3ds z&4U`R+OsMmKi(7@M014#nMHN^oC?g7R@ix1#YslQ2XYDkn-l>&DTSm1(trUGJp}+Dj0ymdC{QQ@MFWnM0K0aI0AYWZr2s`2EHOYDkPZa^PUyIx z2pIsN2pb^K1?y=5#%L}jq6~mzr3H>y+CORvh^;B0u|o;~YY=fj6U8Rl0K(QKK4k!6 z7bK8q0<_PxaX<+b*-?rBY|;q zEXSxLE-~JKD_-9027oc%;K?8(r2rD!q|1Ui6u_??x0XJYeL_1EEXNr1uR{g!vKjTJ zw>B-@dsc|u9#yK4Wj~EX&WITjPx9>*k~&P;nC|J)h5~ND17{dYECd1Y zHi|3&J?O9t=P=-OqQELHM<*0m1ZaRxqKg4tWFYg!FdmZR^`;`!Zic9m3AsNl05T>h za9fH1S(qN3DKs?3bsSR>8-l#!6wuJ*pDV>%1R>2LfF7MF0mKo_07bm#u4n)Ukw6b5 z5kL@32s}^(DIq)>0GdCSsh|e~gGr$@K_|!w=~6Q$?owX|r6ho5NIa^S{PM^Y0y$o-1BbB6v2ZD&@H5v60ggnYe?`eTmf6rfA0B_*I(M zcYIe;W(Uo`6m7hXVV41LYo2l{FtJ<1`V4xFlgfvc9jk^+=|eoWu(NEz>0Hk?gT zJ;(N9(-QmQMVq)AQ9-U4tH`8})eRb91%x9!Z34ZjCKU*EsXo;SkFlyR**(4^Syt;>1Q()UI(RgrK!y`<=@%~657gm+upqzS{_|i zd=uhZZJLD4Iud`KX)<7QlHELA9E!iamlIzRlbf7HC7azVnRt(T&Gx-v6k%~ZiFM_;j_E4z9 zBv1#S+k99y1Lazz>|`w88SVx!NC&lRdjU@G;%1|H8QT*TKGhCmv4s!Bd;4kI_5K%hY}?O?VkR zu&eZ>XmUEu)VHyljjTK86s$Yg=M!GLM=F<6MUID+~%`aaVB+`z!i!NCvDKd{70oh(?_; zO29oN+eLtw$O3ar21hRQf!319Yb+f1`dPMq1!x#^k! z^!*3KHl9kZM(k8slVfJ~|w>DFEh`Fa{{N5X#uiXhBzS zX+|p`JFg4P4cnXElu0v#<*`&!9PyI9Dr5|= zw5g~hLiojMS-qhq=@jCE4yBkP&<1_UE^;UWruJTfGt0SgQc1qP#4xW`HekbV3L z2`k*ifyZx3XlRNi$67)gG2*PENIu~8ri_V6F}D?HmRg9J0L?Q7TZ`qOlENur#WlGM zw-iM&S{#)Quff>+bXoPF$sYABj8kZNAbrtVp|jb}%F!;qZ7uWq6+q*S=v!lBf7 zLiJtDUGrXbX!5hi#Wn1i_cy?zKjB3|iDFRrVSlr)Xl;tT{ZY;n&0~{YO=rF(iIoepBsTxXe8_6QZ^P zq;GTBbiWc`CB%ET?LO7>^>DPg9giNBR~gv$4Ib6C8w4`*$9njB^&?7lN8VvD>a+|W z5@^xcZgs+g)K<`&s(4wZ4N}z~AO6wa9ft1Y+<*fC&MV!)w@lk7#ymQ?=U)rH5!`s2 zQCWf?Bw(>oo-4Yod+2*utkFsjQ|r$O_)^EiRK?7t1|(FcrlxK~M)3}C4kzyqaL8h+86W2pUIA2Imf;XakEBP=i& ztHI+HdVHt5{?1<# zpqoHjIi(3CX8=^e#Q99)11fR65%so*;y7B$HuFXZuR9Rpndac}Q=++MK7SDTvf&Q{ z-m#@PGI2QCm(h%Ue4rV6^kYUd*x;69S_2^0Op^%5IrOb%hqAe?EL)SaH|=33pIY-| z?0p6vo0g@UiF}Apu&kpSv%a-YX9LTAB6!#PCre;H{HJ2}uWA(4KY00BRC9MYpN9Sx z8pOi-@8;gw0~Ocxu2|@0Df7Kgd(u2V4ZI;y02r?ZwkA?~pGkwu)Ro!5{AKW@7kW>e z6B4fj0=sZ^T%C?QRvI{Y^B*u?_%=(aOnzIOH>G`LC86I8Whucclfm%X;v%x5H|1Lm z<}jMFXRmxLg5ylNg(L-VYs$pJ>7F%tDaz;2){G1z>0cXDMtv3;r%dHMa;(}#&cVo2 zUZw@7BzU;5c1+?v5$KT#E+o1e=3~>+y)1m37Kb(#)gGs-O<@eSS5D{$B6t~(;o81MKF`Mjt6gQ_bEk4`N~PgTAx47;oGh5BS+xyE1}Y{^y?8#K-18? zHGdV%QaVZUkzLf{k2&>WvbHUDd|2pbh+ENa6EBa*1#tllZV86&gZ3R zS3haCSM?dMm8}k0So)kDPH2?1pAki+ISvTpirR)5*4WPxiS|t9t~_{`0IJ4UAKvag ztG!@c7N?0<6;isi5%O2Mv{nfeFtWjTcUwSJw4f9&Y_N`J;a znA147U&HxVhhKB=FyF%{Yljd?^fl3iJgmpyKRiEcyr+vavN#*tn)=!i_f9OPPuke= zsjp1Zz&iZEe=6w2u3BfmiI*)oGwaWT{u=vkiD+;ZHBp-RsueFY^SpGq-L8E{;ZF|3 zYha#KYA`y1Sz;<$XQ70_`$%0EH6#{G&jN~1+8mg=UsgG-f5kT1ZINlD5w1F#^l4Dm z=a&lJ)!6y+<6pPd{CNwoes>(DXdiXfJOE)$D01Cw(l-AYZ+T^J!Q1le0VVcozuV z`7h&Ngsi5s6S9Sqc^Lx&y~jZ%bC)fRrkg%?(!4O&dby1wC+FM6d2@36YpXM_2Z#3Bo0pm-ByV_w zmFQSzqMY?QYv8?{ZhZOiAVj)QOm4UsKKyjAT0G5YaNzO(09T!v@OOxO z1%KfEPJ4OcZzvt1@moQ1v-M0KVG7HX&xs0w+b8s|a*w=@$u}2tdJn^^Xzu4$^7oCy zt$29U+^l@IXIju_-`@)~M7{9cx|sGTQPRF?E=q^SLXXxN-dlJj7FBf$0@tJU+eXK& zM=DEGcuO9YZ-bANbgl-SX0A{tv%k8|dKf(}(Us!Q z1hZL_rznlU>08jox1^cNiorj!eB0v*XVUK^hF}A!?Ow$8ECMD`47#EW*)AY8@--f(=-4>glRVqv z!sAbN9AnbG+zzPuEPGL^?tHD{32q{|{oJlcZng9j;+g4Tu+%rXjJH~nI+bY$;%lNb z;?G~|)LOagp9XF<33Z76((KE}Q(XAEe6VNFVkyB$`j)|Vp#K0G_=>uorU_XUyi{|e zPxP%YYxk(?#y7}(Bk|V3W${hH^@hF7b7}_pWMd_EeA|5qib)5dBD@sOu%{giz?+wv z*w&J}C|&_pj`;3mPB7e3I}u%mYRY?+$_fV&2OVlrV`e}{6xa|13Tjq>JIf3>;)khm zB3Ot#)<#6h7JBrc2F(D+6dIL$pK1kSHd23vhC7o)NMA|{QW4)hD?1EowE!aWahk$K zM0&3kC5=65SP`%U)=be1m2$l+Hj70bl>;URcQgRkIZ6O!xcSCv3pxu+aLNryMWq`^ z#Q;saiU67^IHUxT51OP67+U~P27~~TC<96<$}T7ZH*A^!BVesr6o@a_JZBUXj$Y#- z&1TKEBLj6!?rMg}yG3L&65D733123F2>9zj78c+qKZO7=$}>O+K5_J*2dm?@XaV9( z@F)S{GPs}uD1y)hki-fAXqe}k0F&==Kmd;ingEhA4-^*@TEk~@C@h-jp&Vn10MB3; z@jwxKopuirxXB$U{D2ln z14c!{WKaeb!;wBKl4g+D_TSmg=s~K=PxmoiJyW)c^>wDAl3Qc_kz7dBr6C0UO+ws2 zs7F4PObN}-*uWy8fY!SRxdWvjD*pg!7Cd*V0rr}K=-gn?1>1=PjrdRl5)JFX=|BT! zJm#1YM&auEgmxrV{zAKOp9?aP^`D6$U$IC>rEZRtc-E;V$=G@mQ8S_jb3Wmkgmf~i z^UVwp-QLwD5w*tF0)Pf^;LrtD1zvz8RwR1R1C(>fpa#h*js*ZPHb+VTVM2OwOhT&d z6-0fX?|Xm0O^L|)|whBsrkN@Y!NESp0tV(e5QaT zG6v%m0TXdR5f>oP0tvqg0GLb0C;^^MUy1+1bGCBcEWBMWPicB$x}y>sK3eg*ci9=6> zeXJ8wjak-_cJ@z z)-gLvj}FVLT&I%>e38*>ILWkhSo#a#55w!d6H!~{jK)HM30l&WUCBPt(tJ+eVu?xQ z0bb1$l4hCKyuuD^p`7wAL8u1CPAN1~*tPc5MdzB@D8-z=i6x1xq;u(AaDmHHL*Vb) zk6T^x!y9vxUD1t?Dtez2c%tFF#=y@sOHn}Nq%6ac+OgT1NUJ87CD{ZDu40Q;-W8DG z;*MdLqww_B_&DiDFpZr%OqS#2#T>wF4+UIEyaPutSi#ryDfH<6>HMhX3mBI1Wc_Le zT>i&t7b7$Qr{UiZUA@La6;VJNI-QeQNf>;b&}4L<4|HpNQ&@^Sg!2@)3Tj)iqDRwq zUkfz5npS_;cX7ylJmTZ5TTV`KuW_9SI*o>tC}an&`yC{=F9`@~n^^ z2da1}>~CeY!3`P3ZDOn~zlBUu{LBy5lh6#y>md}%Fr`85Q*gD8wV}3?L@R~P+G0Fn z`1yL2hz|hM14j*r*~k{G17o5N09w#0j&cgN6i3MnMUq9Q$RtusHOrW$94@MCT85HJu1i}wIQbi z=~)5j8Xc)IrU#_}dOwFlEE`P*47;l)V>tA!7R0lSy;7|j7C^aLPZ0Dr}NQODP z3JDy{8-q;^5X_3bIHV$}`Gr|Un0{5@Q$|GC+|_87T(?{bW(=Qc%>`&(eR%>reXKnGscb~xKaL@hl<%!c`U$4b)1UeU3u z5gkP<7N%W}5LSyD*iizlG1y}w;;9p%LgNb5G6^k_oN+;#lN}^Zy{kk!vL18NsLPm@ z5SptRDHbDLrxjKjM68$;Sau`lJkenfi>($JY=Ems%5KP(DKaQCjJbujawVY79}%@~ z#ncL<%xY?FctM4}su{X&73X5v;o{#dHhuH(HgR#_YdxKbt*~`9vH132?!ITxo*7(Q zFSTKgdY_?i`u)-;@es|7zwV#qS>YY3Bhk%&hJ1bT*2W31ZDl8GdW!u9jwji~`K%xB zswlHZ;Etmnr3~>&AY%!@%|&cqIyti#`zn?`gwgJlK&XeHubitJyPWi~cGBgG)oD-w z54}Y;qI!{+wMIs%;i#_cBmnn72)|iSK0(EHQpGhTVX7X?@qY!vbRUSV1b@3$d}G?a zhY@O&E?~YZA4?>Ap;8Fqz7kKB>Uy*zWMKG`&`)6^mFT^z(ZW=;lSdXJmpS?M`%X)5 zqIlJQU7Ln*I#<%H%1}qUmC%H$Cu5)ZPw@gvU4GUi!Hz?U^D#NBn%wQpQl+9t-d-NI z8nv~wfD8~nm2gnUdq}&Vn$wRpL)f$^j5f%vi~t3E?k-W3u6~)9d$2HmAPu&swmGjy zFKM10dvtt<@kI`|tO%PYJYl#M?>Ta8csNTnsq4Q5ye2e#9w^q|mmhgXFdu7ynedj6tz7s!UzQbSQ=Wj<5WJ7lsZ))3d+7D85B7xC{5>#`v)o0DZR?84 ztrpSY*U3GlN63v`NA|?kiC?qXqVd#^nz*A0)70dv!vuN6#g`gjLUEJ}-dbO&1wmeJ*8`{k#k4*R#<4a9z zL4#v_$bM?Zj2zL1K8m3v(eHK|)wo1oF-bfDTyT5c6 z9VemHh@&uA;$;Oy&Ca&i0E@+>iZe$dK7*lie(;NIXEJ`cZPA?_rAHzv>mEw zR@&vbj%4f4N{V&eJ&KhDJ&a%YRhnl|gTHF%`t*vH4asO#e}KR4IUW*C znig6-ej3Dz3HnxZtM!cy>0z8*&1LbGw3gsxD}jtxBfv>Y=dAInP~|)iJzbvXj_cgq8XgOG_xEC01eMR9*AG8ZM z-R5ppIUh>ztBQ>2-5)(VKF(*iXqKWYbWHRW@fez(_0P1+DPAWh@g~HB1cRn|uVXB# zwYlbJ6uD26ziG{PS@7qEB)-z_niN5j5a5IDU09rAi?JCLBHHFY7roZ6{7ru)#-Vd8 z>~M1=6aJ`He>?4OjFS3d!B{xw@dRpLkyVVaBJgTPxh32{$~d1 zqwfCzfwFng$NtNITK!`T{{V20j->BY`fpDE08qb&@~3*F&hOhE>2172r>Xw{o`)6nl%!>n!{52%nkBEDeP&PHW*^eF%q40^uZ~=_+4tAO zO9W30S}bZ8bYrK|zE1-Arq9T6IE&Os`U}EQY||rF^san!wAuPrO=(g^PZ*{xTOV5A z1nhX+K^sNb`M2YSp>b`bzR~-z1$!~BldrtC6+=&F923+Rsk@LL% zexx1U+vYhZ)Ym(w%&vXL7Khc9kyk>6^!-^R-H@jkrv1V(6z1c5pKkuo+P~QBBOrml z8LLm79|MW^CwU)CTV15FsmSSE5`pVc#6_Y*EOQJg2BjwxSxytt91n>87TRlfO*sgx z$2_fil<{sT@g;?vOq*YW(c8XN;>Y`y)Aj2ZJUvO5-@xNcUom#GFH8(pu*Ipn9({Z? zptU*=4QiicU6Xvct}Im^ePVZIc#mZ4%lh7L>mXQgS^@y!@`niso-V_&9i}<;Ps5l$t35{SF0RsS3`=NYDcLX{28%z!B*qb6{K?%j?ZaVZA*Gb zfRn=$tg&z7)|~N*y0ODjH6reMUX69WADCkm^OfPQhtc3F*=keqMfy#rNfMrMTHz_U zPnhY&*NmZK;GfzHTN+=Br$;CKReHF}iN?oXSqU@1E^h_VX8!Reyd|g(+O{DBnzEka6a-LINQ&{rN+>|N9Fa|cg#oByV-FtGlP)C6 zgB2uLLb#?PH%y8~rGG5da+P63c^Ik~o?9(vVhALG%_7K5Qb*dAfK*lu(z)b{i6aI@ z=cQy>QK#pcmISIY0Tq)pLXIgGiq9IeR5UUYxW;&?0#7cpxnd~UAf^XLV1INfN+sL# zW`GgJ07)KjC<5#R3P5N@hQ=yD(YF{PfG-uG04{i-2xe0etcpcR<^9>9q;r?9R!YW3 z>sCaP?X1(WsXMTB%^)gB1prm@{%>jk2Tytca3d4|nLgA2^gl`fQS2xI`RPE*5#+Do zKog+ife#o1GywrL03^6-0Fmwu04y$OE+xpP0OEqlnfDG206G}UPz1hC1pq3?wT37H z`y9Z2lopxH>Pdz)6k$ss1d0G={EP|!V7^Va6_RGjUMNkziimUTas7GSkI%n_| z-GpB2GLiLniCm?OgZ-N5M@l?fSby}F`qnZ#AZ09r1XgCmOxtJyf?=8fj^RNR0NZSp zI2|YeopIY7Py}L4f}}z{$IHR08Cj<1iU4xT+|UAvUy8B^EXOneJfQJF5EFqw4m@|D z2r-jYVyrnv!NKB~7L~z3C>dPwKnxF$Y5-J}p7a4IIiLh)Bdq{Ko^omcGl~FJY^^XQ zf=#@dwn`eZ03_DqQ9+ky99L9;*^bZYu8`E^y{{VMxoMW)6jvB*qJIyo0 zk!jcpfGG48*&1mX^BcDC2#3q*SFz4oSqK7{7j5py$20+QYu(WGu81?9N6#O$2gR8@ zBd5rTHrF74E7YcWwP$1CKO1~Ox$!=yD@`UrCbn|1=i8j%FKrg_k9^i##-n4SPgwixfZ6)PJ77HCAjSzGg z#Z8HO952KlDXh-|v{5rWtL@QSxX)u!)rPk^og)4@qa8X{E=GrMplmk8t~sqh&ha^n z%)6Wn3INvlTQ)pJYqhdBe_H2|;(foMM6+qowmHE5RVkaE5&KAK=?q?Sj+NU5p~*L6 z<|ZCwyN^Rq(J*8)1y%Z1q?B4fTLYn>N51~e7qDr1;(4J#Z~!^3Su>&s*pDnyz|5pJ z^~dQ_k)w7#H29$#X#O*~bMnSce_H8UZgVzy-nrt2z*HE)6%y2F$gtJE)gki-TFsgr z1e$`Sp5OLsNR1w4su1_i!~4{v46>}EG3$zyBK3$8b*7lET%0PARx$^PU1$OWU^wYf zZ~+gMQff;XUPUW`M)j3WD0&fQX1+^tQ_hfBsVlrs_k}KEEXSqG{{R&n!Db}au7{2V zYtDv89X$-N;|aQOtmnJKPVuRn8V2X{@!h{Rb3tg^d~f zQmli1#mBWzv=xf&;+PC105eCl3m8{EAREa8GihTZLa~34kH?J8#g-^5>F&En( z`;`u&E1a4O@~51wTxOWm$=2XEdb^sYJAUS-H>mZGnrMc^oeD;3g>sgkDHmftqOp4t zk7e+Bq^TZ#1q))4=r%AcYIf5xVrq?u8`w}La5|fO#MYo?H2tUonVSUqW`HMIGAIGj z^7H|{q)Fwl6cBWZ;iIZTWeb(s{8JH9Tc(e-CNx9ld50#1430ty;)Xbh5F4mPi*+KD zG{D4_w#h&iVLPxWB1NHTo_kP8h&GA}h^$nPdJ20L*cIl3Fbq%9f|C^Rv=%{drbOa^ zD69+{Jp~3s_A&|pjhwI4-Fl4sRy@n}>(1`7;F=WuU%qSt7HqwoN zRiY$MH%h3S(k|%>y5&Bin=xm3Aat|Gu&rXup68Z$`ZB^y_NOLv$8*d#d2(QOu4!&{ z!(&35>}|Br1aCGZ0)RH{q#jWEPzFA_`L{nx)W@m7@BaWuBD0ZJH0_q5^s9?6n1W3mL9U3QbCqV(3Un2#g~{h57Ay{Gc}?y+$l567D&&7>$*CBk z9kg>;F2}2YX==xr1V276bb zUh+rMM=U7#IWIx*asp`SvFV zEHu22bklDk)Gd|Ox`EQXs<3I;^{{FXN%bw=&lDEFHr|-5Y13|c9k`mYM~};&Gsip`s0|}kvbe?qFwfy$1$u3sj%iBn z=jp$}ZEnxRUKO*rje$ECAx}!n4@;9n^Z4Q83G*}CbnB=@3NknXv9Cr==zYcy5k?S6 zvz7RXa{<#OCP1Y@Kc#lzo1}d1Y~zTRx_mFBdu*6b~!3aHazRax|CXDO>oQ?ViV1Kv}G8_-aZDr+o{V; z`A_0+?FT)+ruNn{`ADbcVr!QXi;B?qu$fIs#Y#xI@U!D28olU-_D|oj1I#CbQ$rkz z$7N3s8>8xv4#@?{jnf#~In8xctf4sWX_N^z&SjGV9UDcbB_nBw2gmyW!gPioP%q-jv z4A&3!jUs#v`%3uU-%-$Ydk7sQ+53V=ZhKcICK9Pp99c#e*vrwMCD_;qXDui;-) zg{Nw2=joA4l+tLzis*4O*rhXVtYnQ5N_XP1X5}JkMuj@E+u>0^kSJ6|IqE15=;?D}uyh5-?Zcp%IimY^;=~(qM z3^RCnAEEyM12x-SQ^S@r0=q;?G3aq#CJGX!ozKUycqFPOeI4QHVQWFO@GFZI7bzd4 zTkb6YYQ5d&PFsMIFA~n8p~_79x|AJ_{KII=syv zV(5B|K1R?~A9GwYr6iAcFRf0LEpq0ga}c(CmFdngQAU$eI12+CloAzrqb(3*Qdei4-7A00nuFqs=>>UNTCItk0n|X`(jC zw;089){9K~%(|sML|tY9xbIx)SoGYL^cFSEMCM&MUr{Dyjd`nrY>Lv17N?PXWs=e@ z3ieP!$YLWk@8Mjce9WCGHqt*oJ|Np#+*|peubrN2=pfvWrpugpFJk`y!kGoCVUHWD z&#fL;a~#@yl$rVu@NP)k!`AG+mGjt_c6`=bFMjOxm`VgZ{#D>fd&lV%oKy}!##c!d zjN6C`7o7h9TK90vL*#MoPG{wZ#+_VW{8`ndh4T_QMf@wOTC{rEY7w#dM!-VyKgggNFdUbIf`qZC#`%d_~gD6yIXs*e69!5r7lC`G585ePU}PL?*w?} z+r?T`b7{9;YB=Z*BD9J}##M~)x*n6^m{>sOOzzKm;KWVb`V77bOPWUB#r3W$v)kl| zzFG>au;iv8iY8u^)TD&z(vsNcd{to+>GBBE5ABNd@RcN|so>)wMw;00?;H5qPY77N zw^Ly-LIQdAuc=C$Pd^Vw>k>F0gZ}^-Ewv3+cqh0A&vw8kziP^?;MI*Wd1TadN77y& zy9Qv${{SOnfnG);H%E%V}*+rYZ&B@=2aq`YBp6&EE+i96?lq05*CUD0rA?soGu^Q>m$I#IMUG`IpUv; zx^IHC>8`Kt14>k|k2T$koM^_;IVfOdik~w+75Jy|Ec{0CUES~-A!G7=E9O;6RC=GK z<@i5fuF=cdD-`S6w@0rOB#YNIyiF84l&&D(-mOU5D9yOH=9SL0RcLn9^9-*c+ea|2 zCUeCXHMrpy6kKBkzD*+=4trycC;_p+@z540OIDYq0%O*}fyO8*<)zYdA4)DVteRdA z0;D;R__UILhNk1@ER7pK!%F5{;vENf@cxusIfsu9WNryqGj4K)-i${Zcoi(ZV;)T* zgMHpB7HXLrTC^vCNAj#%xw2$(N8c3%6y;za6bNqIoYp4jYt6NADoqg#bAuo>?lY*< zBvyz~a*ZX3GVxRdE|@r=2M>~<1x&l~KpHR-i=Rpa(WPNGnAo6`SdN4q)Bx0eXaZRx z5+LHBq+;qTvpt0dkufBT%zkSYY_}qk+*Wcl8PRc21!;Jo2IMf}fEdaYVuIo#bj<>4 z!^~&%pjWUcz!{)Kk)}>MPz8@53IMSP9Ss00VS03+*hXQtfFSSfKnHG|Py-f#E<$pm zfF%k=09cHLUU{Gkk+1+#M5H{LrMW_u0~CO`jwFk2J5UEZt*+etC^b13Y{*Bw08bKN zQ~*eDhJZ6>x!BcUIxC>nErA@jSsYLU5>q17upJelkc?LyPNzgRJ(uBDpCzeA1Jn9f zlTh9Vy=ga18YX=Ob2CVt2LAvuA4-CXYNW9lsshYs$-&Jg1a~)Ha@2ud1J-~aFU+bv zDr^PIDV1ZvJm$1OI^}_EsKzRQg|7AZT@SSCuZlv_xSjg;wkX+}FO3c_nCm>J)w4(-qE5|E4V007#b_oO^@tOdwbt!kq z$e;@6C3v6+dhmFl2*Y!M+JGibJJ1BDj0&;?869yz3<`G+cr*blfNVT-KpFB#`?%>% z4GxZ8Fl$5%cI+XbK(dgvB@V%Q21496C<0i3#Q+T^J*WY5#X$toMi^w!1XB!jsadKIUMVU7?>jAFVHIOVy_c%#NB%*z~=)fpV_H$1Og)_mz0ZmE>b zV*{7IVIrF4Z0ShEmrrR;NwPvirx;r_SbV1SnuyRIl*P(+9vJZ&S}=}d@}8NXE_yzj za}DY#MjX|nK4xy&+ahE6RTFab*4xnS9R({0t!oRJF^Z5bn+GGM05xnSQ_`3Zk|v5C zMRY+N(mpEvu{7Jc!MZ?9W3DT;Nb;o{pAua>o_=wRb*A0SJ`wTCtBuv6rIBH8BZy}s zrC%u*W8(1zv@SUne5LavsnUE=Y32RlM<{b5?azwr{J*?2OO*=dLGdI$Z_2<5IZ#}= zCy1l+f7S|0AkRMWp1Jn5V6D=%NQ2AnXJ(hLLs-bTqp0xnv&K2VHOkhdqtg5~TkNC; z>05~B{9$na0B$Lt;h+wW;MAx75*PtJ7*{OE6YoC_T#U7S( z)Qa^Wv^XZulP+~s$O5IUNP{cA&~-SZQBfSVz#XVyd#Ax2DerX{pLWGeVz_Q}M?>p4 zwGpT3<1L+w$^0v(5eCnc{xIvY>YgH)Cup^z+qzs*qM!$th5SG`TOrF$;EX}Eh=~kIZkF~x9 zdF@egmR{*aAR?0BFsf#zeAnVUdsc0XiWx=Zpn9AET#l)BJrBZv2Q@De0W5KyoMYvz z9NP;WJpTZ*2Cp#6sn61{oQ^`O{>xq?jq~N@ew6vx`AZXi%UaygWXH7qDE5pwN|1ib zx~dFFy$Bi8HZ6;;r{^f(Mz3+kaWMZexU;KCV$`}j&?p%JN^_drRI=9{HtGR z!;n_}pS2;9x%>ql(T5?=YkviG+d=;T9&*SBq8guQ9EE)+;Eu0;*lS7S{{Vm=@T1xX zA+0~`{{X0{RTlFWKkOgyrR@WhuX+16c#;SVk?GUOJ&^wZg+FL6Lz91ony#O7 zWDSf^LC{1&PPIr(hGrY-h=zECqopP^XS8NBLIzTq{%RQFKhDDOLM*OkE-8Ushcp2- zqv3@FNYpnQP-22ga>@zff}%ygG!*tFQJ%eMC844}Ps)NsbRc8WgHk=bU{C<4C^7>O zW$8c^ZXm+oQ@HL&3|w&03z64!KZOh$}yVywuy2RJkkX+qdhS|ceIjD+Hx zPI(%dAN+krJ;(H_W-Rn8&)wLm$8YIX6F#Src;@UhlC3uGZynDwekMO!%12~2H0(ze zCWVp18UTfmyn=u?ZDak{;XoL=`3nmVYS9_k;i89q*}$lgOJkz6gUgFJr4h7QM9ntH zWvfKRXUt&q65>7mD`g{=L+tGEMLuJiOxT7DV>PrUb4ZnA6Jex=rG=*l;h8#(MhJPOYrZ}Jq3CYrO@`USZLFiGdayV+sV4$(2?m~ zv7mdE9X7T-TfqJ@Z6j*46wU_~6N5*|fZFJe&&9st2efXyqk?O3zRkt~I{in3eZ&Ho{ZuZZ(e8llutVcr`LrGnS{#n2`(J&g%2HL30CNQ z%kcxncRn)IWW1D}&PnyG=c)ENT_-_YVh9#US z#x^nIiut@wCDCWcV(?N|R)!X(XQ}cYqvN})%~_<792q)QLMPHk0Tmrl^^d?$i+W_bRlS7w zN*sUyW6gEKZVyxBG5846g^~2Xhiwh$j%2v<;yo2lQCrUw7LS*uLVVh@(zIB_5e3e1 zUOjnS`;52UT94PAe#a;$99DD|r)FV4cF6hr$NvBm?K}}_IlPcy5)`qod8;iCjH8F= zotg0m#xIUHo-w_e-qJ(%a(3`|#X@K3d45=&Z)3|XB9du0F|PV4A45)`v(VhOCNL|# zD~j6Ox#tDI^{l5Dv?)6wqUKv>-xtC?cs_lx{B&loka@OhqH%-=FTuJC`x z{{Rl_HYhFi7eZkUZYi;?<>yZ`~>4Yf~%F z@ixK{x=m)rG4C*}A&0KsrfU&eTd@zu4FDiRuUH246RmpmsMdAKXxMgUn{@+;Y)6>U$1#${`rqq+5^Z-BPEp5m9LaW(8)nV(~VZZRY^a1l7UfARP zu}xBePYsG^KdgSx9vU)vFRVq+_c^7@80=*%1}0Ba=C6-m7hv%OtEXvl8ErGnoip_B z(zb;8w>_-CDV!v`9xrWfWb5(|df(Wmt3LWM_EE8YVc5S)^DEgKuq188c(r6pE6E9F zuMV|1&*D5xT_-&cdiW3fN!|Q9*`#X&?HV&42p>FMxUFbWjD8d6C4}~E^CRgSPuj!5 zXJ`CPL@~ErKD2ogIsL&RvCQsVPk}=_p`TGK4F$fyPHQ?^9|qvFj+btRsxkCQM&K zh0jXCqeD7en&&zQSu(s-6o^_gkx&73oew(A%&lRbnue#vC#^W#7~twpA39b_=Tx_K-QjnsoGu7xj{d4SECAv=zZ2xLD5Z|%B$PN zjyT2!E3RoxSobk2o`X#KhxUH>mlcJiR?^=Hw(wIp=Dcc^c{V>R&TxLlOH=G!2jcaF zHc1}o8NeCa--_Yq=G^lsV&_ISInRi{9%9pM-TaA{0FYQ#)MT2nIx19^e~9=?(osUw>ON~EQ#K1J2FCcU_sK_q12y*hD7{TByLRO4ogeiYZ6 zNVp3wcJ3V49wA)i%y8b)Ha@QXpZ+@8+iK~i-(DY zWDU2Q1j*#E$ZX_(2E18NcQ%$bYBoobd{+3S2A`naw6muQepTl@SEWXqNb_SkRCj0R zkBM$0)%-_wc%OCLx93}RKGy>jsWK-|-TbH9@~+f(%~}Mus7rDMbSRE8zd}AOsd*U?>-6qV$?nyc!milmE^Kw5#rBJrE%S@4-*@fQ>**SAEJH(__LyTyG4TU zPr6o82H~H$amY0;H{AJIUW4Us9qnF@0jE zW*_jdEmI+#SN;W4EG4>|M!jvUV)&!tPMfCb?{PicN|^rjZBB}bomw?&>U=}-&*Fgb zUZWtklgzeKQ~-Fzce;wl(&c$?vXX4@>m7dkMb%@wfy$^n1vP`SKDMqNokeDku|5fW zdbiYcsI4!j5L~DLGa97%&&~1qH?h@jeFdX@RJDrSVpWt5;ip4vdQMlrZ+d#3IEL%?kqngE~ zVw}(%WGyu#WZvZ`kB*fWETM4*O*@H^`3;)d=C&q40KuZ;9H3J&CWbx6fGo*pl%RGU zX=+r^OEd&HrD8eB6*!{7&N@3pk-Ac~$meDF?4v~KQqXgf#{@*TUrU zL^M+a?8M&5;Y5gF>vF8psAR1{47X zHt~#50C7Qa5M+u3-N{k%C>5?d%Fj;J2$ff1?b3iH-`ap1xDKC{07hrsL9rsMHv^gg zLWFxz12+l)aOQv}hagY|q(3bHJw93hog^jk=}Bw_sK!lF+-VMY&fX~qxoV#$;X$c| zuMWRT0OF?kW5VU?-sRAa+HEk5hV^%@MV%U+@_)kP_ zM&UOOGAov*#?aXMTfklqOKTN;dVfmtY8?;DJAml#1>gW4SFJD!BVdG)QoxECaa8&Q zb|*`vtDoPb(!Q-U^BarTH1prM1oM7=l5lYx<0Fe%A2m>b0 zDvBe?cEvH1y}slGRS-kukbS8!q6vR?deSHn!Ky^CnS(DBm?2@1DC<&z7Gk(k*q~yv zjL-zpv505Vm4#x}wx>0}%nmV012q@MQ>q!d`wM7?>R|R`eWOcq6)@~vJcTiE^=-whM%CA&p zcbaX~bH)xq6u{Ji-XhshDcFK$2)%JtU?zeUlb{rm0h)!9{hZ}RbwwP~K707r;B@gW zmvL-k4Z+23ju{`GK0okD@W!uk1111tif0^;GS;R0Iug7QTO<)VdG%Saa7}7UMkUSk z+w+nuE3qUIYnO2V1cr;Uxe1QBc-Z;coyp}My4~J5W}@P-_114m`|XOZVv5&SHxqs0 zd83#JVX!hd%}aMJTEYh8b zq#m^ZeNo}xk3JuZONu7EMRiaxO$c{9pW~OrD?b(6?X{RV9+lRfhdhs-9%9pb)?C_< zyA#JQ(a-5nYCUdMCsIJFxQ}f3SL3}eLXi!`o>&9gvgI~CFT@`mbjQ-(3t4wNa4UF{ z)z2AyNgz_12bg#n>8pzEW96DKt~J(-j&9c%Z3euK9wefv7a*;^q>hOfr{}@ zm}tkjfy96jT-78|>c0Yh9Yx~L5L=6t^CJun0Qy&${pVtj(jNhQH_~*SIs}f@Kngmo zc19Wu>}K$_f{&KnKPuk9BKT_8ZxFBLMSz)nCuGUYDfv`#0YdA+HZ7c6NBLCpk3yRG z8pC!CtYV+D5po{{X{b21kMg70fmZ9l`eAYYi67)C`y#oCExaS9MgIUUk&nPp?1Pxo z9vISDerqTX`~X#ZFy<`7;cXvh%V7Zj0DuZTm~#!2!a85de!~Vw`vd$a_G0EQCW~OO z?fWbsdTbxzN3#xN0{;NP+D*mI=drb8>S71_QS40aR+qs#NIdEGxc>mIEB+LF5nQVm z!;cL!9ojXQ{rMFh$%V;B!w(FG{{WCJmOtx=r|g2|`%eexnqeMBnQIXL0A>+a?2QeG zv`Yt2e$5E_AQXEcqGO}kr~zS*{`sV+vqPKl2ZglzsGH2QhD68lMh$nJa;4K1GjPFrP%0;xSKZ~D>P{m?YeRWHqByf z=}@ljMMy;MG&?=m!0ae2?zEc%k>?z3puv^(xi&?!+O8rv8~*^BLOtq$xunFaD&~MX zaV&mG98d=>s*K1$pbTkXC8z@alo~byf(`VWjw+Ct$z|g>rXm%%+6_TPTd9jJX%I7H zd@Uw4LS!bOpd2j#K=R{`qJSR@=cNK=!69Uzk&c+}KqYO46XoMH7D%jH7@!LgxfIA_ zGm%!r#+AUSV1y$TBOTKil)MB(oGBqviw;#f)Co|mC(;)J1A^?97{*>91JFQALI&$ED0RB~RGwOIRjhWX_ zaoFej)SVGIM^MJHXL>GLore?wI^~*xCXOZ~aX=cd#M?S{pbl@ud&0E|*v*{&nFxEF z;-!|3grHc(P%<&;Nuvo$#L-E#;MP((WijjXy;Y7W)kwVx1dMZ1I`NvCh7wY~(1FN2V*+!_}ixW@@2QS~OmW2-IV}dI~Xn z7UL>q$kYyNnxu3Fxiv&rQo)>JylS#hXFW=-=v5Z0IXgOM9E$Sii-kSe%_*q$A$ut! zf4kGts+K+-#~RDHv7@2;hw`sxjy5u8^y@|f%_>`{l0e%>Wfkp4y*7_l6Q@yJe0sgD z+U-fUu*2|>y3IH8bZJXK3gg(Ty-UTr^T`$MgHl@*NJ<|0*&-14OTE6J%* zA{v6-k|eEg`y-3x`d1Ug(~nc0wBl}A+4(T(im#7s|9DhTn7Hxn9+;v*z5zNy*r| zwV?qABht2lQ90`2k}TWA#j)fD`c{&hJDh59r0mAXk}RCSe+m{N25!7jY7~Ayaf*Li zit1||M0$&G_YjVZzd>0`7Y1iKke-E@JWG3F5D3LbJs(@80mFk(CL)KhRSYnals@g|{b2b5+ktS~dpVCh|%MvaP6#P>56ke+(i zWhmI*ICzoBssZDtO43G&TSB*%q;du;noP7oHLv{g!k)xem59SasZya9jg13V)^v9d z1=~gkBeiluf&NC~EYT6pNvUad(l=ww97)^)L#khEi>t3bUT5M*!Cof$})Kwsf9~ z=iGK)EwQ_@j^^6#CyB;Y!wecuk>Yzwl(t4UioPIe_L@ZZ5?#cxA|1zYIQFfn!pthv zuT?D%&2QQp;|qBE#8bs(BMTVG5#xZvJ!`J_(EALlD{yybm1)+DKxXP|qANK2cc7Ykk7+8M}wwa1(IS1jdo2}hK(_KnGp%Dnj{aYizqLaDZ2H`D5B zeVP)dPEO3zw$-%_FCSvRnk7HnatG*ZDN;=HYvt5r?uajST}xA9E-xmVKi+a{KV@x- zr-4x1r45(wK5k7l3mwslj)k8qwP@pWJB~3(pKxNK(YY^Pgw~grmW~W_x;OhL6yr^G zIMxb_cWAeF;tfHg=I&B`&2++zKBlh$MqLb!k*i4>klg`{V0#+q=8Qt*lU_yY z1}nqFRErL!RlbYYHR4ooWu}M!0BBpO2Y404;RbTaCd$)lcJ6-nO1Sbw=9MKa8B$$d z%;8DrzH`NT5S$sru(3?f)NWGkHb?hY>s_&wPg<25Y*d0c_GBo{X&ujA9||;`zmXcRJBg6jy9(-A# z>FaDRJoN*EC(LnN{{T~WJgT`p8Ej>1{{Rs+e-+APyq_8V72sE=Mu#btEV7+9c4pW_ zCVlPJzK)D}PhyNYQK;6UMdH0KWX-0oSB6WyHhuQa(XjsM#brDysCjsdGOo2pZJ>Nv z@otJyqPzJ}3~uJRWq`KFiB~65)f4!q;}3{@QEVc-k#>$a6{Ij6n;Fr|V-3zWB@;7m z`HOp3Z7LSr_TlX4`=rTXuzY|mbo;2)I$$*Rjl=Xki8P%hP;QkeqtHAp@n>J~mY&wu z*G;n+9Px~HtX=~8pE*+|ryJOwe~i8%)GcI&YnCCs`c+fFw8XM_C8@`oNw4R5rEi&! zC)T};J_`GuNIPmqJ8ESO5zAS@j22c(cBfQscwQI>FX_?9A1e)!q3m>In zaCGDEGHWQm7AKnTPRFgw1Y9*y< z#oB#`+3hu!Fb<0DPCpD*9>Fb7J{es&^*rlZ@s78ve7E;f$&S60{y0 zR#Ed2*0EMP6$)@JETGpyNQpQt2S+Z$TU?07!{&cCr!|DUPTn_tME2`>I%wD%UzR zbTHPlGXfbGoRWQNtaZXXyAa%14V@^t(x+m5DtnzOeepRM?LR!2JD`Zo(#F)KB!<&kl`eH44vD zkELfRCU(VBH1w-n;IOWFYq4-W1rSEg^&`WlQ(B4d86Q*FG_}0 zu2O9{py^n>O=F>*bq?Ou2BuW&h7{4VHuQ^psHzdr+oshOWfq5}G{BlO#Y;dxV9iJk zWycf&ucHr~PzOVfC?Jj0bf5~CKpYB!kz6mzMhym$GxZ399OAQOM9Y#)3{a%D0tnCVa=Hu-btDOfHd9%+cZ3Ew~xkh^)H2)PD;0y97ngzb)K0>U3J z+JfQ?;pi-aSoWF#bX{>k7ZEVD0Wx4v09=kJ0() zPzO8W1;%IthqxcTKUx4q;}rla{K-HNf2*JXg>jQqhK5TDsK}{fTb8u=MZ|z&v23P# zkApPvY-4N=)yYuO8y@@N4MhdAryakgdGj}c(@fi<&!Db$X%juJSPm;DQC*2FGsQ>~ zl7chqKoP>>&fb&(KawLk+%Z-I79k{^y{Wwk<|4X$rfaVuSvvu4^?^9srHSQxT zZNl;@?8VGr88-Fnn!9L;j^$Y!Bv*bNXvmTFr;UDHHeaEwk=Tz7)ng`iKU&5|WDKNC z?fTYc#i)+j084A5X`wRwe62eLz)UlY^r;w=wnlS-Ng@=A48wO?rcx0U zhTzbQ%VB^|dTj>8_A(6NsIeucWL)4;Vv{OLzF;Pc49YhKE1U{UM!mevA`A*cH*{N% zE26cBn<8J!-LuE_uxekcR1(mY1a*v(bQHZCqCl~k#!xEh*Nu7Gc(m=^UZ zvu;gwL7dV&d&gR|Hn7RKfN@)5F_v`uwKoEn{3`d2`Rzr(xetI5a2kt&cH80nFDL}ja5P`xy|iF) zR8J|)c#Fd@Sw7X^)3wFQV5XW-oPHGAD&)4dk{J%_k)76oW1Czl#zg=@d*SJ>?>|jT*i;#{;K@#8%#3_s#&6<--_iAnhNM$Je(Qq|w zXYtwzkJ&@r)13xgi}(KK0TgF-0B+7{z*4vI9xC zFyf?}l%$?mO#yN7-$={Arp0km;zw>)kX)Rcj@0ful_i!)=(t>q&XPhiMZ`8~SyGFE zT_=&40M&N`R=~)=c=1KRzV06b9MNzYFe)*&i-AXA^Ew)LAeEv#XQdY+#5WesgCOFn z!{!|g$y5OHMHUN^G4p#KDz0g9p`<~9kSMtBO&0P&#}p2C+4CX3GF* z2%fLu8*STiI5?oQ(BrpA?emVJ%T$EMwiNSBL^5r`s3@Z|g1D^^6=sj*q{fPlc4nZW z2AFi92=SJn3lOCOWyAq!B%RYEj@4z9rB)-JlmRpggy*dQNLv&D9&zbSfe!pODp(R8 zaZ*5f)si$0{i+HZGmWR)XwpPg9}Bs2Tf5E03ql}O0DZ~N8)nJnq7E&RBFHD*k= z4bPVKaXDV*=7^tZzkmV!L;6t6ndRgYiU8I78l-(YPzN#M9X(zqz^P<2kFvJ%u819~ zN=CVl_cgLca@c?!riP9=OHl@0$E`z&=GC2zZ9c{1VNop+<5sMqc3LlhuJuV{NTO$V z`>3Fj-%8-A3DsWd^VRbx)6n{3z`wDUt#hG^tyWm{`Q*V{cEd8SVsLOfabGo43yMdoCd9VM^qWo0aX>6*(%eHIa2lyLZl&Io2wTgaI|Vr>P^UtPJ|=&`MPaKPdj zvd!((dj7=z6&!zU>OVJqss0tL@HOlyXRuvNy=V3j&@W_Fwec<7asL1qc+l5_h|BRW zxz$UER342UQSmSK67e^Mtlv`5Cr=RBMx~x48z)hYNM88%uS)?&wI{1*)8O;i;ufcj zS?JQ(Mdh(90XbG5H`CbrcCWOfS*_1e<6~85O)P9wAmW{B>c={*&AT=HGvG}_#MjZ= zTEfyxA;N&Uuad>$B;JRWUoed)Wx4f#fWKpVJADkvt=f4&xrgfZ+CyEFx0Ie z1%pPoa=yE3t}b*Qr`KS!-)!v960kGcUkiByU~qCViuNdC`Ie{Cs*1F3nbReRoP2<{ z6{T5oqs*0FOPz;<{ue)qrT{N+Fe}8ZhH^f46PQglqR*l{1N$gwl329boTqBa+r=CZ~_U%a$FhBn;nJTKwd_5DUPi=2_hD~7FWB5O}g8SEC{3a;$! z#_J}P@zDi&vYe#%J{up1lrG|I>Awa)v`{pfY>EEx#bnf%Q|5lXId2nUKYz79mTBtFsOB?IL}Y89vu3L{fi=_-ZIK3ivs*&H zE3-X#W;GOaK6ci0>#YY+Xf9)D++-E>uX2pjK9-GlQp9U2x&z;(cV6dfRwj%DUvXM; z+~(=>Pe7D+o?&JqNBJS>(895Qq*j6eBDX-80? zX_{_DJh{OGiqx)RIHlzJQZJL3t9fp>1Ft{IrO7fmWWE+=$8Ppl9$?Cm(2C}iOZPwJ z;yo|JKNq}Bp=fPkZL7$UIKWa#^7zL9@G7@HetBeJvBc}1G4WocuWvS`aWqG$4tAfz zfmV^-PX|gW%^0?tG_2d_a>V-eu8MFyn7?PCtsTFVE30Cqc1nIh?No>=%Lm#I%hXj$ zc1Jrb+1S53GhTg4wmJE&c5LbX1lKj&h~cq;rjkMes)A4HUQJ9zjvASgqWMwkn(yp| z<9#o~_PT)bWO4@kc)t4jbk2X3VH{(*$5S5{vE$w#@J74g!cELFs;R-?j2}wwg?A$9 zV<9cfTRA5aZDM~K>Wr>>4sKf;(Z%wE>ss35P;%7^<>1w*1&KrPgH>UThTrW|1wcsY z#d21}N-IO2wCT}Y%k;{*9r(#&ef*_eN9^Xz!l{gnD$$%^6@iGju#W1mX)AzpGA^Rb#2;f%q)}m37D<;v3^x*U~ zNuk#Ib6q*(1L;n6EEB29b(&9sn*KCYVhR5MVz?)WaXh-1-d?9!fBP}(aA?C*jgolS zXUafxT#?5%>_f*pUgw?7;ETI^lN+Rn!{zev{{Ysjc!@KHS+h@4{-5xs{{T~T5!*M- zUNw4^nUu3SlK4)t-}YNp@@)2H<5>_ z{?E78OY;Q$E2&{CvAo(z8E4=f+~nY{M@;dWQNm&)xansyo`pSA;8wR|Z1Y27EU~Zg zCP-TEglk80=qTn=wuhZu*v+L}7^gy{9C9&T)TefREef2-b}}rdT({DyrY1C3yl~W%qHEardiPPcGDT@DrGL1F zyeVhYsqT26U#7P#!QmZVX8APvx&G{CxFw%csPtz%vpQGUy9a_b=uaAb%YWM=TK@oO zC8^CE(kbj3uZ6YPt+F(U!S{^PS?npD(aj;%Smou@b(>|~J)E(i9a=nMy{s;Bs-A=~ zkedxY@ENfv9V^sXf{EzPy*^u=6lQsobT;9~L5k;|dkQCkUlmQJ&0Rl1*7d+(hfkVL z2g?+BtiQM8Q1hdk(WR#4DWTPT5q_fX)@yQj*&xMpOBL-kI$?=eog{i!fj%r=>2qCdd6$WNS*&=$h@MxT zLJH*9n=DgDuL`vAi(Smv{uy|XTCLdA?mU7%{yNss%=#T`HjS@s2$REluAlSG08e6h zuC&e_zRK4|bqMLjEv z`$*$i+*asv{50`x;^eI1NAO{X^{rkxoWJV@2ChPW33#66zFRw|Zoz|BpH~?pAN7iZ zc1R!Khl?%2&X;lT^4B-l8LQ5tBOdd>8qJEG_Or&rsB}24%6PdlqngorGX~`@EFa6b zO{5%juTq{PnDrrFn`UTP;zSHb6*Xpbr*iZ-9R+f88>G=MmW|X>xn87rx6dt#ak<6q zY2V@yq%G07_OC{oze3eWJ)|&27JlHbN)bi#`3Uf-Tm=HUc@YiYpo*jl$;6i?5)ei@ z3g>1LpD3^Z8#o1h>qLxHTPLW8kTa@$g-WU>j(x+A|vj{>xIifK-okYvNVyM94 zxoayO^p=_#m)9#9!&i+}*wQY{hR(?9GXCzoI{1rq6-x2wUS9~yI`*$U;u}756N_%(9mQ)3w>Tv1LpGor7_D7(6E0ziGl98risYVSNf7EK)KTIlw&2HH z>ocS^E9_}$zACo3b&a4ZPXOkWvltOl(N{W2?B!!CQ(z}}_Kcc9XlX|h4%7kEK!!yeP(dBcdo#rVWn4*v z=}=NBt-w-wpwcC4w)4jo0Ax(KsWwG!QL6@MNxL+FU~W&f00qW4pa`6EjCHEz9fT3! zb5bJByJL?2lmR<7O#lFMKnmDZ=|B(%=iY!OJg+@yE+t%)dUmm8fWgN~Ks_VD6ajWH z8F4@p#cT}#OZGuP62ob<@F^f0ak)+as#}^5wt%(?q%&6B`ALcZ!`Jx+fH;eV{o(r1 z19tSN0gT&06aiXKo`4oO&MKlbEQ6q>BGIkk`zCvV#dAn!s_0hEktK6aV&k#b={l^| zGN=cRKc#u|H-YGyPNvr}08dd^NYW;fL%WgMvM7$_Laj&;!2`(0(vT0hguuDSBANp| z@1J%JP!5XXLNnNKD@z#{V7MDTbI7FP1?i^=GPxL_389~FY5=gTAMg|a!TM7~vQ5Q6 z#~7v|KG-w!aA*O{Lv)}3OY-BUC;}Eapa$Tn;(#8H-hdh<`M97B3rm6+1Nl${w=-mQ zrD5F8U?frv77LZxHsExk!EzUsqi&Q~E=>z>T$*AX_Mnah0A9D9N(|5h4(bkb-hdot z45PT9Rv4+3JWv)Z%O~0K%>N4HWlzvt&|Y zHmtG`3X6=|u}__OrZb_n+(rckW{exrY1|DRJ5x5(jGAI+q3JrbR~GA&%~>Ypz2Xos zXo}Rtq2e8QTUno&3hjy}N6xTkQDHB94Yn_b!kQ;I~T zVy2yU9i8g=gms{!)3mGWYp6yx_NU5MGA6o?-PGU=(aIdi!n(FqC#EU#i3NVZ|ihw%r4cw=cz@F3^lj=Hx+fKU= zP$&Y(P#F}KVt4AV?RHb4YGW=2ED@%(BB3w3Vi9Ei;PX5um{oF5qoM2+LS{abt>ak3@+zR5gOixlfEq*4G%M3j$7r4o_R#&%T zIr(bhG zfr>e97qNzQXsw@VWf-lMGbd8Tn008R0|jaOF)5d0D<}k1TE>D+9^ulJ>`_$3=qZ6l zRgmyO#XA9|rof^y%mE^uI9!=K6JP3a+DE}$0bPE~=W{acd{1p9#=|1y;~dsfr(&Z! zofhG77pELoGe*0RrMn?L`ck=8EErXAPZ%AmR6|C6_54w(Ur$=vF}alHj&9?`aKShm zlvTLcAypUcHT!u${oo|}R&Ew8NZPfXE{6mVJu52|q+E<)K8C3jVR)o@nskHxn(u+b zS!jM{e%27|@n|1k{Ryh2vEi*5;8v;g5${|K^k7;Nc`Zau_AIr~D@~UZTS1e2h&{zi z+|?e<;a2%#_5z$sM{s8U07c{4ti_yOvv4Dj*62qYsa*n24|*6GnctpjKv5SwPz0GN z)bWZ7C5Gbznhc4eVABj$kaSjj#yF@akgKUK&P7rVTNtulzDmLk4lqLl<^ds9@RiepOjE!8@qO(2o_C^j?@x~nn2>3Efi-38yzapq%m^+d8TB` zFh|8SV9iZFFeDsSh?A?fiGI(;5jojVj5pG@Ihosd9^6lR2=rn7DKi-!=c!qL#Hf!* z{{W?RQdcmOK1TSv50|Sy`^Wj3nuzSA&o{OoF&_2CdeM3sQ1QhC8W8-0=|L>p((ZQ9 zBTcUhU{D55tl*;xga;{Y7#D16nMJu=BARHzrih|*G?*3F2;A~2%Er!}q?a3yL5kWm zv^;!7+E+SV8q6%w@!VINjgPZ|^SGLtGwgra^TRfJZ;Jdr*NUP?fXNy9=8Upxq#60V zu`!k+rrTkouZnZ6|y-ck&QkWLO4NP3cMD^PbHT@ zpYV$){twct>oI0O*e|f3@QbJS2jx)e(|rg1hRI(@`vtpf7~+&%9*F5!2ZW8$@sI5# z@Ow&!RvNyM`-!egHN*`%2PjxFTQ+Z<-QqrIn{XyCLH0+{ycU8V|QM$`HO3-{Wnit8&JJydfgG8*UZgf%jOJ%MG6yq5c=Fb$Q#VoSN%l`niFT;~I zlJ_egnCN*p$;Dg~o6yq@Sz$GO4~j4J39P02jDQ~F99H*q&$6YM)5J$cd&j^_0d}b~ zW6ehZ4ZSPHl=-B2c#Ll>H$M3ArjczXk#D*LEL3tuHtdf+iu%c}{t&d0w}q^Ip>;CV zA~E3WU~m?xR7WTFUZdB+cC5oI*0OaHI^mVwl(ioSLaVnQE=^|r;|xv-Qb)yqwWq=O zo@dtV5GagdUA4XzvxBn?zP~Kg5%Ptf?&DQF;=Z1b?=?R0D?uXZn+j{CM3)9hF~qekH42aFoVTk3n%oNZ{At?xl@YoRq`B@0UP zEs1w@qnOj2a9j$9A*lkO&lHiB*t4J`nxrziFpTuA5-GA_Icm`vyWAat>0OaJZ*nr( zs|&HN+pwdW(kK%y*x5Al1weNmiUvupI?}PlR;r2WJ_zte9wnX~_Y+`jJ*&%U)!6ww zUOhq$OLl#I@TcL$j)P{qU|+Pb1j&!mp-_EKjpp{Ue5bc*z8qr;w5$pMFu1Nc#xb6U zzlXy~)4M#=#orF}EqdcohVM>I@&XHEt#wqU;|rt0b-jgFnfdAQJHf-i{xQDOmn9VD zGJQKyhut5cWjK#*NyVJueUYiJdNIDItrXy{C!4yBw*;;wy3eRwCB3qjm5vouX1FTU ziW8EKhr4_s_+u8Etip9je$ym?5}3~4TH#;5K35%zN!g!acz5CacL^I?Ft@kB3cWA~ zN+pMkH$HRO`*~<})_xKG$xX&jwE6?uvrwY6NZnywIG-hc)c*hoE$=l4)-?4NvoZbR zuSPz#>PmuCu8zC~S3-BW@;;+uHkUC=gg;zyUp%YwyB}#wmVXfYX#f@JQJSc9&Yvd5 z@SAXJxke*OA^0CGo&a!WT021l1pQqs~w+%GzoQ0TH4OqJ?3Ho zY*&GeuBpiIF}0;bMb!IOz#juNeJ;k`rH{w2xrYJ{W1% zw(*NwWdJQ>H%&cF=u_v9x@*Vagv$Qw7_8+gRxT7=8!GBP0}68-UoJVWidCs2k>TCU zslFP?bgE1uyCI9C0@emnA7}88JLRAN3{+Ic(mN^O^ds@Mg5Dm|0wiM+pD(8vu4On^ zE-B&J@K?vb4GkN_GRUzQ)$(~9eJjhZy^Fc^*bHP;`EGeNoTfq}#~H?PUX~iNsE@L6 zw0WXw%cC%eGWQkHIV%nqWe>6q#CIHGzJC!`%=94=QKT`CBZ~5!b52t#^3b6pihkXY z(Ls~S8)>?s%8;?4Zk42Dj4Driv^gMQl6kYf$eTvRkGm!P=a zfn2j;a^g+20xODBA1f_EtNBA!LNolgYJHYiD0Ojdpdx&cOc+Ke+R!WAsQxC@O{cfyY{j1VwY@u5(b3Phu=XBXL{DP0k5Ypq_+txV0ZMY!&KJ zt4=$TsYz)TEH|(ohc)WcsY|0f)hOF?W0xN(t@lUlHRs9dNoK|X z=uc{%YYvqq%XW4C3-Cq9h&5P}BrFaw#{#Lg)z1eRjaYf|#QN*QAG3a+7LRKq%w?Pa zSr>plmFBvVi`4n&jFn|$(^ughkh)q!_F%v$8DUi#>gP>3)Uoql#jk+YkwtrP8hL(E zYyd00Rk{@#(Wg-+e7Ae28w;soAb^d5TT-dbq4u=w;G|WWZ?h<=uN0NfMm0XJtco_e z9p{O)NT9Yxd1N?LUe)ngh9yNbjvPiOk=?ydu09g}%bIP5jE}2Hy83Vq&~cAy_$Vs4 znV&aX9TmGhyTd=TCx>*)Ovz|2B{(O2eLXxJlvU0}daFckr@#*l!ZI|wf59uzh8eBQ zKB$*L@q82T*gR=g{{V_osfM|W!uc7VF8!Q5FXDX`OEafMZ6`Y=L4vjB{YqTNZ76#+ zsy+?);rlx3-wpMd6>ZG6T$G)9SF0R5j8AV3m?}=vXO-#NtlAvH3$;F3QI*})bZ<7X`O$BJ_>5yAn_fpo8%%%aN9xj^*=#d)TixZq2brX$$O{U zz6kx7d==r{4l7&z2HRD*9JJRl0$AW@9l);+ragmtX-AaxvKumpA9@oq{(95 z2(z`3l!dvq9#f(0at}X^Z$~9m_d6(Nm0Hy^!0bO^s6092=p>rzE3FU_CeAnG`=owV z=uan&SUyyBRnBK?N$7n$55u1gq)@{SgJb2!p+PXO_*c&^9j(oyK1#N0Db&#tr^9Uk zANc}Vf8Uk=01EUW!}DEEKd#TQI{ZJ-SN>qufA{5U(1uFZ?0;XMVL#ztczw8S3doQ8 z;y=Q@8d*&`dxzJg`WEc`4WU_o$F`4Y3I70Q75@MV=fvd@sj;LnvgnTy{iJ_n-804d z9p1O$%Ve|H5E&ZT1`>a7V_tM}=Sce8zZ>;tnm;(aQ{h|x01n@}+(Rw3#7slP>Qt!} zjHc*2AERM0u&%Uvn;ss#7nY;5jtKXz=u}B)ddYQ0jm@sq&MK3>=E`?gF)q)RO(bg* z9^h}LYPK^Cnwfd^tn6%#k4?7;5T+4hf(bA&iU4R`v;is%ngEb|v(R5hIYkwh_skRSd4!4(3tkbfKW;C$N+=Fh6uwh=PW>sheIS=qlLR zpQr1MEb0lxVPi9_iM-?uo`SUl4^h-Pkv^iTTOygx%`WGzygtl^euMbhJ=$cs^fgHB zM}g}_MZxK|tCuS(=T=0A_$8We1u7YQD}z04$fRIL{OT6HFScf~?0W$4YlB zgaY*Aw~ql5=e4OM{7y<-fS6(VOt;hV;rXx&yx+rukGA70gQ z+?ykc@t&eq+CjRzVvJ?E^S_L>yu80PZjKqBH+(?Uyvw&Iny0bO&o#Q#LvH64B%FS) zeq*0@>SAuhnqQ5kvKaFZ=USO_7eC=+T<+oM4q@Zs1ggE|T7~+7rwFkr& zxQr_vwUMdM>l&BZZi|Wlj>_yzy9wz*kvxsFrCLC4cwrjmRzP}+gHrE^H1@JYW<7C0 z7JeIP$5`<_wZA(V#z(bt&6-6YY}ouS(WKL(jM^Y223v2nX-`m+K1TSn;PlWuUvmUQ z5wRslwof0WbaI)TOWJuy80k@A*t2bS_E0&%6<&2ZwH169rWEtzjFEN6Xg`n66|{afUU!iICg9Q|xxwFU@B3 z8B4=Dfw|Sd-I1Ebf$18G?6UOYw20B;ek7b3Wdw9IF&vgabK0|I#I+M;aB5jY*@zh9 z6o85uNI1nNBv?>f=A>dWlo_f+L%j3PJq2STkD-5KpAvnK;uWlJen}86KM`E3a7XDi zl!##jcU%hVM(bis?BrACxeX(p@lLGP7P3A%Ep$RRIVS9U^WwjYOQl{&W|L?Jx@}|6 zoXR>U?GvWSI-!(fr&`KVG^Xx*zk@t&ZLeELEHZ`+2SZ%fx#^>~`$D<`D6kPB+}&#I z1(%6Y4^EXy0mk@`#PR5{Io=cj&34m)!&0f>-w=Oj3ww8Hqo3!_-Ya(-96r+Q^V?sH z7xy}}F9gQ`kEL-`m5$gSVEjArJL_5`CSGJJGJgu;a=F^HJ7t@G+>BO=Bsm4`>O*ea zjshfK|8y@Noe*sSSHcIDz3EdE;jm%FKH zz0FbYo(a)>*wa3OoJ&V|+A^(~t1)Mm__D#Hm5AwEp&Sj?L2cf&Gc-aD(Nbs&afiXI zi6@dpDmbl5D#>sZ&<2i}L%6F0tG7V0E(JiWRn%1M%~BPPZI=vbvTQ6+^GP#8afp_r z1T!lpC<2gAE%~4bE{5TOKn=H6jlHv02CNIv&;*iX4mwZ;a`-r)2WO!a0EBPkP(wxA zd4eDm7C|Z9B>*vp6cNfoPCC#Wq$G|-1R_X|(dkVTh$kVrt3=3r5bwaIW@y7_vWff3+fB+lTb5S%jJNE!0Q*7 z&N)3#kbXA6V%1xZ;y=!nMWNkEv&yZX=095VCAsTG=xD-usu_MfkSG&0?6{ytoH!x!0ci}P5SA;=X`IqIzlG`mf=pplmeCgC@AtbsSSC~_df`4qle~o`JPwMkCd5ty=s>Gw0P(HMK35nzBnb%t7obBX^12N+Qx2J`i zedciDF>tE6QNVbU!V7abncxycynsePuYQGCHOkMMuO3HqdG3?pTRkgXxx8o)mM3Wg zr_#L%VIfmf%jdgtJ1rx_vTC=G%uuWgjw>3~A%~ADG^2&~(o$!s+g(GaM5&N42Nm-3 zgsQurVMh@hIxa=5MtR7tSB8>32xhd|Al9K@;$D5~eybyOn$r?ZYBC7m)hshaPd2Bq zi+LP3#et6_*1ZTyRL`2k#x>pSd2hw9g|^--(u|}8xPeJmBmV%cUX==*&dlJKxf>j3 z!T$h-kwtAS{H(LEaHzQHitVdaLF{r;PR-uz^<7uP;Svpq11>oOn!*@{@2S}uP3q3; zM!NFs&^J&y2D}>3ypN>AVih!8YvJu&?vt_7@u&^*;O3XHV_3+tjqbPhXuo!wy{o2{ z8t+y^#ouF<)wFTq+hn|hAiYVeIb$QbF&j%Naacv{)4~3r!VDPoPM#>Pk z?gsV)pQU2oviOgeg=yBEk?VTyfYNw%mr#jiLI`NsX0Y~=sO=NesZOORT=>uSi2a-N zt!qKK*1S7u2 z#D0dpZy8@x!o}Bg(`VFQ4E`3&CX)uSbr$QXmwKi?;I51$(?33~DOQg(w>{gzdK{^N zEPE9|#%p{{RUMAZyAIpc9+3=79kw=(1$^9_lKJc%851N&a z%JVPdw0sf!SZUv3@%l~LSPNGb7w=g6EQPqn_vmb4J8w~9|h_41tRi1~-VsWmEcW2Q* z4?Y>qEzjHLU7Y7Q#z&=h(w8QO&sOHEsqNZrkh4&O81$!7PDf@QJcaph?cp08)Dg!$ zs~A(MC3_y6szIwBWAQJ+u=t+Q(NMfi&mO+D>r#`Hd1QFCUA8`5_?!DZE&i1^*pxNg ze=?4DU=Ee%*T8b>cHr{|CvK?uLshc7&~N0mxQU^7vM@#~jVVz++YM9Otx2slUoZt` zuS%Ur^*eM(x^FhFM>Wv#6yv#@#3+^C`Qypi*14WOn%R@>;`d{%wHYLke(Ubl$1$e% zXDv$6J?r*=GanaP%5kx4&#gt`S@OB1Zg+LD_pgU_6uA+6!#J+Lt9u9^H-(yWJA}c9 z@UI)*Q$IwHE2Mn&@m~J``wz$V@*oPY7(cCjrUou_u6}=w(!Gyb@U83@nrt%0!0uY{ z@lkP`N27vqQ8puZ%LU^#!qMqNmQ0dp)fAjmr*h{`F;^=`GUN^2D`y=HBT^_)HrGAC z_oU-=H&!v&<9;CB32mZMgzav>{c83y`YE%67c|y~oon9=G=Cpx{{UdV)1jEU^D$4D zdJt&F;nd!TjY7QXNlBj){B!-EwI77~qOw1krpiy3agGPIaYjDHXnu>9=F+N^CiFbj zHCwo?L~=x11BS(Ub@Nz2`ZbJq(Ze z(Qk7q=5@FyV5(S%vrnx=&Ysp~8?nHsb@eG?lOwmeRcv%M>fz^erW5BbMIbfq(?Ehx zn~K#VW>uOkz}ASy#%%eF*1}{6rHy3O$uC9%t}Bjl3l`E`ui;)*SlNd;M&?4N0M|UK z`!#YMOLaX<;E%&QpBn0sotvXp8OW^Z;#GYQjLq@>%iTxbUIh4T`X!`G3VfgdcGsy( z1gEP%I>h3hCFpx^g>62^0!1O+gOTmhyQNK2Ug_6@S8JH;(g?~91$okxu6>kf(s2{) z$~#hXkiM@0;@wAKOP#Y-hk`Km`(YxoFMmqzqTP>_&8bDZMDTx&UI<-RMc&cjqdX8f z#cuG*rl-us&K~8<{{XX{gm7q%ISi*8Pio;*c`9smVA_&L&^9ry&sxr`wLd_?Q;#mD zSBaxlwQ>hR(-pNg+C03<3KC8y=1;~OWlxFvx|6pv5;(0=I6wAB6igL<`u11l=mALQ|!ydV^U9;{XYpx z9?2Yc#8DJFZ0^H9E-TT(r!(Sn=*39peiS@W!!&&fY>Mr~#!4q9T;ZZUJ#C|rc^R(} z)RbBFal_M`bwak8X5*O9{sNk)r~#n8gdtW~@retJHc{9>b~ixLn3^ha)`rMD|%(mjrQB%6%0Y zms*TudyFctUiDTLYPLtL{1x~Kd+`rbidPK=lN$jn*yNuq5ygD+o8~_1RA8y> zeDm?n86wdyN@TN22OfsLf)3F?GR7wfIj-l#pAu{>^vQuA>m+K*g;&mf)>~iN!9d_F z>?PGN76`#<9flUVF?Cys>EgfH|6K}I^3 zUNx-wtai!~7%k9r0;x*oUdkjlaf2Da8TP7!)aAs%I6EF=<4qS#)?&D{)S^kmfQ&F7 zg?lQBsT~i9t0}@>N6Eh%zh%EaN|#d6R5GSp%TdU$mc>(aBz+bKGiuH!%r<{%(C$r? z;QXf;^u>BOXGFkRk~ryJt{YT*PA3)n?o;jyJ2)kqJ4P$ErAHJzH7a%G zv8g4DF~`Dzk3n3KsM|f7m|Ad-i14o*>c>j)%xNT_x^fq9)QbCj6cq6HvGF*bRXZM~ zrs>ho1ZcjlDYk{=&Y(EhKz%?9^IAwl?Bj$_B3SnsP^W+ z*-&GbM@1bedvisV!5ID{fLYkQg?Fm*N40ig+N|t(n5rLFN6G&H6Fvvo{9)E_Z$8eJ zY!?c|5197O2Q|%^Wy&$SIdB<_=+wNK@ZXR82dVrl@f_BdQ9Np{MsdOa0P9!BMaqP= z*!@cZj{3xG-lmS5BH!4oo}#^VQB7!jbZ2%^)HJ=rII2gd6Q8=U^2y?;)fp4QR#Aac zXty^s;rp?XRFSGIPM94kL_idG6adK+&{YE_w~r!|0?33XYMTM8VJAUJfz?=YfDcMU zlyzmnGy#G4A3drIM0a>};)7V3b0JYs#|A(5CtlSCWo@Mzfs&!BPQaNb6(TL$qOxXy5x`SG%I(sD735r+4T3&{G!W4!Cphgv zW!O3j0F#r_fD>tAC<9whi;k2P65}Q@L1l|MOmCV1wRb0&nDw9v(2P(8wNbY?pbV{2 zMG>CWF%mfY_XL`unT&F30ER>-CV(sB2B`rzZYhlsF~f?64C;I>a23UK$k865qq|A7 z9V-d!XpZ|xwhtQfToWx8?%``}6|Bw5$nr4emBn%JOj1Azsc4f@NzamYp49+UitXhE zPg%l^F@ariP47PjtEOR#Bf61`-{6R1j_2_<>@J%P-C-AgOeG!dup= zTOyg9!{#;DhuM(N(H|L|+GL=58l-lk=Y4aF$v(A=j@T9BJ5*L?qB4BG)BzJnIP|1| zk9N^kiDgweIL$j)2T2&(F-qZKs=OLua;&IOS*zG`z zMCSw!1vE5PmR<_+R8d{w%JI__0i7kdR-g;EtGQ?b`YDI$O%lYC?DLLkh^S;I+JXs{ z$U%w#JkOkBfF@pg9w-5WdKkDHNx;H0qS=Q&;y50TAP5^V7Si|Tnh6z1A|4t ztLL!#QE(US_yiiD9J3>e0J4N^6af$4>Z#nG*hxs)y`P0O@o8K2X#%rY zM6PD{GvmBA^ZIj0M)Wsgc*pms1EcVi;@0#RI20O}ykDxM+f`0Lpbn?tOagBfTS^Wv z!nmew9gn*$?$#|mTRFmt>Y#~{@@L0KOP?14FnBfC%u(a_XUg6&T<>wC5_wFA6w|Ss zh-0{D9AmX*BBFhT`#S1FQSic^n?MzcXG%Rk#a7F4po1J6W8pID))#vhUBp!=fEs8v6#Es_2=N(N;*prKT?!BkR%HQ2X!)u@Q*x#7C}4Xx?A2=|9y-%yQ-xAG zdSq7=w=Ey0x(vnbqWQ9MT?=DXv8lTZjA0>Qq_^17yZ7eZMnHwX6(!2DEJn1{L z9bPlMCL zdR#C8lBzOmvXbTrBWmhM?Q%%~3ht*KW1X&3%6=pKO1IH%XKyuE3P2gJPBhOxtxITp z3-M#(eEufX?pw^nV4MumC(4dk#kP2~x36_~yq(qMR-Mk+Qa*+Kn?4`U;%#~xf=Gre z9+l=r>UvzZKSR7X;rOgABT?Bv{VS>?I%reN0<~L+HmuJEg}-{k-n{u9l{K+tNDWkumRPsO`b?;!r||UKCy(kYBXoTS z;9IOfpFvv0M`?MeD%l#^CNezpT$wJF^I9V{x#k+w8)Z8^YD%#|Vq!YhM2waatPUtD z!;FV&_Mo+)S^ofd^aIr0(WBdnq{*{7Xx|DkQb@DM=0(!AD8sq3+S$cQOhjm$Q zd&k+*fFguvy#PrzHvK39iBe4fA;852m*D`Q?TP?!d>nSFxSArAj?`R{@i50UTnCpJ z;){S5+Bl{HpgGB)2LXXeAR(1@jwyi$lv%n^WHR8DU!^oOLb-faggwjlza~3SbG^+- z4&W<9WXYq8EzNCXII~{L-vXR>Ipj+y#w&B3=ysnDBHgM*_5T10!dsX~`Z7qi=^6h3 zXZly6K;o9?$)6n}L#Q{dO%)ExJDw$MpS@x4UQEwwnW+?6BZ^3~N+iP736@}etw4My4v;+~`?c4QM|1ORL)o6| z;YZ)5r?q;RXzGDLsx{MF^yN$HQSI z=#EJNJdVGGc=-0C8zVd{Tbbz|B0&z1a^nCh6Z%)pVWs=TeGV>)_GgM|dgZRUezwYu zm1_HDoM7ac@r^!vSoWU|+9Fx1g~fbkE^WyA>;)LgCnNDwR{p@XigZ2e(#v7bp~a4) zeUBo4juIf-<^A3(-gr2sdGxRnniKvPTRbJUk;koR^;jcMsm=)Nz8`!*ZxP-kQj;D} z8LkXXC`LDl=gySnBiLgY312TZeH8?f)X4Fhta=UE&fk#NW(8iPPJCoji?PA{7D*8% zQb|z#>($KQqIq}-wDmo1cu|gUE9PT2dmmR;KJJH@{8RDV-U+u`i1|^;74Km%>asiu zb4loN`d{rOrE2(6ZxS6dFQX`}=m_%wNoajE_q4F>&@wLU3;NEYCn|HCTW|9=vg1JnFt}PqO;7X9I`v-MZW9 zbCA5?^IokVeE6(xAKA&+`2$nd8^c~M`%5SQzH&R@*Q3gGLDkyRQz%El@C8Qq?t;jbLNqZ~y!3aka`^;l}U zYG8QVS8YDWD0-Y%qk^QQ&m%F!IOvZI@$bglPYCJqq_YQ<9Br>^bP@C4R-IMMe3Rq< z03RO}ODmToNuS2Br%LSltiKBAE3?a`wP7g<*bE5GG7nZLE-lDlM&eFN*W)gCbDm=FL!17LJiHGiABN*WOeJk6QMNs(3 z(w#YS#Q1~um;ImaJTIuWr(*#&GBGX{ctc!Op>yl9++(DbIc$9G6i;a}jkqgbT6R9N zP)}I0+T8B>NyaOp6KIWRpn~GvhZ3pv;=L7CFDp)6{u#4KQDX=ZM;H{TQ*>f{)&;8dH2~;97r#3wqF9t;6@;YZ z&%UQh6ymH|X9g2lrDk_ZNz-SX_^4v_O?%Y8hJH<%`?Z@s8vU?T9~0R>q(oPpPxyz| zWHR?r^ZuJ;_pAmuucn->Nc)M&LY>+4Pr&bl7Vji_#q>M&7ww?PhcynO$QRO6bCcNjBS3Z7Ol;hv;FUR{hcuLL^ zpJN-n+Pm<%U1+^ZhG#dUMisZiPxx1w?Y+&T53@+v=Dz;`1BC8sr+Bw>=uZMn(n}LL z8OCu-9}ZPxiw?Oe-1SJG_aB9Pd-%`MDa-dq%-^(S;k5A;w8x-~57xf#DTV2uDJp*2 zvmM}niV?%7MQw8N%m@2ue($rhItac)vHxk)NeiOs)<( z8~Z6zS4XhvcUB%R)8n0Xx{x#VubHVDm`T`}$J%aPZ7vHqnPWv?rC_ScZ&E8^)G1jW zC-L)1x4!U&?X;@0Ng(++&MVcYhgG6*(!{z4fcOjce7V>3_Iuk!hS`9_GT_(3=6NSH zv^(XQ%acQ*)jwuSD|-$0v56;9(Rs~yzu3(j)N>whiJZol`!U~mlg%29qG@oTKRG*i zuBDcHG;dEgsX6(l;hjae` zVK0^3FkDuv29eUSwf;7E=f(d3f_I(|w@aCAzRhy{m}BYqSEEXoA@n(BWa~ajZh0N8 z`or=`^0!WF+M_K`xTLO)c$3YGa@V2D%u8+zMu3IsNh=a(&0x|RZP=>_H0D8!RNO2E z(5+z5g$uWe^XjXd&00kIev~yE#a26G?tLrfGgWE~;l);T(`fx6{hB-xp=rJX(;mbr znnYP$i21i3*{>fXsOm`k!!*Q3q4chsro>@PDC4z$i0XSZXM@5xR+(UqTu6IXZ!P82!nSE$X=ARkzWZ5LevQvcN0rSNBZ9@kXeY(P6QJ#d=s;9Qq$Go8x&I@t4Klgp%L*w(C}fOT{Z5ovYM_ z6QQnzrI$|lbZ}o3+d6*S{o`J?4`bsqy=t8I!s}h~yn(juJgs(O zwmi(fKfJN)Y}+B?ycDk^^a(yncR1a1#v?{T(ze`Jvr7o79~C^pz3h%|AB^@bAy=0e zuE&AzI_fCFH)qL#{5XpVwFNjieq4R0y|fJ6Zf$=aXi?1sUzUO zi5?iXi(S+b%`p^moB`=v__*>O_7b|ILQK>6W$=pU#EkR2du^8(V_bN3%+D_onK>qw z$Jsvzd^2g`izxK#bsGm!nFlK5sH_53CQ2em?%sw(&FS{u{CKpusahob&1{sX|cDj239AE3|yQ zf2GZ)%YIY#GE;4cE)>$jt1Eh2H83W!zqhs@&h7%Gh;>#u}9 z0#6N}wRs^p0dgz5*fxBvOlp(&PoOmI2H#SKIb3|$=M{{rIxfiU!(ml@S-+&{acR$# z7|n3juIg@mE)xc*yPIs^CM(WcpFt~rdWru^a}wE(H;J-x>RMH;0n^6A8oPAR~GQPBPu@~SWUvp zwbk_ru7?}V6CB<*vyDkJD9620z)!K0&6*86#@1S-{{Ux(P4DedQwJ(~v!WS?w2hfI zx#3mTpbj{YR8hqHCuVie1?{O^=e0i%3tb{xc`#V1%U32js<658^(g&TN9MQ2{{Re4 z;r{>}->XCA#?lsEI@c~8@<981lMtV}%+CS%ds^{L`r6ppHQRyr6l7J@uH=tT5t%A- zv^70a&*Arrw9A2S7J>7qA9&ZE{qCoYTQ{jK6iuPxvY4~OP12y`me&*+u&Tb>gC1Kv$5*s z^*K?}$#L=;^At5b$=A^NQ}%<^r_;Ps49IY+U!~;~RPe`x3O>@Mk?8&e_@N%5p<2rk z4pjgn-xcIzGQZw8+){s4sdjo7hx}YMf&Q;y9`*CMj32Sw>Z6TvHm?3lhD|xEo_sS&PB$U4_}`^nC>h7&MDj_bYb?@K;dXVJ zcBco4tWMFCab8VYPX!}tHP$g$#ne0<4W-m`L@|o&Sg&tSmPe6{!E!sE1^ZNd6|(W~ zg|2Vy;#Jh_(8;(AWkBRt%i`!?p`Kliczi-h>VAJ|j&#W+wZ;N5#d1M%AFyDnbJ|9- zks{W#k9{(Rqi&ls$K_o{shr%lp^r*T>4|dEEx6*O)VR=PdQeG1bl7+l0S@le6j-x6 zNX;e%*JayMfU&0h?kO-k4Lxw5g(6D2s`Ee?bHS0tL83F{;LvFjy!gdK8r;sAxZu`I zvZ;JhTOA0DxIJk|8ClNK4@!bdP|cBp-l;nd;<~SDkrAr^aX=EfdQbu*7|#>|GkoKU z0FP?{L2)ZcNPe79Su9DBp40&-j}!qa4hAR!#7JlYR8N>t2F0Ehf}&WMWC{xw-Ee3E zX*0(Z0FbFIKo<;qVrl^7_327vW`H?6iT5=CMtH?50V&$i)Rhv9=qiyUU~Lq}hHeCk znFBg+3&dJcisq4`J(I(h%8)OvE1qV@Z=|xSsLm^nKxk?*uajA%Zet{cbDoqm0!=PX z>|(RAHYc{c$O>rzYBW&!9qLdLJ*%=7tr19}BZW9UshOC1t>J(Yy(ly>{{Xe$kUCV@ zRx)PP-EP&lVv7ZimK{sXwqH^|r4r~V5t$1yuKYgC&7Y$_EnMkyA7e=!sQJ%XCGVvA z)-pR_LW`VxR%WEG;75u8&S9~-(m*Pm{2H`N7_lg9tvlQS@k+sRHf}T9r7;jlalsuZ z0zb5ETu=mYN&{m(XajQ1<9PdLZYTn+!76w>cEvO_Qhp9`&%IGVBxBUj10{$aqJSF) z9<=W!&3m%*hwE05?0cb`NI`Jb{2EIAOdR0 zHmxHQ5KUOpH>?7~k4m@+oQK6XiLE%UsO)k*PZFO@4-w67j%glq;|mWhX29qvoRd6) z>UI|_p7a@}EP61xa7gPxX4S8PVx0zSHi@yPZQwh2$jZ{ONg5G&O4ckKP+EyJ-WRrD zKXxcINWs=TGj6C#AQ-Ah#hhK$nyiHY7#voK*vam5x|V{w%Ypb(5n9S0E>ORf0CaF$ z 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�h+00`?q7NTWQ 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 diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 093900ec..00000000 --- a/examples/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.venv/ -meshcore/corpus/generated/ diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index b8de3063..00000000 --- a/examples/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# DongLoRa Examples - -Example scripts demonstrating the [Python client library](../clients/python/). - -## Running - -```sh -cd examples -just rx # receive packets -just tx # transmit a packet -just ping-pong --role tx # two-dongle ping-pong demo -just test-commands # exercise all DongLoRa commands -just bridge --mode server # LoRa bridge over TCP -just meshcore # MeshCore packet decoder -just orac # MeshCore AI bot (needs ANTHROPIC_API_KEY) -just telemetry # MeshCore repeater telemetry monitor -``` - -## Depends On - -- [clients/python](../clients/python/) — the `donglora` client library diff --git a/examples/all_commands.py b/examples/all_commands.py deleted file mode 100755 index 3a3f1e74..00000000 --- a/examples/all_commands.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise every DongLoRa command to verify the API works. - -Usage: - python examples/all_commands.py [PORT] -""" - -import sys - -import serial - -import donglora as dl - -try: - ser = dl.connect(sys.argv[1] if len(sys.argv) > 1 else None) - - print("── Ping ──") - print(dl.send(ser, "Ping")) - - print("\n── SetConfig ──") - print(dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG)) - - print("\n── GetConfig ──") - print(dl.send(ser, "GetConfig")) - - print("\n── StartRx ──") - print(dl.send(ser, "StartRx")) - - print("\n── StopRx ──") - print(dl.send(ser, "StopRx")) - - print("\n── Transmit ──") - print(dl.send(ser, "Transmit", payload=b"Hello from all_commands.py!")) - - print("\n── DisplayOff ──") - print(dl.send(ser, "DisplayOff")) - - print("\n── DisplayOn ──") - print(dl.send(ser, "DisplayOn")) - - print("\n── GetMac ──") - print(dl.send(ser, "GetMac")) - - print("\nAll 9 commands exercised successfully.") - -except KeyboardInterrupt: - print("\nInterrupted.") -except serial.SerialException as e: - print(f"\nSerial error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/examples/justfile b/examples/justfile deleted file mode 100644 index e6c8c0c5..00000000 --- a/examples/justfile +++ /dev/null @@ -1,42 +0,0 @@ -set shell := ["bash", "-c"] - -_proj := source_directory() -_uv := "uv run --project " + _proj -_uv_meshcore := "uv run --project " + _proj + " --extra meshcore" -_uv_orac := "uv run --project " + _proj + " --extra orac" - -[private] -_ensure_tools: - @mise trust --yes . 2>/dev/null; mise install --quiet - -# Receive LoRa packets (Ctrl-C to stop) -rx *args: _ensure_tools - @{{_uv}} {{source_directory()}}/simple_rx.py {{args}} - -# Transmit a single packet -tx *args: _ensure_tools - @{{_uv}} {{source_directory()}}/simple_tx.py {{args}} - -# Two-dongle ping-pong demo (--role tx|rx) -ping-pong *args: _ensure_tools - @{{_uv}} {{source_directory()}}/ping_pong.py {{args}} - -# Exercise all DongLoRa commands -test-commands *args: _ensure_tools - @{{_uv}} {{source_directory()}}/all_commands.py {{args}} - -# Two-way LoRa bridge over TCP -bridge *args: _ensure_tools - @{{_uv}} {{source_directory()}}/lora_bridge.py {{args}} - -# MeshCore packet decoder/monitor -meshcore *args: _ensure_tools - @{{_uv_meshcore}} {{source_directory()}}/meshcore/meshcore_rx.py {{args}} - -# MeshCore AI bot (requires ANTHROPIC_API_KEY) -orac *args: _ensure_tools - @{{_uv_orac}} {{source_directory()}}/meshcore/ai_bot.py {{args}} - -# MeshCore repeater telemetry monitor -telemetry *args: _ensure_tools - @{{_uv_meshcore}} {{source_directory()}}/meshcore/telemetry_monitor.py {{args}} diff --git a/examples/lora_bridge.py b/examples/lora_bridge.py deleted file mode 100755 index de373616..00000000 --- a/examples/lora_bridge.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -"""Two-way LoRa bridge over TCP — relay packets between two DongLoRa dongles -across any IP network (LAN, Tailscale, WireGuard, internet). - -Architecture: - [Radio A] <-USB-> [DongLoRa A] <-TCP-> [DongLoRa B] <-USB-> [Radio B] - -Usage: - # Machine A (server): - python examples/lora_bridge.py --mode server --port 9100 - - # Machine B (client): - python examples/lora_bridge.py --mode client --host machineA --port 9100 - -Both sides: - - Open local DongLoRa, configure radio, start RX - - Forward received LoRa packets -> TCP -> remote side -> TX - - Bidirectional: both sides receive AND transmit -""" -import argparse -import serial -import socket -import struct -import sys -import threading -import time - -import donglora as dl - - -def tcp_send(sock: socket.socket, payload: bytes): - """Send a length-prefixed message over TCP.""" - sock.sendall(struct.pack(" bytes | None: - """Receive a length-prefixed message from TCP. Returns None on disconnect.""" - header = b"" - while len(header) < 4: - chunk = sock.recv(4 - len(header)) - if not chunk: - return None - header += chunk - length = struct.unpack(" 65536: - return None # sanity check - data = b"" - while len(data) < length: - chunk = sock.recv(length - len(data)) - if not chunk: - return None - data += chunk - return data - - -_ser_lock = threading.Lock() - - -def radio_to_tcp(ser: serial.Serial, sock: socket.socket): - """Thread: forward LoRa RX packets to TCP.""" - ser.timeout = 1 - while True: - try: - with _ser_lock: - data = dl.read_frame(ser) - if data is None: - continue - resp = dl.decode_response(data) - if resp["type"] == "RxPacket": - payload = resp["payload"] - snr = resp["snr"] - rssi = resp["rssi"] - sf = dl.DEFAULT_CONFIG["sf"] - min_snr = -2.5 * (sf - 4) - - # Grade per PROTOCOL.md — drop likely-corrupt packets - if snr < -32 or snr > 32: - grade = "INVALID" - elif snr < min_snr: - grade = "UNRELIABLE" - elif snr < min_snr + 3: - grade = "MARGINAL" - else: - grade = "GOOD" - - if grade in ("INVALID", "UNRELIABLE"): - print(f" RX drop len:{len(payload):3d} rssi:{rssi}dBm snr:{snr}dB [{grade}]") - continue - - tag = f" [{grade}]" if grade == "MARGINAL" else "" - print(f" RX→TCP len:{len(payload):3d} rssi:{rssi}dBm snr:{snr}dB{tag}") - tcp_send(sock, payload) - except (serial.SerialException, OSError) as e: - print(f" [radio→tcp error: {e}]") - break - - -def tcp_to_radio(ser: serial.Serial, sock: socket.socket): - """Thread: forward TCP packets to LoRa TX.""" - while True: - try: - payload = tcp_recv(sock) - if payload is None: - print(" [TCP disconnected]") - break - print(f" TCP→TX len:{len(payload):3d}") - with _ser_lock: - dl.send(ser, "Transmit", payload=payload) - except (serial.SerialException, OSError) as e: - print(f" [tcp→radio error: {e}]") - break - - -def run_bridge(ser: serial.Serial, sock: socket.socket): - """Run bidirectional bridge between serial and TCP.""" - print("Bridge active — forwarding packets bidirectionally\n") - - t1 = threading.Thread(target=radio_to_tcp, args=(ser, sock), daemon=True) - t2 = threading.Thread(target=tcp_to_radio, args=(ser, sock), daemon=True) - t1.start() - t2.start() - - try: - while t1.is_alive() and t2.is_alive(): - time.sleep(0.5) - except KeyboardInterrupt: - pass - print("\nBridge stopped.") - - -def main(): - parser = argparse.ArgumentParser(description="DongLoRa two-way LoRa bridge over TCP") - parser.add_argument("--mode", choices=["server", "client"], required=True) - parser.add_argument("--host", default="localhost", help="Remote host (client mode)") - parser.add_argument("--port", type=int, default=9100, help="TCP port") - parser.add_argument("--serial", default=None, help="Serial port (auto-detect if omitted)") - args = parser.parse_args() - - try: - # Open DongLoRa - ser = dl.connect(args.serial) - print(dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG)) - print(dl.send(ser, "StartRx")) - - # Establish TCP connection - if args.mode == "server": - print(f"Listening on port {args.port}...") - srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - srv.bind(("0.0.0.0", args.port)) - srv.listen(1) - sock, addr = srv.accept() - print(f"Connected from {addr}") - srv.close() - else: - print(f"Connecting to {args.host}:{args.port}...") - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((args.host, args.port)) - print("Connected") - - run_bridge(ser, sock) - - except KeyboardInterrupt: - print("\nInterrupted.") - except serial.SerialException as e: - print(f"\nSerial error: {e}", file=sys.stderr) - sys.exit(1) - except OSError as e: - print(f"\nNetwork error: {e}", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/examples/meshcore/README.md b/examples/meshcore/README.md deleted file mode 100644 index e6af1379..00000000 --- a/examples/meshcore/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# MeshCore Example - -This is a **protocol implementation** built on top of DongLoRa. The DongLoRa -firmware itself is protocol-agnostic — it just gives you a LoRa radio pipe. -MeshCore is one of many possible protocols you can run over it. - -## What's here - -- **meshcore_rx.py** — MeshCore packet decoder, Watchman mesh health reporter -- **channels.csv** — 276 known MeshCore channels (Public + hashtag channels) -- **corpus/** — Test vectors for MeshCore packet decoders - -## Usage - -From the `examples/` directory: - -```sh -just meshcore [PORT] -``` - -Connects to a DongLoRa device, configures the radio for the MeshCore -frequency/modulation, and decodes received packets in real time. - -## Running the test corpus - -From the `examples/` directory: - -```sh -uv run --extra meshcore meshcore/corpus/validate.py -``` diff --git a/examples/meshcore/ai_bot.py b/examples/meshcore/ai_bot.py deleted file mode 100644 index 659ce161..00000000 --- a/examples/meshcore/ai_bot.py +++ /dev/null @@ -1,1424 +0,0 @@ -#!/usr/bin/env python3 -"""Orac: a terse AI assistant on the MeshCore LoRa mesh network. - -Monitors all decryptable MeshCore channels for !askai, !ai, !claude, or !orac -triggers. Also accepts direct messages (DMs) — no trigger needed for DMs. -Advertises itself on the mesh and persists state across restarts. -""" -import collections -import csv -import hashlib -import hmac -import json -import os -import random -import struct -import sys -import time - -import anthropic -import serial -from cobs import cobs -from Crypto.Cipher import AES -from nacl.bindings import crypto_scalarmult -from nacl.signing import SigningKey, VerifyKey -from pathlib import Path - -import donglora as dl - - -# ── Constants ───────────────────────────────────────────────────── - -BOT_NAME = "Orac" -MAX_GRP_TEXT = 163 # max bytes for "SenderName: message" in a GRP_TXT -MAX_RESPONSE_CHARS = MAX_GRP_TEXT - len(BOT_NAME) - 2 # subtract "Orac: " -# DM plaintext: timestamp(4) + txt_type_attempt(1) + text + padding to 16 -# Max payload 184 - dst(1) - src(1) - mac(2) = 180 bytes ciphertext -# 180 bytes ciphertext / 16 = 11 blocks → 176 bytes plaintext max -# 176 - 5 (header) = 171 bytes text, minus null terminator padding -MAX_DM_TEXT = 170 - -TRIGGERS = ("!askai", "!ai", "!claude", "!orac") -AT_MENTIONS = (f"@{BOT_NAME}".lower(), f"@[{BOT_NAME}]".lower()) # "@orac" and "@[Orac]" - -CHANNEL_HISTORY_SIZE = 20 # messages per channel -DM_HISTORY_SIZE = 20 # messages per DM peer - -RATE_LIMIT_PER_SENDER = 5 # for channel messages -RATE_LIMIT_GLOBAL = 2 # between any two responses -RATE_LIMIT_DM = 2 # for DMs (more interactive) - -ADVERT_INTERVAL = 7200 # 2 hours in seconds - -DATA_DIR = Path.home() / ".donglora" -KEY_FILE = DATA_DIR / "orac_key.bin" -STATE_FILE = DATA_DIR / "orac_state.json" - -RADIO_CONFIG = { - "freq_hz": 910_525_000, - "bw": 6, # 62.5 kHz - "sf": 7, - "cr": 5, # CR 4/5 - "sync_word": 0x3444, - "tx_power_dbm": -128, # TX_POWER_MAX -} - -SYSTEM_PROMPT = f"""\ -You are Orac, a terse AI assistant on a MeshCore LoRa radio mesh network. - -ABSOLUTE HARD LIMIT: Your ENTIRE response must be {{max_chars}} characters or fewer. -This is a physical constraint of the radio protocol. Every character beyond this limit -is permanently lost and never transmitted. There is NO exception. - -Stay well under {{max_chars}}. Aim for 120 or fewer. NEVER show a character count or HTML tags in your reply. - -You can see recent messages for context. When someone asks you a question, they may -be referring to the ongoing conversation — use the chat history to understand what they mean. -For example, "what do you think?" refers to whatever was just discussed. "tell me more" means -elaborate on the recent topic. Read the room. - -Rules: -- One sentence or short phrase. Never more. -- Emoji OK but don't overdo it. No markdown, no bullet points, no formatting. -- Use common abbreviations freely (e.g., w/, b/c, approx, etc). -- Skip all pleasantries, greetings, and filler. -- You have a dry, sardonic wit. Be helpful but never wordy. -- If the question requires a long answer, give the most important point only. -- NEVER repeat a previous answer. Always find a fresh angle, new phrasing, or different take. -- Be creative and unpredictable. Surprise the reader.\ -""" - -# ── ANSI helpers ────────────────────────────────────────────────── - -RST = "\033[0m" -DIM = "\033[2m" -RED = "\033[31m" -GRN = "\033[32m" -YEL = "\033[33m" -CYN = "\033[36m" -BCYN = "\033[1;36m" -BGRN = "\033[1;32m" -BMAG = "\033[1;35m" -BWHT = "\033[1;37m" - - -# ── Persistent state ───────────────────────────────────────────── - -_state: dict = { - "channel_history": {}, # channel_name → list of messages - "dm_history": {}, # peer_pubkey_hex → list of messages - "known_nodes": {}, # pubkey_hex → {"name": str, "seen": float} -} - - -def _load_state(): - global _state - if STATE_FILE.is_file(): - try: - with open(STATE_FILE) as f: - loaded = json.load(f) - # Merge with defaults - for key in _state: - if key in loaded: - _state[key] = loaded[key] - print(f" {DIM}Loaded state from {STATE_FILE}{RST}") - except Exception as e: - print(f" {YEL}Failed to load state: {e}{RST}") - - -def _save_state(): - try: - DATA_DIR.mkdir(parents=True, exist_ok=True) - tmp = STATE_FILE.with_suffix(".tmp") - with open(tmp, "w") as f: - json.dump(_state, f, indent=2) - tmp.rename(STATE_FILE) - except Exception as e: - print(f" {RED}Failed to save state: {e}{RST}") - - -# ── Identity / Keypair ─────────────────────────────────────────── - -_signing_key: SigningKey | None = None -_verify_key: VerifyKey | None = None -_pubkey_bytes: bytes = b"" - - -def _init_identity(): - """Load or generate Ed25519 keypair. Persisted to disk.""" - global _signing_key, _verify_key, _pubkey_bytes - - DATA_DIR.mkdir(parents=True, exist_ok=True) - - if KEY_FILE.is_file(): - seed = KEY_FILE.read_bytes() - if len(seed) == 32: - _signing_key = SigningKey(seed) - print(f" {DIM}Loaded keypair from {KEY_FILE}{RST}") - else: - print(f" {YEL}Invalid key file, regenerating{RST}") - _signing_key = SigningKey.generate() - KEY_FILE.write_bytes(bytes(_signing_key)) - else: - _signing_key = SigningKey.generate() - KEY_FILE.write_bytes(bytes(_signing_key)) - print(f" {BWHT}Generated new keypair → {KEY_FILE}{RST}") - - _verify_key = _signing_key.verify_key - _pubkey_bytes = bytes(_verify_key) - print(f" {DIM}Pubkey: {_pubkey_bytes.hex()}{RST}") - print(f" {DIM}Node hash: 0x{_pubkey_bytes[0]:02x}{RST}") - - -def _my_hash() -> int: - """Our 1-byte node hash (first byte of pubkey).""" - return _pubkey_bytes[0] - - -# ── MeshCore channel crypto ────────────────────────────────────── - - -def _channel_secret_from_hashtag(name: str) -> bytes: - h = hashlib.sha256(name.encode()).digest() - return h[:16] + b"\x00" * 16 - - -def _channel_hash(secret: bytes) -> int: - key_len = 16 if secret[16:] == b"\x00" * 16 else 32 - return hashlib.sha256(secret[:key_len]).digest()[0] - - -def _grp_verify_and_decrypt( - secret: bytes, mac_bytes: bytes, ciphertext: bytes -) -> bytes | None: - key_len = 32 # HMAC always uses full 32-byte secret (spec Section 14) - computed = hmac.new(secret[:key_len], ciphertext, hashlib.sha256).digest()[:2] - if computed != mac_bytes: - return None - cipher = AES.new(secret[:16], AES.MODE_ECB) - plaintext = b"" - for i in range(0, len(ciphertext), 16): - plaintext += cipher.decrypt(ciphertext[i : i + 16]) - return plaintext - - -def _parse_grp_plaintext(plaintext: bytes) -> tuple[int, str] | None: - if len(plaintext) < 5: - return None - timestamp = struct.unpack_from(" bytes: - """Compute X25519 shared secret from our Ed25519 key and peer's Ed25519 pubkey.""" - my_x25519 = bytes(_signing_key.to_curve25519_private_key()) - peer_verify = VerifyKey(peer_ed25519_pub) - peer_x25519 = bytes(peer_verify.to_curve25519_public_key()) - return crypto_scalarmult(my_x25519, peer_x25519) - - -def _peer_encrypt(shared_secret: bytes, text: str) -> bytes: - """Encrypt a DM plaintext. Returns mac(2) + ciphertext.""" - plaintext = struct.pack(" bytes | None: - """Verify MAC then decrypt a peer message. Returns plaintext or None.""" - computed = hmac.new(shared_secret[:32], ciphertext, hashlib.sha256).digest()[:2] - if computed != mac_bytes: - return None - cipher = AES.new(shared_secret[:16], AES.MODE_ECB) - plaintext = b"" - for i in range(0, len(ciphertext), 16): - plaintext += cipher.decrypt(ciphertext[i : i + 16]) - return plaintext - - -def _parse_peer_plaintext(plaintext: bytes) -> str | None: - """Parse decrypted peer message: timestamp(4) + txt_type_attempt(1) + text.""" - if len(plaintext) < 5: - return None - text = plaintext[5:].split(b"\x00", 1)[0] - return text.decode("utf-8", errors="replace") if text else None - - -# ── Node registry ──────────────────────────────────────────────── - -# pubkey_hex → {"name": str, "seen": float} -# Stored in _state["known_nodes"], persisted to disk - -ADVERT_MAX_AGE = 43200 # 12 hours — used for display, NOT for key expiry - -# Pending DMs from unknown senders — queued until we learn their pubkey via ADVERT -# src_hash → list of (raw_payload, monotonic_timestamp) -_pending_dms: dict[int, list[tuple[bytes, float]]] = {} -PENDING_DM_TTL = 300 # 5 minutes — discard stale pending DMs - -# ── Route table ───────────────────────────────────────────────── -# Learned return paths from incoming packets. -# src_hash (int) → (reversed_path_hops: list[bytes], hash_size: int) -_route_table: dict[int, tuple[list[bytes], int]] = {} - - -def _learn_route(src_hash: int, path_hops: list[bytes], hash_size: int): - """Learn a return route from an incoming packet's path. - - path_hops is ordered closest-relay-first as received. - We reverse it so our outgoing path goes through the same relays - back toward the sender. Always updates the route, but prefers - larger hash sizes (3 > 2 > 1) for better addressing precision. - """ - existing = _route_table.get(src_hash) - if existing is not None: - _, existing_hs = existing - if hash_size < existing_hs: - return # Don't downgrade hash size - _route_table[src_hash] = (list(reversed(path_hops)), hash_size) - - -def _get_route(dest_hash: int) -> tuple[list[bytes], int] | None: - """Get a learned return route for a destination, or None if unknown.""" - return _route_table.get(dest_hash) - - -_RT_NAMES = {0: "tflood", 1: "flood", 2: "direct", 3: "tdirect"} - - -def _route_name(route_type: int) -> str: - return _RT_NAMES.get(route_type, f"rt{route_type}") - - -def _register_node(pubkey: bytes, name: str) -> bool: - """Register a node. Returns True if this is a NEW node (not just an update).""" - pk_hex = pubkey.hex() - is_new = pk_hex not in _state["known_nodes"] - _state["known_nodes"][pk_hex] = {"name": name, "seen": time.time()} - _save_state() - return is_new - - -def _lookup_node_by_hash(hash_byte: int) -> list[tuple[bytes, str]]: - """Find all known nodes whose pubkey first byte matches. Returns [(pubkey, name)]. - - Never expires keys — once we learn a peer's pubkey, we can always decrypt - their DMs. Peers shouldn't need to re-advertise just to message the bot. - """ - results = [] - for pk_hex, info in _state["known_nodes"].items(): - pk_bytes = bytes.fromhex(pk_hex) - if pk_bytes[0] == hash_byte: - results.append((pk_bytes, info["name"])) - return results - - -def _node_name(pubkey_hex: str) -> str: - info = _state["known_nodes"].get(pubkey_hex) - return info["name"] if info else pubkey_hex[:8] - - -# ── Channel registry ───────────────────────────────────────────── - -_KNOWN_CHANNELS: dict[str, bytes] = {} -_CHANNEL_BY_HASH: dict[int, list[tuple[str, bytes]]] = {} - - -def _register_channel(name: str, secret: bytes): - _KNOWN_CHANNELS[name] = secret - h = _channel_hash(secret) - _CHANNEL_BY_HASH.setdefault(h, []).append((name, secret)) - - -_CHANNELS_CSV = Path(__file__).parent / "channels.csv" - - -def _init_channels(): - if _CHANNELS_CSV.is_file(): - with open(_CHANNELS_CSV, newline="") as f: - for row in csv.DictReader(f): - name = row["channel_name"] - is_hashtag = row["hashtag"].strip().lower() == "true" - key_hex = row["key_hex"].strip() - if is_hashtag: - secret = _channel_secret_from_hashtag(name) - else: - secret = bytes.fromhex(key_hex) + b"\x00" * 16 - _register_channel(name, secret) - else: - print(f" {RED}channels.csv not found at {_CHANNELS_CSV}{RST}") - - for name in ("#devtest", "#devtestdevtest", "#askai", "#orac", "#ai", "#aibot", "#aibots"): - _register_channel(name, _channel_secret_from_hashtag(name)) - - -_init_channels() - - -# ── GRP_TXT encrypt + transmit ─────────────────────────────────── - - -def _grp_encrypt(secret: bytes, sender: str, text: str) -> bytes: - ch = _channel_hash(secret) - plaintext = struct.pack(" bytes: - header = bytes([0x15]) # GRP_TXT flood: (5 << 2) | 1 - path_len = bytes([0x40]) # 0 hops, 2-byte hash mode - return header + path_len + channel_payload - - -def _transmit_packet(ser: serial.Serial, packet: bytes, label: str = "Transmit"): - """Transmit a raw MeshCore packet.""" - try: - resp = send_cmd(ser, {"type": "Transmit", "payload": packet}, label=label) - if resp is None: - print(f" {RED}TX failed: no response from radio{RST}") - elif resp["type"] == "Error": - print(f" {RED}TX failed: {resp}{RST}") - except Exception as e: - print(f" {RED}TX exception: {e}{RST}") - - -def _grp_transmit(ser: serial.Serial, channel_name: str, sender: str, text: str): - secret = _KNOWN_CHANNELS.get(channel_name) - if secret is None: - print(f" {RED}TX failed: unknown channel {channel_name}{RST}") - return - payload = _grp_encrypt(secret, sender, text) - packet = _grp_build_packet(payload) - _transmit_packet(ser, packet) - - -# ── ADVERT construction + transmit ─────────────────────────────── - - -def _build_advert_packet() -> bytes: - """Build a MeshCore ADVERT packet for this node.""" - timestamp = struct.pack(" bytes: - """Build a packet using a learned route if available, otherwise flood.""" - route = _get_route(dest_hash) - if route is not None: - hops, hash_size = route - hash_size_code = hash_size - 1 - path_len_byte = (hash_size_code << 6) | (len(hops) & 0x3F) - path_data = b"".join(hops) - # direct route_type = 2 - header = bytes([(payload_type << 2) | 2]) - return header + bytes([path_len_byte]) + path_data + payload - else: - # flood route_type = 1 - header = bytes([(payload_type << 2) | 1]) - path_len = bytes([0x40]) # 0 hops, 2-byte hash mode - return header + path_len + payload - - -def _dm_transmit(ser: serial.Serial, peer_pubkey: bytes, text: str): - """Encrypt and transmit a TXT_MSG DM using learned route or flood.""" - # Simulate human typing delay (~40ms/char, clamped to 1–5s) - delay = min(max(len(text) * 0.04, 1.0), 5.0) - time.sleep(delay) - shared_secret = _ecdh_shared_secret(peer_pubkey) - mac_ct = _peer_encrypt(shared_secret, text) - - # Payload: dest_hash(1) + src_hash(1) + mac(2) + ciphertext - dm_payload = bytes([peer_pubkey[0], _my_hash()]) + mac_ct - - packet = _build_routed_packet(0x02, dm_payload, peer_pubkey[0]) - - peer_name = _node_name(peer_pubkey.hex()) - route = _get_route(peer_pubkey[0]) - route_str = "direct" if route is not None else "flood" - _transmit_packet(ser, packet, label=f"DM({route_str})\u2192{peer_name}") - - -# ── ACK transmit ───────────────────────────────────────────────── - - -def _compute_ack_hash(plaintext: bytes, sender_pubkey: bytes) -> bytes: - """Compute 4-byte ACK hash per MeshCore BaseChatMesh.cpp. - - Hash = SHA-256(frag1 || sender_pubkey)[0:4] - frag1 = timestamp(4) + txt_type_attempt(1) + text (no null terminator) - sender_pubkey = sender's Ed25519 public key (32 bytes) - """ - text_bytes = plaintext[5:] - null_pos = text_bytes.find(b"\x00") - text_len = null_pos if null_pos >= 0 else len(text_bytes) - frag1 = plaintext[: 5 + text_len] - return hashlib.sha256(frag1 + sender_pubkey).digest()[:4] - - -def _send_ack(ser: serial.Serial, ack_crc: bytes, dest_hash: int): - """Send an ACK using learned route or flood, with a brief delay for the DM flood to clear.""" - time.sleep(0.5) - packet = _build_routed_packet(0x03, ack_crc, dest_hash) - _transmit_packet(ser, packet, label="ACK") - - -def _send_path_return(ser: serial.Serial, peer_pubkey: bytes, path_hops: list[bytes], hash_size: int): - """Send a PATH return (0x08) so the sender can learn a direct route to us. - - Per spec Section 9: inner plaintext = path_len(1) + path + extra_type(1) + extra. - The path is reversed (our incoming path becomes the sender's outgoing path). - """ - reversed_hops = list(reversed(path_hops)) - hash_size_code = hash_size - 1 - path_len_byte = (hash_size_code << 6) | (len(reversed_hops) & 0x3F) - path_data = b"".join(reversed_hops) - - # Inner plaintext: path_len + path + 0xFF (dummy extra) + 4 random bytes - inner = bytes([path_len_byte]) + path_data + b"\xff" + os.urandom(4) - pad_len = (16 - len(inner) % 16) % 16 - inner += b"\x00" * pad_len - - shared_secret = _ecdh_shared_secret(peer_pubkey) - cipher = AES.new(shared_secret[:16], AES.MODE_ECB) - ciphertext = b"" - for i in range(0, len(inner), 16): - ciphertext += cipher.encrypt(inner[i : i + 16]) - mac = hmac.new(shared_secret[:32], ciphertext, hashlib.sha256).digest()[:2] - - # Outer: dest_hash + src_hash + mac + ciphertext - path_payload = bytes([peer_pubkey[0], _my_hash()]) + mac + ciphertext - packet = _build_routed_packet(0x08, path_payload, peer_pubkey[0]) - _transmit_packet(ser, packet, label="PATH") - - -# ── Serial / USB ───────────────────────────────────────────────── - - -def open_connection(port: str | None = None): - """Open a DongLoRa connection (mux auto-detected, falls back to USB).""" - return dl.connect(port=port, timeout=2) - - -# ── COBS framing ───────────────────────────────────────────────── - - -def cobs_frame(payload: bytes) -> bytes: - return cobs.encode(payload) + b"\x00" - - -def read_frame(ser: serial.Serial) -> bytes | None: - buf = b"" - while True: - b = ser.read(1) - if not b: - return None - if b == b"\x00": - break - buf += b - if not buf: - return None - try: - return cobs.decode(buf) - except cobs.DecodeError: - return None - - -# ── Command encoding / response decoding ───────────────────────── - - -def encode_radio_config(cfg: dict) -> bytes: - return struct.pack( - " bytes: - kind = cmd["type"] - tags = { - "Ping": 0, "GetConfig": 1, "SetConfig": 2, "StartRx": 3, - "StopRx": 4, "Transmit": 5, "DisplayOn": 6, "DisplayOff": 7, - } - out = bytes([tags[kind]]) - if kind == "SetConfig": - out += encode_radio_config(cmd["config"]) - elif kind == "Transmit": - config = cmd.get("config") - if config is None: - out += b"\x00" - else: - out += b"\x01" + encode_radio_config(config) - payload = cmd["payload"] - out += struct.pack(" dict: - if not data: - return {"type": "Empty"} - tag = data[0] - rest = data[1:] - if tag == 0: - return {"type": "Pong"} - elif tag == 1: - return {"type": "Config", "raw": rest.hex()} - elif tag == 2: - rssi = struct.unpack_from(" dict | None: - payload = encode_command(cmd) - frame = cobs_frame(payload) - if not quiet: - print(f" {DIM}>>>{RST} {label}") - ser.write(frame) - ser.flush() - for _ in range(50): - resp_data = read_frame(ser) - if resp_data is None: - if not quiet: - print(f" {DIM}<<<{RST} {YEL}timeout{RST}") - return None - resp = decode_response(resp_data) - if resp["type"] == "RxPacket": - continue - if not quiet: - t = resp["type"] - print(f" {DIM}<<<{RST} {t}") - return resp - if not quiet: - print(f" {DIM}<<<{RST} {YEL}timeout (RxPacket flood){RST}") - return None - - -# ── Claude API ─────────────────────────────────────────────────── - -_client: anthropic.Anthropic | None = None - - -def _get_client() -> anthropic.Anthropic: - global _client - if _client is None: - _client = anthropic.Anthropic() - return _client - - -def _shorten_with_claude(text: str, max_chars: int) -> str: - try: - resp = _get_client().messages.create( - model="claude-sonnet-4-6", - max_tokens=256, - messages=[ - { - "role": "user", - "content": f"Shorten this to {max_chars} characters: {text}", - } - ], - ) - shortened = resp.content[0].text.strip() - except Exception as e: - print(f" {RED}Shorten API error: {e}{RST}") - shortened = text - encoded = shortened.encode("utf-8") - if len(encoded) > max_chars: - while len(shortened.encode("utf-8")) > max_chars - 1: - shortened = shortened[:-1] - shortened += "…" - return shortened - - -def _extract_text(resp) -> str: - text = "" - for block in resp.content: - if block.type == "text" and block.text.strip(): - text = block.text.strip() - return text - - -def call_claude(query: str, sender: str, history: list[str], max_chars: int, context_label: str) -> str | None: - """Send a query to Claude with conversation context. Returns response text or None.""" - try: - nonce = random.randint(1000, 9999) - now = time.strftime("%Y-%m-%d %H:%M %Z") - prompt = SYSTEM_PROMPT.replace("{max_chars}", str(max_chars)) + f"\n\nCurrent date/time: {now}" - - if history: - context = "\n".join(history) - user_content = ( - f"[seed:{nonce}]\n" - f"Recent messages in {context_label}:\n{context}\n\n" - f"{sender} says: {query}" - ) - else: - user_content = f"[seed:{nonce}] {sender} says: {query}" - - messages = [{"role": "user", "content": user_content}] - tools = [{"type": "web_search_20260209", "name": "web_search"}] - - text = "" - for round_num in range(3): - resp = _get_client().messages.create( - model="claude-sonnet-4-6", - max_tokens=4096, - temperature=1.0, - system=prompt, - tools=tools, - messages=messages, - ) - - text = _extract_text(resp) - if text: - break - - if resp.stop_reason != "tool_use": - block_types = [b.type for b in resp.content] - print(f" {YEL}No text, stop={resp.stop_reason}, blocks={block_types}{RST}") - break - - print(f" {DIM}(web search round {round_num + 1})...{RST}") - messages.append({"role": "assistant", "content": resp.content}) - tool_results = [] - for block in resp.content: - if hasattr(block, "id") and block.type == "server_tool_use": - tool_results.append( - {"type": "tool_result", "tool_use_id": block.id, "content": ""} - ) - if tool_results: - messages.append({"role": "user", "content": tool_results}) - else: - break - - if not text: - print(f" {YEL}No text after tool loop{RST}") - return None - except Exception as e: - print(f" {RED}Claude API error: {e}{RST}") - return None - - if len(text.encode("utf-8")) > max_chars: - print(f" {YEL}Response too long ({len(text.encode('utf-8'))}B), shortening...{RST}") - text = _shorten_with_claude(text, max_chars) - - return text - - -def _rate_limit_message() -> str: - try: - nonce = random.randint(1000, 9999) - resp = _get_client().messages.create( - model="claude-sonnet-4-6", - max_tokens=100, - temperature=1.0, - system=( - f"You are Orac, a sardonic AI on a radio mesh network. " - f"Generate a single short message ({MAX_RESPONSE_CHARS} chars max, ASCII only) " - f"telling someone to slow down and try again in a few seconds. " - f"Be witty, varied, and in-character. No quotes around the message." - ), - messages=[ - {"role": "user", "content": f"[seed:{nonce}] Rate limit hit."} - ], - ) - text = resp.content[0].text.strip().strip('"\'') - if len(text.encode("utf-8")) <= MAX_RESPONSE_CHARS and text: - return text - except Exception: - pass - return "Patience. Try again in a moment." - - -# ── Trigger detection ──────────────────────────────────────────── - - -def _extract_trigger_query(text: str) -> str | None: - """Check for !triggers and @mentions. Returns the query (everything except the trigger) or None.""" - lower = text.lower() - for trigger in (*TRIGGERS, *AT_MENTIONS): - idx = lower.find(trigger) - if idx != -1: - before = text[:idx] - after = text[idx + len(trigger) :] - query = (before + " " + after).strip() - if query: - return query - return None - - -# ── Packet parsing helpers ─────────────────────────────────────── - - -def _parse_header_and_path(packet: bytes) -> tuple[int, int, int, int, bytes, list[bytes], int] | None: - """Parse MeshCore header + path. - - Returns (payload_type, route_type, payload_ver, pos, payload, path_hops, hash_size) or None. - path_hops is a list of raw hash bytes for each hop (closest relay first). - """ - if len(packet) < 3: - return None - - header = packet[0] - route_type = header & 0x03 - payload_type = (header >> 2) & 0x0F - payload_ver = (header >> 6) & 0x03 - - if payload_ver != 0: - return None - - has_tc = route_type in (0, 3) - - for skip_tc in ([True, False] if has_tc else [False]): - pos = 5 if skip_tc else 1 - if pos >= len(packet): - continue - - path_len_byte = packet[pos] - pos += 1 - hash_size_code = path_len_byte >> 6 - if hash_size_code == 3: - continue - hash_size = hash_size_code + 1 - hop_count = path_len_byte & 0x3F - path_bytes = hop_count * hash_size - if path_bytes > 64 or pos + path_bytes > len(packet): - continue - - hops = [] - for i in range(hop_count): - hops.append(packet[pos + i * hash_size : pos + (i + 1) * hash_size]) - pos += path_bytes - - payload = packet[pos:] - if payload and len(payload) <= 184: - return payload_type, route_type, payload_ver, pos, payload, hops, hash_size - - return None - - -# ── GRP_TXT interception ──────────────────────────────────────── - - -def _try_decrypt_grp(raw_payload: bytes) -> tuple[str, str] | None: - if len(raw_payload) < 19: - return None - ch = raw_payload[0] - mac_bytes = raw_payload[1:3] - ciphertext = raw_payload[3:] - - candidates = _CHANNEL_BY_HASH.get(ch, []) - for chan_name, secret in candidates: - plaintext = _grp_verify_and_decrypt(secret, mac_bytes, ciphertext) - if plaintext is not None: - parsed = _parse_grp_plaintext(plaintext) - if parsed: - _, text = parsed - return chan_name, text - return None - - -# ── DM interception ────────────────────────────────────────────── - - -def _try_decrypt_dm(raw_payload: bytes) -> tuple[bytes, str, str, bytes] | None: - """Try to decrypt a TXT_MSG DM addressed to us. - - Returns (sender_pubkey, sender_name, message_text, raw_plaintext) or None. - raw_plaintext is needed for ACK hash computation. - """ - if len(raw_payload) < 20: - return None - - dest_hash = raw_payload[0] - src_hash = raw_payload[1] - mac_bytes = raw_payload[2:4] - ciphertext = raw_payload[4:] - - print(f" {DIM}TXT_MSG dest=0x{dest_hash:02x} src=0x{src_hash:02x} me=0x{_my_hash():02x} [{len(ciphertext)}B]{RST}") - - if dest_hash != _my_hash(): - return None - - candidates = _lookup_node_by_hash(src_hash) - if not candidates: - print(f" {YEL}DM for us but no known node with hash 0x{src_hash:02x}{RST}") - return None - - for peer_pubkey, peer_name in candidates: - try: - shared_secret = _ecdh_shared_secret(peer_pubkey) - plaintext = _peer_verify_and_decrypt(shared_secret, mac_bytes, ciphertext) - if plaintext is not None: - text = _parse_peer_plaintext(plaintext) - if text: - return peer_pubkey, peer_name, text, plaintext - except Exception: - # ECDH failed — pubkey is likely corrupted (bad ADVERT). Evict it. - pk_hex = peer_pubkey.hex() - print(f" {YEL}Evicting bad key for {peer_name} ({pk_hex[:16]}...){RST}") - _state["known_nodes"].pop(pk_hex, None) - _save_state() - continue - - return None - - -RESP_SERVER_LOGIN_OK = 0x01 - - -def _try_decrypt_anon_req(raw_payload: bytes) -> tuple[bytes, str, bytes | None] | None: - """Try to decrypt an ANON_REQ (login). Returns (sender_pubkey, sender_name, plaintext_or_None) or None.""" - if len(raw_payload) < 51: - return None - - dest_hash = raw_payload[0] - sender_pubkey = raw_payload[1:33] - mac_bytes = raw_payload[33:35] - ciphertext = raw_payload[35:] - - print(f" {BCYN}ANON_REQ{RST} dest=0x{dest_hash:02x} sender={sender_pubkey.hex()[:16]}... [{len(ciphertext)}B]") - - if dest_hash != _my_hash(): - return None - - # Register sender — we now have their full pubkey - peer_name = _node_name(sender_pubkey.hex()) - is_new = _register_node(sender_pubkey, peer_name) - if is_new: - print(f" {BCYN}Registered new peer from ANON_REQ: {peer_name} (hash=0x{sender_pubkey[0]:02x}){RST}") - - try: - shared_secret = _ecdh_shared_secret(sender_pubkey) - plaintext = _peer_verify_and_decrypt(shared_secret, mac_bytes, ciphertext) - if plaintext is not None: - return sender_pubkey, peer_name, plaintext - else: - print(f" {YEL}ANON_REQ MAC mismatch{RST}") - except Exception as e: - print(f" {RED}ANON_REQ decrypt error: {e}{RST}") - - # Even if decryption failed, we registered the pubkey — return with no plaintext - return sender_pubkey, peer_name, None - - -def _send_login_response(ser: serial.Serial, peer_pubkey: bytes): - """Send a RESPONSE packet (login OK) back to the peer, like a room server.""" - shared_secret = _ecdh_shared_secret(peer_pubkey) - - # Response payload: timestamp(4) + RESP_SERVER_LOGIN_OK(1) + zero(1) + permissions(2) - reply = struct.pack(" tuple[bytes, str] | None: - """Decode and verify an ADVERT payload. Returns (pubkey, name) or None.""" - if len(raw_payload) < 100: - return None - - pubkey = raw_payload[0:32] - timestamp = raw_payload[32:36] - signature = raw_payload[36:100] - app_data = raw_payload[100:] - - # Verify Ed25519 signature to reject corrupted ADVERTs - try: - verify_key = VerifyKey(pubkey) - sign_msg = pubkey + timestamp + app_data - verify_key.verify(sign_msg, signature) - except Exception: - return None - - name = "" - if app_data: - flags = app_data[0] - pos = 1 - if flags & 0x10: # has location - pos += 8 - if flags & 0x20: # feat1 - pos += 2 - if flags & 0x40: # feat2 - pos += 2 - if flags & 0x80 and pos < len(app_data): # has name - name = app_data[pos:].decode("utf-8", errors="replace") - - return pubkey, name if name else pubkey.hex()[:8] - - -# ── Deduplication ──────────────────────────────────────────────── - -DEDUP_TTL = 120 -_seen_packets: dict[bytes, float] = {} - -# DM dedup on decrypted text (catches retries with different attempt counters) -DM_DEDUP_TTL = 60 -_seen_dm_texts: dict[str, float] = {} # "pubkey_hex:text" → monotonic timestamp - - -def _compute_packet_hash(payload_type: int, payload: bytes) -> bytes: - """Compute 8-byte packet hash per spec Section 16.""" - return hashlib.sha256(bytes([payload_type]) + payload).digest()[:8] - - -def _is_duplicate(payload_type: int, payload: bytes) -> bool: - """Dedup via 8-byte packet hash (spec Section 16).""" - now = time.monotonic() - if len(_seen_packets) > 500: - expired = [k for k, t in _seen_packets.items() if now - t > DEDUP_TTL] - for k in expired: - del _seen_packets[k] - - pkt_hash = _compute_packet_hash(payload_type, payload) - if pkt_hash in _seen_packets and now - _seen_packets[pkt_hash] < DEDUP_TTL: - return True - _seen_packets[pkt_hash] = now - return False - - -def _is_dm_duplicate(peer_pubkey_hex: str, text: str) -> bool: - """Dedup on decrypted DM text (catches retries with different attempt counters).""" - now = time.monotonic() - if len(_seen_dm_texts) > 200: - expired = [k for k, t in _seen_dm_texts.items() if now - t > DM_DEDUP_TTL] - for k in expired: - del _seen_dm_texts[k] - - key = f"{peer_pubkey_hex}:{text}" - if key in _seen_dm_texts and now - _seen_dm_texts[key] < DM_DEDUP_TTL: - return True - _seen_dm_texts[key] = now - return False - - -# ── History (persistent) ──────────────────────────────────────── - - -def _record_channel_msg(channel: str, text: str): - hist = _state["channel_history"] - if channel not in hist: - hist[channel] = [] - hist[channel].append(text) - # Trim to max size - if len(hist[channel]) > CHANNEL_HISTORY_SIZE: - hist[channel] = hist[channel][-CHANNEL_HISTORY_SIZE:] - _save_state() - - -def _get_channel_history(channel: str) -> list[str]: - return list(_state["channel_history"].get(channel, [])) - - -def _record_dm_msg(peer_pubkey_hex: str, text: str): - hist = _state["dm_history"] - if peer_pubkey_hex not in hist: - hist[peer_pubkey_hex] = [] - hist[peer_pubkey_hex].append(text) - if len(hist[peer_pubkey_hex]) > DM_HISTORY_SIZE: - hist[peer_pubkey_hex] = hist[peer_pubkey_hex][-DM_HISTORY_SIZE:] - _save_state() - - -def _get_dm_history(peer_pubkey_hex: str) -> list[str]: - return list(_state["dm_history"].get(peer_pubkey_hex, [])) - - -# ── Rate limiting ──────────────────────────────────────────────── - -_last_response: dict[str, float] = {} -_last_global_response = 0.0 -_last_rate_limit_reply: dict[str, float] = {} -RATE_LIMIT_REPLY_COOLDOWN = 30 - - -def _rate_limit_check(key: str) -> bool: - global _last_global_response - now = time.monotonic() - if now - _last_global_response < RATE_LIMIT_GLOBAL: - return False - limit = RATE_LIMIT_DM if key.startswith("dm:") else RATE_LIMIT_PER_SENDER - if key in _last_response and now - _last_response[key] < limit: - return False - return True - - -def _rate_limit_reply_ok(key: str) -> bool: - now = time.monotonic() - if key in _last_rate_limit_reply and now - _last_rate_limit_reply[key] < RATE_LIMIT_REPLY_COOLDOWN: - return False - _last_rate_limit_reply[key] = now - return True - - -def _rate_limit_record(key: str): - global _last_global_response - now = time.monotonic() - _last_response[key] = now - _last_global_response = now - - -# ── Bot loop ───────────────────────────────────────────────────── - - -def bot_loop(ser: serial.Serial): - send_cmd(ser, {"type": "Ping"}, "Ping") - send_cmd( - ser, - {"type": "SetConfig", "config": RADIO_CONFIG}, - "SetConfig 910.525/62.5k/SF7/CR4/5", - ) - send_cmd(ser, {"type": "StartRx"}, "StartRx") - - # Send initial ADVERT - _send_advert(ser) - last_advert = time.monotonic() - - print(f"\n {BWHT}{BOT_NAME} listening{RST} {DIM}(Ctrl+C to stop){RST}") - triggers_str = ", ".join(TRIGGERS) - print(f" {DIM}Channels: {len(_KNOWN_CHANNELS)} | Triggers: {triggers_str} | DMs: enabled{RST}\n") - ser.timeout = 1 - - while True: - # Periodic ADVERT - now = time.monotonic() - if now - last_advert >= ADVERT_INTERVAL: - _send_advert(ser) - last_advert = now - - - data = read_frame(ser) - if data is None: - continue - try: - resp = decode_response(data) - except Exception: - continue - if resp["type"] != "RxPacket": - continue - packet = resp["payload"] - parsed = _parse_header_and_path(packet) - if parsed is None: - continue - - payload_type, route_type, payload_ver, pos, raw_payload, path_hops, hash_size = parsed - - # Learn return route from incoming packet's path - if raw_payload and len(raw_payload) >= 2: - if payload_type in (0x01, 0x02, 0x08): # RESPONSE, TXT_MSG, PATH - src_hash = raw_payload[1] - elif payload_type == 0x04: # ADVERT - src_hash = raw_payload[0] - else: - src_hash = None - if src_hash is not None: - _learn_route(src_hash, path_hops, hash_size) - - # Dedup via packet hash (spec Section 16) - if _is_duplicate(payload_type, raw_payload): - continue - - # ── ACK (0x03) — ignore (we fire-and-forget DMs) ── - if payload_type == 0x03: - continue - - # ── ADVERT (0x04) ── - if payload_type == 0x04: - result = _try_decode_advert(raw_payload) - if result: - pubkey, name = result - if pubkey == _pubkey_bytes: - continue # ignore our own ADVERT echo - is_new = _register_node(pubkey, name) - print(f" {BCYN}ADVERT{RST} {name} {DIM}(hash=0x{pubkey[0]:02x} pk={pubkey.hex()[:16]}...){RST}") - - # Check for pending DMs from this node's hash - node_hash = pubkey[0] - if node_hash in _pending_dms and _pending_dms[node_hash]: - now = time.monotonic() - pending = _pending_dms.pop(node_hash) - print(f" {BCYN}Processing {len(pending)} pending DM(s) from {name}{RST}") - for pending_payload, ts in pending: - if now - ts > PENDING_DM_TTL: - continue # expired - dm_result = _try_decrypt_dm(pending_payload) - if dm_result: - peer_pubkey, peer_name, dm_text, dm_plaintext = dm_result - pk_hex = peer_pubkey.hex() - _send_ack(ser, _compute_ack_hash(dm_plaintext, peer_pubkey), peer_pubkey[0]) - if _is_dm_duplicate(pk_hex, dm_text): - continue - print(f" {BCYN}DM(flood){RST} from {BWHT}{peer_name}{RST}: {dm_text}") - _record_dm_msg(pk_hex, f"{peer_name}: {dm_text}") - history = _get_dm_history(pk_hex) - response = call_claude(dm_text, peer_name, history, MAX_DM_TEXT, f"DM with {peer_name}") - if response: - _rt = "direct" if _get_route(peer_pubkey[0]) else "flood" - print(f" {BGRN}<<< DM({_rt}) {BOT_NAME} → {peer_name}: {response}{RST}") - _dm_transmit(ser, peer_pubkey, response) - _record_dm_msg(pk_hex, f"{BOT_NAME}: {response}") - _rate_limit_record(f"dm:{pk_hex}") - continue - - # ── GRP_TXT (0x05) ── - if payload_type == 0x05: - result = _try_decrypt_grp(raw_payload) - if result is None: - continue - - channel_name, text = result - print(f" {BMAG}GRP_TXT{RST} {BGRN}{channel_name}{RST} {BWHT}{text}{RST}") - _record_channel_msg(channel_name, text) - - if ": " not in text: - continue - sender, _, body = text.partition(": ") - if sender == BOT_NAME: - continue - - query = _extract_trigger_query(body) - if query is None: - continue - - print(f" {BMAG}>>> Query from {sender} on {channel_name}: {query}{RST}") - - rl_key = f"ch:{channel_name}:{sender}" - if not _rate_limit_check(rl_key): - print(f" {YEL}Rate limited: {sender} on {channel_name}{RST}") - if _rate_limit_reply_ok(f"ch:{channel_name}"): - msg = _rate_limit_message() - print(f" {BGRN}<<< {BOT_NAME}: {msg}{RST}") - _grp_transmit(ser, channel_name, BOT_NAME, msg) - _rate_limit_record(rl_key) - continue - - history = _get_channel_history(channel_name) - response = call_claude(query, sender, history, MAX_RESPONSE_CHARS, f"channel {channel_name}") - if response is None: - continue - - print(f" {BGRN}<<< {BOT_NAME}: {response}{RST}") - _grp_transmit(ser, channel_name, BOT_NAME, response) - _record_channel_msg(channel_name, f"{BOT_NAME}: {response}") - _rate_limit_record(rl_key) - continue - - # ── ANON_REQ (0x07) — LOGIN from companion (includes full pubkey) ── - if payload_type == 0x07: - result = _try_decrypt_anon_req(raw_payload) - if result is None: - continue - - peer_pubkey, peer_name, plaintext = result - - # Learn route before responding so login response can be direct-routed - _learn_route(peer_pubkey[0], path_hops, hash_size) - - # Send login-OK response so the companion knows we accepted them - print(f" {BCYN}LOGIN{RST} from {BWHT}{peer_name}{RST} — sending login OK") - _send_login_response(ser, peer_pubkey) - - # If we also got a decrypted message, process pending DMs from this peer - pk_hex = peer_pubkey.hex() - node_hash = peer_pubkey[0] - if node_hash in _pending_dms and _pending_dms[node_hash]: - now_mono = time.monotonic() - pending = _pending_dms.pop(node_hash) - print(f" {BCYN}Processing {len(pending)} pending DM(s) from {peer_name}{RST}") - for pending_payload, ts in pending: - if now_mono - ts > PENDING_DM_TTL: - continue - dm_result = _try_decrypt_dm(pending_payload) - if dm_result: - dm_pubkey, dm_name, dm_text, dm_plaintext = dm_result - # ACK + dedup - _send_ack(ser, _compute_ack_hash(dm_plaintext, dm_pubkey), dm_pubkey[0]) - if _is_dm_duplicate(pk_hex, dm_text): - continue - print(f" {BCYN}DM(flood){RST} from {BWHT}{dm_name}{RST}: {dm_text}") - _record_dm_msg(pk_hex, f"{dm_name}: {dm_text}") - history = _get_dm_history(pk_hex) - response = call_claude(dm_text, dm_name, history, MAX_DM_TEXT, f"DM with {dm_name}") - if response: - _rt = "direct" if _get_route(dm_pubkey[0]) else "flood" - print(f" {BGRN}<<< DM({_rt}) {BOT_NAME} → {dm_name}: {response}{RST}") - _dm_transmit(ser, dm_pubkey, response) - _record_dm_msg(pk_hex, f"{BOT_NAME}: {response}") - _rate_limit_record(f"dm:{pk_hex}") - continue - - # ── TXT_MSG DM (0x02) ── - if payload_type == 0x02: - result = _try_decrypt_dm(raw_payload) - if result is None: - # If addressed to us but sender unknown, queue for later - if len(raw_payload) >= 4 and raw_payload[0] == _my_hash(): - src_hash = raw_payload[1] - if not _lookup_node_by_hash(src_hash): - _pending_dms.setdefault(src_hash, []).append((raw_payload, time.monotonic())) - if len(_pending_dms[src_hash]) == 1: - print(f" {BCYN}Queued DM from unknown 0x{src_hash:02x}, waiting for their ADVERT{RST}") - continue - - peer_pubkey, peer_name, dm_text, raw_plaintext = result - - # Send ACK immediately so the sender stops retrying - ack_crc = _compute_ack_hash(raw_plaintext, peer_pubkey) - _send_ack(ser, ack_crc, peer_pubkey[0]) - - # Send PATH return so sender can learn a direct route to us - _send_path_return(ser, peer_pubkey, path_hops, hash_size) - - pk_hex = peer_pubkey.hex() - - # Dedup on decrypted text (catches retries with different attempt counters) - if _is_dm_duplicate(pk_hex, dm_text): - continue - - print(f" {BCYN}DM({_route_name(route_type)}){RST} from {BWHT}{peer_name}{RST}: {dm_text}") - _record_dm_msg(pk_hex, f"{peer_name}: {dm_text}") - - rl_key = f"dm:{pk_hex}" - if not _rate_limit_check(rl_key): - print(f" {YEL}Rate limited DM: {peer_name}{RST}") - continue - - history = _get_dm_history(pk_hex) - response = call_claude(dm_text, peer_name, history, MAX_DM_TEXT, f"DM with {peer_name}") - if response is None: - continue - - _rt = "direct" if _get_route(peer_pubkey[0]) else "flood" - print(f" {BGRN}<<< DM({_rt}) {BOT_NAME} → {peer_name}: {response}{RST}") - _dm_transmit(ser, peer_pubkey, response) - _record_dm_msg(pk_hex, f"{BOT_NAME}: {response}") - _rate_limit_record(rl_key) - continue - - - -# ── Main ───────────────────────────────────────────────────────── - - -def main(): - if not os.environ.get("ANTHROPIC_API_KEY"): - print(f" {RED}ANTHROPIC_API_KEY environment variable not set{RST}") - sys.exit(1) - - _init_identity() - _load_state() - - print(f" {BWHT}{BOT_NAME}{RST} — MeshCore AI Bot") - print(f" {DIM}Max GRP response: {MAX_RESPONSE_CHARS} chars | Max DM response: {MAX_DM_TEXT} chars{RST}") - print(f" {DIM}Channels: {len(_KNOWN_CHANNELS)} | Known nodes: {len(_state['known_nodes'])}{RST}") - - port = sys.argv[1] if len(sys.argv) > 1 else None - - while True: - try: - print(f" {DIM}Connecting...{RST}") - ser = open_connection(port) - print(f" {GRN}Connected{RST}") - bot_loop(ser) - except (serial.SerialException, ConnectionError, OSError) as e: - print(f"\n {RED}Disconnected: {e}{RST}") - print(f" {DIM}Reconnecting when device reappears...{RST}") - time.sleep(1) - except KeyboardInterrupt: - print() - _save_state() - try: - ser.timeout = 2 - send_cmd(ser, {"type": "StopRx"}, "StopRx") - except Exception: - pass - break - - -if __name__ == "__main__": - main() diff --git a/examples/meshcore/channels.csv b/examples/meshcore/channels.csv deleted file mode 100644 index 81aeb515..00000000 --- a/examples/meshcore/channels.csv +++ /dev/null @@ -1,281 +0,0 @@ -channel_name,hashtag,key_hex,channel_hash,description,region -Public,False,8b3387e9c5cdea6ac9e5edbaa115cd72,17,"Default public channel (fixed PSK, not hashtag-derived)",global -#test,True,9cd8fcf22a47333b591d96a2b848b73f,156,Global testing channel,global -#testing,True,cde5e82cf515647dcb547a79a4f065d1,205,Testing (alternate name),global -#ping,True,3cae16fd067ba9c32a98be22e9b98525,60,Ping-bot testing channel,global -#emergency,True,e1ad578d25108e344808f30dfdaaf926,225,Emergency communications,global -#hamradio,True,83c8b01997654265938da8765cbc7db9,131,HAM radio operators,global -#qrv,True,4d6de18b9bb6ef3f7c7197e8441044ed,77,HAM QSO coordination/logging,global -#hard-software,True,c4419524d9ce72ac093fdd2a0a856595,196,Hardware/software discussion & troubleshooting,global -#blackout,True,75420460f335577fa27cdd20dacf9edd,117,Power outage info/notifications,global -#warnings,True,31d44d29ed676f8020dc09211a2ee9d8,49,General warnings channel,global -#aurora-alert,True,e823127951fc6a425abe0c65ac07b323,232,Aurora/northern lights alerts,global -#flachwitze,True,ec98cd8821fada7fe21ff9e1df6c619f,236,Jokes channel (German flat jokes),global -#general,True,4c49f3f24629f5ee4ad5b3965db47985,76,General chat (common default),global -#switzerland,True,8ad1ce57ad257627090ed28413c1f0b7,138,Swiss national channel,CH -#svizzera,True,e571e6921d9c19d1c393f2ba9286abba,229,Italian-speaking Switzerland,CH -#suisse,True,2b8dd2d22f0820d9945316be97359ab1,43,French-speaking Switzerland,CH -#chtest,True,ee95ca5221f5b41941b26d89fd702c37,238,Swiss testing channel,CH -#bern,True,7658cadca2e40ba98cb13ce19782e994,118,Bern region,CH -#zuerich,True,15d09e6787401c4fdbef4bf10ffcef53,21,Zürich canton/city,CH -#wallis,True,1e65af5328cec2c29d03c77a5dfb8cb8,30,Wallis/Valais canton,CH -#freiburg,True,ec1c88f03aad858a6d74ec36ac04c420,236,"Freiburg (DE, near CH border)",CH/DE -#netzstatus,True,e84d5ce15046d5a7f69db3a40f604851,232,"Network status, mainly Germany",DE -#muenchen,True,3f50b3350a8ed17a303f07e9641dff4a,63,Munich area,DE -#hansemesh,True,21ddee26dc5215153c3903165fd4262f,33,HanseMesh community (Hamburg),DE -#de-sn-dd,True,c252cf274c306e76d9531fdf96f8dfa0,194,Dresden/Saxony test channel,DE -#berlin,True,8bffd7b0bf481d92afc625e409b88a16,139,Berlin (mentioned in MeshCore tutorial),DE -#hamburg,True,6ee6b4b98d5856392596cc1929eb2e36,110,Hamburg area,DE -#koeln,True,cc2de011842824b10ee2e28e97f91339,204,Cologne area,DE -#frankfurt,True,0d8e969330d62f9356b5e4d415ea7f98,13,Frankfurt area,DE -#stuttgart,True,74710f9e61866f184734bc367d9f492a,116,Stuttgart area,DE -#deutschland,True,6870d6013221f0278122cb8c88990794,104,Germany national,DE -#ruhrgebiet,True,6b5f2c549d9de0116749aa087e3caa4e,107,Ruhr area,DE -#nuernberg,True,a6e4f905a64512843b12ec20750f4161,166,Nuremberg area,DE -#dresden,True,a6f1bb7dbc598884b7a15be59e61ecd6,166,Dresden area,DE -#leipzig,True,0cc55d55809a7b048c39dd01c3254381,12,Leipzig area,DE -#hannover,True,b9430bfc2908b23efcae886f16eb425b,185,Hannover area,DE -#duesseldorf,True,1dfb747e50229101f2232e391d1eb5a7,29,Düsseldorf area,DE -#dortmund,True,37951d1ca4e428fd24c57f3087f160f0,55,Dortmund area,DE -#essen,True,80174f513e9099612b537bc1cd450a41,128,Essen area,DE -#austria,True,faaa5ef01081222e319a8205357321f4,250,Austria national channel,AT -#wien,True,aba9d5e2fb16d2b02111db5926819065,171,Vienna,AT -#graz,True,ea00635b84c245d519d29dc4044a43a1,234,Graz,AT -#salzburg,True,132d8883191473fc8a82f3447909a37d,19,Salzburg,AT -#innsbruck,True,dee607ccd01ca7ef375c0567c554db97,222,Innsbruck,AT -#linz,True,9b417e09eb452be6d068c90b3c46303d,155,Linz,AT -#italy,True,f8d4dd36f6b9476eb4cec18a1536b17d,248,Italy (English name variant),IT -#italia,True,17fa9f15bec81ed7da12116253db5a20,23,Italy (Italian name variant),IT -#roma,True,3523b8022329e6648738f0e5448ffcc1,53,Rome,IT -#milano,True,ea8929998e608b67fe5c2bcf29473d8f,234,Milan,IT -#nederland,True,c14f7b1e0416e4fa74f5f7b81a707b50,193,Netherlands national,NL -#amsterdam,True,d768f5a0aa65f8c54e4ea521bd49eb4f,215,Amsterdam,NL -#rotterdam,True,e3f3ad251e512a029bd27161b14f0bdd,227,Rotterdam,NL -#utrecht,True,042e458dfb05b17cbdee57f93df4be4c,4,Utrecht,NL -#denhaag,True,84eed36f62c7b22527dbf8883585ad14,132,The Hague,NL -#eindhoven,True,aceb8cceae584a3890c8db173668ae2b,172,Eindhoven,NL -#groningen,True,6a18329035bd3d2be69fed9094ffa5f4,106,Groningen,NL -#localmesh,True,13934f45750ce5f54311771bc52dc2a9,19,LocalMesh NL community,NL -#belgium,True,7085b78ed010599094f8c8e7d1aa0e27,112,Belgium national,BE -#belgie,True,559a87a20b1170a11656ffbe15d27c07,85,Belgium (Dutch),BE -#brussel,True,58648303d8e2b4e35f4301c4551d6307,88,Brussels,BE -#antwerpen,True,5900168627ddb2963d3fd8dba3fecbb9,89,Antwerp,BE -#gent,True,82f82c2835bd5fbda1ab0eb134364532,130,Ghent,BE -#france,True,edcb362b38e74b99025a7e551d925d20,237,France national,FR -#paris,True,d0fc2b1ec400eb8669010ce0311a00fb,208,Paris,FR -#lyon,True,0b4b602516088ea6d245be43102643b0,11,Lyon,FR -#marseille,True,c91ed99a23e560300b60325c0b9fb3ea,201,Marseille,FR -#uk,True,22b2eed34b5cc429ce1dc5e88635ff84,34,United Kingdom,UK -#london,True,9881d2b7ab9105a41a8d0f6ba449447e,152,London (mentioned in MeshCore tutorial),UK -#manchester,True,5ddc4f781fa3f504e9f514e77df13790,93,Manchester,UK -#birmingham,True,13b1c76ef007b4ad0c3b168610337513,19,Birmingham,UK -#scotland,True,f4fce403cfd56f7089920d065718f29c,244,Scotland,UK -#ireland,True,1b2a12acc5db1517d9d407946756b1da,27,Ireland,IE -#dublin,True,8792638977132bc05a1f72d6bb913694,135,Dublin,IE -#seattle,True,ef627a9bbbb549347fdb76bf0cd3bc14,239,Seattle (Puget Mesh),US-WA -#bot,True,eb50a1bcb3e4e5d7bf69a57c9dada211,235,Bot channel (Puget Mesh),US-WA -#bot-tacoma,True,278df637e12a0dda01c826e1fd9779fd,39,Bot channel Tacoma,US-WA -#bot-van,True,bf07fbf914e37710561cb741e914efeb,191,Bot channel Vancouver,US-WA/CA -#capitolhill,True,7f281916c8ec32e13c5ef687d182160a,127,Capitol Hill Seattle,US-WA -#protest,True,195e9167baf010c4538d8532b6d7296e,25,Protest coordination,US-WA -#queer,True,5754476f162d93bbee3de0efba136860,87,LGBTQ+ community,US-WA -#seahawks,True,4973d6ceb6c35a09894a83688847c8a3,73,Seattle Seahawks,US-WA -#sports,True,e8ee81f3aabf105d9ba2d2d4bd94fe4a,232,Sports discussion,US-WA -#salishmesh,True,c1ec55555132c7598393c869715b7555,193,Salish Mesh (Vancouver BC area),CA-BC -#dwebyvr,True,b8f956aab74dae02f70f11ddba36de53,184,DWeb Vancouver,CA-BC -#zspace,True,fc86b8cceea87091d45f166c7a698f78,252,ZSpace Vancouver,CA-BC -#portland,True,45c6bc719c15b9fb809f48f594359877,69,Portland OR,US-OR -#pnw,True,98059014c708581fbf0a398cfe8a486d,152,Pacific Northwest,US-PNW -#tacoma,True,9ba83a22a865c9baaffc6bc8b6a33d2f,155,Tacoma WA,US-WA -#idahomesh,True,60baad727ec5235cee359135aeecb3f4,96,Idaho statewide mesh,US-ID -#nodakmesh,True,156134e4a3f6d9826a39e0b7f85afdcd,21,North Dakota mesh,US-ND -#florida,True,c44bd74eac2c81dcb6dfb217727b05cd,196,Florida statewide,US-FL -#miami,True,d81c566a5d337d588ebd250df6fc1b63,216,Miami,US-FL -#tampa,True,8d10508c39d5d8e6c5e3e8fab41d1c09,141,Tampa,US-FL -#orlando,True,3553bdbd9b3a54da760624a27dcda156,53,Orlando,US-FL -#jacksonville,True,6cf63df8459d99d4a4a6177acf6661f6,108,Jacksonville,US-FL -#texas,True,c4a214e133de5e9ad276f99fdbd7216f,196,Texas statewide,US-TX -#austin,True,b2e6f9af95d959734d71cdf90ca62533,178,Austin TX,US-TX -#dallas,True,5b2efa4e2ad0a2b83c5486ca4dd244de,91,Dallas TX,US-TX -#houston,True,c001fbacad2d97676395ca37e2576345,192,Houston TX,US-TX -#sanantonio,True,c2e72548c615d1cf2796a6f77b9545b0,194,San Antonio TX,US-TX -#dfw,True,5b7dc809ba579affccab5462c537244e,91,Dallas-Fort Worth,US-TX -#california,True,55da17570958a6a311939c99582e6f60,85,California statewide,US-CA -#losangeles,True,3dd9373dcea0294bd05ab067cc58e9d4,61,Los Angeles,US-CA -#sanfrancisco,True,ae3608083279fd2ad03f0d6a99617d25,174,San Francisco,US-CA -#sandiego,True,08623bbd90a96ecdc1f5c34e7292b35f,8,San Diego,US-CA -#bayarea,True,7f9a5fd3070ad14e337ba100ca53a89b,127,Bay Area,US-CA -#sacramento,True,a6d6927f0b48762cf1346e2ae95cec14,166,Sacramento,US-CA -#socal,True,f4018307615ac79d2e5ef17bb44654d4,244,Southern California,US-CA -#norcal,True,f2438510715f5d9d55eb4370664330f5,242,Northern California,US-CA -#sanjose,True,ce964c28170b1c043d06073e6fcd83a7,206,San Jose,US-CA -#sfbay,True,5bd50243f42644439c21dfe367eea031,91,SF Bay,US-CA -#baymesh,True,1be0e569ad371f72d1e36da7242ae2f0,27,Bay Mesh community,US-CA -#boston,True,9587d847a7208da684c89cc1f525bc03,149,Boston MA,US-MA -#newyork,True,82a78024dd7edf6c9be298c919632e25,130,New York,US-NY -#nyc,True,6e6554655f84ca26fbc09d81d15d6b96,110,New York City,US-NY -#chicago,True,c1c289b131e5222370cbc2048445844b,193,Chicago IL,US-IL -#denver,True,b24355a0d22ed2bf393ec530d75810b4,178,Denver CO,US-CO -#phoenix,True,027850d9410fa98809819c96644ec04c,2,Phoenix AZ,US-AZ -#atlanta,True,3c8f15665f99a349a97427e7c312ee0e,60,Atlanta GA,US-GA -#nashville,True,717490d2561c2e1f211b13e95073fb16,113,Nashville TN,US-TN -#charlotte,True,b67efd607080d98640774cb31181b0ce,182,Charlotte NC,US-NC -#raleigh,True,1a2999ed1fea10e2cdf456ff118193d9,26,Raleigh NC,US-NC -#minneapolis,True,3283cca82b7b0ac50e8014c344cb8a86,50,Minneapolis MN,US-MN -#stlouis,True,f366422991a19e745f65096e59b43d51,243,St. Louis MO,US-MO -#pittsburgh,True,926cb5461a1ef572619da576ef7de04b,146,Pittsburgh PA,US-PA -#detroit,True,bd01f26bf7d8c90952753157a41c61ac,189,Detroit MI,US-MI -#washingtondc,True,0067d451cc79f26bc9924b3fc53f28d7,0,Washington DC,US-DC -#dc,True,0f3aa71fed514f5c16ebaf265ef05b2e,15,DC (short form),US-DC -#colorado,True,ef61c9e5a3286053746f7603044bcb08,239,Colorado statewide,US-CO -#ohio,True,e89e8c2f72ebae476c4ded78a360d0ea,232,Ohio statewide,US-OH -#virginia,True,07655778c8adeb16c1a0470cc3516b01,7,Virginia statewide,US-VA -#georgia,True,6621a99c4c21a1f4897a9833a95ac962,102,Georgia statewide,US-GA -#michigan,True,4b0dc02774aa2ad8fe120d4ab0d17f1c,75,Michigan statewide,US-MI -#newengland,True,4ad1e19832319b916ab4f3a0fbb0a38e,74,New England region,US-NE -#appalachia,True,ab353b2bf5d281d97cd73d897ef8e149,171,Appalachian region,US -#hawaii,True,3fd57495820328594e1641d14583faa6,63,Hawaii,US-HI -#alaska,True,2a5841192b151422baec71537b0b5238,42,Alaska,US-AK -#vegas,True,d45435b6467e7ded33c28f6796bf5183,212,Las Vegas,US-NV -#lasvegas,True,b82b823d6dcc845ead47cee8cfa758b3,184,Las Vegas (long form),US-NV -#saltlakecity,True,07e180549b35782901d407832b3ec6a6,7,Salt Lake City,US-UT -#slc,True,fd31a70b220eb3e2430e59c15432ef8c,253,SLC (short form),US-UT -#utah,True,ee42ff5f055f2e83527e4efb91b10d0a,238,Utah statewide,US-UT -#canada,True,8373bb1055f34164c6dd2663927cb6d9,131,Canada national,CA -#toronto,True,e0d3774dd1da4dc5c55d8bd731555334,224,Toronto,CA-ON -#vancouver,True,16d6034be448ab86d11858cfa4c57c9c,22,Vancouver,CA-BC -#montreal,True,0c4c03b5fbea5b80f89e2a2a16ed3f40,12,Montreal,CA-QC -#calgary,True,bbbb1ad23fe1cdd7739667418204a57d,187,Calgary,CA-AB -#ottawa,True,7871ec72b45617696c35c970bddd8124,120,Ottawa,CA-ON -#edmonton,True,ce451a3a24ee89ee31564ae29e9dee04,206,Edmonton,CA-AB -#winnipeg,True,3c3294aac8a83b1143ab6bedee5b5226,60,Winnipeg,CA-MB -#australia,True,eadb84fa1da64c44b77c40fd11b9d78e,234,Australia national,AU -#sydney,True,57fe5284a5b905835193868d9bdbe1e9,87,Sydney,AU-NSW -#melbourne,True,4d73731d9450ccb9673eb923c0f40af2,77,Melbourne,AU-VIC -#brisbane,True,e4bd09784621decac600d0d4d857e3b2,228,Brisbane (MBUG community),AU-QLD -#perth,True,9873c6cf14dac409b5a573574cff5ce1,152,Perth,AU-WA -#adelaide,True,9ad0c536f3eabc7c2f0434d68e242009,154,Adelaide,AU-SA -#canberra,True,c716951221e6c17faf5f22b6245ce64b,199,Canberra,AU-ACT -#newzealand,True,1870a961e4aa06f62b02b835efcacb71,24,New Zealand national,NZ -#nz,True,eb87ee8817ba71315ac7be9c733b523a,235,NZ (short form),NZ -#auckland,True,7c6f1c71a5a3d6823a1d7bfd2a349ac8,124,Auckland,NZ -#wellington,True,c2da57d74f78996ad71bbe3e22446f16,194,Wellington,NZ -#christchurch,True,7b88b875fc5ae76305c8a15349549a3b,123,Christchurch,NZ -#gisborne,True,d8b45a1eb52d0bd45655d5f2a72d571f,216,Gisborne (mentioned in MeshCore tutorial),NZ -#weather,True,88f502554fee92a1625cfb311546e7cb,136,Weather updates,topical -#jokes,True,abe610d61dee508c0ac18793c36c41c3,171,Jokes channel,topical -#quotes,True,086be6f920280b7cc51d6ad91d6a786c,8,Quotes channel,topical -#secret,True,be657c0527e122bf93bc735999cd7e0d,190,Secret channel (Brisbane MBUG),topical -#chat,True,d0bdd6d71538138ed979eec00d98ad97,208,General chat,topical -#news,True,ecadb1a7d803db8958bea1302ca6e8be,236,News channel,topical -#help,True,dcc67fae2067046832af7b2b0b743165,220,Help channel,topical -#sos,True,9ed2c78bcd68ac7ce2a2fc3bb4045114,158,SOS / distress,topical -#prepper,True,1eec1a7df7080a392cb490473c4a9920,30,Prepper community,topical -#offgrid,True,aaa26662bebf122262692d0bca61dcef,170,Off-grid living,topical -#radio,True,266f225baced1b2a868dcc8e9c69a304,38,Radio hobby,topical -#lora,True,0749ea267c6be7b54ca1dbbae7dba0aa,7,LoRa technology discussion,topical -#meshcore,True,2fa78a5aef618e7c2a78f0ab5c8869b3,47,MeshCore project discussion,topical -#meshtastic,True,73a2e13ff0dea9ed19b24b2ab753650f,115,Meshtastic discussion,topical -#iot,True,233b42534ed82b8ae6a88d785f1b27fd,35,IoT / sensors,topical -#solar,True,7592a730ff1af2b40b95589fbafcca01,117,Solar power,topical -#emcomm,True,a9a49340642dfc4c562d7849b7c8a258,169,Emergency communications,topical -#ares,True,44419e394cde859e45710f288db939a8,68,ARES (Amateur Radio Emergency Service),topical -#gmrs,True,2ad811e84ab4ad0e3a648d8fcf3d650a,42,GMRS radio,topical -#cb,True,a35b5f87bbb67859a02e7666c823de7a,163,CB radio,topical -#hiking,True,2370a013053e384e5f18918bc2b26baf,35,Hiking,topical -#camping,True,c011b7c2abf33748cd9bd5a78c2b4955,192,Camping,topical -#hunting,True,0c0c28fd9bbf096ee06e01af840037af,12,Hunting,topical -#fishing,True,a671ca13cc499898aea4596e804ecf20,166,Fishing,topical -#skiing,True,b3baf3586d347ecaf2c113106a564207,179,Skiing,topical -#biking,True,9c0591d708b7c7970c9ad6712e3c4d05,156,Biking,topical -#ham,True,5270db3979da687fa133fec6684cd952,82,HAM radio (short form),topical -#cq,True,bc9da8bdebcaeee40fefff7dd16855f1,188,CQ (calling all stations),topical -#dx,True,adf77706da76b66bb0a313c1484d3be4,173,DX (long distance contacts),topical -#repeater,True,289991a3077903263f2d31493887c651,40,Repeater discussion,topical -#firmware,True,6163564e7449c1c842e58dcf2cd1ab4f,97,Firmware discussion,topical -#debug,True,fd7a60ed4796efcd1965c2de466105cb,253,Debug channel,topical -#dev,True,d41bcba61e9dca7177c7b8533d23a0bc,212,Development channel,topical -#admin,True,889334b7e486938c776dbdde120da9de,136,Admin channel,topical -#monitor,True,131479235e65b7c591307f699d64dc74,19,Monitoring channel,topical -#alert,True,678c7d2e08c019e113ace03eaaa128ad,103,Alert channel,topical -#local,True,d2d35fa76be9875ed254db80397483a5,210,Local community,topical -#neighborhood,True,53403881955f0e11573ba1e5d3a82c4b,83,Neighborhood chat,topical -#community,True,b9323ce55a729068ed8e3ed49b9b84b5,185,Community channel,topical -#events,True,d2cbcc606979cdb05c7bd66f49f23baa,210,Events channel,topical -#trade,True,c2be982890fb3a63113de637b9aee6fe,194,Trading/swap channel,topical -#marketplace,True,354a741c2975c843ec0706089390b968,53,Marketplace,topical -#gaming,True,3802c79121f195ea50ad9ab2aa2c402a,56,Gaming,topical -#music,True,b025ab29b0a5f56fb68a474741d7a2cc,176,Music,topical -#tech,True,5177b749eefbfab3c90a21b4e2518c5f,81,Technology,topical -#science,True,8abc980d3764fdabda5dfa6b6bc3a6cc,138,Science,topical -#crypto,True,0aa7c45d99be27024b82a1474c693951,10,Cryptocurrency,topical -#bitcoin,True,94bf6fd2688b1456c071ebad63d7021d,148,Bitcoin,topical -#linux,True,02c74eda5d8ec8b9bb945049fb2f55c6,2,Linux,topical -#arduino,True,ce8c596922eff9274f3b1e19ad296754,206,Arduino,topical -#raspberry,True,469bc476d8263b64b6f90ca868b04b91,70,Raspberry Pi,topical -#esp32,True,9f8aac4c48973b07bbdc47acbaea3e8e,159,ESP32 projects,topical -#heltec,True,71af5d9a209e8b17a19ee22aa7b6be23,113,Heltec devices,topical -#lilygo,True,a33dd4b025cc82b81f97318a63a9d5d3,163,LilyGo devices,topical -#rak,True,1f4734675fdfcabfaa67a0a059c65c5f,31,RAK Wireless devices,topical -#antenna,True,e8979157107155ade90822e29b186ee8,232,Antenna discussion,topical -#rf,True,46aaa16f78b3969040aad2b6b694b59e,70,RF engineering,topical -#preppers,True,4877173813de9668b9ef33adbc1b8f37,72,Preppers community,topical -#survival,True,e1f465ea51df09fc901389758f1c5f01,225,Survival skills,topical -#grid-down,True,94ddbd51f2ee0bd47426a6f3ed79be99,148,Grid-down scenarios,topical -#wildfire,True,b9124c3aecc0c813634f79f144b93f83,185,Wildfire alerts,topical -#hurricane,True,c13a2f9d18400e5d93bae4bb3afef40c,193,Hurricane alerts,topical -#earthquake,True,0c7082c04a1a90502a5f32fc6a9f6524,12,Earthquake alerts,topical -#flood,True,f2a0fd0abe4c9fc6865b5f8eebf319d6,242,Flood alerts,topical -#tornado,True,76e577e8187d0bab89052343f3835039,118,Tornado alerts,topical -#storm,True,113761b9e31a5c30e0ee4fc78dc7310b,17,Storm alerts,topical -#traffic,True,c1216dacbb7e4c3b9a3356e52f1c27fa,193,Traffic updates,topical -#fire,True,3d74a070077293ab66baf3aa724349ff,61,Fire alerts,topical -#police,True,79b89aa870ea85dbb8396f401e547d99,121,Police scanner / alerts,topical -#ems,True,f714461f26d43d9a17b2530c3ad29bc2,247,EMS / first responders,topical -#search-rescue,True,cd8ab9e4944ee656ae6e961a4437c551,205,Search and rescue,topical -#volunteer,True,2d3d64746075cb99407d681e547b99cb,45,Volunteer coordination,topical -#data,True,b0d9c01c8278a711c68ffdca98ef1245,176,Data/telemetry channel,topical -#sensor,True,6218df72386dd48259024327e8ebf66f,98,Sensor data channel,topical -#tracker,True,70258e69aeb03d1cd2e16072e1fe14b5,112,Asset/pet tracker,topical -#gps,True,af4b9f722a6200b1eb2696660017d0df,175,GPS tracking,topical -#boat,True,c03117578e38c3f1eb5ae8c3d0019753,192,Boating/maritime,topical -#sailing,True,c2f6052f8b89639f81c8ff4def32deae,194,Sailing,topical -#marine,True,7f4b40aac63642805a54a959beb07694,127,Marine channel,topical -#aviation,True,e17538c835ee0990b349ca1879b4f57a,225,Aviation,topical -#drone,True,4460931543103de42d2f85093a80273c,68,Drone / UAV,topical -#fpv,True,6d2752bc94797cfce43e2138eab7a772,109,FPV flying,topical -#farm,True,4a2f1b2147165cf942402a4c6bd7d42d,74,Farming / agriculture,topical -#ranch,True,5885e728fc06bfcbe7d6c9020c13ff10,88,Ranch / livestock,topical -#tucson,True,cf989ccea881cf5ddcf40d87bbfc441e,207,Tucson AZ,US-AZ -#albuquerque,True,2a59b6ce7d3ce661664217f9b8255e86,42,Albuquerque NM,US-NM -#boise,True,f4e641f9c90ac3bd5bc442ef379f6eb3,244,Boise ID,US-ID -#spokane,True,dfcf2a863b3baa99e3d3274bfb56439f,223,Spokane WA,US-WA -#olympia,True,baec83dd15ef758ad8bf8f357237c9cb,186,Olympia WA,US-WA -#bellingham,True,6000c8e3c1343fd920749eb3ad2a42b4,96,Bellingham WA,US-WA -#eugene,True,e6ce6f5e71d88bb90c0b7dacc2235662,230,Eugene OR,US-OR -#bend,True,291ddea6fc1c1742f7ad425819b66f7b,41,Bend OR,US-OR -#reno,True,e9edf1edf57a04b8f66e75829b4edc06,233,Reno NV,US-NV -#provo,True,72744153a7a2d84ed69338be4dc3fc31,63,Provo,US-UT -#orem,True,97236968b8a6c0cf297f1c1365539d0d,157,Orem,US-UT -#ogden,True,cdeec2a40203056a6c6f2d1fb5f50b81,245,Ogden,US-UT -#logan,True,b0ee430ede14470d5ff8fafbe092ee50,145,Logan,US-UT -#stgeorge,True,7792659def2811af89a02270b2918235,128,St. George,US-UT -#parkcity,True,bbb64d6ae8eaeaf4302f501844f64511,25,Park City,US-UT -#lehi,True,18f20a96d251665105d59df6ca5bcc5c,98,Lehi,US-UT -#byu,True,65633d6a99bbcb173cb01afeae3a51d0,194,Brigham Young University,US-UT -#uvu,True,cb305b9b3d0ab33fce2186ee6b9c58ff,91,Utah Valley University,US-UT -#uofu,True,c08fe0d4d37b4b7f233f2977296979d5,178,University of Utah,US-UT -#usu,True,ceb222637d010d1c5551a5035e86f89d,104,Utah State University,US-UT -#utahcounty,True,f191a8a833d2ae61b7fbfa8c9891cc0c,23,Utah County,US-UT -#daviscounty,True,926847daaed0baa7758df5ef8af76f32,11,Davis County,US-UT -#webercounty,True,364726e43d1fb9b8079ededb03f00764,172,Weber County,US-UT -#wasatchfront,True,eb9fa3bfa390eed3737634bed39c7460,44,Wasatch Front metro corridor,US-UT -#wasatch,True,15bf0e80d3d88092d38e4c82da7e523e,0,Wasatch region,US-UT -#saltlake,True,56327c6958d9108213f8638fb2a2b7a4,173,Salt Lake (alternate),US-UT -#tooele,True,092cbdcf351bdbe77ce6c00e1dbeb294,184,Tooele,US-UT -#sanpete,True,4ae01748b17bfc90e13004f19e734bbc,151,Sanpete County,US-UT -#nodeops,True,33677d99a0e707e37b57de3e3cd26cf3,151,Node operations channel,topical -#weekly-net,True,bdc4f5cb11e766c9a09eeb3586e65a58,18,Weekly net check-in,topical -#net,True,13aef6b8ce642cf4c482b91f0816fe68,157,Net channel,topical -#repeater-ops,True,915c91c726e0c9d558365a7ecc72bbd6,108,Repeater operations,topical -#watchman,True,11489fe661e776b3ccdcf864bd14f7d6,245,Watchman mesh health alerts,global diff --git a/examples/meshcore/corpus/build_hand.py b/examples/meshcore/corpus/build_hand.py deleted file mode 100755 index b611e577..00000000 --- a/examples/meshcore/corpus/build_hand.py +++ /dev/null @@ -1,649 +0,0 @@ -#!/usr/bin/env python3 -"""Build hand-crafted MeshCore test vectors as individual JSON files.""" -# /// script -# requires-python = ">=3.10" -# dependencies = [] -# /// - -import json -import os -import struct -from pathlib import Path - -OUT = Path(__file__).parent / "hand" -OUT.mkdir(exist_ok=True) - - -def write_case(name: str, case: dict): - path = OUT / f"{name}.json" - path.write_text(json.dumps(case, indent=2, ensure_ascii=False) + "\n") - print(f" {path.name}") - - -# ── Packet building helpers ─────────────────────────────────────── - -def mc_header(payload_type: int, route_type: int, ver: int = 0) -> int: - return (ver << 6) | (payload_type << 2) | route_type - - -def path_len_byte(hop_count: int, hash_size: int) -> int: - return ((hash_size - 1) << 6) | (hop_count & 0x3F) - - -DUMMY_PUBKEY = bytes(range(32)) -DUMMY_SIG = bytes(64) -TS_2026 = struct.pack(" bytes: - hdr = bytes([mc_header(0x04, route_type)]) - hops = path_hops or [] - pl = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - payload = pubkey + timestamp + DUMMY_SIG + app_data - return hdr + pl + path + payload - - -def peer_packet(payload_type: int, route_type: int, dst: int, src: int, - mac: bytes, ciphertext: bytes, - path_hops: list[bytes] | None = None, - hash_size: int = 1, tc: tuple[int, int] | None = None) -> bytes: - hdr = bytes([mc_header(payload_type, route_type)]) - hops = path_hops or [] - pl_byte = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - body = bytes([dst, src]) + mac + ciphertext - tc_bytes = b"" - if tc is not None: - tc_bytes = struct.pack(" bytes: - hdr = bytes([mc_header(0x03, route_type)]) - hops = path_hops or [] - pl_byte = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - return hdr + pl_byte + path + struct.pack(" bytes: - hdr = bytes([mc_header(payload_type, route_type)]) - hops = path_hops or [] - pl_byte = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - return hdr + pl_byte + path + bytes([ch_hash]) + mac + ciphertext - - -def anon_req_packet(dst: int, ephem_pub: bytes, mac: bytes, ciphertext: bytes, - route_type: int = 1, path_hops: list[bytes] | None = None, - hash_size: int = 1) -> bytes: - hdr = bytes([mc_header(0x07, route_type)]) - hops = path_hops or [] - pl_byte = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - return hdr + pl_byte + path + bytes([dst]) + ephem_pub + mac + ciphertext - - -def trace_packet(tag: int, auth_code: int, flags: int, - trace_hashes: bytes = b"", - route_type: int = 1, - path_hops: list[bytes] | None = None, - hash_size: int = 1) -> bytes: - hdr = bytes([mc_header(0x09, route_type)]) - hops = path_hops or [] - pl_byte = bytes([path_len_byte(len(hops), hash_size)]) - path = b"".join(hops) - payload = struct.pack(" bytes: - hdr = bytes([mc_header(0x0A, route_type)]) - pl_byte = bytes([path_len_byte(0, 1)]) - control = bytes([(remaining << 4) | (inner_type & 0x0F)]) - return hdr + pl_byte + control + inner_payload - - -def control_packet(flags_byte: int, payload: bytes, - route_type: int = 1) -> bytes: - hdr = bytes([mc_header(0x0B, route_type)]) - pl_byte = bytes([path_len_byte(0, 1)]) - return hdr + pl_byte + bytes([flags_byte]) + payload - - -# ── Test cases ──────────────────────────────────────────────────── - -print("Building hand-crafted test vectors...") - -# ADVERT: basic chat node with name -write_case("advert_basic", { - "description": "ADVERT flood, chat node with simple ASCII name", - "tags": ["advert", "flood", "name", "chat"], - "packet_hex": advert_packet(bytes([0x81]) + b"TestNode").hex(), - "valid": True, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": None, - "path": {"hash_size": 1, "hops": []}, - "payload": { - "pubkey_prefix": DUMMY_PUBKEY[:4].hex(), - "timestamp": 1774600000, - "advert": { - "flags": 0x81, - "node_type": "chat", - "location": None, - "name": "TestNode" - } - } - } -}) - -# ADVERT: repeater with valid GPS location + name -write_case("advert_location", { - "description": "ADVERT flood, repeater with valid GPS coords and name", - "tags": ["advert", "flood", "location", "repeater"], - "packet_hex": advert_packet( - bytes([0x92]) - + struct.pack("= 3)", - "tags": ["invalid", "too_short"], - "packet_hex": "1100", - "valid": False, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "error": "too_short" - } -}) - -# Bad path_len: reserved hash_size_code 3 (bits 6-7 = 0b11) -bad_pkt = bytes([mc_header(0x02, 1), 0xC1]) + bytes(24) -write_case("bad_path_len_reserved_hash_size", { - "description": "TXT_MSG flood, path_len has reserved hash_size_code 3", - "tags": ["invalid", "bad_framing", "path", "reserved_hash_size"], - "packet_hex": bad_pkt.hex(), - "valid": False, - "expected": { - "route_type": "flood", - "payload_type": "TXT_MSG", - "error": "bad_framing" - } -}) - -# Bad path_len: claims 33 hops (2B hash) but only 24 bytes remain -bad_pkt2 = bytes([mc_header(0x02, 1), 0x61]) + bytes(24) -write_case("bad_path_len", { - "description": "TXT_MSG flood, path_len claims 33 hops (2B hash) but data is too short", - "tags": ["invalid", "bad_framing", "path"], - "packet_hex": bad_pkt2.hex(), - "valid": False, - "expected": { - "route_type": "flood", - "payload_type": "TXT_MSG", - "error": "bad_framing" - } -}) - -# Noise: random bytes that don't parse as meshcore -write_case("noise_packet", { - "description": "Random bytes, not a valid MeshCore packet", - "tags": ["invalid", "noise"], - "packet_hex": "03d135839fc28d00", - "valid": False, - "expected": { - "route_type": "tdirect", - "payload_type": "REQ", - "error": "not_meshcore" - } -}) - -# Empty packet -write_case("empty_packet", { - "description": "Empty packet (0 bytes)", - "tags": ["invalid", "too_short"], - "packet_hex": "", - "valid": False, - "expected": { - "route_type": "flood", - "payload_type": "REQ", - "error": "too_short" - } -}) - -# Bad advert: too short for pubkey+timestamp+signature -short_advert = advert_packet(b"") # 102 bytes: hdr + path_len + 100 payload -truncated = short_advert[:50] # cut in half -write_case("advert_truncated", { - "description": "ADVERT flood, truncated mid-signature (50 bytes)", - "tags": ["invalid", "advert", "truncated"], - "packet_hex": truncated.hex(), - "valid": True, # parses as ADVERT but payload is bad - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": None, - "path": {"hash_size": 1, "hops": []} - } -}) - -print(f"\nDone! {len(list(OUT.glob('*.json')))} test vectors written to {OUT}/") diff --git a/examples/meshcore/corpus/generate.py b/examples/meshcore/corpus/generate.py deleted file mode 100755 index 756883c5..00000000 --- a/examples/meshcore/corpus/generate.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env python3 -"""Generate MeshCore test vectors using Hypothesis (property-based testing).""" -# /// script -# requires-python = ">=3.10" -# dependencies = ["hypothesis", "jsonschema"] -# /// - -import argparse -import json -import struct -import sys -from pathlib import Path - -from hypothesis import given, settings, HealthCheck, Phase -from hypothesis import strategies as st - -CORPUS_DIR = Path(__file__).parent -OUT = CORPUS_DIR / "generated" -SCHEMA_PATH = CORPUS_DIR / "schema.json" - -# MeshCore constants -MAX_PAYLOAD = 184 -MAX_PATH_SIZE = 64 -MAX_ADVERT_DATA_SIZE = 32 -PUB_KEY_SIZE = 32 -SIGNATURE_SIZE = 64 - -ROUTE_TYPES = {0: "tflood", 1: "flood", 2: "direct", 3: "tdirect"} -PAYLOAD_TYPES = { - 0: "REQ", 1: "RESPONSE", 2: "TXT_MSG", 3: "ACK", 4: "ADVERT", - 5: "GRP_TXT", 6: "GRP_DATA", 7: "ANON_REQ", 8: "PATH", - 9: "TRACE", 10: "MULTIPART", 11: "CONTROL", 15: "RAW_CUSTOM", -} -NODE_TYPES = {1: "chat", 2: "repeater", 3: "room", 4: "sensor"} - - -def mc_header(payload_type: int, route_type: int, ver: int = 0) -> int: - return (ver << 6) | (payload_type << 2) | route_type - - -def path_len_byte(hop_count: int, hash_size: int) -> int: - return ((hash_size - 1) << 6) | (hop_count & 0x3F) - - -# ── Hypothesis strategies ───────────────────────────────────────── - -hash_sizes = st.sampled_from([1, 2, 3]) -route_types_no_tc = st.sampled_from([1, 2]) # flood, direct (no transport codes) -route_types_all = st.sampled_from([0, 1, 2, 3]) -payload_vers = st.sampled_from([0]) # only v0 is well-defined - - -@st.composite -def path_strategy(draw, hash_size=None): - hs = hash_size or draw(hash_sizes) - max_hops = min(63, MAX_PATH_SIZE // hs) - hop_count = draw(st.integers(min_value=0, max_value=min(max_hops, 10))) - hops = [draw(st.binary(min_size=hs, max_size=hs)) for _ in range(hop_count)] - return hs, hops - - -@st.composite -def advert_appdata(draw): - """Generate valid ADVERT app_data.""" - node_type = draw(st.sampled_from([1, 2, 3, 4])) - has_loc = draw(st.booleans()) - has_name = draw(st.booleans()) - flags = node_type - data = b"" - loc = None - name = None - - if has_loc: - flags |= 0x10 - lat = draw(st.integers(min_value=-90_000_000, max_value=90_000_000)) - lon = draw(st.integers(min_value=-180_000_000, max_value=180_000_000)) - data += struct.pack(" MAX_ADVERT_DATA_SIZE: - data = data[:MAX_ADVERT_DATA_SIZE - 1] - if name: - name = data[8 if has_loc else 0:].decode("ascii", errors="replace") - - app_data = bytes([flags]) + data - node_label = NODE_TYPES.get(node_type, f"0x{node_type:02x}") - return app_data, {"flags": flags, "node_type": node_label, "location": loc, "name": name} - - -@st.composite -def valid_advert_packet(draw): - """Generate a complete valid ADVERT packet with expected decode.""" - route_type = draw(route_types_no_tc) - ver = draw(payload_vers) - hs, hops = draw(path_strategy()) - pubkey = draw(st.binary(min_size=PUB_KEY_SIZE, max_size=PUB_KEY_SIZE)) - timestamp = draw(st.integers(min_value=0, max_value=0xFFFFFFFF)) - signature = draw(st.binary(min_size=SIGNATURE_SIZE, max_size=SIGNATURE_SIZE)) - app_data, advert_expected = draw(advert_appdata()) - - hdr = bytes([mc_header(0x04, route_type, ver)]) - pl = bytes([path_len_byte(len(hops), hs)]) - path = b"".join(hops) - payload = pubkey + struct.pack("> 2) & 0x0F - - return packet, { - "route_type": ROUTE_TYPES.get(route_type, f"rt{route_type}"), - "payload_type": PAYLOAD_TYPES.get(payload_type, f"0x{payload_type:02x}"), - "error": error if len(packet) < 2 else error - } - - -# ── Generation ──────────────────────────────────────────────────── - -_counter = 0 - - -def save_case(packet: bytes, expected: dict, valid: bool, prefix: str): - global _counter - _counter += 1 - ptype = expected["payload_type"].lower() - name = f"{prefix}_{ptype}_{_counter:04d}" - - tags = [ptype, expected["route_type"]] - if not valid: - tags.append("invalid") - if "error" in expected: - tags.append(expected["error"]) - - case = { - "description": f"Generated {prefix}: {expected['payload_type']} {expected['route_type']}", - "tags": tags, - "packet_hex": packet.hex(), - "valid": valid, - "expected": expected, - } - - # Validate against schema - import jsonschema - schema = json.loads(SCHEMA_PATH.read_text()) - try: - jsonschema.validate(case, schema) - except jsonschema.ValidationError as e: - print(f" SKIP {name}: schema error: {e.message}") - return - - (OUT / f"{name}.json").write_text(json.dumps(case, indent=2, ensure_ascii=False) + "\n") - - -def generate(count: int): - OUT.mkdir(exist_ok=True) - - # Clear previous generated files - for f in OUT.glob("*.json"): - f.unlink() - - global _counter - _counter = 0 - - strategies = [ - ("valid", valid_advert_packet(), True), - ("valid", valid_ack_packet(), True), - ("valid", valid_encrypted_peer_packet(), True), - ("valid", valid_group_packet(), True), - ("invalid", invalid_packet(), False), - ] - - per_strategy = max(1, count // len(strategies)) - - for prefix, strategy, valid in strategies: - for i in range(per_strategy): - example = strategy.example() - packet, expected = example - save_case(packet, expected, valid, prefix) - - total = len(list(OUT.glob("*.json"))) - print(f"Generated {total} test vectors in {OUT}/") - - -def main(): - parser = argparse.ArgumentParser(description="Generate MeshCore test vectors") - parser.add_argument("--count", type=int, default=50, help="Total test cases to generate") - args = parser.parse_args() - generate(args.count) - - -if __name__ == "__main__": - main() diff --git a/examples/meshcore/corpus/hand/ack_basic.json b/examples/meshcore/corpus/hand/ack_basic.json deleted file mode 100644 index c99a7f36..00000000 --- a/examples/meshcore/corpus/hand/ack_basic.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "description": "ACK flood, simple acknowledgment", - "tags": [ - "ack", - "flood" - ], - "packet_hex": "0d00efbeadde", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ACK", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "ack_crc": 3735928559 - } - } -} diff --git a/examples/meshcore/corpus/hand/ack_with_path.json b/examples/meshcore/corpus/hand/ack_with_path.json deleted file mode 100644 index 70d6fe82..00000000 --- a/examples/meshcore/corpus/hand/ack_with_path.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "description": "ACK flood, 2 hops with 1-byte hashes", - "tags": [ - "ack", - "flood", - "path" - ], - "packet_hex": "0d02d1ac9fc28d00", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ACK", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [ - "d1", - "ac" - ] - }, - "payload": { - "ack_crc": 9290399 - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_basic.json b/examples/meshcore/corpus/hand/advert_basic.json deleted file mode 100644 index 87045681..00000000 --- a/examples/meshcore/corpus/hand/advert_basic.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "description": "ADVERT flood, chat node with simple ASCII name", - "tags": [ - "advert", - "flood", - "name", - "chat" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc6690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081546573744e6f6465", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 129, - "node_type": "chat", - "location": null, - "name": "TestNode" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_emoji_name.json b/examples/meshcore/corpus/hand/advert_emoji_name.json deleted file mode 100644 index b3b94cb7..00000000 --- a/examples/meshcore/corpus/hand/advert_emoji_name.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "description": "ADVERT flood, chat node with emoji in name (multi-byte UTF-8)", - "tags": [ - "advert", - "flood", - "emoji", - "name", - "utf8" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc6690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081f09fa68520526170746f72", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 129, - "node_type": "chat", - "location": null, - "name": "🦅 Raptor" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_feat_flags_as_name.json b/examples/meshcore/corpus/hand/advert_feat_flags_as_name.json deleted file mode 100644 index 9e06be63..00000000 --- a/examples/meshcore/corpus/hand/advert_feat_flags_as_name.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "description": "ADVERT flood, feat1+feat2 bits set but bytes are actually the name", - "tags": [ - "advert", - "flood", - "feat", - "scoring", - "emoji" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc66900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e1f09f8f94efb88f204d746e4e6f6465", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 225, - "node_type": "chat", - "location": null, - "name": "🏔️ MtnNode" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_location.json b/examples/meshcore/corpus/hand/advert_location.json deleted file mode 100644 index 43b6c159..00000000 --- a/examples/meshcore/corpus/hand/advert_location.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "description": "ADVERT flood, repeater with valid GPS coords and name", - "tags": [ - "advert", - "flood", - "location", - "repeater" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc6690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092346640023807b4f848696c6c746f7052707472", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 146, - "node_type": "repeater", - "location": { - "lat": 37.7749, - "lon": -122.4194 - }, - "name": "HilltopRptr" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_no_appdata.json b/examples/meshcore/corpus/hand/advert_no_appdata.json deleted file mode 100644 index 8aafcaed..00000000 --- a/examples/meshcore/corpus/hand/advert_no_appdata.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "description": "ADVERT flood, bare minimum (no app_data)", - "tags": [ - "advert", - "flood", - "minimal" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc66900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": null - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_sensor.json b/examples/meshcore/corpus/hand/advert_sensor.json deleted file mode 100644 index c5865534..00000000 --- a/examples/meshcore/corpus/hand/advert_sensor.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "description": "ADVERT flood, sensor node type", - "tags": [ - "advert", - "flood", - "sensor" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc669000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008454656d7053656e736f72", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 132, - "node_type": "sensor", - "location": null, - "name": "TempSensor" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_truncated.json b/examples/meshcore/corpus/hand/advert_truncated.json deleted file mode 100644 index 3f111509..00000000 --- a/examples/meshcore/corpus/hand/advert_truncated.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "description": "ADVERT flood, truncated mid-signature (50 bytes)", - "tags": [ - "invalid", - "advert", - "truncated" - ], - "packet_hex": "1100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc669000000000000000000000000", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - } - } -} diff --git a/examples/meshcore/corpus/hand/advert_with_path.json b/examples/meshcore/corpus/hand/advert_with_path.json deleted file mode 100644 index 4a20e68c..00000000 --- a/examples/meshcore/corpus/hand/advert_with_path.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ADVERT flood, 4 hops with 1-byte hashes", - "tags": [ - "advert", - "flood", - "path" - ], - "packet_hex": "11042a3d35ed000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f403fc669000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008152656c617965644e6f6465", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [ - "2a", - "3d", - "35", - "ed" - ] - }, - "payload": { - "pubkey_prefix": "00010203", - "timestamp": 1774600000, - "advert": { - "flags": 129, - "node_type": "chat", - "location": null, - "name": "RelayedNode" - } - } - } -} diff --git a/examples/meshcore/corpus/hand/anon_req_tc_fallback.json b/examples/meshcore/corpus/hand/anon_req_tc_fallback.json deleted file mode 100644 index 1d038318..00000000 --- a/examples/meshcore/corpus/hand/anon_req_tc_fallback.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "description": "ANON_REQ tdirect header but NO transport codes (older firmware)", - "tags": [ - "anon_req", - "tdirect", - "tc_fallback" - ], - "packet_hex": "1f03133542d9202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3fb2e2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "tdirect", - "payload_type": "ANON_REQ", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [ - "13", - "35", - "42" - ] - }, - "payload": { - "dst": "d9", - "ephem_prefix": "20212223", - "mac": "b2e2", - "ciphertext_len": 48 - } - } -} diff --git a/examples/meshcore/corpus/hand/bad_path_len.json b/examples/meshcore/corpus/hand/bad_path_len.json deleted file mode 100644 index 1b1c68b3..00000000 --- a/examples/meshcore/corpus/hand/bad_path_len.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "description": "TXT_MSG flood, path_len claims 33 hops (2B hash) but data is too short", - "tags": [ - "invalid", - "bad_framing", - "path" - ], - "packet_hex": "0961000000000000000000000000000000000000000000000000", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "TXT_MSG", - "error": "bad_framing" - } -} diff --git a/examples/meshcore/corpus/hand/bad_path_len_reserved_hash_size.json b/examples/meshcore/corpus/hand/bad_path_len_reserved_hash_size.json deleted file mode 100644 index f4f30d77..00000000 --- a/examples/meshcore/corpus/hand/bad_path_len_reserved_hash_size.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "TXT_MSG flood, path_len has reserved hash_size_code 3", - "tags": [ - "invalid", - "bad_framing", - "path", - "reserved_hash_size" - ], - "packet_hex": "09c1000000000000000000000000000000000000000000000000", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "TXT_MSG", - "error": "bad_framing" - } -} diff --git a/examples/meshcore/corpus/hand/control_discover_req.json b/examples/meshcore/corpus/hand/control_discover_req.json deleted file mode 100644 index cadc5791..00000000 --- a/examples/meshcore/corpus/hand/control_discover_req.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "description": "CONTROL flood, DISCOVER_REQ sub-type", - "tags": [ - "control", - "flood", - "discover" - ], - "packet_hex": "2d0080033412cdab", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "CONTROL", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "control_subtype": "DISCOVER_REQ" - } - } -} diff --git a/examples/meshcore/corpus/hand/empty_packet.json b/examples/meshcore/corpus/hand/empty_packet.json deleted file mode 100644 index e893db91..00000000 --- a/examples/meshcore/corpus/hand/empty_packet.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "Empty packet (0 bytes)", - "tags": [ - "invalid", - "too_short" - ], - "packet_hex": "", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "REQ", - "error": "too_short" - } -} diff --git a/examples/meshcore/corpus/hand/grp_txt_basic.json b/examples/meshcore/corpus/hand/grp_txt_basic.json deleted file mode 100644 index bf645987..00000000 --- a/examples/meshcore/corpus/hand/grp_txt_basic.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "description": "GRP_TXT flood, group text message", - "tags": [ - "grp_txt", - "flood", - "group", - "encrypted" - ], - "packet_hex": "1500ccabcd0000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "GRP_TXT", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "channel_hash": "cc", - "mac": "abcd", - "ciphertext_len": 32 - } - } -} diff --git a/examples/meshcore/corpus/hand/multipart_ack.json b/examples/meshcore/corpus/hand/multipart_ack.json deleted file mode 100644 index 4b1cec29..00000000 --- a/examples/meshcore/corpus/hand/multipart_ack.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "description": "MULTIPART flood, remaining=3, inner type=ACK", - "tags": [ - "multipart", - "flood", - "ack" - ], - "packet_hex": "290033bebafeca", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "MULTIPART", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "multipart_remaining": 3, - "multipart_inner": "ACK" - } - } -} diff --git a/examples/meshcore/corpus/hand/noise_packet.json b/examples/meshcore/corpus/hand/noise_packet.json deleted file mode 100644 index 8140c32e..00000000 --- a/examples/meshcore/corpus/hand/noise_packet.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "Random bytes, not a valid MeshCore packet", - "tags": [ - "invalid", - "noise" - ], - "packet_hex": "03d135839fc28d00", - "valid": false, - "expected": { - "route_type": "tdirect", - "payload_type": "REQ", - "error": "not_meshcore" - } -} diff --git a/examples/meshcore/corpus/hand/path_3byte_hash.json b/examples/meshcore/corpus/hand/path_3byte_hash.json deleted file mode 100644 index 386e4d2b..00000000 --- a/examples/meshcore/corpus/hand/path_3byte_hash.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "description": "PATH flood, 1 hop with 3-byte hash", - "tags": [ - "path", - "flood", - "3byte_hash", - "encrypted" - ], - "packet_hex": "21811afff3e6e92e4700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "PATH", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 3, - "hops": [ - "1afff3" - ] - }, - "payload": { - "dst": "e6", - "src": "e9", - "mac": "2e47", - "ciphertext_len": 46 - } - } -} diff --git a/examples/meshcore/corpus/hand/path_4byte_hash.json b/examples/meshcore/corpus/hand/path_4byte_hash.json deleted file mode 100644 index dd4a99cc..00000000 --- a/examples/meshcore/corpus/hand/path_4byte_hash.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "PATH flood, 4-byte hash (reserved hash_size_code 3, invalid)", - "tags": [ - "invalid", - "path", - "flood", - "4byte_hash", - "reserved_hash_size" - ], - "packet_hex": "21c1aabbccdde6e92e4700000000000000000000000000000000", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "PATH", - "error": "bad_framing" - } -} diff --git a/examples/meshcore/corpus/hand/req_transport_direct.json b/examples/meshcore/corpus/hand/req_transport_direct.json deleted file mode 100644 index 17772d48..00000000 --- a/examples/meshcore/corpus/hand/req_transport_direct.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "description": "REQ transport-direct with transport codes and 1-byte path", - "tags": [ - "req", - "tdirect", - "transport_codes", - "encrypted" - ], - "packet_hex": "033412785602ccdda1b2ef010000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "tdirect", - "payload_type": "REQ", - "payload_ver": 0, - "transport_codes": { - "tc1": 4660, - "tc2": 22136 - }, - "path": { - "hash_size": 1, - "hops": [ - "cc", - "dd" - ] - }, - "payload": { - "dst": "a1", - "src": "b2", - "mac": "ef01", - "ciphertext_len": 32 - } - } -} diff --git a/examples/meshcore/corpus/hand/response_corrupted_pathlen_1.json b/examples/meshcore/corpus/hand/response_corrupted_pathlen_1.json deleted file mode 100644 index bd188ef6..00000000 --- a/examples/meshcore/corpus/hand/response_corrupted_pathlen_1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "RESPONSE flood, bit-corrupted path_len (0xc7 should be small), LoRa bit error", - "tags": ["invalid", "response", "flood", "corrupted", "real_capture", "bit_error"], - "packet_hex": "05c735935ed17863f6ec7bbbd9bf8e5ea7d44b6a0e8f6318", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "RESPONSE", - "error": "bad_framing" - } -} diff --git a/examples/meshcore/corpus/hand/response_corrupted_pathlen_2.json b/examples/meshcore/corpus/hand/response_corrupted_pathlen_2.json deleted file mode 100644 index 111aa42d..00000000 --- a/examples/meshcore/corpus/hand/response_corrupted_pathlen_2.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "RESPONSE flood, bit-corrupted path_len (0x32 should be 0x02 — bits 4,5 flipped), LoRa bit error", - "tags": ["invalid", "response", "flood", "corrupted", "real_capture", "bit_error"], - "packet_hex": "0532af1a5bd17863f6ec7bbbd9bfbe5ea7d44b6a0e8f6318", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "RESPONSE", - "error": "bad_framing" - } -} diff --git a/examples/meshcore/corpus/hand/response_valid_1hop.json b/examples/meshcore/corpus/hand/response_valid_1hop.json deleted file mode 100644 index 5041e87b..00000000 --- a/examples/meshcore/corpus/hand/response_valid_1hop.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "RESPONSE flood, 1 hop (real capture — same message as corrupted variants)", - "tags": ["response", "flood", "path", "real_capture"], - "packet_hex": "0501ac5bd17863f6ec7bbbd9bf8e5ea7d44b6a0e8f6318", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "RESPONSE", - "payload_ver": 0, - "transport_codes": null, - "path": {"hash_size": 1, "hops": ["ac"]}, - "payload": {"dst": "5b", "src": "d1", "mac": "7863", "ciphertext_len": 16} - } -} diff --git a/examples/meshcore/corpus/hand/response_valid_2hop.json b/examples/meshcore/corpus/hand/response_valid_2hop.json deleted file mode 100644 index eb01f5cc..00000000 --- a/examples/meshcore/corpus/hand/response_valid_2hop.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "RESPONSE flood, 2 hops [ac>ea] (real capture — same message as corrupted variants)", - "tags": ["response", "flood", "path", "real_capture"], - "packet_hex": "0502acea5bd17863f6ec7bbbd9bf8e5ea7d44b6a0e8f6318", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "RESPONSE", - "payload_ver": 0, - "transport_codes": null, - "path": {"hash_size": 1, "hops": ["ac", "ea"]}, - "payload": {"dst": "5b", "src": "d1", "mac": "7863", "ciphertext_len": 16} - } -} diff --git a/examples/meshcore/corpus/hand/too_short_1byte.json b/examples/meshcore/corpus/hand/too_short_1byte.json deleted file mode 100644 index e9f72c7d..00000000 --- a/examples/meshcore/corpus/hand/too_short_1byte.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "Packet too short (1 byte)", - "tags": [ - "invalid", - "too_short" - ], - "packet_hex": "11", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "error": "too_short" - } -} diff --git a/examples/meshcore/corpus/hand/too_short_2bytes.json b/examples/meshcore/corpus/hand/too_short_2bytes.json deleted file mode 100644 index a80f66c6..00000000 --- a/examples/meshcore/corpus/hand/too_short_2bytes.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "description": "Packet too short (2 bytes, need >= 3)", - "tags": [ - "invalid", - "too_short" - ], - "packet_hex": "1100", - "valid": false, - "expected": { - "route_type": "flood", - "payload_type": "ADVERT", - "error": "too_short" - } -} diff --git a/examples/meshcore/corpus/hand/trace_4byte_hash.json b/examples/meshcore/corpus/hand/trace_4byte_hash.json deleted file mode 100644 index 00bc543e..00000000 --- a/examples/meshcore/corpus/hand/trace_4byte_hash.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "description": "TRACE flood with 4-byte trace hashes (flags=0x02, hash_size=4)", - "tags": [ - "trace", - "flood", - "4byte_hash" - ], - "packet_hex": "2500ddccbbaa4433221102deadbeefcafebabe", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "TRACE", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "trace_tag": 2864434397, - "trace_hops": 2 - } - } -} diff --git a/examples/meshcore/corpus/hand/trace_basic.json b/examples/meshcore/corpus/hand/trace_basic.json deleted file mode 100644 index a32e8a20..00000000 --- a/examples/meshcore/corpus/hand/trace_basic.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "description": "TRACE flood with tag, auth, and 2 trace hops", - "tags": [ - "trace", - "flood" - ], - "packet_hex": "250078563412ddccbbaa001122", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "TRACE", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 1, - "hops": [] - }, - "payload": { - "trace_tag": 305419896, - "trace_hops": 2 - } - } -} diff --git a/examples/meshcore/corpus/hand/txt_msg_direct_2b_hash.json b/examples/meshcore/corpus/hand/txt_msg_direct_2b_hash.json deleted file mode 100644 index c03b9fc9..00000000 --- a/examples/meshcore/corpus/hand/txt_msg_direct_2b_hash.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "description": "TXT_MSG direct, 2-byte hashes, 2 hops", - "tags": [ - "txt_msg", - "direct", - "2byte_hash", - "encrypted" - ], - "packet_hex": "0a42ac98d14d5b43abcd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "direct", - "payload_type": "TXT_MSG", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 2, - "hops": [ - "ac98", - "d14d" - ] - }, - "payload": { - "dst": "5b", - "src": "43", - "mac": "abcd", - "ciphertext_len": 48 - } - } -} diff --git a/examples/meshcore/corpus/hand/txt_msg_flood_with_loops.json b/examples/meshcore/corpus/hand/txt_msg_flood_with_loops.json deleted file mode 100644 index 15749c2e..00000000 --- a/examples/meshcore/corpus/hand/txt_msg_flood_with_loops.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "description": "TXT_MSG flood with routing loop (ac98 appears 3x)", - "tags": [ - "txt_msg", - "flood", - "loop", - "2byte_hash" - ], - "packet_hex": "0945ac98d14dac98ff01ac985b43abcd0000000000000000000000000000000000000000000000000000000000000000", - "valid": true, - "expected": { - "route_type": "flood", - "payload_type": "TXT_MSG", - "payload_ver": 0, - "transport_codes": null, - "path": { - "hash_size": 2, - "hops": [ - "ac98", - "d14d", - "ac98", - "ff01", - "ac98" - ] - }, - "payload": { - "dst": "5b", - "src": "43", - "mac": "abcd", - "ciphertext_len": 32 - } - } -} diff --git a/examples/meshcore/corpus/schema.json b/examples/meshcore/corpus/schema.json deleted file mode 100644 index add8bb8d..00000000 --- a/examples/meshcore/corpus/schema.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MeshCore Packet Test Case", - "description": "A single test vector for MeshCore packet decoders", - "type": "object", - "required": ["description", "tags", "packet_hex", "valid", "expected"], - "additionalProperties": false, - "properties": { - "description": { - "type": "string", - "minLength": 1 - }, - "tags": { - "type": "array", - "items": { "type": "string" }, - "minItems": 1 - }, - "packet_hex": { - "type": "string", - "pattern": "^[0-9a-f]*$", - "description": "Raw MeshCore packet as lowercase hex (no 0x prefix)" - }, - "valid": { - "type": "boolean", - "description": "Whether this packet should parse successfully" - }, - "expected": { - "type": "object", - "required": ["route_type", "payload_type"], - "properties": { - "route_type": { - "type": "string", - "enum": ["flood", "direct", "tflood", "tdirect"] - }, - "payload_type": { - "type": "string", - "enum": [ - "REQ", "RESPONSE", "TXT_MSG", "ACK", "ADVERT", - "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", - "TRACE", "MULTIPART", "CONTROL", "RAW_CUSTOM" - ] - }, - "payload_ver": { "type": "integer", "minimum": 0, "maximum": 3 }, - "transport_codes": { - "oneOf": [ - { "type": "null" }, - { - "type": "object", - "required": ["tc1", "tc2"], - "properties": { - "tc1": { "type": "integer" }, - "tc2": { "type": "integer" } - }, - "additionalProperties": false - } - ] - }, - "path": { - "type": "object", - "required": ["hash_size", "hops"], - "properties": { - "hash_size": { "type": "integer", "minimum": 1, "maximum": 4 }, - "hops": { - "type": "array", - "items": { "type": "string", "pattern": "^[0-9a-f]+$" } - } - }, - "additionalProperties": false - }, - "error": { - "type": "string", - "enum": ["too_short", "not_meshcore", "bad_framing"], - "description": "Expected error category for invalid packets" - }, - "payload": { - "type": "object", - "description": "Payload-type-specific decoded fields", - "properties": { - "dst": { "type": "string", "pattern": "^[0-9a-f]{2}$" }, - "src": { "type": "string", "pattern": "^[0-9a-f]{2}$" }, - "mac": { "type": "string", "pattern": "^[0-9a-f]+$" }, - "ciphertext_len": { "type": "integer", "minimum": 0 }, - "channel_hash": { "type": "string", "pattern": "^[0-9a-f]{2}$" }, - "ack_crc": { "type": "integer" }, - "pubkey_prefix": { "type": "string", "pattern": "^[0-9a-f]+$" }, - "timestamp": { "type": "integer" }, - "advert": { - "oneOf": [ - { "type": "null" }, - { - "type": "object", - "properties": { - "flags": { "type": "integer", "minimum": 0, "maximum": 255 }, - "node_type": { "type": "string" }, - "location": { - "oneOf": [ - { "type": "null" }, - { - "type": "object", - "required": ["lat", "lon"], - "properties": { - "lat": { "type": "number" }, - "lon": { "type": "number" } - }, - "additionalProperties": false - } - ] - }, - "name": { "oneOf": [{ "type": "string" }, { "type": "null" }] } - }, - "additionalProperties": false - } - ] - }, - "ephem_prefix": { "type": "string", "pattern": "^[0-9a-f]+$" }, - "trace_tag": { "type": "integer" }, - "trace_hops": { "type": "integer" }, - "multipart_remaining": { "type": "integer" }, - "multipart_inner": { "type": "string" }, - "control_subtype": { "type": "string" } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - } -} diff --git a/examples/meshcore/corpus/validate.py b/examples/meshcore/corpus/validate.py deleted file mode 100755 index 0308a561..00000000 --- a/examples/meshcore/corpus/validate.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python3 -"""Validate MeshCore test corpus against schema and decoder.""" -# /// script -# requires-python = ">=3.10" -# dependencies = ["jsonschema", "cobs", "pyserial", "pycryptodome"] -# /// - -import json -import re -import sys -from pathlib import Path - -import jsonschema - -# Add meshcore/ directory to path so we can import the decoder -sys.path.insert(0, str(Path(__file__).parent.parent)) -from meshcore_rx import decode_meshcore_packet # noqa: E402 - -CORPUS_DIR = Path(__file__).parent -SCHEMA_PATH = CORPUS_DIR / "schema.json" - -# ANSI escape stripper -_ANSI_RE = re.compile(r"\033\[[0-9;]*m") - - -def strip_ansi(s: str) -> str: - return _ANSI_RE.sub("", s) - - -def load_schema() -> dict: - return json.loads(SCHEMA_PATH.read_text()) - - -def find_test_cases() -> list[Path]: - cases = [] - for d in ["hand", "generated"]: - p = CORPUS_DIR / d - if p.is_dir(): - cases.extend(sorted(p.glob("*.json"))) - return cases - - -def validate_schema(case: dict, schema: dict, path: Path) -> list[str]: - """Validate a test case against the JSON schema. Returns list of errors.""" - errors = [] - try: - jsonschema.validate(case, schema) - except jsonschema.ValidationError as e: - errors.append(f"Schema: {e.message}") - return errors - - -def validate_decode(case: dict, path: Path) -> list[str]: - """Run the decoder and check output against expected fields.""" - errors = [] - packet = bytes.fromhex(case["packet_hex"]) if case["packet_hex"] else b"" - expected = case["expected"] - - # Run decoder — must not crash - try: - raw_output, _warnings = decode_meshcore_packet(packet) - except Exception as e: - errors.append(f"Decoder crashed: {e}") - return errors - - output = strip_ansi(raw_output) - - if case["valid"]: - # Valid packet: should contain the expected payload type name - ptype = expected["payload_type"] - if ptype not in output: - errors.append(f"Expected '{ptype}' in output, got: {output[:120]}") - - # Check route type - rtype = expected["route_type"] - if rtype not in output: - errors.append(f"Expected route '{rtype}' in output, got: {output[:120]}") - - # Check path hops if specified - if "path" in expected: - exp_path = expected["path"] - for hop in exp_path["hops"]: - if hop not in output: - errors.append(f"Expected hop '{hop}' in output") - - # Check payload-specific fields - payload = expected.get("payload") - if payload: - _check_payload(payload, output, errors) - - # Should NOT contain error markers - for marker in ["", "", ""]: - if marker in output: - errors.append(f"Valid packet produced error marker: {marker}") - - else: - # Invalid packet: must not crash. Error marker check is best-effort - # (random mutations may accidentally produce parseable packets). - pass # decoder didn't crash — that's the key requirement - - return errors - - -def _check_payload(payload: dict, output: str, errors: list[str]): - """Check payload-specific fields in the decoder output.""" - if "ack_crc" in payload: - crc_hex = f"0x{payload['ack_crc']:08x}" - if crc_hex not in output: - errors.append(f"Expected CRC '{crc_hex}' in output") - - if "dst" in payload: - if payload["dst"] not in output: - errors.append(f"Expected dst='{payload['dst']}' in output") - - if "src" in payload: - if payload["src"] not in output: - errors.append(f"Expected src='{payload['src']}' in output") - - if "mac" in payload: - if payload["mac"] not in output: - errors.append(f"Expected mac='{payload['mac']}' in output") - - if "channel_hash" in payload: - if payload["channel_hash"] not in output: - errors.append(f"Expected ch='{payload['channel_hash']}' in output") - - if "ciphertext_len" in payload: - ct_str = f"[{payload['ciphertext_len']}B]" - if ct_str not in output: - errors.append(f"Expected '{ct_str}' in output") - - if "pubkey_prefix" in payload: - pk = payload["pubkey_prefix"] - if pk not in output: - errors.append(f"Expected pubkey prefix '{pk}' in output") - - if "advert" in payload and payload["advert"] is not None: - advert = payload["advert"] - if "name" in advert and advert["name"] is not None: - if advert["name"] not in output: - errors.append(f"Expected name '{advert['name']}' in output") - if "node_type" in advert: - if advert["node_type"] not in output: - errors.append(f"Expected node_type '{advert['node_type']}' in output") - if "location" in advert and advert["location"] is not None: - loc = advert["location"] - lat_str = f"{loc['lat']:.4f}" - if lat_str not in output: - errors.append(f"Expected lat '{lat_str}' in output") - - if "ephem_prefix" in payload: - ep = payload["ephem_prefix"] - if ep not in output: - errors.append(f"Expected ephemeral prefix '{ep}' in output") - - if "multipart_remaining" in payload: - r = payload["multipart_remaining"] - if f"remaining={r}" not in output.replace(" ", ""): - errors.append(f"Expected remaining={r} in output") - - if "multipart_inner" in payload: - inner = payload["multipart_inner"] - if inner not in output: - errors.append(f"Expected inner type '{inner}' in output") - - if "control_subtype" in payload: - sub = payload["control_subtype"] - if sub not in output: - errors.append(f"Expected control subtype '{sub}' in output") - - -def main(): - schema = load_schema() - cases = find_test_cases() - - if not cases: - print("No test cases found!") - sys.exit(1) - - passed = 0 - failed = 0 - total_errors: list[tuple[str, list[str]]] = [] - - for path in cases: - case = json.loads(path.read_text()) - name = f"{path.parent.name}/{path.stem}" - - errors = validate_schema(case, schema, path) - errors.extend(validate_decode(case, path)) - - if errors: - failed += 1 - total_errors.append((name, errors)) - print(f" \033[1;31mFAIL\033[0m {name}") - for e in errors: - print(f" {e}") - else: - passed += 1 - print(f" \033[32mPASS\033[0m {name}") - - print() - print(f"\033[1m{passed + failed} tests: {passed} passed, {failed} failed\033[0m") - sys.exit(1 if failed else 0) - - -if __name__ == "__main__": - main() diff --git a/examples/meshcore/meshcore_rx.py b/examples/meshcore/meshcore_rx.py deleted file mode 100755 index e656d871..00000000 --- a/examples/meshcore/meshcore_rx.py +++ /dev/null @@ -1,931 +0,0 @@ -#!/usr/bin/env python3 -"""DongLoRa MeshCore receiver: decode and display MeshCore packets.""" -import base64 -import csv -import donglora as dl -import hashlib -import hmac -import json -import struct -import serial -import sys -import tempfile -import time -from Crypto.Cipher import AES -from datetime import datetime, timezone -from pathlib import Path - - -# ── ANSI colors ─────────────────────────────────────────────────── - -RST = "\033[0m" -BOLD = "\033[1m" -DIM = "\033[2m" -RED = "\033[31m" -GRN = "\033[32m" -YEL = "\033[33m" -BLU = "\033[34m" -MAG = "\033[35m" -CYN = "\033[36m" -WHT = "\033[37m" -BRED = "\033[1;31m" -BGRN = "\033[1;32m" -BYEL = "\033[1;33m" -BBLU = "\033[1;34m" -BMAG = "\033[1;35m" -BCYN = "\033[1;36m" -BWHT = "\033[1;37m" - -TYPE_COLORS = { - "ADVERT": BGRN, - "ACK": DIM, - "TXT_MSG": BCYN, - "REQ": BYEL, - "RESPONSE": BYEL, - "GRP_TXT": BMAG, - "GRP_DATA": BMAG, - "ANON_REQ": YEL, - "PATH": BBLU, - "TRACE": BLU, - "MULTIPART": YEL, - "CONTROL": BLU, - "RAW_CUSTOM": DIM, -} - - -def _rssi_color(rssi: int) -> str: - if rssi >= -70: - return GRN - if rssi >= -100: - return YEL - return RED - - -def _snr_color(snr: int) -> str: - if snr >= 0: - return GRN - if snr >= -10: - return YEL - return RED - - -# ── MeshCore channel crypto ──────────────────────────────────────── - - -def _channel_secret_from_hashtag(name: str) -> bytes: - """Derive 32-byte secret from a hashtag channel name (e.g., '#test').""" - h = hashlib.sha256(name.encode()).digest() - return h[:16] + b"\x00" * 16 - - -def _channel_secret_from_psk(psk_b64: str) -> bytes: - """Decode a base64 PSK into a 32-byte secret (16-byte key + 16 zero bytes).""" - key = base64.b64decode(psk_b64) - return key[:16] + b"\x00" * 16 - - -def _channel_hash(secret: bytes) -> int: - """Compute the 1-byte channel hash from a 32-byte secret.""" - # Hash over the key portion (16 bytes for hashtag/PSK channels) - key_len = 16 if secret[16:] == b"\x00" * 16 else 32 - return hashlib.sha256(secret[:key_len]).digest()[0] - - -def _grp_verify_and_decrypt( - secret: bytes, mac_bytes: bytes, ciphertext: bytes -) -> bytes | None: - """Verify MAC then decrypt a GRP_TXT/GRP_DATA payload. Returns plaintext or None.""" - if not ciphertext or len(ciphertext) % 16 != 0: - return None - # HMAC-SHA256 over ciphertext, truncated to 2 bytes - key_len = 16 if secret[16:] == b"\x00" * 16 else 32 - computed_mac = hmac.new(secret[:key_len], ciphertext, hashlib.sha256).digest()[:2] - if computed_mac != mac_bytes: - return None - # AES-128-ECB decrypt - cipher = AES.new(secret[:16], AES.MODE_ECB) - plaintext = b"" - for i in range(0, len(ciphertext), 16): - plaintext += cipher.decrypt(ciphertext[i : i + 16]) - return plaintext - - -def _parse_grp_plaintext(plaintext: bytes) -> tuple[int, str] | None: - """Parse decrypted GRP_TXT: timestamp(4) + flags(1) + text. Returns (timestamp, text).""" - if len(plaintext) < 5: - return None - timestamp = struct.unpack_from(" bytes: - """Build an encrypted GRP_TXT payload (channel_hash + mac + ciphertext).""" - ch = _channel_hash(secret) - # Plaintext: timestamp(4) + flags(1) + "sender: text\0" + zero-pad - plaintext = struct.pack(" bytes: - """Wrap encrypted GRP_TXT payload into a full MeshCore packet.""" - header = bytes([0x15]) # GRP_TXT flood: (5 << 2) | 1 - path_len = bytes([0x40]) # 0 hops, 2-byte hash mode (hash_size_code=1 << 6) - return header + path_len + channel_payload - - -def _grp_transmit(ser, channel_name: str, sender: str, text: str): - """Encrypt and transmit a GRP_TXT message, then resume RX.""" - secret = _KNOWN_CHANNELS.get(channel_name) - if secret is None: - return - payload = _grp_encrypt(secret, sender, text) - packet = _grp_build_packet(payload) - try: - send_cmd(ser, "Transmit", quiet=True, payload=packet) - except Exception: - pass - # Always try to resume RX, even if TX failed - try: - send_cmd(ser, "StartRx", quiet=True) - except Exception: - pass - - -# ── Loop/collision aggregator ───────────────────────────────────── - -REPORT_CHANNEL = "#watchman" -REPORT_SENDER = "Watchman" -REPORT_WINDOW = 60 # seconds — collect events before reporting -REPORT_COOLDOWN = 3600 # seconds — suppress repeat reports for same issue - - -def _nodes_for_hash(hash_hex: str, hash_size: int) -> int: - """Count how many known nodes share this path hash.""" - return sum(1 for pk in _known_nodes if pk[: hash_size * 2] == hash_hex) - - -class LoopAggregator: - def __init__(self): - self._pending: list[dict] = [] - self._cooldown: dict[str, float] = {} - self._window_start = time.monotonic() - - def record(self, dupes: dict[str, int], hash_size: int, all_hops: list[str]): - """Record duplicate hops from one packet.""" - first_hop = all_hops[0] if all_hops else None - self._pending.append( - { - "dupes": dupes, - "hash_size": hash_size, - "hops": all_hops, - "first_hop": first_hop, - } - ) - - def maybe_report(self, ser) -> bool: - if not self._pending: - self._window_start = time.monotonic() - return False - if time.monotonic() - self._window_start < REPORT_WINDOW: - return False - self._send_report(ser) - return True - - def _send_report(self, ser): - now = time.monotonic() - messages: list[str] = [] - - # Aggregate: (hash, hash_size) → {packets, relayers, senders} - agg: dict[tuple[str, int], dict] = {} - for ev in self._pending: - for h, count in ev["dupes"].items(): - key = (h, ev["hash_size"]) - if key not in agg: - agg[key] = {"packets": 0, "relayers": set(), "senders": set()} - agg[key]["packets"] += 1 - if ev["first_hop"]: - agg[key]["senders"].add(ev["first_hop"]) - for hop in ev["hops"]: - if hop != h: - agg[key]["relayers"].add(hop) - - for (h, hs), info in agg.items(): - issue_key = f"{h}:{hs}" - if ( - issue_key in self._cooldown - and now - self._cooldown[issue_key] < REPORT_COOLDOWN - ): - continue - - n_known = _nodes_for_hash(h, hs) - pkts = info["packets"] - senders = sorted(info["senders"]) - sender_str = ",".join(senders[:3]) if senders else "?" - if len(senders) > 3: - sender_str += f"+{len(senders) - 3}" - relayer_str = ",".join(sorted(info["relayers"])[:3]) - if len(info["relayers"]) > 3: - relayer_str += f"+{len(info['relayers']) - 3}" - - if n_known == 1: - # Exactly one node matches → confirmed loop - msg = f"Loop: {h} re-sent its own traffic ({pkts} pkt)." - if relayer_str: - msg += f" Relayed by {relayer_str}." - messages.append(msg) - elif hs >= 2: - if n_known > 1: - # 2B+ hash collision — rare, definitely a routing issue - msg = ( - f"Routing conflict: {n_known} nodes share {hs}B " - f"hash {h} ({pkts} pkt)." - ) - messages.append(msg) - else: - # 2B+ duplicate, unknown node → almost certainly a loop - msg = f"Probable loop: {h} appeared {pkts}x." - if relayer_str: - msg += f" Relayed by {relayer_str}." - messages.append(msg) - else: - # 1B hash — can't distinguish loop from collision - if n_known > 1: - msg = ( - f"1B hash {h} duplicated in {pkts} pkt " - f"({n_known} known nodes share it). " - f"Sender {sender_str}: use 2B routing!" - ) - messages.append(msg) - else: - msg = ( - f"1B hash {h} duplicated in {pkts} pkt. " - f"Sender {sender_str}: use 2B routing to clarify!" - ) - messages.append(msg) - - self._cooldown[issue_key] = now - - if not messages: - self._reset() - return - - full = " | ".join(messages) - max_len = MAX_GRP_TEXT - len(REPORT_SENDER) - 2 - if len(full) > max_len: - full = full[: max_len - 5] + "+more" - - print(f" {DIM}>>>{RST} {BYEL}Report → {REPORT_CHANNEL}:{RST} {full}") - _grp_transmit(ser, REPORT_CHANNEL, REPORT_SENDER, full) - self._reset() - - def _reset(self): - self._pending.clear() - self._window_start = time.monotonic() - - -_aggregator = LoopAggregator() - - - - - -# ── MeshCore packet decoder ────────────────────────────────────── - -ROUTE_NAMES = {0: "tflood", 1: "flood", 2: "direct", 3: "tdirect"} - -PAYLOAD_NAMES = { - 0x00: "REQ", - 0x01: "RESPONSE", - 0x02: "TXT_MSG", - 0x03: "ACK", - 0x04: "ADVERT", - 0x05: "GRP_TXT", - 0x06: "GRP_DATA", - 0x07: "ANON_REQ", - 0x08: "PATH", - 0x09: "TRACE", - 0x0A: "MULTIPART", - 0x0B: "CONTROL", - 0x0F: "RAW_CUSTOM", -} - -NODE_TYPES = {0x00: "none", 0x01: "chat", 0x02: "repeater", 0x03: "room", 0x04: "sensor"} - -MAX_PATH_SIZE = 64 - -# ── Node registry (persistent, pruned at 12h) ──────────────────── - -ADVERT_MAX_AGE = 12 * 3600 # seconds -_ADVERT_FILE = Path(tempfile.gettempdir()) / "donglora_adverts.json" - -# In-memory: pubkey_hex → {"name": str, "seen": float (unix timestamp)} -_known_nodes: dict[str, dict] = {} - - -def _load_adverts(): - """Load persisted adverts from disk, pruning stale entries.""" - global _known_nodes - try: - data = json.loads(_ADVERT_FILE.read_text()) - cutoff = time.time() - ADVERT_MAX_AGE - _known_nodes = {k: v for k, v in data.items() if v.get("seen", 0) > cutoff} - except (FileNotFoundError, json.JSONDecodeError, ValueError): - _known_nodes = {} - - -def _save_adverts(): - """Persist current adverts to disk (atomic write).""" - try: - tmp = _ADVERT_FILE.with_suffix(".tmp") - tmp.write_text(json.dumps(_known_nodes)) - tmp.rename(_ADVERT_FILE) - except OSError: - pass - - -def _lookup_hash(hash_hex: str, hash_size: int) -> list[str]: - """Find node names whose pubkey prefix matches a path hash.""" - return [ - v["name"] - for pk_hex, v in _known_nodes.items() - if pk_hex[: hash_size * 2] == hash_hex - ] - - -def _register_node(pubkey: bytes, name: str): - """Register or update a node from an ADVERT.""" - pk_hex = pubkey.hex() - _known_nodes[pk_hex] = {"name": name, "seen": time.time()} - _save_adverts() - - -def _utf8_score(data: bytes) -> int: - """Score how cleanly data decodes as UTF-8. Higher = more replacement chars.""" - return data.decode("utf-8", errors="replace").count("\ufffd") - - -def _best_name(candidates: list[bytes]) -> bytes: - """Pick the candidate with the fewest UTF-8 replacement chars; longest wins ties.""" - return min(candidates, key=lambda d: (_utf8_score(d), -len(d))) - - -def _decode_advert_appdata(app_data: bytes) -> tuple[str, str, list[str]]: - """Decode advert app_data. Returns (display_string, node_name, quality_warnings).""" - if not app_data: - return "", "", [] - flags = app_data[0] - node_type = flags & 0x0F - node_label = NODE_TYPES.get(node_type, f"0x{node_type:02x}") - parts = [f"{DIM}flags={RST}{flags:02x} {DIM}node={RST}{node_label}"] - warnings: list[str] = [] - - if node_type > 4: - warnings.append("bad node_type") - - # Parse location if flagged and coords are valid - off = 1 - loc_str = "" - if flags & 0x10 and off + 8 <= len(app_data): - lat_raw = struct.unpack_from(" str | None: - """Check for duplicate hops in a path. Returns a warning line or None.""" - if len(hops) < 2: - return None - counts: dict[str, int] = {} - for h in hops: - counts[h] = counts.get(h, 0) + 1 - dupes = {h: c for h, c in counts.items() if c > 1} - if not dupes: - return None - - # Feed the aggregator for deferred reporting - _aggregator.record(dupes, hash_size, hops) - - parts = [] - for h, c in dupes.items(): - n_known = _nodes_for_hash(h, hash_size) - if n_known == 1: - # Exactly one node matches → confirmed loop - parts.append(f"{BRED}{h} x{c} LOOP{RST}") - elif hash_size >= 2: - if n_known > 1: - # 2B+ collision is rare and real - parts.append(f"{BCYN}{h} x{c} COLLISION {DIM}({n_known} nodes){RST}") - else: - # 2B+ duplicate, unknown node → almost certainly a loop - parts.append(f"{BRED}{h} x{c} LOOP{RST}") - else: - # 1B hash — ambiguous: could be loop or collision - if n_known > 1: - parts.append( - f"{YEL}{h} x{c} {DIM}(loop or collision? {n_known} nodes share this 1B hash){RST}" - ) - else: - parts.append(f"{YEL}{h} x{c} {DIM}(loop or collision? 1B hash){RST}") - - return f"{LOOP_INDENT}\u26a0 {', '.join(parts)}" - - -def _try_decode_body( - data: bytes, - pos: int, - type_name: str, - type_color: str, - payload_type: int, - payload_ver: int, - route_name: str, - tc_str: str, -) -> tuple[str | None, list[str]]: - """Try to parse path + payload starting at pos. Returns (decoded_string, warnings) or (None, []).""" - if pos >= len(data): - return None, [] - path_len_byte = data[pos] - pos += 1 - hash_size_code = path_len_byte >> 6 - if hash_size_code == 3: - return None, [] # reserved hash size code per spec Section 3 - hash_size = hash_size_code + 1 - hop_count = path_len_byte & 0x3F - - path_bytes = hop_count * hash_size - if path_bytes > MAX_PATH_SIZE or pos + path_bytes > len(data): - return None, [] - - hops_str = "" - loop_warn = "" - hops: list[str] = [] - if hop_count > 0: - for i in range(hop_count): - h = data[pos + i * hash_size : pos + (i + 1) * hash_size] - hops.append(h.hex()) - hops_str = f" {DIM}path=[{RST}{'>'.join(hops)}{DIM}]{RST}" - warn = _detect_loops(hops, hash_size) - if warn: - loop_warn = f"\n{warn}" - pos += path_bytes - - payload = data[pos:] - if not payload or len(payload) > 184: - return None, [] - prefix = f"{type_color}{type_name}{RST} {route_name}{tc_str}{hops_str}" - - warnings: list[str] = [] - if payload_type == 0x04: - result, warnings = _decode_advert(prefix, payload) - elif payload_type == 0x03: - result = _decode_ack(prefix, payload) - elif payload_type in (0x00, 0x01, 0x02, 0x08): - result = _decode_encrypted_peer(prefix, type_name, payload) - elif payload_type in (0x05, 0x06): - result = _decode_encrypted_group(prefix, payload) - elif payload_type == 0x07: - result = _decode_anon_req(prefix, payload) - elif payload_type == 0x09: - result = _decode_trace(prefix, payload) - elif payload_type == 0x0A: - result = _decode_multipart(prefix, payload) - elif payload_type == 0x0B: - result = _decode_control(prefix, payload) - else: - result = f"{prefix} {DIM}{payload.hex()}{RST}" if payload else prefix - - return result + loop_warn, warnings - - -def decode_meshcore_packet(data: bytes) -> tuple[str, list[str]]: - """Decode a MeshCore packet. Returns (display_string, quality_warnings).""" - if len(data) < 3: - return f"{RED}{RST} {DIM}{data.hex()}{RST}", [] - - header = data[0] - if header == 0xFF: - return f"{DIM} {data[1:].hex()}{RST}", [] - - route_type = header & 0x03 - payload_type = (header >> 2) & 0x0F - payload_ver = (header >> 6) & 0x03 - - # MeshCore only implements PAYLOAD_VER_1 (0x00) — versions 1-3 are reserved - # and rejected by real nodes. Packets with ver > 0 are noise or non-MeshCore. - if payload_ver != 0: - return ( - f"{RED}{RST} {DIM}hdr=0x{header:02x} " - f"(ver={payload_ver} — only v0 exists) {data[1:].hex()}{RST}", - [], - ) - - route_name = ROUTE_NAMES.get(route_type, f"rt{route_type}") - type_name = PAYLOAD_NAMES.get(payload_type, f"0x{payload_type:02x}") - type_color = TYPE_COLORS.get(type_name, WHT) - - has_tc = route_type in (0, 3) - - # Try with transport codes first if indicated, fallback without - if has_tc and len(data) >= 6: - tc1, tc2 = struct.unpack_from("{RST} {type_color}{type_name}{RST} " - f"{route_name} {DIM}{data[1:].hex()}{RST}", - [], - ) - - -def _decode_advert(prefix: str, payload: bytes) -> tuple[str, list[str]]: - if len(payload) < 100: - return f"{prefix} {RED}=100>{RST} {DIM}{payload.hex()}{RST}", [] - pubkey = payload[0:32] - timestamp = struct.unpack_from(" time.time() + 3600: - warnings.append("future ts") - except (OSError, OverflowError, ValueError): - ts = f"epoch={timestamp}" - warnings.append("bad ts") - app_str, name, app_warnings = _decode_advert_appdata(app_data) - warnings.extend(app_warnings) - if not warnings: - _register_node(pubkey, name) - return f"{prefix} {DIM}pub={RST}{pubkey[:4].hex()}.. {DIM}ts={RST}{ts}Z {app_str}", warnings - - -def _decode_ack(prefix: str, payload: bytes) -> str: - if len(payload) < 4: - return ( - f"{prefix} {RED}{RST} {DIM}{payload.hex()}{RST}" - ) - crc = struct.unpack_from(" str: - if len(payload) < 20: - return f"{prefix} {RED}{RST} {DIM}{payload.hex()}{RST}" - dst = payload[0] - src = payload[1] - mac = payload[2:4] - ct_len = len(payload) - 4 - return f"{prefix} {DIM}dst={RST}{dst:02x} {DIM}src={RST}{src:02x} {DIM}mac={RST}{mac.hex()} {DIM}[{ct_len}B]{RST}" - - -def _decode_encrypted_group(prefix: str, payload: bytes) -> str: - if len(payload) < 19: - return ( - f"{prefix} {RED}{RST} {DIM}{payload.hex()}{RST}" - ) - ch = payload[0] - mac_bytes = payload[1:3] - ciphertext = payload[3:] - - # Try to decrypt with known channels matching this hash - candidates = _CHANNEL_BY_HASH.get(ch, []) - for chan_name, secret in candidates: - plaintext = _grp_verify_and_decrypt(secret, mac_bytes, ciphertext) - if plaintext is not None: - parsed = _parse_grp_plaintext(plaintext) - if parsed: - ts, text = parsed - try: - ts_str = datetime.fromtimestamp(ts, tz=timezone.utc).strftime( - "%H:%M:%S" - ) - except (OSError, OverflowError, ValueError): - ts_str = f"epoch={ts}" - return ( - f"{prefix} {BGRN}{chan_name}{RST} " - f"{DIM}ts={RST}{ts_str}Z {BWHT}{text}{RST}" - ) - # MAC matched but plaintext unparseable - return ( - f"{prefix} {BGRN}{chan_name}{RST} {DIM}[decrypted but unparseable]{RST}" - ) - - # No known channel matched — show raw info - return ( - f"{prefix} {DIM}ch={RST}{ch:02x} {DIM}mac={RST}{mac_bytes.hex()} " - f"{DIM}[{len(ciphertext)}B]{RST}" - ) - - -def _decode_anon_req(prefix: str, payload: bytes) -> str: - if len(payload) < 51: - return f"{prefix} {RED}{RST} {DIM}{payload.hex()}{RST}" - dst = payload[0] - ephem_pub = payload[1:33] - mac = payload[33:35] - ct_len = len(payload) - 35 - return f"{prefix} {DIM}dst={RST}{dst:02x} {DIM}ephem={RST}{ephem_pub[:4].hex()}.. {DIM}mac={RST}{mac.hex()} {DIM}[{ct_len}B]{RST}" - - -def _decode_trace(prefix: str, payload: bytes) -> str: - if len(payload) < 9: - return ( - f"{prefix} {RED}{RST} {DIM}{payload.hex()}{RST}" - ) - tag = struct.unpack_from("'.join(hops)}{DIM}]{RST}" if hops else "" - return f"{prefix} {DIM}tag={RST}0x{tag:08x}{hops_str}" - - -def _decode_multipart(prefix: str, payload: bytes) -> str: - if len(payload) < 2: - return f"{prefix} {RED}{RST}" - remaining = (payload[0] >> 4) & 0x0F - inner_type = payload[0] & 0x0F - inner_name = PAYLOAD_NAMES.get(inner_type, f"0x{inner_type:02x}") - return f"{prefix} {DIM}remaining={RST}{remaining} {DIM}inner={RST}{inner_name} {DIM}[{len(payload) - 1}B]{RST}" - - -def _decode_control(prefix: str, payload: bytes) -> str: - if len(payload) < 1: - return f"{prefix} {RED}{RST}" - sub_type = (payload[0] >> 4) & 0x0F - sub_names = {0x8: "DISCOVER_REQ", 0x9: "DISCOVER_RESP"} - sub_name = sub_names.get(sub_type, f"sub=0x{sub_type:x}") - if sub_type == 0x8 and len(payload) >= 6: - type_filter = payload[1] - tag = struct.unpack_from("= 6: - node_type = payload[0] & 0x0F - snr_raw = struct.unpack_from(" str: - """Format a response dict for themed display.""" - t = resp["type"] - if t == "Pong": - return "Pong" - if t == "Ok": - return "Ok" - if t == "TxDone": - return "TxDone" - if t == "Config": - return f"Config {DIM}{resp.get('raw', '')}{RST}" - if t == "Error": - return f"{RED}Error (code {resp.get('code', '?')}){RST}" - return t - - -def send_cmd(ser, cmd_name: str, label: str = "", quiet: bool = False, **kwargs) -> dict | None: - frame = dl.cobs_encode(dl.encode_command(cmd_name, **kwargs)) - if not quiet: - print(f" {DIM}>>>{RST} {label}") - ser.write(frame) - ser.flush() - resp_data = dl.read_frame(ser) - if resp_data is None: - if not quiet: - print(f" {DIM}<<<{RST} {YEL}timeout{RST}") - return None - resp = dl.decode_response(resp_data) - if not quiet: - print(f" {DIM}<<<{RST} {_format_response(resp)}") - return resp - - -RADIO_CONFIG = { - "freq_hz": 910_525_000, - "bw": 6, # Khz62 = variant index 6 - "sf": 7, - "cr": 5, # CR 4/5 — denominator value - "sync_word": 0x3444, - "tx_power_dbm": -128, # TX_POWER_MAX: use board's maximum - "preamble_len": 0, # firmware default (16) - "cad": 1, # CAD on -} - - -def _format_rssi_snr(rssi: int, snr: int) -> str: - rc = _rssi_color(rssi) - sc = _snr_color(snr) - line = f"{rc}RSSI:{rssi:4d}dBm{RST} {sc}SNR:{snr:3d}dB{RST}" - # Flag bogus dongle responses (SNR outside SX126x range -32..+32) - if not (-32 <= snr <= 32): - line += f" {BRED}[bad SNR]{RST}" - return line - - -DISPLAY_IDLE_TIMEOUT = 90 # seconds — turn display off when no activity - - -def configure_and_listen(ser): - send_cmd(ser, "Ping", "Ping") - send_cmd(ser, "SetConfig", "SetConfig 910.525/62.5k/SF7/CR4/5", config=RADIO_CONFIG) - send_cmd(ser, "StartRx", "StartRx") - - print(f"\n {BWHT}Listening{RST} {DIM}(Ctrl+C to stop){RST}\n") - ser.timeout = 1 - - last_activity = time.monotonic() - display_off = False - - while True: - try: - _aggregator.maybe_report(ser) - except Exception: - pass - - # Auto-off display after idle timeout - now = time.monotonic() - if not display_off and now - last_activity > DISPLAY_IDLE_TIMEOUT: - send_cmd(ser, "DisplayOff", quiet=True) - display_off = True - - data = dl.read_frame(ser) - if data is None: - continue - try: - resp = dl.decode_response(data) - if resp["type"] == "RxPacket": - # Wake display on activity - if display_off: - send_cmd(ser, "DisplayOn", quiet=True) - display_off = False - last_activity = time.monotonic() - - payload = resp["payload"] - decoded, warnings = decode_meshcore_packet(payload) - snr = resp["snr"] - # SNR below LoRa demodulation floor is one quality signal - sf = RADIO_CONFIG["sf"] - if -32 <= snr <= 32 and snr < -2.5 * (sf - 4): - warnings.append("low SNR") - warn_tag = "" - if warnings: - warn_tag = f" {BYEL}[{', '.join(warnings)}]{RST}" - rssi_snr = _format_rssi_snr(resp["rssi"], snr) - print(f" {rssi_snr} {DIM}len:{len(payload):3d}{RST} {decoded}{warn_tag}") - except Exception: - pass - - -def main(): - _load_adverts() - n = len(_known_nodes) - if n: - print(f" {DIM}Loaded {n} known node(s){RST}") - - port = sys.argv[1] if len(sys.argv) > 1 else None - - while True: - try: - ser = dl.connect(port) - configure_and_listen(ser) - except (serial.SerialException, ConnectionError, OSError) as e: - print(f"\n {RED}Disconnected: {e}{RST}") - print(f" {DIM}Reconnecting when device reappears...{RST}") - time.sleep(1) - except KeyboardInterrupt: - print() - try: - ser.timeout = 2 - send_cmd(ser, "StopRx", "StopRx") - except Exception: - pass - break - - -if __name__ == "__main__": - main() diff --git a/examples/meshcore/telemetry_monitor.py b/examples/meshcore/telemetry_monitor.py deleted file mode 100644 index 43eb8308..00000000 --- a/examples/meshcore/telemetry_monitor.py +++ /dev/null @@ -1,1143 +0,0 @@ -#!/usr/bin/env python3 -"""MeshCore repeater telemetry monitor. - -Periodically logs into configured repeaters and fetches telemetry data -(battery voltage, temperature, etc.). Uses route escalation to minimise -flooding: last-known route → zero-hop direct → flood, then learns the -return path for next time. - -Configuration lives in ~/.donglora/telemetry.json: - - { - "repeaters": [ - { - "name": "Hilltop-1", - "password": "guest123" - } - ], - "poll_interval_secs": 3600 - } - -The "pubkey" field is optional. If missing (or empty), the monitor -listens for MeshCore ADVERTs, matches by name, fills in the pubkey -automatically, and saves the config back to disk. -""" -import hashlib -import hmac -import json -import os -import random -import struct -import sys -import time -from pathlib import Path - -import serial -from cobs import cobs -from Crypto.Cipher import AES -from nacl.bindings import crypto_scalarmult -from nacl.signing import SigningKey, VerifyKey - -import donglora as dl - - -# ── Constants ───────────────────────────────────────────────────── - -DATA_DIR = Path.home() / ".donglora" -CONFIG_FILE = DATA_DIR / "telemetry.json" -KEY_FILE = DATA_DIR / "telemetry_key.bin" -STATE_FILE = DATA_DIR / "telemetry_state.json" - -DEFAULT_POLL_INTERVAL = 3600 # 1 hour - -# MeshCore protocol constants -REQ_TYPE_GET_TELEMETRY_DATA = 0x03 -TELEM_PERM_BASE = 0x01 -TELEM_PERM_LOCATION = 0x02 -TELEM_PERM_ENVIRONMENT = 0x04 - -# Timeouts and retries -LOGIN_TIMEOUT = 15.0 # seconds to wait for login response -TELEM_TIMEOUT = 15.0 # seconds to wait for telemetry response -TX_SETTLE = 1.0 # delay between transmissions -FLOOD_RETRIES = 3 # retry flood login/telemetry this many times - -RADIO_CONFIG = { - "freq_hz": 910_525_000, - "bw": 6, # 62.5 kHz - "sf": 7, - "cr": 5, # CR 4/5 - "sync_word": 0x3444, - "tx_power_dbm": -128, # TX_POWER_MAX -} - - -# ── ANSI helpers ────────────────────────────────────────────────── - -RST = "\033[0m" -DIM = "\033[2m" -RED = "\033[31m" -GRN = "\033[32m" -YEL = "\033[33m" -CYN = "\033[36m" -BCYN = "\033[1;36m" -BGRN = "\033[1;32m" -BWHT = "\033[1;37m" - - -# ── Identity ────────────────────────────────────────────────────── - -_signing_key: SigningKey | None = None -_verify_key: VerifyKey | None = None -_pubkey_bytes: bytes = b"" - - -def _init_identity(): - global _signing_key, _verify_key, _pubkey_bytes - DATA_DIR.mkdir(parents=True, exist_ok=True) - - if KEY_FILE.is_file(): - seed = KEY_FILE.read_bytes() - if len(seed) == 32: - _signing_key = SigningKey(seed) - print(f" {DIM}Loaded keypair from {KEY_FILE}{RST}") - else: - _signing_key = SigningKey.generate() - KEY_FILE.write_bytes(bytes(_signing_key)) - else: - _signing_key = SigningKey.generate() - KEY_FILE.write_bytes(bytes(_signing_key)) - print(f" {BWHT}Generated new keypair → {KEY_FILE}{RST}") - - _verify_key = _signing_key.verify_key - _pubkey_bytes = bytes(_verify_key) - print(f" {DIM}Pubkey: {_pubkey_bytes.hex()}{RST}") - print(f" {DIM}Node hash: 0x{_pubkey_bytes[0]:02x}{RST}") - - -def _my_hash() -> int: - return _pubkey_bytes[0] - - -# ── Crypto ──────────────────────────────────────────────────────── - - -def _ecdh_shared_secret(peer_ed25519_pub: bytes) -> bytes: - my_x25519 = bytes(_signing_key.to_curve25519_private_key()) - peer_verify = VerifyKey(peer_ed25519_pub) - peer_x25519 = bytes(peer_verify.to_curve25519_public_key()) - return crypto_scalarmult(my_x25519, peer_x25519) - - -def _encrypt(shared_secret: bytes, plaintext: bytes) -> bytes: - """Encrypt-then-MAC. Returns mac(2) + ciphertext.""" - pad_len = (16 - len(plaintext) % 16) % 16 - plaintext += b"\x00" * pad_len - cipher = AES.new(shared_secret[:16], AES.MODE_ECB) - ciphertext = b"" - for i in range(0, len(plaintext), 16): - ciphertext += cipher.encrypt(plaintext[i : i + 16]) - mac = hmac.new(shared_secret[:32], ciphertext, hashlib.sha256).digest()[:2] - return mac + ciphertext - - -def _verify_and_decrypt(shared_secret: bytes, mac_bytes: bytes, ciphertext: bytes) -> bytes | None: - computed = hmac.new(shared_secret[:32], ciphertext, hashlib.sha256).digest()[:2] - if computed != mac_bytes: - return None - cipher = AES.new(shared_secret[:16], AES.MODE_ECB) - plaintext = b"" - for i in range(0, len(ciphertext), 16): - plaintext += cipher.decrypt(ciphertext[i : i + 16]) - return plaintext - - -# ── Route table ─────────────────────────────────────────────────── -# pubkey_hex → (path_hops: list[bytes], hash_size: int) - -_route_table: dict[str, tuple[list[bytes], int]] = {} - - -MIN_HASH_SIZE = 2 # always use 2-byte routing minimum - - -def _learn_route(pubkey_hex: str, path_hops: list[bytes], hash_size: int): - """Learn a return route from an incoming packet's path (reversed).""" - if hash_size < MIN_HASH_SIZE: - return # reject 1-byte routes - existing = _route_table.get(pubkey_hex) - if existing is not None: - _, existing_hs = existing - if hash_size < existing_hs: - return - _route_table[pubkey_hex] = (list(reversed(path_hops)), hash_size) - - -def _get_route(pubkey_hex: str) -> tuple[list[bytes], int] | None: - return _route_table.get(pubkey_hex) - - -# ── Persistent state ───────────────────────────────────────────── - -_state: dict = {"routes": {}} - - -def _load_state(): - global _state - if STATE_FILE.is_file(): - try: - with open(STATE_FILE) as f: - loaded = json.load(f) - _state.update(loaded) - # Restore routes from state - for pk_hex, route_data in _state.get("routes", {}).items(): - hops = [bytes.fromhex(h) for h in route_data["hops"]] - _route_table[pk_hex] = (hops, route_data["hash_size"]) - print(f" {DIM}Loaded state ({len(_route_table)} routes){RST}") - except Exception as e: - print(f" {YEL}Failed to load state: {e}{RST}") - - -def _save_state(): - try: - # Serialise routes - routes = {} - for pk_hex, (hops, hs) in _route_table.items(): - routes[pk_hex] = {"hops": [h.hex() for h in hops], "hash_size": hs} - _state["routes"] = routes - - DATA_DIR.mkdir(parents=True, exist_ok=True) - tmp = STATE_FILE.with_suffix(".tmp") - with open(tmp, "w") as f: - json.dump(_state, f, indent=2) - tmp.rename(STATE_FILE) - except Exception as e: - print(f" {RED}Failed to save state: {e}{RST}") - - -# ── Serial / USB ────────────────────────────────────────────────── - -def open_connection(port: str | None = None): - """Open a DongLoRa connection (mux auto-detected, falls back to USB).""" - return dl.connect(port=port, timeout=2) - - -# ── COBS framing ───────────────────────────────────────────────── - - -def cobs_frame(payload: bytes) -> bytes: - return cobs.encode(payload) + b"\x00" - - -def read_frame(ser: serial.Serial) -> bytes | None: - buf = b"" - while True: - b = ser.read(1) - if not b: - return None - if b == b"\x00": - break - buf += b - if not buf: - return None - try: - return cobs.decode(buf) - except cobs.DecodeError: - return None - - -# ── Command encoding / response decoding ───────────────────────── - - -def encode_radio_config(cfg: dict) -> bytes: - return struct.pack( - " bytes: - kind = cmd["type"] - tags = { - "Ping": 0, "GetConfig": 1, "SetConfig": 2, "StartRx": 3, - "StopRx": 4, "Transmit": 5, "DisplayOn": 6, "DisplayOff": 7, - } - out = bytes([tags[kind]]) - if kind == "SetConfig": - out += encode_radio_config(cmd["config"]) - elif kind == "Transmit": - config = cmd.get("config") - if config is None: - out += b"\x00" - else: - out += b"\x01" + encode_radio_config(config) - payload = cmd["payload"] - out += struct.pack(" dict: - if not data: - return {"type": "Empty"} - tag = data[0] - rest = data[1:] - if tag == 0: - return {"type": "Pong"} - elif tag == 1: - return {"type": "Config", "raw": rest.hex()} - elif tag == 2: - rssi = struct.unpack_from(" dict | None: - payload = encode_command(cmd) - frame = cobs_frame(payload) - if label: - print(f" {DIM}>>> {label}{RST}") - ser.write(frame) - ser.flush() - for _ in range(50): - resp_data = read_frame(ser) - if resp_data is None: - return None - resp = decode_response(resp_data) - if resp["type"] == "RxPacket": - continue - return resp - return None - - -def transmit(ser: serial.Serial, packet: bytes, label: str = "TX"): - resp = send_cmd(ser, {"type": "Transmit", "payload": packet}, label=label) - if resp is None: - print(f" {RED}TX failed: no response{RST}") - elif resp["type"] == "Error": - print(f" {RED}TX error: {resp}{RST}") - - -# ── MeshCore packet parsing ────────────────────────────────────── - - -def parse_header_and_path(packet: bytes): - """Parse MeshCore header + path. - - Returns (payload_type, route_type, pos, payload, path_hops, hash_size) or None. - """ - if len(packet) < 3: - return None - - header = packet[0] - route_type = header & 0x03 - payload_type = (header >> 2) & 0x0F - payload_ver = (header >> 6) & 0x03 - if payload_ver != 0: - return None - - has_tc = route_type in (0, 3) - - for skip_tc in ([True, False] if has_tc else [False]): - pos = 5 if skip_tc else 1 - if pos >= len(packet): - continue - - path_len_byte = packet[pos] - pos += 1 - hash_size_code = path_len_byte >> 6 - if hash_size_code == 3: - continue - hash_size = hash_size_code + 1 - hop_count = path_len_byte & 0x3F - path_bytes = hop_count * hash_size - if path_bytes > 64 or pos + path_bytes > len(packet): - continue - - hops = [] - for i in range(hop_count): - hops.append(packet[pos + i * hash_size : pos + (i + 1) * hash_size]) - pos += path_bytes - - payload = packet[pos:] - if payload and len(payload) <= 184: - return payload_type, route_type, pos, payload, hops, hash_size - - return None - - -# ── Packet construction ────────────────────────────────────────── - -_RT_NAMES = {0: "tflood", 1: "flood", 2: "direct", 3: "tdirect"} - - -def _build_routed_packet( - payload_type: int, - payload: bytes, - route: tuple[list[bytes], int] | None, -) -> tuple[bytes, str]: - """Build a packet with the given route. Returns (packet, route_description).""" - if route is not None: - hops, hash_size = route - hash_size_code = hash_size - 1 - path_len_byte = (hash_size_code << 6) | (len(hops) & 0x3F) - path_data = b"".join(hops) - header = bytes([(payload_type << 2) | 2]) # DIRECT - return header + bytes([path_len_byte]) + path_data + payload, "direct" - else: - header = bytes([(payload_type << 2) | 1]) # FLOOD - path_len = bytes([0x40]) # 0 hops, 2-byte hash mode - return header + path_len + payload, "flood" - - -def _build_direct_zero_hop_packet(payload_type: int, payload: bytes) -> bytes: - """Build a zero-hop direct packet (reaches only immediate neighbours).""" - header = bytes([(payload_type << 2) | 2]) # DIRECT - path_len = bytes([0x40]) # 0 hops, 2-byte hash mode - return header + path_len + payload - - -# ── ANON_REQ (login) ───────────────────────────────────────────── - - -def build_login_packet( - repeater_pubkey: bytes, - password: str, - route: tuple[list[bytes], int] | None, -) -> tuple[bytes, str]: - """Build an ANON_REQ login packet for a repeater. - - Plaintext: timestamp(4) + password (UTF-8, no null terminator needed — - encryption pads to 16-byte boundary, server reads until null/end). - - Returns (packet, route_description). - """ - shared_secret = _ecdh_shared_secret(repeater_pubkey) - - # Repeater login plaintext: timestamp(4) + password bytes - plaintext = struct.pack(" tuple[bytes, str]: - """Build a REQ packet with protocol_code=GET_TELEMETRY_DATA. - - Plaintext: timestamp(4) + protocol_code(1) + inverse_perm_mask(1) - We want base + environment (battery + temp), skip location. - - Returns (packet, route_description). - """ - shared_secret = _ecdh_shared_secret(repeater_pubkey) - - # inverse perm mask: skip location only - inv_mask = TELEM_PERM_LOCATION - - plaintext = struct.pack(" list[dict]: - """Decode CayenneLPP payload. Returns list of sensor readings.""" - sensors = [] - pos = 0 - while pos < len(data): - if pos + 2 > len(data): - break - channel = data[pos] - type_id = data[pos + 1] - pos += 2 - - type_info = CAYENNE_TYPES.get(type_id) - if type_info is None: - # Unknown type — can't determine size, stop parsing - break - - name, size, divisor = type_info - if pos + size > len(data): - break - - raw = data[pos : pos + size] - pos += size - - # Decode value (big-endian per CayenneLPP spec) - signed = name in ("temperature", "analog_in", "analog_out", "voltage", "current") - val_raw = int.from_bytes(raw, "big", signed=signed) if size > 1 else raw[0] - value = val_raw / divisor - - UNITS = { - "temperature": "°C", "humidity": "%", "barometer": "hPa", - "voltage": "V", "analog_in": "V", "analog_out": "V", - "current": "A", "power": "W", "distance": "m", - "illuminance": "lux", "percentage": "%", - } - sensors.append({ - "channel": channel, "type": name, - "value": value, "unit": UNITS.get(name, ""), - }) - - return sensors - - -# ── Response waiting ────────────────────────────────────────────── - - -def wait_for_response( - ser: serial.Serial, - repeater_pubkey: bytes, - expected_types: set[int], - timeout: float, -) -> tuple[dict | None, list[bytes], int]: - """Wait for a MeshCore response addressed to us from the repeater. - - Returns (parsed_info, path_hops, hash_size) or (None, [], 0). - parsed_info depends on payload type. - """ - pk_hex = repeater_pubkey.hex() - shared_secret = _ecdh_shared_secret(repeater_pubkey) - dest_hash = repeater_pubkey[0] - deadline = time.monotonic() + timeout - old_timeout = ser.timeout - ser.timeout = 1 - - try: - while time.monotonic() < deadline: - data = read_frame(ser) - if data is None: - continue - - resp = decode_response(data) - if resp["type"] != "RxPacket": - continue - - packet = resp["payload"] - parsed = parse_header_and_path(packet) - if parsed is None: - continue - - payload_type, route_type, pos, raw_payload, path_hops, hash_size = parsed - - if payload_type not in expected_types: - continue - - # ── PATH (0x08) — responses routed back via PATH ── - if payload_type == 0x08: - if len(raw_payload) < 20: - continue - dst = raw_payload[0] - src = raw_payload[1] - if dst != _my_hash(): - continue - if src != dest_hash: - continue - - mac_bytes = raw_payload[2:4] - ciphertext = raw_payload[4:] - plaintext = _verify_and_decrypt(shared_secret, mac_bytes, ciphertext) - if plaintext is None: - continue - - # PATH inner: path_len(1) + path_data + extra_type(1) + extra_data - inner_path_len_byte = plaintext[0] - inner_hash_size = (inner_path_len_byte >> 6) + 1 - inner_hop_count = inner_path_len_byte & 0x3F - inner_path_bytes = inner_hop_count * inner_hash_size - ipos = 1 + inner_path_bytes - - if ipos < len(plaintext): - extra_type = plaintext[ipos] - extra_data = plaintext[ipos + 1:] - else: - extra_type = 0xFF - extra_data = b"" - - # Learn route from inner path - if inner_hop_count > 0: - inner_hops = [] - for i in range(inner_hop_count): - inner_hops.append(plaintext[1 + i * inner_hash_size : 1 + (i + 1) * inner_hash_size]) - _learn_route(pk_hex, inner_hops, inner_hash_size) - _save_state() - - if extra_type != 0x01 or len(extra_data) < 4: - continue - - timestamp = struct.unpack_from("= 13 - and extra_data[4] in (0x00, 0x80) - and extra_data[12] in (0x01, 0x02, 0x03) - ) - - if is_login_response: - if 0x01 in expected_types: - # Waiting for telemetry — skip this stale login response - continue # skip stale login response - response_code = extra_data[4] - is_admin = extra_data[6] if len(extra_data) > 6 else 0 - return { - "type": "login_response", - "response_code": response_code, - "is_admin": bool(is_admin), - "timestamp": timestamp, - }, path_hops, hash_size - - # Telemetry response: ts(4) + CayenneLPP (no response_code) - if 0x01 in expected_types: - lpp_data = extra_data[4:] - while lpp_data and lpp_data[-1] == 0: - lpp_data = lpp_data[:-1] - sensors = decode_cayenne_lpp(lpp_data) if lpp_data else [] - return { - "type": "telemetry", - "reflected_timestamp": timestamp, - "sensors": sensors, - "raw_hex": extra_data.hex(), - }, path_hops, hash_size - - continue - - # ── RESPONSE (0x01) — telemetry data ── - if payload_type == 0x01: - if len(raw_payload) < 20: - continue - dst = raw_payload[0] - src = raw_payload[1] - if dst != _my_hash(): - continue - if src != dest_hash: - continue - - mac_bytes = raw_payload[2:4] - ciphertext = raw_payload[4:] - plaintext = _verify_and_decrypt(shared_secret, mac_bytes, ciphertext) - if plaintext is None: - continue - - # Telemetry response: timestamp(4) + CayenneLPP (no response_code) - if len(plaintext) < 4: - continue - - reflected_ts = struct.unpack_from(" list[tuple[tuple[list[bytes], int] | None, str]]: - """Return route strategies in escalation order. - - a) last known route b) zero-hop direct c) flood - """ - strategies = [] - - # a) Last known route - known = _get_route(pk_hex) - if known is not None: - strategies.append((known, "last-known")) - - # b) Zero-hop direct (immediate neighbours only — no flooding) - strategies.append(("zero-hop", "zero-hop")) - - # c) Flood (only as last resort) - strategies.append((None, "flood")) - - return strategies - - -# ── Poll one repeater ───────────────────────────────────────────── - - -def poll_repeater(ser: serial.Serial, repeater: dict) -> dict | None: - """Login to repeater, fetch telemetry. Returns telemetry dict or None.""" - name = repeater["name"] - pubkey = bytes.fromhex(repeater["pubkey"]) - password = repeater["password"] - pk_hex = repeater["pubkey"] - - print(f"\n {BCYN}── {name} ──{RST}") - - strategies = _route_strategies(pk_hex) - - # Step 1: Login with route escalation + flood retries - login_ok = False - for route_val, route_desc in strategies: - # Flood gets multiple attempts since it should always eventually work - attempts = FLOOD_RETRIES if route_val is None else 1 - - for attempt in range(attempts): - if route_val == "zero-hop": - shared_secret = _ecdh_shared_secret(pubkey) - plaintext = struct.pack(" 1 and attempt > 0 else "" - print(f" {DIM}Login via {actual_desc}{retry_label}...{RST}", end="", flush=True) - transmit(ser, packet, label=f"LOGIN({actual_desc})→{name}") - time.sleep(TX_SETTLE) - - result, path_hops, hash_size = wait_for_response( - ser, pubkey, {0x08}, LOGIN_TIMEOUT - ) - - if result is not None and result.get("response_code") in (0x00, 0x80): - login_ok = True - if path_hops: - _learn_route(pk_hex, path_hops, hash_size) - _save_state() - print(f" {GRN}OK{RST} {DIM}(learned {len(path_hops)}-hop route){RST}") - else: - print(f" {GRN}OK{RST}") - break - elif result is not None: - print(f" {RED}rejected (code=0x{result.get('response_code', -1):02x}){RST}") - login_ok = False - break # Wrong password — don't retry - else: - print(f" {YEL}timeout{RST}") - - if login_ok or (result is not None and result.get("response_code") not in (0x00, 0x80)): - break - - if not login_ok: - print(f" {RED}Login failed for {name}{RST}") - return None - - # Step 2: Request telemetry with escalation (direct → flood with retries) - telem_strategies = [] - known = _get_route(pk_hex) - if known is not None: - telem_strategies.append((known, "direct")) - telem_strategies.append((None, "flood")) - - result = None - for route, route_desc in telem_strategies: - attempts = FLOOD_RETRIES if route is None else 1 - - for attempt in range(attempts): - packet, _ = build_telemetry_request(pubkey, route) - retry_label = f" #{attempt + 1}" if attempts > 1 and attempt > 0 else "" - print(f" {DIM}Requesting telemetry via {route_desc}{retry_label}...{RST}", end="", flush=True) - transmit(ser, packet, label=f"TELEM_REQ({route_desc})→{name}") - time.sleep(TX_SETTLE) - - result, path_hops, hash_size = wait_for_response( - ser, pubkey, {0x01, 0x08}, TELEM_TIMEOUT - ) - - if result is not None: - if path_hops: - _learn_route(pk_hex, path_hops, hash_size) - _save_state() - print(f" {GRN}OK{RST}") - break - else: - print(f" {YEL}timeout{RST}") - - if result is not None: - break - # Clear stale route before escalating to next strategy - _route_table.pop(pk_hex, None) - - if result is None: - print(f" {RED}Telemetry failed for {name}{RST}") - return None - - # Display raw hex for debugging - raw_hex = result.get("raw_hex", "") - if raw_hex: - print(f" {DIM}raw: {raw_hex}{RST}") - - # Display parsed sensors — deduplicate by (channel, type), keep first - # (external sensors are added before MCU fallback in firmware) - sensors = result.get("sensors", []) - seen = set() - deduped = [] - for s in sensors: - key = (s["channel"], s["type"]) - if key not in seen: - seen.add(key) - deduped.append(s) - - if deduped: - for s in deduped: - stype = s["type"] - val = s["value"] - unit = s.get("unit", "") - if stype == "temperature": - f_val = val * 9 / 5 + 32 - print(f" {BWHT}temperature{RST}: {val:.1f}°C / {f_val:.1f}°F") - elif stype in ("voltage", "analog_in", "analog_out") and unit == "V": - print(f" {BWHT}voltage{RST}: {val:.2f}V") - elif stype in ("digital_in", "digital_out"): - state = "On" if val else "Off" - print(f" {DIM}digital {stype.split('_')[1]}{RST}: {state}") - else: - print(f" {BWHT}{stype}{RST} ch{s['channel']}: {val:.2f}{unit}") - - return result - - -# ── ADVERT decoding ────────────────────────────────────────────── - - -def decode_advert(raw_payload: bytes) -> tuple[bytes, str, int] | None: - """Decode and verify an ADVERT. Returns (pubkey, name, node_type) or None.""" - if len(raw_payload) < 100: - return None - - pubkey = raw_payload[0:32] - timestamp = raw_payload[32:36] - signature = raw_payload[36:100] - app_data = raw_payload[100:] - - try: - verify_key = VerifyKey(pubkey) - sign_msg = pubkey + timestamp + app_data - verify_key.verify(sign_msg, signature) - except Exception: - return None - - name = "" - node_type = 0 - if app_data: - flags = app_data[0] - node_type = flags & 0x0F - pos = 1 - if flags & 0x10: # has location - pos += 8 - if flags & 0x20: # feat1 - pos += 2 - if flags & 0x40: # feat2 - pos += 2 - if flags & 0x80 and pos < len(app_data): # has name - name = app_data[pos:].decode("utf-8", errors="replace") - - return pubkey, name if name else pubkey.hex()[:8], node_type - - -def discover_pubkeys(ser: serial.Serial, wanted: list[dict]) -> int: - """Listen for ADVERTs and match against repeaters missing pubkeys. - - Returns number of pubkeys discovered. - """ - # Build a lookup: lowercase name → list of config entries wanting that name - by_name: dict[str, list[dict]] = {} - for r in wanted: - by_name.setdefault(r["name"].lower(), []).append(r) - - found = 0 - total = len(wanted) - print(f"\n {BCYN}Waiting for ADVERTs from {total} repeater(s):{RST}") - for r in wanted: - print(f" {CYN}{r['name']}{RST}") - - old_timeout = ser.timeout - ser.timeout = 1 - - try: - while found < total: - data = read_frame(ser) - if data is None: - continue - - resp = decode_response(data) - if resp["type"] != "RxPacket": - continue - - packet = resp["payload"] - parsed = parse_header_and_path(packet) - if parsed is None: - continue - - payload_type, route_type, pos, raw_payload, path_hops, hash_size = parsed - if payload_type != 0x04: # not ADVERT - continue - - result = decode_advert(raw_payload) - if result is None: - continue - - pubkey, adv_name, node_type = result - pk_hex = pubkey.hex() - - # Match against wanted names (case-insensitive) - matches = by_name.get(adv_name.lower(), []) - if not matches: - print(f" {DIM}ADVERT {adv_name} (not in config){RST}") - continue - - for r in matches: - if r.get("pubkey"): - continue # already discovered - r["pubkey"] = pk_hex - found += 1 - # Learn route from the ADVERT path too - if path_hops: - _learn_route(pk_hex, path_hops, hash_size) - print( - f" {BGRN}Discovered {adv_name}{RST} " - f"{DIM}pubkey={pk_hex[:16]}... " - f"hash=0x{pubkey[0]:02x}{RST}" - ) - - if found >= total: - break - finally: - ser.timeout = old_timeout - - return found - - -# ── Config ──────────────────────────────────────────────────────── - - -def load_config() -> dict: - if not CONFIG_FILE.is_file(): - print(f" {RED}Config not found: {CONFIG_FILE}{RST}") - print(f" {DIM}Create it with:{RST}") - print(f""" - {{ - "repeaters": [ - {{ - "name": "MyRepeater", - "password": "guest123" - }} - ], - "poll_interval_secs": 3600 - }}""") - sys.exit(1) - - with open(CONFIG_FILE) as f: - config = json.load(f) - - repeaters = config.get("repeaters", []) - if not repeaters: - print(f" {RED}No repeaters configured in {CONFIG_FILE}{RST}") - sys.exit(1) - - for r in repeaters: - if "name" not in r or "password" not in r: - print(f" {RED}Repeater missing name or password: {r}{RST}") - sys.exit(1) - pk = r.get("pubkey", "") - if pk and len(pk) != 64: - print(f" {RED}Invalid pubkey length for {r['name']}: expected 64 hex chars{RST}") - sys.exit(1) - - return config - - -def save_config(config: dict): - """Write config back to disk (atomic).""" - try: - DATA_DIR.mkdir(parents=True, exist_ok=True) - tmp = CONFIG_FILE.with_suffix(".tmp") - with open(tmp, "w") as f: - json.dump(config, f, indent=4, ensure_ascii=False) - tmp.rename(CONFIG_FILE) - except Exception as e: - print(f" {RED}Failed to save config: {e}{RST}") - - -def needs_discovery(config: dict) -> list[dict]: - """Return repeaters that are missing their pubkey.""" - return [r for r in config["repeaters"] if not r.get("pubkey")] - - -# ── Main loop ───────────────────────────────────────────────────── - - -def _print_repeater_status(repeaters: list[dict]): - for r in repeaters: - pk = r.get("pubkey", "") - if pk: - route = _get_route(pk) - route_str = f"{len(route[0])}-hop" if route else "none" - print(f" {CYN}{r['name']}{RST} {DIM}(hash=0x{bytes.fromhex(pk)[0]:02x} route={route_str}){RST}") - else: - print(f" {CYN}{r['name']}{RST} {YEL}(pubkey unknown — will discover via ADVERT){RST}") - - -def main(): - import argparse - parser = argparse.ArgumentParser(description="MeshCore repeater telemetry monitor") - parser.add_argument("--port", "-p", default=None, help="Serial port (auto-detect if omitted)") - args = parser.parse_args() - - _init_identity() - _load_state() - config = load_config() - - repeaters = config["repeaters"] - poll_interval = config.get("poll_interval_secs", DEFAULT_POLL_INTERVAL) - - print(f"\n {BWHT}Telemetry Monitor{RST}") - print(f" {DIM}Repeaters: {len(repeaters)} | Poll interval: {poll_interval}s{RST}") - _print_repeater_status(repeaters) - - port = args.port - - while True: - try: - print(f" {DIM}Connecting...{RST}") - ser = open_connection(port) - print(f" {GRN}Connected{RST}") - - send_cmd(ser, {"type": "Ping"}, "Ping") - send_cmd(ser, {"type": "SetConfig", "config": RADIO_CONFIG}, "SetConfig") - send_cmd(ser, {"type": "StartRx"}, "StartRx") - - # Discover any repeaters missing their pubkey - missing = needs_discovery(config) - if missing: - found = discover_pubkeys(ser, missing) - if found: - save_config(config) - _save_state() - print(f" {BGRN}Saved {found} pubkey(s) to {CONFIG_FILE}{RST}") - still_missing = needs_discovery(config) - if still_missing: - names = ", ".join(r["name"] for r in still_missing) - print(f" {YEL}Still waiting for: {names}{RST}") - print(f" {DIM}Will retry discovery each poll cycle{RST}") - - while True: - ts = time.strftime("%Y-%m-%d %H:%M:%S %Z") - print(f"\n {BWHT}═══ Poll cycle {ts} ═══{RST}") - - # Re-check for missing pubkeys each cycle - missing = needs_discovery(config) - if missing: - print(f" {DIM}Listening for {len(missing)} missing ADVERT(s)...{RST}") - found = discover_pubkeys(ser, missing) - if found: - save_config(config) - _save_state() - - for repeater in repeaters: - if not repeater.get("pubkey"): - print(f"\n {BCYN}── {repeater['name']} ──{RST}") - print(f" {YEL}Skipping — pubkey not yet discovered{RST}") - continue - try: - poll_repeater(ser, repeater) - except (ConnectionError, OSError): - raise # mux/serial disconnect — let outer loop reconnect - except Exception as e: - print(f" {RED}Error polling {repeater['name']}: {e}{RST}") - - _save_state() - - next_poll = time.strftime( - "%H:%M:%S", time.localtime(time.time() + poll_interval) - ) - print(f"\n {DIM}Next poll at {next_poll} (sleeping {poll_interval}s){RST}") - - # Sleep in 1-second increments so Ctrl-C is responsive - deadline = time.monotonic() + poll_interval - while time.monotonic() < deadline: - time.sleep(1) - - except (serial.SerialException, ConnectionError, OSError) as e: - print(f"\n {RED}Disconnected: {e}{RST}") - print(f" {DIM}Reconnecting...{RST}") - time.sleep(1) - except KeyboardInterrupt: - print() - _save_state() - try: - ser.timeout = 2 - send_cmd(ser, {"type": "StopRx"}, "StopRx") - except Exception: - pass - break - - -if __name__ == "__main__": - main() diff --git a/examples/mise.toml b/examples/mise.toml deleted file mode 100644 index a245b1c0..00000000 --- a/examples/mise.toml +++ /dev/null @@ -1,4 +0,0 @@ -[tools] -python = "3.14" -uv = "latest" -just = "latest" diff --git a/examples/ping_pong.py b/examples/ping_pong.py deleted file mode 100755 index d289999a..00000000 --- a/examples/ping_pong.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -"""Two-dongle demo: one transmits, one receives. - -Usage: - python examples/ping_pong.py --role tx [PORT] - python examples/ping_pong.py --role rx [PORT] -""" - -import argparse -import sys -import time - -import serial - -import donglora as dl - -parser = argparse.ArgumentParser(description="DongLoRa ping-pong demo") -parser.add_argument("--role", choices=["tx", "rx"], required=True) -parser.add_argument("port", nargs="?", help="Serial port (auto-detect if omitted)") -args = parser.parse_args() - -try: - ser = dl.connect(args.port) - print(dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG)) - - if args.role == "tx": - print("Transmitting every 2 seconds (Ctrl+C to stop)...\n") - seq = 0 - while True: - msg = f"ping #{seq}" - resp = dl.send(ser, "Transmit", payload=msg.encode()) - print(f" TX: {msg!r} → {resp['type']}") - seq += 1 - time.sleep(2) - - else: - print(dl.send(ser, "StartRx")) - print("Receiving (Ctrl+C to stop)...\n") - ser.timeout = 1 - while True: - data = dl.read_frame(ser) - if data is None: - continue - resp = dl.decode_response(data) - if resp["type"] == "RxPacket": - p = resp["payload"] - text = p.decode("utf-8", errors="replace") - print(f" RX: {text!r} RSSI:{resp['rssi']}dBm SNR:{resp['snr']}dB") - -except KeyboardInterrupt: - try: - dl.send(ser, "StopRx") - except Exception: - pass - print("\nDone.") -except serial.SerialException as e: - print(f"\nSerial error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/examples/pyproject.toml b/examples/pyproject.toml deleted file mode 100644 index 7efe40e4..00000000 --- a/examples/pyproject.toml +++ /dev/null @@ -1,19 +0,0 @@ -[project] -name = "donglora-examples" -version = "0.1.0" -description = "DongLoRa example scripts" -license = "MIT" -requires-python = ">=3.10" -dependencies = [ - "donglora", -] - -[project.optional-dependencies] -meshcore = ["donglora[meshcore]"] -orac = ["donglora[orac]"] - -[tool.uv] -package = false - -[tool.uv.sources] -donglora = { path = "../clients/python", editable = true } diff --git a/examples/simple_rx.py b/examples/simple_rx.py deleted file mode 100755 index a5cb1d9a..00000000 --- a/examples/simple_rx.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -"""Receive LoRa packets and print them. - -Usage: - python examples/simple_rx.py [PORT] -""" - -import sys - -import serial - -import donglora as dl - -try: - ser = dl.connect(sys.argv[1] if len(sys.argv) > 1 else None) - print(dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG)) - print(dl.send(ser, "StartRx")) - - print("\nListening (Ctrl+C to stop)...\n") - ser.timeout = 1 - - while True: - data = dl.read_frame(ser) - if data is None: - continue - resp = dl.decode_response(data) - if resp["type"] == "RxPacket": - p = resp["payload"] - print(f" RSSI:{resp['rssi']:4d}dBm SNR:{resp['snr']:3d}dB len:{len(p):3d} {p.hex()}") -except KeyboardInterrupt: - try: - dl.send(ser, "StopRx") - except Exception: - pass - print("\nDone.") -except serial.SerialException as e: - print(f"\nSerial error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/examples/simple_tx.py b/examples/simple_tx.py deleted file mode 100755 index dab7465b..00000000 --- a/examples/simple_tx.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -"""Transmit a single LoRa packet. - -Usage: - python examples/simple_tx.py [PORT] [MESSAGE] -""" - -import sys - -import serial - -import donglora as dl - -port = None -message = "Hello from DongLoRa!" - -args = sys.argv[1:] -if args and not args[0].startswith("/dev"): - message = " ".join(args) -elif args: - port = args[0] - if len(args) > 1: - message = " ".join(args[1:]) - -try: - ser = dl.connect(port) - print(dl.send(ser, "SetConfig", config=dl.DEFAULT_CONFIG)) - print(dl.send(ser, "Transmit", payload=message.encode())) - print(f"Sent: {message!r}") -except serial.SerialException as e: - print(f"\nSerial error: {e}", file=sys.stderr) - sys.exit(1) diff --git a/examples/uv.lock b/examples/uv.lock deleted file mode 100644 index 211e1c08..00000000 --- a/examples/uv.lock +++ /dev/null @@ -1,630 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anthropic" -version = "0.88.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "docstring-parser" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/68/565f13059c0a6a6fd5f96f306f2a0fb478a0e1174ec18a4df16b5fac9379/anthropic-0.88.0.tar.gz", hash = "sha256:f4c7f6863d08c869913516f08d658fe53caaf8bcc4fbea3218df343d2a876c58", size = 596654, upload-time = "2026-04-01T19:59:05.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ac/68f646998160c9f2e6f9353a31dd87292ef02b915b455aaf70a52a059a75/anthropic-0.88.0-py3-none-any.whl", hash = "sha256:71898b32332bc75d9739bc10095288d40a29605da6d00da2fe832b1aa036552f", size = 478338, upload-time = "2026-04-01T19:59:03.832Z" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "cobs" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/ef/ea149311227a4fc3160cc885fce06da7c7d76782a308ef070b8065c69953/cobs-1.2.2.tar.gz", hash = "sha256:dbdd5e32111d72786f83d0c269215dcd6ac629b1ac1962c6878221f3b2ca98da", size = 14582, upload-time = "2025-07-20T01:08:35.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/e0/9574590639a624a9a84c89a569f36343523a306fff2ffddc9468a2b9b05d/cobs-1.2.2-cp312-cp312-win32.whl", hash = "sha256:3afe0cec5d91164f84dcf35b471949aa36285664d21ee984350e39d485707d11", size = 26400, upload-time = "2025-07-20T01:08:30.288Z" }, - { url = "https://files.pythonhosted.org/packages/d3/4f/5418d9decdc4cf27a814fc189bc4fa44d216d37b871d633654f37ed331f8/cobs-1.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:4da6048f6e16cbd6c8ec5e95e8520e226248ffb8d31e9d6fc5e0888b3b65c54b", size = 27776, upload-time = "2025-07-20T01:08:31.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/df/7f07559954d53d09680aa4f5c72db999e7643253e2ca4e431023fe5c1c40/cobs-1.2.2-cp313-cp313-win32.whl", hash = "sha256:a8fdf6ca70247e7271caea6f2f6f9c0343f5e872e2fd47e21f71831fa9ababa8", size = 26401, upload-time = "2025-07-20T01:08:32.508Z" }, - { url = "https://files.pythonhosted.org/packages/0c/00/5b063105ff567bb7fb72c3cd3e96116a3889381fa2c0e7747497391dbecc/cobs-1.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe3f7194d5fbbc02e8fb03d26ad626a33fa0d420b50d66ed60ac4bf516322d8e", size = 27774, upload-time = "2025-07-20T01:08:33.692Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/de1e889c26fcfc9ae6852b8f99e744e6690a3baae99f63e2b9fffdd21710/cobs-1.2.2-cp314-cp314-win32.whl", hash = "sha256:bcf08cb0b68c1824a87cfa89e0684d5a3eaee9b9e08b02ce4a54d5589536f01c", size = 26779, upload-time = "2026-03-09T02:18:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/b6/64/390e226843cc83066acc60257e0e71ebdbce6dd915ec4f3d34101b9ac31b/cobs-1.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:1f27b7a553d809487287e4ed88540b2c30ed9c9bb596173bfcf549e705af7bf0", size = 28205, upload-time = "2026-03-09T02:18:20.193Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - -[[package]] -name = "docstring-parser" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, -] - -[[package]] -name = "donglora" -version = "0.1.0" -source = { editable = "../clients/python" } -dependencies = [ - { name = "cobs" }, - { name = "pyserial" }, -] - -[package.optional-dependencies] -meshcore = [ - { name = "pycryptodome" }, - { name = "pynacl" }, -] -orac = [ - { name = "anthropic" }, - { name = "pycryptodome" }, - { name = "pynacl" }, -] - -[package.metadata] -requires-dist = [ - { name = "anthropic", marker = "extra == 'orac'" }, - { name = "cobs" }, - { name = "pycryptodome", marker = "extra == 'meshcore'" }, - { name = "pycryptodome", marker = "extra == 'orac'" }, - { name = "pynacl", marker = "extra == 'meshcore'" }, - { name = "pynacl", marker = "extra == 'orac'" }, - { name = "pyserial" }, -] -provides-extras = ["meshcore", "orac"] - -[[package]] -name = "donglora-examples" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "donglora" }, -] - -[package.optional-dependencies] -meshcore = [ - { name = "donglora", extra = ["meshcore"] }, -] -orac = [ - { name = "donglora", extra = ["orac"] }, -] - -[package.metadata] -requires-dist = [ - { name = "donglora", editable = "../clients/python" }, - { name = "donglora", extras = ["meshcore"], marker = "extra == 'meshcore'", editable = "../clients/python" }, - { name = "donglora", extras = ["orac"], marker = "extra == 'orac'", editable = "../clients/python" }, -] -provides-extras = ["meshcore", "orac"] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "jiter" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, - { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, - { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, - { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, - { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, - { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, - { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, - { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, - { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, - { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, - { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, - { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, - { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, - { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, - { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, - { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, - { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, - { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, - { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, - { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, - { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, - { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, - { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, - { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, - { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, - { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pycryptodome" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, - { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, - { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, - { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, - { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, - { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, - { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, - { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, - { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, - { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, - { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pynacl" -version = "1.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, - { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, - { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, - { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, - { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, - { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, - { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, - { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, - { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, - { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, - { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, - { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, - { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, -] - -[[package]] -name = "pyserial" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] diff --git a/firmware/.cargo/config.toml b/firmware/.cargo/config.toml deleted file mode 100644 index b18ce923..00000000 --- a/firmware/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -[target.'cfg(target_arch = "xtensa")'] -rustflags = [ - "-C", "link-arg=-Wl,-Tlinkall.x", - "-C", "link-arg=-Wl,-Tdefmt.x", - "-C", "link-arg=-nostartfiles", -] - -[target.thumbv7em-none-eabihf] -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", - "-C", "opt-level=z", -] diff --git a/firmware/.gitignore b/firmware/.gitignore deleted file mode 100644 index 96aeed3b..00000000 --- a/firmware/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target -/vendor -/builds -/.hypothesis -src/board/mod.rs diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock deleted file mode 100644 index 5dcd879c..00000000 --- a/firmware/Cargo.lock +++ /dev/null @@ -1,2017 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - -[[package]] -name = "bitfield" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" -dependencies = [ - "bitfield-macros", -] - -[[package]] -name = "bitfield-macros" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "byte-slice-cast" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal", - "bitfield 0.13.2", - "critical-section", - "embedded-hal 0.2.7", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "defmt" -version = "0.3.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" -dependencies = [ - "defmt 1.0.1", -] - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "defmt-rtt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" -dependencies = [ - "critical-section", - "defmt 1.0.1", -] - -[[package]] -name = "delegate" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "crypto-common", -] - -[[package]] -name = "display-interface" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0" - -[[package]] -name = "display-interface-i2c" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d964fa85bbbb5a6ecd06e58699407ac5dc3e3ad72dac0ab7e6b0d00a1cd262d" -dependencies = [ - "display-interface", - "embedded-hal 1.0.0", - "embedded-hal-async", -] - -[[package]] -name = "display-interface-spi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9ec30048b1955da2038fcc3c017f419ab21bb0001879d16c0a3749dc6b7a" -dependencies = [ - "byte-slice-cast", - "display-interface", - "embedded-hal 1.0.0", - "embedded-hal-async", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "donglora" -version = "0.2.0" -dependencies = [ - "cfg-if", - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt 1.0.1", - "defmt-rtt", - "display-interface", - "embassy-embedded-hal 0.6.0", - "embassy-executor", - "embassy-futures", - "embassy-nrf", - "embassy-sync 0.8.0", - "embassy-time", - "embassy-usb", - "embassy-usb-driver", - "embedded-graphics", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-io-async 0.7.0", - "esp-backtrace", - "esp-hal", - "esp-println", - "esp-rtos", - "heapless 0.9.2", - "lora-phy", - "minijinja", - "owo-colors", - "panic-probe", - "ssd1306", - "static_cell", - "ucobs", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" -dependencies = [ - "embassy-futures", - "embassy-hal-internal 0.3.0", - "embassy-sync 0.7.2", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" -dependencies = [ - "defmt 1.0.1", - "embassy-futures", - "embassy-hal-internal 0.4.0", - "embassy-sync 0.8.0", - "embassy-time", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-executor" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" -dependencies = [ - "cortex-m", - "critical-section", - "document-features", - "embassy-executor-macros", - "embassy-executor-timer-queue", -] - -[[package]] -name = "embassy-executor-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "embassy-executor-timer-queue" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" - -[[package]] -name = "embassy-futures" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" - -[[package]] -name = "embassy-hal-internal" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" -dependencies = [ - "num-traits", -] - -[[package]] -name = "embassy-hal-internal" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" -dependencies = [ - "num-traits", -] - -[[package]] -name = "embassy-hal-internal" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568659fc53866d3d85c60fa33723fb751aa69e71507634fc2c19e7649432fb75" -dependencies = [ - "cortex-m", - "critical-section", - "defmt 1.0.1", - "num-traits", -] - -[[package]] -name = "embassy-net-driver" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" - -[[package]] -name = "embassy-net-driver-channel" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d2eb9f05a6fc876500949856ea1be40773d866d8cb99384f72d0ae4568c16" -dependencies = [ - "embassy-futures", - "embassy-net-driver", - "embassy-sync 0.8.0", -] - -[[package]] -name = "embassy-nrf" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b715380574504335c40ec3016bc44598fac3c385f82959b26c41b25c30b7648d" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt 1.0.1", - "document-features", - "embassy-embedded-hal 0.6.0", - "embassy-futures", - "embassy-hal-internal 0.5.0", - "embassy-sync 0.8.0", - "embassy-time-driver", - "embassy-time-queue-utils", - "embassy-usb-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-io 0.7.1", - "embedded-io-async 0.7.0", - "embedded-storage", - "embedded-storage-async", - "fixed", - "nrf-pac", - "rand_core 0.6.4", - "rand_core 0.9.5", -] - -[[package]] -name = "embassy-sync" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" -dependencies = [ - "cfg-if", - "critical-section", - "embedded-io-async 0.6.1", - "futures-sink", - "futures-util", - "heapless 0.8.0", -] - -[[package]] -name = "embassy-sync" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" -dependencies = [ - "cfg-if", - "critical-section", - "embedded-io-async 0.6.1", - "futures-core", - "futures-sink", - "heapless 0.8.0", -] - -[[package]] -name = "embassy-sync" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" -dependencies = [ - "cfg-if", - "critical-section", - "defmt 1.0.1", - "embedded-io-async 0.7.0", - "futures-core", - "futures-sink", - "heapless 0.9.2", -] - -[[package]] -name = "embassy-time" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" -dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-core", -] - -[[package]] -name = "embassy-time-driver" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" -dependencies = [ - "document-features", -] - -[[package]] -name = "embassy-time-queue-utils" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" -dependencies = [ - "embassy-executor-timer-queue", - "heapless 0.8.0", -] - -[[package]] -name = "embassy-usb" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25746d8b152b72fbf2a217f489a083dbbe243f281f09184a1f2cfbe9bbb245f" -dependencies = [ - "bitflags 2.11.0", - "defmt 1.0.1", - "embassy-futures", - "embassy-net-driver-channel", - "embassy-sync 0.8.0", - "embassy-time", - "embassy-usb-driver", - "embedded-io-async 0.7.0", - "heapless 0.9.2", - "ssmarshal", - "usbd-hid", -] - -[[package]] -name = "embassy-usb-driver" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a" -dependencies = [ - "defmt 1.0.1", - "embedded-io-async 0.6.1", -] - -[[package]] -name = "embassy-usb-synopsys-otg" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbe46f4083109c7ea12a03ca61095d1e87c76fec52c7ca9ee06a42935606dacb" -dependencies = [ - "critical-section", - "embassy-sync 0.8.0", - "embassy-usb-driver", -] - -[[package]] -name = "embedded-can" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "embedded-graphics" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe" -dependencies = [ - "az", - "byteorder", - "embedded-graphics-core", - "float-cmp", - "micromath", -] - -[[package]] -name = "embedded-graphics-core" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f" -dependencies = [ - "az", - "byteorder", -] - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - -[[package]] -name = "embedded-hal-async" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" -dependencies = [ - "embedded-hal 1.0.0", -] - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "embedded-io" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" - -[[package]] -name = "embedded-io-async" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" -dependencies = [ - "embedded-io 0.6.1", -] - -[[package]] -name = "embedded-io-async" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" -dependencies = [ - "embedded-io 0.7.1", -] - -[[package]] -name = "embedded-storage" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" - -[[package]] -name = "embedded-storage-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" -dependencies = [ - "embedded-storage", -] - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "enumset" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "esp-backtrace" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3318413fb566c7227387f67736cf70cd74d80a11f2bb31c7b95a9eb48d079669" -dependencies = [ - "cfg-if", - "defmt 1.0.1", - "document-features", - "esp-config", - "esp-metadata-generated", - "esp-println", - "heapless 0.9.2", - "riscv", - "semihosting", - "xtensa-lx", -] - -[[package]] -name = "esp-config" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102871054f8dd98202177b9890cb4b71d0c6fe1f1413b7a379a8e0841fc2473c" -dependencies = [ - "document-features", - "esp-metadata-generated", - "serde", - "serde_yaml", - "somni-expr", -] - -[[package]] -name = "esp-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54786287c0a61ca0f78cb0c338a39427551d1be229103b4444591796c579e093" -dependencies = [ - "bitfield 0.19.4", - "bitflags 2.11.0", - "bytemuck", - "cfg-if", - "critical-section", - "delegate", - "digest", - "document-features", - "embassy-embedded-hal 0.5.0", - "embassy-futures", - "embassy-sync 0.7.2", - "embassy-usb-driver", - "embassy-usb-synopsys-otg", - "embedded-can", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-io 0.6.1", - "embedded-io 0.7.1", - "embedded-io-async 0.6.1", - "embedded-io-async 0.7.0", - "enumset", - "esp-config", - "esp-hal-procmacros", - "esp-metadata-generated", - "esp-riscv-rt", - "esp-rom-sys", - "esp-sync", - "esp-synopsys-usb-otg", - "esp32", - "esp32c2", - "esp32c3", - "esp32c6", - "esp32h2", - "esp32s2", - "esp32s3", - "fugit", - "instability", - "nb 1.1.0", - "paste", - "portable-atomic", - "rand_core 0.6.4", - "rand_core 0.9.5", - "riscv", - "strum", - "ufmt-write", - "xtensa-lx", - "xtensa-lx-rt", -] - -[[package]] -name = "esp-hal-procmacros" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e025a7a7a0affdb4ff913b5c4494aef96ee03d085bf83c27453ae3a71d50da6" -dependencies = [ - "document-features", - "object", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.117", - "termcolor", -] - -[[package]] -name = "esp-metadata-generated" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a93e39c8ad8d390d248dc7b9f4b59a873f313bf535218b8e2351356972399e3" - -[[package]] -name = "esp-println" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a30e6c9fbcc01c348d46706fef8131c7775ab84c254a3cd65d0cd3f6414d592" -dependencies = [ - "defmt 1.0.1", - "document-features", - "esp-metadata-generated", - "esp-sync", - "log", - "portable-atomic", -] - -[[package]] -name = "esp-riscv-rt" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502744a5b1e7268d27fd2a4e56ad45efe42ead517d6c517a6961540de949b0ee" -dependencies = [ - "document-features", - "riscv", - "riscv-rt", -] - -[[package]] -name = "esp-rom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd66cccc6dd2d13e9f33668a57717ab14a6d217180ec112e6be533de93e7ecbf" -dependencies = [ - "cfg-if", - "document-features", - "esp-metadata-generated", -] - -[[package]] -name = "esp-rtos" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ec711c8d06e79c67b75d01595539e86b0aac209643af98ca87a12250428b3" -dependencies = [ - "cfg-if", - "document-features", - "embassy-executor", - "embassy-sync 0.7.2", - "embassy-time-driver", - "embassy-time-queue-utils", - "esp-config", - "esp-hal", - "esp-hal-procmacros", - "esp-metadata-generated", - "esp-sync", - "portable-atomic", -] - -[[package]] -name = "esp-sync" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44974639b4e88914f83fe60d2832c00276657d7d857628fdfc966cc7302e8a8" -dependencies = [ - "cfg-if", - "document-features", - "embassy-sync 0.6.2", - "embassy-sync 0.7.2", - "esp-metadata-generated", - "riscv", - "xtensa-lx", -] - -[[package]] -name = "esp-synopsys-usb-otg" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8938451cb19032f13365328ea66ab38c8d16deecdf322067442297110eb74468" -dependencies = [ - "critical-section", - "embedded-hal 0.2.7", - "ral-registers", - "usb-device", - "vcell", -] - -[[package]] -name = "esp32" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76170a463d18f888a1ad258031901036fd827a9ef126733053ba5f8739fb0c8" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32c2" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62cf8932966b8d445b6f1832977b468178f0a84effb2e9fda89f60c24d45aa3" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32c3" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356af3771d0d6536c735bf71136594f4d1cbb506abf6e0c51a6639e9bf4e7988" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32c6" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5e511df672d79cd63365c92045135e01ba952b6bddd25b660baff5e1110f6b" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32h2" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4a50bbd1380931e095e0973b9b12f782a9c481f2edf1f7c42e7eb4ff736d6d" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32s2" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98574d4c577fbe888fe3e6df7fc80d25a05624d9998f7d7de1500ae21fcca78f" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32s3" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1810d8ee4845ef87542af981e38eb80ab531d0ef1061e1486014ab7af74c337a" -dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "fixed" -version = "1.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "fugit" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" -dependencies = [ - "gcd", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", -] - -[[package]] -name = "gcd" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", -] - -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - -[[package]] -name = "instability" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" -dependencies = [ - "darling 0.23.0", - "indoc", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lora-modulation" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c96c4a41a1f1ad04de765306e1829c7007a37898fe4033204fa82cf2e055624" - -[[package]] -name = "lora-phy" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61471c3b2909789e3332083577f6cf6c41a4fcf37674ef15156bcbb20504ac65" -dependencies = [ - "defmt 0.3.100", - "embedded-hal 1.0.0", - "embedded-hal-async", - "lora-modulation", - "num-traits", -] - -[[package]] -name = "maybe-async-cfg" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "pulldown-cmark", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memo-map" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" - -[[package]] -name = "micromath" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" - -[[package]] -name = "minijinja" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328251e58ad8e415be6198888fc207502727dc77945806421ab34f35bf012e7d" -dependencies = [ - "memo-map", - "serde", -] - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - -[[package]] -name = "nrf-pac" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddd547fab92b0553ff37032034fd04ce8b8b60d3f9a83f89ea815dfd0400336" -dependencies = [ - "cortex-m", - "cortex-m-rt", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "owo-colors" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" - -[[package]] -name = "panic-probe" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" -dependencies = [ - "cortex-m", - "defmt 1.0.1", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulldown-cmark" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" -dependencies = [ - "bitflags 2.11.0", - "memchr", - "unicase", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ral-registers" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46b71a9d9206e8b46714c74255adcaea8b11e0350c1d8456165073c3f75fc81a" - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" - -[[package]] -name = "riscv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" -dependencies = [ - "critical-section", - "embedded-hal 1.0.0", - "paste", - "riscv-macros", - "riscv-pac", -] - -[[package]] -name = "riscv-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "riscv-pac" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" - -[[package]] -name = "riscv-rt" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d07b9f3a0eff773fc4df11f44ada4fa302e529bff4b7fe7e6a4b98a65ce9174" -dependencies = [ - "riscv", - "riscv-pac", - "riscv-rt-macros", - "riscv-target-parser", -] - -[[package]] -name = "riscv-rt-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def519ddeeb5e43c2b4fc3952c27b3a86782fc05192f322b2309125cd85b1fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "riscv-target-parser" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1376b15f3ff160e9b1e8ea564ce427f2f6fcf77528cc0a8bf405cb476f9cea7" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "semihosting" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e4abf97879f4e80db69a9fba7bd64998e9bdad25f58ef045a778e191172fd4" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "somni-expr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed9b7648d5e8b2df6c5e49940c54bcdd2b4dd71eafc6e8f1c714eb4581b0f53" -dependencies = [ - "somni-parser", -] - -[[package]] -name = "somni-parser" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c" - -[[package]] -name = "ssd1306" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea6aac2d078bbc71d9b8ac3f657335311f3b6625e9a1a96ccc29f5abfa77c56" -dependencies = [ - "display-interface", - "display-interface-i2c", - "display-interface-spi", - "embedded-graphics-core", - "embedded-hal 1.0.0", - "embedded-hal-async", - "maybe-async-cfg", -] - -[[package]] -name = "ssmarshal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" -dependencies = [ - "encode_unicode", - "serde", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_cell" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.9+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" -dependencies = [ - "winnow", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucobs" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fbab511bafd13c109aff41f67d4c4426bf919a6670647e69f0d2af95ac01d" - -[[package]] -name = "ufmt-write" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "usb-device" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" -dependencies = [ - "heapless 0.8.0", - "portable-atomic", -] - -[[package]] -name = "usbd-hid" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68beab087e4971a2fe76f631478b0e91d39593f58efd2775026ce6dc07a7bac6" -dependencies = [ - "usb-device", - "usbd-hid-macros", -] - -[[package]] -name = "usbd-hid-descriptors" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b297f021719c4308d5d0c61b6c1e7c6b3ba383deba774b49aa5484f996bdb8f1" -dependencies = [ - "bitfield 0.14.0", -] - -[[package]] -name = "usbd-hid-macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011a3219e0933f5b3ad7dc90d9a66541a967d084c98c067deed1cd608e557ed7" -dependencies = [ - "byteorder", - "hashbrown 0.13.2", - "log", - "proc-macro2", - "quote", - "serde", - "syn 2.0.117", - "usbd-hid-descriptors", -] - -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" -dependencies = [ - "vcell", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "winnow" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" -dependencies = [ - "memchr", -] - -[[package]] -name = "xtensa-lx" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e012d667b0aa6d2592ace8ef145a98bff3e76cca7a644f4181ecd7a916ed289b" -dependencies = [ - "critical-section", -] - -[[package]] -name = "xtensa-lx-rt" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8709f037fb123fe7ff146d2bce86f9dc0dfc53045c016bfd9d703317b6502845" -dependencies = [ - "document-features", - "xtensa-lx", - "xtensa-lx-rt-proc-macros", -] - -[[package]] -name = "xtensa-lx-rt-proc-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fb42cd29c42f8744c74276e9f5bee7b06685bbe5b88df891516d72cb320450" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml deleted file mode 100644 index f65fd306..00000000 --- a/firmware/Cargo.toml +++ /dev/null @@ -1,127 +0,0 @@ -[workspace] -members = ["."] - -[package] -name = "donglora" -version = "0.2.0" -edition = "2021" -build = "build.rs" -authors = ["Stephen Waits "] -license = "MIT" -description = "Transparent LoRa radio over USB — plug in a board, talk LoRa from your host" -repository = "https://github.com/swaits/donglora" -keywords = ["lora", "radio", "embedded", "usb", "firmware"] -categories = ["embedded", "hardware-support", "no-std"] - -# UPGRADE BLOCKED: embassy-executor 0.10 and ucobs 0.3 need Rust >= 1.93 -# stable. The ESP Xtensa toolchain (espup) is a nightly fork that may not -# support the required language features or pass MSRV gating. Revisit when -# espup catches up. Check: rustup run esp rustc --version -[dependencies] -cfg-if = "1.0" -critical-section = "1.2" -defmt = "1.0" -embedded-hal = "1.0" -embedded-hal-async = "1.0" -embassy-embedded-hal = "0.6" -embassy-executor = "0.9" -embassy-sync = "0.8" -embassy-time = "0.5" -embassy-usb = { version = "0.6", features = ["defmt"] } -embassy-usb-driver = "0.2" -ucobs = { version = "0.3", features = ["legacy-msrv"] } -heapless = "0.9" -lora-phy = "3.0" -static_cell = "2.1" - -# Display rendering -display-interface = "0.5" -embedded-graphics = "0.8" -ssd1306 = { version = "0.10", features = ["async"] } - -# Async IO traits and combinators -embedded-io-async = "0.7" -embassy-futures = "0.1" - -# ESP32-S3 (Heltec V3) — requires esp toolchain (espup) -esp-backtrace = { version = "0.18", optional = true, features = [ - "esp32s3", - "panic-handler", - "defmt", -] } -esp-hal = { version = "1.0", optional = true, features = [ - "esp32s3", - "unstable", -] } -esp-println = { version = "0.16", optional = true, features = [ - "esp32s3", - "defmt-espflash", -] } -esp-rtos = { version = "0.2", optional = true, features = [ - "esp32s3", - "embassy", -] } - -# nRF52840 (RAK 4631) -cortex-m = { version = "0.7", optional = true, features = ["critical-section-single-core"] } -cortex-m-rt = { version = "0.7", optional = true, features = ["set-vtor"] } -defmt-rtt = { version = "1.0", optional = true } -embassy-nrf = { version = "0.10", optional = true, features = [ - "nrf52840", - "gpiote", - "time-driver-rtc1", - "defmt", -] } -panic-probe = { version = "1.0", optional = true, features = ["print-defmt"] } - -[build-dependencies] -minijinja = "2.18" -owo-colors = "4.3" - -[features] -heltec_v3 = [ - "dep:esp-backtrace", - "dep:esp-hal", - "dep:esp-println", - "dep:esp-rtos", -] -heltec_v3_uart = [ - "dep:esp-backtrace", - "dep:esp-hal", - "dep:esp-println", - "dep:esp-rtos", -] -heltec_v4 = [ - "dep:esp-backtrace", - "dep:esp-hal", - "dep:esp-println", - "dep:esp-rtos", -] -rak_wisblock_4631 = [ - "dep:cortex-m", - "dep:cortex-m-rt", - "dep:defmt-rtt", - "dep:embassy-nrf", - "dep:panic-probe", - "embassy-executor/arch-cortex-m", - "embassy-executor/executor-thread", -] -wio_tracker_l1 = [ - "dep:cortex-m", - "dep:cortex-m-rt", - "dep:defmt-rtt", - "dep:embassy-nrf", - "dep:panic-probe", - "embassy-executor/arch-cortex-m", - "embassy-executor/executor-thread", -] - -[lints.clippy] -undocumented_unsafe_blocks = "deny" - -[profile.release] -codegen-units = 1 -lto = "fat" -opt-level = 3 # "s"/"z" break Xtensa (l32r literal range exceeded) -panic = "abort" -strip = true diff --git a/firmware/PROTOCOL.md b/firmware/PROTOCOL.md deleted file mode 100644 index 5a5f999c..00000000 --- a/firmware/PROTOCOL.md +++ /dev/null @@ -1,291 +0,0 @@ -# DongLoRa USB Protocol - -DongLoRa communicates over USB CDC-ACM (virtual serial port). -No baud rate configuration needed. - -**USB identifiers:** VID `1209`, PID `5741` - -## Framing - -Each message is [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing)-encoded -and terminated with a `0x00` sentinel byte: - -``` -[COBS-encoded bytes] [0x00] -``` - -COBS guarantees `0x00` never appears in the encoded data, so the -sentinel unambiguously marks frame boundaries. USB packets are -chunked at 64 bytes. - -## Encoding - -All integers are **fixed-size little-endian**. No variable-length -encoding. Every field has a known size at a known offset. - -| Type | Size | Encoding | -|------|------|----------| -| `u8` | 1 | Raw byte | -| `i8` | 1 | Raw byte (two's complement) | -| `u16` | 2 | Little-endian | -| `i16` | 2 | Little-endian (two's complement) | -| `u32` | 4 | Little-endian | - -## RadioConfig (13 bytes) - -| Offset | Size | Field | Type | Valid range | -|--------|------|-------|------|-------------| -| 0 | 4 | `freq_hz` | u32 LE | 150,000,000 – 960,000,000 | -| 4 | 1 | `bw` | u8 | 0–9 (see bandwidth table) | -| 5 | 1 | `sf` | u8 | 5–12 | -| 6 | 1 | `cr` | u8 | 5–8 (coding rate denominator) | -| 7 | 2 | `sync_word` | u16 LE | any | -| 9 | 1 | `tx_power_dbm` | i8 | board-dependent, or `-128` for max | -| 10 | 2 | `preamble_len` | u16 LE | 6–65535, or `0` for default (16) | -| 12 | 1 | `cad` | u8 | 0 = disabled, non-zero = enabled | - -Set `tx_power_dbm` to `-128` (0x80) to use the board's maximum -TX power. The firmware resolves this to the actual hardware max. - -Set `preamble_len` to `0` to use the firmware default of 16 symbols. -Longer preambles improve reliability in mesh/noisy environments at -the cost of slightly longer air time. - -Set `cad` to `1` (or any non-zero value) to enable Channel Activity -Detection (listen-before-talk) before each transmission. The radio -checks for channel activity and waits for a clear channel before -transmitting. Set to `0` to disable. Default: enabled. - -## Commands (Host → Firmware) - -Each command is a tag byte followed by fixed-size fields. - -| Tag | Command | Payload | -|-----|---------|---------| -| 0 | Ping | — | -| 1 | GetConfig | — | -| 2 | SetConfig | RadioConfig (10 bytes) | -| 3 | StartRx | — | -| 4 | StopRx | — | -| 5 | Transmit | has_config (1) + [RadioConfig if 1] + len (u16 LE) + payload | -| 6 | DisplayOn | — | -| 7 | DisplayOff | — | -| 8 | GetMac | — | - -## Responses (Firmware → Host) - -| Tag | Response | Payload | -|-----|----------|---------| -| 0 | Pong | — | -| 1 | Config | RadioConfig (10 bytes) | -| 2 | RxPacket | rssi (i16 LE) + snr (i16 LE) + len (u16 LE) + payload | -| 3 | TxDone | — | -| 4 | Ok | — | -| 5 | Error | code (u8) | -| 6 | MacAddress | 6 raw bytes (board's MAC/device address) | - -## Bandwidth - -| Value | Bandwidth | -|-------|-----------| -| 0 | 7.8 kHz | -| 1 | 10.4 kHz | -| 2 | 15.6 kHz | -| 3 | 20.8 kHz | -| 4 | 31.25 kHz | -| 5 | 41.7 kHz | -| 6 | 62.5 kHz | -| 7 | 125 kHz | -| 8 | 250 kHz | -| 9 | 500 kHz | - -## Error Codes - -| Value | Error | Meaning | -|-------|-------|---------| -| 0 | InvalidConfig | Radio config validation failed | -| 1 | RadioBusy | Radio busy (e.g. RX restart failed) | -| 2 | TxTimeout | Transmission timed out | -| 3 | CrcError | CRC error on received packet | -| 4 | NotConfigured | SetConfig required first | -| 5 | NoDisplay | No display attached | - -## Worked Example - -Configure for 915 MHz, 125 kHz BW, SF7, CR 4/5, max TX power, default preamble, CAD on: - -### 1. Build the bytes - -```python -import struct -config = struct.pack(" 32: - return INVALID # bad metadata, discard or flag - - # Step 2: check against demodulation floor - min_snr = -2.5 * (sf - 4) - margin = snr - min_snr # dB above the floor - - if margin < 0: - return UNRELIABLE # below floor, likely corrupt - if margin < 3: - return MARGINAL # near floor, may contain errors - return GOOD # comfortable margin -``` - -**What to do with each grade:** - -- **GOOD** — process normally. -- **MARGINAL** — process, but prefer application-layer integrity - checks (checksums, sequence numbers, known-format validation) - before acting on the payload. Log for diagnostics. -- **UNRELIABLE** — log for diagnostics but do not trust the payload - for routing decisions, command execution, or retransmission. - Applications may still display the data with a warning. -- **INVALID** — discard. The SNR metadata itself is corrupt. - -### Application-layer defenses - -For safety-critical or command-and-control applications, do not rely -solely on the radio CRC. Add your own integrity checks: - -- An application-layer CRC or hash over the payload -- Sequence numbers to detect duplicates and missing packets -- Known-format validation (magic bytes, expected lengths) -- Require acknowledgement before acting on commands - -### Radio config impact - -Wider bandwidth and lower spreading factor increase throughput but -reduce sensitivity, making low-SNR packets more common in marginal -links. If your application sees frequent MARGINAL/UNRELIABLE packets, -consider increasing SF or decreasing BW to improve link margin at the -cost of air time. - -## Host Implementation Checklist - -1. Open USB serial port (find by VID:PID `1209:5741`) -2. Implement COBS encode/decode (libraries exist for every language) -3. Pack commands with `struct.pack` or equivalent — all fields are fixed-size LE -4. Optionally send `GetMac`, read back `MacAddress` (useful with multiple dongles) -5. Send `SetConfig`, read back `Ok` -6. Send `StartRx`, read back `Ok` -7. Loop: read frames, decode `RxPacket` (rssi, snr, payload at known offsets) -8. To transmit: send `Transmit`, read back `TxDone` - -See `examples/` for working Python implementations. diff --git a/firmware/README.md b/firmware/README.md deleted file mode 100644 index f997b623..00000000 --- a/firmware/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# DongLoRa Firmware - -Embedded Rust firmware for DongLoRa boards. Exposes the LoRa radio over -USB CDC-ACM using a COBS-framed binary protocol. - -## Building - -```sh -just setup # one-time toolchain install -just build heltec_v4 # build for a specific board -just flash heltec_v4 # build + flash -just check-all # compile-check all boards -just clippy heltec_v4 # lint -just test # host-side protocol unit tests -``` - -All commands are run from the `firmware/` directory. - -## Supported Boards - -| Board | Feature | MCU | Radio | USB | Target | -|-------|---------|-----|-------|-----|--------| -| Heltec V3 | `heltec_v3` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf | -| Heltec V3 (UART) | `heltec_v3_uart` | ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf | -| Heltec V4 | `heltec_v4` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf | -| RAK WisBlock 4631 | `rak_wisblock_4631` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf | -| Wio Tracker L1 | `wio_tracker_l1` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf | - -### Heltec V3 USB Note - -The default `heltec_v3` build uses **native USB CDC-ACM**, which requires a -hardware modification to your board. Stock Heltec V3 boards route USB through a -CP2102 bridge chip to UART — the ESP32-S3's native USB pins (GPIO19/GPIO20) are -routed to header pins but not connected to the USB connector. - -**To enable native USB on a Heltec V3:** - -1. Solder 0-ohm (or 22-ohm) resistors at pads **R29** (D+) and **R3** (D-) -2. Disconnect the CP2102 from the USB connector's D+/D- lines (cut traces or - remove the CP2102 bridge resistors) - -After this mod, the board appears as `/dev/ttyACM0` with the DongLoRa VID:PID -(1209:5741), just like the V4 and nRF boards. - -**If you have a stock (unmodified) V3**, use `heltec_v3_uart` instead: - -```sh -just build heltec_v3_uart -just flash heltec_v3_uart -``` - -This builds firmware that communicates via the CP2102 bridge (appears as -`/dev/ttyUSB0`). - -## Adding a Board - -See [src/board/PORTING.md](src/board/PORTING.md). - -## Protocol - -See [PROTOCOL.md](PROTOCOL.md) for the complete wire format specification. diff --git a/firmware/TODO.md b/firmware/TODO.md deleted file mode 100644 index 1ce960ad..00000000 --- a/firmware/TODO.md +++ /dev/null @@ -1,4 +0,0 @@ -- LED blinking on TX/RX? colored? -- publish new client, mux, bridge (updates in client for UART crap) -- split out mux and client repos -- move to GH organization diff --git a/firmware/build.rs b/firmware/build.rs deleted file mode 100644 index 75f1ce76..00000000 --- a/firmware/build.rs +++ /dev/null @@ -1,121 +0,0 @@ -use minijinja::{context, Environment}; -use owo_colors::OwoColorize; -use std::collections::HashMap; -use std::fs; - -fn main() { - // Protocol tests run on the host without a board feature. - if std::env::var_os("DONGLORA_HOST_TEST").is_some() { - return; - } - - let board_dir = "src/board"; - - // ── Discover boards vs helpers by content ─────────────────────── - // - // A .rs file in src/board/ that implements LoRaBoard is a board. - // Everything else (traits.rs, esp32s3.rs, etc.) is a helper module. - // No exclusion lists — detection is based on what the file contains. - let mut boards: Vec = Vec::new(); - - for entry in fs::read_dir(board_dir).expect("failed to read src/board directory") { - let p = match entry { - Ok(e) => e.path(), - Err(_) => continue, - }; - if !p.is_file() || p.extension().and_then(|e| e.to_str()) != Some("rs") { - continue; - } - let stem = match p.file_stem().and_then(|s| s.to_str()) { - Some("mod") => continue, // Skip generated file - Some(s) => s.to_string(), - None => continue, - }; - let content = match fs::read_to_string(&p) { - Ok(c) => c, - Err(_) => continue, - }; - if content.contains("LoRaBoard for Board") { - boards.push(stem); - } - } - - boards.sort(); // Deterministic ordering - - // ── Validate exactly one board feature is enabled ─────────────── - let enabled: Vec<_> = std::env::vars() - .filter(|(k, _)| k.starts_with("CARGO_FEATURE_")) - .map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase()) - .filter(|f| boards.contains(f)) - .collect(); - - match enabled.len() { - 0 => panic!( - "\n\n{}\n", - format!("No board selected! Found: {:?}", boards) - .red() - .bold() - ), - 1 => println!("cargo:rustc-cfg=board_selected"), - _ => panic!( - "\n\n{}\n", - format!("Conflict: Multiple boards enabled {:?}", enabled) - .yellow() - .bold() - ), - } - - // ── Auto-discover helper modules used by board files ──────────── - // - // Scan each board file for `use super::` to find which helper - // modules they depend on. Generate cfg gates so helpers compile only - // when a dependent board is selected. - let mut helper_boards: HashMap> = HashMap::new(); - - for board in &boards { - let path = format!("{}/{}.rs", board_dir, board); - if let Ok(content) = fs::read_to_string(&path) { - for line in content.lines() { - let trimmed = line.trim(); - if let Some(rest) = trimmed.strip_prefix("use super::") { - let helper = rest - .split(|c: char| !c.is_alphanumeric() && c != '_') - .next() - .unwrap_or(""); - if !helper.is_empty() && helper != "traits" { - helper_boards - .entry(helper.to_string()) - .or_default() - .push(board.clone()); - } - } - } - } - } - - // ── Render board module template ──────────────────────────────── - let mut env = Environment::new(); - let template = fs::read_to_string("src/board/mod.rs.j2").expect("missing src/board/mod.rs.j2"); - env.add_template("mod", &template) - .expect("failed to parse board template"); - let rendered = env - .get_template("mod") - .expect("template not found") - .render(context!(boards => boards, helpers => helper_boards)) - .expect("failed to render board module"); - fs::write("src/board/mod.rs", rendered).expect("failed to write src/board/mod.rs"); - - println!("cargo:rerun-if-changed=src/board/mod.rs.j2"); - println!("cargo:rerun-if-changed={}", board_dir); - - // cortex-m-rt's link.x does `INCLUDE memory.x`. Copy our nRF layout into - // OUT_DIR so it's only visible to ARM builds. - let target = std::env::var("TARGET").unwrap_or_default(); - if !target.starts_with("xtensa") { - let out_dir = std::env::var("OUT_DIR").unwrap(); - fs::copy("ld/nrf52840-memory.x", format!("{out_dir}/memory.x")) - .expect("failed to copy ld/nrf52840-memory.x"); - println!("cargo:rustc-link-search={out_dir}"); - } - println!("cargo:rerun-if-changed=ld/nrf52840-memory.x"); -} diff --git a/firmware/justfile b/firmware/justfile deleted file mode 100644 index 34880717..00000000 --- a/firmware/justfile +++ /dev/null @@ -1,211 +0,0 @@ -set shell := ["bash", "-c"] - -# Board definitions: feature target chip -# Xtensa targets use nightly cargo + esp rustc + -Zbuild-std=core. -# Tool versions are pinned in mise.toml; run `just setup` to install everything. -heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3" -heltec_v3_uart := "heltec_v3_uart xtensa-esp32s3-none-elf esp32s3" -heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3" -rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA" -wio_tracker_l1 := "wio_tracker_l1 thumbv7em-none-eabihf nRF52840_xxAA" - -builds_dir := "builds" -version := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml` - -# All known boards -boards := "heltec_v3 heltec_v3_uart heltec_v4 rak_wisblock_4631 wio_tracker_l1" - -# Install all required tools and toolchains -setup: - mise trust --yes - mise install - @# ESP Xtensa toolchain (not managed by mise) - @if ! rustup toolchain list | grep -q "^esp"; then \ - echo "ESP toolchain not found, installing via espup (this may take a while)..."; \ - espup install || { echo "error: espup install failed" >&2; exit 1; }; \ - fi - @# nightly rust-src for -Zbuild-std - @if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \ - rustup component add rust-src --toolchain nightly; \ - fi - @# ARM target - @rustup target list --installed | grep -q "^thumbv7em-none-eabihf$" || rustup target add thumbv7em-none-eabihf - -# Build release firmware for all boards, installing toolchains as needed -build-all: - @trap 'exit 130' INT; \ - for board in {{boards}}; do \ - if just _can_build $board; then \ - echo "── building $board ──"; \ - just build $board || exit $?; \ - else \ - echo "── skipping $board (toolchain install failed) ──"; \ - fi; \ - done - -# Check all boards compile, installing toolchains as needed -check-all: - @trap 'exit 130' INT; \ - for board in {{boards}}; do \ - if just _can_build $board; then \ - echo "── checking $board ──"; \ - just check $board || exit $?; \ - else \ - echo "── skipping $board (toolchain install failed) ──"; \ - fi; \ - done - -# Check a single board compiles -check board: - @just _cargo {{board}} "check --release" - -# Run clippy on a single board -clippy board: - @just _cargo {{board}} "clippy --release" "-- -D warnings" - -# Build release firmware and copy to builds/ with a readable name -build board profile="release": - @just _cargo {{board}} "build --{{profile}}" - @just _copy_firmware {{board}} {{profile}} - -# Build and flash a board (espflash for Xtensa, UF2 DFU for ARM) -flash board: - @just _ensure_tools - @just build {{board}} release - @read -r feat target chip <<< "$(just _info {{board}})"; \ - case "$target" in \ - xtensa-*) \ - port=$(just _find_port "303a:1001" "1209:5741" "10c4:ea60"); \ - if [ -n "$port" ]; then \ - echo "Using $port"; \ - espflash flash -p "$port" "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf"; \ - else \ - echo "No port found, falling back to espflash auto-detection..." >&2; \ - espflash flash "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf"; \ - fi ;; \ - *) just _flash_uf2 {{board}} ;; \ - esac - -# Flash ARM board via debug probe (requires J-Link or similar) -flash-probe board: - @just _ensure_tools - @just build {{board}} release - @read -r feat target chip <<< "$(just _info {{board}})"; \ - probe-rs run --chip $chip "{{builds_dir}}/donglora-{{board}}-v{{version}}.elf" - -# Show binary size for a release build -size board: - @just _cargo {{board}} "size --release" - -# Run host-side protocol unit tests -test: - DONGLORA_HOST_TEST=1 cargo test - -# Check for outdated dependencies -outdated: - @# embassy-executor 0.10 renamed arch-* → platform-* features. - @# esp-rtos 0.2 still pins embassy-executor ^0.9, blocking the upgrade. - @# cargo-outdated crashes trying to resolve 0.10 with the old feature name. - cargo outdated --exclude embassy-executor - -# ── Private helpers ─────────────────────────────────────────────────── - -# Install mise-managed tools if any are missing (fast no-op when current) -[private] -_ensure_tools: - @mise trust --yes . 2>/dev/null; mise install --quiet - -# Run a cargo command for a board. -# Xtensa: nightly cargo + esp rustc (via RUSTC override) + -Zbuild-std=core -[private] -_cargo board cmd extra_args="": - @just _ensure_tools - @read -r feat target chip <<< "$(just _info {{board}})"; \ - env=""; extra=""; \ - case "$target" in xtensa-*) \ - just _require_esp_toolchain; \ - [ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \ - env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly"; buildstd="-Zbuild-std=core,alloc";; \ - esac; \ - eval $env cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}} - -# Ensure a board's toolchain is available, auto-installing if needed -[private] -_can_build board: - @read -r feat target chip <<< "$(just _info {{board}})"; \ - case "$target" in \ - xtensa-*) just _require_esp_toolchain ;; \ - *) rustup target list --installed | grep -q "^$target$" || rustup target add "$target" ;; \ - esac - -# Ensure the ESP Xtensa toolchain is installed (espup installed via mise) -[private] -_require_esp_toolchain: - @just _ensure_tools - @if ! rustup toolchain list | grep -q "^esp"; then \ - echo "ESP toolchain not found, installing via espup (this may take a while)..." >&2; \ - espup install || { echo "error: espup install failed" >&2; exit 1; }; \ - fi; \ - if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \ - echo "Installing nightly rust-src (needed for -Zbuild-std)..." >&2; \ - rustup component add rust-src --toolchain nightly || { echo "error: failed to install rust-src" >&2; exit 1; }; \ - fi - -# Find a serial port matching any of the given VID:PID pairs (checked in order) -[private] -_find_port +vid_pids: - @for vidpid in {{vid_pids}}; do \ - vid="${vidpid%%:*}"; pid="${vidpid##*:}"; \ - for dev in /dev/ttyACM* /dev/ttyUSB*; do \ - [ -e "$dev" ] || continue; \ - info=$(udevadm info --query=property --name="$dev" 2>/dev/null) || continue; \ - dev_vid=$(echo "$info" | sed -n 's/^ID_VENDOR_ID=//p' | tr '[:upper:]' '[:lower:]'); \ - dev_pid=$(echo "$info" | sed -n 's/^ID_MODEL_ID=//p' | tr '[:upper:]' '[:lower:]'); \ - if [ "$dev_vid" = "$vid" ] && [ "$dev_pid" = "$pid" ]; then \ - echo "$dev"; exit 0; \ - fi; \ - done; \ - done - -[private] -_copy_firmware board profile: - @mkdir -p {{builds_dir}} - @read -r feat target chip <<< "$(just _info {{board}})"; \ - src="target/$target/{{profile}}/donglora"; \ - dst="{{builds_dir}}/donglora-{{board}}-v{{version}}"; \ - if [ -f "$src" ]; then \ - cp "$src" "$dst.elf"; echo "→ $dst.elf"; \ - case "$target" in \ - thumbv7em-*) \ - mise exec -- rust-objcopy -O ihex "$src" "$dst.hex"; \ - mise exec -- cargo-hex-to-uf2 hex-to-uf2 --input-path "$dst.hex" --output-path "$dst.uf2" --family nrf52840; \ - rm "$dst.hex"; \ - echo "→ $dst.uf2"; \ - ;; \ - esac; \ - fi - -# Copy UF2 to a mounted UF2 bootloader drive -[private] -_flash_uf2 board: - @uf2="{{builds_dir}}/donglora-{{board}}-v{{version}}.uf2"; \ - if [ ! -f "$uf2" ]; then \ - echo "error: $uf2 not found — run 'just build {{board}}' first" >&2; exit 1; \ - fi; \ - mnt="$(find /run/media -maxdepth 3 -name INFO_UF2.TXT -printf '%h\n' -quit 2>/dev/null)"; \ - if [ -z "$mnt" ]; then \ - echo "error: no UF2 drive found — double-tap reset to enter bootloader" >&2; exit 1; \ - fi; \ - echo "Copying to $mnt ..."; \ - cp "$uf2" "$mnt/"; \ - sync; \ - echo "Done — board will reboot automatically" - -[private] -_info name: - @if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \ - elif [ "{{name}}" == "heltec_v3_uart" ]; then echo "{{heltec_v3_uart}}"; \ - elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \ - elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \ - elif [ "{{name}}" == "wio_tracker_l1" ]; then echo "{{wio_tracker_l1}}"; \ - else echo "Unknown board: {{name}}" >&2; exit 1; fi diff --git a/firmware/ld/nrf52840-memory.x b/firmware/ld/nrf52840-memory.x deleted file mode 100644 index 8de03147..00000000 --- a/firmware/ld/nrf52840-memory.x +++ /dev/null @@ -1,16 +0,0 @@ -/* nRF52840 memory layout — Adafruit UF2 bootloader - * - * 0x00000 – 0x01000 MBR (Master Boot Record) - * 0x01000 – 0xEA000 Application ← we live here - * 0xEA000 – 0xF4000 Reserved (app data) - * 0xF4000 – 0xFC000 UF2 Bootloader - * 0xFF000 – 0x100000 Bootloader settings - * - * RAM: first 8 bytes reserved for MBR interrupt forwarding. - */ - -MEMORY -{ - FLASH : ORIGIN = 0x00001000, LENGTH = 0xE9000 - RAM : ORIGIN = 0x20000008, LENGTH = 255K -} diff --git a/firmware/mise.toml b/firmware/mise.toml deleted file mode 100644 index 2e61d348..00000000 --- a/firmware/mise.toml +++ /dev/null @@ -1,9 +0,0 @@ -[tools] -rust = "1.93" -just = "latest" - -"cargo:cargo-binutils" = "latest" # `cargo size`, `cargo objdump`, etc. -"cargo:cargo-hex-to-uf2" = "latest" # ELF → UF2 via Intel HEX for nRF52840 DFU -"cargo:espup" = "0.16" -"cargo:espflash" = "3" # 4.x has broken nix dependency on current stable Rust -"cargo:probe-rs-tools" = "0.31" diff --git a/firmware/src/board/PORTING.md b/firmware/src/board/PORTING.md deleted file mode 100644 index 7ed53189..00000000 --- a/firmware/src/board/PORTING.md +++ /dev/null @@ -1,96 +0,0 @@ -# Porting DongLoRa to a New Board - -## Steps - -### 1. Create the board file - -Create `src/board/your_board.rs`. Use an existing board as a template: -- ESP32-S3 boards: copy `heltec_v4.rs` (uses shared helpers from `board/esp32s3.rs` and `hal/esp32s3.rs`) -- nRF52840 boards: copy `rak_wisblock_4631.rs` (uses shared helpers from `hal/nrf52840.rs`) -- New MCU family: create a new `hal/.rs` with MCU primitives first - -`build.rs` auto-discovers your board file — any `.rs` file in `src/board/` that -implements `LoRaBoard for Board` is automatically detected as a board. Helper -modules (like `esp32s3.rs`) are auto-discovered via `use super::` imports -in board files. No template editing or exclusion lists needed. - -### 2. Define concrete types - -Your board must export these type aliases: - -```rust -pub type RadioDriver = ...; // Must work with lora_phy::LoRa -pub type UsbDriver = ...; // Must implement embassy_usb_driver::Driver<'static> - // (or UartDriver for UART boards) -pub type DisplayI2c = ...; // Must implement embedded_hal_async::i2c::I2c -pub type DisplayDriver = ...; // Must implement DrawTarget -``` - -### 3. Define peripheral bundles - -```rust -pub struct RadioParts { - pub driver: RadioDriver, - pub delay: embassy_time::Delay, -} - -pub struct UsbParts { // or UartParts for UART boards - pub driver: UsbDriver, -} - -pub struct DisplayParts { - pub i2c: DisplayI2c, -} -``` - -### 4. Implement the LoRaBoard trait - -```rust -use super::traits::{BoardParts, LoRaBoard}; - -impl LoRaBoard for Board { - const NAME: &'static str = "Your Board Name"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); // check your radio + PA - - type RadioParts = RadioParts; - type CommParts = UsbParts; // or UartParts - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - - fn init() -> Self { ... } - fn mac_address() -> [u8; 6] { ... } // read from efuse, FICR, etc. - fn into_parts(self) -> BoardParts { - // Initialize buses via hal::, construct drivers, return BoardParts - BoardParts { radio, host, display: Some(display_parts), mac: Self::mac_address() } - } -} -``` - -### 5. Add display init - -```rust -pub async fn create_display(i2c: DisplayI2c) -> Option { - // Construct and initialize your display driver (SSD1306, SH1106, etc.) -} -``` - -### 6. Add Cargo feature - -In `firmware/Cargo.toml`, add a feature with your board's HAL dependencies: - -```toml -[features] -your_board = ["dep:your-hal", ...] -``` - -### 7. Add to justfile - -Add your board's feature/target/chip to the board definitions at the top of `justfile`. - -### 8. Build and test - -```sh -just check your_board # Must compile -just clippy your_board # Must be clean -just build your_board # Produces firmware -``` diff --git a/firmware/src/board/esp32s3.rs b/firmware/src/board/esp32s3.rs deleted file mode 100644 index 1a1cda5c..00000000 --- a/firmware/src/board/esp32s3.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Shared types and peripheral wiring for ESP32-S3 boards. -//! -//! Delegates MCU-level init to `hal::esp32s3` and defines peripheral-level -//! types (RadioDriver, display) used by ESP32-S3 board files. -//! Each board defines its own comm types (UsbParts/UartParts) directly. - -use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_time::Delay; -use esp_hal::gpio::{Input, InputConfig, Level, Output, OutputConfig, Pull}; - -use lora_phy::iv::GenericSx126xInterfaceVariant; -use lora_phy::sx126x::{self, Sx1262, Sx126x}; - -use crate::hal::esp32s3 as mcu; - -// ── Re-export MCU types ───────────────────────────────────────────── - -pub use mcu::{I2cBus as DisplayI2c, SpiBus}; - -// ── Concrete peripheral types ─────────────────────────────────────── - -type Nss = Output<'static>; -type Iv = GenericSx126xInterfaceVariant, Input<'static>>; -type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>; -pub type RadioDriver = Sx126x; - -// ── Peripheral bundles ────────────────────────────────────────────── - -pub struct RadioParts { - pub driver: RadioDriver, - pub delay: Delay, -} - -pub struct DisplayParts { - pub i2c: DisplayI2c, -} - -// ── Display driver ────────────────────────────────────────────────── - -pub type DisplayDriver = ssd1306::Ssd1306Async< - ssd1306::prelude::I2CInterface, - ssd1306::size::DisplaySize128x64, - ssd1306::mode::BufferedGraphicsModeAsync, ->; - -/// Construct and initialize an SSD1306 display from raw I2C. -pub async fn create_display(i2c: DisplayI2c) -> Option { - use ssd1306::mode::DisplayConfigAsync; - use ssd1306::prelude::{Brightness, DisplayRotation}; - use ssd1306::size::DisplaySize128x64; - use ssd1306::{I2CDisplayInterface, Ssd1306Async}; - - let interface = I2CDisplayInterface::new(i2c); - let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) - .into_buffered_graphics_mode(); - - embassy_time::Timer::after_millis(100).await; - if display.init().await.is_err() { - defmt::error!("SSD1306 display init failed"); - return None; - } - let _ = display.set_brightness(Brightness::BRIGHTEST).await; - Some(display) -} - -// ── Shared peripheral init helpers ────────────────────────────────── - -/// Construct SX1262 radio from an initialized SPI bus. -pub fn init_radio( - spi_bus: &'static embassy_sync::mutex::Mutex, - nss_pin: esp_hal::peripherals::GPIO8<'static>, - reset_pin: esp_hal::peripherals::GPIO12<'static>, - dio1_pin: esp_hal::peripherals::GPIO14<'static>, - busy_pin: esp_hal::peripherals::GPIO13<'static>, -) -> RadioParts { - let nss = Output::new(nss_pin, Level::High, OutputConfig::default()); - let spi_device = SpiDevice::new(spi_bus, nss); - - let reset = Output::new(reset_pin, Level::High, OutputConfig::default()); - let dio1 = Input::new(dio1_pin, InputConfig::default().with_pull(Pull::Down)); - let busy = Input::new(busy_pin, InputConfig::default().with_pull(Pull::Down)); - - let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None) - .expect("SX1262 interface init"); - - let sx_config = sx126x::Config { - chip: Sx1262, - tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V8), - use_dcdc: true, - rx_boost: false, - }; - - RadioParts { - driver: Sx126x::new(spi_device, iv, sx_config), - delay: Delay, - } -} diff --git a/firmware/src/board/heltec_v3.rs b/firmware/src/board/heltec_v3.rs deleted file mode 100644 index cec9e691..00000000 --- a/firmware/src/board/heltec_v3.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Heltec WiFi LoRa 32 V3 — native USB (requires hardware mod). -//! -//! Stock V3 boards route USB through a CP2102 bridge to UART. To use native -//! USB CDC-ACM, solder R29 (D+) and R3 (D-) on the PCB and disconnect the -//! CP2102 from the USB data lines. See the project README for details. -//! -//! For stock (unmodified) V3 boards, use the `heltec_v3_uart` feature instead. - -use esp_hal::gpio::{Level, Output, OutputConfig}; -use static_cell::StaticCell; - -use super::esp32s3; -use super::traits::{BoardParts, LoRaBoard}; -use crate::driver::simple_led::SimpleLed; -use crate::hal::esp32s3 as mcu; - -pub use super::esp32s3::{ - create_display, DisplayDriver, DisplayParts, RadioDriver, RadioParts, -}; - -pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>; -pub type LedDriver = SimpleLed>; - -pub struct UsbParts { - pub driver: UsbDriver, -} - -pub struct Board { - p: esp_hal::peripherals::Peripherals, -} - -impl LoRaBoard for Board { - const NAME: &'static str = "Heltec V3"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); - - type RadioParts = RadioParts; - type CommParts = UsbParts; - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - type LedDriver = LedDriver; - - fn init() -> Self { - let p = esp_hal::init(esp_hal::Config::default()); - Self { p } - } - - fn mac_address() -> [u8; 6] { - mcu::mac_address() - } - - fn into_parts(self) -> BoardParts { - let p = self.p; - - mcu::start_timer(p.TIMG0); - - // Vext power: GPIO36, active LOW to enable peripherals - let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default()); - core::mem::forget(vext); - - let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11); - let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13); - - // Note: switches internal USB PHY from Serial-JTAG to OTG. - let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19); - static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new(); - let ep_out_buf = EP_OUT_BUF.init([0u8; 1024]); - let host = UsbParts { - driver: esp_hal::otg_fs::asynch::Driver::new( - usb_inst, - ep_out_buf, - esp_hal::otg_fs::asynch::Config::default(), - ), - }; - - // SSD1306 display reset: pulse GPIO21 low->high before I2C init - let mut display_rst = Output::new(p.GPIO21, Level::Low, OutputConfig::default()); - esp_hal::delay::Delay::new().delay_millis(10); - display_rst.set_high(); - esp_hal::delay::Delay::new().delay_millis(10); - core::mem::forget(display_rst); - - let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18); - let display = Some(DisplayParts { i2c }); - - // White LED on GPIO35 - let led_pin = Output::new(p.GPIO35, Level::Low, OutputConfig::default()); - let led = SimpleLed(led_pin); - - BoardParts { - radio, - host, - display, - led, - mac: Self::mac_address(), - } - } -} diff --git a/firmware/src/board/heltec_v3_uart.rs b/firmware/src/board/heltec_v3_uart.rs deleted file mode 100644 index 80c1b492..00000000 --- a/firmware/src/board/heltec_v3_uart.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Heltec WiFi LoRa 32 V3 — stock UART (via CP2102 bridge). -//! -//! This is for unmodified V3 boards where USB goes through the CP2102 -//! bridge to UART0. The board appears as /dev/ttyUSB* (not ttyACM*). -//! -//! If you've done the hardware mod (solder R29/R3, disconnect CP2102), -//! use the `heltec_v3` feature instead for native USB CDC-ACM support. - -use esp_hal::gpio::{Level, Output, OutputConfig}; - -use super::esp32s3; -use super::traits::{BoardParts, LoRaBoard}; -use crate::driver::simple_led::SimpleLed; -use crate::hal::esp32s3 as mcu; - -pub use super::esp32s3::{ - create_display, DisplayDriver, DisplayParts, RadioDriver, RadioParts, -}; - -pub type UartDriver = esp_hal::uart::Uart<'static, esp_hal::Async>; -pub type LedDriver = SimpleLed>; - -pub struct UartParts { - pub driver: UartDriver, -} - -pub struct Board { - p: esp_hal::peripherals::Peripherals, -} - -impl LoRaBoard for Board { - const NAME: &'static str = "Heltec V3 (UART)"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); - - type RadioParts = RadioParts; - type CommParts = UartParts; - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - type LedDriver = LedDriver; - - fn init() -> Self { - let p = esp_hal::init(esp_hal::Config::default()); - Self { p } - } - - fn mac_address() -> [u8; 6] { - mcu::mac_address() - } - - fn into_parts(self) -> BoardParts { - let p = self.p; - - mcu::start_timer(p.TIMG0); - - // Vext power: GPIO36, active LOW to enable peripherals - let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default()); - core::mem::forget(vext); - - let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11); - let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13); - - use esp_hal::uart::{Config as UartConfig, Uart}; - let host = UartParts { - driver: Uart::new(p.UART0, UartConfig::default()) - .expect("UART init") - .with_tx(p.GPIO43) - .with_rx(p.GPIO44) - .into_async(), - }; - - // SSD1306 display reset: pulse GPIO21 low->high before I2C init - let mut display_rst = Output::new(p.GPIO21, Level::Low, OutputConfig::default()); - esp_hal::delay::Delay::new().delay_millis(10); - display_rst.set_high(); - esp_hal::delay::Delay::new().delay_millis(10); - core::mem::forget(display_rst); - - let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18); - let display = Some(DisplayParts { i2c }); - - // White LED on GPIO35 - let led_pin = Output::new(p.GPIO35, Level::Low, OutputConfig::default()); - let led = SimpleLed(led_pin); - - BoardParts { - radio, - host, - display, - led, - mac: Self::mac_address(), - } - } -} diff --git a/firmware/src/board/heltec_v4.rs b/firmware/src/board/heltec_v4.rs deleted file mode 100644 index 3aa77c54..00000000 --- a/firmware/src/board/heltec_v4.rs +++ /dev/null @@ -1,90 +0,0 @@ -use esp_hal::gpio::{Level, Output, OutputConfig}; -use static_cell::StaticCell; - -use super::esp32s3; -use super::traits::{BoardParts, LoRaBoard}; -use crate::driver::simple_led::SimpleLed; -use crate::hal::esp32s3 as mcu; - -pub use super::esp32s3::{ - create_display, DisplayDriver, DisplayParts, RadioDriver, RadioParts, -}; - -pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>; - -pub type LedDriver = SimpleLed>; - -pub struct UsbParts { - pub driver: UsbDriver, -} - -pub struct Board { - p: esp_hal::peripherals::Peripherals, -} - -impl LoRaBoard for Board { - const NAME: &'static str = "Heltec V4"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); - - type RadioParts = RadioParts; - type CommParts = UsbParts; - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - type LedDriver = LedDriver; - - fn init() -> Self { - let p = esp_hal::init(esp_hal::Config::default()); - Self { p } - } - - fn mac_address() -> [u8; 6] { - mcu::mac_address() - } - - fn into_parts(self) -> BoardParts { - let p = self.p; - - mcu::start_timer(p.TIMG0); - - // Vext power: GPIO36, active LOW to enable peripherals - let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default()); - core::mem::forget(vext); - - let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11); - let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13); - - // Note: switches internal USB PHY from Serial-JTAG to OTG. - let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19); - static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new(); - let ep_out_buf = EP_OUT_BUF.init([0u8; 1024]); - let host = UsbParts { - driver: esp_hal::otg_fs::asynch::Driver::new( - usb_inst, - ep_out_buf, - esp_hal::otg_fs::asynch::Config::default(), - ), - }; - - // SSD1315 display reset: pulse GPIO21 low->high before I2C init - let mut display_rst = Output::new(p.GPIO21, Level::Low, OutputConfig::default()); - esp_hal::delay::Delay::new().delay_millis(10); - display_rst.set_high(); - esp_hal::delay::Delay::new().delay_millis(10); - core::mem::forget(display_rst); - - let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18); - let display = Some(DisplayParts { i2c }); - - // Orange LED on GPIO35 - let led_pin = Output::new(p.GPIO35, Level::Low, OutputConfig::default()); - let led = SimpleLed(led_pin); - - BoardParts { - radio, - host, - display, - led, - mac: Self::mac_address(), - } - } -} diff --git a/firmware/src/board/mod.rs.j2 b/firmware/src/board/mod.rs.j2 deleted file mode 100644 index ba6daa79..00000000 --- a/firmware/src/board/mod.rs.j2 +++ /dev/null @@ -1,16 +0,0 @@ -// @generated by build.rs — DO NOT EDIT -pub mod traits; -pub use traits::*; - -{% for helper, helper_features in helpers|items -%} -#[cfg(any({% for f in helper_features %}feature = "{{ f }}"{% if not loop.last %}, {% endif %}{% endfor %}))] -mod {{ helper }}; -{% endfor %} -cfg_if::cfg_if! { - {%- for board in boards %} - {% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] { - mod {{ board }}; - pub use {{ board }}::*; - } - {%- endfor %} -} diff --git a/firmware/src/board/rak_wisblock_4631.rs b/firmware/src/board/rak_wisblock_4631.rs deleted file mode 100644 index c17ccc19..00000000 --- a/firmware/src/board/rak_wisblock_4631.rs +++ /dev/null @@ -1,144 +0,0 @@ -use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; -use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; -use embassy_nrf::spim::{self, Spim}; -use embassy_nrf::twim; -use embassy_nrf::usb::Driver; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_time::Delay; - -use lora_phy::iv::GenericSx126xInterfaceVariant; -use lora_phy::sx126x::{self, Sx1262, Sx126x}; - -use super::traits::{BoardParts, LoRaBoard}; -use crate::hal::nrf52840 as mcu; - -// ── Concrete peripheral types ─────────────────────────────────────── - -type Nss = Output<'static>; -type Iv = GenericSx126xInterfaceVariant, Input<'static>>; -type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, mcu::SpiBus, Nss>; -pub type RadioDriver = Sx126x; -pub type UsbDriver = mcu::UsbNrfDriver; -pub type DisplayI2c = mcu::I2cBus; -pub type LedDriver = (); - -pub type DisplayDriver = ssd1306::Ssd1306Async< - ssd1306::prelude::I2CInterface, - ssd1306::size::DisplaySize128x64, - ssd1306::mode::BufferedGraphicsModeAsync, ->; - -// ── Peripheral bundles ────────────────────────────────────────────── - -pub struct RadioParts { - pub driver: RadioDriver, - pub delay: Delay, -} - -pub struct UsbParts { - pub driver: UsbDriver, -} - -pub struct DisplayParts { - pub i2c: DisplayI2c, -} - -// ── Display init ──────────────────────────────────────────────────── - -pub async fn create_display(i2c: DisplayI2c) -> Option { - use ssd1306::mode::DisplayConfigAsync; - use ssd1306::prelude::{Brightness, DisplayRotation}; - use ssd1306::size::DisplaySize128x64; - use ssd1306::{I2CDisplayInterface, Ssd1306Async}; - - let interface = I2CDisplayInterface::new(i2c); - let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) - .into_buffered_graphics_mode(); - - embassy_time::Timer::after_millis(100).await; - if display.init().await.is_err() { - defmt::error!("SSD1306 display init failed"); - return None; - } - let _ = display.set_brightness(Brightness::BRIGHTEST).await; - Some(display) -} - -// ── Board init ────────────────────────────────────────────────────── - -pub struct Board { - p: embassy_nrf::Peripherals, -} - -impl LoRaBoard for Board { - const NAME: &'static str = "RAK WisBlock 4631"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); - - type RadioParts = RadioParts; - type CommParts = UsbParts; - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - type LedDriver = (); - - fn init() -> Self { - let p = embassy_nrf::init(Default::default()); - Self { p } - } - - fn mac_address() -> [u8; 6] { - mcu::mac_address() - } - - fn into_parts(self) -> BoardParts { - let p = self.p; - - // ── SPI bus for SX1262 ────────────────────────────────── - let mut spi_cfg = spim::Config::default(); - spi_cfg.frequency = spim::Frequency::M1; - let spi = Spim::new(p.SPI3, mcu::Irqs, p.P1_11, p.P1_13, p.P1_12, spi_cfg); - let spi_bus = mcu::share_spi_bus(spi); - - let nss = Output::new(p.P1_10, Level::High, OutputDrive::Standard); - let spi_device = SpiDevice::new(spi_bus, nss); - - let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard); - let dio1 = Input::new(p.P1_15, Pull::Down); - let busy = Input::new(p.P1_14, Pull::Down); - - let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None) - .expect("SX1262 interface init"); - - let sx_config = sx126x::Config { - chip: Sx1262, - tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V8), - use_dcdc: true, - rx_boost: false, - }; - - let radio = RadioParts { - driver: Sx126x::new(spi_device, iv, sx_config), - delay: Delay, - }; - - // ── USB ───────────────────────────────────────────────── - let vbus = mcu::alloc_vbus_detect(true, false); - let host = UsbParts { - driver: Driver::new(p.USBD, mcu::Irqs, vbus), - }; - - // ── Display (optional RAK1921 SSD1306 OLED on I2C) ───── - let twim_buf = mcu::alloc_i2c_buffer(); - let i2c = embassy_nrf::twim::Twim::new( - p.TWISPI0, mcu::Irqs, p.P0_13, p.P0_14, twim::Config::default(), twim_buf, - ); - let display = Some(DisplayParts { i2c }); - - BoardParts { - radio, - host, - display, - led: (), - mac: Self::mac_address(), - } - } -} diff --git a/firmware/src/board/traits.rs b/firmware/src/board/traits.rs deleted file mode 100644 index 28b68124..00000000 --- a/firmware/src/board/traits.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Board abstraction trait. -//! -//! Every board must implement [`LoRaBoard`] with concrete associated types -//! for its radio, host communication, display, and LED peripherals. The -//! compiler enforces completeness — missing types or methods are compile errors. -//! -//! See `src/board/PORTING.md` for a step-by-step guide to adding a new board. - -use embedded_graphics::draw_target::DrawTarget; -use embedded_graphics::pixelcolor::BinaryColor; - -/// Async RGB LED control. Boards without an LED use `()` which is a no-op. -pub trait RgbLed { - fn set_rgb(&mut self, r: u8, g: u8, b: u8) -> impl core::future::Future; -} - -impl RgbLed for () { - async fn set_rgb(&mut self, _r: u8, _g: u8, _b: u8) {} -} - -/// Compile-time contract for a DongLoRa board. -pub trait LoRaBoard: Sized { - /// Human-readable board name (shown on display splash screen). - const NAME: &'static str; - - /// TX power range in dBm (min, max) for this board's radio + PA. - const TX_POWER_RANGE: (i8, i8); - - /// Radio peripheral bundle (driver + delay). - type RadioParts; - - /// Host communication peripheral bundle (USB or UART driver). - type CommParts; - - /// Display peripheral bundle (I2C bus for display init). - type DisplayParts; - - /// Concrete display driver type (must implement DrawTarget for rendering). - type DisplayDriver: DrawTarget; - - /// RGB LED driver. Boards without an LED use `()`. - type LedDriver: RgbLed; - - /// Initialize the board hardware. - fn init() -> Self; - - /// Read the board's unique hardware address (MAC, device ID, etc.). - fn mac_address() -> [u8; 6]; - - /// Decompose initialized board into peripheral bundles for each task. - fn into_parts(self) -> BoardParts; -} - -/// Peripheral bundles for each firmware task, produced by [`LoRaBoard::into_parts`]. -pub struct BoardParts { - pub radio: R, - pub host: C, - pub display: Option, - pub led: L, - pub mac: [u8; 6], -} diff --git a/firmware/src/board/wio_tracker_l1.rs b/firmware/src/board/wio_tracker_l1.rs deleted file mode 100644 index ccf1c2bd..00000000 --- a/firmware/src/board/wio_tracker_l1.rs +++ /dev/null @@ -1,147 +0,0 @@ -use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; -use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; -use embassy_nrf::spim::{self, Spim}; -use embassy_nrf::twim; -use embassy_nrf::usb::Driver; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_time::Delay; - -use lora_phy::iv::GenericSx126xInterfaceVariant; -use lora_phy::sx126x::{self, Sx1262, Sx126x}; - -use super::traits::{BoardParts, LoRaBoard}; -use crate::hal::nrf52840 as mcu; - -// ── Concrete peripheral types ─────────────────────────────────────── - -type Nss = Output<'static>; -type Iv = GenericSx126xInterfaceVariant, Input<'static>>; -type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, mcu::SpiBus, Nss>; -pub type RadioDriver = Sx126x; -pub type UsbDriver = mcu::UsbNrfDriver; -pub type DisplayI2c = mcu::I2cBus; - -pub type LedDriver = crate::driver::simple_led::SimpleLed>; - -pub type DisplayDriver = crate::driver::sh1106::Sh1106; - -// ── Peripheral bundles ────────────────────────────────────────────── - -pub struct RadioParts { - pub driver: RadioDriver, - pub delay: Delay, -} - -pub struct UsbParts { - pub driver: UsbDriver, -} - -pub struct DisplayParts { - pub i2c: DisplayI2c, -} - -// ── Display init ──────────────────────────────────────────────────── - -pub async fn create_display(i2c: DisplayI2c) -> Option { - let mut display = crate::driver::sh1106::Sh1106::new(i2c, 0x3D); - - embassy_time::Timer::after_millis(100).await; - if display.init().await.is_err() { - defmt::error!("SH1106 display init failed"); - return None; - } - let _ = display.set_brightness(0xFF).await; - Some(display) -} - -// ── Board init ────────────────────────────────────────────────────── - -pub struct Board { - p: embassy_nrf::Peripherals, -} - -impl LoRaBoard for Board { - const NAME: &'static str = "Wio Tracker L1"; - const TX_POWER_RANGE: (i8, i8) = (-9, 22); - - type RadioParts = RadioParts; - type CommParts = UsbParts; - type DisplayParts = DisplayParts; - type DisplayDriver = DisplayDriver; - type LedDriver = LedDriver; - - fn init() -> Self { - let mut config = embassy_nrf::config::Config::default(); - config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal; - config.lfclk_source = embassy_nrf::config::LfclkSource::ExternalXtal; - let p = embassy_nrf::init(config); - Self { p } - } - - fn mac_address() -> [u8; 6] { - mcu::mac_address() - } - - fn into_parts(self) -> BoardParts { - let p = self.p; - - // ── SPI bus for SX1262 ────────────────────────────────── - let mut spi_cfg = spim::Config::default(); - spi_cfg.frequency = spim::Frequency::M1; - let spi = Spim::new(p.SPI3, mcu::Irqs, p.P0_30, p.P0_03, p.P0_28, spi_cfg); - let spi_bus = mcu::share_spi_bus(spi); - - let nss = Output::new(p.P1_14, Level::High, OutputDrive::Standard); - let spi_device = SpiDevice::new(spi_bus, nss); - - let reset = Output::new(p.P1_07, Level::High, OutputDrive::Standard); - let dio1 = Input::new(p.P0_07, Pull::Down); - let busy = Input::new(p.P1_10, Pull::Down); - - // RF switch: RXEN on P1.08 (GPIO, toggled by lora_phy), TX via DIO2 (SX1262 internal). - let rx_enable = Output::new(p.P1_08, Level::Low, OutputDrive::Standard); - let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, Some(rx_enable), None) - .expect("SX1262 interface init"); - - let sx_config = sx126x::Config { - chip: Sx1262, - tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V8), - use_dcdc: true, - rx_boost: false, - }; - - let radio = RadioParts { - driver: Sx126x::new(spi_device, iv, sx_config), - delay: Delay, - }; - - // ── USB ───────────────────────────────────────────────── - let vbus = mcu::alloc_vbus_detect(true, true); - let host = UsbParts { - driver: Driver::new(p.USBD, mcu::Irqs, vbus), - }; - - // ── BAT_CTL: P0.04, active HIGH — enables power to OLED and battery sense - let bat_ctl = Output::new(p.P0_04, Level::High, OutputDrive::Standard); - core::mem::forget(bat_ctl); - - // ── Display (SH1106 OLED on I2C) ──────────────────────── - let twim_buf = mcu::alloc_i2c_buffer(); - let i2c = embassy_nrf::twim::Twim::new( - p.TWISPI0, mcu::Irqs, p.P0_06, p.P0_05, twim::Config::default(), twim_buf, - ); - let display = Some(DisplayParts { i2c }); - - // Green LED on P1.01 - let led_pin = Output::new(p.P1_01, Level::Low, OutputDrive::Standard); - let led = crate::driver::simple_led::SimpleLed(led_pin); - - BoardParts { - radio, - host, - display, - led, - mac: Self::mac_address(), - } - } -} diff --git a/firmware/src/channel.rs b/firmware/src/channel.rs deleted file mode 100644 index e431aaa1..00000000 --- a/firmware/src/channel.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Inter-task communication channels. -//! -//! Each channel connects exactly two tasks: -//! - [`CommandChannel`]: USB → Radio (host commands) -//! - [`ResponseChannel`]: Radio → USB (firmware responses) -//! - [`DisplayCommandChannel`]: USB → Display (on/off/reset) -//! - [`StatusWatch`]: Radio → Display (observable radio state) - -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::channel::Channel; -use embassy_sync::watch::Watch; - -use crate::protocol::{Command, RadioConfig, Response}; - -/// Display commands routed from host_task to display_task. -#[derive(Debug, Clone, Copy, defmt::Format)] -pub enum DisplayCommand { - On, - Off, - Reset, -} - -/// USB-to-display command channel (depth 4: display commands are rare events). -pub type DisplayCommandChannel = Channel; - -/// Host-to-radio command channel (depth 16: buffer bursty host command sequences). -pub type CommandChannel = Channel; - -/// Radio-to-host response channel (depth 32: buffer RX packets while USB writes). -pub type ResponseChannel = Channel; - -/// Observable radio status for the display task (2 receivers max). -/// Display always sees the latest state — intermediate updates may be -/// skipped, which is fine for a status display. -pub type StatusWatch = Watch; - -/// Current radio state exposed to observers (e.g. display). -#[derive(Debug, Clone, defmt::Format)] -pub struct RadioStatus { - pub state: RadioState, - pub config: Option, - pub rx_count: u32, - pub tx_count: u32, - pub last_rssi: Option, - pub last_snr: Option, -} - -/// Radio state machine states. -#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] -pub enum RadioState { - Idle, - Receiving, - Transmitting, -} - -impl Default for RadioStatus { - fn default() -> Self { - Self { - state: RadioState::Idle, - config: None, - rx_count: 0, - tx_count: 0, - last_rssi: None, - last_snr: None, - } - } -} diff --git a/firmware/src/display.rs b/firmware/src/display.rs deleted file mode 100644 index 0d29ba72..00000000 --- a/firmware/src/display.rs +++ /dev/null @@ -1,631 +0,0 @@ -//! OLED display dashboard: radio status, sparkline, splash screen. - -use embassy_executor::task; -use embassy_futures::select::{select3, Either3}; -use embassy_time::Timer; -use embedded_graphics::draw_target::DrawTarget; -use embedded_graphics::pixelcolor::BinaryColor; - -use crate::board::{Board, DisplayParts, LedDriver, LoRaBoard, RgbLed}; -use crate::channel::{DisplayCommand, DisplayCommandChannel, RadioState, RadioStatus, StatusWatch}; - -use render::RSSI_HISTORY_LEN; - -const BOARD_NAME: &str = Board::NAME; - -/// Duration per sparkline slot. 128 slots * 1s = ~2 minutes of history. -const SPARK_SLOT_MS: u64 = 1000; - -/// Sentinel: no packet received in this slot. Below SX1262 sensitivity -/// floor (-120 dBm), so it cannot be confused with a real RSSI value. -const NO_SIGNAL: i16 = -121; - -struct DisplayState { - rssi_history: [i16; RSSI_HISTORY_LEN], - tx_history: [bool; RSSI_HISTORY_LEN], - rssi_count: usize, - current_slot_rssi: i16, - current_slot_tx: bool, - display_on: bool, - disconnected: bool, - last_status: RadioStatus, -} - -impl DisplayState { - fn new() -> Self { - Self { - rssi_history: [NO_SIGNAL; RSSI_HISTORY_LEN], - tx_history: [false; RSSI_HISTORY_LEN], - rssi_count: 0, - current_slot_rssi: NO_SIGNAL, - current_slot_tx: false, - display_on: true, - disconnected: false, - last_status: RadioStatus::default(), - } - } - - /// Record an RSSI sample in the current time slot (keep best). - fn record_rssi(&mut self, rssi: i16) { - if self.current_slot_rssi == NO_SIGNAL || rssi > self.current_slot_rssi { - self.current_slot_rssi = rssi; - } - } - - /// Mark the current time slot as having a transmit. - fn record_tx(&mut self) { - self.current_slot_tx = true; - } - - /// Advance to the next time slot, committing the current slot's data. - fn advance_slot(&mut self) { - let idx = self.rssi_count % RSSI_HISTORY_LEN; - self.rssi_history[idx] = self.current_slot_rssi; - self.tx_history[idx] = self.current_slot_tx; - self.rssi_count += 1; - self.current_slot_rssi = NO_SIGNAL; - self.current_slot_tx = false; - } - - /// Whether the active dashboard should be shown (RX or TX mode). - fn is_active(&self) -> bool { - matches!( - self.last_status.state, - RadioState::Receiving | RadioState::Transmitting - ) - } -} - -/// Map SNR (dB) to LED brightness (4..64). Stronger signal = brighter. -fn snr_brightness(snr: Option) -> u8 { - let snr = snr.unwrap_or(-10); - let clamped = snr.clamp(-20, 15) as i32; - // -20 → 4, +15 → 64 - ((clamped + 20) * 60 / 35 + 4) as u8 -} - -/// Render the appropriate screen for the current state into the display buffer. -fn render_current( - display: &mut impl DrawTarget, - state: &DisplayState, - board: &render::BoardInfo<'_>, -) { - if state.is_active() { - render::dashboard( - display, - &state.last_status, - &state.rssi_history, - &state.tx_history, - state.rssi_count, - state.current_slot_rssi, - state.current_slot_tx, - board, - ); - } else { - render::splash(display, board); - } -} - -#[task] -pub async fn display_task( - parts: DisplayParts, - mut led: LedDriver, - status: &'static StatusWatch, - display_commands: &'static DisplayCommandChannel, -) { - // Format MAC address as "XX:XX:XX:XX:XX:XX" - let mut mac_str: heapless::String<18> = heapless::String::new(); - let m = Board::mac_address(); - let _ = core::fmt::Write::write_fmt( - &mut mac_str, - format_args!( - "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", - m[0], m[1], m[2], m[3], m[4], m[5] - ), - ); - - let board_info = render::BoardInfo { - name: BOARD_NAME, - version: env!("CARGO_PKG_VERSION"), - mac: &mac_str, - }; - - let Some(mut display) = crate::board::create_display(parts.i2c).await else { - defmt::error!("display init failed, giving up"); - return; - }; - - let mut state = DisplayState::new(); - let Some(mut receiver) = status.receiver() else { - defmt::error!("no watch receiver available for display"); - return; - }; - - // Show splash/waiting screen - render::splash(&mut display, &board_info); - let _ = display.flush().await; - - loop { - match select3( - receiver.changed(), - display_commands.receive(), - Timer::after_millis(SPARK_SLOT_MS), - ) - .await - { - Either3::First(radio_status) => { - if state.disconnected { - continue; - } - let rx_packet = radio_status.rx_count != state.last_status.rx_count; - let tx_packet = radio_status.tx_count != state.last_status.tx_count; - - if let Some(rssi) = radio_status.last_rssi { - if rx_packet { - state.record_rssi(rssi); - } - } - if tx_packet { - state.record_tx(); - } - let last_snr = radio_status.last_snr; - state.last_status = radio_status; - - if state.display_on { - render_current(&mut display, &state, &board_info); - let _ = display.flush().await; - - // Brief LED blink: red on TX, green scaled by SNR on RX - if tx_packet { - led.set_rgb(32, 0, 0).await; - Timer::after_millis(50).await; - led.set_rgb(0, 0, 0).await; - } else if rx_packet { - let b = snr_brightness(last_snr); - led.set_rgb(0, b, 0).await; - Timer::after_millis(50).await; - led.set_rgb(0, 0, 0).await; - } - } - } - Either3::Second(cmd) => match cmd { - DisplayCommand::Off => { - state.disconnected = false; - state.display_on = false; - render::blank(&mut display); - let _ = display.flush().await; - led.set_rgb(0, 0, 0).await; - } - DisplayCommand::On => { - state.disconnected = false; - state.display_on = true; - if let Some(s) = receiver.try_get() { - state.last_status = s; - } - render_current(&mut display, &state, &board_info); - let _ = display.flush().await; - } - DisplayCommand::Reset => { - state = DisplayState::new(); - render::splash(&mut display, &board_info); - let _ = display.flush().await; - led.set_rgb(0, 0, 0).await; - } - }, - Either3::Third(()) => { - // Timer tick: advance sparkline slot - state.advance_slot(); - if state.display_on && state.is_active() { - render_current(&mut display, &state, &board_info); - let _ = display.flush().await; - } - } - } - } -} - -// ── Rendering ─────────────────────────────────────────────────────── - -mod render { - use core::fmt::Write; - - use embedded_graphics::mono_font::ascii::{FONT_6X10, FONT_9X15_BOLD}; - use embedded_graphics::mono_font::MonoTextStyle; - use embedded_graphics::pixelcolor::BinaryColor; - use embedded_graphics::prelude::*; - use embedded_graphics::primitives::{Line, PrimitiveStyle, Rectangle}; - use embedded_graphics::text::renderer::CharacterStyle; - use embedded_graphics::text::{Alignment, Text}; - use heapless::String; - - use crate::channel::{RadioState, RadioStatus}; - use crate::protocol::Bandwidth; - - /// RSSI ring buffer length — supports displays up to 256px wide (2px per bar). - pub const RSSI_HISTORY_LEN: usize = 128; - - // Font metrics (FONT_6X10) - const CHAR_W: i32 = 6; - const FONT_H: i32 = 10; - - // Mode box: 2 chars + 1px padding each side - const MODE_BOX_W: i32 = 2 * CHAR_W + 2; // 14px - - // RSSI mapping range (dBm) - const RSSI_MIN: i16 = -120; - const RSSI_MAX: i16 = 0; - - /// Static board identity info for display rendering. - pub struct BoardInfo<'a> { - pub name: &'a str, - pub version: &'a str, - pub mac: &'a str, - } - - /// Render the active dashboard (shown when radio is in RX or TX mode). - #[allow(clippy::too_many_arguments)] - pub fn dashboard( - target: &mut impl DrawTarget, - status: &RadioStatus, - rssi_history: &[i16; RSSI_HISTORY_LEN], - tx_history: &[bool; RSSI_HISTORY_LEN], - rssi_count: usize, - current_slot_rssi: i16, - current_slot_tx: bool, - board: &BoardInfo, - ) { - let bb = target.bounding_box(); - let w = bb.size.width as i32; - let h = bb.size.height as i32; - let title_x = MODE_BOX_W + 1; - let title_w = w - title_x; - let header_h = 2 * FONT_H; - let sep1_y = header_h + 3; // 3px gap below header text (clears descenders) - let info_y = sep1_y + 1; // 1px line, info text tight below separator - let sep2_y = info_y + 2 * FONT_H + 3; // 3px gap below info text (clears descenders) - let spark_top = sep2_y + 2; // 1px line + 1px gap above graph - let spark_h = h - spark_top; - let visible_bars = ((w / 2) as usize).min(RSSI_HISTORY_LEN); - - let _ = target.clear(BinaryColor::Off); - let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); - let mut buf: String<32> = String::new(); - - // ── Mode box (left column, 2 rows tall) ───────────────────────── - let fill = PrimitiveStyle::with_fill(BinaryColor::On); - let mut inv_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::Off); - inv_style.set_background_color(Some(BinaryColor::On)); - - match status.state { - RadioState::Receiving => { - // "RX" inverted on row 0 - Rectangle::new( - Point::new(0, 0), - Size::new(MODE_BOX_W as u32, FONT_H as u32), - ) - .into_styled(fill) - .draw(target) - .ok(); - Text::new("RX", Point::new(1, FONT_H - 1), inv_style) - .draw(target) - .ok(); - } - RadioState::Transmitting => { - // "TX" inverted on row 1 - Rectangle::new( - Point::new(0, FONT_H), - Size::new(MODE_BOX_W as u32, FONT_H as u32), - ) - .into_styled(fill) - .draw(target) - .ok(); - Text::new("TX", Point::new(1, 2 * FONT_H - 1), inv_style) - .draw(target) - .ok(); - } - RadioState::Idle => {} // Not shown on active screen - } - - // Vertical separator between mode box and title box - Line::new(Point::new(MODE_BOX_W, 0), Point::new(MODE_BOX_W, sep1_y)) - .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) - .draw(target) - .ok(); - - // ── Title box (right of mode box) ─────────────────────────────── - let title_center_x = title_x + title_w / 2; - - // Row 0: "DongLoRa v{version}" - buf.clear(); - let _ = write!(buf, "DongLoRa v{}", board.version); - Text::with_alignment( - &buf, - Point::new(title_center_x, FONT_H - 1), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - - // Row 1: radio settings "{freq}/{bw}/{sf}/{cr}" - if let Some(cfg) = status.config { - buf.clear(); - let freq_mhz = cfg.freq_hz / 1_000_000; - let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000; - let bw_str = match cfg.bw { - Bandwidth::Khz7 => "7.8", - Bandwidth::Khz10 => "10.4", - Bandwidth::Khz15 => "15.6", - Bandwidth::Khz20 => "20.8", - Bandwidth::Khz31 => "31.2", - Bandwidth::Khz41 => "41.7", - Bandwidth::Khz62 => "62.5", - Bandwidth::Khz125 => "125", - Bandwidth::Khz250 => "250", - Bandwidth::Khz500 => "500", - }; - let _ = write!( - buf, - "{}.{:03}/{}/{}/{}", - freq_mhz, freq_khz, bw_str, cfg.sf, cfg.cr - ); - Text::with_alignment( - &buf, - Point::new(title_center_x, 2 * FONT_H - 1), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - } - - // ── First separator line ──────────────────────────────────────── - Line::new(Point::new(0, sep1_y), Point::new(w - 1, sep1_y)) - .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) - .draw(target) - .ok(); - - // ── Info rows (centered) ──────────────────────────────────────── - let center_x = w / 2; - - // Row 2: packet counters - buf.clear(); - let _ = write!( - buf, - "RX:{} TX:{}", - compact_count(status.rx_count), - compact_count(status.tx_count) - ); - Text::with_alignment( - &buf, - Point::new(center_x, info_y + FONT_H - 1), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - - // Row 3: RSSI + SNR - buf.clear(); - match (status.last_rssi, status.last_snr) { - (Some(rssi), Some(snr)) => { - let _ = write!(buf, "RSSI:{}dBm SNR:{}dB", rssi, snr); - } - (Some(rssi), None) => { - let _ = write!(buf, "RSSI:{}dBm", rssi); - } - _ => { - let _ = write!(buf, "No signal"); - } - } - Text::with_alignment( - &buf, - Point::new(center_x, info_y + 2 * FONT_H - 1), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - - // ── Second separator line ─────────────────────────────────────── - Line::new(Point::new(0, sep2_y), Point::new(w - 1, sep2_y)) - .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) - .draw(target) - .ok(); - - // ── Sparkline bar graph ───────────────────────────────────────── - if spark_h > 0 { - rssi_sparkline( - target, - rssi_history, - tx_history, - rssi_count, - current_slot_rssi, - current_slot_tx, - spark_top, - spark_h, - visible_bars, - ); - } - } - - /// Render the splash/waiting screen (shown when idle or no config). - pub fn splash(target: &mut impl DrawTarget, board: &BoardInfo) { - let bb = target.bounding_box(); - let w = bb.size.width as i32; - - let _ = target.clear(BinaryColor::Off); - let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); - let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On); - let mut buf: String<32> = String::new(); - - let center_x = w / 2; - - // Row 1: "DongLoRa" bold left, version small right - Text::new("DongLoRa", Point::new(4, 15), title_style) - .draw(target) - .ok(); - buf.clear(); - let _ = write!(buf, "v{}", board.version); - Text::with_alignment(&buf, Point::new(w - 2, 15), style, Alignment::Right) - .draw(target) - .ok(); - - // Row 2: board name - Text::with_alignment( - board.name, - Point::new(center_x, 28), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - - // Row 3: MAC address - Text::with_alignment( - board.mac, - Point::new(center_x, 41), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - - // Row 4: status - Text::with_alignment( - "Waiting for host...", - Point::new(center_x, 54), - style, - Alignment::Center, - ) - .draw(target) - .ok(); - } - - /// Format a u32 count compactly for display. - fn compact_count(n: u32) -> String<10> { - let mut s: String<10> = String::new(); - if n > 9_999_999 { - let _ = write!(s, "{}M", n / 1_000_000); - } else if n > 999_999 { - let _ = write!(s, "{}k", n / 1_000); - } else { - let _ = write!(s, "{}", n); - } - s - } - - /// Render the RSSI history as a bar-chart sparkline. - /// - /// The current (uncommitted) slot is rendered at the rightmost position - /// so that incoming packets appear on the graph immediately. - #[allow(clippy::too_many_arguments)] - fn rssi_sparkline( - target: &mut impl DrawTarget, - history: &[i16; RSSI_HISTORY_LEN], - tx_history: &[bool; RSSI_HISTORY_LEN], - count: usize, - current_rssi: i16, - current_tx: bool, - spark_top: i32, - spark_h: i32, - visible_bars: usize, - ) { - let live = current_rssi > RSSI_MIN || current_tx; - let committed = count.min(RSSI_HISTORY_LEN); - let hist_slots = if live { - committed.min(visible_bars.saturating_sub(1)) - } else { - committed.min(visible_bars) - }; - let total = hist_slots + if live { 1 } else { 0 }; - - if total == 0 { - return; - } - - let fill = PrimitiveStyle::with_fill(BinaryColor::On); - - // Draw committed history - for i in 0..hist_slots { - let idx = if count <= RSSI_HISTORY_LEN { - // Buffer hasn't wrapped: slots occupy indices 0..committed-1. - // Skip oldest entries that don't fit on screen. - let effective_bars = if live { visible_bars - 1 } else { visible_bars }; - i + committed.saturating_sub(effective_bars) - } else { - let start = count - RSSI_HISTORY_LEN; - let skip = - RSSI_HISTORY_LEN.saturating_sub(if live { visible_bars - 1 } else { visible_bars }); - (start + skip + i) % RSSI_HISTORY_LEN - }; - let is_tx = tx_history[idx]; - let rssi = history[idx]; - - if let Some(bar_h) = bar_height(rssi, is_tx, spark_h) { - let x = (visible_bars - total + i) as i32 * 2; - draw_bar(target, x, bar_h, is_tx, spark_top, spark_h, &fill); - } - } - - // Draw live (current) bar at the rightmost position - if live { - if let Some(bar_h) = bar_height(current_rssi, current_tx, spark_h) { - let x = (visible_bars - 1) as i32 * 2; - draw_bar(target, x, bar_h, current_tx, spark_top, spark_h, &fill); - } - } - } - - /// Compute the pixel height for a sparkline bar, or None to skip. - fn bar_height(rssi: i16, is_tx: bool, spark_h: i32) -> Option { - let h = if rssi <= RSSI_MIN { - if is_tx { - spark_h / 3 - } else { - return None; - } - } else { - let clamped = rssi.clamp(RSSI_MIN, RSSI_MAX); - ((clamped - RSSI_MIN) as i32 * spark_h) / (RSSI_MAX - RSSI_MIN) as i32 - }; - if h == 0 { - None - } else { - Some(h) - } - } - - /// Draw a single sparkline bar (solid for RX, dotted for TX). - fn draw_bar( - target: &mut impl DrawTarget, - x: i32, - bar_h: i32, - is_tx: bool, - spark_top: i32, - spark_h: i32, - fill: &PrimitiveStyle, - ) { - let y = spark_top + spark_h - bar_h; - if is_tx { - for row in 0..bar_h { - if row % 2 == 0 { - Rectangle::new(Point::new(x, y + row), Size::new(2, 1)) - .into_styled(*fill) - .draw(target) - .ok(); - } - } - } else { - Rectangle::new(Point::new(x, y), Size::new(2, bar_h as u32)) - .into_styled(*fill) - .draw(target) - .ok(); - } - } - - /// Clear the display (display-off state). - pub fn blank(target: &mut impl DrawTarget) { - let _ = target.clear(BinaryColor::Off); - } -} diff --git a/firmware/src/driver/mod.rs b/firmware/src/driver/mod.rs deleted file mode 100644 index 9a0f6a46..00000000 --- a/firmware/src/driver/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(feature = "wio_tracker_l1")] -pub mod sh1106; - -#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "wio_tracker_l1"))] -pub mod simple_led; diff --git a/firmware/src/driver/sh1106.rs b/firmware/src/driver/sh1106.rs deleted file mode 100644 index c5cc2a15..00000000 --- a/firmware/src/driver/sh1106.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Minimal async SH1106 I2C OLED driver (128x64, page addressing). -//! -//! The SH1106 is very similar to the SSD1306 but only supports page-mode -//! addressing (no horizontal/vertical auto-advance). This driver sends -//! framebuffer data page-by-page with explicit page/column commands. - -use embedded_graphics::draw_target::DrawTarget; -use embedded_graphics::geometry::{OriginDimensions, Size}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::Pixel; -use embedded_hal_async::i2c::I2c; - -const WIDTH: usize = 128; -const HEIGHT: usize = 64; -const PAGES: usize = HEIGHT / 8; -const BUF_SIZE: usize = WIDTH * PAGES; // 1024 - -/// SH1106 has a 132-column RAM; visible 128 columns are centered with a -/// 2-column offset on each side. -const COL_OFFSET: u8 = 2; - -pub struct Sh1106 { - i2c: I, - addr: u8, - buffer: [u8; BUF_SIZE], -} - -impl Sh1106 { - pub fn new(i2c: I, addr: u8) -> Self { - Self { - i2c, - addr, - buffer: [0u8; BUF_SIZE], - } - } - - /// Send a sequence of commands (each prefixed with the I2C command byte 0x00). - async fn cmd(&mut self, commands: &[u8]) -> Result<(), I::Error> { - for &c in commands { - self.i2c.write(self.addr, &[0x00, c]).await?; - } - Ok(()) - } - - /// Initialize the display with sensible defaults. - pub async fn init(&mut self) -> Result<(), I::Error> { - self.cmd(&[ - 0xAE, // display off - 0xD5, 0x80, // clock div: default - 0xA8, 0x3F, // multiplex ratio: 64 - 0xD3, 0x00, // display offset: 0 - 0x40, // start line: 0 - 0x8D, 0x14, // charge pump: enable - 0xA1, // segment remap: column 127 mapped to SEG0 - 0xC8, // COM scan direction: remapped - 0xDA, 0x12, // COM pin config: alternative, no L/R remap - 0x81, 0xCF, // contrast: 207 - 0xD9, 0xF1, // pre-charge: phase1=1, phase2=15 - 0xDB, 0x40, // VCOMH deselect: ~0.77×Vcc - 0xA4, // entire display ON (follow RAM) - 0xA6, // normal display (not inverted) - 0xAF, // display on - ]) - .await - } - - /// Flush the entire framebuffer to the display, page by page. - pub async fn flush(&mut self) -> Result<(), I::Error> { - // Each I2C data write is prefixed with 0x40 (data continuation byte). - let mut page_buf = [0u8; 1 + WIDTH]; // 0x40 + 128 data bytes - page_buf[0] = 0x40; - - for page in 0..PAGES { - // Set page address and column start (with SH1106 2-column offset). - self.cmd(&[ - 0xB0 | page as u8, // page address - COL_OFFSET & 0x0F, // column low nibble - 0x10 | (COL_OFFSET >> 4), // column high nibble - ]) - .await?; - - let start = page * WIDTH; - page_buf[1..].copy_from_slice(&self.buffer[start..start + WIDTH]); - self.i2c.write(self.addr, &page_buf).await?; - } - Ok(()) - } - - /// Set display brightness (contrast). - pub async fn set_brightness(&mut self, value: u8) -> Result<(), I::Error> { - self.cmd(&[0x81, value]).await - } - -} - -impl DrawTarget for Sh1106 { - type Color = BinaryColor; - type Error = core::convert::Infallible; - - fn draw_iter(&mut self, pixels: T) -> Result<(), Self::Error> - where - T: IntoIterator>, - { - for Pixel(pos, color) in pixels { - let x = pos.x; - let y = pos.y; - if x >= 0 && x < WIDTH as i32 && y >= 0 && y < HEIGHT as i32 { - let x = x as usize; - let y = y as usize; - let idx = (y / 8) * WIDTH + x; - let bit = (y % 8) as u8; - if color.is_on() { - self.buffer[idx] |= 1 << bit; - } else { - self.buffer[idx] &= !(1 << bit); - } - } - } - Ok(()) - } - - fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { - self.buffer.fill(if color.is_on() { 0xFF } else { 0x00 }); - Ok(()) - } -} - -impl OriginDimensions for Sh1106 { - fn size(&self) -> Size { - Size::new(WIDTH as u32, HEIGHT as u32) - } -} diff --git a/firmware/src/driver/simple_led.rs b/firmware/src/driver/simple_led.rs deleted file mode 100644 index 6839a0c6..00000000 --- a/firmware/src/driver/simple_led.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Simple single-color LED driver (GPIO on/off). -//! -//! Generic over any `OutputPin`. Any non-zero RGB value turns the LED on; -//! all zeros turns it off. - -use embedded_hal::digital::OutputPin; - -use crate::board::RgbLed; - -pub struct SimpleLed

(pub P); - -impl RgbLed for SimpleLed

{ - async fn set_rgb(&mut self, r: u8, g: u8, b: u8) { - if r > 0 || g > 0 || b > 0 { - let _ = self.0.set_high(); - } else { - let _ = self.0.set_low(); - } - } -} diff --git a/firmware/src/hal/esp32s3.rs b/firmware/src/hal/esp32s3.rs deleted file mode 100644 index db5f9b5b..00000000 --- a/firmware/src/hal/esp32s3.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! ESP32-S3 MCU initialization primitives. -//! -//! Provides low-level bus init for ESP32-S3 boards. -//! Board files call these with specific pins and construct higher-level -//! drivers (Sx126x, SSD1306, USB, UART, etc.) themselves. - -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use esp_hal::dma::{DmaRxBuf, DmaTxBuf}; -use esp_hal::i2c::master::{Config as I2cConfig, I2c}; -use esp_hal::spi::master::{Config as SpiConfig, Spi, SpiDmaBus}; -use esp_hal::spi::Mode as SpiMode; -use esp_hal::time::Rate; -use esp_hal::timer::timg::TimerGroup; -use static_cell::StaticCell; - -// ── MCU-level types ───────────────────────────────────────────────── - -pub type SpiBus = SpiDmaBus<'static, esp_hal::Async>; -pub type I2cBus = I2c<'static, esp_hal::Async>; - -// ── Timer ─────────────────────────────────────────────────────────── - -/// Start the ESP-RTOS scheduler and Embassy time driver. -pub fn start_timer(timg0: esp_hal::peripherals::TIMG0<'static>) { - let timg0 = TimerGroup::new(timg0); - esp_rtos::start(timg0.timer0); -} - -// ── SPI bus ───────────────────────────────────────────────────────── - -/// Initialize SPI2 with DMA and wrap in a shared bus (StaticCell + Mutex). -pub fn init_spi( - spi2: esp_hal::peripherals::SPI2<'static>, - dma_ch0: esp_hal::peripherals::DMA_CH0<'static>, - sck: esp_hal::peripherals::GPIO9<'static>, - mosi: esp_hal::peripherals::GPIO10<'static>, - miso: esp_hal::peripherals::GPIO11<'static>, -) -> &'static embassy_sync::mutex::Mutex { - let spi = Spi::new( - spi2, - SpiConfig::default() - .with_frequency(Rate::from_mhz(1)) - .with_mode(SpiMode::_0), - ) - .expect("SPI init") - .with_sck(sck) - .with_mosi(mosi) - .with_miso(miso) - .with_dma(dma_ch0); - - let (rx_buf, rx_desc, tx_buf, tx_desc) = esp_hal::dma_buffers!(256); - let dma_rx = DmaRxBuf::new(rx_desc, rx_buf).expect("DMA RX buf"); - let dma_tx = DmaTxBuf::new(tx_desc, tx_buf).expect("DMA TX buf"); - - let spi = spi.with_buffers(dma_rx, dma_tx).into_async(); - - static SPI_BUS: StaticCell> = - StaticCell::new(); - SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi)) -} - -// ── I2C bus ───────────────────────────────────────────────────────── - -/// Initialize I2C0 for peripherals (display, sensors, etc.). -pub fn init_i2c( - i2c0: esp_hal::peripherals::I2C0<'static>, - sda: esp_hal::peripherals::GPIO17<'static>, - scl: esp_hal::peripherals::GPIO18<'static>, -) -> I2cBus { - I2c::new(i2c0, I2cConfig::default()) - .expect("I2C init") - .with_sda(sda) - .with_scl(scl) - .into_async() -} - -// ── MAC address ───────────────────────────────────────────────────── - -/// Read the factory-programmed MAC address from eFuse. -pub fn mac_address() -> [u8; 6] { - esp_hal::efuse::Efuse::mac_address() -} diff --git a/firmware/src/hal/mod.rs b/firmware/src/hal/mod.rs deleted file mode 100644 index 372fea3a..00000000 --- a/firmware/src/hal/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! MCU family initialization primitives. -//! -//! Each submodule provides low-level bus and peripheral init for one MCU family. -//! Board files call these with specific pins and construct higher-level drivers. - -#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] -pub mod esp32s3; - -#[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] -pub mod nrf52840; diff --git a/firmware/src/hal/nrf52840.rs b/firmware/src/hal/nrf52840.rs deleted file mode 100644 index 1bf4c029..00000000 --- a/firmware/src/hal/nrf52840.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! nRF52840 MCU initialization primitives. -//! -//! Provides shared boilerplate for nRF52840 boards: interrupt bindings, -//! type aliases, StaticCell bus wrapping, USB init, and MAC address reading. - -use embassy_nrf::bind_interrupts; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use static_cell::StaticCell; - -// ── Interrupts (shared by all nRF52840 boards) ───────────────────── - -bind_interrupts!(pub struct Irqs { - USBD => embassy_nrf::usb::InterruptHandler; - SPIM3 => embassy_nrf::spim::InterruptHandler; - TWISPI0 => embassy_nrf::twim::InterruptHandler; -}); - -// ── MCU-level types ───────────────────────────────────────────────── - -pub type SpiBus = embassy_nrf::spim::Spim<'static>; -pub type I2cBus = embassy_nrf::twim::Twim<'static>; -pub type UsbNrfDriver = - embassy_nrf::usb::Driver<'static, &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>; - -// ── SPI bus sharing ───────────────────────────────────────────────── - -/// Wrap an initialized SPI peripheral in a shared bus (StaticCell + Mutex). -pub fn share_spi_bus( - spi: SpiBus, -) -> &'static embassy_sync::mutex::Mutex { - static SPI_BUS: StaticCell> = - StaticCell::new(); - SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi)) -} - -// ── I2C DMA buffer ────────────────────────────────────────────────── - -/// Allocate a DMA-safe buffer for Twim I2C. -/// -/// Every nRF52840 Twim user needs a `&'static mut [u8]` buffer. -/// This provides one via StaticCell so board files don't repeat the pattern. -pub fn alloc_i2c_buffer() -> &'static mut [u8; 256] { - static TWIM_BUF: StaticCell<[u8; 256]> = StaticCell::new(); - TWIM_BUF.init([0u8; 256]) -} - -// ── USB VBUS detection ────────────────────────────────────────────── - -/// Allocate and initialize software VBUS detection. -pub fn alloc_vbus_detect( - vbus_detect: bool, - self_powered: bool, -) -> &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect { - static VBUS: StaticCell = StaticCell::new(); - VBUS.init(embassy_nrf::usb::vbus_detect::SoftwareVbusDetect::new( - vbus_detect, - self_powered, - )) -} - -// ── MAC address ───────────────────────────────────────────────────── - -/// Read the factory-programmed device address from FICR registers. -pub fn mac_address() -> [u8; 6] { - // SAFETY: 0x10000000 is the nRF52840 FICR (Factory Information Configuration - // Registers) base address. Offsets 0x0A4 and 0x0A8 are the DEVICEADDR[0] and - // DEVICEADDR[1] registers containing the factory-programmed device address. - // read_volatile is correct for memory-mapped hardware registers. - unsafe { - let ficr = 0x10000000 as *const u32; - let addr0 = core::ptr::read_volatile(ficr.byte_add(0x0A4)); - let addr1 = core::ptr::read_volatile(ficr.byte_add(0x0A8)); - [ - addr0 as u8, - (addr0 >> 8) as u8, - (addr0 >> 16) as u8, - (addr0 >> 24) as u8, - addr1 as u8, - (addr1 >> 8) as u8, - ] - } -} diff --git a/firmware/src/host/framing.rs b/firmware/src/host/framing.rs deleted file mode 100644 index 114d1a09..00000000 --- a/firmware/src/host/framing.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Shared COBS protocol helpers for USB and UART transports. -//! -//! Both `usb_task` and `uart_task` use these helpers for frame -//! accumulation, response encoding, and command routing. - -use defmt::warn; - -use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel}; -use crate::protocol::{Command, ErrorCode, Response}; - -/// Maximum COBS frame size (bytes). -pub const MAX_FRAME: usize = 512; - -// Worst case: tag(1) + rssi(2) + snr(2) + len(2) + payload(256) = 263 bytes. -// COBS adds ceil(263/254)+1 = 3 bytes. 266 < 512. Plenty of room. -const _: () = assert!( - MAX_FRAME >= crate::protocol::MAX_PAYLOAD + 64, - "MAX_FRAME too small for max payload + COBS overhead" -); - -/// Decodes COBS-framed commands from a byte stream. -pub struct CobsDecoder { - buf: [u8; MAX_FRAME], - len: usize, -} - -impl CobsDecoder { - pub const fn new() -> Self { - Self { - buf: [0u8; MAX_FRAME], - len: 0, - } - } - - /// Reset the accumulator (discard partial frame). - pub fn reset(&mut self) { - self.len = 0; - } - - /// Feed a chunk of bytes, calling `on_command` for each decoded command. - pub fn feed(&mut self, data: &[u8], mut on_command: impl FnMut(Command)) { - for &byte in data { - if byte == 0x00 { - // End of COBS frame — decode and dispatch - if self.len > 0 { - let mut decode_buf = [0u8; MAX_FRAME]; - if let Some(decoded_len) = - ucobs::decode(&self.buf[..self.len], &mut decode_buf) - { - if let Some(cmd) = Command::from_bytes(&decode_buf[..decoded_len]) { - on_command(cmd); - } - } - } - self.len = 0; - } else if self.len < MAX_FRAME { - self.buf[self.len] = byte; - self.len += 1; - } else { - // Frame too large — discard - self.len = 0; - } - } - } -} - -/// COBS-encode a response into `encode_buf` with trailing 0x00 sentinel. -/// -/// Returns the slice to send (encoded frame + sentinel), or `None` on overflow. -pub fn cobs_encode_response<'a>( - response: Response, - write_buf: &mut [u8; MAX_FRAME], - encode_buf: &'a mut [u8; MAX_FRAME], -) -> Option<&'a [u8]> { - let raw_len = response.write_to(write_buf); - let encoded_len = ucobs::encode(&write_buf[..raw_len], encode_buf).unwrap_or(0); - if encoded_len < encode_buf.len() { - encode_buf[encoded_len] = 0x00; - Some(&encode_buf[..encoded_len + 1]) - } else { - warn!("COBS encode buffer overflow"); - None - } -} - -/// Route a parsed command to the appropriate handler. -/// -/// Display and MAC commands are handled locally (response sent immediately). -/// All other commands are forwarded to the radio task (response sent later). -/// Both paths feed the same `ResponseChannel`, so the one-outstanding-command -/// rule in PROTOCOL.md is required to keep solicited responses ordered. -pub async fn route_command( - cmd: Command, - commands: &CommandChannel, - responses: &ResponseChannel, - display_commands: &DisplayCommandChannel, - has_display: bool, - mac: [u8; 6], -) { - match cmd { - Command::DisplayOn => { - if has_display { - display_commands.send(DisplayCommand::On).await; - responses.send(Response::Ok).await; - } else { - responses.send(Response::Error(ErrorCode::NoDisplay)).await; - } - } - Command::DisplayOff => { - if has_display { - display_commands.send(DisplayCommand::Off).await; - responses.send(Response::Ok).await; - } else { - responses.send(Response::Error(ErrorCode::NoDisplay)).await; - } - } - Command::GetMac => { - responses.send(Response::MacAddress(mac)).await; - } - other => { - commands.send(other).await; - } - } -} diff --git a/firmware/src/host/mod.rs b/firmware/src/host/mod.rs deleted file mode 100644 index 15467ba0..00000000 --- a/firmware/src/host/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Host communication: unified USB/UART transport. -//! -//! Exports a single `host_task` regardless of transport. -//! The cfg dispatch is contained entirely within this module. - -pub mod framing; - -cfg_if::cfg_if! { - if #[cfg(feature = "heltec_v3_uart")] { - mod uart; - pub use uart::host_task; - } else { - mod usb; - pub use usb::host_task; - } -} diff --git a/firmware/src/host/uart.rs b/firmware/src/host/uart.rs deleted file mode 100644 index a5a8fa1c..00000000 --- a/firmware/src/host/uart.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! UART host task: COBS-framed fixed-size LE command/response protocol. -//! -//! Used for boards with USB-UART bridge chips (e.g. CP2102 on Heltec V3). - -use defmt::warn; -use embassy_executor::task; -use embedded_io_async::Write; - -use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel}; -use super::framing::{self, CobsDecoder, MAX_FRAME}; - -#[task] -pub async fn host_task( - parts: crate::board::UartParts, - commands: &'static CommandChannel, - responses: &'static ResponseChannel, - display_commands: &'static DisplayCommandChannel, - has_display: bool, - mac: [u8; 6], -) { - let (mut rx, mut tx) = parts.driver.split(); - - // UART has no DTR — signal the display that a host is present at boot. - if has_display { - display_commands.send(DisplayCommand::Reset).await; - } - - let mut read_buf = [0u8; 64]; - let mut write_buf = [0u8; MAX_FRAME]; - let mut cobs_encode_buf = [0u8; MAX_FRAME]; - let mut decoder = CobsDecoder::new(); - - loop { - use embassy_futures::select::{select, Either}; - - match select( - embedded_io_async::Read::read(&mut rx, &mut read_buf), - responses.receive(), - ) - .await - { - Either::First(result) => { - let n = match result { - Ok(0) => continue, - Ok(n) => n, - Err(_) => { - decoder.reset(); - embassy_time::Timer::after_millis(100).await; - continue; - } - }; - - let mut cmds = heapless::Vec::<_, 4>::new(); - decoder.feed(&read_buf[..n], |cmd| { - let _ = cmds.push(cmd); - }); - for cmd in cmds { - framing::route_command( - cmd, commands, responses, display_commands, has_display, mac, - ) - .await; - } - } - Either::Second(response) => { - if let Some(frame) = - framing::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf) - { - if tx.write_all(frame).await.is_err() { - warn!("UART write failed, response dropped"); - } - } - } - } - } -} diff --git a/firmware/src/host/usb.rs b/firmware/src/host/usb.rs deleted file mode 100644 index 71e089e7..00000000 --- a/firmware/src/host/usb.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! USB CDC-ACM host task: COBS-framed fixed-size LE command/response protocol. - -use defmt::warn; -use embassy_executor::task; -use embassy_futures::join::join; -use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; -use embassy_usb::Builder; -use static_cell::StaticCell; - -use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel}; -use super::framing::{self, CobsDecoder, MAX_FRAME}; - -const MAX_PACKET: usize = 64; - -#[task] -pub async fn host_task( - parts: crate::board::UsbParts, - commands: &'static CommandChannel, - responses: &'static ResponseChannel, - display_commands: &'static DisplayCommandChannel, - has_display: bool, - mac: [u8; 6], -) { - let mut config = embassy_usb::Config::new(0x1209, 0x5741); - config.manufacturer = Some("DongLoRa"); - config.product = Some("DongLoRa LoRa Radio"); - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - - static SERIAL_BUF: StaticCell<[u8; 12]> = StaticCell::new(); - let serial_buf = SERIAL_BUF.init([0u8; 12]); - const HEX: &[u8; 16] = b"0123456789ABCDEF"; - for (i, &byte) in mac.iter().enumerate() { - serial_buf[i * 2] = HEX[(byte >> 4) as usize]; - serial_buf[i * 2 + 1] = HEX[(byte & 0x0F) as usize]; - } - config.serial_number = Some(core::str::from_utf8(serial_buf).expect("MAC hex is valid UTF-8")); - - static DESC_BUF: StaticCell<[u8; 256]> = StaticCell::new(); - static CONF_BUF: StaticCell<[u8; 256]> = StaticCell::new(); - static BOS_BUF: StaticCell<[u8; 256]> = StaticCell::new(); - static CTRL_BUF: StaticCell<[u8; 64]> = StaticCell::new(); - static CDC_STATE: StaticCell = StaticCell::new(); - - let desc_buf = DESC_BUF.init([0; 256]); - let conf_buf = CONF_BUF.init([0; 256]); - let bos_buf = BOS_BUF.init([0; 256]); - let ctrl_buf = CTRL_BUF.init([0; 64]); - let cdc_state = CDC_STATE.init(State::new()); - - let mut builder = Builder::new(parts.driver, config, desc_buf, conf_buf, bos_buf, ctrl_buf); - - let class = CdcAcmClass::new(&mut builder, cdc_state, MAX_PACKET as u16); - let mut usb_dev = builder.build(); - let (sender, receiver) = class.split(); - - join( - usb_dev.run(), - protocol_loop(sender, receiver, commands, responses, display_commands, has_display, mac), - ) - .await; -} - -async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( - mut sender: Sender<'d, D>, - mut receiver: Receiver<'d, D>, - commands: &'static CommandChannel, - responses: &'static ResponseChannel, - display_commands: &'static DisplayCommandChannel, - has_display: bool, - mac: [u8; 6], -) { - use embassy_futures::select::{select3, Either3}; - - let mut read_buf = [0u8; MAX_PACKET]; - let mut write_buf = [0u8; MAX_FRAME]; - let mut cobs_encode_buf = [0u8; MAX_FRAME]; - let mut decoder = CobsDecoder::new(); - let mut was_connected = false; - - loop { - match select3( - receiver.read_packet(&mut read_buf), - responses.receive(), - embassy_time::Timer::after_millis(250), - ) - .await - { - Either3::First(result) => { - let n = match result { - Ok(0) => continue, - Ok(n) => n, - Err(_) => { - decoder.reset(); - embassy_time::Timer::after_millis(100).await; - continue; - } - }; - - let mut cmds = heapless::Vec::<_, 4>::new(); - decoder.feed(&read_buf[..n], |cmd| { - let _ = cmds.push(cmd); - }); - for cmd in cmds { - framing::route_command( - cmd, commands, responses, display_commands, has_display, mac, - ) - .await; - } - } - Either3::Second(response) => { - if let Some(frame) = - framing::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf) - { - for chunk in frame.chunks(MAX_PACKET) { - if sender.write_packet(chunk).await.is_err() { - warn!("USB write failed, response dropped"); - break; - } - } - } - } - Either3::Third(()) => {} - } - - let connected = receiver.dtr(); - if was_connected && !connected && has_display { - display_commands.send(DisplayCommand::Reset).await; - } - if !was_connected && connected && has_display { - display_commands.send(DisplayCommand::On).await; - } - was_connected = connected; - } -} diff --git a/firmware/src/main.rs b/firmware/src/main.rs deleted file mode 100644 index 7ebd943e..00000000 --- a/firmware/src/main.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! DongLoRa firmware — transparent LoRa radio over USB. -//! -//! Three async tasks communicate via static channels: -//! -//! ```text -//! host_task ──Command──► radio_task ──► SX1262 -//! ◄──Response── │ -//! StatusWatch -//! ▼ -//! display_task (optional) -//! ``` -//! -//! The host drives everything. The radio idles until commanded. - -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), no_main)] - -#[cfg(not(test))] -mod board; -#[cfg(not(test))] -mod channel; -#[cfg(not(test))] -mod display; -#[cfg(not(test))] -mod driver; -#[cfg(not(test))] -mod hal; -#[cfg(not(test))] -mod host; -mod protocol; -#[cfg(not(test))] -mod radio; - -#[cfg(not(test))] -use embassy_executor::Spawner; - -#[cfg(not(test))] -use crate::board::LoRaBoard; -#[cfg(not(test))] -use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, StatusWatch}; - -#[cfg(not(test))] -cfg_if::cfg_if! { - if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] { - use defmt_rtt as _; - use panic_probe as _; - } else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] { - use esp_backtrace as _; - use esp_println as _; - } -} - -#[cfg(not(test))] -static COMMANDS: CommandChannel = CommandChannel::new(); -#[cfg(not(test))] -static RESPONSES: ResponseChannel = ResponseChannel::new(); -#[cfg(not(test))] -static STATUS: StatusWatch = StatusWatch::new(); -#[cfg(not(test))] -static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new(); - -#[cfg(not(test))] -cfg_if::cfg_if! { - if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] { - #[embassy_executor::main] - async fn main(spawner: Spawner) { - run(spawner).await; - } - } else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] { - #[esp_rtos::main] - async fn main(spawner: Spawner) { - run(spawner).await; - } - } -} - -#[cfg(not(test))] -async fn run(spawner: Spawner) { - let board = ::init(); - let parts = board.into_parts(); - - spawner - .spawn(radio::radio_task(parts.radio, &COMMANDS, &RESPONSES, &STATUS)) - .expect("spawn radio_task"); - - spawner - .spawn(host::host_task( - parts.host, - &COMMANDS, - &RESPONSES, - &DISPLAY_COMMANDS, - parts.display.is_some(), - parts.mac, - )) - .expect("spawn host_task"); - - if let Some(dp) = parts.display { - spawner - .spawn(display::display_task(dp, parts.led, &STATUS, &DISPLAY_COMMANDS)) - .expect("spawn display_task"); - } -} diff --git a/firmware/src/protocol.rs b/firmware/src/protocol.rs deleted file mode 100644 index a21731b7..00000000 --- a/firmware/src/protocol.rs +++ /dev/null @@ -1,707 +0,0 @@ -//! Wire protocol types and fixed-size little-endian serialization. -//! -//! Every integer is fixed-width LE. No varints, no zigzag. -//! See `docs/PROTOCOL.md` for the complete specification. - -use heapless::Vec; - -/// Maximum LoRa payload size in bytes. -pub const MAX_PAYLOAD: usize = 256; - -/// RadioConfig wire size (fixed). -pub const RADIO_CONFIG_SIZE: usize = 13; - -/// Sentinel value for `tx_power_dbm`: use the board's maximum TX power. -pub const TX_POWER_MAX: i8 = i8::MIN; // -128 on the wire - -/// Sentinel value for `preamble_len`: use the firmware default (16 symbols). -pub const PREAMBLE_DEFAULT: u16 = 0; - -/// LoRa signal bandwidth. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(not(test), derive(defmt::Format))] -#[repr(u8)] -pub enum Bandwidth { - Khz7 = 0, - Khz10 = 1, - Khz15 = 2, - Khz20 = 3, - Khz31 = 4, - Khz41 = 5, - Khz62 = 6, - Khz125 = 7, - Khz250 = 8, - Khz500 = 9, -} - -impl Bandwidth { - fn from_u8(v: u8) -> Option { - match v { - 0 => Some(Self::Khz7), - 1 => Some(Self::Khz10), - 2 => Some(Self::Khz15), - 3 => Some(Self::Khz20), - 4 => Some(Self::Khz31), - 5 => Some(Self::Khz41), - 6 => Some(Self::Khz62), - 7 => Some(Self::Khz125), - 8 => Some(Self::Khz250), - 9 => Some(Self::Khz500), - _ => None, - } - } -} - -/// Complete LoRa radio configuration. -/// -/// Wire layout (13 bytes, all little-endian): -/// ```text -/// [freq_hz:4] [bw:1] [sf:1] [cr:1] [sync_word:2] [tx_power_dbm:1] [preamble_len:2] [cad:1] -/// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(not(test), derive(defmt::Format))] -pub struct RadioConfig { - /// Frequency in Hz (150_000_000 - 960_000_000 for SX1262). - pub freq_hz: u32, - pub bw: Bandwidth, - /// Spreading factor (5-12). - pub sf: u8, - /// Coding rate denominator (5-8). E.g. 5 = CR 4/5, 8 = CR 4/8. - pub cr: u8, - pub sync_word: u16, - /// Transmit power in dBm. Set to [`TX_POWER_MAX`] (-128) for the - /// board's maximum. - pub tx_power_dbm: i8, - /// Preamble length in symbols. Set to [`PREAMBLE_DEFAULT`] (0) for - /// the firmware default (16 symbols). Valid range: 6–65535. - pub preamble_len: u16, - /// Channel Activity Detection (listen-before-talk) before TX. - /// 0 = disabled, non-zero = enabled. Default: 1 (enabled). - pub cad: u8, -} - -impl RadioConfig { - /// Validate fields against hardware limits. - pub fn validate(&self, power_range: (i8, i8)) -> Result<(), &'static str> { - if !(150_000_000..=960_000_000).contains(&self.freq_hz) { - return Err("frequency out of range (150-960 MHz)"); - } - if !(5..=12).contains(&self.sf) { - return Err("spreading factor out of range (5-12)"); - } - if !(5..=8).contains(&self.cr) { - return Err("coding rate out of range (5-8)"); - } - if self.tx_power_dbm != TX_POWER_MAX - && !(power_range.0..=power_range.1).contains(&self.tx_power_dbm) - { - return Err("TX power out of range for this board"); - } - if self.preamble_len != PREAMBLE_DEFAULT && self.preamble_len < 6 { - return Err("preamble length too short (min 6)"); - } - Ok(()) - } - - /// Resolve sentinel values to concrete defaults. - pub fn resolve(mut self, power_range: (i8, i8)) -> Self { - if self.tx_power_dbm == TX_POWER_MAX { - self.tx_power_dbm = power_range.1; - } - if self.preamble_len == PREAMBLE_DEFAULT { - self.preamble_len = 16; - } - self - } - - /// Whether CAD (listen-before-talk) is enabled. - pub fn cad_enabled(&self) -> bool { - self.cad != 0 - } - - /// Serialize to fixed-size LE bytes. Returns number of bytes written (always 13). - pub fn write_to(self, buf: &mut [u8]) -> usize { - buf[0..4].copy_from_slice(&self.freq_hz.to_le_bytes()); - buf[4] = self.bw as u8; - buf[5] = self.sf; - buf[6] = self.cr; - buf[7..9].copy_from_slice(&self.sync_word.to_le_bytes()); - buf[9] = self.tx_power_dbm as u8; - buf[10..12].copy_from_slice(&self.preamble_len.to_le_bytes()); - buf[12] = self.cad; - RADIO_CONFIG_SIZE - } - - /// Deserialize from fixed-size LE bytes. - pub fn from_bytes(buf: &[u8]) -> Option { - if buf.len() < RADIO_CONFIG_SIZE { - return None; - } - Some(Self { - freq_hz: u32::from_le_bytes(buf[0..4].try_into().ok()?), - bw: Bandwidth::from_u8(buf[4])?, - sf: buf[5], - cr: buf[6], - sync_word: u16::from_le_bytes(buf[7..9].try_into().ok()?), - tx_power_dbm: buf[9] as i8, - preamble_len: u16::from_le_bytes(buf[10..12].try_into().ok()?), - cad: buf[12], - }) - } -} - -/// Host → firmware commands. -#[allow(clippy::large_enum_variant)] -#[derive(Debug, PartialEq)] -pub enum Command { - Ping, - GetConfig, - SetConfig(RadioConfig), - StartRx, - StopRx, - Transmit { - config: Option, - payload: Vec, - }, - DisplayOn, - DisplayOff, - GetMac, -} - -impl Command { - /// Deserialize from a COBS-decoded frame. - pub fn from_bytes(buf: &[u8]) -> Option { - let tag = *buf.first()?; - let rest = &buf[1..]; - match tag { - 0 => Some(Self::Ping), - 1 => Some(Self::GetConfig), - 2 => Some(Self::SetConfig(RadioConfig::from_bytes(rest)?)), - 3 => Some(Self::StartRx), - 4 => Some(Self::StopRx), - 5 => { - // Transmit: has_config:u8 + [RadioConfig if 1] + len:u16 LE + payload - if rest.is_empty() { - return None; - } - let (config, pos) = if rest[0] == 0 { - (None, 1) - } else if rest[0] == 1 && rest.len() > RADIO_CONFIG_SIZE { - ( - Some(RadioConfig::from_bytes(&rest[1..])?), - 1 + RADIO_CONFIG_SIZE, - ) - } else { - return None; - }; - if rest.len() < pos + 2 { - return None; - } - let len = u16::from_le_bytes(rest[pos..pos + 2].try_into().ok()?) as usize; - let data_start = pos + 2; - if rest.len() < data_start + len { - return None; - } - let mut payload = Vec::new(); - let _ = payload.extend_from_slice(&rest[data_start..data_start + len]); - Some(Self::Transmit { config, payload }) - } - 6 => Some(Self::DisplayOn), - 7 => Some(Self::DisplayOff), - 8 => Some(Self::GetMac), - _ => None, - } - } -} - -/// Firmware → host responses. -#[allow(clippy::large_enum_variant)] -#[derive(Debug, PartialEq)] -pub enum Response { - Pong, - Config(RadioConfig), - RxPacket { - rssi: i16, - snr: i16, - payload: Vec, - }, - TxDone, - Ok, - Error(ErrorCode), - MacAddress([u8; 6]), -} - -impl Response { - /// Serialize to fixed-size LE bytes. Returns number of bytes written. - pub fn write_to(self, buf: &mut [u8]) -> usize { - match self { - Self::Pong => { - buf[0] = 0; - 1 - } - Self::Config(cfg) => { - buf[0] = 1; - 1 + cfg.write_to(&mut buf[1..]) - } - Self::RxPacket { rssi, snr, payload } => { - buf[0] = 2; - buf[1..3].copy_from_slice(&rssi.to_le_bytes()); - buf[3..5].copy_from_slice(&snr.to_le_bytes()); - buf[5..7].copy_from_slice(&(payload.len() as u16).to_le_bytes()); - buf[7..7 + payload.len()].copy_from_slice(&payload); - 7 + payload.len() - } - Self::TxDone => { - buf[0] = 3; - 1 - } - Self::Ok => { - buf[0] = 4; - 1 - } - Self::Error(code) => { - buf[0] = 5; - buf[1] = code as u8; - 2 - } - Self::MacAddress(mac) => { - buf[0] = 6; - buf[1..7].copy_from_slice(&mac); - 7 - } - } - } -} - -/// Error codes reported to the host. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(not(test), derive(defmt::Format))] -#[repr(u8)] -pub enum ErrorCode { - InvalidConfig = 0, - RadioBusy = 1, - TxTimeout = 2, - // 3 reserved (was CrcError — SX1262 silently drops bad-CRC packets) - NotConfigured = 4, - NoDisplay = 5, -} - -// ── Tests ─────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn make_config() -> RadioConfig { - RadioConfig { - freq_hz: 915_000_000, - bw: Bandwidth::Khz125, - sf: 7, - cr: 5, - sync_word: 0x3444, - tx_power_dbm: 22, - preamble_len: 16, - cad: 1, - } - } - - // ── RadioConfig roundtrip ─────────────────────────────────────── - - #[test] - fn radio_config_roundtrip() { - let cfg = make_config(); - let mut buf = [0u8; RADIO_CONFIG_SIZE]; - let n = cfg.write_to(&mut buf); - assert_eq!(n, RADIO_CONFIG_SIZE); - assert_eq!(RadioConfig::from_bytes(&buf), Some(cfg)); - } - - #[test] - fn radio_config_roundtrip_all_bandwidths() { - for bw_val in 0u8..=9 { - let bw = Bandwidth::from_u8(bw_val).unwrap(); - let cfg = RadioConfig { - freq_hz: 433_000_000, - bw, - sf: 12, - cr: 8, - sync_word: 0x1234, - tx_power_dbm: -9, - preamble_len: 16, - cad: 1, - }; - let mut buf = [0u8; RADIO_CONFIG_SIZE]; - cfg.write_to(&mut buf); - assert_eq!(RadioConfig::from_bytes(&buf), Some(cfg)); - } - } - - #[test] - fn radio_config_roundtrip_negative_power() { - let cfg = RadioConfig { - tx_power_dbm: TX_POWER_MAX, - ..make_config() - }; - let mut buf = [0u8; RADIO_CONFIG_SIZE]; - cfg.write_to(&mut buf); - assert_eq!(RadioConfig::from_bytes(&buf), Some(cfg)); - } - - #[test] - fn radio_config_from_short_buffer() { - let buf = [0u8; RADIO_CONFIG_SIZE - 1]; - assert!(RadioConfig::from_bytes(&buf).is_none()); - } - - #[test] - fn radio_config_from_empty_buffer() { - assert!(RadioConfig::from_bytes(&[]).is_none()); - } - - #[test] - fn radio_config_invalid_bandwidth() { - let mut buf = [0u8; RADIO_CONFIG_SIZE]; - make_config().write_to(&mut buf); - buf[4] = 255; // invalid bandwidth - assert!(RadioConfig::from_bytes(&buf).is_none()); - } - - // ── RadioConfig::validate ─────────────────────────────────────── - - #[test] - fn validate_freq_boundaries() { - let power_range = (-9, 22); - let base = make_config(); - - let mut cfg = RadioConfig { - freq_hz: 150_000_000, - ..base - }; - assert!(cfg.validate(power_range).is_ok()); - - cfg.freq_hz = 960_000_000; - assert!(cfg.validate(power_range).is_ok()); - - cfg.freq_hz = 149_999_999; - assert!(cfg.validate(power_range).is_err()); - - cfg.freq_hz = 960_000_001; - assert!(cfg.validate(power_range).is_err()); - } - - #[test] - fn validate_sf_boundaries() { - let power_range = (-9, 22); - let base = make_config(); - - for sf in 5..=12 { - assert!(RadioConfig { sf, ..base }.validate(power_range).is_ok()); - } - assert!(RadioConfig { sf: 4, ..base }.validate(power_range).is_err()); - assert!(RadioConfig { sf: 13, ..base } - .validate(power_range) - .is_err()); - } - - #[test] - fn validate_cr_boundaries() { - let power_range = (-9, 22); - let base = make_config(); - - for cr in 5..=8 { - assert!(RadioConfig { cr, ..base }.validate(power_range).is_ok()); - } - assert!(RadioConfig { cr: 4, ..base }.validate(power_range).is_err()); - assert!(RadioConfig { cr: 9, ..base }.validate(power_range).is_err()); - } - - #[test] - fn validate_tx_power_max_sentinel() { - let cfg = RadioConfig { - tx_power_dbm: TX_POWER_MAX, - ..make_config() - }; - assert!(cfg.validate((-9, 22)).is_ok()); - } - - #[test] - fn validate_tx_power_out_of_range() { - let cfg = RadioConfig { - tx_power_dbm: 23, - ..make_config() - }; - assert!(cfg.validate((-9, 22)).is_err()); - - let cfg = RadioConfig { - tx_power_dbm: -10, - ..make_config() - }; - assert!(cfg.validate((-9, 22)).is_err()); - } - - #[test] - fn validate_preamble_default_sentinel() { - let cfg = RadioConfig { - preamble_len: PREAMBLE_DEFAULT, - ..make_config() - }; - assert!(cfg.validate((-9, 22)).is_ok()); - } - - #[test] - fn validate_preamble_boundaries() { - let power_range = (-9, 22); - let base = make_config(); - - assert!(RadioConfig { - preamble_len: 6, - ..base - } - .validate(power_range) - .is_ok()); - assert!(RadioConfig { - preamble_len: 5, - ..base - } - .validate(power_range) - .is_err()); - } - - // ── RadioConfig::resolve ────────────────────────────────────── - - #[test] - fn resolve_power_max_sentinel() { - let cfg = RadioConfig { - tx_power_dbm: TX_POWER_MAX, - ..make_config() - }; - assert_eq!(cfg.resolve((-9, 22)).tx_power_dbm, 22); - } - - #[test] - fn resolve_power_explicit_unchanged() { - let cfg = RadioConfig { - tx_power_dbm: 10, - ..make_config() - }; - assert_eq!(cfg.resolve((-9, 22)).tx_power_dbm, 10); - } - - #[test] - fn resolve_preamble_default() { - let cfg = RadioConfig { - preamble_len: PREAMBLE_DEFAULT, - ..make_config() - }; - assert_eq!(cfg.resolve((-9, 22)).preamble_len, 16); - } - - #[test] - fn resolve_preamble_explicit_unchanged() { - let cfg = RadioConfig { - preamble_len: 32, - ..make_config() - }; - assert_eq!(cfg.resolve((-9, 22)).preamble_len, 32); - } - - // ── Command::from_bytes ───────────────────────────────────────── - - #[test] - fn command_ping() { - assert_eq!(Command::from_bytes(&[0]), Some(Command::Ping)); - } - - #[test] - fn command_get_config() { - assert_eq!(Command::from_bytes(&[1]), Some(Command::GetConfig)); - } - - #[test] - fn command_set_config() { - let cfg = make_config(); - let mut buf = [0u8; 1 + RADIO_CONFIG_SIZE]; - buf[0] = 2; - cfg.write_to(&mut buf[1..]); - assert_eq!(Command::from_bytes(&buf), Some(Command::SetConfig(cfg))); - } - - #[test] - fn command_start_stop_rx() { - assert_eq!(Command::from_bytes(&[3]), Some(Command::StartRx)); - assert_eq!(Command::from_bytes(&[4]), Some(Command::StopRx)); - } - - #[test] - fn command_transmit_no_config() { - let payload = b"hello"; - let mut buf = [0u8; 64]; - buf[0] = 5; // tag - buf[1] = 0; // has_config = false - buf[2..4].copy_from_slice(&(payload.len() as u16).to_le_bytes()); - buf[4..9].copy_from_slice(payload); - - match Command::from_bytes(&buf[..9]).unwrap() { - Command::Transmit { config, payload: p } => { - assert!(config.is_none()); - assert_eq!(p.as_slice(), b"hello"); - } - _ => panic!("expected Transmit"), - } - } - - #[test] - fn command_transmit_with_config() { - let cfg = make_config(); - let mut buf = [0u8; 64]; - buf[0] = 5; // tag - buf[1] = 1; // has_config = true - cfg.write_to(&mut buf[2..]); - let payload = b"test"; - let pos = 2 + RADIO_CONFIG_SIZE; - buf[pos..pos + 2].copy_from_slice(&(payload.len() as u16).to_le_bytes()); - buf[pos + 2..pos + 6].copy_from_slice(payload); - - match Command::from_bytes(&buf[..pos + 6]).unwrap() { - Command::Transmit { config, payload: p } => { - assert_eq!(config, Some(cfg)); - assert_eq!(p.as_slice(), b"test"); - } - _ => panic!("expected Transmit"), - } - } - - #[test] - fn command_transmit_empty_payload() { - let mut buf = [0u8; 4]; - buf[0] = 5; // tag - buf[1] = 0; // has_config = false - buf[2..4].copy_from_slice(&0u16.to_le_bytes()); - - match Command::from_bytes(&buf).unwrap() { - Command::Transmit { config, payload } => { - assert!(config.is_none()); - assert!(payload.is_empty()); - } - _ => panic!("expected Transmit"), - } - } - - #[test] - fn command_transmit_truncated() { - // Tag only — missing has_config byte - assert!(Command::from_bytes(&[5]).is_none()); - - // has_config=1 but no config bytes - assert!(Command::from_bytes(&[5, 1]).is_none()); - - // has_config=0 but no length bytes - assert!(Command::from_bytes(&[5, 0]).is_none()); - - // has_config=0, length says 5 but only 2 bytes of payload - let mut buf = [0u8; 6]; - buf[0] = 5; - buf[1] = 0; - buf[2..4].copy_from_slice(&5u16.to_le_bytes()); - buf[4] = 0xAA; - buf[5] = 0xBB; - assert!(Command::from_bytes(&buf).is_none()); - } - - #[test] - fn command_display_and_mac() { - assert_eq!(Command::from_bytes(&[6]), Some(Command::DisplayOn)); - assert_eq!(Command::from_bytes(&[7]), Some(Command::DisplayOff)); - assert_eq!(Command::from_bytes(&[8]), Some(Command::GetMac)); - } - - #[test] - fn command_invalid_tag() { - assert!(Command::from_bytes(&[9]).is_none()); - assert!(Command::from_bytes(&[255]).is_none()); - } - - #[test] - fn command_empty_buffer() { - assert!(Command::from_bytes(&[]).is_none()); - } - - // ── Response::write_to ────────────────────────────────────────── - - #[test] - fn response_pong() { - let mut buf = [0u8; 1]; - assert_eq!(Response::Pong.write_to(&mut buf), 1); - assert_eq!(buf[0], 0); - } - - #[test] - fn response_config() { - let cfg = make_config(); - let mut buf = [0u8; 1 + RADIO_CONFIG_SIZE]; - let n = Response::Config(cfg).write_to(&mut buf); - assert_eq!(n, 1 + RADIO_CONFIG_SIZE); - assert_eq!(buf[0], 1); - assert_eq!(RadioConfig::from_bytes(&buf[1..]), Some(cfg)); - } - - #[test] - fn response_rx_packet() { - let mut payload = Vec::new(); - let _ = payload.extend_from_slice(b"data"); - let mut buf = [0u8; 64]; - let n = Response::RxPacket { - rssi: -80, - snr: 10, - payload, - } - .write_to(&mut buf); - assert_eq!(n, 7 + 4); // tag(1) + rssi(2) + snr(2) + len(2) + "data"(4) - assert_eq!(buf[0], 2); - assert_eq!(i16::from_le_bytes([buf[1], buf[2]]), -80); - assert_eq!(i16::from_le_bytes([buf[3], buf[4]]), 10); - assert_eq!(u16::from_le_bytes([buf[5], buf[6]]), 4); - assert_eq!(&buf[7..11], b"data"); - } - - #[test] - fn response_tx_done() { - let mut buf = [0u8; 1]; - assert_eq!(Response::TxDone.write_to(&mut buf), 1); - assert_eq!(buf[0], 3); - } - - #[test] - fn response_ok() { - let mut buf = [0u8; 1]; - assert_eq!(Response::Ok.write_to(&mut buf), 1); - assert_eq!(buf[0], 4); - } - - #[test] - fn response_error_codes() { - let mut buf = [0u8; 2]; - for (code, val) in [ - (ErrorCode::InvalidConfig, 0), - (ErrorCode::RadioBusy, 1), - (ErrorCode::TxTimeout, 2), - (ErrorCode::NotConfigured, 4), - (ErrorCode::NoDisplay, 5), - ] { - let n = Response::Error(code).write_to(&mut buf); - assert_eq!(n, 2); - assert_eq!(buf[0], 5); - assert_eq!(buf[1], val); - } - } - - #[test] - fn response_mac_address() { - let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]; - let mut buf = [0u8; 7]; - let n = Response::MacAddress(mac).write_to(&mut buf); - assert_eq!(n, 7); - assert_eq!(buf[0], 6); - assert_eq!(&buf[1..7], &mac); - } -} diff --git a/firmware/src/radio.rs b/firmware/src/radio.rs deleted file mode 100644 index 99d09e3b..00000000 --- a/firmware/src/radio.rs +++ /dev/null @@ -1,386 +0,0 @@ -//! LoRa radio task: SX1262 state machine driven by host commands. -//! -//! Owns the radio peripheral exclusively. Receives [`Command`]s from the USB -//! task, drives the SX1262 via [`lora_phy`], and sends [`Response`]s back. -//! Publishes [`RadioStatus`] to the display task via a watch channel. -//! -//! # State machine -//! -//! ```text -//! Idle ──StartRx──► Receiving ──StopRx──► Idle -//! │ │ -//! └──Transmit──► Transmitting ──TxDone──► (previous state) -//! ``` -//! -//! # Invariants -//! -//! - This task never panics. All errors are reported to the host or logged. -//! - Config is validated before use (see [`RadioConfig::validate`]). - -use defmt::{error, info, warn}; -use embassy_executor::task; -use embassy_futures::select::{select, Either}; -use embassy_time::Delay; -use lora_phy::mod_params::RadioError; -use lora_phy::{LoRa, RxMode}; - -use crate::board::{Board, LoRaBoard, RadioDriver, RadioParts}; -use crate::channel::{CommandChannel, RadioState, RadioStatus, ResponseChannel, StatusWatch}; -use crate::protocol::{self, Command, ErrorCode, RadioConfig, Response}; - -const MAX_PAYLOAD: usize = protocol::MAX_PAYLOAD; -// TX power range comes from the board trait: Board::TX_POWER_RANGE - -type Radio = LoRa; - -// ── Fixed LoRa radio parameters ───────────────────────────────────── -// -// These are intentionally not host-configurable. They are sensible -// defaults for a general-purpose LoRa pipe. Making them configurable -// would add protocol complexity for marginal benefit. - -/// Explicit header mode (variable-length packets). -const IMPLICIT_HEADER: bool = false; - -/// Enable CRC on received/transmitted packets. -const CRC_ON: bool = true; - -/// Standard IQ polarity (not inverted). -const IQ_INVERTED: bool = false; - -// ── Task entry point ──────────────────────────────────────────────── - -#[task] -pub async fn radio_task( - parts: RadioParts, - commands: &'static CommandChannel, - responses: &'static ResponseChannel, - status: &'static StatusWatch, -) { - let mut state = RadioStatus::default(); - status.sender().send(state.clone()); - - let mut lora = match LoRa::new(parts.driver, false, parts.delay).await { - Ok(l) => l, - Err(e) => { - error!("radio init failed: {}", e); - responses - .send(Response::Error(ErrorCode::InvalidConfig)) - .await; - // Radio is non-functional — respond to commands but can't do RF. - loop { - let cmd = commands.receive().await; - if let Command::Ping = cmd { - responses.send(Response::Pong).await; - } else { - responses - .send(Response::Error(ErrorCode::InvalidConfig)) - .await; - } - } - } - }; - - info!("radio initialized"); - let mut rx_buf = [0u8; MAX_PAYLOAD]; - - loop { - if state.state == RadioState::Receiving { - // Defensive: config must be Some when Receiving. If not, recover. - let cfg = match state.config { - Some(c) => c, - None => { - warn!("BUG: receiving without config, returning to idle"); - state.state = RadioState::Idle; - status.sender().send(state.clone()); - continue; - } - }; - - match select(rx_once(&mut lora, &cfg, &mut rx_buf), commands.receive()).await { - Either::First(rx_result) => match rx_result { - Ok((len, pkt_status)) => { - state.rx_count = state.rx_count.wrapping_add(1); - state.last_rssi = Some(pkt_status.rssi); - state.last_snr = Some(pkt_status.snr); - status.sender().send(state.clone()); - - let copy_len = (len as usize).min(MAX_PAYLOAD); - if (len as usize) > copy_len { - warn!("RX payload truncated: {} > {}", len, MAX_PAYLOAD); - } - let mut payload = heapless::Vec::new(); - // copy_len <= MAX_PAYLOAD == Vec capacity, so this cannot fail. - let _ = payload.extend_from_slice(&rx_buf[..copy_len]); - - responses - .send(Response::RxPacket { - rssi: pkt_status.rssi, - snr: pkt_status.snr, - payload, - }) - .await; - - if let Err(e) = start_rx(&mut lora, &cfg).await { - warn!("restart RX failed: {}", e); - state.state = RadioState::Idle; - status.sender().send(state.clone()); - responses.send(Response::Error(ErrorCode::RadioBusy)).await; - } - } - Err(e) => { - warn!("RX error: {}", e); - if start_rx(&mut lora, &cfg).await.is_err() { - state.state = RadioState::Idle; - status.sender().send(state.clone()); - responses.send(Response::Error(ErrorCode::RadioBusy)).await; - } - } - }, - Either::Second(cmd) => { - handle_cmd(cmd, &mut lora, &mut state, responses, status).await; - } - } - } else { - let cmd = commands.receive().await; - handle_cmd(cmd, &mut lora, &mut state, responses, status).await; - } - } -} - -// ── Command handler ───────────────────────────────────────────────── - -async fn handle_cmd( - cmd: Command, - lora: &mut Radio, - state: &mut RadioStatus, - responses: &ResponseChannel, - status: &StatusWatch, -) { - match cmd { - Command::Ping => { - responses.send(Response::Pong).await; - } - Command::GetConfig => { - if let Some(cfg) = state.config { - responses.send(Response::Config(cfg)).await; - } else { - responses - .send(Response::Error(ErrorCode::NotConfigured)) - .await; - } - } - Command::SetConfig(cfg) => { - if let Err(reason) = cfg.validate(Board::TX_POWER_RANGE) { - warn!("SetConfig rejected: {}", reason); - responses - .send(Response::Error(ErrorCode::InvalidConfig)) - .await; - } else { - // Resolve TX_POWER_MAX sentinel to board's actual max - state.config = Some(cfg.resolve(Board::TX_POWER_RANGE)); - status.sender().send(state.clone()); - responses.send(Response::Ok).await; - } - } - Command::StartRx => { - if let Some(cfg) = state.config { - match start_rx(lora, &cfg).await { - Ok(()) => { - state.state = RadioState::Receiving; - status.sender().send(state.clone()); - responses.send(Response::Ok).await; - } - Err(e) => { - warn!("StartRx failed: {}", e); - responses - .send(Response::Error(ErrorCode::InvalidConfig)) - .await; - } - } - } else { - responses - .send(Response::Error(ErrorCode::NotConfigured)) - .await; - } - } - Command::StopRx => { - // Best-effort: if standby fails, radio is in unknown state - // but there's nothing useful we can do except continue. - let _ = lora.enter_standby().await; - state.state = RadioState::Idle; - status.sender().send(state.clone()); - responses.send(Response::Ok).await; - } - Command::DisplayOn | Command::DisplayOff | Command::GetMac => {} - Command::Transmit { config, payload } => { - let tx_config = config - .map(|c| c.resolve(Board::TX_POWER_RANGE)) - .or(state.config); - if let Some(cfg) = tx_config { - if let Err(reason) = cfg.validate(Board::TX_POWER_RANGE) { - warn!("TX config rejected: {}", reason); - responses - .send(Response::Error(ErrorCode::InvalidConfig)) - .await; - return; - } - - let was_receiving = state.state == RadioState::Receiving; - state.state = RadioState::Transmitting; - status.sender().send(state.clone()); - - match do_tx(lora, &cfg, &payload).await { - Ok(()) => { - state.tx_count = state.tx_count.wrapping_add(1); - responses.send(Response::TxDone).await; - } - Err(e) => { - warn!("TX failed: {}", e); - responses.send(Response::Error(ErrorCode::TxTimeout)).await; - } - } - - // Restore previous state. If we were receiving, restart RX - // using the stored config — NOT the per-packet TX override. - state.state = if was_receiving { - // state.config is always Some when was_receiving (invariant). - let rx_cfg = state.config.unwrap_or(cfg); - match start_rx(lora, &rx_cfg).await { - Ok(()) => RadioState::Receiving, - Err(e) => { - warn!("post-TX RX restart failed: {}", e); - RadioState::Idle - } - } - } else { - RadioState::Idle - }; - status.sender().send(state.clone()); - } else { - responses - .send(Response::Error(ErrorCode::NotConfigured)) - .await; - } - } - } -} - -// ── LoRa helpers ──────────────────────────────────────────────────── - -fn to_bw(bw: protocol::Bandwidth) -> lora_phy::mod_params::Bandwidth { - use lora_phy::mod_params::Bandwidth::*; - match bw { - protocol::Bandwidth::Khz7 => _7KHz, - protocol::Bandwidth::Khz10 => _10KHz, - protocol::Bandwidth::Khz15 => _15KHz, - protocol::Bandwidth::Khz20 => _20KHz, - protocol::Bandwidth::Khz31 => _31KHz, - protocol::Bandwidth::Khz41 => _41KHz, - protocol::Bandwidth::Khz62 => _62KHz, - protocol::Bandwidth::Khz125 => _125KHz, - protocol::Bandwidth::Khz250 => _250KHz, - protocol::Bandwidth::Khz500 => _500KHz, - } -} - -fn to_sf(sf: u8) -> lora_phy::mod_params::SpreadingFactor { - use lora_phy::mod_params::SpreadingFactor::*; - match sf { - 5 => _5, - 6 => _6, - 7 => _7, - 8 => _8, - 9 => _9, - 10 => _10, - 11 => _11, - 12 => _12, - _ => { - warn!("BUG: invalid SF {} passed validation", sf); - _7 - } - } -} - -fn to_cr(cr: u8) -> lora_phy::mod_params::CodingRate { - use lora_phy::mod_params::CodingRate::*; - match cr { - 5 => _4_5, - 6 => _4_6, - 7 => _4_7, - 8 => _4_8, - _ => { - warn!("BUG: invalid CR {} passed validation", cr); - _4_5 - } - } -} - -/// Create modulation parameters from a validated config. -fn modulation_params( - lora: &mut Radio, - cfg: &RadioConfig, -) -> Result { - lora.create_modulation_params(to_sf(cfg.sf), to_bw(cfg.bw), to_cr(cfg.cr), cfg.freq_hz) -} - -async fn start_rx(lora: &mut Radio, cfg: &RadioConfig) -> Result<(), RadioError> { - let mdltn = modulation_params(lora, cfg)?; - let pkt = lora.create_rx_packet_params( - cfg.preamble_len, - IMPLICIT_HEADER, - MAX_PAYLOAD as u8, - CRC_ON, - IQ_INVERTED, - &mdltn, - )?; - lora.prepare_for_rx(RxMode::Continuous, &mdltn, &pkt).await -} - -async fn rx_once( - lora: &mut Radio, - cfg: &RadioConfig, - buf: &mut [u8], -) -> Result<(u8, lora_phy::mod_params::PacketStatus), RadioError> { - let mdltn = modulation_params(lora, cfg)?; - let pkt = lora.create_rx_packet_params( - cfg.preamble_len, - IMPLICIT_HEADER, - MAX_PAYLOAD as u8, - CRC_ON, - IQ_INVERTED, - &mdltn, - )?; - lora.rx(&pkt, buf).await -} - -/// Maximum CAD attempts before transmitting anyway. -const CAD_MAX_RETRIES: u8 = 10; - -async fn do_tx(lora: &mut Radio, cfg: &RadioConfig, payload: &[u8]) -> Result<(), RadioError> { - let mdltn = modulation_params(lora, cfg)?; - - // Listen-before-talk: check channel is clear before transmitting. - if cfg.cad_enabled() { - lora.prepare_for_cad(&mdltn).await?; - for _ in 0..CAD_MAX_RETRIES { - if !lora.cad(&mdltn).await? { - break; // Channel is clear - } - // Channel busy — wait briefly and retry - embassy_time::Timer::after_millis(10).await; - lora.prepare_for_cad(&mdltn).await?; - } - } - - let mut tx_pkt = lora.create_tx_packet_params( - cfg.preamble_len, - IMPLICIT_HEADER, - CRC_ON, - IQ_INVERTED, - &mdltn, - )?; - lora.prepare_for_tx(&mdltn, &mut tx_pkt, cfg.tx_power_dbm as i32, payload) - .await?; - lora.tx().await -} diff --git a/mux/python/.gitignore b/mux/python/.gitignore deleted file mode 100644 index 21d0b898..00000000 --- a/mux/python/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.venv/ diff --git a/mux/python/README.md b/mux/python/README.md deleted file mode 100644 index 551b067f..00000000 --- a/mux/python/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# DongLoRa Mux (Python) - -USB multiplexer daemon — lets multiple applications share one DongLoRa -dongle simultaneously. - -## What It Does - -- Owns the USB serial connection exclusively -- Exposes a Unix domain socket (and optional TCP) speaking the same - COBS-framed protocol -- RxPacket frames broadcast to all connected clients -- StartRx/StopRx reference-counted across clients -- SetConfig locked once set (single client can change freely) - -## Running - -```sh -just run # start the mux daemon -just verbose # start with verbose logging -just run --tcp 5741 --port /dev/ttyACM0 # with options -``` - -## Depends On - -- [clients/python](../../clients/python/) — the `donglora` client library - (protocol encoding, device discovery) diff --git a/mux/python/donglora_mux/__init__.py b/mux/python/donglora_mux/__init__.py deleted file mode 100644 index 02877d82..00000000 --- a/mux/python/donglora_mux/__init__.py +++ /dev/null @@ -1,517 +0,0 @@ -"""DongLoRa USB Multiplexer — share one dongle with multiple applications. - -Owns the USB serial connection and exposes a Unix domain socket that speaks -the same COBS-framed protocol. Clients connect via the socket (or just call -dl.connect() which auto-detects the mux) and get: - - • Solicited responses routed back to the commanding client - • RxPacket frames broadcast to ALL connected clients - • StartRx/StopRx reference-counted so the radio stays in RX as long as - anyone wants it - • SetConfig locked once set — subsequent clients must use the same config - (a single client can change config freely, like a scanner) - -Usage: - donglora-mux [--port /dev/ttyACM0] [--socket /tmp/donglora-mux.sock] [--tcp 5741] -""" - -from __future__ import annotations - -import argparse -import asyncio -import logging -import os -import signal -from pathlib import Path - -import serial -from cobs import cobs - -import donglora as dl - -log = logging.getLogger("donglora-mux") - -# ── Protocol constants ─────────────────────────────────────────── - -TAG_RXPACKET = 2 -TAG_OK = 4 -TAG_ERROR = 5 -CMD_TAG_SET_CONFIG = 2 -CMD_TAG_START_RX = 3 -CMD_TAG_STOP_RX = 4 - -ERROR_INVALID_CONFIG = 0 - - -def default_socket_path() -> str: - """Resolve the mux socket path in priority order.""" - env = os.environ.get("DONGLORA_MUX") - if env: - return env - xdg = os.environ.get("XDG_RUNTIME_DIR") - if xdg: - return os.path.join(xdg, "donglora", "mux.sock") - return "/tmp/donglora-mux.sock" - - -# ── Per-client state ───────────────────────────────────────────── - -class Client: - """Tracks one connected client.""" - - _next_id = 0 - - def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): - self.id = Client._next_id - Client._next_id += 1 - self.reader = reader - self.writer = writer - self.rx_interested = False # has this client called StartRx? - self.send_queue: asyncio.Queue[bytes] = asyncio.Queue(maxsize=256) - self._sender_task: asyncio.Task | None = None - - @property - def label(self) -> str: - return f"client-{self.id}" - - def start_sender(self) -> None: - self._sender_task = asyncio.create_task(self._sender(), name=f"{self.label}-sender") - - async def _sender(self) -> None: - """Drain send_queue → socket. Runs until cancelled or write fails.""" - try: - while True: - frame = await self.send_queue.get() - self.writer.write(frame) - await self.writer.drain() - except (ConnectionError, asyncio.CancelledError): - pass - - async def enqueue(self, cobs_frame: bytes) -> None: - """Enqueue a COBS frame for sending. Drops oldest RxPacket on overflow.""" - try: - self.send_queue.put_nowait(cobs_frame) - except asyncio.QueueFull: - # Drop oldest to make room (best-effort for slow clients) - try: - self.send_queue.get_nowait() - except asyncio.QueueEmpty: - pass - try: - self.send_queue.put_nowait(cobs_frame) - except asyncio.QueueFull: - pass - - def close(self) -> None: - if self._sender_task: - self._sender_task.cancel() - try: - self.writer.close() - except Exception: - pass - - -# ── Mux daemon ─────────────────────────────────────────────────── - -class MuxDaemon: - def __init__(self, serial_port: str, socket_path: str, tcp_addr: tuple[str, int] | None = None): - self.serial_port = serial_port - self.socket_path = socket_path - self.tcp_addr = tcp_addr - self.ser: serial.Serial | None = None - self.clients: dict[int, Client] = {} - self.cmd_queue: asyncio.Queue[tuple[int, bytes]] = asyncio.Queue() - self.pending_response: asyncio.Future[bytes] | None = None - self.locked_config: bytes | None = None # raw RadioConfig bytes (13 bytes) once set - self._shutdown = asyncio.Event() - - # ── Serial I/O (blocking, run in threads) ──────────────────── - - def _serial_read_frame(self) -> bytes | None: - """Blocking read of one COBS frame from dongle.""" - assert self.ser is not None - return dl.read_frame(self.ser) - - def _serial_write(self, data: bytes) -> None: - """Blocking write to dongle.""" - assert self.ser is not None - self.ser.write(data) - self.ser.flush() - - # ── Dongle reader task ─────────────────────────────────────── - - async def dongle_reader(self) -> None: - """Read frames from the dongle, dispatch solicited vs broadcast.""" - consecutive_errors = 0 - max_retries = 3 - while not self._shutdown.is_set(): - try: - raw = await asyncio.to_thread(self._serial_read_frame) - except (serial.SerialException, OSError) as e: - consecutive_errors += 1 - if consecutive_errors >= max_retries: - log.error("Dongle read error (giving up after %d consecutive failures): %s", consecutive_errors, e) - self._handle_dongle_disconnect() - return - log.warning("Dongle read glitch (%d/%d): %s", consecutive_errors, max_retries, e) - await asyncio.sleep(0.1) - continue - - consecutive_errors = 0 - - if raw is None: - continue - - tag = raw[0] - cobs_frame = dl.cobs_encode(raw) - - if tag == TAG_RXPACKET: - # Broadcast to all clients - for client in list(self.clients.values()): - await client.enqueue(cobs_frame) - else: - # Solicited response — resolve the pending future - if self.pending_response and not self.pending_response.done(): - self.pending_response.set_result(raw) - else: - log.warning("Unsolicited non-RxPacket response (tag %d) — dropped", tag) - - # ── Dongle writer task ─────────────────────────────────────── - - async def dongle_writer(self) -> None: - """Pull commands from queue, send one at a time, route responses.""" - loop = asyncio.get_running_loop() - - while not self._shutdown.is_set(): - client_id, raw_cmd = await self.cmd_queue.get() - - # Check for StartRx/StopRx ref-counting intercept - intercepted = await self._maybe_intercept(client_id, raw_cmd) - if intercepted is not None: - # Send the synthetic response to the client - if client_id in self.clients: - await self.clients[client_id].enqueue(dl.cobs_encode(intercepted)) - continue - - # Create future for the solicited response - self.pending_response = loop.create_future() - - # Send COBS-encoded command to dongle - cobs_frame = dl.cobs_encode(raw_cmd) - try: - await asyncio.to_thread(self._serial_write, cobs_frame) - except (serial.SerialException, OSError) as e: - log.error("Dongle write error: %s", e) - self.pending_response.set_result(bytes([5, 1])) # Error(RadioBusy) - self._handle_dongle_disconnect() - return - - # Wait for dongle_reader to resolve the future - try: - response = await asyncio.wait_for(self.pending_response, timeout=10.0) - except asyncio.TimeoutError: - log.error("Dongle response timeout") - response = bytes([5, 1]) # Error(RadioBusy) - finally: - self.pending_response = None - - # Track state on successful forward - cmd_tag = raw_cmd[0] - resp_tag = response[0] if response else 0xFF - if cmd_tag == CMD_TAG_SET_CONFIG and resp_tag == TAG_OK: - self.locked_config = raw_cmd[1:] - log.info("Radio config locked: %s", self.locked_config.hex()) - elif cmd_tag == CMD_TAG_START_RX and resp_tag == TAG_OK: - if client_id in self.clients: - self.clients[client_id].rx_interested = True - elif cmd_tag == CMD_TAG_STOP_RX and resp_tag == TAG_OK: - if client_id in self.clients: - self.clients[client_id].rx_interested = False - - # Route response to commanding client (if still connected) - if client_id in self.clients: - await self.clients[client_id].enqueue(dl.cobs_encode(response)) - - # ── StartRx / StopRx reference counting ────────────────────── - - def _rx_interest_count(self) -> int: - return sum(1 for c in self.clients.values() if c.rx_interested) - - async def _maybe_intercept(self, client_id: int, raw_cmd: bytes) -> bytes | None: - """Intercept SetConfig/StartRx/StopRx for smart multiplexing. - - Returns a synthetic response if intercepted, None to forward normally. - """ - if not raw_cmd: - return None - cmd_tag = raw_cmd[0] - client = self.clients.get(client_id) - - if cmd_tag == CMD_TAG_SET_CONFIG: - config_bytes = raw_cmd[1:] # 13-byte RadioConfig - if len(self.clients) <= 1: - # Single client — allow free config changes (scanner mode) - # Forward to dongle, update locked_config on success - return None - if self.locked_config is None: - # First SetConfig with multiple clients — forward and lock - return None - if config_bytes == self.locked_config: - # Same config — reply Ok without hitting the dongle - log.debug("%s: SetConfig matches locked config — Ok", - client.label if client else f"id-{client_id}") - return bytes([TAG_OK]) - # Different config — reject - log.warning("%s: SetConfig rejected (conflicts with locked config)", - client.label if client else f"id-{client_id}") - return bytes([TAG_ERROR, ERROR_INVALID_CONFIG]) - - if cmd_tag == CMD_TAG_START_RX: - if client and client.rx_interested: - # Already interested — no-op, reply Ok - return bytes([TAG_OK]) - if self._rx_interest_count() > 0: - # Someone else is already receiving — just mark this client, reply Ok - if client: - client.rx_interested = True - return bytes([TAG_OK]) - # First interested client — forward to dongle (return None) - return None - - if cmd_tag == CMD_TAG_STOP_RX: - if client and not client.rx_interested: - # Wasn't interested — no-op, reply Ok - return bytes([TAG_OK]) - if client: - client.rx_interested = False - if self._rx_interest_count() > 0: - # Others still interested — don't stop, reply Ok - return bytes([TAG_OK]) - # Last interested client — forward to dongle (return None) - return None - - return None - - # ── Client handling ────────────────────────────────────────── - - async def client_handler(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: - client = Client(reader, writer) - self.clients[client.id] = client - client.start_sender() - log.info("%s connected (%d total)", client.label, len(self.clients)) - - try: - buf = b"" - while True: - data = await reader.read(4096) - if not data: - break - buf += data - # Extract complete COBS frames (delimited by 0x00) - while b"\x00" in buf: - idx = buf.index(b"\x00") - encoded = buf[:idx] - buf = buf[idx + 1:] - if not encoded: - continue - try: - raw_cmd = cobs.decode(encoded) - except cobs.DecodeError: - log.warning("%s: bad COBS frame — skipped", client.label) - continue - await self.cmd_queue.put((client.id, raw_cmd)) - except (ConnectionError, asyncio.CancelledError): - pass - finally: - await self._remove_client(client) - - async def _remove_client(self, client: Client) -> None: - """Clean up after a disconnected client.""" - was_interested = client.rx_interested - client.close() - self.clients.pop(client.id, None) - log.info("%s disconnected (%d remain)", client.label, len(self.clients)) - - # If this client had RX interest and was the last one, stop RX - if was_interested and self._rx_interest_count() == 0: - log.info("Last RX-interested client gone — sending StopRx") - await self.cmd_queue.put((-1, bytes([CMD_TAG_STOP_RX]))) - - # Reset locked config when all clients are gone - if not self.clients: - if self.locked_config is not None: - log.info("All clients gone — config lock released") - self.locked_config = None - - # ── Dongle disconnect ──────────────────────────────────────── - - _dongle_lost: asyncio.Event - - def _handle_dongle_disconnect(self) -> None: - log.error("Dongle disconnected — will reconnect") - if self.pending_response and not self.pending_response.done(): - self.pending_response.set_result(bytes([5, 1])) # Error(RadioBusy) - if self.ser: - try: - self.ser.close() - except Exception: - pass - self.ser = None - self._dongle_lost.set() - - # ── Dongle connection ────────────────────────────────────────── - - def _open_dongle(self) -> bool: - """Open and ping the dongle. Returns True on success.""" - try: - self.ser = serial.Serial(self.serial_port, timeout=2.0, exclusive=True) - self.ser.reset_input_buffer() - self._serial_write(dl.cobs_encode(dl.encode_command("Ping"))) - pong = self._serial_read_frame() - if not pong or pong[0] != 0: - log.warning("Dongle did not respond to Ping") - self.ser.close() - self.ser = None - return False - log.info("Dongle responded to Ping") - return True - except (serial.SerialException, OSError) as e: - log.warning("Could not open dongle: %s", e) - self.ser = None - return False - - # ── Main entry ─────────────────────────────────────────────── - - async def run(self) -> None: - self._dongle_lost = asyncio.Event() - - # Prepare socket path - sock_path = Path(self.socket_path) - sock_path.parent.mkdir(parents=True, exist_ok=True) - if sock_path.exists(): - sock_path.unlink() - - # Start servers (these stay up for the lifetime of the daemon) - server = await asyncio.start_unix_server(self.client_handler, path=str(sock_path)) - log.info("Unix socket listening on %s", self.socket_path) - - tcp_server = None - if self.tcp_addr: - tcp_server = await asyncio.start_server( - self.client_handler, self.tcp_addr[0], self.tcp_addr[1], - ) - log.info("TCP listening on %s:%d", *self.tcp_addr) - - # Connect/reconnect loop — servers stay up, dongle comes and goes - try: - while not self._shutdown.is_set(): - # Connect to dongle (retry until it appears) - log.info("Opening dongle on %s", self.serial_port) - while not self._shutdown.is_set(): - if await asyncio.to_thread(self._open_dongle): - break - await asyncio.sleep(2) - - if self._shutdown.is_set(): - break - - # Run dongle reader/writer until dongle is lost - self._dongle_lost.clear() - reader_task = asyncio.create_task(self.dongle_reader(), name="dongle-reader") - writer_task = asyncio.create_task(self.dongle_writer(), name="dongle-writer") - - # Wait for dongle loss or shutdown - shutdown_task = asyncio.create_task(self._shutdown.wait()) - lost_task = asyncio.create_task(self._dongle_lost.wait()) - await asyncio.wait( - [shutdown_task, lost_task], - return_when=asyncio.FIRST_COMPLETED, - ) - shutdown_task.cancel() - lost_task.cancel() - - # Tear down dongle tasks - reader_task.cancel() - writer_task.cancel() - try: - await asyncio.gather(reader_task, writer_task, return_exceptions=True) - except asyncio.CancelledError: - pass - - # Drain any pending commands with errors - while not self.cmd_queue.empty(): - try: - self.cmd_queue.get_nowait() - except asyncio.QueueEmpty: - break - - if not self._shutdown.is_set(): - log.info("Reconnecting in 2 seconds...") - await asyncio.sleep(2) - finally: - # Cleanup - log.info("Shutting down...") - for client in list(self.clients.values()): - client.close() - server.close() - await server.wait_closed() - if tcp_server: - tcp_server.close() - await tcp_server.wait_closed() - if self.ser: - self.ser.close() - if sock_path.exists(): - sock_path.unlink() - log.info("Stopped.") - - -# ── CLI ────────────────────────────────────────────────────────── - -def main() -> None: - parser = argparse.ArgumentParser(description="DongLoRa USB Multiplexer") - parser.add_argument("--port", "-p", default=None, help="Serial port (auto-detect if omitted)") - parser.add_argument("--socket", "-s", default=None, help="Unix socket path") - parser.add_argument("--tcp", "-t", default="0.0.0.0:5741", metavar="[HOST:]PORT", - help="TCP listen address (default: 0.0.0.0:5741, 'none' to disable)") - parser.add_argument("--verbose", "-v", action="store_true") - args = parser.parse_args() - - logging.basicConfig( - level=logging.DEBUG if args.verbose else logging.INFO, - format="%(asctime)s %(name)s %(levelname)s %(message)s", - datefmt="%H:%M:%S", - ) - - port = args.port or dl.find_port() - if not port: - log.info("Waiting for DongLoRa device...") - port = dl.wait_for_device() - - socket_path = args.socket or default_socket_path() - - tcp_addr = None - if args.tcp and args.tcp.lower() != "none": - if ":" in args.tcp: - host, _, p = args.tcp.rpartition(":") - tcp_addr = (host, int(p)) - else: - tcp_addr = ("0.0.0.0", int(args.tcp)) - - daemon = MuxDaemon(port, socket_path, tcp_addr=tcp_addr) - - loop = asyncio.new_event_loop() - - def _signal_handler() -> None: - daemon._shutdown.set() - - for sig in (signal.SIGINT, signal.SIGTERM): - loop.add_signal_handler(sig, _signal_handler) - - try: - loop.run_until_complete(daemon.run()) - finally: - loop.close() - - -if __name__ == "__main__": - main() diff --git a/mux/python/donglora_mux/__main__.py b/mux/python/donglora_mux/__main__.py deleted file mode 100644 index b52f6c79..00000000 --- a/mux/python/donglora_mux/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from donglora_mux import main - -main() diff --git a/mux/python/justfile b/mux/python/justfile deleted file mode 100644 index e37cddd9..00000000 --- a/mux/python/justfile +++ /dev/null @@ -1,15 +0,0 @@ -set shell := ["bash", "-c"] - -default: run - -[private] -_ensure_tools: - @mise trust --yes . 2>/dev/null; mise install --quiet - -# Run the mux daemon -run *args: _ensure_tools - @uv run -m donglora_mux {{args}} - -# Run with verbose logging -verbose *args: _ensure_tools - @uv run -m donglora_mux --verbose {{args}} diff --git a/mux/python/mise.toml b/mux/python/mise.toml deleted file mode 100644 index a245b1c0..00000000 --- a/mux/python/mise.toml +++ /dev/null @@ -1,4 +0,0 @@ -[tools] -python = "3.14" -uv = "latest" -just = "latest" diff --git a/mux/python/pyproject.toml b/mux/python/pyproject.toml deleted file mode 100644 index 8ff3deaa..00000000 --- a/mux/python/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[project] -name = "donglora-mux" -version = "0.1.0" -description = "DongLoRa USB Multiplexer — share one dongle with multiple applications" -license = "MIT" -requires-python = ">=3.10" -authors = [{ name = "Stephen Waits" }] -dependencies = [ - "donglora", - "cobs", - "pyserial", -] - -[project.scripts] -donglora-mux = "donglora_mux:main" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.uv.sources] -donglora = { path = "../../clients/python", editable = true } diff --git a/mux/python/uv.lock b/mux/python/uv.lock deleted file mode 100644 index 7ea4c811..00000000 --- a/mux/python/uv.lock +++ /dev/null @@ -1,64 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.10" - -[[package]] -name = "cobs" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/ef/ea149311227a4fc3160cc885fce06da7c7d76782a308ef070b8065c69953/cobs-1.2.2.tar.gz", hash = "sha256:dbdd5e32111d72786f83d0c269215dcd6ac629b1ac1962c6878221f3b2ca98da", size = 14582, upload-time = "2025-07-20T01:08:35.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/e0/9574590639a624a9a84c89a569f36343523a306fff2ffddc9468a2b9b05d/cobs-1.2.2-cp312-cp312-win32.whl", hash = "sha256:3afe0cec5d91164f84dcf35b471949aa36285664d21ee984350e39d485707d11", size = 26400, upload-time = "2025-07-20T01:08:30.288Z" }, - { url = "https://files.pythonhosted.org/packages/d3/4f/5418d9decdc4cf27a814fc189bc4fa44d216d37b871d633654f37ed331f8/cobs-1.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:4da6048f6e16cbd6c8ec5e95e8520e226248ffb8d31e9d6fc5e0888b3b65c54b", size = 27776, upload-time = "2025-07-20T01:08:31.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/df/7f07559954d53d09680aa4f5c72db999e7643253e2ca4e431023fe5c1c40/cobs-1.2.2-cp313-cp313-win32.whl", hash = "sha256:a8fdf6ca70247e7271caea6f2f6f9c0343f5e872e2fd47e21f71831fa9ababa8", size = 26401, upload-time = "2025-07-20T01:08:32.508Z" }, - { url = "https://files.pythonhosted.org/packages/0c/00/5b063105ff567bb7fb72c3cd3e96116a3889381fa2c0e7747497391dbecc/cobs-1.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe3f7194d5fbbc02e8fb03d26ad626a33fa0d420b50d66ed60ac4bf516322d8e", size = 27774, upload-time = "2025-07-20T01:08:33.692Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/de1e889c26fcfc9ae6852b8f99e744e6690a3baae99f63e2b9fffdd21710/cobs-1.2.2-cp314-cp314-win32.whl", hash = "sha256:bcf08cb0b68c1824a87cfa89e0684d5a3eaee9b9e08b02ce4a54d5589536f01c", size = 26779, upload-time = "2026-03-09T02:18:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/b6/64/390e226843cc83066acc60257e0e71ebdbce6dd915ec4f3d34101b9ac31b/cobs-1.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:1f27b7a553d809487287e4ed88540b2c30ed9c9bb596173bfcf549e705af7bf0", size = 28205, upload-time = "2026-03-09T02:18:20.193Z" }, -] - -[[package]] -name = "donglora" -version = "0.1.0" -source = { editable = "../../clients/python" } -dependencies = [ - { name = "cobs" }, - { name = "pyserial" }, -] - -[package.metadata] -requires-dist = [ - { name = "anthropic", marker = "extra == 'orac'" }, - { name = "cobs" }, - { name = "pycryptodome", marker = "extra == 'meshcore'" }, - { name = "pycryptodome", marker = "extra == 'orac'" }, - { name = "pynacl", marker = "extra == 'meshcore'" }, - { name = "pynacl", marker = "extra == 'orac'" }, - { name = "pyserial" }, -] -provides-extras = ["meshcore", "orac"] - -[[package]] -name = "donglora-mux" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "cobs" }, - { name = "donglora" }, - { name = "pyserial" }, -] - -[package.metadata] -requires-dist = [ - { name = "cobs" }, - { name = "donglora", editable = "../../clients/python" }, - { name = "pyserial" }, -] - -[[package]] -name = "pyserial" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, -] diff --git a/mux/rust/.gitignore b/mux/rust/.gitignore deleted file mode 100644 index ea8c4bf7..00000000 --- a/mux/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/mux/rust/CHANGELOG.md b/mux/rust/CHANGELOG.md deleted file mode 100644 index 07452c90..00000000 --- a/mux/rust/CHANGELOG.md +++ /dev/null @@ -1,29 +0,0 @@ -# Changelog - -## 0.1.1 — 2026-04-06 - -### Fixed - -- Prevent multiple mux instances from running simultaneously via file-based - exclusive lock (`fd-lock`); stale sockets from crashed instances are cleaned - up automatically -- Clearer error message when TCP port is already in use - -## 0.1.0 — 2026-04-06 - -Initial release. - -### Features - -- USB multiplexer daemon: share one DongLoRa dongle with multiple applications -- Unix domain socket and optional TCP listeners (same COBS-framed protocol) -- RxPacket broadcast to all connected clients -- Reference-counted StartRx/StopRx across clients -- Config locking: first client sets radio config, others must match or fail -- Auto-reconnect on USB hot-plug with ping verification -- Bounded per-client send queues (256 frames) with backpressure isolation -- Smart interception: redundant commands get synthetic responses without - hitting the dongle -- Graceful shutdown on SIGINT/SIGTERM with socket cleanup -- TCP_NODELAY on accepted connections for low-latency frame delivery -- CLI: `--port`, `--socket`, `--tcp`, `--verbose` options diff --git a/mux/rust/Cargo.lock b/mux/rust/Cargo.lock deleted file mode 100644 index ec5d4d5a..00000000 --- a/mux/rust/Cargo.lock +++ /dev/null @@ -1,940 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "donglora-client" -version = "0.1.0" -dependencies = [ - "anyhow", - "serialport", - "tracing", - "ucobs", -] - -[[package]] -name = "donglora-mux" -version = "0.1.1" -dependencies = [ - "anyhow", - "clap", - "donglora-client", - "fd-lock", - "tokio", - "tokio-serial", - "tokio-util", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "io-kit-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" -dependencies = [ - "core-foundation-sys", - "mach2", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[package]] -name = "libudev" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" -dependencies = [ - "libc", - "libudev-sys", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "mach2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "mio-serial" -version = "5.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029e1f407e261176a983a6599c084efd322d9301028055c87174beac71397ba3" -dependencies = [ - "log", - "mio", - "nix 0.29.0", - "serialport", - "winapi", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serialport" -version = "4.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "core-foundation", - "core-foundation-sys", - "io-kit-sys", - "libudev", - "mach2", - "nix 0.26.4", - "scopeguard", - "unescaper", - "windows-sys 0.52.0", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tokio" -version = "1.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-serial" -version = "5.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1d5427f11ba7c5e6384521cfd76f2d64572ff29f3f4f7aa0f496282923fdc8" -dependencies = [ - "cfg-if", - "futures", - "log", - "mio-serial", - "serialport", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "ucobs" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fbab511bafd13c109aff41f67d4c4426bf919a6670647e69f0d2af95ac01d" - -[[package]] -name = "unescaper" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/mux/rust/Cargo.toml b/mux/rust/Cargo.toml deleted file mode 100644 index 2591586b..00000000 --- a/mux/rust/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "donglora-mux" -version = "0.1.1" -edition = "2024" -authors = ["Stephen Waits "] -license = "MIT" -description = "DongLoRa USB Multiplexer — share one dongle with multiple applications" -repository = "https://github.com/swaits/donglora" -keywords = ["lora", "radio", "usb", "multiplexer"] -categories = ["hardware-support"] - -[dependencies] -donglora-client = { version = "0.1.0", path = "../../clients/rust" } -anyhow = "1" -clap = { version = "4", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -tokio-serial = "5" -tokio-util = "0.7" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -fd-lock = "4" - -[lints.clippy] -unwrap_used = "deny" -expect_used = "deny" -panic = "deny" diff --git a/mux/rust/LICENSE b/mux/rust/LICENSE deleted file mode 100644 index a4ad6f14..00000000 --- a/mux/rust/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/mux/rust/README.md b/mux/rust/README.md deleted file mode 100644 index 013aadeb..00000000 --- a/mux/rust/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# DongLoRa Mux (Rust) - -USB multiplexer daemon — lets multiple applications share one DongLoRa -dongle simultaneously. - -## What It Does - -- Owns the USB serial connection exclusively -- Exposes a Unix domain socket (and optional TCP) speaking the same - COBS-framed protocol -- RxPacket frames broadcast to all connected clients -- StartRx/StopRx reference-counted across clients -- SetConfig locked once set (single client can change freely) -- Single-instance enforcement via file lock (stale sockets auto-cleaned) -- Automatic dongle reconnect on hot-plug -- No panics — enforced by clippy deny lints - -## Running - -```sh -just run # start the mux daemon -just verbose # start with verbose logging -just run -- --tcp 5741 --port /dev/ttyACM0 # with options -``` - -## Depends On - -- [clients/rust](../../clients/rust/) — the `donglora-client` library - (protocol types, COBS framing, device discovery) diff --git a/mux/rust/justfile b/mux/rust/justfile deleted file mode 100644 index bfd0025e..00000000 --- a/mux/rust/justfile +++ /dev/null @@ -1,19 +0,0 @@ -set shell := ["bash", "-c"] - -default: run - -# Run the mux daemon -run *args: - cargo run -- {{args}} - -# Run with verbose logging -verbose *args: - cargo run -- --verbose {{args}} - -# Run clippy and tests -check: - cargo clippy -- -D warnings && cargo test - -# Build release -build: - cargo build --release diff --git a/mux/rust/src/daemon.rs b/mux/rust/src/daemon.rs deleted file mode 100644 index 92001e63..00000000 --- a/mux/rust/src/daemon.rs +++ /dev/null @@ -1,651 +0,0 @@ -//! Core mux daemon: dongle I/O, client management, reconnect loop. -//! -//! The daemon owns the USB serial connection and exposes Unix domain socket -//! and optional TCP listeners. It routes commands from clients to the dongle -//! and broadcasts/routes responses back. - -use std::collections::HashMap; -use std::path::Path; -use std::sync::Arc; -use std::time::Duration; - -use donglora_client::{ - encode_frame, FrameReader, CMD_TAG_SET_CONFIG, CMD_TAG_START_RX, CMD_TAG_STOP_RX, - RADIO_CONFIG_SIZE, RESP_TAG_OK, RESP_TAG_RX_PACKET, -}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::{TcpListener, UnixListener}; -use tokio::sync::{mpsc, oneshot, Mutex}; - -/// A queued command: (client_id, raw_cmd_bytes). -type QueuedCmd = (u64, Vec); - -/// Shared command receiver, wrapped for use across reconnect cycles. -type SharedCmdRx = Arc>>; -use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, warn}; - -use crate::intercept::{self, MuxState}; -use crate::session::ClientSession; - -/// Command timeout waiting for dongle response. -const COMMAND_TIMEOUT: Duration = Duration::from_secs(10); - -/// Delay between dongle reconnect attempts. -const RECONNECT_DELAY: Duration = Duration::from_secs(2); - -/// Max consecutive dongle read errors before declaring disconnect. -const MAX_CONSECUTIVE_ERRORS: u32 = 3; - -/// Sentinel client ID for synthetic commands (e.g. auto StopRx). -const SYNTHETIC_CLIENT_ID: u64 = u64::MAX; - -/// Shared mux state, protected by async mutex. -type SharedState = Arc>; - -struct MuxInner { - sessions: HashMap, - mux_state: MuxState, -} - -/// The multiplexer daemon. -pub struct MuxDaemon { - serial_port: String, - socket_path: String, - tcp_addr: Option<(String, u16)>, - shutdown: CancellationToken, -} - -impl MuxDaemon { - pub fn new( - serial_port: String, - socket_path: String, - tcp_addr: Option<(String, u16)>, - shutdown: CancellationToken, - ) -> Self { - Self { - serial_port, - socket_path, - tcp_addr, - shutdown, - } - } - - /// Run the mux daemon. Returns when shutdown is signalled. - pub async fn run(&self) -> anyhow::Result<()> { - let state: SharedState = Arc::new(Mutex::new(MuxInner { - sessions: HashMap::new(), - mux_state: MuxState::new(), - })); - - // Command channel: client handlers → dongle writer - let (cmd_tx, cmd_rx) = mpsc::channel::<(u64, Vec)>(64); - let cmd_rx = Arc::new(Mutex::new(cmd_rx)); - - // Pending response slot: dongle writer installs, dongle reader resolves - let pending_response: Arc>>>> = - Arc::new(Mutex::new(None)); - - // Prepare socket directory - let sock_path = Path::new(&self.socket_path); - if let Some(parent) = sock_path.parent() { - tokio::fs::create_dir_all(parent).await.ok(); - } - // Acquire an exclusive lock to prevent multiple mux instances. - // The lock is held for the process lifetime and auto-released on exit (even SIGKILL). - let lock_path = format!("{}.lock", self.socket_path); - let lock_file = std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(false) - .open(&lock_path) - .map_err(|e| anyhow::anyhow!("failed to open lock file {lock_path}: {e}"))?; - let mut lock = fd_lock::RwLock::new(lock_file); - let _lock_guard = match lock.try_write() { - Ok(guard) => guard, - Err(_) => anyhow::bail!("another donglora-mux is already running (lock held on {lock_path})"), - }; - - if sock_path.exists() { - // Lock acquired but socket file exists — stale from a crashed instance. - info!("removing stale socket {}", self.socket_path); - tokio::fs::remove_file(sock_path).await.ok(); - } - - // Start Unix socket server - let unix_listener = UnixListener::bind(&self.socket_path) - .map_err(|e| anyhow::anyhow!("failed to bind Unix socket {}: {e}", self.socket_path))?; - info!("Unix socket listening on {}", self.socket_path); - - // Start TCP server (optional) - let tcp_listener = if let Some((ref host, port)) = self.tcp_addr { - let addr = format!("{host}:{port}"); - let listener = TcpListener::bind(&addr).await.map_err(|e| { - if e.kind() == std::io::ErrorKind::AddrInUse { - anyhow::anyhow!("another donglora-mux is already listening on TCP {addr}") - } else { - anyhow::anyhow!("failed to bind TCP {addr}: {e}") - } - })?; - info!("TCP listening on {addr}"); - Some(listener) - } else { - None - }; - - // Spawn client accept tasks - tokio::spawn(accept_unix_clients( - unix_listener, - state.clone(), - cmd_tx.clone(), - self.shutdown.clone(), - )); - if let Some(listener) = tcp_listener { - tokio::spawn(accept_tcp_clients( - listener, - state.clone(), - cmd_tx.clone(), - self.shutdown.clone(), - )); - } - - // Connect/reconnect loop - self.reconnect_loop(state, cmd_tx, cmd_rx, pending_response) - .await; - - // Cleanup - info!("shutting down..."); - if Path::new(&self.socket_path).exists() { - tokio::fs::remove_file(&self.socket_path).await.ok(); - } - info!("stopped."); - Ok(()) - } - - async fn reconnect_loop( - &self, - state: SharedState, - _cmd_tx: mpsc::Sender<(u64, Vec)>, // kept alive to prevent channel close - cmd_rx: SharedCmdRx, - pending_response: Arc>>>>, - ) { - loop { - if self.shutdown.is_cancelled() { - break; - } - - info!("opening dongle on {}", self.serial_port); - let serial = loop { - if self.shutdown.is_cancelled() { - return; - } - match open_and_ping(&self.serial_port).await { - Ok(s) => break s, - Err(e) => { - debug!("could not open dongle: {e}"); - tokio::select! { - () = self.shutdown.cancelled() => return, - () = tokio::time::sleep(RECONNECT_DELAY) => continue, - } - } - } - }; - - info!("dongle connected"); - let (serial_read, serial_write) = tokio::io::split(serial); - - let dongle_lost = CancellationToken::new(); - - let reader_handle = tokio::spawn(dongle_reader( - serial_read, - state.clone(), - pending_response.clone(), - dongle_lost.clone(), - self.shutdown.clone(), - )); - - let writer_handle = tokio::spawn(dongle_writer( - serial_write, - cmd_rx.clone(), - state.clone(), - pending_response.clone(), - dongle_lost.clone(), - self.shutdown.clone(), - )); - - // Wait for dongle loss or shutdown - tokio::select! { - () = self.shutdown.cancelled() => {}, - () = dongle_lost.cancelled() => {}, - } - - reader_handle.abort(); - writer_handle.abort(); - let _ = reader_handle.await; - let _ = writer_handle.await; - - // Drain pending commands - { - let mut rx = cmd_rx.lock().await; - while rx.try_recv().is_ok() {} - } - - if !self.shutdown.is_cancelled() { - info!("reconnecting in 2 seconds..."); - tokio::select! { - () = self.shutdown.cancelled() => return, - () = tokio::time::sleep(RECONNECT_DELAY) => {}, - } - } - } - } -} - -// ── Dongle I/O ───────────────────────────────────────────────────── - -async fn open_and_ping(port: &str) -> anyhow::Result { - let builder = tokio_serial::new(port, 115_200).timeout(Duration::from_secs(2)); - let mut serial = tokio_serial::SerialStream::open(&builder) - .map_err(|e| anyhow::anyhow!("failed to open {port}: {e}"))?; - - let ping_frame = encode_frame(&[0]); - AsyncWriteExt::write_all(&mut serial, &ping_frame) - .await - .map_err(|e| anyhow::anyhow!("failed to write ping: {e}"))?; - - let mut reader = FrameReader::new(); - let mut buf = [0u8; 64]; - let deadline = tokio::time::Instant::now() + Duration::from_secs(2); - - loop { - let remaining = deadline.saturating_duration_since(tokio::time::Instant::now()); - if remaining.is_zero() { - anyhow::bail!("ping timeout"); - } - - let n = tokio::select! { - result = serial.read(&mut buf) => { - result.map_err(|e| anyhow::anyhow!("serial read error: {e}"))? - } - () = tokio::time::sleep(remaining) => { - anyhow::bail!("ping timeout"); - } - }; - - if n == 0 { - anyhow::bail!("serial port closed during ping"); - } - - for frame in reader.feed(&buf[..n]) { - if frame.first() == Some(&0) { - info!("dongle responded to Ping"); - return Ok(serial); - } - } - } -} - -async fn dongle_reader( - mut serial: tokio::io::ReadHalf, - state: SharedState, - pending_response: Arc>>>>, - dongle_lost: CancellationToken, - shutdown: CancellationToken, -) { - let mut reader = FrameReader::new(); - let mut buf = [0u8; 512]; - let mut consecutive_errors: u32 = 0; - - loop { - if shutdown.is_cancelled() || dongle_lost.is_cancelled() { - return; - } - - let read_result = tokio::select! { - result = serial.read(&mut buf) => result, - () = shutdown.cancelled() => return, - () = dongle_lost.cancelled() => return, - }; - - match read_result { - Ok(0) => { - error!("serial port closed"); - resolve_pending_with_error(&pending_response).await; - dongle_lost.cancel(); - return; - } - Ok(n) => { - consecutive_errors = 0; - for frame in reader.feed(&buf[..n]) { - dispatch_frame(&frame, &state, &pending_response).await; - } - } - Err(e) => { - consecutive_errors += 1; - if consecutive_errors >= MAX_CONSECUTIVE_ERRORS { - error!("dongle read error (giving up after {consecutive_errors}): {e}"); - resolve_pending_with_error(&pending_response).await; - dongle_lost.cancel(); - return; - } - warn!("dongle read glitch ({consecutive_errors}/{MAX_CONSECUTIVE_ERRORS}): {e}"); - tokio::time::sleep(Duration::from_millis(100)).await; - } - } - } -} - -async fn dispatch_frame( - frame: &[u8], - state: &SharedState, - pending_response: &Arc>>>>, -) { - let Some(&tag) = frame.first() else { - return; - }; - - if tag == RESP_TAG_RX_PACKET { - let cobs_frame = encode_frame(frame); - let inner = state.lock().await; - for session in inner.sessions.values() { - session.enqueue(cobs_frame.clone()); - } - } else { - let mut slot = pending_response.lock().await; - if let Some(sender) = slot.take() { - let _ = sender.send(frame.to_vec()); - } else { - warn!("unsolicited non-RxPacket response (tag {tag}) — dropped"); - } - } -} - -async fn resolve_pending_with_error( - pending_response: &Arc>>>>, -) { - let mut slot = pending_response.lock().await; - if let Some(sender) = slot.take() { - let _ = sender.send(vec![5, 1]); // Error(RadioBusy) - } -} - -async fn dongle_writer( - mut serial: tokio::io::WriteHalf, - cmd_rx: SharedCmdRx, - state: SharedState, - pending_response: Arc>>>>, - dongle_lost: CancellationToken, - shutdown: CancellationToken, -) { - loop { - // Pull next command from queue - let (client_id, raw_cmd) = { - let mut rx = cmd_rx.lock().await; - tokio::select! { - cmd = rx.recv() => { - match cmd { - Some(c) => c, - None => return, - } - } - () = shutdown.cancelled() => return, - () = dongle_lost.cancelled() => return, - } - }; - - // Check for interception - let intercepted = { - let mut inner = state.lock().await; - let locked = inner.mux_state.locked_config; - intercept::maybe_intercept( - &raw_cmd, - client_id, - &mut inner.sessions, - &locked, - ) - }; - if let Some(synthetic) = intercepted { - let inner = state.lock().await; - if let Some(session) = inner.sessions.get(&client_id) { - session.enqueue(encode_frame(&synthetic)); - } - continue; - } - - // Install oneshot for the response - let (resp_tx, resp_rx) = oneshot::channel(); - { - let mut slot = pending_response.lock().await; - *slot = Some(resp_tx); - } - - // Send command to dongle - let cobs_frame = encode_frame(&raw_cmd); - if let Err(e) = AsyncWriteExt::write_all(&mut serial, &cobs_frame).await { - error!("dongle write error: {e}"); - resolve_pending_with_error(&pending_response).await; - dongle_lost.cancel(); - return; - } - - // Wait for response - let response = match tokio::time::timeout(COMMAND_TIMEOUT, resp_rx).await { - Ok(Ok(resp)) => resp, - Ok(Err(_)) => { - warn!("response channel dropped"); - vec![5, 1] - } - Err(_) => { - error!("dongle response timeout"); - let mut slot = pending_response.lock().await; - *slot = None; - vec![5, 1] - } - }; - - // Track state on successful forward - let cmd_tag = raw_cmd.first().copied().unwrap_or(0xFF); - let resp_tag = response.first().copied().unwrap_or(0xFF); - { - let mut inner = state.lock().await; - if cmd_tag == CMD_TAG_SET_CONFIG && resp_tag == RESP_TAG_OK { - if let Some(config_bytes) = raw_cmd.get(1..1 + RADIO_CONFIG_SIZE) { - let mut locked = [0u8; RADIO_CONFIG_SIZE]; - locked.copy_from_slice(config_bytes); - inner.mux_state.locked_config = Some(locked); - info!("radio config locked"); - } - } else if cmd_tag == CMD_TAG_START_RX && resp_tag == RESP_TAG_OK { - if let Some(session) = inner.sessions.get_mut(&client_id) { - session.rx_interested = true; - } - } else if cmd_tag == CMD_TAG_STOP_RX - && resp_tag == RESP_TAG_OK - && let Some(session) = inner.sessions.get_mut(&client_id) - { - session.rx_interested = false; - } - } - - // Route response to commanding client - if client_id != SYNTHETIC_CLIENT_ID { - let inner = state.lock().await; - if let Some(session) = inner.sessions.get(&client_id) { - session.enqueue(encode_frame(&response)); - } - } - } -} - -// ── Client management ────────────────────────────────────────────── - -async fn accept_unix_clients( - listener: UnixListener, - state: SharedState, - cmd_tx: mpsc::Sender<(u64, Vec)>, - shutdown: CancellationToken, -) { - loop { - let accept_result = tokio::select! { - result = listener.accept() => result, - () = shutdown.cancelled() => return, - }; - - match accept_result { - Ok((stream, _addr)) => { - let (read_half, write_half) = stream.into_split(); - spawn_client(read_half, write_half, state.clone(), cmd_tx.clone(), shutdown.clone()); - } - Err(e) => warn!("Unix accept error: {e}"), - } - } -} - -async fn accept_tcp_clients( - listener: TcpListener, - state: SharedState, - cmd_tx: mpsc::Sender<(u64, Vec)>, - shutdown: CancellationToken, -) { - loop { - let accept_result = tokio::select! { - result = listener.accept() => result, - () = shutdown.cancelled() => return, - }; - - match accept_result { - Ok((stream, addr)) => { - debug!("TCP connection from {addr}"); - // Low-latency: disable Nagle (COBS frames are small) - let _ = stream.set_nodelay(true); - let (read_half, write_half) = stream.into_split(); - spawn_client(read_half, write_half, state.clone(), cmd_tx.clone(), shutdown.clone()); - } - Err(e) => warn!("TCP accept error: {e}"), - } - } -} - -fn spawn_client( - read_half: R, - write_half: W, - state: SharedState, - cmd_tx: mpsc::Sender<(u64, Vec)>, - shutdown: CancellationToken, -) where - R: AsyncReadExt + Unpin + Send + 'static, - W: AsyncWriteExt + Unpin + Send + 'static, -{ - let (session, send_rx) = ClientSession::new(); - let client_id = session.id; - let label = session.label(); - - let state_clone = state.clone(); - let cmd_tx_clone = cmd_tx.clone(); - - tokio::spawn(async move { - { - let mut inner = state_clone.lock().await; - inner.sessions.insert(client_id, session); - info!("{label} connected ({} total)", inner.sessions.len()); - } - - let writer_shutdown = shutdown.clone(); - let writer_handle = tokio::spawn(client_writer(write_half, send_rx, writer_shutdown)); - - client_reader(read_half, client_id, cmd_tx_clone.clone(), shutdown).await; - - writer_handle.abort(); - let _ = writer_handle.await; - - remove_client(client_id, &state_clone, &label, &cmd_tx_clone).await; - }); -} - -async fn client_reader( - mut read_half: R, - client_id: u64, - cmd_tx: mpsc::Sender<(u64, Vec)>, - shutdown: CancellationToken, -) { - let mut reader = FrameReader::new(); - let mut buf = [0u8; 4096]; - - loop { - let read_result = tokio::select! { - result = read_half.read(&mut buf) => result, - () = shutdown.cancelled() => return, - }; - - match read_result { - Ok(0) => return, - Ok(n) => { - for frame in reader.feed(&buf[..n]) { - if cmd_tx.send((client_id, frame)).await.is_err() { - return; - } - } - } - Err(_) => return, - } - } -} - -async fn client_writer( - mut write_half: W, - mut send_rx: mpsc::Receiver>, - shutdown: CancellationToken, -) { - loop { - let frame = tokio::select! { - f = send_rx.recv() => { - match f { - Some(f) => f, - None => return, - } - } - () = shutdown.cancelled() => return, - }; - - if AsyncWriteExt::write_all(&mut write_half, &frame) - .await - .is_err() - { - return; - } - } -} - -async fn remove_client( - client_id: u64, - state: &SharedState, - label: &str, - cmd_tx: &mpsc::Sender<(u64, Vec)>, -) { - let mut inner = state.lock().await; - - let was_interested = inner - .sessions - .get(&client_id) - .is_some_and(|s| s.rx_interested); - - inner.sessions.remove(&client_id); - info!("{label} disconnected ({} remain)", inner.sessions.len()); - - // If last RX-interested client gone, send synthetic StopRx - if was_interested && intercept::rx_interest_count(&inner.sessions) == 0 { - info!("last RX-interested client gone — sending StopRx"); - // Drop the lock before sending to avoid deadlock - drop(inner); - let _ = cmd_tx - .send((SYNTHETIC_CLIENT_ID, vec![CMD_TAG_STOP_RX])) - .await; - return; - } - - // Reset locked config when all clients are gone - if inner.sessions.is_empty() && inner.mux_state.locked_config.is_some() { - info!("all clients gone — config lock released"); - inner.mux_state.locked_config = None; - } -} diff --git a/mux/rust/src/intercept.rs b/mux/rust/src/intercept.rs deleted file mode 100644 index 8e626433..00000000 --- a/mux/rust/src/intercept.rs +++ /dev/null @@ -1,291 +0,0 @@ -//! Smart multiplexing: SetConfig locking, StartRx/StopRx reference counting. -//! -//! The mux intercepts certain commands to avoid redundant dongle operations -//! when multiple clients are connected. Intercepted commands get synthetic -//! responses without hitting the physical dongle. - -use std::collections::HashMap; - -use donglora_client::{ - CMD_TAG_SET_CONFIG, CMD_TAG_START_RX, CMD_TAG_STOP_RX, ERROR_INVALID_CONFIG, RADIO_CONFIG_SIZE, - RESP_TAG_ERROR, RESP_TAG_OK, -}; -use tracing::{debug, warn}; - -use crate::session::ClientSession; - -/// Mux-level state for interception logic. -pub struct MuxState { - /// Raw 13-byte RadioConfig, locked once set by any client with others connected. - pub locked_config: Option<[u8; RADIO_CONFIG_SIZE]>, -} - -impl MuxState { - pub fn new() -> Self { - Self { - locked_config: None, - } - } -} - -/// Check if a command should be intercepted (synthetic response) vs forwarded to the dongle. -/// -/// Returns `Some(response_bytes)` if intercepted, `None` to forward normally. -pub fn maybe_intercept( - raw_cmd: &[u8], - client_id: u64, - sessions: &mut HashMap, - locked_config: &Option<[u8; RADIO_CONFIG_SIZE]>, -) -> Option> { - let cmd_tag = *raw_cmd.first()?; - - match cmd_tag { - CMD_TAG_SET_CONFIG => intercept_set_config(raw_cmd, client_id, sessions, locked_config), - CMD_TAG_START_RX => intercept_start_rx(client_id, sessions), - CMD_TAG_STOP_RX => intercept_stop_rx(client_id, sessions), - _ => None, - } -} - -fn intercept_set_config( - raw_cmd: &[u8], - client_id: u64, - sessions: &HashMap, - locked_config: &Option<[u8; RADIO_CONFIG_SIZE]>, -) -> Option> { - let config_bytes = raw_cmd.get(1..1 + RADIO_CONFIG_SIZE)?; - - // Single client — allow free config changes (scanner mode) - if sessions.len() <= 1 { - return None; - } - - // First SetConfig with multiple clients — forward and lock on success - let Some(locked) = locked_config else { - return None; - }; - - // Same config — reply Ok without hitting the dongle - if config_bytes == locked.as_slice() { - let label = sessions - .get(&client_id) - .map(ClientSession::label) - .unwrap_or_else(|| format!("id-{client_id}")); - debug!("{label}: SetConfig matches locked config — Ok"); - return Some(vec![RESP_TAG_OK]); - } - - // Different config — reject - let label = sessions - .get(&client_id) - .map(ClientSession::label) - .unwrap_or_else(|| format!("id-{client_id}")); - warn!("{label}: SetConfig rejected (conflicts with locked config)"); - Some(vec![RESP_TAG_ERROR, ERROR_INVALID_CONFIG]) -} - -fn intercept_start_rx( - client_id: u64, - sessions: &mut HashMap, -) -> Option> { - let client = sessions.get(&client_id)?; - - // Already interested — no-op - if client.rx_interested { - return Some(vec![RESP_TAG_OK]); - } - - // Others already receiving — just mark this client - if rx_interest_count(sessions) > 0 { - if let Some(c) = sessions.get_mut(&client_id) { - c.rx_interested = true; - } - return Some(vec![RESP_TAG_OK]); - } - - // First interested client — forward to dongle - None -} - -fn intercept_stop_rx( - client_id: u64, - sessions: &mut HashMap, -) -> Option> { - let client = sessions.get(&client_id)?; - - // Wasn't interested — no-op - if !client.rx_interested { - return Some(vec![RESP_TAG_OK]); - } - - // Mark as not interested - if let Some(c) = sessions.get_mut(&client_id) { - c.rx_interested = false; - } - - // Others still interested — don't stop the dongle - if rx_interest_count(sessions) > 0 { - return Some(vec![RESP_TAG_OK]); - } - - // Last interested client — forward StopRx to dongle - None -} - -/// Count how many sessions have rx_interested set. -pub fn rx_interest_count(sessions: &HashMap) -> usize { - sessions.values().filter(|s| s.rx_interested).count() -} - -// ── Tests ────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn make_sessions(n: usize) -> HashMap { - let mut map = HashMap::new(); - for _ in 0..n { - let (session, _rx) = ClientSession::new(); - let id = session.id; - map.insert(id, session); - } - map - } - - fn first_id(sessions: &HashMap) -> u64 { - *sessions.keys().min().unwrap() - } - - fn second_id(sessions: &HashMap) -> u64 { - let mut ids: Vec = sessions.keys().copied().collect(); - ids.sort(); - ids[1] - } - - // ── SetConfig ──────────────────────────────���─────────────────── - - #[test] - fn set_config_single_client_forwards() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - let cmd = [CMD_TAG_SET_CONFIG, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - assert!(maybe_intercept(&cmd, id, &mut sessions, &locked).is_none()); - } - - #[test] - fn set_config_first_with_multiple_forwards() { - let mut sessions = make_sessions(2); - let locked = None; - let id = first_id(&sessions); - let cmd = [CMD_TAG_SET_CONFIG, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; - assert!(maybe_intercept(&cmd, id, &mut sessions, &locked).is_none()); - } - - #[test] - fn set_config_matching_returns_ok() { - let mut sessions = make_sessions(2); - let config = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; - let locked = Some(config); - let id = first_id(&sessions); - let mut cmd = vec![CMD_TAG_SET_CONFIG]; - cmd.extend_from_slice(&config); - assert_eq!(maybe_intercept(&cmd, id, &mut sessions, &locked), Some(vec![RESP_TAG_OK])); - } - - #[test] - fn set_config_conflicting_returns_error() { - let mut sessions = make_sessions(2); - let locked = Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); - let id = first_id(&sessions); - let mut cmd = vec![CMD_TAG_SET_CONFIG]; - cmd.extend_from_slice(&[99, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); - assert_eq!( - maybe_intercept(&cmd, id, &mut sessions, &locked), - Some(vec![RESP_TAG_ERROR, ERROR_INVALID_CONFIG]) - ); - } - - // ── StartRx ──────────────────────────────────────────────────── - - #[test] - fn start_rx_first_client_forwards() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - assert!(maybe_intercept(&[CMD_TAG_START_RX], id, &mut sessions, &locked).is_none()); - } - - #[test] - fn start_rx_already_interested_returns_ok() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - sessions.get_mut(&id).unwrap().rx_interested = true; - assert_eq!( - maybe_intercept(&[CMD_TAG_START_RX], id, &mut sessions, &locked), - Some(vec![RESP_TAG_OK]) - ); - } - - #[test] - fn start_rx_others_interested_marks_and_returns_ok() { - let mut sessions = make_sessions(2); - let locked = None; - let id1 = first_id(&sessions); - let id2 = second_id(&sessions); - sessions.get_mut(&id1).unwrap().rx_interested = true; - let result = maybe_intercept(&[CMD_TAG_START_RX], id2, &mut sessions, &locked); - assert_eq!(result, Some(vec![RESP_TAG_OK])); - assert!(sessions.get(&id2).unwrap().rx_interested); - } - - // ── StopRx ───────────────────────────────────────────────────── - - #[test] - fn stop_rx_not_interested_returns_ok() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - assert_eq!( - maybe_intercept(&[CMD_TAG_STOP_RX], id, &mut sessions, &locked), - Some(vec![RESP_TAG_OK]) - ); - } - - #[test] - fn stop_rx_others_remain_returns_ok() { - let mut sessions = make_sessions(2); - let locked = None; - let id1 = first_id(&sessions); - let id2 = second_id(&sessions); - sessions.get_mut(&id1).unwrap().rx_interested = true; - sessions.get_mut(&id2).unwrap().rx_interested = true; - let result = maybe_intercept(&[CMD_TAG_STOP_RX], id1, &mut sessions, &locked); - assert_eq!(result, Some(vec![RESP_TAG_OK])); - assert!(!sessions.get(&id1).unwrap().rx_interested); - } - - #[test] - fn stop_rx_last_interested_forwards() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - sessions.get_mut(&id).unwrap().rx_interested = true; - assert!(maybe_intercept(&[CMD_TAG_STOP_RX], id, &mut sessions, &locked).is_none()); - assert!(!sessions.get(&id).unwrap().rx_interested); - } - - // ── Other commands ───────────────────────────────────────────── - - #[test] - fn other_commands_not_intercepted() { - let mut sessions = make_sessions(1); - let locked = None; - let id = first_id(&sessions); - for tag in [0u8, 1, 5, 6, 7, 8] { - assert!(maybe_intercept(&[tag], id, &mut sessions, &locked).is_none()); - } - } -} diff --git a/mux/rust/src/main.rs b/mux/rust/src/main.rs deleted file mode 100644 index dfcf0e59..00000000 --- a/mux/rust/src/main.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! DongLoRa USB Multiplexer — share one dongle with multiple applications. -//! -//! Owns the USB serial connection and exposes a Unix domain socket (and -//! optional TCP) that speaks the same COBS-framed protocol. Clients connect -//! and get solicited responses routed, RxPackets broadcast, and StartRx/StopRx -//! reference-counted. - -mod daemon; -mod intercept; -mod session; - -use clap::Parser; -use tokio_util::sync::CancellationToken; -use tracing::info; - -#[derive(Parser)] -#[command(name = "donglora-mux", about = "DongLoRa USB Multiplexer")] -struct Args { - /// Serial port (auto-detect by USB VID:PID if omitted) - #[arg(short, long)] - port: Option, - - /// Unix socket path (default: XDG or /tmp/donglora-mux.sock) - #[arg(short, long)] - socket: Option, - - /// TCP listen address [HOST:]PORT (default: 0.0.0.0:5741, "none" to disable) - #[arg(short, long, default_value = "0.0.0.0:5741")] - tcp: String, - - /// Enable debug logging - #[arg(short, long)] - verbose: bool, -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let args = Args::parse(); - - // Init tracing — daemon-style: wall-clock time, level, message - let filter = if args.verbose { "debug" } else { "info" }; - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter)), - ) - .with_target(false) - .init(); - - // Resolve serial port - let port = match args.port { - Some(p) => p, - None => tokio::task::spawn_blocking(|| { - donglora_client::find_port().unwrap_or_else(|| { - info!("waiting for DongLoRa device..."); - donglora_client::wait_for_device() - }) - }) - .await - .map_err(|e| anyhow::anyhow!("device discovery task failed: {e}"))?, - }; - - // Resolve socket path - let socket_path = args - .socket - .unwrap_or_else(donglora_client::default_socket_path); - - // Parse TCP address - let tcp_addr = parse_tcp_addr(&args.tcp); - - // Shutdown token - let shutdown = CancellationToken::new(); - - // Install signal handlers - let sig_shutdown = shutdown.clone(); - tokio::spawn(async move { - if let Err(e) = signal_handler(sig_shutdown).await { - tracing::error!("signal handler error: {e}"); - } - }); - - // Run the daemon - let daemon = daemon::MuxDaemon::new(port, socket_path, tcp_addr, shutdown); - daemon.run().await -} - -fn parse_tcp_addr(tcp: &str) -> Option<(String, u16)> { - if tcp.eq_ignore_ascii_case("none") { - return None; - } - if let Some((host, port_str)) = tcp.rsplit_once(':') - && let Ok(port) = port_str.parse() - { - return Some((host.to_string(), port)); - } - if let Ok(port) = tcp.parse() { - return Some(("0.0.0.0".to_string(), port)); - } - None -} - -async fn signal_handler(shutdown: CancellationToken) -> anyhow::Result<()> { - let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()) - .map_err(|e| anyhow::anyhow!("failed to register SIGINT handler: {e}"))?; - let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) - .map_err(|e| anyhow::anyhow!("failed to register SIGTERM handler: {e}"))?; - - tokio::select! { - _ = sigint.recv() => info!("received SIGINT"), - _ = sigterm.recv() => info!("received SIGTERM"), - } - - shutdown.cancel(); - Ok(()) -} diff --git a/mux/rust/src/session.rs b/mux/rust/src/session.rs deleted file mode 100644 index 55a1ce5a..00000000 --- a/mux/rust/src/session.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Per-client session state and bounded send queue. -//! -//! Each connected client gets a [`ClientSession`] that tracks its ID, -//! RX interest flag, and a bounded channel for outgoing COBS frames. - -use std::sync::atomic::{AtomicU64, Ordering}; - -use tokio::sync::mpsc; -use tracing::warn; - -/// Bounded send queue capacity per client. -const SEND_QUEUE_CAP: usize = 256; - -/// Global client ID counter. -static NEXT_CLIENT_ID: AtomicU64 = AtomicU64::new(0); - -/// Per-client state tracked by the mux daemon. -pub struct ClientSession { - /// Unique client identifier. - pub id: u64, - /// Whether this client has called StartRx. - pub rx_interested: bool, - /// Sender half of the bounded channel to the client's writer task. - tx: mpsc::Sender>, -} - -impl ClientSession { - /// Create a new session. Returns the session and the receiver half - /// for the client's writer task to drain. - pub fn new() -> (Self, mpsc::Receiver>) { - let id = NEXT_CLIENT_ID.fetch_add(1, Ordering::Relaxed); - let (tx, rx) = mpsc::channel(SEND_QUEUE_CAP); - let session = Self { - id, - rx_interested: false, - tx, - }; - (session, rx) - } - - /// Human-readable label for logging. - pub fn label(&self) -> String { - format!("client-{}", self.id) - } - - /// Best-effort enqueue of a COBS frame for sending to this client. - /// - /// If the channel is full (slow client), the frame is dropped with a warning. - /// This prevents backpressure from one slow client blocking the entire mux. - pub fn enqueue(&self, frame: Vec) { - if let Err(mpsc::error::TrySendError::Full(_)) = self.tx.try_send(frame) { - warn!("{}: send queue full, dropping frame", self.label()); - } - // Closed channel (client disconnected) is silently ignored — the session - // will be cleaned up when the client handler task exits. - } -}