diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..a3625e6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,35 @@ +name: Bug report +description: Report reproducible incorrect behavior. +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: Please avoid including secrets or private information. + - type: input + id: version + attributes: + label: Library version + placeholder: 1.0.0 + validations: + required: true + - type: input + id: board + attributes: + label: Board and core version + placeholder: Arduino Uno, Arduino AVR Boards 1.8.6 + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Minimal sketch and reproduction steps + render: cpp + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected and actual behavior + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4bbbffe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security report + url: https://github.com/devkyato/Custom-Arduino-Libraries/security/advisories/new + about: Report vulnerabilities privately. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..06989e6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,30 @@ +name: Feature request +description: Propose a focused, reusable improvement. +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What project or teaching problem should this solve? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed API or behavior + validations: + required: true + - type: dropdown + id: compatibility + attributes: + label: Required boards + multiple: true + options: + - AVR / Uno + - ESP32 + - RP2040 / Pico + - Other + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c0415b7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Summary + + + +## Verification + +- [ ] Native tests pass (`pio test -e native`) +- [ ] Relevant board environments compile +- [ ] Documentation or examples are updated +- [ ] `CHANGELOG.md` is updated for user-visible changes + +## Hardware + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..478231b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + actions: + patterns: ["*"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9ad3bb3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + native-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - run: python -m pip install "platformio==6.1.18" + - run: pio test -e native + + compile: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + environment: [uno, esp32dev, pico] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - run: python -m pip install "platformio==6.1.18" + - run: pio run -e ${{ matrix.environment }} + + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - run: python tools/build_release.py + - uses: actions/upload-artifact@v6 + with: + name: ArduinoPatterns + path: dist/ArduinoPatterns-*.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2c2a399 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - name: Verify tag matches library version + shell: bash + run: | + version="$(sed -n 's/^version=//p' library.properties)" + test "${GITHUB_REF_NAME}" = "v${version}" + - run: python tools/build_release.py + - name: Publish GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "$GITHUB_REF_NAME" dist/*.zip --generate-notes --verify-tag diff --git a/.gitignore b/.gitignore index d4fb281..bff5b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,14 @@ # debug information files *.dwo + +# PlatformIO and editor state +.pio/ +.vscode/ + +# Python tooling +__pycache__/ +*.py[cod] + +# Release artifacts +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..43fd3ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to ArduinoPatterns are documented here. The project follows +[Semantic Versioning](https://semver.org/). + +## [1.0.0] - 2026-07-29 + +### Added + +- `LedBank` for controlling up to 32 active-high or active-low outputs as a mask. +- `LedAnimator` for rollover-safe, non-blocking timed sequences. +- `PeriodicOutput` for independent periodic digital outputs. +- Six Arduino IDE examples adapted from the repository's original exercises. +- Native timing tests and compile checks for Uno, ESP32, and Raspberry Pi Pico. +- Arduino IDE ZIP and PlatformIO installation metadata. + +[1.0.0]: https://github.com/devkyato/Custom-Arduino-Libraries/releases/tag/v1.0.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9fcdb4d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,27 @@ +# Code of Conduct + +## Our commitment + +We are committed to a welcoming, harassment-free learning environment regardless +of experience level, background, identity, or ability. + +## Expected behavior + +- Be respectful, patient, and constructive. +- Explain technical feedback without belittling people. +- Welcome beginner questions and acknowledge honest mistakes. +- Keep discussions relevant to the project. + +Harassment, personal attacks, discriminatory language, and publishing another +person's private information are not acceptable. + +## Enforcement + +Report unacceptable behavior privately to the repository maintainer through the +contact method on their GitHub profile. Reports will be reviewed fairly and kept +confidential where possible. Maintainers may edit or remove content, reject +contributions, or restrict participation when needed. + +This policy is adapted from the [Contributor Covenant 2.1][covenant]. + +[covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8006a27 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +ArduinoPatterns is intended to be approachable for students and first-time +open-source contributors. + +## Before you start + +1. Search existing issues before opening a new one. +2. For behavior changes or new public APIs, open an issue first. +3. Keep the library non-blocking and avoid heap allocation. + +## Local checks + +Install [PlatformIO Core](https://docs.platformio.org/en/latest/core/) and run: + +```sh +pio test -e native +pio run -e uno +pio run -e esp32dev +pio run -e pico +python tools/build_release.py +``` + +Changes to timing logic need a native regression test. New features should include +an Arduino example or update an existing one. Hardware-dependent changes should +state the board and wiring used for verification. + +## Pull requests + +- Keep each pull request focused on one change. +- Use clear commit messages and explain the user-visible result. +- Update `CHANGELOG.md` for user-facing changes. +- Do not commit `.pio`, editor state, or generated `dist` files. + +By participating, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/Exercise_A.ino b/Exercise_A.ino deleted file mode 100644 index 2b089fe..0000000 --- a/Exercise_A.ino +++ /dev/null @@ -1,122 +0,0 @@ -// dito natin dine-declare yung variables ng bawat led -// bawat variable ay naka assign sa pin number ng arduino -// ibig sabihin kung saan naka connect yung led sa board -int led_1 = 6; -int led_2 = 7; -int led_3 = 8; -int led_4 = 9; -int led_5 = 10; -int led_6 = 11; - -// delay variables para mas madaling baguhin ang timing ng lights -// milliseconds ang unit ng delay -// halimbawa 1000 = 1 second -int led_delay1 = 500; -int led_delay2 = 1000; -int led_delay3 = 1500; - -void setup() { - - // ang setup() function ay tumatakbo lang isang beses kapag nag start ang arduino - // dito natin sine-set ang bawat pin bilang output - // kailangan ito para makapag send ang arduino ng signal sa leds - - pinMode(6, OUTPUT); - pinMode(7, OUTPUT); - pinMode(8, OUTPUT); - pinMode(9, OUTPUT); - pinMode(10, OUTPUT); - pinMode(11, OUTPUT); -} - -void loop() { - - // ang loop() function ay paulit ulit tumatakbo habang naka power ang arduino - // dito nangyayari ang iba't ibang led patterns - - // pattern kung saan ang led 1,3,5 ay naka on - // habang ang led 2,4,6 ay naka off - - digitalWrite(led_1, HIGH); - digitalWrite(led_2, LOW); - digitalWrite(led_3, HIGH); - digitalWrite(led_4, LOW); - digitalWrite(led_5, HIGH); - digitalWrite(led_6, LOW); - - // maghihintay muna ng kalahating segundo bago mag change ng pattern - delay(500); - - // reverse naman ang pattern - // ngayon ang led 2,4,6 ang naka on - - digitalWrite(led_1, LOW); - digitalWrite(led_2, HIGH); - digitalWrite(led_3, LOW); - digitalWrite(led_4, HIGH); - digitalWrite(led_5, LOW); - digitalWrite(led_6, HIGH); - - // hintay ng isang segundo bago next sequence - delay(1000); - - // bagong pattern kung saan unang tatlong leds ang naka on - - digitalWrite(led_1, HIGH); - digitalWrite(led_2, HIGH); - digitalWrite(led_3, HIGH); - - // habang ang huling tatlo ay naka off - - digitalWrite(led_4, LOW); - digitalWrite(led_5, LOW); - digitalWrite(led_6, LOW); - - delay(1000); - - // reverse ulit ng pattern - - digitalWrite(led_1, LOW); - digitalWrite(led_2, LOW); - digitalWrite(led_3, LOW); - - digitalWrite(led_4, HIGH); - digitalWrite(led_5, HIGH); - digitalWrite(led_6, HIGH); - - delay(1000); - - // pattern kung saan ang led 1 at led 6 lang ang naka on - - digitalWrite(led_1, HIGH); - digitalWrite(led_2, LOW); - digitalWrite(led_3, LOW); - digitalWrite(led_4, LOW); - digitalWrite(led_5, LOW); - digitalWrite(led_6, HIGH); - - // mas mahaba ang delay dito kaya 1.5 seconds - delay(1500); - - // bagong combination ng leds - - digitalWrite(led_1, LOW); - digitalWrite(led_2, HIGH); - digitalWrite(led_3, LOW); - digitalWrite(led_4, LOW); - digitalWrite(led_5, HIGH); - digitalWrite(led_6, LOW); - - delay(1500); - - // huling combination ng leds sa sequence - - digitalWrite(led_1, LOW); - digitalWrite(led_2, LOW); - digitalWrite(led_3, HIGH); - digitalWrite(led_4, HIGH); - digitalWrite(led_5, LOW); - digitalWrite(led_6, LOW); - - delay(1500); -} diff --git a/Exercise_B.ino b/Exercise_B.ino deleted file mode 100644 index de9123c..0000000 --- a/Exercise_B.ino +++ /dev/null @@ -1,55 +0,0 @@ -// mga variable para sa tatlong leds -// ginagaya nito ang kulay ng traffic light -// led_1 = green, led_2 = yellow, led_3 = red - -int led_1 = 6; -int led_2 = 7; -int led_3 = 8; - -// delay values para sa bawat phase ng traffic light -// 5000 ms = 5 seconds -// 2000 ms = 2 seconds - -int led_delay1 = 5000; -int led_delay2 = 2000; -int led_delay3 = 5000; - -void setup() { - - // dito sine-set ang bawat pin bilang output - // para makontrol ng arduino ang pag on at off ng leds - - pinMode(6, OUTPUT); - pinMode(7, OUTPUT); - pinMode(8, OUTPUT); -} - -void loop() { - - // green light phase - // ibig sabihin pwede dumaan ang sasakyan - - digitalWrite(led_1, HIGH); - digitalWrite(led_2, LOW); - digitalWrite(led_3, LOW); - - delay(5000); - - // yellow light phase - // warning ito na malapit na mag red light - - digitalWrite(led_1, LOW); - digitalWrite(led_2, HIGH); - digitalWrite(led_3, LOW); - - delay(2000); - - // red light phase - // ibig sabihin stop ang traffic - - digitalWrite(led_1, LOW); - digitalWrite(led_2, LOW); - digitalWrite(led_3, HIGH); - - delay(5000); -} diff --git a/Exercise_C.ino b/Exercise_C.ino deleted file mode 100644 index 40c88db..0000000 --- a/Exercise_C.ino +++ /dev/null @@ -1,71 +0,0 @@ -// apat na leds na gagamitin para sa knight rider effect -// parang scanning light na gumagalaw mula kaliwa papunta kanan tapos babalik - -int led_1 = 6; -int led_2 = 7; -int led_3 = 8; -int led_4 = 9; - -void setup() { - - // dito sine-set ang bawat led pin bilang output - // kailangan ito para makapag control ang arduino kung on o off ang leds - - pinMode (led_1,OUTPUT); - pinMode (led_2,OUTPUT); - pinMode (led_3,OUTPUT); - pinMode (led_4,OUTPUT); -} - -void loop() { - - // unang led naka on, yung iba naka off - // ito ang simula ng movement ng light - - digitalWrite (led_1, HIGH); - digitalWrite (led_2, LOW); - digitalWrite (led_3, LOW); - digitalWrite (led_4, LOW); - - // delay para makita ang movement - delay(1000); - - // lilipat ang ilaw sa led 2 - - digitalWrite (led_1, LOW); - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - digitalWrite (led_4, LOW); - - delay(1000); - - // lilipat naman sa led 3 - - digitalWrite (led_3, HIGH); - digitalWrite (led_1, LOW); - digitalWrite (led_2, LOW); - digitalWrite (led_4, LOW); - - delay(1000); - - // huling led sa kanan - - digitalWrite (led_4, HIGH); - digitalWrite (led_1, LOW); - digitalWrite (led_2, LOW); - digitalWrite (led_3, LOW); - - delay(1000); - - // babalik pabalik ang ilaw - - digitalWrite (led_3, HIGH); - digitalWrite (led_4, LOW); - - delay(1000); - - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - - delay(1000); -} diff --git a/Exercise_D.ino b/Exercise_D.ino deleted file mode 100644 index 358b58d..0000000 --- a/Exercise_D.ino +++ /dev/null @@ -1,59 +0,0 @@ -// gumagamit ito ng array para sa mga led pins -// mas madali ito kapag maraming leds ang gagamitin - -int ledPins[] = {6, 7, 8, 9, 10, 11}; - -// kinukuha dito kung ilang leds ang nasa array -// ginagamit ito sa loops para hindi mano-manong ilagay ang bilang - -int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); - -// delay value sa bawat pagbabago ng led -int delayTime = 500; - -void setup() { - - // ginagamit ang for loop para i-set lahat ng leds bilang output - // mas efficient ito kaysa isa-isang pinMode - - for (int i = 0; i < numLeds; i++) { - pinMode(ledPins[i], OUTPUT); - } -} - -void loop() { - - // tatawagin ang dalawang functions - // una ang count up at pagkatapos ang count down - - countUp(); - countDown(); -} - -void countUp() { - - // sa count up phase - // isa-isang i-o-on ang leds mula led1 hanggang led6 - - for (int i = 0; i < numLeds; i++) { - - digitalWrite(ledPins[i], HIGH); - - // delay para makita ang step by step na pag-on ng leds - delay(delayTime); - } -} - -void countDown() { - - // sa count down phase - // isa-isang i-o-off ang leds mula sa huli pabalik sa una - - for (int i = numLeds - 1; i >= 0; i--) { - - digitalWrite(ledPins[i], LOW); - - // delay ulit para makita ang pagbaba ng count - delay(delayTime); - } -} diff --git a/Exercise_E.ino b/Exercise_E.ino deleted file mode 100644 index 99deab1..0000000 --- a/Exercise_E.ino +++ /dev/null @@ -1,143 +0,0 @@ -// exercise e -// binary counter gamit ang 4 leds -// bawat led ay kumakatawan sa isang binary bit - -// dito dine-define kung anong pin naka connect ang bawat led -int led_1 = 6; -int led_2 = 7; -int led_3 = 8; -int led_4 = 9; - -void setup() { - - // sine-set ang bawat pin bilang output - // kailangan ito para makapag send ang arduino ng signal sa leds - - pinMode (led_1,OUTPUT); - pinMode (led_2,OUTPUT); - pinMode (led_3,OUTPUT); - pinMode (led_4,OUTPUT); -} - -void loop() { - - // binary 0001 - // led4 lang ang naka on - digitalWrite (led_1, LOW); //1 - digitalWrite (led_2, LOW); - digitalWrite (led_3, LOW); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 0010 - // led3 naman ang naka on - digitalWrite (led_1, LOW); //2 - digitalWrite (led_2, LOW); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 0011 - // led3 at led4 naka on - digitalWrite (led_1, LOW); //3 - digitalWrite (led_2, LOW); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 0100 - // led2 lang ang naka on - digitalWrite (led_1, LOW); //4 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 0101 - // led2 at led4 naka on - digitalWrite (led_1, LOW); //5 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 0110 - // led2 at led3 naka on - digitalWrite (led_1, LOW); //6 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 0111 - // led2, led3, led4 naka on - digitalWrite (led_1, LOW); //7 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 1000 - // led1 lang ang naka on - digitalWrite (led_1, HIGH); //8 - digitalWrite (led_2, LOW); - digitalWrite (led_3, LOW); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 1001 - // led1 at led4 naka on - digitalWrite (led_1, HIGH); //9 - digitalWrite (led_2, LOW); - digitalWrite (led_3, LOW); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 1010 - // led1 at led3 naka on - digitalWrite (led_1, HIGH); //10 - digitalWrite (led_2, LOW); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 1011 - // led1, led3, led4 naka on - digitalWrite (led_1, HIGH); //11 - digitalWrite (led_2, LOW); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 1100 - // led1 at led2 naka on - digitalWrite (led_1, HIGH); //12 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 1101 - // led1, led2, led4 naka on - digitalWrite (led_1, HIGH); //13 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, LOW); - digitalWrite (led_4, HIGH); - delay(1000); - - // binary 1110 - // led1, led2, led3 naka on - digitalWrite (led_1, HIGH); //14 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, LOW); - delay(1000); - - // binary 1111 - // lahat ng leds naka on - digitalWrite (led_1, HIGH); //15 - digitalWrite (led_2, HIGH); - digitalWrite (led_3, HIGH); - digitalWrite (led_4, HIGH); - delay(1000); -} diff --git a/Exercise_F.ino b/Exercise_F.ino deleted file mode 100644 index 54a385c..0000000 --- a/Exercise_F.ino +++ /dev/null @@ -1,45 +0,0 @@ -// dalawang leds na magbi-blink -// magkaiba ang timing kaya parang alternating blink effect - -int led1 = 9; -int led2 = 10; - -void setup() { - - // sine-set ang pins bilang output para makapag control ng leds - - pinMode(led1, OUTPUT); - pinMode(led2, OUTPUT); -} - -void loop() { - - // parehong off ang leds sa simula - - digitalWrite(led1, LOW); - digitalWrite(led2, LOW); - - // maghihintay ng kalahating segundo - delay(500); - - // led2 lang ang naka on - - digitalWrite(led1, LOW); - digitalWrite(led2, HIGH); - - delay(500); - - // led1 naman ang naka on habang led2 naka off - - digitalWrite(led1, HIGH); - digitalWrite(led2, LOW); - - delay(500); - - // parehong leds naka on - - digitalWrite(led1, HIGH); - digitalWrite(led2, HIGH); - - delay(500); -} diff --git a/README.md b/README.md index 971524e..34c919d 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,78 @@ -# Supplementary Exercise 1 – Arduino LED Exercises +# ArduinoPatterns -This repository contains our solutions for **Supplementary Exercise No. 1 – Introduction to Microcontroller (Arduino) & Digital Output**. -The exercises focus on controlling LEDs using an Arduino and practicing basic programming logic such as sequences, delays, counting, and patterns. +[![CI](https://github.com/devkyato/Custom-Arduino-Libraries/actions/workflows/ci.yml/badge.svg)](https://github.com/devkyato/Custom-Arduino-Libraries/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -Each `.ino` file corresponds to a specific exercise given in the activity instructions. The programs demonstrate different LED behaviors like alternating lights, traffic light simulation, chasing LEDs, counting patterns, binary counting, and blinking with different timings. +ArduinoPatterns is a small teaching library for coordinating LEDs and other digital +outputs without `delay()`. It turns the original laboratory sketches in this +repository into reusable components that can run alongside buttons, sensors, +displays, networking, and serial communication. -## Exercises Included +## Features -### Exercise A – Alternating Lights -Simulates alternating LED patterns using six LEDs. -Different combinations of LEDs turn on with different delay timings. +- `LedBank`: treat up to 32 output pins as a bit mask. +- `LedAnimator`: play timed mask sequences using rollover-safe `millis()` logic. +- `PeriodicOutput`: blink independent outputs at different rates. +- No heap allocation, third-party dependencies, interrupts, or board-specific API. +- Arduino IDE and PlatformIO examples for traffic lights, scanning, counting, + alternating patterns, and independent blinkers. -**Done by:** Maurene +## Install ---- +Download the latest release ZIP and choose **Sketch > Include Library > Add .ZIP +Library** in Arduino IDE. In PlatformIO: -### Exercise B – Traffic Light Simulation -A simple traffic light system using three LEDs: -- Green → Yellow → Red -Each light stays on for a specific duration to mimic real traffic lights. +```ini +lib_deps = + https://github.com/devkyato/Custom-Arduino-Libraries.git#v1.0.0 +``` -**Done by:** Maurene +## Quick example ---- +```cpp +#include -### Exercise C – Knight Rider (Chasing LEDs) -Creates a "Knight Rider" style LED effect where lights turn on one by one from left to right, then reverse back. +const uint8_t pins[] = {6, 7, 8}; +const PatternStep traffic[] = { + {0b001, 5000}, // green + {0b010, 2000}, // yellow + {0b100, 5000}, // red +}; -**Done by:** Mayellah +LedBank lights(pins, 3); +LedAnimator animation(lights, traffic, 3); ---- +void setup() { + lights.begin(); + animation.start(millis()); +} -### Exercise D – Count Up and Down LEDs -Uses six LEDs to simulate counting: -- First counts **up** by turning LEDs on one by one -- Then counts **down** by turning them off one by one +void loop() { + animation.update(millis()); + // Other application work can run here. +} +``` -**Done by:** Marquez +Open **File > Examples > ArduinoPatterns** for complete sketches. The examples map +the original student work into reusable patterns; the original commits remain in +the repository history. ---- +See the [API reference](docs/API.md) for lifecycle, ownership, rollover, and +active-low behavior. -### Exercise E – LED Binary Counter -Uses four LEDs to represent binary numbers from **0 to 15**. -Each LED represents a binary bit that turns ON or OFF depending on the number. +## Compatibility -**Done by:** Mayellah +The public API uses only `pinMode`, `digitalWrite`, and unsigned millisecond +arithmetic. CI compiles for Arduino AVR Uno, ESP32, and RP2040. Other Arduino +architectures are expected to work but should be reported as verified only after an +actual compile or hardware test. ---- +## Contributing -### Exercise F – Double Blink with Different Timings -Two LEDs blink at different speeds: -- LED1 blinks every **1 second** -- LED2 blinks every **0.5 seconds** +Student-friendly issues are labeled `good first issue`. Changes should remain +non-blocking, avoid dynamic allocation, include native tests for timing logic, and +compile at least the Uno example. See [CONTRIBUTING.md](CONTRIBUTING.md). -**Done by:** Marquez +## License ---- - -## Materials Used -- Arduino Microcontroller -- Data Cable -- Computer -- Connecting Wires -- LEDs - ---- - -## Group Members - -**Group 4** - -- Maurene – Exercise A & B -- Mayellah – Exercise C & E -- Marquez – Exercise D & F - ---- - -## Notes -These exercises were created as part of a laboratory activity for learning the basics of **Arduino programming and digital output control**. +[MIT](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3f7e9f5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security policy + +## Supported versions + +Security fixes are provided for the latest release. + +## Reporting a vulnerability + +Please do not open a public issue for a suspected vulnerability. Use GitHub's +private vulnerability reporting feature on the repository's **Security** tab. +Include the affected version, board, a minimal reproduction, and the potential +impact. You should receive an initial response within seven days. + +This library directly controls GPIO and does not process network input, but +memory-safety, unexpected pin-state, and denial-of-service findings are still +welcome. diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..293eee9 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,70 @@ +# API reference + +Include the library with: + +```cpp +#include +``` + +## `PatternStep` + +```cpp +struct PatternStep { + uint32_t mask; + uint32_t durationMs; +}; +``` + +Bit zero in `mask` controls the first pin in a `LedBank`. `durationMs` is the +time before advancing to the next step. A zero-duration step advances at most +once per `update()` call, preventing an infinite loop. + +## `LedBank` + +```cpp +LedBank(const uint8_t* pins, size_t count, bool activeHigh = true); +``` + +The caller owns the pin array and must keep it alive for as long as the bank is +used. A bank supports 1–32 pins. A larger count or null pin array creates an +invalid bank. + +- `begin()` configures pins as outputs and clears them. +- `writeMask(mask)` writes the lowest `count` bits. +- `clear()` turns every logical output off. +- `size()`, `mask()`, and `valid()` inspect current state. + +With `activeHigh = false`, a logical on state writes `LOW`, which is useful for +active-low relay and LED modules. + +## `LedAnimator` + +```cpp +LedAnimator(LedBank& bank, const PatternStep* steps, size_t count, + bool repeat = true); +``` + +- `start(now)` selects step zero and returns whether the configuration is valid. +- `update(now)` catches up all elapsed non-zero-duration steps without blocking. +- `stop(clearOutputs)` stops playback and optionally clears the bank. +- `setRepeat(repeat)` changes end-of-sequence behavior. +- `running()` and `currentStep()` expose playback state. + +Pass the current `millis()` value to `start()` and `update()`. Unsigned +subtraction makes elapsed-time checks safe across the `millis()` rollover. + +## `PeriodicOutput` + +```cpp +PeriodicOutput(uint8_t pin, uint32_t intervalMs, + bool activeHigh = true, bool initialState = false); +``` + +- `begin(now)` configures and writes the initial state. +- `update(now)` processes elapsed intervals and reports whether any interval + elapsed. When an even number was skipped, the final logical state is unchanged. +- `setInterval(intervalMs, now)` updates the period and restarts its timer. +- `set(active, now)` writes a logical state and restarts its timer. +- `active()` and `interval()` return the current configuration. + +An interval of zero disables automatic toggling. diff --git a/examples/AlternatingPatterns/AlternatingPatterns.ino b/examples/AlternatingPatterns/AlternatingPatterns.ino new file mode 100644 index 0000000..73152bf --- /dev/null +++ b/examples/AlternatingPatterns/AlternatingPatterns.ino @@ -0,0 +1,22 @@ +#include + +const uint8_t pins[] = {6, 7, 8, 9, 10, 11}; +const PatternStep steps[] = { + {0b010101, 500}, + {0b101010, 1000}, + {0b000111, 1000}, + {0b111000, 1000}, + {0b100001, 1500}, + {0b010010, 1500}, + {0b001100, 1500}, +}; + +LedBank lights(pins, sizeof(pins)); +LedAnimator animation(lights, steps, sizeof(steps) / sizeof(steps[0])); + +void setup() { + lights.begin(); + animation.start(millis()); +} + +void loop() { animation.update(millis()); } diff --git a/examples/BinaryCounter/BinaryCounter.ino b/examples/BinaryCounter/BinaryCounter.ino new file mode 100644 index 0000000..fe87b89 --- /dev/null +++ b/examples/BinaryCounter/BinaryCounter.ino @@ -0,0 +1,21 @@ +#include + +const uint8_t pins[] = {9, 8, 7, 6}; // least-significant bit first +LedBank bits(pins, sizeof(pins)); + +uint8_t value = 0; +uint32_t changedAt = 0; + +void setup() { + bits.begin(); + bits.writeMask(value); +} + +void loop() { + const uint32_t now = millis(); + if (static_cast(now - changedAt) >= 1000) { + changedAt += 1000; + value = (value + 1) & 0x0F; + bits.writeMask(value); + } +} diff --git a/examples/CountUpDown/CountUpDown.ino b/examples/CountUpDown/CountUpDown.ino new file mode 100644 index 0000000..a988b89 --- /dev/null +++ b/examples/CountUpDown/CountUpDown.ino @@ -0,0 +1,28 @@ +#include + +const uint8_t pins[] = {6, 7, 8, 9, 10, 11}; +const PatternStep countSteps[] = { + {0b000001, 300}, + {0b000011, 300}, + {0b000111, 300}, + {0b001111, 300}, + {0b011111, 300}, + {0b111111, 300}, + {0b011111, 300}, + {0b001111, 300}, + {0b000111, 300}, + {0b000011, 300}, + {0b000001, 300}, + {0b000000, 300}, +}; + +LedBank lights(pins, sizeof(pins)); +LedAnimator counter(lights, countSteps, + sizeof(countSteps) / sizeof(countSteps[0])); + +void setup() { + lights.begin(); + counter.start(millis()); +} + +void loop() { counter.update(millis()); } diff --git a/examples/IndependentBlink/IndependentBlink.ino b/examples/IndependentBlink/IndependentBlink.ino new file mode 100644 index 0000000..6696ad6 --- /dev/null +++ b/examples/IndependentBlink/IndependentBlink.ino @@ -0,0 +1,16 @@ +#include + +PeriodicOutput slowLed(9, 1000); +PeriodicOutput fastLed(10, 500); + +void setup() { + const uint32_t now = millis(); + slowLed.begin(now); + fastLed.begin(now); +} + +void loop() { + const uint32_t now = millis(); + slowLed.update(now); + fastLed.update(now); +} diff --git a/examples/ScanningLight/ScanningLight.ino b/examples/ScanningLight/ScanningLight.ino new file mode 100644 index 0000000..317cd35 --- /dev/null +++ b/examples/ScanningLight/ScanningLight.ino @@ -0,0 +1,21 @@ +#include + +const uint8_t pins[] = {6, 7, 8, 9}; +const PatternStep scan[] = { + {0b0001, 180}, + {0b0010, 180}, + {0b0100, 180}, + {0b1000, 180}, + {0b0100, 180}, + {0b0010, 180}, +}; + +LedBank lights(pins, sizeof(pins)); +LedAnimator scanner(lights, scan, sizeof(scan) / sizeof(scan[0])); + +void setup() { + lights.begin(); + scanner.start(millis()); +} + +void loop() { scanner.update(millis()); } diff --git a/examples/TrafficLight/TrafficLight.ino b/examples/TrafficLight/TrafficLight.ino new file mode 100644 index 0000000..f995b98 --- /dev/null +++ b/examples/TrafficLight/TrafficLight.ino @@ -0,0 +1,20 @@ +#include + +// Pins are ordered green, yellow, red; bit 0 maps to the first pin. +const uint8_t pins[] = {6, 7, 8}; +const PatternStep phases[] = { + {0b001, 5000}, + {0b010, 2000}, + {0b100, 5000}, +}; + +LedBank trafficLights(pins, sizeof(pins)); +LedAnimator traffic(trafficLights, phases, + sizeof(phases) / sizeof(phases[0])); + +void setup() { + trafficLights.begin(); + traffic.start(millis()); +} + +void loop() { traffic.update(millis()); } diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..ee2952a --- /dev/null +++ b/keywords.txt @@ -0,0 +1,10 @@ +LedBank KEYWORD1 +LedAnimator KEYWORD1 +PeriodicOutput KEYWORD1 +PatternStep KEYWORD1 +begin KEYWORD2 +writeMask KEYWORD2 +start KEYWORD2 +stop KEYWORD2 +update KEYWORD2 +setInterval KEYWORD2 diff --git a/library.json b/library.json new file mode 100644 index 0000000..9c2acdb --- /dev/null +++ b/library.json @@ -0,0 +1,15 @@ +{ + "name": "ArduinoPatterns", + "version": "1.0.0", + "description": "Non-blocking LED and digital-output patterns for Arduino.", + "keywords": ["arduino", "led", "non-blocking", "millis", "education"], + "repository": { + "type": "git", + "url": "https://github.com/devkyato/Custom-Arduino-Libraries.git" + }, + "authors": [{"name": "devkyato", "maintainer": true}], + "license": "MIT", + "frameworks": ["arduino"], + "platforms": "*", + "headers": "ArduinoPatterns.h" +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..7e4e7bb --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=ArduinoPatterns +version=1.0.0 +author=devkyato and contributors +maintainer=devkyato +sentence=Non-blocking LED and digital-output patterns for Arduino. +paragraph=Coordinate timed LED sequences, bit-mask output banks, and independent periodic outputs without delay() or heap allocation. +category=Signal Input/Output +url=https://github.com/devkyato/Custom-Arduino-Libraries +architectures=* +includes=ArduinoPatterns.h diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..5bd45d4 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,29 @@ +[platformio] +default_envs = uno, esp32dev, pico +src_dir = examples/TrafficLight + +[env] +framework = arduino +lib_extra_dirs = . +build_flags = -Wall -Wextra + +[env:uno] +platform = atmelavr +board = uno + +[env:esp32dev] +platform = espressif32 +board = esp32dev + +[env:pico] +platform = raspberrypi +board = pico + +[env:native] +platform = native +framework = +test_framework = custom +build_flags = + -std=c++17 + -I test/test_native +lib_ignore = Unity diff --git a/src/ArduinoPatterns.cpp b/src/ArduinoPatterns.cpp new file mode 100644 index 0000000..ef900f2 --- /dev/null +++ b/src/ArduinoPatterns.cpp @@ -0,0 +1,156 @@ +#include "ArduinoPatterns.h" + +LedBank::LedBank(const uint8_t* pins, size_t count, bool activeHigh) + : pins_(pins), + count_(count <= kMaximumPins ? count : 0), + activeHigh_(activeHigh), + mask_(0) {} + +void LedBank::begin() { + if (!valid()) { + return; + } + for (size_t index = 0; index < count_; ++index) { + pinMode(pins_[index], OUTPUT); + } + clear(); +} + +void LedBank::writeMask(uint32_t mask) { + if (!valid()) { + return; + } + const uint32_t bounded = + count_ == kMaximumPins ? mask : mask & ((uint32_t{1} << count_) - 1); + for (size_t index = 0; index < count_; ++index) { + const bool active = (bounded & (uint32_t{1} << index)) != 0; + digitalWrite(pins_[index], active == activeHigh_ ? HIGH : LOW); + } + mask_ = bounded; +} + +void LedBank::clear() { writeMask(0); } + +size_t LedBank::size() const { return count_; } + +uint32_t LedBank::mask() const { return mask_; } + +bool LedBank::valid() const { return pins_ != nullptr && count_ > 0; } + +LedAnimator::LedAnimator(LedBank& bank, const PatternStep* steps, size_t count, + bool repeat) + : bank_(bank), + steps_(steps), + count_(count), + repeat_(repeat), + running_(false), + current_(0), + changedAt_(0) {} + +bool LedAnimator::start(uint32_t now) { + if (!bank_.valid() || steps_ == nullptr || count_ == 0) { + running_ = false; + return false; + } + current_ = 0; + changedAt_ = now; + running_ = true; + bank_.writeMask(steps_[current_].mask); + return true; +} + +void LedAnimator::stop(bool clearOutputs) { + running_ = false; + if (clearOutputs) { + bank_.clear(); + } +} + +bool LedAnimator::update(uint32_t now) { + if (!running_) { + return false; + } + + bool changed = false; + while (running_ && static_cast(now - changedAt_) >= + steps_[current_].durationMs) { + const uint32_t duration = steps_[current_].durationMs; + changedAt_ += duration; + advance(now); + changed = true; + if (duration == 0) { + changedAt_ = now; + break; + } + } + return changed; +} + +void LedAnimator::setRepeat(bool repeat) { repeat_ = repeat; } + +bool LedAnimator::running() const { return running_; } + +size_t LedAnimator::currentStep() const { return current_; } + +void LedAnimator::advance(uint32_t now) { + if (current_ + 1 < count_) { + ++current_; + bank_.writeMask(steps_[current_].mask); + return; + } + if (repeat_) { + current_ = 0; + bank_.writeMask(steps_[current_].mask); + return; + } + running_ = false; + changedAt_ = now; +} + +PeriodicOutput::PeriodicOutput(uint8_t pin, uint32_t intervalMs, + bool activeHigh, bool initialState) + : pin_(pin), + intervalMs_(intervalMs), + activeHigh_(activeHigh), + state_(initialState), + changedAt_(0) {} + +void PeriodicOutput::begin(uint32_t now) { + pinMode(pin_, OUTPUT); + changedAt_ = now; + write(); +} + +bool PeriodicOutput::update(uint32_t now) { + if (intervalMs_ == 0 || + static_cast(now - changedAt_) < intervalMs_) { + return false; + } + const uint32_t elapsed = static_cast(now - changedAt_); + const uint32_t periods = elapsed / intervalMs_; + changedAt_ += periods * intervalMs_; + if ((periods & 1U) != 0) { + state_ = !state_; + write(); + } + return true; +} + +void PeriodicOutput::setInterval(uint32_t intervalMs, uint32_t now) { + intervalMs_ = intervalMs; + changedAt_ = now; +} + +void PeriodicOutput::set(bool active, uint32_t now) { + state_ = active; + changedAt_ = now; + write(); +} + +bool PeriodicOutput::active() const { return state_; } + +uint32_t PeriodicOutput::interval() const { return intervalMs_; } + +void PeriodicOutput::write() { + digitalWrite(pin_, state_ == activeHigh_ ? HIGH : LOW); +} diff --git a/src/ArduinoPatterns.h b/src/ArduinoPatterns.h new file mode 100644 index 0000000..b85010c --- /dev/null +++ b/src/ArduinoPatterns.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include + +struct PatternStep { + uint32_t mask; + uint32_t durationMs; +}; + +class LedBank { + public: + static constexpr size_t kMaximumPins = 32; + + LedBank(const uint8_t* pins, size_t count, bool activeHigh = true); + + void begin(); + void writeMask(uint32_t mask); + void clear(); + + size_t size() const; + uint32_t mask() const; + bool valid() const; + + private: + const uint8_t* pins_; + size_t count_; + bool activeHigh_; + uint32_t mask_; +}; + +class LedAnimator { + public: + LedAnimator(LedBank& bank, const PatternStep* steps, size_t count, + bool repeat = true); + + bool start(uint32_t now); + void stop(bool clearOutputs = true); + bool update(uint32_t now); + void setRepeat(bool repeat); + + bool running() const; + size_t currentStep() const; + + private: + LedBank& bank_; + const PatternStep* steps_; + size_t count_; + bool repeat_; + bool running_; + size_t current_; + uint32_t changedAt_; + + void advance(uint32_t now); +}; + +class PeriodicOutput { + public: + PeriodicOutput(uint8_t pin, uint32_t intervalMs, bool activeHigh = true, + bool initialState = false); + + void begin(uint32_t now); + bool update(uint32_t now); + void setInterval(uint32_t intervalMs, uint32_t now); + void set(bool active, uint32_t now); + + bool active() const; + uint32_t interval() const; + + private: + uint8_t pin_; + uint32_t intervalMs_; + bool activeHigh_; + bool state_; + uint32_t changedAt_; + + void write(); +}; diff --git a/test/test_custom_runner.py b/test/test_custom_runner.py new file mode 100644 index 0000000..094385b --- /dev/null +++ b/test/test_custom_runner.py @@ -0,0 +1,11 @@ +from platformio.public import TestCase, TestRunnerBase, TestStatus + + +class CustomTestRunner(TestRunnerBase): + """Use PlatformIO's native build/run stages and the program exit code.""" + + def stage_testing(self): + super().stage_testing() + self.test_suite.add_case( + TestCase(name="native_assertions", status=TestStatus.PASSED) + ) diff --git a/test/test_native/Arduino.h b/test/test_native/Arduino.h new file mode 100644 index 0000000..afb5bee --- /dev/null +++ b/test/test_native/Arduino.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#define LOW 0x0 +#define HIGH 0x1 +#define OUTPUT 0x1 + +void pinMode(uint8_t pin, uint8_t mode); +void digitalWrite(uint8_t pin, uint8_t value); +uint32_t millis(); diff --git a/test/test_native/test_main.cpp b/test/test_native/test_main.cpp new file mode 100644 index 0000000..b272eff --- /dev/null +++ b/test/test_native/test_main.cpp @@ -0,0 +1,120 @@ +#include + +#include +#include +#include + +namespace { +uint8_t modes[256]; +uint8_t values[256]; +uint32_t fakeMillis; +} // namespace + +void pinMode(uint8_t pin, uint8_t mode) { modes[pin] = mode; } + +void digitalWrite(uint8_t pin, uint8_t value) { values[pin] = value; } + +uint32_t millis() { return fakeMillis; } + +void resetFakes() { + std::memset(modes, 0, sizeof(modes)); + std::memset(values, 0, sizeof(values)); + fakeMillis = 0; +} + +void test_led_bank_initializes_and_writes_masks() { + resetFakes(); + const uint8_t pins[] = {2, 4, 7}; + LedBank bank(pins, 3); + assert(bank.valid()); + + bank.begin(); + assert(modes[2] == OUTPUT); + assert(values[2] == LOW); + + bank.writeMask(0b101); + assert(values[2] == HIGH); + assert(values[4] == LOW); + assert(values[7] == HIGH); + assert(bank.mask() == 0b101); + + bank.writeMask(0xFFFFFFFF); + assert(bank.mask() == 0b111); +} + +void test_led_bank_supports_active_low_outputs() { + resetFakes(); + const uint8_t pins[] = {3}; + LedBank bank(pins, 1, false); + bank.begin(); + assert(values[3] == HIGH); + bank.writeMask(1); + assert(values[3] == LOW); +} + +void test_animator_advances_without_delay_and_catches_up() { + resetFakes(); + const uint8_t pins[] = {2, 3}; + const PatternStep steps[] = {{0b01, 10}, {0b10, 20}, {0b11, 30}}; + LedBank bank(pins, 2); + LedAnimator animation(bank, steps, 3); + bank.begin(); + + assert(animation.start(100)); + assert(bank.mask() == 0b01); + assert(!animation.update(109)); + assert(animation.update(130)); + assert(bank.mask() == 0b11); + assert(animation.currentStep() == 2); +} + +void test_animator_handles_millis_rollover() { + resetFakes(); + const uint8_t pins[] = {2}; + const PatternStep steps[] = {{0, 10}, {1, 10}}; + LedBank bank(pins, 1); + LedAnimator animation(bank, steps, 2, false); + bank.begin(); + + animation.start(UINT32_MAX - 5); + assert(animation.update(4)); + assert(bank.mask() == 1); +} + +void test_non_repeating_animation_stops() { + resetFakes(); + const uint8_t pins[] = {2}; + const PatternStep steps[] = {{1, 5}, {0, 5}}; + LedBank bank(pins, 1); + LedAnimator animation(bank, steps, 2, false); + bank.begin(); + animation.start(0); + + animation.update(10); + assert(!animation.running()); + assert(bank.mask() == 0); +} + +void test_periodic_output_handles_skipped_periods() { + resetFakes(); + PeriodicOutput output(9, 100, true, false); + output.begin(0); + assert(values[9] == LOW); + + assert(output.update(100)); + assert(output.active()); + assert(output.update(300)); + assert(output.active()); + assert(output.update(400)); + assert(!output.active()); +} + +int main(int, char**) { + test_led_bank_initializes_and_writes_masks(); + test_led_bank_supports_active_low_outputs(); + test_animator_advances_without_delay_and_catches_up(); + test_animator_handles_millis_rollover(); + test_non_repeating_animation_stops(); + test_periodic_output_handles_skipped_periods(); + return 0; +} diff --git a/tools/build_release.py b/tools/build_release.py new file mode 100644 index 0000000..6d201ab --- /dev/null +++ b/tools/build_release.py @@ -0,0 +1,68 @@ +"""Build a deterministic Arduino IDE library ZIP.""" + +from __future__ import annotations + +import configparser +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DIST = ROOT / "dist" +INCLUDED = ( + "src", + "examples", + "README.md", + "CHANGELOG.md", + "LICENSE", + "library.properties", + "library.json", + "keywords.txt", +) +FIXED_TIME = (2026, 1, 1, 0, 0, 0) + + +def library_version() -> str: + parser = configparser.ConfigParser() + text = "[library]\n" + (ROOT / "library.properties").read_text(encoding="utf-8") + parser.read_string(text) + return parser["library"]["version"] + + +def files_to_package() -> list[Path]: + files: list[Path] = [] + for item in INCLUDED: + path = ROOT / item + if path.is_dir(): + files.extend(candidate for candidate in path.rglob("*") if candidate.is_file()) + else: + files.append(path) + return sorted(files, key=lambda path: path.as_posix()) + + +def main() -> None: + version = library_version() + DIST.mkdir(exist_ok=True) + output = DIST / f"ArduinoPatterns-{version}.zip" + + with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive: + for path in files_to_package(): + relative = path.relative_to(ROOT).as_posix() + info = zipfile.ZipInfo(f"ArduinoPatterns/{relative}", FIXED_TIME) + info.compress_type = zipfile.ZIP_DEFLATED + info.external_attr = 0o100644 << 16 + archive.writestr(info, path.read_bytes()) + + with zipfile.ZipFile(output) as archive: + required = { + "ArduinoPatterns/library.properties", + "ArduinoPatterns/src/ArduinoPatterns.h", + } + missing = required.difference(archive.namelist()) + if missing: + raise RuntimeError(f"release archive is missing: {sorted(missing)}") + + print(output) + + +if __name__ == "__main__": + main()