Compare commits
73 Commits
test
..
df0f440dcb
| Author | SHA1 | Date | |
|---|---|---|---|
| df0f440dcb | |||
| 3af958b69f | |||
| 6f9d2912d3 | |||
| ece874a8ed | |||
| 14781b8c07 | |||
| 3d17e7bab9 | |||
| 4030048401 | |||
| 72636f9ecf | |||
| 688b1905e5 | |||
| b68d36bb85 | |||
| ba8789797c | |||
| fd04dea8dd | |||
| 9c8f67dccb | |||
| 6d51234f21 | |||
| 66f09c3468 | |||
| 913373ca72 | |||
| f367bd365b | |||
| dfd511e499 | |||
| 4da32466a8 | |||
| a3164d722e | |||
| dd1c13fbbc | |||
| ec8ec4cd18 | |||
| 59b2bf01b7 | |||
| 3dc66a536e | |||
| 16f02ed967 | |||
| d6eb2cd561 | |||
| e4609c6978 | |||
| c81e23f7cd | |||
| 34ca86d771 | |||
| 9f7a383b38 | |||
| 1961631e2c | |||
| 67613120ad | |||
| 3b8e54c511 | |||
| 4ea7165148 | |||
| 2831ecf43c | |||
| 5ce02a6eff | |||
| ea8aab0a68 | |||
| 4c0991b8bd | |||
| bfba3b02fd | |||
| 46289b9d40 | |||
| 4f389ac0fe | |||
| 17c44d9b08 | |||
| cbe7efe883 | |||
| a42415e64e | |||
| d961afd950 | |||
| 8e39a1f23c | |||
| c4adb38576 | |||
| ff073c762b | |||
| 8e9bd18676 | |||
| f39364f67f | |||
| 0ace263324 | |||
| 23712a152b | |||
| d61c9c60bf | |||
| 2907dac33d | |||
| 246bfe0cb9 | |||
| de45f89f9b | |||
| 66b4e75174 | |||
| 9f0e603215 | |||
| 257bc9aa82 | |||
| b47993b29a | |||
| 7085bfff04 | |||
| 89f3248747 | |||
| c7ea8d5817 | |||
| ff197dc889 | |||
| 838afaa36f | |||
| 75c3f5706b | |||
| dc62265fbb | |||
| fa6bf6fd6a | |||
| ce413dc20c | |||
| 6c4cfd3a45 | |||
| 86b94e3781 | |||
| da2a077343 | |||
| 6b2921aa30 |
@@ -0,0 +1,5 @@
|
|||||||
|
local M = {}
|
||||||
|
M.board = 'esp32:esp32:esp32'
|
||||||
|
M.port = '/dev/ttyUSB0'
|
||||||
|
M.baudrate = 115200
|
||||||
|
return M
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Arduino/C++ friendly format
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
|
||||||
|
# Indentation
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
IndentCaseLabels: false
|
||||||
|
|
||||||
|
# Braces - keep opening brace on same line
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
|
||||||
|
# Spacing (fixed)
|
||||||
|
SpaceBeforeParens: Never
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
|
||||||
|
# Line length
|
||||||
|
ColumnLimit: 100
|
||||||
|
|
||||||
|
# Includes
|
||||||
|
SortIncludes: true
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Doorbell Touch - Build Harness
|
||||||
|
|
||||||
|
## Session Handoff Note
|
||||||
|
⚠️ ALWAYS check `mise.toml` for available build tasks before compiling/uploading. Run `mise tasks` to list.
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
```bash
|
||||||
|
BOARD=esp32-32e-4 mise run compile # Compile
|
||||||
|
BOARD=esp32-32e-4 mise run upload # Upload (auto-kills monitor)
|
||||||
|
BOARD=esp32-32e-4 mise run monitor # Start JSON monitor daemon
|
||||||
|
BOARD=esp32-32e-4 mise run log-tail # Watch colored logs
|
||||||
|
BOARD=esp32-32e-4 mise run cmd COMMAND=dashboard # Send command
|
||||||
|
BOARD=esp32-32e-4 mise run state # Show device state
|
||||||
|
BOARD=esp32-32e-4 mise run install # Install libs (shared + board)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `mise.toml` - Tasks (compile, upload, monitor, log-tail, cmd, state, install, clean)
|
||||||
|
- `scripts/monitor-agent.py` - Serial monitor with JSON log + command FIFO
|
||||||
|
- `scripts/install-shared.sh` - Shared Arduino libs (ArduinoJson, NTPClient)
|
||||||
|
- `boards/{BOARD}/install.sh` - Board-specific vendor libs
|
||||||
|
|
||||||
|
## Monitor Daemon
|
||||||
|
- JSON log: `/tmp/doorbell-$BOARD.jsonl` - `{"ts":123.456,"line":"..."}`
|
||||||
|
- State: `/tmp/doorbell-$BOARD-state.json` - `{"screen":"DASHBOARD",...}`
|
||||||
|
- Cmd: `echo 'dashboard' > /tmp/doorbell-$BOARD-cmd.fifo`
|
||||||
|
|
||||||
|
## Boards
|
||||||
|
- esp32-32e (original 2.8" ILI9341)
|
||||||
|
- esp32-32e-4 (4" ST7796)
|
||||||
|
- esp32-s3-lcd-43 (4.3" S3 display)
|
||||||
|
|
||||||
|
## Known Fixes
|
||||||
|
- dashboard/off admin commands now reset inactivity timer (DoorbellLogic.cpp:234,240)
|
||||||
|
|
||||||
|
## Reverted Changes Log
|
||||||
|
Track changes that were reverted to avoid flapping:
|
||||||
|
- 2025-02-18: Initially configured LSP via neovim/mason (`.config/nvim/lua/plugins/arduino.lua`) — user clarified they wanted Crush-native LSP config instead
|
||||||
|
|
||||||
|
## Documentation Lookup Rule
|
||||||
|
When uncertain about CLI tool flags or argument syntax:
|
||||||
|
1. Run the tool with `-h` or `--help` first
|
||||||
|
2. If unclear, search for official documentation matching the tool version
|
||||||
|
3. Prefer checking the tool's own help/docs over guessing
|
||||||
|
4. For Arduino CLI, check `arduino-cli <command> --help` or official Arduino CLI docs
|
||||||
|
|
||||||
|
## Neovim LSP Configuration
|
||||||
|
For Neovim projects:
|
||||||
|
- Use Mason + mason-lspconfig to manage LSP installations
|
||||||
|
- Add LSP configs to `~/.config/nvim/lua/plugins/` as separate plugin files
|
||||||
|
- Use `on_new_config` for dynamic settings (e.g., BOARD env var → FQBN mapping)
|
||||||
|
- Example: `~/.config/nvim/lua/plugins/arduino.lua` (arduino-language-server)
|
||||||
|
|
||||||
|
## Crush LSP Configuration
|
||||||
|
- Configure via `.crush.json` in project root (or `$HOME/.config/crush/crush.json` globally)
|
||||||
|
- Supports env var interpolation: `$BOARD` or `${BOARD}`
|
||||||
|
- Supports command substitution: `$(cat boards/$BOARD/board-config.sh | grep '^FQBN=' | cut -d'"' -f2)`
|
||||||
|
- Example for Arduino (auto-detects FQBN from BOARD):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lsp": {
|
||||||
|
"arduino": {
|
||||||
|
"command": "arduino-language-server",
|
||||||
|
"args": ["-fqbn", "$(cat boards/${BOARD:-esp32-32e-4}/board-config.sh | grep '^FQBN=' | cut -d'\"' -f2)"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Run `mise run gen-crush-config` to regenerate static config
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Secrets (WiFi passwords etc.)
|
||||||
|
**/secrets.h
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# Vendored libraries (re-created by install-libs)
|
||||||
|
vendor/esp32-32e/TFT_eSPI/
|
||||||
|
vendor/esp32-32e-4/TFT_eSPI/
|
||||||
|
vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
compile_commands.json
|
||||||
|
.board-last
|
||||||
|
.cache/
|
||||||
|
boards/uno-r4-wifi/uno-r4-wifi.ino.backup
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"MD013": false,
|
||||||
|
"MD060": false,
|
||||||
|
"MD025": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"MD013": false,
|
||||||
|
"MD060": false,
|
||||||
|
"MD025": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# AGENTS.md — Klubhaus Doorbell
|
||||||
|
|
||||||
|
Multi-target Arduino/ESP32 doorbell alert system using ntfy.sh. Default BOARD: `esp32-s3-lcd-43`.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All tasks use just (see justfile). Default BOARD: esp32-s3-lcd-43
|
||||||
|
|
||||||
|
# Set target board
|
||||||
|
just BOARD=esp32-32e-4 compile # ESP32-32E 4" (320x480 ST7796)
|
||||||
|
just BOARD=esp32-32e compile # ESP32-32E 3.5" (320x240 ILI9341)
|
||||||
|
just BOARD=esp32-s3-lcd-43 compile # ESP32-S3-Touch-LCD-4.3 (800x480 RGB)
|
||||||
|
just BOARD=uno-r4-wifi compile # Arduino Uno R4 WiFi (12x8 LED matrix)
|
||||||
|
|
||||||
|
# Core commands
|
||||||
|
just compile # compile for current BOARD
|
||||||
|
just upload # upload (auto-kills monitor first)
|
||||||
|
just monitor # start JSON monitor daemon (background)
|
||||||
|
just kill # kill monitor/release serial port
|
||||||
|
just full # compile + upload + monitor + log-tail (full feedback loop)
|
||||||
|
just watch # tail colored logs
|
||||||
|
|
||||||
|
# Formatting & cleanup
|
||||||
|
just format # format code with clang-format
|
||||||
|
just clean # remove build artifacts
|
||||||
|
just clean-temp # remove monitor logs/FIFOs/state files
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
just cmd dashboard # send command to device
|
||||||
|
just state # show device state
|
||||||
|
just monitor-raw # raw serial monitor (115200 baud)
|
||||||
|
just monitor-tio # show tio command for terminal UI
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
just install-libs-shared # shared libs (ArduinoJson, NTPClient)
|
||||||
|
just install # shared + board-specific libs
|
||||||
|
|
||||||
|
# LSP / IDE
|
||||||
|
just gen-compile-commands # generate compile_commands.json
|
||||||
|
just gen-crush-config # generate .crush.json for BOARD
|
||||||
|
```
|
||||||
|
|
||||||
|
**Serial debug commands** (115200 baud): `alert`, `silence`, `dashboard`, `off`, `status`, `reboot`
|
||||||
|
|
||||||
|
**No unit tests exist** — verify changes by compiling and deploying to hardware.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### Formatting (.clang-format)
|
||||||
|
- BasedOnStyle: WebKit
|
||||||
|
- 4-space indentation, no tabs
|
||||||
|
- Column limit: 100
|
||||||
|
- Opening brace on same line (`BreakBeforeBraces: Attach`)
|
||||||
|
- Run `just format` to format code
|
||||||
|
|
||||||
|
### Header Guards
|
||||||
|
Use `#pragma once` (not `#ifndef` guards).
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
| Type | Convention | Example |
|
||||||
|
|------|------------|---------|
|
||||||
|
| Classes | PascalCase | `DisplayManager`, `IDisplayDriver` |
|
||||||
|
| Constants/enums | SCREAMING_SNAKE | `POLL_INTERVAL_MS`, `ScreenState::DASHBOARD` |
|
||||||
|
| Variables/functions | camelCase | `currentState`, `updateDisplay` |
|
||||||
|
| Member variables | `_` prefix | `_screenWidth`, `_isConnected` |
|
||||||
|
|
||||||
|
### Types
|
||||||
|
- Use fixed-width types for protocol/serialization (`uint8_t`, not `byte`)
|
||||||
|
- Use `size_t` for sizes and array indices
|
||||||
|
- Avoid `bool` for pin states — use `uint8_t` or `int`
|
||||||
|
|
||||||
|
### Imports Organization (in order)
|
||||||
|
1. Arduino core (`Arduino.h`)
|
||||||
|
2. Standard C/C++ (`<cstdint>`, `<String>`, `<vector>`)
|
||||||
|
3. Third-party libs (`TFT_eSPI.h`, `ArduinoJson.h`)
|
||||||
|
4. Local project (`"Config.h"`, `"ScreenState.h"`)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Serial logging: `Serial.println("[ERROR] message")`
|
||||||
|
- Use `Serial.printf()` for formatted debug
|
||||||
|
- Return error codes, not exceptions
|
||||||
|
- Log state: `[STATE] → DASHBOARD`
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Display Driver Interface
|
||||||
|
- Pure virtual `IDisplayDriver` in shared `KlubhausCore`
|
||||||
|
- Each board implements concrete driver (`DisplayDriverTFT`, `DisplayDriverGFX`)
|
||||||
|
- `DisplayManager` delegates to `IDisplayDriver` — no display-lib coupling in shared code
|
||||||
|
|
||||||
|
### Arduino Patterns
|
||||||
|
- `setup()` — call `begin()` on managers
|
||||||
|
- `loop()` — call `update()` on managers
|
||||||
|
- Use `millis()` for timing (not `delay()`)
|
||||||
|
- Serial baud: 115200
|
||||||
|
|
||||||
|
### Style System
|
||||||
|
- Style constants in board's `board_config.h`: `STYLE_SPACING_X`, `STYLE_COLOR_BG`, etc.
|
||||||
|
- Font abstraction via `IDisplayDriver`: `setTitleFont()`, `setBodyFont()`, etc.
|
||||||
|
- Layout helpers in `KlubhausCore/src/Style.h`
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
```
|
||||||
|
libraries/KlubhausCore/src/
|
||||||
|
├── KlubhausCore.h # Umbrella include
|
||||||
|
├── Config.h # Timing, WiFiCred struct
|
||||||
|
├── ScreenState.h # State enums/structs
|
||||||
|
├── IDisplayDriver.h # Pure virtual interface
|
||||||
|
├── DisplayManager.h # Delegates to IDisplayDriver
|
||||||
|
├── NetManager.* # WiFi, HTTP, NTP
|
||||||
|
└── DoorbellLogic.* # State machine, ntfy polling
|
||||||
|
|
||||||
|
boards/{BOARD}/
|
||||||
|
├── {BOARD}.ino # Main sketch
|
||||||
|
├── board_config.h # Board-specific config (ESP32 boards)
|
||||||
|
├── secrets.h # WiFi credentials (optional, use env vars)
|
||||||
|
├── tft_user_setup.h # TFT_eSPI config (TFT boards)
|
||||||
|
└── DisplayDriver*.{h,cpp} # Concrete IDisplayDriver (ESP32 boards)
|
||||||
|
|
||||||
|
boards/uno-r4-wifi/ # Uno R4 WiFi (LED matrix display)
|
||||||
|
├── uno-r4-wifi.ino # Main sketch (standalone)
|
||||||
|
├── test_messages.sh # Test script to send ntfy messages
|
||||||
|
└── README.md # Quick start guide
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
1. **secrets.h**: Boards with `-DLOCAL_SECRETS` use local `secrets.h`; others use `KlubhausCore/src/secrets.h`
|
||||||
|
2. **Vendored libs**: Each board links only its display lib — never TFT_eSPI + LovyanGFX together
|
||||||
|
3. **LSP errors**: Run `just gen-compile-commands` then restart LSP; build works regardless
|
||||||
|
4. **Serial port**: `upload`/`monitor` auto-depend on `kill` to release port
|
||||||
|
5. **State tags**: Use `[STATE] → DASHBOARD`, `[ADMIN]`, `[TOUCH]`, `[ALERT]` for monitor parsing
|
||||||
|
|
||||||
|
## Config Constants (Config.h)
|
||||||
|
|
||||||
|
| Constant | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `FW_VERSION` | "5.1" | Firmware version |
|
||||||
|
| `POLL_INTERVAL_MS` | 15000 | ntfy.sh poll interval |
|
||||||
|
| `ALERT_TIMEOUT_MS` | 120000 | Auto-clear alert |
|
||||||
|
| `INACTIVITY_TIMEOUT_MS` | 30000 | Display off timeout |
|
||||||
|
| `HOLD_TO_SILENCE_MS` | 3000 | Hold to silence |
|
||||||
|
| `WIFI_CONNECT_TIMEOUT_MS` | 15000 | WiFi timeout |
|
||||||
|
| `HTTP_TIMEOUT_MS` | 10000 | HTTP request timeout |
|
||||||
|
|
||||||
|
## Screen States
|
||||||
|
|
||||||
|
- **BOOT** — Initializing
|
||||||
|
- **DASHBOARD** — Normal operation
|
||||||
|
- **ALERT** — Doorbell ring detected
|
||||||
|
- **OFF** — Display backlight off (polling continues)
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Klubhaus Doorbell
|
||||||
|
|
||||||
|
Multi-target doorbell alert system powered by [ntfy.sh](https://ntfy.sh).
|
||||||
|
|
||||||
|
## Targets
|
||||||
|
|
||||||
|
| Board | Display | Library | Build Command |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ESP32-32E | SPI TFT (ILI9341 etc.) | TFT_eSPI | `BOARD=esp32-32e mise run compile` |
|
||||||
|
| ESP32-32E-4" | SPI TFT 320x480 (ST7796) | TFT_eSPI | `BOARD=esp32-32e-4 mise run compile` |
|
||||||
|
| ESP32-S3-Touch-LCD-4.3 | 800x480 RGB parallel | LovyanGFX | `BOARD=esp32-s3-lcd-43 mise run compile` |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Install prerequisites: arduino-cli (with esp32:esp32 platform) and mise.
|
||||||
|
|
||||||
|
2. Edit WiFi credentials:
|
||||||
|
|
||||||
|
cp boards/esp32-32e/secrets.h.example boards/esp32-32e/secrets.h
|
||||||
|
cp boards/esp32-s3-lcd-43/secrets.h.example boards/esp32-s3-lcd-43/secrets.h
|
||||||
|
|
||||||
|
Then edit both secrets.h files with your WiFi SSIDs and passwords.
|
||||||
|
|
||||||
|
3. Build and upload:
|
||||||
|
|
||||||
|
BOARD=esp32-s3-lcd-43 mise run compile
|
||||||
|
BOARD=esp32-s3-lcd-43 mise run upload
|
||||||
|
|
||||||
|
BOARD=esp32-32e-4 mise run compile
|
||||||
|
BOARD=esp32-32e-4 mise run upload
|
||||||
|
|
||||||
|
BOARD=esp32-32e mise run compile
|
||||||
|
BOARD=esp32-32e mise run upload
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
.
|
||||||
|
├── libraries/
|
||||||
|
│ └── KlubhausCore/ Shared Arduino library
|
||||||
|
│ └── src/
|
||||||
|
│ ├── KlubhausCore.h Umbrella include
|
||||||
|
│ ├── Config.h Shared constants
|
||||||
|
│ ├── ScreenState.h State enums / structs
|
||||||
|
│ ├── IDisplayDriver.h Abstract display interface
|
||||||
|
│ ├── DisplayManager.h Thin delegation wrapper
|
||||||
|
│ ├── NetManager.* WiFi, NTP, HTTP
|
||||||
|
│ └── DoorbellLogic.* State machine, ntfy polling
|
||||||
|
├── boards/
|
||||||
|
│ ├── esp32-32e/ ESP32-32E sketch
|
||||||
|
│ │ ├── esp32-32e.ino
|
||||||
|
│ │ ├── board_config.h
|
||||||
|
│ │ ├── secrets.h (gitignored, copy from .example)
|
||||||
|
│ │ ├── tft_user_setup.h
|
||||||
|
│ │ └── DisplayDriverTFT.*
|
||||||
|
│ ├── esp32-32e-4/ ESP32-32E-4" sketch
|
||||||
|
│ │ ├── esp32-32e-4.ino
|
||||||
|
│ │ ├── board_config.h
|
||||||
|
│ │ ├── secrets.h (gitignored, copy from .example)
|
||||||
|
│ │ ├── tft_user_setup.h
|
||||||
|
│ │ └── DisplayDriverTFT.*
|
||||||
|
│ └── esp32-s3-lcd-43/ ESP32-S3-LCD-4.3 sketch
|
||||||
|
│ ├── esp32-s3-lcd-43.ino
|
||||||
|
│ ├── board_config.h
|
||||||
|
│ ├── secrets.h (gitignored, copy from .example)
|
||||||
|
│ ├── LovyanPins.h
|
||||||
|
│ └── DisplayDriverGFX.*
|
||||||
|
├── vendor/ Per-board vendored display libs
|
||||||
|
│ ├── esp32-32e/TFT_eSPI/
|
||||||
|
│ ├── esp32-32e-4/TFT_eSPI/
|
||||||
|
│ └── esp32-s3-lcd-43/LovyanGFX/
|
||||||
|
└── mise.toml Build harness
|
||||||
|
|
||||||
|
## Serial Commands
|
||||||
|
|
||||||
|
Type into the serial monitor at 115200 baud:
|
||||||
|
|
||||||
|
| Command | Action |
|
||||||
|
|---|---|
|
||||||
|
| alert | Trigger a test alert |
|
||||||
|
| silence | Silence current alert |
|
||||||
|
| dashboard | Show dashboard screen |
|
||||||
|
| off | Turn off display |
|
||||||
|
| status | Print state + memory info |
|
||||||
|
| reboot | Restart device |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Each board target gets its own sketch directory with a concrete IDisplayDriver
|
||||||
|
implementation. The shared KlubhausCore library contains all business logic,
|
||||||
|
networking, and the abstract interface. Arduino CLI's --libraries flag ensures
|
||||||
|
each board only links its own vendored display library -- no preprocessor conflicts.
|
||||||
|
|
||||||
|
Board sketch (.ino)
|
||||||
|
|
|
||||||
|
+-- #include <KlubhausCore.h> (shared library)
|
||||||
|
| +-- DoorbellLogic (state machine + ntfy polling)
|
||||||
|
| +-- NetManager (WiFi, HTTP, NTP)
|
||||||
|
| +-- DisplayManager (delegates to IDisplayDriver)
|
||||||
|
| +-- IDisplayDriver (pure virtual interface)
|
||||||
|
|
|
||||||
|
+-- DisplayDriverXxx (board-specific, concrete driver)
|
||||||
|
+-- links against vendored display lib
|
||||||
|
(TFT_eSPI or Arduino_GFX, never both)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Session Notes - 2026-02-19
|
||||||
|
|
||||||
|
## What's Been Done
|
||||||
|
|
||||||
|
### Build System
|
||||||
|
- Fixed mise.toml compile task (single quotes, proper shell escaping)
|
||||||
|
- Added upload task back
|
||||||
|
- All 3 boards now compile successfully
|
||||||
|
|
||||||
|
### Code Changes
|
||||||
|
- Added font abstraction (`setTitleFont`, `setBodyFont`, `setLabelFont`, `setDefaultFont`)
|
||||||
|
- Added CSS-like styling constants (`STYLE_*` in board_config.h)
|
||||||
|
- Added Layout helpers in `KlubhausCore/src/Style.h`
|
||||||
|
- Added `drawDebugTouch()` for red crosshair debug feature
|
||||||
|
- Added test harness for touch injection (`TEST:touch` commands)
|
||||||
|
- Fixed dashboard tile layout to use STYLE constants
|
||||||
|
|
||||||
|
### Neovim Integration
|
||||||
|
- Added keymaps in `~/.config/nvim/lua/config/keymaps.lua`
|
||||||
|
- Board auto-detection from current file path
|
||||||
|
- Notifications via `vim.notify()` on task completion/failure
|
||||||
|
- Keybindings: `<leader>mc`, `<leader>mu`, `<leader>mm`, `<leader>ma`, `<leader>mk`
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Updated AGENTS.md with Style System section
|
||||||
|
- Updated RTK section (slimmed down)
|
||||||
|
- Added note about multiple mise tasks with `&&`
|
||||||
|
|
||||||
|
## Current Issues / TODO
|
||||||
|
|
||||||
|
### High Priority
|
||||||
|
- [ ] Test touch coordinates on esp32-s3-lcd-43 - crosshair should show where user taps
|
||||||
|
- [ ] Debug why tiles overlap header (should be fixed with STYLE_HEADER_HEIGHT)
|
||||||
|
|
||||||
|
### Medium Priority
|
||||||
|
- [ ] Add theme support (dark/light mode)
|
||||||
|
- [ ] Fix esp32-32e and esp32-32e-4 dashboard rendering
|
||||||
|
|
||||||
|
### Low Priority
|
||||||
|
- [ ] Add more tile labels for esp32-s3-lcd-43 (currently just "1", "2", "3"...)
|
||||||
|
|
||||||
|
## Board Status
|
||||||
|
|
||||||
|
| Board | Display | Build | Notes |
|
||||||
|
|-------|---------|-------|-------|
|
||||||
|
| esp32-s3-lcd-43 | 800x480 | ✅ 35% | Main development board |
|
||||||
|
| esp32-32e-4 | 320x480 | ✅ 83% | |
|
||||||
|
| esp32-32e | 320x240 | ✅ 82% | |
|
||||||
|
|
||||||
|
## Style Constants Reference
|
||||||
|
|
||||||
|
### Spacing (in board_config.h)
|
||||||
|
- `STYLE_SPACING_X` - Base horizontal margin
|
||||||
|
- `STYLE_SPACING_Y` - Base vertical margin
|
||||||
|
- `STYLE_HEADER_HEIGHT` - Header bar height
|
||||||
|
- `STYLE_TILE_GAP` - Gap between tiles
|
||||||
|
- `STYLE_TILE_PADDING` - Tile internal padding
|
||||||
|
- `STYLE_TILE_RADIUS` - Tile border radius
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
- `STYLE_COLOR_BG` - Screen background
|
||||||
|
- `STYLE_COLOR_HEADER` - Header background
|
||||||
|
- `STYLE_COLOR_FG` - Primary text color
|
||||||
|
- `STYLE_COLOR_ALERT` - Alert screen
|
||||||
|
- `STYLE_COLOR_TILE_1` through `STYLE_COLOR_TILE_4` - Tile colors
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compile and upload
|
||||||
|
mise run compile && mise run upload && mise run monitor
|
||||||
|
|
||||||
|
# Debug touch (in serial monitor)
|
||||||
|
TEST:touch X Y press
|
||||||
|
TEST:touch X Y release
|
||||||
|
TEST:touch clear
|
||||||
|
```
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
#include "DisplayDriverTFT.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
#include <Style.h>
|
||||||
|
|
||||||
|
// ── Fonts ───────────────────────────────────────────────────
|
||||||
|
// TFT_eSPI built-in fonts for 320x480 display (scaled from 800x480)
|
||||||
|
// Using FreeFonts - scaled bitmap fonts via setTextSize would be too pixelated
|
||||||
|
// Note: FreeFonts are enabled via LOAD_GFXFF=1 in board-config.sh
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setTitleFont() { _tft.setFreeFont(&FreeSansBold18pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBodyFont() { _tft.setFreeFont(&FreeSans12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setLabelFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setDefaultFont() { _tft.setTextFont(2); }
|
||||||
|
|
||||||
|
// ── Test harness ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
// Commands:
|
||||||
|
// TEST:touch x y press - simulate press at (x, y)
|
||||||
|
// TEST:touch x y release - simulate release at (x, y)
|
||||||
|
// TEST:touch clear - clear test mode
|
||||||
|
bool DisplayDriverTFT::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
||||||
|
if(!Serial.available())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(Serial.peek() != 'T') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = Serial.readStringUntil('\n');
|
||||||
|
cmd.trim();
|
||||||
|
|
||||||
|
if(!cmd.startsWith("TEST:touch"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int firstSpace = cmd.indexOf(' ');
|
||||||
|
if(firstSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String args = cmd.substring(firstSpace + 1);
|
||||||
|
args.trim();
|
||||||
|
|
||||||
|
if(args.equals("clear")) {
|
||||||
|
_testMode = false;
|
||||||
|
Serial.println("[TEST] Test mode cleared");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secondSpace = args.indexOf(' ');
|
||||||
|
if(secondSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String xStr = args.substring(0, secondSpace);
|
||||||
|
String yState = args.substring(secondSpace + 1);
|
||||||
|
yState.trim();
|
||||||
|
|
||||||
|
int x = xStr.toInt();
|
||||||
|
int y = yState.substring(0, yState.indexOf(' ')).toInt();
|
||||||
|
String state = yState.substring(yState.indexOf(' ') + 1);
|
||||||
|
state.trim();
|
||||||
|
|
||||||
|
bool pressed = state.equals("press");
|
||||||
|
|
||||||
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
|
if(outX)
|
||||||
|
*outX = x;
|
||||||
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
|
_testMode = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::begin() {
|
||||||
|
// Backlight
|
||||||
|
pinMode(PIN_LCD_BL, OUTPUT);
|
||||||
|
digitalWrite(PIN_LCD_BL, LOW);
|
||||||
|
|
||||||
|
_tft.init();
|
||||||
|
_tft.setRotation(DISPLAY_ROTATION);
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
Serial.printf("[GFX] Display OK: const %dx%d, tft %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT,
|
||||||
|
_tft.width(), _tft.height());
|
||||||
|
Serial.flush();
|
||||||
|
|
||||||
|
// Debug: check if touch controller is responding
|
||||||
|
uint16_t z = _tft.getTouchRawZ();
|
||||||
|
Serial.printf("[TOUCH] Raw Z=%d (non-zero = controller detected)\n", z);
|
||||||
|
Serial.flush();
|
||||||
|
|
||||||
|
ScreenState st;
|
||||||
|
st.screen = ScreenID::BOOT;
|
||||||
|
st.bootStage = BootStage::SPLASH;
|
||||||
|
drawBoot(st);
|
||||||
|
|
||||||
|
digitalWrite(PIN_LCD_BL, HIGH);
|
||||||
|
Serial.println("[GFX] Backlight ON");
|
||||||
|
Serial.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBacklight(bool on) { digitalWrite(PIN_LCD_BL, on ? HIGH : LOW); }
|
||||||
|
|
||||||
|
// ── Rendering ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DisplayDriverTFT::render(const ScreenState& st) {
|
||||||
|
if(st.screen != _lastScreen
|
||||||
|
|| (st.screen == ScreenID::BOOT && st.bootStage != _lastBootStage)) {
|
||||||
|
_needsRedraw = true;
|
||||||
|
_lastScreen = st.screen;
|
||||||
|
_lastBootStage = st.bootStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(st.screen) {
|
||||||
|
case ScreenID::BOOT:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawBoot(st);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenID::ALERT:
|
||||||
|
drawAlert(st);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::DASHBOARD:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawDashboard(st);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenID::STATUS:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawStatus(st);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenID::OFF:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawBoot(const ScreenState& st) {
|
||||||
|
BootStage stage = st.bootStage;
|
||||||
|
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
|
||||||
|
setTitleFont();
|
||||||
|
_tft.setCursor(10, 28); // y=28 baseline accounts for ~18px font height above baseline
|
||||||
|
_tft.print("KLUBHAUS");
|
||||||
|
|
||||||
|
setBodyFont();
|
||||||
|
_tft.setCursor(10, 55); // y adjusted for ~12px font
|
||||||
|
_tft.print(BOARD_NAME);
|
||||||
|
|
||||||
|
// Show boot stage status
|
||||||
|
setLabelFont();
|
||||||
|
_tft.setCursor(10, 85); // y adjusted for ~9px label font
|
||||||
|
switch(stage) {
|
||||||
|
case BootStage::SPLASH:
|
||||||
|
_tft.print("Initializing...");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_DISPLAY:
|
||||||
|
_tft.print("Display OK");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_NETWORK:
|
||||||
|
_tft.print("Network init...");
|
||||||
|
break;
|
||||||
|
case BootStage::CONNECTING_WIFI:
|
||||||
|
_tft.print("Connecting WiFi...");
|
||||||
|
break;
|
||||||
|
case BootStage::READY:
|
||||||
|
_tft.print("All systems go!");
|
||||||
|
break;
|
||||||
|
case BootStage::DONE:
|
||||||
|
_tft.print("Ready!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawAlert(const ScreenState& st) {
|
||||||
|
// Static 2-color pulse - alternate every ~2 seconds
|
||||||
|
uint32_t elapsed = millis() - st.alertStartMs;
|
||||||
|
bool brightPhase = (elapsed / 2000) % 2 == 0;
|
||||||
|
|
||||||
|
// Redraw when phase changes OR when touch was released (to clear fill)
|
||||||
|
bool needsRedraw = (brightPhase != _lastAlertPhase) || _alertNeedsRedraw;
|
||||||
|
|
||||||
|
if(needsRedraw) {
|
||||||
|
_lastAlertPhase = brightPhase;
|
||||||
|
_alertNeedsRedraw = false; // clear the flag
|
||||||
|
uint16_t bg = brightPhase ? TFT_RED : _tft.color565(180, 0, 0);
|
||||||
|
|
||||||
|
_tft.fillScreen(bg);
|
||||||
|
_tft.setTextColor(TFT_WHITE, bg);
|
||||||
|
|
||||||
|
setTitleFont();
|
||||||
|
_tft.setCursor(10, 28);
|
||||||
|
_tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT");
|
||||||
|
|
||||||
|
setBodyFont();
|
||||||
|
_tft.setCursor(10, 70);
|
||||||
|
_tft.print(st.alertBody);
|
||||||
|
|
||||||
|
setLabelFont();
|
||||||
|
_tft.setCursor(10, _tft.height() - 10);
|
||||||
|
_tft.print("Hold to silence...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progressive fill hint - while touch is held
|
||||||
|
if(_alertTouchDown) {
|
||||||
|
uint32_t touchElapsed = millis() - _alertTouchStartMs;
|
||||||
|
float progress = (float)touchElapsed / (float)ALERT_FILL_DURATION_MS;
|
||||||
|
if(progress > 1.0f)
|
||||||
|
progress = 1.0f;
|
||||||
|
|
||||||
|
int dispH = _tft.height();
|
||||||
|
int fillHeight = (int)(dispH * progress);
|
||||||
|
if(fillHeight > 0) {
|
||||||
|
uint16_t overlay = _tft.color565(80, 0, 0);
|
||||||
|
_tft.fillRect(0, dispH - fillHeight, _tft.width(), fillHeight, overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
// Use actual display dimensions (after rotation)
|
||||||
|
int dispW = _tft.width();
|
||||||
|
int dispH = _tft.height();
|
||||||
|
|
||||||
|
// Header - using standard bitmap font for reliable positioning
|
||||||
|
_tft.fillRect(0, 0, dispW, STYLE_HEADER_HEIGHT, 0x1A1A); // Dark gray header
|
||||||
|
_tft.setTextSize(1);
|
||||||
|
_tft.setTextColor(TFT_WHITE);
|
||||||
|
_tft.setCursor(5, 20); // y=28 is baseline, text sits above this
|
||||||
|
_tft.print("KLUBHAUS");
|
||||||
|
|
||||||
|
// WiFi indicator - right aligned in header
|
||||||
|
const char* wifiText = st.wifiSsid.length() > 0 ? "WiFi:ON" : "WiFi:OFF";
|
||||||
|
int wifiW = _tft.textWidth(wifiText);
|
||||||
|
_tft.setCursor(dispW - wifiW - 10, 20);
|
||||||
|
_tft.print(wifiText);
|
||||||
|
|
||||||
|
// Calculate tile layouts using TileLayoutHelper
|
||||||
|
TileLayout layouts[DASHBOARD_TILE_COUNT];
|
||||||
|
int gridCols, gridRows;
|
||||||
|
int tileCount = TileLayoutHelper::calculateLayouts(
|
||||||
|
dispW, dispH, STYLE_HEADER_HEIGHT, STYLE_TILE_GAP, layouts, &gridCols, &gridRows);
|
||||||
|
|
||||||
|
const char* tileLabels[] = { "Alert", "Silent", "Status", "Reboot" };
|
||||||
|
const uint16_t tileColors[] = { 0x0280, 0x0400, 0x0440, 0x0100 };
|
||||||
|
|
||||||
|
for(int i = 0; i < tileCount && i < 4; i++) {
|
||||||
|
const TileLayout& lay = layouts[i];
|
||||||
|
int x = lay.x;
|
||||||
|
int y = lay.y;
|
||||||
|
int w = lay.w;
|
||||||
|
int h = lay.h;
|
||||||
|
|
||||||
|
// Tile background
|
||||||
|
_tft.fillRoundRect(x, y, w, h, 8, tileColors[i]);
|
||||||
|
|
||||||
|
// Tile border
|
||||||
|
_tft.drawRoundRect(x, y, w, h, 8, TFT_WHITE);
|
||||||
|
|
||||||
|
// Tile label
|
||||||
|
setBodyFont();
|
||||||
|
_tft.setTextColor(TFT_WHITE);
|
||||||
|
int textLen = strlen(tileLabels[i]);
|
||||||
|
int textW = textLen * 12;
|
||||||
|
_tft.setCursor(x + w / 2 - textW / 2, y + h / 2 - 10);
|
||||||
|
_tft.print(tileLabels[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawStatus(const ScreenState& st) {
|
||||||
|
int dispW = _tft.width();
|
||||||
|
int dispH = _tft.height();
|
||||||
|
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
_tft.fillRect(0, 0, dispW, STYLE_HEADER_HEIGHT, 0x1A1A);
|
||||||
|
_tft.setTextSize(1);
|
||||||
|
_tft.setTextColor(TFT_WHITE);
|
||||||
|
_tft.setCursor(5, 20);
|
||||||
|
_tft.print("STATUS");
|
||||||
|
|
||||||
|
// Back button in lower right
|
||||||
|
_tft.setCursor(dispW - 60, dispH - 20);
|
||||||
|
_tft.print("[BACK]");
|
||||||
|
|
||||||
|
// Status info
|
||||||
|
setBodyFont();
|
||||||
|
int y = STYLE_HEADER_HEIGHT + 20;
|
||||||
|
|
||||||
|
// WiFi
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("WiFi: %s", st.wifiSsid.length() > 0 ? st.wifiSsid.c_str() : "N/A");
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("RSSI: %d dBm", st.wifiRssi);
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("IP: %s", st.ipAddr.length() > 0 ? st.ipAddr.c_str() : "N/A");
|
||||||
|
y += 30;
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
uint32_t upSec = st.uptimeMs / 1000;
|
||||||
|
uint32_t upMin = upSec / 60;
|
||||||
|
uint32_t upHr = upMin / 60;
|
||||||
|
upSec = upSec % 60;
|
||||||
|
upMin = upMin % 60;
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("Uptime: %02lu:%02lu:%02lu", upHr, upMin, upSec);
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Heap
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("Heap: %d bytes", ESP.getFreeHeap());
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Last poll
|
||||||
|
uint32_t pollAgo = (millis() - st.lastPollMs) / 1000;
|
||||||
|
_tft.setCursor(10, y);
|
||||||
|
_tft.printf("Last poll: %lu sec ago", pollAgo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Touch ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
TouchEvent DisplayDriverTFT::readTouch() {
|
||||||
|
TouchEvent evt;
|
||||||
|
|
||||||
|
// Check for test injection via serial
|
||||||
|
int testX, testY;
|
||||||
|
bool testPressed;
|
||||||
|
if(parseTestTouch(&testX, &testY, &testPressed)) {
|
||||||
|
if(testPressed && !_touchWasPressed) {
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = testX;
|
||||||
|
_touchDownY = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!testPressed && _touchWasPressed) {
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(testPressed) {
|
||||||
|
evt.x = testX;
|
||||||
|
evt.y = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchWasPressed = testPressed;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tx, ty;
|
||||||
|
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
||||||
|
|
||||||
|
// Debug: log touch state changes with transformed coords
|
||||||
|
if(touched != _touchWasPressed) {
|
||||||
|
int tx_form = tx, ty_form = ty;
|
||||||
|
transformTouch(&tx_form, &ty_form);
|
||||||
|
Serial.printf("[TOUCH] raw touched=%d wasPressed=%d (raw=%d,%d trans=%d,%d)\n", touched,
|
||||||
|
_touchWasPressed, tx, ty, tx_form, ty_form);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect transitions (press/release)
|
||||||
|
if(touched && !_touchWasPressed) {
|
||||||
|
// Press transition: finger just touched down
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = tx;
|
||||||
|
_touchDownY = ty;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!touched && _touchWasPressed) {
|
||||||
|
// Release transition: finger just lifted
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current position if still touched
|
||||||
|
if(touched) {
|
||||||
|
evt.x = tx;
|
||||||
|
evt.y = ty;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track previous state for next call
|
||||||
|
_touchWasPressed = touched;
|
||||||
|
|
||||||
|
// Track alert touch for progressive hint
|
||||||
|
if(evt.pressed) {
|
||||||
|
_alertTouchDown = true;
|
||||||
|
_alertTouchStartMs = millis();
|
||||||
|
} else if(evt.released) {
|
||||||
|
_alertTouchDown = false;
|
||||||
|
_alertNeedsRedraw = true; // force redraw to clear fill
|
||||||
|
}
|
||||||
|
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::transformTouch(int* x, int* y) {
|
||||||
|
// Resistive touch panel is rotated 90° vs display - swap and adjust
|
||||||
|
// Touch panel: 320x480 (portrait), Display: 480x320 (landscape)
|
||||||
|
// This was the original working transform
|
||||||
|
int temp = *x;
|
||||||
|
*x = *y;
|
||||||
|
*y = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) {
|
||||||
|
HoldState h;
|
||||||
|
|
||||||
|
if(evt.pressed) {
|
||||||
|
if(!_holdActive) {
|
||||||
|
_holdActive = true;
|
||||||
|
_holdStartMs = millis();
|
||||||
|
h.started = true;
|
||||||
|
}
|
||||||
|
uint32_t held = millis() - _holdStartMs;
|
||||||
|
h.active = true;
|
||||||
|
h.progress = constrain((float)held / (float)holdMs, 0.0f, 1.0f);
|
||||||
|
h.completed = (held >= holdMs);
|
||||||
|
|
||||||
|
// Simple progress bar at bottom of screen
|
||||||
|
int dispW = _tft.width();
|
||||||
|
int dispH = _tft.height();
|
||||||
|
int barW = (int)(dispW * h.progress);
|
||||||
|
_tft.fillRect(0, dispH - 8, barW, 8, TFT_WHITE);
|
||||||
|
_tft.fillRect(barW, dispH - 8, dispW - barW, 8, TFT_DARKGREY);
|
||||||
|
} else {
|
||||||
|
if(_holdActive) {
|
||||||
|
// Clear the progress bar when released
|
||||||
|
_tft.fillRect(0, _tft.height() - 8, _tft.width(), 8, TFT_DARKGREY);
|
||||||
|
}
|
||||||
|
_holdActive = false;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_config.h"
|
||||||
|
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
class DisplayDriverTFT : public IDisplayDriver {
|
||||||
|
public:
|
||||||
|
void begin() override;
|
||||||
|
void setBacklight(bool on) override;
|
||||||
|
void render(const ScreenState& state) override;
|
||||||
|
TouchEvent readTouch() override;
|
||||||
|
HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
|
||||||
|
int width() override {
|
||||||
|
// Use TFT_eSPI's dimensions after rotation - it's more reliable
|
||||||
|
return _tft.width();
|
||||||
|
}
|
||||||
|
int height() override { return _tft.height(); }
|
||||||
|
|
||||||
|
// Dashboard - uses transform for touch coordinate correction
|
||||||
|
void transformTouch(int* x, int* y) override;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawBoot(const ScreenState& st);
|
||||||
|
void drawAlert(const ScreenState& st);
|
||||||
|
void drawDashboard(const ScreenState& st);
|
||||||
|
void drawStatus(const ScreenState& st);
|
||||||
|
|
||||||
|
TFT_eSPI _tft;
|
||||||
|
|
||||||
|
bool _holdActive = false;
|
||||||
|
uint32_t _holdStartMs = 0;
|
||||||
|
ScreenID _lastScreen = ScreenID::BOOT;
|
||||||
|
BootStage _lastBootStage = BootStage::SPLASH;
|
||||||
|
bool _needsRedraw = true;
|
||||||
|
|
||||||
|
// Touch hint for alert - progressive fill from bottom
|
||||||
|
bool _alertTouchDown = false;
|
||||||
|
bool _alertNeedsRedraw = false; // force redraw after touch release
|
||||||
|
uint32_t _alertTouchStartMs = 0;
|
||||||
|
bool _lastAlertPhase = false; // tracks bright/dark phase for 2-color alert
|
||||||
|
static constexpr uint32_t ALERT_FILL_DURATION_MS = 3000;
|
||||||
|
|
||||||
|
// Touch tracking for press/release detection
|
||||||
|
bool _touchWasPressed = false;
|
||||||
|
int _touchDownX = -1;
|
||||||
|
int _touchDownY = -1;
|
||||||
|
|
||||||
|
// Test mode for touch injection
|
||||||
|
bool _testMode = false;
|
||||||
|
bool parseTestTouch(int* outX, int* outY, bool* outPressed);
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
||||||
|
PORT="/dev/ttyUSB0"
|
||||||
|
LIBS="--library ./vendor/esp32-32e-4/TFT_eSPI"
|
||||||
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLOAD_GFXFF=1"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define BOARD_NAME "esp32-32e-4"
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// Hosyond ESP32-32E 4" (320x480) with ST7796 + XPT2046
|
||||||
|
// Pin mapping from lcdwiki.com/4.0inch_ESP32-32E_Display
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
#define DISPLAY_WIDTH 320
|
||||||
|
#define DISPLAY_HEIGHT 480
|
||||||
|
#define DISPLAY_ROTATION 1 // landscape
|
||||||
|
|
||||||
|
// Backlight GPIO (HIGH = on)
|
||||||
|
#define PIN_LCD_BL 27
|
||||||
|
|
||||||
|
// Touch — XPT2046 configured in tft_user_setup.h
|
||||||
|
// Touch CS: GPIO33, Touch IRQ: GPIO36
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing - scaled for 320x480
|
||||||
|
#define STYLE_SPACING_X 6
|
||||||
|
#define STYLE_SPACING_Y 6
|
||||||
|
#define STYLE_HEADER_HEIGHT 24
|
||||||
|
#define STYLE_TILE_GAP 4
|
||||||
|
#define STYLE_TILE_PADDING 8
|
||||||
|
#define STYLE_TILE_RADIUS 4
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG TFT_BLACK
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Klubhaus Doorbell — ESP32-32E-4" target
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DisplayDriverTFT.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
|
||||||
|
// Include local secrets.h if it exists (board-specific credentials),
|
||||||
|
// otherwise KlubhausCore/src/secrets.h provides defaults.
|
||||||
|
#ifdef LOCAL_SECRETS
|
||||||
|
#include "secrets.h"
|
||||||
|
#else
|
||||||
|
#include <secrets.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
DisplayDriverTFT tftDriver;
|
||||||
|
DisplayManager display(&tftDriver);
|
||||||
|
DoorbellLogic logic(&display);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(500);
|
||||||
|
|
||||||
|
logic.begin(FW_VERSION, BOARD_NAME, wifiNetworks, wifiNetworkCount);
|
||||||
|
logic.finishBoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Read touch
|
||||||
|
TouchEvent evt = display.readTouch();
|
||||||
|
|
||||||
|
// State machine tick
|
||||||
|
logic.update();
|
||||||
|
|
||||||
|
// Render current screen
|
||||||
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
|
// Handle tap gestures
|
||||||
|
logic.handleTouch(evt);
|
||||||
|
|
||||||
|
// Handle hold-to-silence gesture
|
||||||
|
logic.updateHold(evt);
|
||||||
|
|
||||||
|
// Serial console
|
||||||
|
logic.processSerial();
|
||||||
|
|
||||||
|
// Yield to WiFi/BT stack
|
||||||
|
delay(LOOP_YIELD_MS);
|
||||||
|
}
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install TFT_eSPI for esp32-32e-4
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
VENDOR_DIR="$DIR/../../vendor/esp32-32e-4"
|
||||||
|
|
||||||
|
if [ ! -d "$VENDOR_DIR/TFT_eSPI" ]; then
|
||||||
|
echo "Cloning TFT_eSPI..."
|
||||||
|
git clone --depth 1 --branch V2.5.43 \
|
||||||
|
https://github.com/Bodmer/TFT_eSPI.git \
|
||||||
|
"$VENDOR_DIR/TFT_eSPI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$DIR/tft_user_setup.h" "$VENDOR_DIR/TFT_eSPI/User_Setup.h"
|
||||||
|
echo "[OK] TFT_eSPI vendored for esp32-32e-4"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
// Copy this file to secrets.h and fill in your credentials.
|
||||||
|
// secrets.h is gitignored.
|
||||||
|
|
||||||
|
static const WiFiCred wifiNetworks[] = {
|
||||||
|
{ "Your_SSID_1", "password1" },
|
||||||
|
{ "Your_SSID_2", "password2" },
|
||||||
|
};
|
||||||
|
static const int wifiNetworkCount = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// TFT_eSPI User_Setup for ESP32-32E-4" (Hosyond 320x480)
|
||||||
|
// This file is copied over vendor/esp32-32e-4/TFT_eSPI/User_Setup.h
|
||||||
|
// by the install-libs-32e-4 task.
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
#define USER_SETUP_ID 201
|
||||||
|
|
||||||
|
// ── Driver ──
|
||||||
|
#define ST7796_DRIVER
|
||||||
|
|
||||||
|
// ── Resolution ──
|
||||||
|
#define TFT_WIDTH 320
|
||||||
|
#define TFT_HEIGHT 480
|
||||||
|
|
||||||
|
// ── SPI Pins (from lcdwiki.com/4.0inch_ESP32-32E_Display) ──
|
||||||
|
#define TFT_MISO 12
|
||||||
|
#define TFT_MOSI 13
|
||||||
|
#define TFT_SCLK 14
|
||||||
|
#define TFT_CS 15
|
||||||
|
#define TFT_DC 2
|
||||||
|
#define TFT_RST -1 // tied to EN, handled by board
|
||||||
|
|
||||||
|
// ── Backlight ──
|
||||||
|
#define TFT_BL 27
|
||||||
|
#define TFT_BACKLIGHT_ON HIGH
|
||||||
|
|
||||||
|
// ── Touch (XPT2046 resistive) ──
|
||||||
|
#define TOUCH_CS 33
|
||||||
|
#define TOUCH_IRQ 36
|
||||||
|
|
||||||
|
// ── SPI speed ──
|
||||||
|
#define SPI_FREQUENCY 40000000
|
||||||
|
#define SPI_READ_FREQUENCY 20000000
|
||||||
|
#define SPI_TOUCH_FREQUENCY 2500000
|
||||||
|
|
||||||
|
// ── Misc ──
|
||||||
|
#define LOAD_GLCD
|
||||||
|
#define LOAD_FONT2
|
||||||
|
#define LOAD_FONT4
|
||||||
|
#define LOAD_FONT6
|
||||||
|
#define LOAD_FONT7
|
||||||
|
#define LOAD_FONT8
|
||||||
|
#define LOAD_GFXFF
|
||||||
|
#define SMOOTH_FONT
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
#include "DisplayDriverTFT.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
extern DisplayManager display;
|
||||||
|
|
||||||
|
// ── Fonts ───────────────────────────────────────────────────
|
||||||
|
// TFT_eSPI built-in fonts for 320x240 display (further scaled)
|
||||||
|
// Using FreeFonts for better readability
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setTitleFont() { _tft.setFreeFont(&FreeSansBold12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBodyFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setLabelFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setDefaultFont() { _tft.setTextFont(2); }
|
||||||
|
|
||||||
|
// ── Test harness ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
// Commands:
|
||||||
|
// TEST:touch x y press - simulate press at (x, y)
|
||||||
|
// TEST:touch x y release - simulate release at (x, y)
|
||||||
|
// TEST:touch clear - clear test mode
|
||||||
|
bool DisplayDriverTFT::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
||||||
|
if(!Serial.available())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(Serial.peek() != 'T') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = Serial.readStringUntil('\n');
|
||||||
|
cmd.trim();
|
||||||
|
|
||||||
|
if(!cmd.startsWith("TEST:touch"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int firstSpace = cmd.indexOf(' ');
|
||||||
|
if(firstSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String args = cmd.substring(firstSpace + 1);
|
||||||
|
args.trim();
|
||||||
|
|
||||||
|
if(args.equals("clear")) {
|
||||||
|
_testMode = false;
|
||||||
|
Serial.println("[TEST] Test mode cleared");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secondSpace = args.indexOf(' ');
|
||||||
|
if(secondSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String xStr = args.substring(0, secondSpace);
|
||||||
|
String yState = args.substring(secondSpace + 1);
|
||||||
|
yState.trim();
|
||||||
|
|
||||||
|
int x = xStr.toInt();
|
||||||
|
int y = yState.substring(0, yState.indexOf(' ')).toInt();
|
||||||
|
String state = yState.substring(yState.indexOf(' ') + 1);
|
||||||
|
state.trim();
|
||||||
|
|
||||||
|
bool pressed = state.equals("press");
|
||||||
|
|
||||||
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
|
if(outX)
|
||||||
|
*outX = x;
|
||||||
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
|
_testMode = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::begin() {
|
||||||
|
// Backlight
|
||||||
|
pinMode(PIN_LCD_BL, OUTPUT);
|
||||||
|
digitalWrite(PIN_LCD_BL, LOW);
|
||||||
|
|
||||||
|
_tft.init();
|
||||||
|
_tft.setRotation(DISPLAY_ROTATION);
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
|
Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||||
|
|
||||||
|
ScreenState st;
|
||||||
|
st.screen = ScreenID::BOOT;
|
||||||
|
st.bootStage = BootStage::SPLASH;
|
||||||
|
drawBoot(st);
|
||||||
|
|
||||||
|
digitalWrite(PIN_LCD_BL, HIGH);
|
||||||
|
Serial.println("[GFX] Backlight ON");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBacklight(bool on) { digitalWrite(PIN_LCD_BL, on ? HIGH : LOW); }
|
||||||
|
|
||||||
|
// ── Rendering ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DisplayDriverTFT::render(const ScreenState& st) {
|
||||||
|
if(st.screen != _lastScreen
|
||||||
|
|| (st.screen == ScreenID::BOOT && st.bootStage != _lastBootStage)) {
|
||||||
|
_needsRedraw = true;
|
||||||
|
_lastScreen = st.screen;
|
||||||
|
_lastBootStage = st.bootStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(st.screen) {
|
||||||
|
case ScreenID::BOOT:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawBoot(st);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenID::ALERT:
|
||||||
|
drawAlert(st);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::DASHBOARD:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawDashboard(st);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScreenID::OFF:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawBoot(const ScreenState& st) {
|
||||||
|
BootStage stage = st.bootStage;
|
||||||
|
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
_tft.setTextSize(2);
|
||||||
|
_tft.setCursor(10, 10);
|
||||||
|
_tft.printf("KLUBHAUS v%s", FW_VERSION);
|
||||||
|
_tft.setTextSize(1);
|
||||||
|
_tft.setCursor(10, 40);
|
||||||
|
_tft.print(BOARD_NAME);
|
||||||
|
|
||||||
|
// Show boot stage status
|
||||||
|
_tft.setCursor(10, 70);
|
||||||
|
switch(stage) {
|
||||||
|
case BootStage::SPLASH:
|
||||||
|
_tft.print("Initializing...");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_DISPLAY:
|
||||||
|
_tft.print("Display OK");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_NETWORK:
|
||||||
|
_tft.print("Network init...");
|
||||||
|
break;
|
||||||
|
case BootStage::CONNECTING_WIFI:
|
||||||
|
_tft.print("Connecting WiFi...");
|
||||||
|
break;
|
||||||
|
case BootStage::READY:
|
||||||
|
_tft.print("All systems go!");
|
||||||
|
break;
|
||||||
|
case BootStage::DONE:
|
||||||
|
_tft.print("Ready!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawAlert(const ScreenState& st) {
|
||||||
|
uint32_t elapsed = millis() - st.alertStartMs;
|
||||||
|
uint8_t pulse = 180 + (uint8_t)(75.0f * sinf(elapsed / 300.0f));
|
||||||
|
uint16_t bg = _tft.color565(pulse, 0, 0);
|
||||||
|
|
||||||
|
_tft.fillScreen(bg);
|
||||||
|
_tft.setTextColor(TFT_WHITE, bg);
|
||||||
|
|
||||||
|
_tft.setTextSize(3);
|
||||||
|
_tft.setCursor(10, 20);
|
||||||
|
_tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT");
|
||||||
|
|
||||||
|
_tft.setTextSize(2);
|
||||||
|
_tft.setCursor(10, 80);
|
||||||
|
_tft.print(st.alertBody);
|
||||||
|
|
||||||
|
_tft.setTextSize(1);
|
||||||
|
_tft.setCursor(10, DISPLAY_HEIGHT - 20);
|
||||||
|
_tft.print("Hold to silence...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
||||||
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
|
||||||
|
_tft.setTextSize(1);
|
||||||
|
_tft.setCursor(5, 10);
|
||||||
|
_tft.printf("KLUBHAUS — %s", deviceStateStr(st.deviceState));
|
||||||
|
|
||||||
|
int y = 30;
|
||||||
|
_tft.setCursor(5, y);
|
||||||
|
y += 18;
|
||||||
|
_tft.printf("WiFi: %s %ddBm", st.wifiSsid.c_str(), st.wifiRssi);
|
||||||
|
|
||||||
|
_tft.setCursor(5, y);
|
||||||
|
y += 18;
|
||||||
|
_tft.printf("IP: %s", st.ipAddr.c_str());
|
||||||
|
|
||||||
|
_tft.setCursor(5, y);
|
||||||
|
y += 18;
|
||||||
|
_tft.printf("Up: %lus Heap: %d", st.uptimeMs / 1000, ESP.getFreeHeap());
|
||||||
|
|
||||||
|
_tft.setCursor(5, y);
|
||||||
|
y += 18;
|
||||||
|
_tft.printf("Last poll: %lus ago", st.lastPollMs > 0 ? (millis() - st.lastPollMs) / 1000 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Touch ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
TouchEvent DisplayDriverTFT::readTouch() {
|
||||||
|
TouchEvent evt;
|
||||||
|
|
||||||
|
// Check for test injection via serial
|
||||||
|
int testX, testY;
|
||||||
|
bool testPressed;
|
||||||
|
if(parseTestTouch(&testX, &testY, &testPressed)) {
|
||||||
|
if(testPressed && !_touchWasPressed) {
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = testX;
|
||||||
|
_touchDownY = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!testPressed && _touchWasPressed) {
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(testPressed) {
|
||||||
|
evt.x = testX;
|
||||||
|
evt.y = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchWasPressed = testPressed;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tx, ty;
|
||||||
|
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
||||||
|
|
||||||
|
// Detect transitions (press/release)
|
||||||
|
if(touched && !_touchWasPressed) {
|
||||||
|
// Press transition: finger just touched down
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = tx;
|
||||||
|
_touchDownY = ty;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!touched && _touchWasPressed) {
|
||||||
|
// Release transition: finger just lifted
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current position if still touched
|
||||||
|
if(touched) {
|
||||||
|
evt.x = tx;
|
||||||
|
evt.y = ty;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track previous state for next call
|
||||||
|
_touchWasPressed = touched;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DisplayDriverTFT::dashboardTouch(int x, int y) {
|
||||||
|
// 2x2 grid, accounting for 30px header
|
||||||
|
if(y < 30)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int col = (x * 2) / DISPLAY_WIDTH; // 0 or 1
|
||||||
|
int row = ((y - 30) * 2) / (DISPLAY_HEIGHT - 30); // 0 or 1
|
||||||
|
|
||||||
|
if(col < 0 || col > 1 || row < 0 || row > 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return row * 2 + col; // 0, 1, 2, or 3
|
||||||
|
}
|
||||||
|
|
||||||
|
HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) {
|
||||||
|
HoldState h;
|
||||||
|
|
||||||
|
if(evt.pressed) {
|
||||||
|
if(!_holdActive) {
|
||||||
|
_holdActive = true;
|
||||||
|
_holdStartMs = millis();
|
||||||
|
h.started = true;
|
||||||
|
}
|
||||||
|
uint32_t held = millis() - _holdStartMs;
|
||||||
|
h.active = true;
|
||||||
|
h.progress = constrain((float)held / (float)holdMs, 0.0f, 1.0f);
|
||||||
|
h.completed = (held >= holdMs);
|
||||||
|
|
||||||
|
// Simple progress bar at bottom of screen
|
||||||
|
int barW = (int)(DISPLAY_WIDTH * h.progress);
|
||||||
|
_tft.fillRect(0, DISPLAY_HEIGHT - 8, barW, 8, TFT_WHITE);
|
||||||
|
_tft.fillRect(barW, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH - barW, 8, TFT_DARKGREY);
|
||||||
|
} else {
|
||||||
|
if(_holdActive) {
|
||||||
|
// Clear the progress bar when released
|
||||||
|
_tft.fillRect(0, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH, 8, TFT_DARKGREY);
|
||||||
|
}
|
||||||
|
_holdActive = false;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverTFT::drawDebugTouch(int x, int y) {
|
||||||
|
const int size = 20;
|
||||||
|
_tft.drawLine(x - size, y, x + size, y, TFT_RED);
|
||||||
|
_tft.drawLine(x, y - size, x, y + size, TFT_RED);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_config.h"
|
||||||
|
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
class DisplayDriverTFT : public IDisplayDriver {
|
||||||
|
public:
|
||||||
|
void begin() override;
|
||||||
|
void setBacklight(bool on) override;
|
||||||
|
void render(const ScreenState& state) override;
|
||||||
|
TouchEvent readTouch() override;
|
||||||
|
int dashboardTouch(int x, int y);
|
||||||
|
HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
|
||||||
|
int width() override { return DISPLAY_WIDTH; }
|
||||||
|
int height() override { return DISPLAY_HEIGHT; }
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
void drawDebugTouch(int x, int y) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawBoot(const ScreenState& st);
|
||||||
|
void drawAlert(const ScreenState& st);
|
||||||
|
void drawDashboard(const ScreenState& st);
|
||||||
|
|
||||||
|
TFT_eSPI _tft;
|
||||||
|
|
||||||
|
bool _holdActive = false;
|
||||||
|
uint32_t _holdStartMs = 0;
|
||||||
|
ScreenID _lastScreen = ScreenID::BOOT;
|
||||||
|
BootStage _lastBootStage = BootStage::SPLASH;
|
||||||
|
bool _needsRedraw = true;
|
||||||
|
|
||||||
|
// Touch tracking for press/release detection
|
||||||
|
bool _touchWasPressed = false;
|
||||||
|
int _touchDownX = -1;
|
||||||
|
int _touchDownY = -1;
|
||||||
|
|
||||||
|
// Test mode for touch injection
|
||||||
|
bool _testMode = false;
|
||||||
|
bool parseTestTouch(int* outX, int* outY, bool* outPressed);
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
||||||
|
PORT="/dev/ttyUSB0"
|
||||||
|
LIBS="--library ./vendor/esp32-32e/TFT_eSPI"
|
||||||
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLOCAL_SECRETS -DLOAD_GFXFF=1"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define BOARD_NAME "esp32-32e"
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
// TODO: Set these to match YOUR display + wiring.
|
||||||
|
// Defaults below are for a common ILI9341 320×240 SPI TFT.
|
||||||
|
// The actual pin mapping must also be set in tft_user_setup.h
|
||||||
|
// (which gets copied into the vendored TFT_eSPI library).
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
#define DISPLAY_WIDTH 320
|
||||||
|
#define DISPLAY_HEIGHT 240
|
||||||
|
#define DISPLAY_ROTATION 1 // landscape
|
||||||
|
|
||||||
|
// Backlight GPIO (directly wired)
|
||||||
|
#define PIN_LCD_BL 22
|
||||||
|
|
||||||
|
// Touch — if using XPT2046 via TFT_eSPI, set TOUCH_CS in tft_user_setup.h
|
||||||
|
// If using capacitive touch (e.g. FT6236), configure I2C pins here:
|
||||||
|
// #define TOUCH_SDA 21
|
||||||
|
// #define TOUCH_SCL 22
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing - scaled for 320x240
|
||||||
|
#define STYLE_SPACING_X 4
|
||||||
|
#define STYLE_SPACING_Y 4
|
||||||
|
#define STYLE_HEADER_HEIGHT 20
|
||||||
|
#define STYLE_TILE_GAP 4
|
||||||
|
#define STYLE_TILE_PADDING 6
|
||||||
|
#define STYLE_TILE_RADIUS 4
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG TFT_BLACK
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Klubhaus Doorbell — ESP32-32E target
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DisplayDriverTFT.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
|
||||||
|
// Include local secrets.h if it exists (board-specific credentials),
|
||||||
|
// otherwise KlubhausCore/src/secrets.h provides defaults.
|
||||||
|
#ifdef LOCAL_SECRETS
|
||||||
|
#include "secrets.h"
|
||||||
|
#else
|
||||||
|
#include <secrets.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
DisplayDriverTFT tftDriver;
|
||||||
|
DisplayManager display(&tftDriver);
|
||||||
|
DoorbellLogic logic(&display);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(500);
|
||||||
|
|
||||||
|
logic.begin(FW_VERSION, BOARD_NAME, wifiNetworks, wifiNetworkCount);
|
||||||
|
logic.finishBoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Read touch
|
||||||
|
TouchEvent evt = display.readTouch();
|
||||||
|
|
||||||
|
// State machine tick
|
||||||
|
logic.update();
|
||||||
|
|
||||||
|
// Render current screen
|
||||||
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
|
// Handle tap gestures
|
||||||
|
logic.handleTouch(evt);
|
||||||
|
|
||||||
|
// Handle hold-to-silence gesture
|
||||||
|
logic.updateHold(evt);
|
||||||
|
|
||||||
|
// Serial console
|
||||||
|
logic.processSerial();
|
||||||
|
|
||||||
|
// Yield to WiFi/BT stack
|
||||||
|
delay(LOOP_YIELD_MS);
|
||||||
|
}
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install TFT_eSPI for esp32-32e
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
VENDOR_DIR="$DIR/../../vendor/esp32-32e"
|
||||||
|
|
||||||
|
if [ ! -d "$VENDOR_DIR/TFT_eSPI" ]; then
|
||||||
|
echo "Cloning TFT_eSPI..."
|
||||||
|
git clone --depth 1 --branch V2.5.43 \
|
||||||
|
https://github.com/Bodmer/TFT_eSPI.git \
|
||||||
|
"$VENDOR_DIR/TFT_eSPI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$DIR/tft_user_setup.h" "$VENDOR_DIR/TFT_eSPI/User_Setup.h"
|
||||||
|
echo "[OK] TFT_eSPI vendored for esp32-32e"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
// Copy this file to secrets.h and fill in your credentials.
|
||||||
|
// secrets.h is gitignored.
|
||||||
|
|
||||||
|
static const WiFiCred wifiNetworks[] = {
|
||||||
|
{ "Your_SSID_1", "password1" },
|
||||||
|
{ "Your_SSID_2", "password2" },
|
||||||
|
};
|
||||||
|
static const int wifiNetworkCount = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]);
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// TFT_eSPI User_Setup for ESP32-32E target
|
||||||
|
// This file is copied over vendor/esp32-32e/TFT_eSPI/User_Setup.h
|
||||||
|
// by the install-libs-32e task.
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
#define USER_SETUP_ID 200
|
||||||
|
|
||||||
|
// ── Driver ──
|
||||||
|
#define ILI9341_DRIVER
|
||||||
|
|
||||||
|
// ── Resolution ──
|
||||||
|
// FIXED: Match board_config.h (320x240 landscape)
|
||||||
|
#define TFT_WIDTH 320
|
||||||
|
#define TFT_HEIGHT 240
|
||||||
|
|
||||||
|
// ── SPI Pins ──
|
||||||
|
#define TFT_MOSI 23
|
||||||
|
#define TFT_SCLK 18
|
||||||
|
#define TFT_CS 5
|
||||||
|
#define TFT_DC 27
|
||||||
|
#define TFT_RST 33
|
||||||
|
|
||||||
|
// ── Backlight (optional, can also use GPIO directly) ──
|
||||||
|
// #define TFT_BL 22
|
||||||
|
// #define TFT_BACKLIGHT_ON HIGH
|
||||||
|
|
||||||
|
// ── Touch (XPT2046 resistive) ──
|
||||||
|
#define TOUCH_CS 14
|
||||||
|
|
||||||
|
// ── SPI speed ──
|
||||||
|
#define SPI_FREQUENCY 40000000
|
||||||
|
#define SPI_READ_FREQUENCY 20000000
|
||||||
|
#define SPI_TOUCH_FREQUENCY 2500000
|
||||||
|
|
||||||
|
// ── Misc ──
|
||||||
|
#define LOAD_GLCD
|
||||||
|
#define LOAD_FONT2
|
||||||
|
#define LOAD_FONT4
|
||||||
|
#define LOAD_FONT6
|
||||||
|
#define LOAD_FONT7
|
||||||
|
#define LOAD_FONT8
|
||||||
|
#define LOAD_GFXFF
|
||||||
|
#define SMOOTH_FONT
|
||||||
@@ -0,0 +1,586 @@
|
|||||||
|
// boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp
|
||||||
|
#include "DisplayDriverGFX.h"
|
||||||
|
|
||||||
|
#include "LovyanPins.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <Style.h>
|
||||||
|
|
||||||
|
// ── Globals ──
|
||||||
|
static LGFX* _gfx = nullptr;
|
||||||
|
|
||||||
|
// ── Forward declarations ──
|
||||||
|
static void initDisplay();
|
||||||
|
|
||||||
|
// ── Dimensions ──
|
||||||
|
static constexpr int DISP_W = 800;
|
||||||
|
static constexpr int DISP_H = 480;
|
||||||
|
|
||||||
|
// ── Display initialization ──
|
||||||
|
static void initDisplay() {
|
||||||
|
Serial.println("LovyanGFX init...");
|
||||||
|
|
||||||
|
// Note: LovyanGFX handles I2C internally (port 1 for touch, port 0 for CH422G)
|
||||||
|
// No need to call Wire.begin() or Wire1.begin()
|
||||||
|
|
||||||
|
_gfx = new LGFX();
|
||||||
|
_gfx->init();
|
||||||
|
_gfx->setRotation(0); // Landscape
|
||||||
|
_gfx->fillScreen(0x000000);
|
||||||
|
|
||||||
|
Serial.println("Display ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Singleton ──
|
||||||
|
DisplayDriverGFX& DisplayDriverGFX::instance() {
|
||||||
|
static DisplayDriverGFX inst;
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── IDisplayDriver implementation ──
|
||||||
|
|
||||||
|
void DisplayDriverGFX::begin() {
|
||||||
|
initDisplay();
|
||||||
|
// Turn on backlight immediately
|
||||||
|
setBacklight(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setBacklight(bool on) {
|
||||||
|
if(_gfx) {
|
||||||
|
// LovyanGFX handles backlight via setBrightness
|
||||||
|
_gfx->setBrightness(on ? 255 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DisplayDriverGFX::width() { return _gfx ? _gfx->width() : DISP_W; }
|
||||||
|
|
||||||
|
int DisplayDriverGFX::height() { return _gfx ? _gfx->height() : DISP_H; }
|
||||||
|
|
||||||
|
// ── Fonts ──
|
||||||
|
// LovyanGFX built-in fonts for 800x480 display
|
||||||
|
void DisplayDriverGFX::setTitleFont() { _gfx->setFont(&fonts::FreeSansBold24pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setBodyFont() { _gfx->setFont(&fonts::FreeSans18pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setLabelFont() { _gfx->setFont(&fonts::FreeSans12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setDefaultFont() { _gfx->setFont(&fonts::Font2); }
|
||||||
|
|
||||||
|
// Transform touch coordinates to match display orientation
|
||||||
|
// GT911 touch panel on this board is rotated 180° relative to display
|
||||||
|
void DisplayDriverGFX::transformTouch(int* x, int* y) {
|
||||||
|
if(!x || !y)
|
||||||
|
return;
|
||||||
|
// Flip both axes: (0,0) becomes (width, height)
|
||||||
|
*x = DISP_W - *x;
|
||||||
|
*y = DISP_H - *y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
// Commands:
|
||||||
|
// TEST:touch x y press - simulate press at (x, y) [raw panel coords]
|
||||||
|
// TEST:touch x y release - simulate release at (x, y)
|
||||||
|
// TEST:touch clear - clear test mode
|
||||||
|
bool DisplayDriverGFX::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
||||||
|
if(!Serial.available())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Only consume if it starts with 'T' - don't steal other commands
|
||||||
|
// Use "TEST:" prefix to avoid conflict with [CMD] echo
|
||||||
|
if(Serial.peek() != 'T') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = Serial.readStringUntil('\n');
|
||||||
|
cmd.trim();
|
||||||
|
|
||||||
|
if(!cmd.startsWith("TEST:touch"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Parse: touch x y press|release
|
||||||
|
int firstSpace = cmd.indexOf(' ');
|
||||||
|
if(firstSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String args = cmd.substring(firstSpace + 1);
|
||||||
|
args.trim();
|
||||||
|
|
||||||
|
if(args.equals("clear")) {
|
||||||
|
_testMode = false;
|
||||||
|
Serial.println("[TEST] Test mode cleared");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse x y state
|
||||||
|
int secondSpace = args.indexOf(' ');
|
||||||
|
if(secondSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String xStr = args.substring(0, secondSpace);
|
||||||
|
String yState = args.substring(secondSpace + 1);
|
||||||
|
yState.trim();
|
||||||
|
|
||||||
|
int x = xStr.toInt();
|
||||||
|
int y = yState.substring(0, yState.indexOf(' ')).toInt();
|
||||||
|
String state = yState.substring(yState.indexOf(' ') + 1);
|
||||||
|
state.trim();
|
||||||
|
|
||||||
|
bool pressed = state.equals("press");
|
||||||
|
|
||||||
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
|
if(outX)
|
||||||
|
*outX = x;
|
||||||
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
|
_testMode = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch handling
|
||||||
|
|
||||||
|
TouchEvent DisplayDriverGFX::readTouch() {
|
||||||
|
TouchEvent evt;
|
||||||
|
if(!_gfx)
|
||||||
|
return evt;
|
||||||
|
|
||||||
|
// Check for test injection via serial
|
||||||
|
int testX, testY;
|
||||||
|
bool testPressed;
|
||||||
|
if(parseTestTouch(&testX, &testY, &testPressed)) {
|
||||||
|
// Handle test touch with same logic as real touch
|
||||||
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
if(testPressed && !_lastTouch.pressed) {
|
||||||
|
evt.pressed = true;
|
||||||
|
evt.downX = testX;
|
||||||
|
evt.downY = testY;
|
||||||
|
_lastTouch.downX = evt.downX;
|
||||||
|
_lastTouch.downY = evt.downY;
|
||||||
|
_touchBounced = false;
|
||||||
|
} else if(!testPressed && _lastTouch.pressed) {
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _lastTouch.downX;
|
||||||
|
evt.downY = _lastTouch.downY;
|
||||||
|
_lastReleaseMs = now;
|
||||||
|
_touchBounced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(testPressed) {
|
||||||
|
evt.x = testX;
|
||||||
|
evt.y = testY;
|
||||||
|
evt.downX = _lastTouch.downX;
|
||||||
|
evt.downY = _lastTouch.downY;
|
||||||
|
_pressStartMs = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_touchBounced && now - _lastReleaseMs >= TOUCH_DEBOUNCE_MS) {
|
||||||
|
_touchBounced = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastTouch.pressed = testPressed;
|
||||||
|
if(testPressed) {
|
||||||
|
_lastTouch.x = evt.x;
|
||||||
|
_lastTouch.y = evt.y;
|
||||||
|
}
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t x, y;
|
||||||
|
bool pressed = _gfx->getTouch(&x, &y);
|
||||||
|
|
||||||
|
// Filter out invalid coordinates (touch panel can return garbage on release)
|
||||||
|
// Ignore both press and release transitions when coordinates are out of bounds
|
||||||
|
bool validCoords = !(x < 0 || x > DISP_W || y < 0 || y > DISP_H);
|
||||||
|
if(!validCoords) {
|
||||||
|
pressed = false;
|
||||||
|
// Don't update _lastTouch.pressed - keep previous state to avoid false release
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce: ignore repeated press events within debounce window after release
|
||||||
|
unsigned long now = millis();
|
||||||
|
if(pressed && _touchBounced) {
|
||||||
|
// Within debounce window - ignore this press
|
||||||
|
_lastTouch.pressed = pressed;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect transitions (press/release)
|
||||||
|
if(pressed && !_lastTouch.pressed) {
|
||||||
|
// Press transition: finger just touched down
|
||||||
|
evt.pressed = true;
|
||||||
|
evt.downX = static_cast<int>(x);
|
||||||
|
evt.downY = static_cast<int>(y);
|
||||||
|
_lastTouch.downX = evt.downX;
|
||||||
|
_lastTouch.downY = evt.downY;
|
||||||
|
_touchBounced = false;
|
||||||
|
} else if(!pressed && _lastTouch.pressed) {
|
||||||
|
// Release transition: finger just lifted
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _lastTouch.downX;
|
||||||
|
evt.downY = _lastTouch.downY;
|
||||||
|
// Start debounce window
|
||||||
|
_lastReleaseMs = now;
|
||||||
|
_touchBounced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current position if still touched
|
||||||
|
if(pressed) {
|
||||||
|
evt.x = static_cast<int>(x);
|
||||||
|
evt.y = static_cast<int>(y);
|
||||||
|
evt.downX = _lastTouch.downX;
|
||||||
|
evt.downY = _lastTouch.downY;
|
||||||
|
_pressStartMs = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if debounce window has expired
|
||||||
|
if(_touchBounced && now - _lastReleaseMs >= TOUCH_DEBOUNCE_MS) {
|
||||||
|
_touchBounced = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track previous state
|
||||||
|
_lastTouch.pressed = pressed;
|
||||||
|
if(pressed) {
|
||||||
|
_lastTouch.x = evt.x;
|
||||||
|
_lastTouch.y = evt.y;
|
||||||
|
}
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DisplayDriverGFX::dashboardTouch(int x, int y) {
|
||||||
|
// Dashboard tiles: 2 rows × 4 columns
|
||||||
|
constexpr int cols = 4;
|
||||||
|
constexpr int rows = 2;
|
||||||
|
constexpr int tileW = DISP_W / cols;
|
||||||
|
constexpr int tileH = DISP_H / rows;
|
||||||
|
|
||||||
|
if(x < 0 || x >= DISP_W || y < 0 || y >= DISP_H) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int col = x / tileW;
|
||||||
|
int row = y / tileH;
|
||||||
|
|
||||||
|
return row * cols + col;
|
||||||
|
}
|
||||||
|
|
||||||
|
HoldState DisplayDriverGFX::updateHold(const TouchEvent& evt, unsigned long holdMs) {
|
||||||
|
HoldState state;
|
||||||
|
|
||||||
|
if(!evt.pressed) {
|
||||||
|
_isHolding = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long elapsed = millis() - _pressStartMs;
|
||||||
|
|
||||||
|
if(!_isHolding) {
|
||||||
|
_isHolding = true;
|
||||||
|
state.started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.active = true;
|
||||||
|
state.progress = static_cast<float>(elapsed) / static_cast<float>(holdMs);
|
||||||
|
|
||||||
|
if(state.progress >= 1.0f) {
|
||||||
|
state.progress = 1.0f;
|
||||||
|
state.completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rendering ──
|
||||||
|
|
||||||
|
void DisplayDriverGFX::render(const ScreenState& state) {
|
||||||
|
if(!_gfx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if we need full redraw
|
||||||
|
if(state.screen != _lastScreen
|
||||||
|
|| (state.screen == ScreenID::BOOT && state.bootStage != _lastBootStage)) {
|
||||||
|
_needsRedraw = true;
|
||||||
|
_lastScreen = state.screen;
|
||||||
|
_lastBootStage = state.bootStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(state.screen) {
|
||||||
|
case ScreenID::BOOT:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawBoot(state);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::OFF:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
_gfx->fillScreen(0x000000);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::ALERT:
|
||||||
|
// Only redraw on first entry or screen change
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawAlert(state);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::DASHBOARD:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawDashboard(state);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenID::STATUS:
|
||||||
|
if(_needsRedraw) {
|
||||||
|
drawStatus(state);
|
||||||
|
_needsRedraw = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawBoot(const ScreenState& state) {
|
||||||
|
BootStage stage = state.bootStage;
|
||||||
|
|
||||||
|
_gfx->fillScreen(TFT_BLACK);
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
setTitleFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y);
|
||||||
|
_gfx->print("KLUBHAUS");
|
||||||
|
|
||||||
|
setBodyFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y + STYLE_HEADER_HEIGHT);
|
||||||
|
_gfx->print(BOARD_NAME);
|
||||||
|
|
||||||
|
// Show boot stage status
|
||||||
|
setLabelFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y + STYLE_HEADER_HEIGHT + 30);
|
||||||
|
switch(stage) {
|
||||||
|
case BootStage::SPLASH:
|
||||||
|
_gfx->print("Initializing...");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_DISPLAY:
|
||||||
|
_gfx->print("Display OK");
|
||||||
|
break;
|
||||||
|
case BootStage::INIT_NETWORK:
|
||||||
|
_gfx->print("Network init...");
|
||||||
|
break;
|
||||||
|
case BootStage::CONNECTING_WIFI:
|
||||||
|
_gfx->print("Connecting WiFi...");
|
||||||
|
break;
|
||||||
|
case BootStage::READY:
|
||||||
|
_gfx->print("All systems go!");
|
||||||
|
break;
|
||||||
|
case BootStage::DONE:
|
||||||
|
_gfx->print("Ready!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawAlert(const ScreenState& state) {
|
||||||
|
uint32_t elapsed = millis() - state.alertStartMs;
|
||||||
|
uint8_t pulse = static_cast<uint8_t>(180.0f + 75.0f * sinf(elapsed / 300.0f));
|
||||||
|
uint16_t bg = _gfx->color565(pulse, 0, 0);
|
||||||
|
|
||||||
|
_gfx->fillScreen(bg);
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG, bg);
|
||||||
|
|
||||||
|
setTitleFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_HEADER_HEIGHT);
|
||||||
|
_gfx->print(state.alertTitle.length() > 0 ? state.alertTitle.c_str() : "ALERT");
|
||||||
|
|
||||||
|
setBodyFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_HEADER_HEIGHT + 50);
|
||||||
|
_gfx->print(state.alertBody);
|
||||||
|
|
||||||
|
setLabelFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, DISP_H - STYLE_SPACING_Y);
|
||||||
|
_gfx->print("Hold to silence...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
||||||
|
_gfx->fillScreen(STYLE_COLOR_BG);
|
||||||
|
|
||||||
|
// Header - use Layout for safe positioning
|
||||||
|
_gfx->fillRect(0, 0, DISP_W, STYLE_HEADER_HEIGHT, STYLE_COLOR_HEADER);
|
||||||
|
Layout header = Layout::header(DISP_W, STYLE_HEADER_HEIGHT);
|
||||||
|
Layout safeText = header.padded(STYLE_SPACING_X);
|
||||||
|
|
||||||
|
setBodyFont();
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
|
||||||
|
// Title with scrolling support
|
||||||
|
_headerScroller.setText("KLUBHAUS");
|
||||||
|
_headerScroller.setScrollSpeed(80);
|
||||||
|
_headerScroller.setPauseDuration(2000);
|
||||||
|
_headerScroller.render(
|
||||||
|
[&](int16_t x, const char* s) {
|
||||||
|
_gfx->setCursor(safeText.x + x, safeText.y + 4);
|
||||||
|
_gfx->print(s);
|
||||||
|
},
|
||||||
|
safeText.w);
|
||||||
|
|
||||||
|
// WiFi status - right aligned with scrolling
|
||||||
|
Layout wifiArea(DISP_W - 150, 0, 140, STYLE_HEADER_HEIGHT);
|
||||||
|
Layout safeWifi = wifiArea.padded(4);
|
||||||
|
_wifiScroller.setText(state.wifiSsid.length() > 0 ? state.wifiSsid.c_str() : "WiFi: OFF");
|
||||||
|
_wifiScroller.setScrollSpeed(60);
|
||||||
|
_wifiScroller.setPauseDuration(1500);
|
||||||
|
_wifiScroller.render(
|
||||||
|
[&](int16_t x, const char* s) {
|
||||||
|
_gfx->setCursor(safeWifi.x + x, safeWifi.y + 4);
|
||||||
|
_gfx->print(s);
|
||||||
|
},
|
||||||
|
safeWifi.w);
|
||||||
|
|
||||||
|
// Calculate tile layouts using TileLayoutHelper
|
||||||
|
TileLayout layouts[DASHBOARD_TILE_COUNT];
|
||||||
|
int gridCols, gridRows;
|
||||||
|
int tileCount = TileLayoutHelper::calculateLayouts(
|
||||||
|
DISP_W, DISP_H, STYLE_HEADER_HEIGHT, STYLE_TILE_GAP, layouts, &gridCols, &gridRows);
|
||||||
|
|
||||||
|
const char* tileLabels[] = { "Alert", "Silent", "Status", "Reboot" };
|
||||||
|
const uint16_t tileColors[] = { 0x0280, 0x0400, 0x0440, 0x0100 };
|
||||||
|
|
||||||
|
for(int i = 0; i < tileCount && i < 4; i++) {
|
||||||
|
const TileLayout& lay = layouts[i];
|
||||||
|
int x = lay.x;
|
||||||
|
int y = lay.y;
|
||||||
|
int w = lay.w;
|
||||||
|
int h = lay.h;
|
||||||
|
|
||||||
|
// Tile background
|
||||||
|
_gfx->fillRoundRect(x, y, w, h, STYLE_TILE_RADIUS, tileColors[i]);
|
||||||
|
|
||||||
|
// Tile border
|
||||||
|
_gfx->drawRoundRect(x, y, w, h, STYLE_TILE_RADIUS, STYLE_COLOR_FG);
|
||||||
|
|
||||||
|
// Tile label
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
setBodyFont();
|
||||||
|
int textLen = strlen(tileLabels[i]);
|
||||||
|
int textW = textLen * 14;
|
||||||
|
_gfx->setCursor(x + w / 2 - textW / 2, y + h / 2 - 10);
|
||||||
|
_gfx->print(tileLabels[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawStatus(const ScreenState& st) {
|
||||||
|
_gfx->fillScreen(STYLE_COLOR_BG);
|
||||||
|
|
||||||
|
// Header with title and back button
|
||||||
|
_gfx->fillRect(0, 0, DISP_W, STYLE_HEADER_HEIGHT, STYLE_COLOR_HEADER);
|
||||||
|
Layout header = Layout::header(DISP_W, STYLE_HEADER_HEIGHT);
|
||||||
|
Layout safeText = header.padded(STYLE_SPACING_X);
|
||||||
|
|
||||||
|
setTitleFont();
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(safeText.x, safeText.y + 4);
|
||||||
|
_gfx->print("STATUS");
|
||||||
|
|
||||||
|
// Back button in lower-right
|
||||||
|
setLabelFont();
|
||||||
|
_gfx->setCursor(DISP_W - 60, DISP_H - 20);
|
||||||
|
_gfx->print("[BACK]");
|
||||||
|
|
||||||
|
// Status items in 2-column layout
|
||||||
|
setBodyFont();
|
||||||
|
int colWidth = DISP_W / 2;
|
||||||
|
int startY = STYLE_HEADER_HEIGHT + 30;
|
||||||
|
int rowHeight = 35;
|
||||||
|
int labelX = STYLE_SPACING_X + 10;
|
||||||
|
int valueX = STYLE_SPACING_X + 120;
|
||||||
|
|
||||||
|
// Column 1
|
||||||
|
int y = startY;
|
||||||
|
|
||||||
|
// WiFi SSID
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(labelX, y);
|
||||||
|
_gfx->print("WiFi:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(valueX, y);
|
||||||
|
_gfx->print(st.wifiSsid.length() > 0 ? st.wifiSsid.c_str() : "N/A");
|
||||||
|
|
||||||
|
// RSSI
|
||||||
|
y += rowHeight;
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(labelX, y);
|
||||||
|
_gfx->print("Signal:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(valueX, y);
|
||||||
|
_gfx->printf("%d dBm", st.wifiRssi);
|
||||||
|
|
||||||
|
// IP Address
|
||||||
|
y += rowHeight;
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(labelX, y);
|
||||||
|
_gfx->print("IP:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(valueX, y);
|
||||||
|
_gfx->print(st.ipAddr.length() > 0 ? st.ipAddr.c_str() : "N/A");
|
||||||
|
|
||||||
|
// Column 2
|
||||||
|
y = startY;
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
uint32_t upSec = st.uptimeMs / 1000;
|
||||||
|
uint32_t upMin = upSec / 60;
|
||||||
|
uint32_t upHr = upMin / 60;
|
||||||
|
upSec = upSec % 60;
|
||||||
|
upMin = upMin % 60;
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(colWidth + labelX, y);
|
||||||
|
_gfx->print("Uptime:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(colWidth + valueX, y);
|
||||||
|
_gfx->printf("%02lu:%02lu:%02lu", upHr, upMin, upSec);
|
||||||
|
|
||||||
|
// Heap
|
||||||
|
y += rowHeight;
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(colWidth + labelX, y);
|
||||||
|
_gfx->print("Heap:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(colWidth + valueX, y);
|
||||||
|
_gfx->printf("%d KB", ESP.getFreeHeap() / 1024);
|
||||||
|
|
||||||
|
// Last Poll
|
||||||
|
y += rowHeight;
|
||||||
|
_gfx->setTextColor(0x8888);
|
||||||
|
_gfx->setCursor(colWidth + labelX, y);
|
||||||
|
_gfx->print("Last Poll:");
|
||||||
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
|
_gfx->setCursor(colWidth + valueX, y);
|
||||||
|
uint32_t pollAgo = (millis() - st.lastPollMs) / 1000;
|
||||||
|
if(pollAgo < 60) {
|
||||||
|
_gfx->printf("%lu sec", pollAgo);
|
||||||
|
} else {
|
||||||
|
_gfx->printf("%lu min", pollAgo / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer with firmware version
|
||||||
|
setLabelFont();
|
||||||
|
_gfx->setTextColor(0x6666);
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, DISP_H - STYLE_SPACING_Y);
|
||||||
|
_gfx->print("v" FW_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawDebugTouch(int x, int y) {
|
||||||
|
if(!_gfx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int size = 20;
|
||||||
|
_gfx->drawLine(x - size, y, x + size, y, TFT_RED);
|
||||||
|
_gfx->drawLine(x, y - size, x, y + size, TFT_RED);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IDisplayDriver.h"
|
||||||
|
#include "Style.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class DisplayDriverGFX : public IDisplayDriver {
|
||||||
|
public:
|
||||||
|
// ── IDisplayDriver ──
|
||||||
|
void begin() override;
|
||||||
|
void setBacklight(bool on) override;
|
||||||
|
void render(const ScreenState& state) override;
|
||||||
|
|
||||||
|
TouchEvent readTouch() override;
|
||||||
|
HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
|
||||||
|
void drawDebugTouch(int x, int y) override;
|
||||||
|
|
||||||
|
int width() override;
|
||||||
|
int height() override;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
|
// Transform touch coordinates (handles rotated touch panels)
|
||||||
|
void transformTouch(int* x, int* y) override;
|
||||||
|
|
||||||
|
// Dashboard tile mapping
|
||||||
|
int dashboardTouch(int x, int y);
|
||||||
|
|
||||||
|
// ── Internal ──
|
||||||
|
static DisplayDriverGFX& instance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
bool parseTestTouch(int* outX, int* outY, bool* outPressed);
|
||||||
|
|
||||||
|
// Helper rendering functions
|
||||||
|
void drawBoot(const ScreenState& state);
|
||||||
|
void drawAlert(const ScreenState& state);
|
||||||
|
void drawDashboard(const ScreenState& state);
|
||||||
|
void drawStatus(const ScreenState& state);
|
||||||
|
|
||||||
|
// Touch handling
|
||||||
|
TouchEvent _lastTouch = { false, false, 0, 0, -1, -1 };
|
||||||
|
unsigned long _pressStartMs = 0;
|
||||||
|
bool _isHolding = false;
|
||||||
|
unsigned long _lastReleaseMs = 0;
|
||||||
|
bool _touchBounced = false;
|
||||||
|
bool _testMode = false;
|
||||||
|
|
||||||
|
// Screen tracking
|
||||||
|
ScreenID _lastScreen = ScreenID::BOOT;
|
||||||
|
BootStage _lastBootStage = BootStage::SPLASH;
|
||||||
|
bool _needsRedraw = true;
|
||||||
|
|
||||||
|
// Text scrollers for scrolling elements
|
||||||
|
TextScroller _headerScroller;
|
||||||
|
TextScroller _wifiScroller;
|
||||||
|
};
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define LGFX_USE_V1
|
||||||
|
#include <LovyanGFX.hpp>
|
||||||
|
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
||||||
|
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
||||||
|
|
||||||
|
// ── Display dimensions ──
|
||||||
|
#define TFT_HOR_RES 800
|
||||||
|
#define TFT_VER_RES 480
|
||||||
|
|
||||||
|
// ── Touch I2C (from Westcott example) ──
|
||||||
|
#define TOUCH_SDA 8
|
||||||
|
#define TOUCH_SCL 9
|
||||||
|
#define TOUCH_INT 4
|
||||||
|
#define TOUCH_RST -1
|
||||||
|
|
||||||
|
// ── CH422G Expander pins ──
|
||||||
|
#define TP_RST 1
|
||||||
|
#define LCD_BL 2
|
||||||
|
#define LCD_RST 3
|
||||||
|
#define SD_CS 4
|
||||||
|
#define USB_SEL 5
|
||||||
|
|
||||||
|
class LGFX : public lgfx::LGFX_Device {
|
||||||
|
public:
|
||||||
|
lgfx::Bus_RGB _bus_instance;
|
||||||
|
lgfx::Panel_RGB _panel_instance;
|
||||||
|
lgfx::Touch_GT911 _touch_instance;
|
||||||
|
|
||||||
|
LGFX(void) {
|
||||||
|
// Panel config
|
||||||
|
{
|
||||||
|
auto cfg = _panel_instance.config();
|
||||||
|
cfg.memory_width = TFT_HOR_RES;
|
||||||
|
cfg.memory_height = TFT_VER_RES;
|
||||||
|
cfg.panel_width = TFT_HOR_RES;
|
||||||
|
cfg.panel_height = TFT_VER_RES;
|
||||||
|
cfg.offset_x = 0;
|
||||||
|
cfg.offset_y = 0;
|
||||||
|
_panel_instance.config(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB parallel bus config (from Westcott)
|
||||||
|
{
|
||||||
|
auto cfg = _bus_instance.config();
|
||||||
|
cfg.panel = &_panel_instance;
|
||||||
|
|
||||||
|
// Blue channel
|
||||||
|
cfg.pin_d0 = 14;
|
||||||
|
cfg.pin_d1 = 38;
|
||||||
|
cfg.pin_d2 = 18;
|
||||||
|
cfg.pin_d3 = 17;
|
||||||
|
cfg.pin_d4 = 10;
|
||||||
|
|
||||||
|
// Green channel
|
||||||
|
cfg.pin_d5 = 39;
|
||||||
|
cfg.pin_d6 = 0;
|
||||||
|
cfg.pin_d7 = 45;
|
||||||
|
cfg.pin_d8 = 48;
|
||||||
|
cfg.pin_d9 = 47;
|
||||||
|
cfg.pin_d10 = 21;
|
||||||
|
|
||||||
|
// Red channel
|
||||||
|
cfg.pin_d11 = 1;
|
||||||
|
cfg.pin_d12 = 2;
|
||||||
|
cfg.pin_d13 = 42;
|
||||||
|
cfg.pin_d14 = 41;
|
||||||
|
cfg.pin_d15 = 40;
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
cfg.pin_henable = 5;
|
||||||
|
cfg.pin_vsync = 3;
|
||||||
|
cfg.pin_hsync = 46;
|
||||||
|
cfg.pin_pclk = 7;
|
||||||
|
cfg.freq_write = 14000000;
|
||||||
|
|
||||||
|
cfg.hsync_polarity = 0;
|
||||||
|
cfg.hsync_front_porch = 20;
|
||||||
|
cfg.hsync_pulse_width = 10;
|
||||||
|
cfg.hsync_back_porch = 10;
|
||||||
|
|
||||||
|
cfg.vsync_polarity = 0;
|
||||||
|
cfg.vsync_front_porch = 10;
|
||||||
|
cfg.vsync_pulse_width = 10;
|
||||||
|
cfg.vsync_back_porch = 10;
|
||||||
|
|
||||||
|
cfg.pclk_active_neg = 0;
|
||||||
|
cfg.de_idle_high = 0;
|
||||||
|
cfg.pclk_idle_high = 0;
|
||||||
|
|
||||||
|
_bus_instance.config(cfg);
|
||||||
|
}
|
||||||
|
_panel_instance.setBus(&_bus_instance);
|
||||||
|
|
||||||
|
// Touch config (I2C port 1, address 0x14 - from Westcott!)
|
||||||
|
{
|
||||||
|
auto cfg = _touch_instance.config();
|
||||||
|
cfg.x_min = 0;
|
||||||
|
cfg.x_max = TFT_HOR_RES - 1;
|
||||||
|
cfg.y_min = 0;
|
||||||
|
cfg.y_max = TFT_VER_RES - 1;
|
||||||
|
cfg.pin_int = TOUCH_INT;
|
||||||
|
cfg.pin_rst = TOUCH_RST;
|
||||||
|
cfg.bus_shared = false;
|
||||||
|
cfg.offset_rotation = 0;
|
||||||
|
cfg.i2c_port = I2C_NUM_1; // IMPORTANT: Port 1, not 0!
|
||||||
|
cfg.pin_sda = TOUCH_SDA;
|
||||||
|
cfg.pin_scl = TOUCH_SCL;
|
||||||
|
cfg.freq = 400000;
|
||||||
|
cfg.i2c_addr = 0x14; // IMPORTANT: Address 0x14, not 0x5D!
|
||||||
|
_touch_instance.config(cfg);
|
||||||
|
_panel_instance.setTouch(&_touch_instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPanel(&_panel_instance);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FQBN="esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB"
|
||||||
|
PORT="/dev/ttyUSB0"
|
||||||
|
LIBS="--library ./vendor/esp32-s3-lcd-43/LovyanGFX"
|
||||||
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLOCAL_SECRETS"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define BOARD_NAME "esp32-s3-lcd-43"
|
||||||
|
#define DISPLAY_WIDTH 800
|
||||||
|
#define DISPLAY_HEIGHT 480
|
||||||
|
#define DISPLAY_ROTATION 0
|
||||||
|
|
||||||
|
// ── RGB parallel bus (from Westcott1 reference) ──
|
||||||
|
#define LCD_DE 5
|
||||||
|
#define LCD_VSYNC 3
|
||||||
|
#define LCD_HSYNC 46
|
||||||
|
#define LCD_PCLK 7
|
||||||
|
|
||||||
|
#define LCD_R0 1
|
||||||
|
#define LCD_R1 2
|
||||||
|
#define LCD_R2 42
|
||||||
|
#define LCD_R3 41
|
||||||
|
#define LCD_R4 40
|
||||||
|
|
||||||
|
#define LCD_G0 39
|
||||||
|
#define LCD_G1 0
|
||||||
|
#define LCD_G2 45
|
||||||
|
#define LCD_G3 48
|
||||||
|
#define LCD_G4 47
|
||||||
|
#define LCD_G5 21
|
||||||
|
|
||||||
|
#define LCD_B0 14
|
||||||
|
#define LCD_B1 38
|
||||||
|
#define LCD_B2 18
|
||||||
|
#define LCD_B3 17
|
||||||
|
#define LCD_B4 10
|
||||||
|
|
||||||
|
// ── I2C bus (shared: CH422G + GT911) ──
|
||||||
|
#define I2C_MASTER_NUM ((i2c_port_t)0)
|
||||||
|
#define I2C_MASTER_SDA 8
|
||||||
|
#define I2C_MASTER_SCL 9
|
||||||
|
|
||||||
|
// ── GT911 Touch ──
|
||||||
|
#define GT911_ADDR 0x5D
|
||||||
|
// #define TOUCH_INT -1
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing
|
||||||
|
#define STYLE_SPACING_X 10
|
||||||
|
#define STYLE_SPACING_Y 10
|
||||||
|
#define STYLE_HEADER_HEIGHT 45
|
||||||
|
#define STYLE_TILE_GAP 8
|
||||||
|
#define STYLE_TILE_PADDING 16
|
||||||
|
#define STYLE_TILE_RADIUS 8
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG 0x001030 // Dark blue
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A // Dark gray
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280 // Green
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400 // Dark green
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440 // Teal
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100 // Dark red
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Klubhaus Doorbell — ESP32-S3-Touch-LCD-4.3 target
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DisplayDriverGFX.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
#include "secrets.h"
|
||||||
|
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
DisplayDriverGFX gfxDriver;
|
||||||
|
DisplayManager display(&gfxDriver);
|
||||||
|
DoorbellLogic logic(&display);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(500);
|
||||||
|
|
||||||
|
logic.begin(FW_VERSION, BOARD_NAME, wifiNetworks, wifiNetworkCount);
|
||||||
|
logic.finishBoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Read touch (includes test injection via serial "touch x y press/release")
|
||||||
|
TouchEvent evt = display.readTouch();
|
||||||
|
|
||||||
|
// State machine tick
|
||||||
|
logic.update();
|
||||||
|
|
||||||
|
// Render current screen
|
||||||
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
|
// Handle tap gestures
|
||||||
|
logic.handleTouch(evt);
|
||||||
|
|
||||||
|
// Handle hold-to-silence gesture
|
||||||
|
logic.updateHold(evt);
|
||||||
|
|
||||||
|
// Serial console
|
||||||
|
logic.processSerial();
|
||||||
|
|
||||||
|
// Yield to WiFi/BT stack
|
||||||
|
delay(LOOP_YIELD_MS);
|
||||||
|
}
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install LovyanGFX for esp32-s3-lcd-43
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
VENDOR_DIR="$DIR/../../vendor/esp32-s3-lcd-43"
|
||||||
|
LOVGFX_DIR="$VENDOR_DIR/LovyanGFX"
|
||||||
|
|
||||||
|
if [ ! -d "$LOVGFX_DIR" ]; then
|
||||||
|
echo "Cloning LovyanGFX..."
|
||||||
|
git clone --depth 1 \
|
||||||
|
https://github.com/lovyan03/LovyanGFX.git \
|
||||||
|
"$LOVGFX_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create library.properties
|
||||||
|
cat > "$LOVGFX_DIR/library.properties" << 'EOF'
|
||||||
|
name=LovyanGFX
|
||||||
|
version=1.2.0
|
||||||
|
author=lovyan03
|
||||||
|
maintainer=lovyan03
|
||||||
|
sentence=Display and touch driver library for ESP32
|
||||||
|
paragraph=Universal graphics library for ESP32 with support for various displays and touch controllers
|
||||||
|
category=Display
|
||||||
|
url=https://github.com/lovyan03/LovyanGFX
|
||||||
|
architectures=esp32
|
||||||
|
includes=LovyanGFX.hpp
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p "$LOVGFX_DIR/src"
|
||||||
|
echo "[OK] LovyanGFX vendored for esp32-s3-lcd-43"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
// Copy this file to secrets.h and fill in your credentials.
|
||||||
|
// secrets.h is gitignored.
|
||||||
|
|
||||||
|
static const WiFiCred wifiNetworks[] = {
|
||||||
|
{ "Your_SSID_1", "password1" },
|
||||||
|
{ "Your_SSID_2", "password2" },
|
||||||
|
};
|
||||||
|
static const int wifiNetworkCount = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]);
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Uno R4 WiFi Doorbell - Quick Start Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This is a simplified doorbell notification system for the Arduino Uno R4 WiFi that displays incoming ntfy.sh messages on the built-in 12x8 LED matrix.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Connects to WiFi and monitors a ntfy.sh topic
|
||||||
|
- Displays incoming messages as scrolling text on LED matrix
|
||||||
|
- Scrolls at 2 columns per second (configurable)
|
||||||
|
- Auto-silences after 60 seconds of display
|
||||||
|
- Simple state machine: CONNECTING → IDLE → DISPLAYING → SILENCED
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Configure WiFi Credentials
|
||||||
|
Edit `boards/uno-r4-wifi/uno-r4-wifi.ino`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define WIFI_SSID "YOUR_WIFI_SSID"
|
||||||
|
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure ntfy.sh Topic
|
||||||
|
Edit `boards/uno-r4-wifi/uno-r4-wifi.ino`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define NTFY_TOPIC "YOUR_TOPIC_HERE"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a free topic at https://ntfy.sh/ (e.g., "my-doorbell-12345")
|
||||||
|
|
||||||
|
### 3. Upload the Sketch
|
||||||
|
```bash
|
||||||
|
arduino-cli compile --fqbn arduino:renesas_uno:unor4wifi boards/uno-r4-wifi/uno-r4-wifi.ino
|
||||||
|
arduino-cli upload --fqbn arduino:renesas_uno:unor4wifi -p /dev/ttyACM0 boards/uno-r4-wifi/uno-r4-wifi.ino
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test the System
|
||||||
|
Open a terminal and run the test script:
|
||||||
|
```bash
|
||||||
|
chmod +x boards/uno-r4-wifi/test_messages.sh
|
||||||
|
./boards/uno-r4-wifi/test_messages.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually send a message:
|
||||||
|
```bash
|
||||||
|
curl -X POST https://ntfy.sh/YOUR_TOPIC_HERE -d "Hello from ntfy!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### State Machine
|
||||||
|
1. **CONNECTING_WIFI** - Attempts to connect to WiFi
|
||||||
|
2. **IDLE** - Polls ntfy.sh every 5 seconds for new messages
|
||||||
|
3. **DISPLAYING_MESSAGE** - Shows scrolling text for 60 seconds
|
||||||
|
4. **SILENCED** - Clears matrix for 5 seconds before returning to IDLE
|
||||||
|
|
||||||
|
### LED Matrix Display
|
||||||
|
- Text scrolls from right to left across the 12x8 matrix
|
||||||
|
- Scroll rate: 2 columns per second (500ms per column)
|
||||||
|
- Characters are 5 pixels wide with 1 pixel spacing
|
||||||
|
- Only alphanumeric characters (A-Z, 0-9) and spaces are supported
|
||||||
|
|
||||||
|
### ntfy.sh Integration
|
||||||
|
- Uses HTTP GET with polling (`/json?poll=1`)
|
||||||
|
- Tracks last message ID to avoid duplicates
|
||||||
|
- Parses JSON response for message body
|
||||||
|
|
||||||
|
## Pinout Reference (Uno R4 WiFi)
|
||||||
|
- **D0-D13**: Standard digital pins
|
||||||
|
- **A0-A5**: Analog inputs
|
||||||
|
- **D26/D27**: Qwiic (I2C) connector (SCL/SDA)
|
||||||
|
- **LED Matrix**: Built-in 12x8 matrix (pins handled internally)
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Adjust Timing
|
||||||
|
Edit constants in `uno-r4-wifi.ino`:
|
||||||
|
```cpp
|
||||||
|
#define POLL_INTERVAL_MS 5000 // How often to check for messages
|
||||||
|
#define DISPLAY_DURATION_MS 60000 // How long to display each message
|
||||||
|
#define SCROLL_SPEED_MS 500 // Scroll speed (lower = faster)
|
||||||
|
#define SILENCE_DURATION_MS 5000 // Time between messages
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add More Characters
|
||||||
|
The font array in `drawCharToFrame()` can be extended to support additional characters.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### WiFi Won't Connect
|
||||||
|
- Check WiFi credentials
|
||||||
|
- Ensure 2.4GHz network (Uno R4 WiFi doesn't support 5GHz)
|
||||||
|
- Verify WiFi signal strength
|
||||||
|
|
||||||
|
### LED Matrix Not Showing Text
|
||||||
|
- Ensure `matrix.begin()` is called in setup()
|
||||||
|
- Check that `matrix.loadFrame()` is called with valid data
|
||||||
|
- Verify text contains only supported characters
|
||||||
|
|
||||||
|
### No Messages Received
|
||||||
|
- Check ntfy.sh topic name matches exactly
|
||||||
|
- Ensure the topic is publicly accessible
|
||||||
|
- Test with curl command manually
|
||||||
|
- Check serial monitor for error messages
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
- Add support for more characters in the font
|
||||||
|
- Implement different message types (Alert, Silence, etc.)
|
||||||
|
- Add color coding with different LED patterns
|
||||||
|
- Integrate with the main doorbell codebase
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FQBN=arduino:renesas_uno:unor4wifi
|
||||||
|
PORT=/dev/ttyACM0
|
||||||
|
LIBS=""
|
||||||
|
OPTS="-DDEBUG_MODE"
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Test script for Uno R4 WiFi doorbell
|
||||||
|
# Sends test messages to ntfy.sh
|
||||||
|
|
||||||
|
# Update this with your actual topic
|
||||||
|
NTFY_TOPIC="ALERT_klubhaus_topic_test"
|
||||||
|
|
||||||
|
# Color codes for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Uno R4 WiFi Doorbell Test Script${NC}"
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
echo -e "Sending test messages to: https://ntfy.sh/$NTFY_TOPIC"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Send different test messages
|
||||||
|
echo "1. Sending simple alert..."
|
||||||
|
curl -s -X POST "https://ntfy.sh/$NTFY_TOPIC" \
|
||||||
|
-d "Doorbell pressed!" \
|
||||||
|
-H "Title: ALERT" \
|
||||||
|
-H "Priority: high"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo -e "\n2. Sending longer message..."
|
||||||
|
curl -s -X POST "https://ntfy.sh/$NTFY_TOPIC" \
|
||||||
|
-d "Front door visitor detected at main entrance" \
|
||||||
|
-H "Title: ALERT" \
|
||||||
|
-H "Priority: high"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo -e "\n3. Sending test with special characters..."
|
||||||
|
curl -s -X POST "https://ntfy.sh/$NTFY_TOPIC" \
|
||||||
|
-d "Package delivered! #12345" \
|
||||||
|
-H "Title: ALERT" \
|
||||||
|
-H "Priority: high"
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo -e "\n4. Sending numeric message..."
|
||||||
|
curl -s -X POST "https://ntfy.sh/$NTFY_TOPIC" \
|
||||||
|
-d "Temperature: 72F" \
|
||||||
|
-H "Title: ALERT" \
|
||||||
|
-H "Priority: high"
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}Done! Check the LED matrix on your Uno R4 WiFi.${NC}"
|
||||||
@@ -0,0 +1,563 @@
|
|||||||
|
//
|
||||||
|
// Klubhaus Doorbell - Uno R4 WiFi Edition
|
||||||
|
// Monitors ntfy.sh alert topic and displays messages on LED matrix
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Arduino_LED_Matrix.h"
|
||||||
|
#include "WiFiS3.h"
|
||||||
|
#include "ArduinoJson.h"
|
||||||
|
#include "RTC.h" // Add RTC library for time synchronization
|
||||||
|
|
||||||
|
// WiFi credentials
|
||||||
|
#define WIFI_SSID "iot-2GHz"
|
||||||
|
#define WIFI_PASS "lesson-greater"
|
||||||
|
|
||||||
|
// ntfy.sh topic
|
||||||
|
#define NTFY_TOPIC "ALERT_klubhaus_topic_test"
|
||||||
|
|
||||||
|
// Timing constants
|
||||||
|
#define POLL_INTERVAL_MS 5000 // Poll ntfy.sh every 5 seconds
|
||||||
|
#define DISPLAY_DURATION_MS 60000 // Show message for 60 seconds
|
||||||
|
#define SCROLL_SPEED_MS 100 // Scroll speed (lower = faster) - 4 columns/sec
|
||||||
|
#define SILENCE_DURATION_MS 5000 // Stay silenced for 5 seconds
|
||||||
|
#define GAUGE_MAX_AGE_MS 180000 // 3-minute window for gauge
|
||||||
|
#define GAUGE_BUCKET_SEC 15 // 15 seconds per column
|
||||||
|
#define MAX_GAUGE_ENTRIES 12 // Max message timestamps
|
||||||
|
|
||||||
|
long gaugeTimestamps[MAX_GAUGE_ENTRIES] = {0};
|
||||||
|
int gaugeCount = 0;
|
||||||
|
|
||||||
|
// LED matrix dimensions
|
||||||
|
#define MATRIX_COLS 12
|
||||||
|
#define MATRIX_ROWS 8
|
||||||
|
|
||||||
|
// Global objects
|
||||||
|
ArduinoLEDMatrix matrix;
|
||||||
|
WiFiClient client;
|
||||||
|
|
||||||
|
// State machine
|
||||||
|
enum AppState {
|
||||||
|
STATE_IDLE,
|
||||||
|
STATE_DISPLAYING_MESSAGE
|
||||||
|
};
|
||||||
|
AppState currentState = STATE_IDLE;
|
||||||
|
|
||||||
|
// Message state
|
||||||
|
String currentMessage = "";
|
||||||
|
String lastDisplayedMessage = ""; // Track last displayed message to avoid duplicates
|
||||||
|
String lastMessageId = ""; // Track last message ID for filtering
|
||||||
|
unsigned long messageStartTime = 0;
|
||||||
|
unsigned long lastScrollTime = 0;
|
||||||
|
unsigned long lastPollTime = 0;
|
||||||
|
unsigned long bootTime = 0;
|
||||||
|
unsigned long bootDelayEndTime = 0; // When to start processing messages
|
||||||
|
unsigned long lastDisplayedMessageTime = 0; // Unix timestamp of last displayed message
|
||||||
|
long currentMessageTime = 0; // Unix timestamp of current message being displayed
|
||||||
|
|
||||||
|
// Non-blocking poll state machine
|
||||||
|
enum PollState { POLL_IDLE, POLL_WAIT, POLL_CONNECT, POLL_SEND, POLL_READ, POLL_PARSE };
|
||||||
|
PollState pollState = POLL_IDLE;
|
||||||
|
unsigned long pollStepTime = 0;
|
||||||
|
String pollResponse = "";
|
||||||
|
long readyTime = 0; // Unix timestamp when device became ready (after NTP sync)
|
||||||
|
int queuedMessageCount = 0;
|
||||||
|
bool rtcSynced = false; // Track if RTC has been synced with NTP
|
||||||
|
|
||||||
|
// Scrolling state
|
||||||
|
int scrollOffset = 0;
|
||||||
|
int messageLength = 0;
|
||||||
|
|
||||||
|
// Frame buffer - 8 rows x 12 columns
|
||||||
|
uint8_t frameBuffer[MATRIX_ROWS][MATRIX_COLS] = {0};
|
||||||
|
|
||||||
|
int getBracketPeriod(long ageSec) {
|
||||||
|
if (ageSec < 15) return 150;
|
||||||
|
if (ageSec < 30) return 200;
|
||||||
|
if (ageSec < 45) return 250;
|
||||||
|
if (ageSec < 60) return 350;
|
||||||
|
if (ageSec < 75) return 500;
|
||||||
|
if (ageSec < 90) return 700;
|
||||||
|
if (ageSec < 105) return 1000;
|
||||||
|
if (ageSec < 120) return 1400;
|
||||||
|
if (ageSec < 135) return 1800;
|
||||||
|
if (ageSec < 150) return 2200;
|
||||||
|
if (ageSec < 165) return 2600;
|
||||||
|
return 3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBracketDuty(long ageSec) {
|
||||||
|
if (ageSec < 15) return 55;
|
||||||
|
if (ageSec < 30) return 48;
|
||||||
|
if (ageSec < 45) return 40;
|
||||||
|
if (ageSec < 60) return 35;
|
||||||
|
if (ageSec < 75) return 30;
|
||||||
|
if (ageSec < 90) return 25;
|
||||||
|
if (ageSec < 105) return 20;
|
||||||
|
if (ageSec < 120) return 16;
|
||||||
|
if (ageSec < 135) return 12;
|
||||||
|
if (ageSec < 150) return 10;
|
||||||
|
if (ageSec < 165) return 8;
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gaugeAddMessage(long unixTime) {
|
||||||
|
if (gaugeCount < MAX_GAUGE_ENTRIES) {
|
||||||
|
gaugeTimestamps[gaugeCount++] = unixTime;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < MAX_GAUGE_ENTRIES - 1; i++) {
|
||||||
|
gaugeTimestamps[i] = gaugeTimestamps[i + 1];
|
||||||
|
}
|
||||||
|
gaugeTimestamps[MAX_GAUGE_ENTRIES - 1] = unixTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gaugePruneOld(long nowUnix) {
|
||||||
|
int w = 0;
|
||||||
|
for (int i = 0; i < gaugeCount; i++) {
|
||||||
|
if (nowUnix - gaugeTimestamps[i] <= GAUGE_MAX_AGE_MS / 1000) {
|
||||||
|
gaugeTimestamps[w++] = gaugeTimestamps[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gaugeCount = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderGauge(long nowUnix) {
|
||||||
|
unsigned long ms = millis();
|
||||||
|
for (int col = 0; col < MATRIX_COLS; col++) {
|
||||||
|
int bucketMin = col * GAUGE_BUCKET_SEC;
|
||||||
|
int bucketMax = bucketMin + GAUGE_BUCKET_SEC;
|
||||||
|
|
||||||
|
int messagesInBucket = 0;
|
||||||
|
long youngestAge = GAUGE_MAX_AGE_MS / 1000 + 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < gaugeCount; i++) {
|
||||||
|
long age = nowUnix - gaugeTimestamps[i];
|
||||||
|
if (age >= bucketMin && age < bucketMax) {
|
||||||
|
messagesInBucket++;
|
||||||
|
if (age < youngestAge) youngestAge = age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messagesInBucket == 0) {
|
||||||
|
frameBuffer[MATRIX_ROWS - 1][col] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int periodMs = getBracketPeriod(youngestAge);
|
||||||
|
int dutyPct = getBracketDuty(youngestAge);
|
||||||
|
|
||||||
|
int phase = ms % periodMs;
|
||||||
|
int onThreshold = periodMs * dutyPct / 100;
|
||||||
|
frameBuffer[MATRIX_ROWS - 1][col] = (phase < onThreshold) ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(1000);
|
||||||
|
Serial.println("\n=== Klubhaus Doorbell - Uno R4 WiFi ===");
|
||||||
|
Serial.print("Build token: ");
|
||||||
|
Serial.println(BUILD_TOKEN);
|
||||||
|
Serial.println("Starting up...");
|
||||||
|
|
||||||
|
// Initialize RTC
|
||||||
|
RTC.begin();
|
||||||
|
Serial.println("RTC initialized");
|
||||||
|
|
||||||
|
bootTime = millis();
|
||||||
|
bootDelayEndTime = bootTime + 10000; // Wait 10 seconds after boot before processing
|
||||||
|
Serial.print("Boot time: ");
|
||||||
|
Serial.print(bootTime);
|
||||||
|
Serial.print(", will start processing at: ");
|
||||||
|
Serial.println(bootDelayEndTime);
|
||||||
|
|
||||||
|
Serial.println("Initializing LED matrix...");
|
||||||
|
matrix.begin();
|
||||||
|
delay(100);
|
||||||
|
matrix.clear();
|
||||||
|
Serial.println("LED matrix initialized");
|
||||||
|
|
||||||
|
Serial.println("Displaying OK...");
|
||||||
|
displayStaticText("OK");
|
||||||
|
delay(500); // Display OK for 500ms
|
||||||
|
matrix.clear();
|
||||||
|
|
||||||
|
Serial.println("Starting WiFi connection...");
|
||||||
|
connectToWiFi();
|
||||||
|
|
||||||
|
// Sync RTC with NTP after WiFi connection
|
||||||
|
syncRTCWithNTP();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
// Heartbeat LED
|
||||||
|
static unsigned long lastHeartbeat = 0;
|
||||||
|
static const unsigned long FADE_PERIOD_MS = 10000;
|
||||||
|
static const int MIN_BRIGHTNESS = 51;
|
||||||
|
static const int MAX_BRIGHTNESS = 204;
|
||||||
|
if (now - lastHeartbeat >= 50) {
|
||||||
|
float phase = (float)(now % FADE_PERIOD_MS) / (float)FADE_PERIOD_MS * 2.0 * PI;
|
||||||
|
analogWrite(LED_BUILTIN, MIN_BRIGHTNESS + (int)((MAX_BRIGHTNESS - MIN_BRIGHTNESS) / 2.0 * (1.0 + sin(phase))));
|
||||||
|
lastHeartbeat = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One RTC read per loop
|
||||||
|
RTCTime ct;
|
||||||
|
RTC.getTime(ct);
|
||||||
|
long nowUnix = ct.getUnixTime();
|
||||||
|
|
||||||
|
// Non-blocking poll — one step per loop
|
||||||
|
if (rtcSynced && now >= bootDelayEndTime) {
|
||||||
|
pollNtfy(nowUnix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear frame, then always render gauge on row 7
|
||||||
|
memset(frameBuffer, 0, sizeof(frameBuffer));
|
||||||
|
gaugePruneOld(nowUnix);
|
||||||
|
renderGauge(nowUnix);
|
||||||
|
|
||||||
|
switch (currentState) {
|
||||||
|
case STATE_IDLE:
|
||||||
|
if (currentMessage.length() > 0) {
|
||||||
|
Serial.print("[DISPLAY] ");
|
||||||
|
Serial.println(currentMessage);
|
||||||
|
currentState = STATE_DISPLAYING_MESSAGE;
|
||||||
|
messageStartTime = now;
|
||||||
|
lastScrollTime = now;
|
||||||
|
scrollOffset = -MATRIX_COLS;
|
||||||
|
messageLength = currentMessage.length();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATE_DISPLAYING_MESSAGE:
|
||||||
|
// Always draw text at current scroll position (not just on tick)
|
||||||
|
drawScrollingText();
|
||||||
|
|
||||||
|
// Advance scroll on timer tick
|
||||||
|
if (now - lastScrollTime >= SCROLL_SPEED_MS) {
|
||||||
|
scrollOffset += 1;
|
||||||
|
if (scrollOffset > messageLength * 6 + MATRIX_COLS) {
|
||||||
|
scrollOffset = -MATRIX_COLS;
|
||||||
|
}
|
||||||
|
lastScrollTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now - messageStartTime >= DISPLAY_DURATION_MS) {
|
||||||
|
lastDisplayedMessage = currentMessage;
|
||||||
|
lastDisplayedMessageTime = currentMessageTime;
|
||||||
|
currentState = STATE_IDLE;
|
||||||
|
currentMessage = "";
|
||||||
|
scrollOffset = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-blocking poll — start cycle on timer, advance one step per loop
|
||||||
|
static unsigned long lastPollStart = 0;
|
||||||
|
if (pollState == POLL_IDLE && now - lastPollStart >= POLL_INTERVAL_MS) {
|
||||||
|
if (rtcSynced && now >= bootDelayEndTime && WiFi.status() == WL_CONNECTED) {
|
||||||
|
pollState = POLL_CONNECT;
|
||||||
|
lastPollStart = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pollState != POLL_IDLE) {
|
||||||
|
pollNtfy(nowUnix);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.renderBitmap(frameBuffer, MATRIX_ROWS, MATRIX_COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectToWiFi() {
|
||||||
|
Serial.print("Connecting to WiFi: ");
|
||||||
|
Serial.println(WIFI_SSID);
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_NO_MODULE) {
|
||||||
|
Serial.println("ERROR: Communication with WiFi module failed!");
|
||||||
|
displayStaticText("ERR");
|
||||||
|
while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
String fv = WiFi.firmwareVersion();
|
||||||
|
Serial.print("WiFi firmware version: ");
|
||||||
|
Serial.println(fv);
|
||||||
|
|
||||||
|
Serial.print("Attempting WiFi connection...");
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
|
||||||
|
Serial.print(".");
|
||||||
|
delay(1000);
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
|
Serial.println("\nERROR: Failed to connect to WiFi");
|
||||||
|
displayStaticText("WIFI ERR");
|
||||||
|
while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("\nWiFi connected!");
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncRTCWithNTP() {
|
||||||
|
Serial.println("Syncing RTC with NTP...");
|
||||||
|
|
||||||
|
unsigned long epochTime = WiFi.getTime();
|
||||||
|
|
||||||
|
if (epochTime > 0) {
|
||||||
|
// Set timezone to UTC (no offset)
|
||||||
|
RTCTime timeToSet = RTCTime(epochTime);
|
||||||
|
RTC.setTime(timeToSet);
|
||||||
|
|
||||||
|
// Get current time to verify
|
||||||
|
RTCTime currentTime;
|
||||||
|
RTC.getTime(currentTime);
|
||||||
|
Serial.print("RTC synced to: ");
|
||||||
|
Serial.println(String(currentTime));
|
||||||
|
|
||||||
|
rtcSynced = true;
|
||||||
|
|
||||||
|
readyTime = epochTime; // Reject messages older than this boot
|
||||||
|
Serial.print("Ready time: ");
|
||||||
|
Serial.println((unsigned long)readyTime);
|
||||||
|
} else {
|
||||||
|
Serial.println("ERROR: Failed to get NTP time");
|
||||||
|
Serial.println("Make sure WiFi firmware version is at least 0.5.0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pollNtfy(long nowUnix) {
|
||||||
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
switch (pollState) {
|
||||||
|
case POLL_CONNECT:
|
||||||
|
if (client.connect("ntfy.sh", 80)) {
|
||||||
|
pollState = POLL_SEND;
|
||||||
|
} else {
|
||||||
|
pollState = POLL_IDLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POLL_SEND: {
|
||||||
|
String url;
|
||||||
|
if (lastMessageId.length() > 0) {
|
||||||
|
url = "/" + String(NTFY_TOPIC) + "/json?poll=1&since=" + lastMessageId;
|
||||||
|
} else {
|
||||||
|
url = "/" + String(NTFY_TOPIC) + "/json?poll=1&since=latest";
|
||||||
|
}
|
||||||
|
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
|
||||||
|
"Host: ntfy.sh\r\n" +
|
||||||
|
"Connection: close\r\n\r\n");
|
||||||
|
pollStepTime = now;
|
||||||
|
pollResponse = "";
|
||||||
|
pollState = POLL_READ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case POLL_READ: {
|
||||||
|
// Non-blocking read — only consume what's available, max 20ms per call
|
||||||
|
unsigned long t = millis();
|
||||||
|
while (client.available() > 0 && (millis() - t) < 20) {
|
||||||
|
pollResponse += (char)client.read();
|
||||||
|
}
|
||||||
|
// Done when disconnected and we have content, or timeout
|
||||||
|
if (!client.connected() || (pollResponse.length() > 0 && now - pollStepTime > 3000)) {
|
||||||
|
client.stop();
|
||||||
|
pollState = POLL_PARSE;
|
||||||
|
} else if (now - pollStepTime > 8000) {
|
||||||
|
client.stop();
|
||||||
|
pollState = POLL_IDLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case POLL_PARSE: {
|
||||||
|
int js = pollResponse.indexOf("{");
|
||||||
|
if (js >= 0) {
|
||||||
|
StaticJsonDocument<1024> doc;
|
||||||
|
if (!deserializeJson(doc, pollResponse.substring(js))) {
|
||||||
|
processMessage(doc, nowUnix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pollState = POLL_IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processMessage(JsonDocument& doc, long nowUnix) {
|
||||||
|
const char* message = doc["message"];
|
||||||
|
const char* id = doc["id"];
|
||||||
|
long msgTime = doc["time"];
|
||||||
|
|
||||||
|
if (!message || strlen(message) == 0 || msgTime <= 0) return;
|
||||||
|
|
||||||
|
String msgStr = String(message);
|
||||||
|
|
||||||
|
if (msgStr == lastDisplayedMessage) {
|
||||||
|
if (id) lastMessageId = String(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastDisplayedMessageTime > 0 && msgTime <= lastDisplayedMessageTime) {
|
||||||
|
if (id) lastMessageId = String(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readyTime > 0 && msgTime < readyTime) {
|
||||||
|
if (id) lastMessageId = String(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nowUnix - msgTime > 3600) {
|
||||||
|
if (id) lastMessageId = String(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("[ACCEPTED] ");
|
||||||
|
Serial.println(msgStr);
|
||||||
|
|
||||||
|
gaugePruneOld(nowUnix);
|
||||||
|
gaugeAddMessage(msgTime);
|
||||||
|
|
||||||
|
if (currentState == STATE_IDLE) {
|
||||||
|
currentMessage = msgStr;
|
||||||
|
currentMessageTime = msgTime;
|
||||||
|
}
|
||||||
|
if (id) lastMessageId = String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawScrollingText() {
|
||||||
|
int charWidth = 5;
|
||||||
|
int charSpacing = 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < messageLength; i++) {
|
||||||
|
char c = currentMessage[i];
|
||||||
|
int charX = i * (charWidth + charSpacing) - scrollOffset;
|
||||||
|
if (charX > -charWidth && charX < MATRIX_COLS) {
|
||||||
|
drawCharToFrame(c, charX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawCharToFrame(char c, int x) {
|
||||||
|
static const uint8_t font[][5] = {
|
||||||
|
// Uppercase A-Z (indices 0-25)
|
||||||
|
{0x7F, 0x41, 0x41, 0x41, 0x7F}, // 'A'
|
||||||
|
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 'B'
|
||||||
|
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 'C'
|
||||||
|
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 'D'
|
||||||
|
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 'E'
|
||||||
|
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 'F'
|
||||||
|
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 'G'
|
||||||
|
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 'H'
|
||||||
|
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 'I'
|
||||||
|
{0x20, 0x40, 0x41, 0x3F, 0x00}, // 'J'
|
||||||
|
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 'K'
|
||||||
|
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 'L'
|
||||||
|
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 'M'
|
||||||
|
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 'N'
|
||||||
|
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 'O'
|
||||||
|
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 'P'
|
||||||
|
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 'Q'
|
||||||
|
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 'R'
|
||||||
|
{0x46, 0x49, 0x49, 0x49, 0x31}, // 'S'
|
||||||
|
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 'T'
|
||||||
|
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 'U'
|
||||||
|
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 'V'
|
||||||
|
{0x7F, 0x20, 0x18, 0x20, 0x7F}, // 'W'
|
||||||
|
{0x63, 0x14, 0x08, 0x14, 0x63}, // 'X'
|
||||||
|
{0x07, 0x08, 0x70, 0x08, 0x07}, // 'Y'
|
||||||
|
{0x61, 0x51, 0x49, 0x45, 0x43}, // 'Z'
|
||||||
|
// Lowercase a-z (indices 26-51)
|
||||||
|
{0x7F, 0x09, 0x09, 0x09, 0x7F}, // 'a'
|
||||||
|
{0x7F, 0x49, 0x49, 0x49, 0x36}, // 'b'
|
||||||
|
{0x3E, 0x41, 0x41, 0x41, 0x22}, // 'c'
|
||||||
|
{0x7F, 0x41, 0x41, 0x22, 0x1C}, // 'd'
|
||||||
|
{0x7F, 0x49, 0x49, 0x49, 0x41}, // 'e'
|
||||||
|
{0x7F, 0x09, 0x09, 0x09, 0x01}, // 'f'
|
||||||
|
{0x3E, 0x41, 0x49, 0x49, 0x7A}, // 'g'
|
||||||
|
{0x7F, 0x08, 0x08, 0x08, 0x7F}, // 'h'
|
||||||
|
{0x00, 0x41, 0x7F, 0x41, 0x00}, // 'i'
|
||||||
|
{0x20, 0x40, 0x41, 0x3F, 0x00}, // 'j'
|
||||||
|
{0x7F, 0x08, 0x14, 0x22, 0x41}, // 'k'
|
||||||
|
{0x7F, 0x40, 0x40, 0x40, 0x40}, // 'l'
|
||||||
|
{0x7F, 0x02, 0x0C, 0x02, 0x7F}, // 'm'
|
||||||
|
{0x7F, 0x04, 0x08, 0x10, 0x7F}, // 'n'
|
||||||
|
{0x3E, 0x41, 0x41, 0x41, 0x3E}, // 'o'
|
||||||
|
{0x7F, 0x09, 0x09, 0x09, 0x06}, // 'p'
|
||||||
|
{0x3E, 0x41, 0x51, 0x21, 0x5E}, // 'q'
|
||||||
|
{0x7F, 0x09, 0x19, 0x29, 0x46}, // 'r'
|
||||||
|
{0x46, 0x49, 0x49, 0x49, 0x31}, // 's'
|
||||||
|
{0x01, 0x01, 0x7F, 0x01, 0x01}, // 't'
|
||||||
|
{0x3F, 0x40, 0x40, 0x40, 0x3F}, // 'u'
|
||||||
|
{0x1F, 0x20, 0x40, 0x20, 0x1F}, // 'v'
|
||||||
|
{0x7F, 0x20, 0x18, 0x20, 0x7F}, // 'w'
|
||||||
|
{0x63, 0x14, 0x08, 0x14, 0x63}, // 'x'
|
||||||
|
{0x07, 0x08, 0x70, 0x08, 0x07}, // 'y'
|
||||||
|
{0x61, 0x51, 0x49, 0x45, 0x43}, // 'z'
|
||||||
|
// Numbers and space (indices 52-62)
|
||||||
|
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // '0'
|
||||||
|
{0x00, 0x42, 0x7F, 0x40, 0x00}, // '1'
|
||||||
|
{0x42, 0x61, 0x51, 0x49, 0x46}, // '2'
|
||||||
|
{0x21, 0x41, 0x45, 0x4B, 0x31}, // '3'
|
||||||
|
{0x18, 0x14, 0x12, 0x7F, 0x10}, // '4'
|
||||||
|
{0x27, 0x45, 0x45, 0x45, 0x39}, // '5'
|
||||||
|
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // '6'
|
||||||
|
{0x01, 0x71, 0x09, 0x05, 0x03}, // '7'
|
||||||
|
{0x36, 0x49, 0x49, 0x49, 0x36}, // '8'
|
||||||
|
{0x06, 0x49, 0x49, 0x29, 0x1E}, // '9'
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00}, // ' ' (space)
|
||||||
|
};
|
||||||
|
|
||||||
|
int fontIndex = -1;
|
||||||
|
|
||||||
|
if (c >= 'A' && c <= 'Z') {
|
||||||
|
fontIndex = c - 'A';
|
||||||
|
}
|
||||||
|
else if (c >= 'a' && c <= 'z') {
|
||||||
|
fontIndex = 26 + (c - 'a');
|
||||||
|
}
|
||||||
|
else if (c >= '0' && c <= '9') {
|
||||||
|
fontIndex = 52 + (c - '0');
|
||||||
|
}
|
||||||
|
else if (c == ' ') {
|
||||||
|
fontIndex = 62;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontIndex >= 0) {
|
||||||
|
for (int col = 0; col < 5; col++) {
|
||||||
|
uint8_t colData = font[fontIndex][col];
|
||||||
|
int drawX = x + col;
|
||||||
|
|
||||||
|
if (drawX >= 0 && drawX < MATRIX_COLS) {
|
||||||
|
for (int row = 0; row < MATRIX_ROWS; row++) {
|
||||||
|
if (colData & (1 << row)) {
|
||||||
|
frameBuffer[row][drawX] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayStaticText(String text) {
|
||||||
|
memset(frameBuffer, 0, sizeof(frameBuffer));
|
||||||
|
|
||||||
|
int textWidth = text.length() * 6;
|
||||||
|
int startX = max(0, (MATRIX_COLS - textWidth) / 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
drawCharToFrame(text[i], startX + i * 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.renderBitmap(frameBuffer, MATRIX_ROWS, MATRIX_COLS);
|
||||||
|
}
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
#include "Display_ST7789.h"
|
|
||||||
|
|
||||||
#define SPI_WRITE(_dat) SPI.transfer(_dat)
|
|
||||||
#define SPI_WRITE_Word(_dat) SPI.transfer16(_dat)
|
|
||||||
void SPI_Init()
|
|
||||||
{
|
|
||||||
SPI.begin(EXAMPLE_PIN_NUM_SCLK,EXAMPLE_PIN_NUM_MISO,EXAMPLE_PIN_NUM_MOSI);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LCD_WriteCommand(uint8_t Cmd)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, LOW);
|
|
||||||
SPI_WRITE(Cmd);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
void LCD_WriteData(uint8_t Data)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI_WRITE(Data);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
void LCD_WriteData_Word(uint16_t Data)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI_WRITE_Word(Data);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
void LCD_WriteData_nbyte(uint8_t* SetData,uint8_t* ReadData,uint32_t Size)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI.transferBytes(SetData, ReadData, Size);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LCD_Reset(void)
|
|
||||||
{
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
delay(50);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, LOW);
|
|
||||||
delay(50);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, HIGH);
|
|
||||||
delay(50);
|
|
||||||
}
|
|
||||||
void LCD_Init(void)
|
|
||||||
{
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_CS, OUTPUT);
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_DC, OUTPUT);
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_RST, OUTPUT);
|
|
||||||
Backlight_Init();
|
|
||||||
SPI_Init();
|
|
||||||
|
|
||||||
LCD_Reset();
|
|
||||||
//************* Start Initial Sequence **********//
|
|
||||||
LCD_WriteCommand(0x11);
|
|
||||||
delay(120);
|
|
||||||
LCD_WriteCommand(0x36);
|
|
||||||
if (HORIZONTAL)
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
else
|
|
||||||
LCD_WriteData(0x70);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x3A);
|
|
||||||
LCD_WriteData(0x05);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB0);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0xE8);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB2);
|
|
||||||
LCD_WriteData(0x0C);
|
|
||||||
LCD_WriteData(0x0C);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB7);
|
|
||||||
LCD_WriteData(0x35);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xBB);
|
|
||||||
LCD_WriteData(0x35);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC0);
|
|
||||||
LCD_WriteData(0x2C);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC2);
|
|
||||||
LCD_WriteData(0x01);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC3);
|
|
||||||
LCD_WriteData(0x13);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC4);
|
|
||||||
LCD_WriteData(0x20);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC6);
|
|
||||||
LCD_WriteData(0x0F);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xD0);
|
|
||||||
LCD_WriteData(0xA4);
|
|
||||||
LCD_WriteData(0xA1);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xD6);
|
|
||||||
LCD_WriteData(0xA1);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xE0);
|
|
||||||
LCD_WriteData(0xF0);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x05);
|
|
||||||
LCD_WriteData(0x29);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x3E);
|
|
||||||
LCD_WriteData(0x38);
|
|
||||||
LCD_WriteData(0x12);
|
|
||||||
LCD_WriteData(0x12);
|
|
||||||
LCD_WriteData(0x28);
|
|
||||||
LCD_WriteData(0x30);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xE1);
|
|
||||||
LCD_WriteData(0xF0);
|
|
||||||
LCD_WriteData(0x07);
|
|
||||||
LCD_WriteData(0x0A);
|
|
||||||
LCD_WriteData(0x0D);
|
|
||||||
LCD_WriteData(0x0B);
|
|
||||||
LCD_WriteData(0x07);
|
|
||||||
LCD_WriteData(0x28);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x3E);
|
|
||||||
LCD_WriteData(0x36);
|
|
||||||
LCD_WriteData(0x14);
|
|
||||||
LCD_WriteData(0x14);
|
|
||||||
LCD_WriteData(0x29);
|
|
||||||
LCD_WriteData(0x32);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x21);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x11);
|
|
||||||
delay(120);
|
|
||||||
LCD_WriteCommand(0x29);
|
|
||||||
}
|
|
||||||
/******************************************************************************
|
|
||||||
function: Set the cursor position
|
|
||||||
parameter :
|
|
||||||
Xstart: Start uint16_t x coordinate
|
|
||||||
Ystart: Start uint16_t y coordinate
|
|
||||||
Xend : End uint16_t coordinates
|
|
||||||
Yend : End uint16_t coordinatesen
|
|
||||||
******************************************************************************/
|
|
||||||
void LCD_SetCursor(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend)
|
|
||||||
{
|
|
||||||
if (HORIZONTAL) {
|
|
||||||
// set the X coordinates
|
|
||||||
LCD_WriteCommand(0x2A);
|
|
||||||
LCD_WriteData(Xstart >> 8);
|
|
||||||
LCD_WriteData(Xstart + Offset_X);
|
|
||||||
LCD_WriteData(Xend >> 8);
|
|
||||||
LCD_WriteData(Xend + Offset_X);
|
|
||||||
|
|
||||||
// set the Y coordinates
|
|
||||||
LCD_WriteCommand(0x2B);
|
|
||||||
LCD_WriteData(Ystart >> 8);
|
|
||||||
LCD_WriteData(Ystart + Offset_Y);
|
|
||||||
LCD_WriteData(Yend >> 8);
|
|
||||||
LCD_WriteData(Yend + Offset_Y);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set the X coordinates
|
|
||||||
LCD_WriteCommand(0x2A);
|
|
||||||
LCD_WriteData(Ystart >> 8);
|
|
||||||
LCD_WriteData(Ystart + Offset_Y);
|
|
||||||
LCD_WriteData(Yend >> 8);
|
|
||||||
LCD_WriteData(Yend + Offset_Y);
|
|
||||||
// set the Y coordinates
|
|
||||||
LCD_WriteCommand(0x2B);
|
|
||||||
LCD_WriteData(Xstart >> 8);
|
|
||||||
LCD_WriteData(Xstart + Offset_X);
|
|
||||||
LCD_WriteData(Xend >> 8);
|
|
||||||
LCD_WriteData(Xend + Offset_X);
|
|
||||||
}
|
|
||||||
LCD_WriteCommand(0x2C);
|
|
||||||
}
|
|
||||||
/******************************************************************************
|
|
||||||
function: Refresh the image in an area
|
|
||||||
parameter :
|
|
||||||
Xstart: Start uint16_t x coordinate
|
|
||||||
Ystart: Start uint16_t y coordinate
|
|
||||||
Xend : End uint16_t coordinates
|
|
||||||
Yend : End uint16_t coordinates
|
|
||||||
color : Set the color
|
|
||||||
******************************************************************************/
|
|
||||||
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color)
|
|
||||||
{
|
|
||||||
// uint16_t i,j;
|
|
||||||
// LCD_SetCursor(Xstart, Ystart, Xend,Yend);
|
|
||||||
// uint16_t Show_Width = Xend - Xstart + 1;
|
|
||||||
// uint16_t Show_Height = Yend - Ystart + 1;
|
|
||||||
// for(i = 0; i < Show_Height; i++){
|
|
||||||
// for(j = 0; j < Show_Width; j++){
|
|
||||||
// LCD_WriteData_Word(color[(i*(Show_Width))+j]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
uint16_t Show_Width = Xend - Xstart + 1;
|
|
||||||
uint16_t Show_Height = Yend - Ystart + 1;
|
|
||||||
uint32_t numBytes = Show_Width * Show_Height * sizeof(uint16_t);
|
|
||||||
uint8_t Read_D[numBytes];
|
|
||||||
LCD_SetCursor(Xstart, Ystart, Xend, Yend);
|
|
||||||
LCD_WriteData_nbyte((uint8_t*)color, Read_D, numBytes);
|
|
||||||
}
|
|
||||||
// backlight
|
|
||||||
void Backlight_Init(void)
|
|
||||||
{
|
|
||||||
ledcAttach(EXAMPLE_PIN_NUM_BK_LIGHT, Frequency, Resolution);
|
|
||||||
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Set_Backlight(uint8_t Light) //
|
|
||||||
{
|
|
||||||
|
|
||||||
if(Light > 100 || Light < 0)
|
|
||||||
printf("Set Backlight parameters in the range of 0 to 100 \r\n");
|
|
||||||
else{
|
|
||||||
uint32_t Backlight = Light*10;
|
|
||||||
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, Backlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
#define LCD_WIDTH 172 //LCD width
|
|
||||||
#define LCD_HEIGHT 320 //LCD height
|
|
||||||
|
|
||||||
#define SPIFreq 80000000
|
|
||||||
#define EXAMPLE_PIN_NUM_MISO 5
|
|
||||||
#define EXAMPLE_PIN_NUM_MOSI 6
|
|
||||||
#define EXAMPLE_PIN_NUM_SCLK 7
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_CS 14
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_DC 15
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_RST 21
|
|
||||||
#define EXAMPLE_PIN_NUM_BK_LIGHT 22
|
|
||||||
#define Frequency 1000
|
|
||||||
#define Resolution 10
|
|
||||||
|
|
||||||
#define VERTICAL 0
|
|
||||||
#define HORIZONTAL 1
|
|
||||||
|
|
||||||
#define Offset_X 34
|
|
||||||
#define Offset_Y 0
|
|
||||||
|
|
||||||
|
|
||||||
void LCD_SetCursor(uint16_t x1, uint16_t y1, uint16_t x2,uint16_t y2);
|
|
||||||
|
|
||||||
void LCD_Init(void);
|
|
||||||
void LCD_SetCursor(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend);
|
|
||||||
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color);
|
|
||||||
|
|
||||||
void Backlight_Init(void);
|
|
||||||
void Set_Backlight(uint8_t Light);
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#include "SD_Card.h"
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
#include "LVGL_Driver.h"
|
|
||||||
#include "LVGL_Example.h"
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
Flash_test();
|
|
||||||
LCD_Init();
|
|
||||||
Lvgl_Init();
|
|
||||||
SD_Init();
|
|
||||||
|
|
||||||
Lvgl_Example1();
|
|
||||||
// lv_demo_widgets();
|
|
||||||
// lv_demo_benchmark();
|
|
||||||
// lv_demo_keypad_encoder();
|
|
||||||
// lv_demo_music();
|
|
||||||
// lv_demo_stress();
|
|
||||||
|
|
||||||
Wireless_Test2();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
Timer_Loop();
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
| File : LVGL_Driver.c
|
|
||||||
|
|
||||||
| help :
|
|
||||||
The provided LVGL library file must be installed first
|
|
||||||
******************************************************************************/
|
|
||||||
#include "LVGL_Driver.h"
|
|
||||||
|
|
||||||
static lv_disp_draw_buf_t draw_buf;
|
|
||||||
static lv_color_t buf1[ LVGL_BUF_LEN ];
|
|
||||||
static lv_color_t buf2[ LVGL_BUF_LEN ];
|
|
||||||
// static lv_color_t* buf1 = (lv_color_t*) heap_caps_malloc(LVGL_BUF_LEN, MALLOC_CAP_SPIRAM);
|
|
||||||
// static lv_color_t* buf2 = (lv_color_t*) heap_caps_malloc(LVGL_BUF_LEN, MALLOC_CAP_SPIRAM);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Serial debugging */
|
|
||||||
void Lvgl_print(const char * buf)
|
|
||||||
{
|
|
||||||
// Serial.printf(buf);
|
|
||||||
// Serial.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Display flushing
|
|
||||||
Displays LVGL content on the LCD
|
|
||||||
This function implements associating LVGL data to the LCD screen
|
|
||||||
*/
|
|
||||||
void Lvgl_Display_LCD( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p )
|
|
||||||
{
|
|
||||||
LCD_addWindow(area->x1, area->y1, area->x2, area->y2, ( uint16_t *)&color_p->full);
|
|
||||||
lv_disp_flush_ready( disp_drv );
|
|
||||||
}
|
|
||||||
/*Read the touchpad*/
|
|
||||||
void Lvgl_Touchpad_Read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data )
|
|
||||||
{
|
|
||||||
// NULL
|
|
||||||
}
|
|
||||||
void example_increase_lvgl_tick(void *arg)
|
|
||||||
{
|
|
||||||
/* Tell LVGL how many milliseconds has elapsed */
|
|
||||||
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
void Lvgl_Init(void)
|
|
||||||
{
|
|
||||||
lv_init();
|
|
||||||
lv_disp_draw_buf_init( &draw_buf, buf1, buf2, LVGL_BUF_LEN);
|
|
||||||
|
|
||||||
/*Initialize the display*/
|
|
||||||
static lv_disp_drv_t disp_drv;
|
|
||||||
lv_disp_drv_init( &disp_drv );
|
|
||||||
/*Change the following line to your display resolution*/
|
|
||||||
disp_drv.hor_res = LVGL_WIDTH;
|
|
||||||
disp_drv.ver_res = LVGL_HEIGHT;
|
|
||||||
disp_drv.flush_cb = Lvgl_Display_LCD;
|
|
||||||
disp_drv.full_refresh = 1; /**< 1: Always make the whole screen redrawn*/
|
|
||||||
disp_drv.draw_buf = &draw_buf;
|
|
||||||
lv_disp_drv_register( &disp_drv );
|
|
||||||
|
|
||||||
/*Initialize the (dummy) input device driver*/
|
|
||||||
static lv_indev_drv_t indev_drv;
|
|
||||||
lv_indev_drv_init( &indev_drv );
|
|
||||||
indev_drv.type = LV_INDEV_TYPE_POINTER;
|
|
||||||
indev_drv.read_cb = Lvgl_Touchpad_Read;
|
|
||||||
lv_indev_drv_register( &indev_drv );
|
|
||||||
|
|
||||||
/* Create simple label */
|
|
||||||
lv_obj_t *label = lv_label_create( lv_scr_act() );
|
|
||||||
lv_label_set_text( label, "Hello Ardino and LVGL!");
|
|
||||||
lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );
|
|
||||||
|
|
||||||
const esp_timer_create_args_t lvgl_tick_timer_args = {
|
|
||||||
.callback = &example_increase_lvgl_tick,
|
|
||||||
.name = "lvgl_tick"
|
|
||||||
};
|
|
||||||
esp_timer_handle_t lvgl_tick_timer = NULL;
|
|
||||||
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
|
|
||||||
esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
|
|
||||||
|
|
||||||
}
|
|
||||||
void Timer_Loop(void)
|
|
||||||
{
|
|
||||||
lv_timer_handler(); /* let the GUI do its work */
|
|
||||||
// delay( 5 );
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <lvgl.h>
|
|
||||||
#include <lv_conf.h>
|
|
||||||
#include <demos/lv_demos.h>
|
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
|
|
||||||
#define LVGL_WIDTH (LCD_WIDTH )
|
|
||||||
#define LVGL_HEIGHT LCD_HEIGHT
|
|
||||||
#define LVGL_BUF_LEN (LVGL_WIDTH * LVGL_HEIGHT / 20)
|
|
||||||
|
|
||||||
#define EXAMPLE_LVGL_TICK_PERIOD_MS 5
|
|
||||||
|
|
||||||
|
|
||||||
void Lvgl_print(const char * buf);
|
|
||||||
void Lvgl_Display_LCD( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p ); // Displays LVGL content on the LCD. This function implements associating LVGL data to the LCD screen
|
|
||||||
void Lvgl_Touchpad_Read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data ); // Read the touchpad
|
|
||||||
void example_increase_lvgl_tick(void *arg);
|
|
||||||
|
|
||||||
void Lvgl_Init(void);
|
|
||||||
void Timer_Loop(void);
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
#include "LVGL_Example.h"
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* TYPEDEFS
|
|
||||||
**********************/
|
|
||||||
typedef enum {
|
|
||||||
DISP_SMALL,
|
|
||||||
DISP_MEDIUM,
|
|
||||||
DISP_LARGE,
|
|
||||||
} disp_size_t;
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* STATIC PROTOTYPES
|
|
||||||
**********************/
|
|
||||||
static void Onboard_create(lv_obj_t * parent);
|
|
||||||
static void color_changer_create(lv_obj_t * parent);
|
|
||||||
|
|
||||||
static void color_changer_event_cb(lv_event_t * e);
|
|
||||||
static void color_event_cb(lv_event_t * e);
|
|
||||||
static void ta_event_cb(lv_event_t * e);
|
|
||||||
static void birthday_event_cb(lv_event_t * e);
|
|
||||||
static void calendar_event_cb(lv_event_t * e);
|
|
||||||
void IRAM_ATTR example1_increase_lvgl_tick(lv_timer_t * t);
|
|
||||||
/**********************
|
|
||||||
* STATIC VARIABLES
|
|
||||||
**********************/
|
|
||||||
static disp_size_t disp_size;
|
|
||||||
|
|
||||||
static lv_obj_t * tv;
|
|
||||||
static lv_obj_t * calendar;
|
|
||||||
lv_style_t style_text_muted;
|
|
||||||
lv_style_t style_title;
|
|
||||||
static lv_style_t style_icon;
|
|
||||||
static lv_style_t style_bullet;
|
|
||||||
|
|
||||||
static lv_obj_t * chart1;
|
|
||||||
static lv_obj_t * chart2;
|
|
||||||
static lv_obj_t * chart3;
|
|
||||||
|
|
||||||
static lv_chart_series_t * ser1;
|
|
||||||
static lv_chart_series_t * ser2;
|
|
||||||
static lv_chart_series_t * ser3;
|
|
||||||
static lv_chart_series_t * ser4;
|
|
||||||
|
|
||||||
static const lv_font_t * font_large;
|
|
||||||
static const lv_font_t * font_normal;
|
|
||||||
|
|
||||||
static lv_timer_t * auto_step_timer;
|
|
||||||
static lv_color_t original_screen_bg_color;
|
|
||||||
|
|
||||||
static lv_timer_t * meter2_timer;
|
|
||||||
|
|
||||||
lv_obj_t * SD_Size;
|
|
||||||
lv_obj_t * FlashSize;
|
|
||||||
lv_obj_t * Board_angle;
|
|
||||||
lv_obj_t * RTC_Time;
|
|
||||||
lv_obj_t * Wireless_Scan;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void IRAM_ATTR auto_switch(lv_timer_t * t)
|
|
||||||
{
|
|
||||||
uint16_t page = lv_tabview_get_tab_act(tv);
|
|
||||||
|
|
||||||
if (page == 0) {
|
|
||||||
lv_tabview_set_act(tv, 1, LV_ANIM_ON);
|
|
||||||
} else if (page == 3) {
|
|
||||||
lv_tabview_set_act(tv, 2, LV_ANIM_ON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Lvgl_Example1(void){
|
|
||||||
|
|
||||||
disp_size = DISP_SMALL;
|
|
||||||
|
|
||||||
font_large = LV_FONT_DEFAULT;
|
|
||||||
font_normal = LV_FONT_DEFAULT;
|
|
||||||
|
|
||||||
lv_coord_t tab_h;
|
|
||||||
tab_h = 45;
|
|
||||||
#if LV_FONT_MONTSERRAT_18
|
|
||||||
font_large = &lv_font_montserrat_18;
|
|
||||||
#else
|
|
||||||
LV_LOG_WARN("LV_FONT_MONTSERRAT_18 is not enabled for the widgets demo. Using LV_FONT_DEFAULT instead.");
|
|
||||||
#endif
|
|
||||||
#if LV_FONT_MONTSERRAT_12
|
|
||||||
font_normal = &lv_font_montserrat_12;
|
|
||||||
#else
|
|
||||||
LV_LOG_WARN("LV_FONT_MONTSERRAT_12 is not enabled for the widgets demo. Using LV_FONT_DEFAULT instead.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
lv_style_init(&style_text_muted);
|
|
||||||
lv_style_set_text_opa(&style_text_muted, LV_OPA_90);
|
|
||||||
|
|
||||||
lv_style_init(&style_title);
|
|
||||||
lv_style_set_text_font(&style_title, font_large);
|
|
||||||
|
|
||||||
lv_style_init(&style_icon);
|
|
||||||
lv_style_set_text_color(&style_icon, lv_theme_get_color_primary(NULL));
|
|
||||||
lv_style_set_text_font(&style_icon, font_large);
|
|
||||||
|
|
||||||
lv_style_init(&style_bullet);
|
|
||||||
lv_style_set_border_width(&style_bullet, 0);
|
|
||||||
lv_style_set_radius(&style_bullet, LV_RADIUS_CIRCLE);
|
|
||||||
|
|
||||||
tv = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, tab_h);
|
|
||||||
|
|
||||||
lv_obj_set_style_text_font(lv_scr_act(), font_normal, 0);
|
|
||||||
|
|
||||||
|
|
||||||
lv_obj_t * t1 = lv_tabview_add_tab(tv, "Onboard");
|
|
||||||
|
|
||||||
|
|
||||||
Onboard_create(t1);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lvgl_Example1_close(void)
|
|
||||||
{
|
|
||||||
/*Delete all animation*/
|
|
||||||
lv_anim_del(NULL, NULL);
|
|
||||||
|
|
||||||
lv_timer_del(meter2_timer);
|
|
||||||
meter2_timer = NULL;
|
|
||||||
|
|
||||||
lv_obj_clean(lv_scr_act());
|
|
||||||
|
|
||||||
lv_style_reset(&style_text_muted);
|
|
||||||
lv_style_reset(&style_title);
|
|
||||||
lv_style_reset(&style_icon);
|
|
||||||
lv_style_reset(&style_bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* STATIC FUNCTIONS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
static void Onboard_create(lv_obj_t * parent)
|
|
||||||
{
|
|
||||||
|
|
||||||
/*Create a panel*/
|
|
||||||
lv_obj_t * panel1 = lv_obj_create(parent);
|
|
||||||
lv_obj_set_height(panel1, LV_SIZE_CONTENT);
|
|
||||||
|
|
||||||
lv_obj_t * panel1_title = lv_label_create(panel1);
|
|
||||||
lv_label_set_text(panel1_title, "Onboard parameter");
|
|
||||||
lv_obj_add_style(panel1_title, &style_title, 0);
|
|
||||||
|
|
||||||
lv_obj_t * SD_label = lv_label_create(panel1);
|
|
||||||
lv_label_set_text(SD_label, "SD Card");
|
|
||||||
lv_obj_add_style(SD_label, &style_text_muted, 0);
|
|
||||||
|
|
||||||
SD_Size = lv_textarea_create(panel1);
|
|
||||||
lv_textarea_set_one_line(SD_Size, true);
|
|
||||||
lv_textarea_set_placeholder_text(SD_Size, "SD Size");
|
|
||||||
lv_obj_add_event_cb(SD_Size, ta_event_cb, LV_EVENT_ALL, NULL);
|
|
||||||
|
|
||||||
lv_obj_t * Flash_label = lv_label_create(panel1);
|
|
||||||
lv_label_set_text(Flash_label, "Flash Size");
|
|
||||||
lv_obj_add_style(Flash_label, &style_text_muted, 0);
|
|
||||||
|
|
||||||
FlashSize = lv_textarea_create(panel1);
|
|
||||||
lv_textarea_set_one_line(FlashSize, true);
|
|
||||||
lv_textarea_set_placeholder_text(FlashSize, "Flash Size");
|
|
||||||
lv_obj_add_event_cb(FlashSize, ta_event_cb, LV_EVENT_ALL, NULL);
|
|
||||||
|
|
||||||
lv_obj_t * Wireless_label = lv_label_create(panel1);
|
|
||||||
lv_label_set_text(Wireless_label, "Wireless scan");
|
|
||||||
lv_obj_add_style(Wireless_label, &style_text_muted, 0);
|
|
||||||
|
|
||||||
Wireless_Scan = lv_textarea_create(panel1);
|
|
||||||
lv_textarea_set_one_line(Wireless_Scan, true);
|
|
||||||
lv_textarea_set_placeholder_text(Wireless_Scan, "Wireless number");
|
|
||||||
lv_obj_add_event_cb(Wireless_Scan, ta_event_cb, LV_EVENT_ALL, NULL);
|
|
||||||
|
|
||||||
// 器件布局
|
|
||||||
static lv_coord_t grid_main_col_dsc[] = {LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
|
|
||||||
static lv_coord_t grid_main_row_dsc[] = {LV_GRID_CONTENT, LV_GRID_CONTENT, LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST};
|
|
||||||
lv_obj_set_grid_dsc_array(parent, grid_main_col_dsc, grid_main_row_dsc);
|
|
||||||
|
|
||||||
|
|
||||||
/*Create the top panel*/
|
|
||||||
static lv_coord_t grid_1_col_dsc[] = {LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
|
|
||||||
static lv_coord_t grid_1_row_dsc[] = {LV_GRID_CONTENT, /*Avatar*/
|
|
||||||
LV_GRID_CONTENT, /*Name*/
|
|
||||||
LV_GRID_CONTENT, /*Description*/
|
|
||||||
LV_GRID_CONTENT, /*Email*/
|
|
||||||
LV_GRID_CONTENT, /*Phone number*/
|
|
||||||
LV_GRID_CONTENT, /*Button1*/
|
|
||||||
LV_GRID_CONTENT, /*Button2*/
|
|
||||||
LV_GRID_TEMPLATE_LAST
|
|
||||||
};
|
|
||||||
|
|
||||||
lv_obj_set_grid_dsc_array(panel1, grid_1_col_dsc, grid_1_row_dsc);
|
|
||||||
|
|
||||||
|
|
||||||
static lv_coord_t grid_2_col_dsc[] = {LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
|
|
||||||
static lv_coord_t grid_2_row_dsc[] = {
|
|
||||||
LV_GRID_CONTENT, /*Title*/
|
|
||||||
5, /*Separator*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_CONTENT, /*Box title*/
|
|
||||||
40, /*Box*/
|
|
||||||
LV_GRID_TEMPLATE_LAST
|
|
||||||
};
|
|
||||||
|
|
||||||
// lv_obj_set_grid_dsc_array(panel2, grid_2_col_dsc, grid_2_row_dsc);
|
|
||||||
// lv_obj_set_grid_dsc_array(panel3, grid_2_col_dsc, grid_2_row_dsc);
|
|
||||||
|
|
||||||
lv_obj_set_grid_cell(panel1, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_START, 0, 1);
|
|
||||||
lv_obj_set_grid_dsc_array(panel1, grid_2_col_dsc, grid_2_row_dsc);
|
|
||||||
lv_obj_set_grid_cell(panel1_title, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_CENTER, 0, 1);
|
|
||||||
lv_obj_set_grid_cell(SD_label, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 2, 1);
|
|
||||||
lv_obj_set_grid_cell(SD_Size, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_CENTER, 3, 1);
|
|
||||||
lv_obj_set_grid_cell(Flash_label, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 4, 1);
|
|
||||||
lv_obj_set_grid_cell(FlashSize, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_CENTER, 5, 1);
|
|
||||||
lv_obj_set_grid_cell(Wireless_label, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 6, 1);
|
|
||||||
lv_obj_set_grid_cell(Wireless_Scan, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_CENTER, 7, 1);
|
|
||||||
|
|
||||||
// 器件布局 END
|
|
||||||
|
|
||||||
auto_step_timer = lv_timer_create(example1_increase_lvgl_tick, 100, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR example1_increase_lvgl_tick(lv_timer_t * t)
|
|
||||||
{
|
|
||||||
char buf[100]={0};
|
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), "%d MB\r\n", SDCard_Size);
|
|
||||||
lv_textarea_set_placeholder_text(SD_Size, buf);
|
|
||||||
snprintf(buf, sizeof(buf), "%d MB\r\n", Flash_Size);
|
|
||||||
lv_textarea_set_placeholder_text(FlashSize, buf);
|
|
||||||
if(Scan_finish)
|
|
||||||
snprintf(buf, sizeof(buf), "W: %d B: %d OK.\r\n",WIFI_NUM,BLE_NUM);
|
|
||||||
// snprintf(buf, sizeof(buf), "WIFI: %d ..OK.\r\n",WIFI_NUM);
|
|
||||||
else
|
|
||||||
snprintf(buf, sizeof(buf), "W: %d B: %d\r\n",WIFI_NUM,BLE_NUM);
|
|
||||||
// snprintf(buf, sizeof(buf), "WIFI: %d \r\n",WIFI_NUM);
|
|
||||||
lv_textarea_set_placeholder_text(Wireless_Scan, buf);
|
|
||||||
}
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
static void color_changer_create(lv_obj_t * parent)
|
|
||||||
{
|
|
||||||
static lv_palette_t palette[] = {
|
|
||||||
LV_PALETTE_BLUE, LV_PALETTE_GREEN, LV_PALETTE_BLUE_GREY, LV_PALETTE_ORANGE,
|
|
||||||
LV_PALETTE_RED, LV_PALETTE_PURPLE, LV_PALETTE_TEAL, _LV_PALETTE_LAST
|
|
||||||
};
|
|
||||||
|
|
||||||
lv_obj_t * color_cont = lv_obj_create(parent);
|
|
||||||
lv_obj_remove_style_all(color_cont);
|
|
||||||
lv_obj_set_flex_flow(color_cont, LV_FLEX_FLOW_ROW);
|
|
||||||
lv_obj_set_flex_align(color_cont, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
||||||
lv_obj_add_flag(color_cont, LV_OBJ_FLAG_FLOATING);
|
|
||||||
|
|
||||||
lv_obj_set_style_bg_color(color_cont, lv_color_white(), 0);
|
|
||||||
lv_obj_set_style_pad_right(color_cont, disp_size == DISP_SMALL ? LV_DPX(47) : LV_DPX(55), 0);
|
|
||||||
lv_obj_set_style_bg_opa(color_cont, LV_OPA_COVER, 0);
|
|
||||||
lv_obj_set_style_radius(color_cont, LV_RADIUS_CIRCLE, 0);
|
|
||||||
|
|
||||||
if(disp_size == DISP_SMALL) lv_obj_set_size(color_cont, LV_DPX(52), LV_DPX(52));
|
|
||||||
else lv_obj_set_size(color_cont, LV_DPX(60), LV_DPX(60));
|
|
||||||
|
|
||||||
lv_obj_align(color_cont, LV_ALIGN_BOTTOM_RIGHT, - LV_DPX(10), - LV_DPX(10));
|
|
||||||
|
|
||||||
uint32_t i;
|
|
||||||
for(i = 0; palette[i] != _LV_PALETTE_LAST; i++) {
|
|
||||||
lv_obj_t * c = lv_btn_create(color_cont);
|
|
||||||
lv_obj_set_style_bg_color(c, lv_palette_main(palette[i]), 0);
|
|
||||||
lv_obj_set_style_radius(c, LV_RADIUS_CIRCLE, 0);
|
|
||||||
lv_obj_set_style_opa(c, LV_OPA_TRANSP, 0);
|
|
||||||
lv_obj_set_size(c, 20, 20);
|
|
||||||
lv_obj_add_event_cb(c, color_event_cb, LV_EVENT_ALL, &palette[i]);
|
|
||||||
lv_obj_clear_flag(c, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t * btn = lv_btn_create(parent);
|
|
||||||
lv_obj_add_flag(btn, LV_OBJ_FLAG_FLOATING | LV_OBJ_FLAG_CLICKABLE);
|
|
||||||
lv_obj_set_style_bg_color(btn, lv_color_white(), LV_STATE_CHECKED);
|
|
||||||
lv_obj_set_style_pad_all(btn, 10, 0);
|
|
||||||
lv_obj_set_style_radius(btn, LV_RADIUS_CIRCLE, 0);
|
|
||||||
lv_obj_add_event_cb(btn, color_changer_event_cb, LV_EVENT_ALL, color_cont);
|
|
||||||
lv_obj_set_style_shadow_width(btn, 0, 0);
|
|
||||||
lv_obj_set_style_bg_img_src(btn, LV_SYMBOL_TINT, 0);
|
|
||||||
|
|
||||||
if(disp_size == DISP_SMALL) {
|
|
||||||
lv_obj_set_size(btn, LV_DPX(42), LV_DPX(42));
|
|
||||||
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -LV_DPX(15), -LV_DPX(15));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lv_obj_set_size(btn, LV_DPX(50), LV_DPX(50));
|
|
||||||
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -LV_DPX(15), -LV_DPX(15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void color_changer_anim_cb(void * var, int32_t v)
|
|
||||||
{
|
|
||||||
lv_obj_t * obj =(lv_obj_t *) var;
|
|
||||||
lv_coord_t max_w = lv_obj_get_width(lv_obj_get_parent(obj)) - LV_DPX(20);
|
|
||||||
lv_coord_t w;
|
|
||||||
|
|
||||||
if(disp_size == DISP_SMALL) {
|
|
||||||
w = lv_map(v, 0, 256, LV_DPX(52), max_w);
|
|
||||||
lv_obj_set_width(obj, w);
|
|
||||||
lv_obj_align(obj, LV_ALIGN_BOTTOM_RIGHT, - LV_DPX(10), - LV_DPX(10));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
w = lv_map(v, 0, 256, LV_DPX(60), max_w);
|
|
||||||
lv_obj_set_width(obj, w);
|
|
||||||
lv_obj_align(obj, LV_ALIGN_BOTTOM_RIGHT, - LV_DPX(10), - LV_DPX(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(v > LV_OPA_COVER) v = LV_OPA_COVER;
|
|
||||||
|
|
||||||
uint32_t i;
|
|
||||||
for(i = 0; i < lv_obj_get_child_cnt(obj); i++) {
|
|
||||||
lv_obj_set_style_opa(lv_obj_get_child(obj, i), v, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void color_changer_event_cb(lv_event_t * e)
|
|
||||||
{
|
|
||||||
if(lv_event_get_code(e) == LV_EVENT_CLICKED) {
|
|
||||||
lv_obj_t * color_cont = (lv_obj_t *)lv_event_get_user_data(e);
|
|
||||||
if(lv_obj_get_width(color_cont) < LV_HOR_RES / 2) {
|
|
||||||
lv_anim_t a;
|
|
||||||
lv_anim_init(&a);
|
|
||||||
lv_anim_set_var(&a, color_cont);
|
|
||||||
lv_anim_set_exec_cb(&a, color_changer_anim_cb);
|
|
||||||
lv_anim_set_values(&a, 0, 256);
|
|
||||||
lv_anim_set_time(&a, 200);
|
|
||||||
lv_anim_start(&a);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lv_anim_t a;
|
|
||||||
lv_anim_init(&a);
|
|
||||||
lv_anim_set_var(&a, color_cont);
|
|
||||||
lv_anim_set_exec_cb(&a, color_changer_anim_cb);
|
|
||||||
lv_anim_set_values(&a, 256, 0);
|
|
||||||
lv_anim_set_time(&a, 200);
|
|
||||||
lv_anim_start(&a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static void color_event_cb(lv_event_t * e)
|
|
||||||
{
|
|
||||||
lv_event_code_t code = lv_event_get_code(e);
|
|
||||||
lv_obj_t * obj = lv_event_get_target(e);
|
|
||||||
|
|
||||||
if(code == LV_EVENT_FOCUSED) {
|
|
||||||
lv_obj_t * color_cont = lv_obj_get_parent(obj);
|
|
||||||
if(lv_obj_get_width(color_cont) < LV_HOR_RES / 2) {
|
|
||||||
lv_anim_t a;
|
|
||||||
lv_anim_init(&a);
|
|
||||||
lv_anim_set_var(&a, color_cont);
|
|
||||||
lv_anim_set_exec_cb(&a, color_changer_anim_cb);
|
|
||||||
lv_anim_set_values(&a, 0, 256);
|
|
||||||
lv_anim_set_time(&a, 200);
|
|
||||||
lv_anim_start(&a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(code == LV_EVENT_CLICKED) {
|
|
||||||
lv_palette_t * palette_primary = (lv_palette_t *)lv_event_get_user_data(e);
|
|
||||||
lv_palette_t palette_secondary =(lv_palette_t) ((*palette_primary) + 3); /*Use another palette as secondary*/
|
|
||||||
if(palette_secondary >= _LV_PALETTE_LAST) palette_secondary =(lv_palette_t)(0);
|
|
||||||
#if LV_USE_THEME_DEFAULT
|
|
||||||
lv_theme_default_init(NULL, lv_palette_main(*palette_primary), lv_palette_main(palette_secondary),
|
|
||||||
LV_THEME_DEFAULT_DARK, font_normal);
|
|
||||||
#endif
|
|
||||||
lv_color_t color = lv_palette_main(*palette_primary);
|
|
||||||
lv_style_set_text_color(&style_icon, color);
|
|
||||||
lv_chart_set_series_color(chart1, ser1, color);
|
|
||||||
lv_chart_set_series_color(chart2, ser3, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void ta_event_cb(lv_event_t * e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void birthday_event_cb(lv_event_t * e)
|
|
||||||
{
|
|
||||||
lv_event_code_t code = lv_event_get_code(e);
|
|
||||||
lv_obj_t * ta = lv_event_get_target(e);
|
|
||||||
|
|
||||||
if(code == LV_EVENT_FOCUSED) {
|
|
||||||
if(lv_indev_get_type(lv_indev_get_act()) == LV_INDEV_TYPE_POINTER) {
|
|
||||||
if(calendar == NULL) {
|
|
||||||
lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
|
|
||||||
calendar = lv_calendar_create(lv_layer_top());
|
|
||||||
lv_obj_set_style_bg_opa(lv_layer_top(), LV_OPA_50, 0);
|
|
||||||
lv_obj_set_style_bg_color(lv_layer_top(), lv_palette_main(LV_PALETTE_GREY), 0);
|
|
||||||
if(disp_size == DISP_SMALL) lv_obj_set_size(calendar, 180, 200);
|
|
||||||
else if(disp_size == DISP_MEDIUM) lv_obj_set_size(calendar, 200, 220);
|
|
||||||
else lv_obj_set_size(calendar, 300, 330);
|
|
||||||
lv_calendar_set_showed_date(calendar, 1990, 01);
|
|
||||||
lv_obj_align(calendar, LV_ALIGN_CENTER, 0, 30);
|
|
||||||
lv_obj_add_event_cb(calendar, calendar_event_cb, LV_EVENT_ALL, ta);
|
|
||||||
|
|
||||||
lv_calendar_header_dropdown_create(calendar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void calendar_event_cb(lv_event_t * e)
|
|
||||||
{
|
|
||||||
lv_event_code_t code = lv_event_get_code(e);
|
|
||||||
lv_obj_t * ta = (lv_obj_t *)lv_event_get_user_data(e);
|
|
||||||
lv_obj_t * obj = (lv_obj_t *)lv_event_get_current_target(e);
|
|
||||||
if(code == LV_EVENT_VALUE_CHANGED) {
|
|
||||||
lv_calendar_date_t d;
|
|
||||||
lv_calendar_get_pressed_date(obj, &d);
|
|
||||||
char buf[32];
|
|
||||||
lv_snprintf(buf, sizeof(buf), "%02d.%02d.%d", d.day, d.month, d.year);
|
|
||||||
lv_textarea_set_text(ta, buf);
|
|
||||||
|
|
||||||
lv_obj_del(calendar);
|
|
||||||
calendar = NULL;
|
|
||||||
lv_obj_clear_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
|
|
||||||
lv_obj_set_style_bg_opa(lv_layer_top(), LV_OPA_TRANSP, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "LVGL_Driver.h"
|
|
||||||
#include "SD_Card.h"
|
|
||||||
#include "Wireless.h"
|
|
||||||
|
|
||||||
#define EXAMPLE1_LVGL_TICK_PERIOD_MS 1000
|
|
||||||
|
|
||||||
|
|
||||||
void Lvgl_Example1(void);
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
#include "SD_Card.h"
|
|
||||||
|
|
||||||
uint16_t SDCard_Size;
|
|
||||||
uint16_t Flash_Size;
|
|
||||||
|
|
||||||
bool SD_Init() {
|
|
||||||
// SD
|
|
||||||
pinMode(SD_CS, OUTPUT);
|
|
||||||
digitalWrite(SD_CS, HIGH);
|
|
||||||
if (SD.begin(SD_CS, SPI)) {
|
|
||||||
printf("SD card initialization successful!\r\n");
|
|
||||||
} else {
|
|
||||||
printf("SD card initialization failed!\r\n");
|
|
||||||
}
|
|
||||||
uint8_t cardType = SD.cardType();
|
|
||||||
if(cardType == CARD_NONE){
|
|
||||||
printf("No SD card attached\r\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
printf("SD Card Type: ");
|
|
||||||
if(cardType == CARD_MMC){
|
|
||||||
printf("MMC\r\n");
|
|
||||||
} else if(cardType == CARD_SD){
|
|
||||||
printf("SDSC\r\n");
|
|
||||||
} else if(cardType == CARD_SDHC){
|
|
||||||
printf("SDHC\r\n");
|
|
||||||
} else {
|
|
||||||
printf("UNKNOWN\r\n");
|
|
||||||
}
|
|
||||||
uint64_t totalBytes = SD.totalBytes();
|
|
||||||
uint64_t usedBytes = SD.usedBytes();
|
|
||||||
SDCard_Size = totalBytes/(1024*1024);
|
|
||||||
printf("Total space: %llu\n", totalBytes);
|
|
||||||
printf("Used space: %llu\n", usedBytes);
|
|
||||||
printf("Free space: %llu\n", totalBytes - usedBytes);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool File_Search(const char* directory, const char* fileName)
|
|
||||||
{
|
|
||||||
File Path = SD.open(directory);
|
|
||||||
if (!Path) {
|
|
||||||
printf("Path: <%s> does not exist\r\n",directory);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
File file = Path.openNextFile();
|
|
||||||
while (file) {
|
|
||||||
if (strcmp(file.name(), fileName) == 0) {
|
|
||||||
if (strcmp(directory, "/") == 0)
|
|
||||||
printf("File '%s%s' found in root directory.\r\n",directory,fileName);
|
|
||||||
else
|
|
||||||
printf("File '%s/%s' found in root directory.\r\n",directory,fileName);
|
|
||||||
Path.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
file = Path.openNextFile();
|
|
||||||
}
|
|
||||||
if (strcmp(directory, "/") == 0)
|
|
||||||
printf("File '%s%s' not found in root directory.\r\n",directory,fileName);
|
|
||||||
else
|
|
||||||
printf("File '%s/%s' not found in root directory.\r\n",directory,fileName);
|
|
||||||
Path.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles)
|
|
||||||
{
|
|
||||||
File Path = SD.open(directory);
|
|
||||||
if (!Path) {
|
|
||||||
printf("Path: <%s> does not exist\r\n",directory);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t fileCount = 0;
|
|
||||||
char filePath[100];
|
|
||||||
File file = Path.openNextFile();
|
|
||||||
while (file && fileCount < maxFiles) {
|
|
||||||
if (!file.isDirectory() && strstr(file.name(), fileExtension)) {
|
|
||||||
strncpy(File_Name[fileCount], file.name(), sizeof(File_Name[fileCount]));
|
|
||||||
if (strcmp(directory, "/") == 0) {
|
|
||||||
snprintf(filePath, 100, "%s%s", directory, file.name());
|
|
||||||
} else {
|
|
||||||
snprintf(filePath, 100, "%s/%s", directory, file.name());
|
|
||||||
}
|
|
||||||
printf("File found: %s\r\n", filePath);
|
|
||||||
fileCount++;
|
|
||||||
}
|
|
||||||
file = Path.openNextFile();
|
|
||||||
}
|
|
||||||
Path.close();
|
|
||||||
if (fileCount > 0) {
|
|
||||||
printf(" %d <%s> files were retrieved\r\n",fileCount,fileExtension);
|
|
||||||
return fileCount;
|
|
||||||
} else {
|
|
||||||
printf("No files with extension '%s' found in directory: %s\r\n", fileExtension, directory);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_file_extension(char *file_name) {
|
|
||||||
char *last_dot = strrchr(file_name, '.');
|
|
||||||
if (last_dot != NULL) {
|
|
||||||
*last_dot = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flash_test()
|
|
||||||
{
|
|
||||||
printf("/********** RAM Test**********/\r\n");
|
|
||||||
// Get Flash size
|
|
||||||
uint32_t flashSize = ESP.getFlashChipSize();
|
|
||||||
Flash_Size = flashSize/1024/1024;
|
|
||||||
printf("Flash size: %d MB \r\n", flashSize/1024/1024);
|
|
||||||
|
|
||||||
printf("/******* RAM Test Over********/\r\n\r\n");
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include <cstring>
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
#include "SD.h"
|
|
||||||
#include "FS.h"
|
|
||||||
|
|
||||||
// Digital I/O used
|
|
||||||
#define SD_CS 4 // SD_D3:
|
|
||||||
|
|
||||||
extern uint16_t SDCard_Size;
|
|
||||||
extern uint16_t Flash_Size;
|
|
||||||
|
|
||||||
bool SD_Init();
|
|
||||||
void Flash_test();
|
|
||||||
|
|
||||||
bool File_Search(const char* directory, const char* fileName);
|
|
||||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles);
|
|
||||||
void remove_file_extension(char *file_name);
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#include "Wireless.h"
|
|
||||||
|
|
||||||
|
|
||||||
bool WIFI_Connection = 0;
|
|
||||||
uint8_t WIFI_NUM = 0;
|
|
||||||
uint8_t BLE_NUM = 0;
|
|
||||||
bool Scan_finish = 0;
|
|
||||||
int wifi_scan_number()
|
|
||||||
{
|
|
||||||
printf("/**********WiFi Test**********/\r\n");
|
|
||||||
// Set WiFi to station mode and disconnect from an AP if it was previously connected.
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.setSleep(true);
|
|
||||||
// WiFi.scanNetworks will return the number of networks found.
|
|
||||||
int count = WiFi.scanNetworks();
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
printf("No WIFI device was scanned\r\n");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
printf("Scanned %d Wi-Fi devices\r\n",count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the scan result to free memory for code below.
|
|
||||||
WiFi.disconnect(true);
|
|
||||||
WiFi.scanDelete();
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
vTaskDelay(100);
|
|
||||||
printf("/*******WiFi Test Over********/\r\n\r\n");
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
int ble_scan_number()
|
|
||||||
{
|
|
||||||
printf("/**********BLE Test**********/\r\n");
|
|
||||||
BLEDevice::init("ESP32");
|
|
||||||
BLEScan* pBLEScan = BLEDevice::getScan();
|
|
||||||
pBLEScan->setActiveScan(true);
|
|
||||||
|
|
||||||
BLEScanResults* foundDevices = pBLEScan->start(5);
|
|
||||||
int count = foundDevices->getCount();
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
printf("No Bluetooth device was scanned\r\n");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
printf("Scanned %d Bluetooth devices\r\n",count);
|
|
||||||
}
|
|
||||||
pBLEScan->stop();
|
|
||||||
pBLEScan->clearResults();
|
|
||||||
BLEDevice::deinit(true);
|
|
||||||
vTaskDelay(100);
|
|
||||||
printf("/**********BLE Test Over**********/\r\n\r\n");
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
extern char buffer[128]; /* Make sure buffer is enough for `sprintf` */
|
|
||||||
void Wireless_Test1(){
|
|
||||||
BLE_NUM = ble_scan_number(); // !!! Please note that continuing to use Bluetooth will result in allocation failure due to RAM usage, so pay attention to RAM usage when Bluetooth is turned on
|
|
||||||
WIFI_NUM = wifi_scan_number();
|
|
||||||
Scan_finish = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WirelessScanTask(void *parameter) {
|
|
||||||
BLE_NUM = ble_scan_number(); // !!! Please note that continuing to use Bluetooth will result in allocation failure due to RAM usage, so pay attention to RAM usage when Bluetooth is turned on
|
|
||||||
WIFI_NUM = wifi_scan_number();
|
|
||||||
Scan_finish = 1;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
}
|
|
||||||
void Wireless_Test2(){
|
|
||||||
xTaskCreatePinnedToCore(
|
|
||||||
WirelessScanTask,
|
|
||||||
"WirelessScanTask",
|
|
||||||
4096,
|
|
||||||
NULL,
|
|
||||||
2,
|
|
||||||
NULL,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "WiFi.h"
|
|
||||||
#include <BLEDevice.h>
|
|
||||||
#include <BLEScan.h>
|
|
||||||
|
|
||||||
extern bool WIFI_Connection;
|
|
||||||
extern uint8_t WIFI_NUM;
|
|
||||||
extern uint8_t BLE_NUM;
|
|
||||||
extern bool Scan_finish;
|
|
||||||
|
|
||||||
int wifi_scan_number();
|
|
||||||
int ble_scan_number();
|
|
||||||
void Wireless_Test1();
|
|
||||||
void Wireless_Test2();
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
#include "Display_ST7789.h"
|
|
||||||
|
|
||||||
#define SPI_WRITE(_dat) SPI.transfer(_dat)
|
|
||||||
#define SPI_WRITE_Word(_dat) SPI.transfer16(_dat)
|
|
||||||
#define SPI_WRITE_nByte(_SetData,_ReadData,_Size) SPI.transferBytes(_SetData,_ReadData,_Size)
|
|
||||||
void SPI_Init()
|
|
||||||
{
|
|
||||||
SPI.begin(EXAMPLE_PIN_NUM_SCLK,EXAMPLE_PIN_NUM_MISO,EXAMPLE_PIN_NUM_MOSI);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LCD_WriteCommand(uint8_t Cmd)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, LOW);
|
|
||||||
SPI_WRITE(Cmd);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
void LCD_WriteData(uint8_t Data)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI_WRITE(Data);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LCD_WriteData_Word(uint16_t Data)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI_WRITE_Word(Data);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
void LCD_WriteData_nbyte(uint8_t* SetData,uint8_t* ReadData,uint32_t Size)
|
|
||||||
{
|
|
||||||
SPI.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
|
||||||
SPI_WRITE_nByte(SetData, ReadData, Size);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LCD_Reset(void)
|
|
||||||
{
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
|
||||||
delay(50);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, LOW);
|
|
||||||
delay(50);
|
|
||||||
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, HIGH);
|
|
||||||
delay(50);
|
|
||||||
}
|
|
||||||
void LCD_Init(void)
|
|
||||||
{
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_CS, OUTPUT);
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_DC, OUTPUT);
|
|
||||||
pinMode(EXAMPLE_PIN_NUM_LCD_RST, OUTPUT);
|
|
||||||
Backlight_Init();
|
|
||||||
SPI_Init();
|
|
||||||
|
|
||||||
LCD_Reset();
|
|
||||||
//************* Start Initial Sequence **********//
|
|
||||||
LCD_WriteCommand(0x11);
|
|
||||||
delay(120);
|
|
||||||
LCD_WriteCommand(0x36);
|
|
||||||
if (HORIZONTAL)
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
else
|
|
||||||
LCD_WriteData(0x70);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x3A);
|
|
||||||
LCD_WriteData(0x05);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB0);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0xE8);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB2);
|
|
||||||
LCD_WriteData(0x0C);
|
|
||||||
LCD_WriteData(0x0C);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xB7);
|
|
||||||
LCD_WriteData(0x35);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xBB);
|
|
||||||
LCD_WriteData(0x35);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC0);
|
|
||||||
LCD_WriteData(0x2C);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC2);
|
|
||||||
LCD_WriteData(0x01);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC3);
|
|
||||||
LCD_WriteData(0x13);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC4);
|
|
||||||
LCD_WriteData(0x20);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xC6);
|
|
||||||
LCD_WriteData(0x0F);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xD0);
|
|
||||||
LCD_WriteData(0xA4);
|
|
||||||
LCD_WriteData(0xA1);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xD6);
|
|
||||||
LCD_WriteData(0xA1);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xE0);
|
|
||||||
LCD_WriteData(0xF0);
|
|
||||||
LCD_WriteData(0x00);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x04);
|
|
||||||
LCD_WriteData(0x05);
|
|
||||||
LCD_WriteData(0x29);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x3E);
|
|
||||||
LCD_WriteData(0x38);
|
|
||||||
LCD_WriteData(0x12);
|
|
||||||
LCD_WriteData(0x12);
|
|
||||||
LCD_WriteData(0x28);
|
|
||||||
LCD_WriteData(0x30);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0xE1);
|
|
||||||
LCD_WriteData(0xF0);
|
|
||||||
LCD_WriteData(0x07);
|
|
||||||
LCD_WriteData(0x0A);
|
|
||||||
LCD_WriteData(0x0D);
|
|
||||||
LCD_WriteData(0x0B);
|
|
||||||
LCD_WriteData(0x07);
|
|
||||||
LCD_WriteData(0x28);
|
|
||||||
LCD_WriteData(0x33);
|
|
||||||
LCD_WriteData(0x3E);
|
|
||||||
LCD_WriteData(0x36);
|
|
||||||
LCD_WriteData(0x14);
|
|
||||||
LCD_WriteData(0x14);
|
|
||||||
LCD_WriteData(0x29);
|
|
||||||
LCD_WriteData(0x32);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x21);
|
|
||||||
|
|
||||||
LCD_WriteCommand(0x11);
|
|
||||||
delay(120);
|
|
||||||
LCD_WriteCommand(0x29);
|
|
||||||
}
|
|
||||||
/******************************************************************************
|
|
||||||
function: Set the cursor position
|
|
||||||
parameter :
|
|
||||||
Xstart: Start uint16_t x coordinate
|
|
||||||
Ystart: Start uint16_t y coordinate
|
|
||||||
Xend : End uint16_t coordinates
|
|
||||||
Yend : End uint16_t coordinatesen
|
|
||||||
******************************************************************************/
|
|
||||||
void LCD_SetCursor(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend)
|
|
||||||
{
|
|
||||||
if (HORIZONTAL) {
|
|
||||||
// set the X coordinates
|
|
||||||
LCD_WriteCommand(0x2A);
|
|
||||||
LCD_WriteData(Xstart >> 8);
|
|
||||||
LCD_WriteData(Xstart + Offset_X);
|
|
||||||
LCD_WriteData(Xend >> 8);
|
|
||||||
LCD_WriteData(Xend + Offset_X);
|
|
||||||
|
|
||||||
// set the Y coordinates
|
|
||||||
LCD_WriteCommand(0x2B);
|
|
||||||
LCD_WriteData(Ystart >> 8);
|
|
||||||
LCD_WriteData(Ystart + Offset_Y);
|
|
||||||
LCD_WriteData(Yend >> 8);
|
|
||||||
LCD_WriteData(Yend + Offset_Y);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set the X coordinates
|
|
||||||
LCD_WriteCommand(0x2A);
|
|
||||||
LCD_WriteData(Ystart >> 8);
|
|
||||||
LCD_WriteData(Ystart + Offset_Y);
|
|
||||||
LCD_WriteData(Yend >> 8);
|
|
||||||
LCD_WriteData(Yend + Offset_Y);
|
|
||||||
// set the Y coordinates
|
|
||||||
LCD_WriteCommand(0x2B);
|
|
||||||
LCD_WriteData(Xstart >> 8);
|
|
||||||
LCD_WriteData(Xstart + Offset_X);
|
|
||||||
LCD_WriteData(Xend >> 8);
|
|
||||||
LCD_WriteData(Xend + Offset_X);
|
|
||||||
}
|
|
||||||
LCD_WriteCommand(0x2C);
|
|
||||||
}
|
|
||||||
/******************************************************************************
|
|
||||||
function: Refresh the image in an area
|
|
||||||
parameter :
|
|
||||||
Xstart: Start uint16_t x coordinate
|
|
||||||
Ystart: Start uint16_t y coordinate
|
|
||||||
Xend : End uint16_t coordinates
|
|
||||||
Yend : End uint16_t coordinates
|
|
||||||
color : Set the color
|
|
||||||
******************************************************************************/
|
|
||||||
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color)
|
|
||||||
{
|
|
||||||
// uint16_t i,j;
|
|
||||||
// LCD_SetCursor(Xstart, Ystart, Xend,Yend);
|
|
||||||
// uint16_t Show_Width = Xend - Xstart + 1;
|
|
||||||
// uint16_t Show_Height = Yend - Ystart + 1;
|
|
||||||
// for(i = 0; i < Show_Height; i++){
|
|
||||||
// for(j = 0; j < Show_Width; j++){
|
|
||||||
// LCD_WriteData_Word(color[(i*(Show_Width))+j]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
uint16_t Show_Width = Xend - Xstart + 1;
|
|
||||||
uint16_t Show_Height = Yend - Ystart + 1;
|
|
||||||
uint32_t numBytes = Show_Width * Show_Height * sizeof(uint16_t);
|
|
||||||
uint8_t Read_D[numBytes];
|
|
||||||
LCD_SetCursor(Xstart, Ystart, Xend, Yend);
|
|
||||||
LCD_WriteData_nbyte((uint8_t*)color, Read_D, numBytes);
|
|
||||||
}
|
|
||||||
// backlight
|
|
||||||
void Backlight_Init(void)
|
|
||||||
{
|
|
||||||
ledcAttach(EXAMPLE_PIN_NUM_BK_LIGHT, Frequency, Resolution);
|
|
||||||
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Set_Backlight(uint8_t Light) //
|
|
||||||
{
|
|
||||||
|
|
||||||
if(Light > 100 || Light < 0)
|
|
||||||
printf("Set Backlight parameters in the range of 0 to 100 \r\n");
|
|
||||||
else{
|
|
||||||
uint32_t Backlight = Light*10;
|
|
||||||
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, Backlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
#define LCD_WIDTH 172 //LCD width
|
|
||||||
#define LCD_HEIGHT 320 //LCD height
|
|
||||||
|
|
||||||
#define SPIFreq 80000000
|
|
||||||
#define EXAMPLE_PIN_NUM_MISO 5
|
|
||||||
#define EXAMPLE_PIN_NUM_MOSI 6
|
|
||||||
#define EXAMPLE_PIN_NUM_SCLK 7
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_CS 14
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_DC 15
|
|
||||||
#define EXAMPLE_PIN_NUM_LCD_RST 21
|
|
||||||
#define EXAMPLE_PIN_NUM_BK_LIGHT 22
|
|
||||||
#define Frequency 1000
|
|
||||||
#define Resolution 10
|
|
||||||
|
|
||||||
#define VERTICAL 0
|
|
||||||
#define HORIZONTAL 1
|
|
||||||
|
|
||||||
#define Offset_X 34
|
|
||||||
#define Offset_Y 0
|
|
||||||
|
|
||||||
void SPI_Init();
|
|
||||||
|
|
||||||
void LCD_Init(void);
|
|
||||||
void LCD_SetCursor(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend);
|
|
||||||
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color);
|
|
||||||
|
|
||||||
void Backlight_Init(void);
|
|
||||||
void Set_Backlight(uint8_t Light);
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
#include "LCD_Image.h"
|
|
||||||
|
|
||||||
PNG png;
|
|
||||||
File Image_file;
|
|
||||||
|
|
||||||
uint16_t Image_CNT;
|
|
||||||
char SD_Image_Name[100][100] ;
|
|
||||||
char File_Image_Name[100][100] ;
|
|
||||||
|
|
||||||
int16_t xpos = 0;
|
|
||||||
int16_t ypos = 0;
|
|
||||||
void * pngOpen(const char *filePath, int32_t *size) {
|
|
||||||
Image_file = SD.open(filePath);
|
|
||||||
*size = Image_file.size();
|
|
||||||
return &Image_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pngClose(void *handle) {
|
|
||||||
File Image_file = *((File*)handle);
|
|
||||||
if (Image_file) Image_file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t pngRead(PNGFILE *page, uint8_t *buffer, int32_t length) {
|
|
||||||
if (!Image_file) return 0;
|
|
||||||
page = page; // Avoid warning
|
|
||||||
return Image_file.read(buffer, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t pngSeek(PNGFILE *page, int32_t position) {
|
|
||||||
if (!Image_file) return 0;
|
|
||||||
page = page; // Avoid warning
|
|
||||||
return Image_file.seek(position);
|
|
||||||
}
|
|
||||||
//=========================================v==========================================
|
|
||||||
// pngDraw
|
|
||||||
//====================================================================================
|
|
||||||
// This next function will be called during decoding of the png file to
|
|
||||||
// render each image line to the TFT. If you use a different TFT library
|
|
||||||
// you will need to adapt this function to suit.
|
|
||||||
// Callback function to draw pixels to the display
|
|
||||||
static uint16_t lineBuffer[MAX_IMAGE_WIDTH];
|
|
||||||
void pngDraw(PNGDRAW *pDraw) {
|
|
||||||
png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
|
|
||||||
uint32_t size = pDraw->iWidth;
|
|
||||||
for (size_t i = 0; i < size; i++) {
|
|
||||||
lineBuffer[i] = (((lineBuffer[i] >> 8) & 0xFF) | ((lineBuffer[i] << 8) & 0xFF00)); // 所有数据修正
|
|
||||||
}
|
|
||||||
LCD_addWindow(xpos, pDraw->y, xpos + pDraw->iWidth, ypos + pDraw->y + 1,lineBuffer); // x_end End index on x-axis (x_end not included)
|
|
||||||
|
|
||||||
}
|
|
||||||
/////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
void Search_Image(const char* directory, const char* fileExtension) {
|
|
||||||
Image_CNT = Folder_retrieval(directory,fileExtension,SD_Image_Name,100);
|
|
||||||
if(Image_CNT) {
|
|
||||||
for (int i = 0; i < Image_CNT; i++) {
|
|
||||||
strcpy(File_Image_Name[i], SD_Image_Name[i]);
|
|
||||||
remove_file_extension(File_Image_Name[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Show_Image(const char * filePath)
|
|
||||||
{
|
|
||||||
printf("Currently display picture %s\r\n",filePath);
|
|
||||||
int16_t ret = png.open(filePath, pngOpen, pngClose, pngRead, pngSeek, pngDraw);
|
|
||||||
if (ret == PNG_SUCCESS) {
|
|
||||||
printf("image specs: (%d x %d), %d bpp, pixel type: %d\r\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
|
|
||||||
|
|
||||||
uint32_t dt = millis();
|
|
||||||
if (png.getWidth() > MAX_IMAGE_WIDTH) {
|
|
||||||
printf("Image too wide for allocated line buffer size!\r\n");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ret = png.decode(NULL, 0);
|
|
||||||
png.close();
|
|
||||||
}
|
|
||||||
printf("%d ms\r\n",millis()-dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display_Image(const char* directory, const char* fileExtension, uint16_t ID)
|
|
||||||
{
|
|
||||||
Search_Image(directory,fileExtension);
|
|
||||||
if(Image_CNT) {
|
|
||||||
String FilePath;
|
|
||||||
if (String(directory) == "/") { // Handle the case when the directory is the root
|
|
||||||
FilePath = String(directory) + SD_Image_Name[ID];
|
|
||||||
} else {
|
|
||||||
FilePath = String(directory) + "/" + SD_Image_Name[ID];
|
|
||||||
}
|
|
||||||
const char* filePathCStr = FilePath.c_str(); // Convert String to c_str() for Show_Image function
|
|
||||||
printf("Show : %s \r\n", filePathCStr); // Print file path for debugging
|
|
||||||
Show_Image(filePathCStr); // Show the image using the file path
|
|
||||||
}
|
|
||||||
else
|
|
||||||
printf("No files with extension '%s' found in directory: %s\r\n", fileExtension, directory);
|
|
||||||
|
|
||||||
}
|
|
||||||
uint16_t Now_Image = 0;
|
|
||||||
void Image_Next(const char* directory, const char* fileExtension)
|
|
||||||
{
|
|
||||||
if(!digitalRead(BOOT_KEY_PIN)){
|
|
||||||
while(!digitalRead(BOOT_KEY_PIN));
|
|
||||||
Now_Image ++;
|
|
||||||
if(Now_Image == Image_CNT)
|
|
||||||
Now_Image = 0;
|
|
||||||
Display_Image(directory,fileExtension,Now_Image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Image_Next_Loop(const char* directory, const char* fileExtension,uint32_t NextTime)
|
|
||||||
{
|
|
||||||
static uint32_t NextTime_Now=0;
|
|
||||||
NextTime_Now++;
|
|
||||||
if(NextTime_Now == NextTime)
|
|
||||||
{
|
|
||||||
NextTime_Now = 0;
|
|
||||||
Now_Image ++;
|
|
||||||
if(Now_Image == Image_CNT)
|
|
||||||
Now_Image = 0;
|
|
||||||
Display_Image(directory,fileExtension,Now_Image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <PNGdec.h>
|
|
||||||
#include "SD_Card.h"
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
|
|
||||||
#define BOOT_KEY_PIN 9
|
|
||||||
#define MAX_IMAGE_WIDTH 172 // Adjust for your images
|
|
||||||
|
|
||||||
void Search_Image(const char* directory, const char* fileExtension);
|
|
||||||
void Show_Image(const char * filePath);
|
|
||||||
void Display_Image(const char* directory, const char* fileExtension, uint16_t ID);
|
|
||||||
void Image_Next(const char* directory, const char* fileExtension);
|
|
||||||
void Image_Next_Loop(const char* directory, const char* fileExtension,uint32_t NextTime);
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#include "SD_Card.h"
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
#include "LCD_Image.h"
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
Flash_test();
|
|
||||||
LCD_Init();
|
|
||||||
SD_Init();
|
|
||||||
Display_Image("/",".png", 0);
|
|
||||||
pinMode(BOOT_KEY_PIN, INPUT);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
Image_Next_Loop("/",".png",300);
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#include "SD_Card.h"
|
|
||||||
|
|
||||||
uint16_t SDCard_Size;
|
|
||||||
uint16_t Flash_Size;
|
|
||||||
|
|
||||||
void SD_Init() {
|
|
||||||
// SD
|
|
||||||
if (SD.begin(SD_CS, SPI, 80000000, "/sd", 5, true)) {
|
|
||||||
printf("SD card initialization successful!\r\n");
|
|
||||||
} else {
|
|
||||||
printf("SD card initialization failed!\r\n");
|
|
||||||
}
|
|
||||||
uint8_t cardType = SD.cardType();
|
|
||||||
if(cardType == CARD_NONE){
|
|
||||||
printf("No SD card attached\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
printf("SD Card Type: ");
|
|
||||||
if(cardType == CARD_MMC){
|
|
||||||
printf("MMC\r\n");
|
|
||||||
} else if(cardType == CARD_SD){
|
|
||||||
printf("SDSC\r\n");
|
|
||||||
} else if(cardType == CARD_SDHC){
|
|
||||||
printf("SDHC\r\n");
|
|
||||||
} else {
|
|
||||||
printf("UNKNOWN\r\n");
|
|
||||||
}
|
|
||||||
uint64_t totalBytes = SD.totalBytes();
|
|
||||||
uint64_t usedBytes = SD.usedBytes();
|
|
||||||
SDCard_Size = totalBytes/(1024*1024);
|
|
||||||
printf("Total space: %llu\n", totalBytes);
|
|
||||||
printf("Used space: %llu\n", usedBytes);
|
|
||||||
printf("Free space: %llu\n", totalBytes - usedBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool File_Search(const char* directory, const char* fileName)
|
|
||||||
{
|
|
||||||
File Path = SD.open(directory);
|
|
||||||
if (!Path) {
|
|
||||||
printf("Path: <%s> does not exist\r\n",directory);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
File file = Path.openNextFile();
|
|
||||||
while (file) {
|
|
||||||
if (strcmp(file.name(), fileName) == 0) {
|
|
||||||
if (strcmp(directory, "/") == 0)
|
|
||||||
printf("File '%s%s' found in root directory.\r\n",directory,fileName);
|
|
||||||
else
|
|
||||||
printf("File '%s/%s' found in root directory.\r\n",directory,fileName);
|
|
||||||
Path.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
file = Path.openNextFile();
|
|
||||||
}
|
|
||||||
if (strcmp(directory, "/") == 0)
|
|
||||||
printf("File '%s%s' not found in root directory.\r\n",directory,fileName);
|
|
||||||
else
|
|
||||||
printf("File '%s/%s' not found in root directory.\r\n",directory,fileName);
|
|
||||||
Path.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles)
|
|
||||||
{
|
|
||||||
File Path = SD.open(directory);
|
|
||||||
if (!Path) {
|
|
||||||
printf("Path: <%s> does not exist\r\n",directory);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t fileCount = 0;
|
|
||||||
char filePath[100];
|
|
||||||
File file = Path.openNextFile();
|
|
||||||
while (file && fileCount < maxFiles) {
|
|
||||||
if (!file.isDirectory() && strstr(file.name(), fileExtension)) {
|
|
||||||
strncpy(File_Name[fileCount], file.name(), sizeof(File_Name[fileCount]));
|
|
||||||
if (strcmp(directory, "/") == 0) {
|
|
||||||
snprintf(filePath, 100, "%s%s", directory, file.name());
|
|
||||||
} else {
|
|
||||||
snprintf(filePath, 100, "%s/%s", directory, file.name());
|
|
||||||
}
|
|
||||||
printf("File found: %s\r\n", filePath);
|
|
||||||
fileCount++;
|
|
||||||
}
|
|
||||||
file = Path.openNextFile();
|
|
||||||
}
|
|
||||||
Path.close();
|
|
||||||
if (fileCount > 0) {
|
|
||||||
printf(" %d <%s> files were retrieved\r\n",fileCount,fileExtension);
|
|
||||||
return fileCount;
|
|
||||||
} else {
|
|
||||||
printf("No files with extension '%s' found in directory: %s\r\n", fileExtension, directory);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_file_extension(char *file_name) {
|
|
||||||
char *last_dot = strrchr(file_name, '.');
|
|
||||||
if (last_dot != NULL) {
|
|
||||||
*last_dot = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Flash_test()
|
|
||||||
{
|
|
||||||
printf("/********** RAM Test**********/\r\n");
|
|
||||||
// Get Flash size
|
|
||||||
uint32_t flashSize = ESP.getFlashChipSize();
|
|
||||||
Flash_Size = flashSize/1024/1024;
|
|
||||||
printf("Flash size: %d MB \r\n", flashSize/1024/1024);
|
|
||||||
|
|
||||||
printf("/******* RAM Test Over********/\r\n\r\n");
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include <cstring>
|
|
||||||
#include "Display_ST7789.h"
|
|
||||||
#include "SD.h"
|
|
||||||
#include "FS.h"
|
|
||||||
|
|
||||||
// Digital I/O used
|
|
||||||
#define SD_CS 4 // SD_D3:
|
|
||||||
|
|
||||||
extern uint16_t SDCard_Size;
|
|
||||||
extern uint16_t Flash_Size;
|
|
||||||
|
|
||||||
void SD_Init();
|
|
||||||
void Flash_test();
|
|
||||||
|
|
||||||
bool File_Search(const char* directory, const char* fileName);
|
|
||||||
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles);
|
|
||||||
void remove_file_extension(char *file_name);
|
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# Klubhaus Doorbell - justfile
|
||||||
|
# Run with: just <recipe>
|
||||||
|
# Set BOARD: just BOARD=esp32-32e-4 compile
|
||||||
|
|
||||||
|
# Default recipe - show help
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# Default BOARD — overridable via env var or CLI: BOARD=uno-r4-wifi just compile
|
||||||
|
BOARD := env_var_or_default('BOARD', 'esp32-s3-lcd-43')
|
||||||
|
|
||||||
|
# Helper to source board config
|
||||||
|
# just passes args as positional, so we source in each recipe
|
||||||
|
|
||||||
|
# Build token header for -include (generated fresh each compile)
|
||||||
|
BUILD_TOKEN_HEADER := "/tmp/doorbell-build-token.h"
|
||||||
|
|
||||||
|
# Compile firmware
|
||||||
|
compile:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
|
||||||
|
BUILD_TOKEN=$(git describe --always --dirty 2>/dev/null || echo "unknown")
|
||||||
|
echo "#pragma once" > /tmp/doorbell-build-token.h
|
||||||
|
echo "#define BUILD_TOKEN \"$BUILD_TOKEN\"" >> /tmp/doorbell-build-token.h
|
||||||
|
echo "Build token: $BUILD_TOKEN"
|
||||||
|
|
||||||
|
CFLAGS="$OPTS -include /tmp/doorbell-build-token.h"
|
||||||
|
|
||||||
|
# Only regenerate compile_commands.json if needed (board changed or first run)
|
||||||
|
NEED_GEN=false
|
||||||
|
if [ ! -f compile_commands.json ]; then
|
||||||
|
NEED_GEN=true
|
||||||
|
elif [ ! -f .board-last ] || [ "$(cat .board-last)" != "{{BOARD}}" ]; then
|
||||||
|
NEED_GEN=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NEED_GEN" = "true" ]; then
|
||||||
|
rm -rf /tmp/arduino-build
|
||||||
|
arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --build-path /tmp/arduino-build ./boards/{{BOARD}}
|
||||||
|
cp /tmp/arduino-build/compile_commands.json .
|
||||||
|
echo "{{BOARD}}" > .board-last
|
||||||
|
echo "[OK] Generated compile_commands.json for {{BOARD}}"
|
||||||
|
else
|
||||||
|
echo "[SKIP] compile_commands.json already up to date for {{BOARD}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
arduino-cli compile --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --warnings default ./boards/{{BOARD}}
|
||||||
|
|
||||||
|
# Upload firmware
|
||||||
|
upload: kill
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
arduino-cli upload --fqbn "$FQBN" --port "$PORT" ./boards/{{BOARD}}
|
||||||
|
|
||||||
|
# Kill processes using serial port
|
||||||
|
kill:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set +e
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
PORT="${PORT:-$PORT}"
|
||||||
|
echo "Killing processes on $PORT..."
|
||||||
|
fuser -k "$PORT" 2>/dev/null || true
|
||||||
|
for pid in $(pgrep -f "monitor-agent.py.*{{BOARD}}" 2>/dev/null || true); do
|
||||||
|
echo "Killing monitor-agent.py (PID: $pid)..."
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
rm -f "/tmp/doorbell-{{BOARD}}.lock" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
echo "[OK] Killed processes for {{BOARD}}"
|
||||||
|
|
||||||
|
# Monitor raw serial
|
||||||
|
monitor-raw: kill
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
PORT="${PORT:-$PORT}"
|
||||||
|
TARGET="$(readlink -f "$PORT" 2>/dev/null || echo "$PORT")"
|
||||||
|
arduino-cli monitor -p "$TARGET" --config baudrate=115200
|
||||||
|
|
||||||
|
# Show tio command
|
||||||
|
monitor-tio: kill
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
PORT="${PORT:-$PORT}"
|
||||||
|
TARGET="$(readlink -f "$PORT" 2>/dev/null || echo "$PORT")"
|
||||||
|
echo "Run: tio --map INLCRNL $TARGET -e"
|
||||||
|
|
||||||
|
# Monitor with JSON logging (runs in background)
|
||||||
|
monitor: kill
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
nohup python3 ./scripts/monitor-agent.py "{{BOARD}}" > /dev/null 2>&1 &
|
||||||
|
PID=$!
|
||||||
|
echo "Monitor agent started for {{BOARD}} (PID: $PID)"
|
||||||
|
echo " Log: /tmp/doorbell-{{BOARD}}.jsonl"
|
||||||
|
echo " State: /tmp/doorbell-{{BOARD}}-state.json"
|
||||||
|
echo " Cmd: echo 'dashboard' > /tmp/doorbell-{{BOARD}}-cmd.fifo"
|
||||||
|
echo " Stop: kill $PID"
|
||||||
|
|
||||||
|
# Tail colored logs
|
||||||
|
watch:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
tail -f "/tmp/doorbell-{{BOARD}}.jsonl" | while read -r line; do
|
||||||
|
ts=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['ts'])" 2>/dev/null)
|
||||||
|
txt=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['line'])" 2>/dev/null)
|
||||||
|
if [[ "$txt" == *"[STATE]"* ]]; then
|
||||||
|
echo -e "\033[1;35m[$ts]\033[0m $txt"
|
||||||
|
elif [[ "$txt" == *"[ADMIN]"* ]]; then
|
||||||
|
echo -e "\033[1;36m[$ts]\033[0m $txt"
|
||||||
|
elif [[ "$txt" == *"[TOUCH]"* ]]; then
|
||||||
|
echo -e "\033[1;33m[$ts]\033[0m $txt"
|
||||||
|
elif [[ "$txt" == *"ALERT"* ]]; then
|
||||||
|
echo -e "\033[1;31m[$ts]\033[0m $txt"
|
||||||
|
else
|
||||||
|
echo "[$ts] $txt"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Send command to device
|
||||||
|
cmd command:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo -n "{{command}}" > "/tmp/doorbell-{{BOARD}}-cmd.fifo"
|
||||||
|
echo "[SENT] {{command}}"
|
||||||
|
|
||||||
|
# Show device state
|
||||||
|
state:
|
||||||
|
@cat "/tmp/doorbell-{{BOARD}}-state.json"
|
||||||
|
|
||||||
|
# Detect connected board
|
||||||
|
detect:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
bash ./scripts/detect-device.sh
|
||||||
|
|
||||||
|
# Install shared libraries
|
||||||
|
install-libs-shared:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
./scripts/install-shared.sh
|
||||||
|
|
||||||
|
# Install all libraries
|
||||||
|
install: install-libs-shared
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if [ -f ./boards/{{BOARD}}/install.sh ]; then
|
||||||
|
./boards/{{BOARD}}/install.sh
|
||||||
|
arduino-cli core install esp32:esp32
|
||||||
|
else
|
||||||
|
echo "[SKIP] No board-specific install script for {{BOARD}}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Full build-compile-monitor loop
|
||||||
|
full: compile upload monitor
|
||||||
|
@just watch
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
clean:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
rm -rf vendor/
|
||||||
|
rm -rf .cache/
|
||||||
|
rm -rf boards/*/build/
|
||||||
|
rm -f .board-last
|
||||||
|
rm -f /tmp/doorbell-build-token.h
|
||||||
|
echo "[OK] Build artifacts cleaned"
|
||||||
|
|
||||||
|
# Clean temporary files (monitor logs, FIFOs, state files)
|
||||||
|
clean-temp:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
rm -f /tmp/doorbell-*.jsonl /tmp/doorbell-*-state.json /tmp/doorbell-*-cmd.fifo
|
||||||
|
rm -f .board-last
|
||||||
|
echo "[OK] Temp files cleaned"
|
||||||
|
|
||||||
|
# Clean Arduino cache
|
||||||
|
arduino-clean:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "Checking ~/.arduino15..."
|
||||||
|
du -sh ~/.arduino15/staging 2>/dev/null || echo "No staging folder"
|
||||||
|
du -sh ~/.arduino15/packages 2>/dev/null || echo "No packages folder"
|
||||||
|
read -p "Delete staging + packages folders? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
rm -rf ~/.arduino15/staging
|
||||||
|
rm -rf ~/.arduino15/packages
|
||||||
|
echo "[OK] Arduino staging + packages cleared"
|
||||||
|
else
|
||||||
|
echo "Aborted"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
format:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
clang-format -i --style=file \
|
||||||
|
boards/esp32-32e/*.cpp \
|
||||||
|
boards/esp32-32e/*.h \
|
||||||
|
boards/esp32-32e/*.ino \
|
||||||
|
boards/esp32-32e-4/*.cpp \
|
||||||
|
boards/esp32-32e-4/*.h \
|
||||||
|
boards/esp32-32e-4/*.ino \
|
||||||
|
boards/esp32-s3-lcd-43/*.cpp \
|
||||||
|
boards/esp32-s3-lcd-43/*.h \
|
||||||
|
boards/esp32-s3-lcd-43/*.ino \
|
||||||
|
boards/uno-r4-wifi/*.cpp \
|
||||||
|
boards/uno-r4-wifi/*.h \
|
||||||
|
boards/uno-r4-wifi/*.ino \
|
||||||
|
libraries/KlubhausCore/src/*.cpp \
|
||||||
|
libraries/KlubhausCore/src/*.h \
|
||||||
|
libraries/KlubhausCore/*.properties
|
||||||
|
|
||||||
|
# Generate compile_commands.json for LSP (no full compile)
|
||||||
|
gen-compile-commands:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
BUILD_TOKEN=$(git describe --always --dirty 2>/dev/null || echo "unknown")
|
||||||
|
echo "#pragma once" > /tmp/doorbell-build-token.h
|
||||||
|
echo "#define BUILD_TOKEN \"$BUILD_TOKEN\"" >> /tmp/doorbell-build-token.h
|
||||||
|
CFLAGS="$OPTS -include /tmp/doorbell-build-token.h"
|
||||||
|
rm -rf /tmp/arduino-build
|
||||||
|
arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --build-path /tmp/arduino-build ./boards/{{BOARD}}
|
||||||
|
cp /tmp/arduino-build/compile_commands.json .
|
||||||
|
echo "{{BOARD}}" > .board-last
|
||||||
|
rm -f /tmp/doorbell-build-token.h
|
||||||
|
echo "[OK] Generated compile_commands.json for {{BOARD}}"
|
||||||
|
|
||||||
|
# Generate crush config
|
||||||
|
gen-crush-config:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
source ./boards/{{BOARD}}/board-config.sh
|
||||||
|
printf '{\n "lsp": {\n "arduino": {\n "command": "arduino-language-server",\n "args": ["-fqbn", "%s"]\n },\n "cpp": {\n "command": "clangd"\n }\n }\n}\n' "$FQBN" > .crush.json
|
||||||
|
echo "[OK] Generated .crush.json with FQBN: $FQBN"
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,422 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @file Adafruit_NeoPixel.h
|
|
||||||
*
|
|
||||||
* This is part of Adafruit's NeoPixel library for the Arduino platform,
|
|
||||||
* allowing a broad range of microcontroller boards (most AVR boards,
|
|
||||||
* many ARM devices, ESP8266 and ESP32, among others) to control Adafruit
|
|
||||||
* NeoPixels, FLORA RGB Smart Pixels and compatible devices -- WS2811,
|
|
||||||
* WS2812, WS2812B, SK6812, etc.
|
|
||||||
*
|
|
||||||
* Adafruit invests time and resources providing this open source code,
|
|
||||||
* please support Adafruit and open-source hardware by purchasing products
|
|
||||||
* from Adafruit!
|
|
||||||
*
|
|
||||||
* Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries,
|
|
||||||
* with contributions by PJRC, Michael Miller and other members of the
|
|
||||||
* open source community.
|
|
||||||
*
|
|
||||||
* This file is part of the Adafruit_NeoPixel library.
|
|
||||||
*
|
|
||||||
* Adafruit_NeoPixel is free software: you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Adafruit_NeoPixel is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with NeoPixel. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ADAFRUIT_NEOPIXEL_H
|
|
||||||
#define ADAFRUIT_NEOPIXEL_H
|
|
||||||
|
|
||||||
#ifdef ARDUINO
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
|
|
||||||
#include <Adafruit_TinyUSB.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TARGET_LPC1768
|
|
||||||
#include <Arduino.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_RP2040)
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "hardware/pio.h"
|
|
||||||
#include "hardware/clocks.h"
|
|
||||||
#include "rp2040_pio.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The order of primary colors in the NeoPixel data stream can vary among
|
|
||||||
// device types, manufacturers and even different revisions of the same
|
|
||||||
// item. The third parameter to the Adafruit_NeoPixel constructor encodes
|
|
||||||
// the per-pixel byte offsets of the red, green and blue primaries (plus
|
|
||||||
// white, if present) in the data stream -- the following #defines provide
|
|
||||||
// an easier-to-use named version for each permutation. e.g. NEO_GRB
|
|
||||||
// indicates a NeoPixel-compatible device expecting three bytes per pixel,
|
|
||||||
// with the first byte transmitted containing the green value, second
|
|
||||||
// containing red and third containing blue. The in-memory representation
|
|
||||||
// of a chain of NeoPixels is the same as the data-stream order; no
|
|
||||||
// re-ordering of bytes is required when issuing data to the chain.
|
|
||||||
// Most of these values won't exist in real-world devices, but it's done
|
|
||||||
// this way so we're ready for it (also, if using the WS2811 driver IC,
|
|
||||||
// one might have their pixels set up in any weird permutation).
|
|
||||||
|
|
||||||
// Bits 5,4 of this value are the offset (0-3) from the first byte of a
|
|
||||||
// pixel to the location of the red color byte. Bits 3,2 are the green
|
|
||||||
// offset and 1,0 are the blue offset. If it is an RGBW-type device
|
|
||||||
// (supporting a white primary in addition to R,G,B), bits 7,6 are the
|
|
||||||
// offset to the white byte...otherwise, bits 7,6 are set to the same value
|
|
||||||
// as 5,4 (red) to indicate an RGB (not RGBW) device.
|
|
||||||
// i.e. binary representation:
|
|
||||||
// 0bWWRRGGBB for RGBW devices
|
|
||||||
// 0bRRRRGGBB for RGB
|
|
||||||
|
|
||||||
// RGB NeoPixel permutations; white and red offsets are always same
|
|
||||||
// Offset: W R G B
|
|
||||||
#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B
|
|
||||||
#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G
|
|
||||||
#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B
|
|
||||||
#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R
|
|
||||||
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G
|
|
||||||
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R
|
|
||||||
|
|
||||||
// RGBW NeoPixel permutations; all 4 offsets are distinct
|
|
||||||
// Offset: W R G B
|
|
||||||
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B
|
|
||||||
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G
|
|
||||||
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B
|
|
||||||
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R
|
|
||||||
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G
|
|
||||||
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R
|
|
||||||
|
|
||||||
#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B
|
|
||||||
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G
|
|
||||||
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B
|
|
||||||
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W
|
|
||||||
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G
|
|
||||||
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W
|
|
||||||
|
|
||||||
#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B
|
|
||||||
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R
|
|
||||||
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B
|
|
||||||
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W
|
|
||||||
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R
|
|
||||||
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W
|
|
||||||
|
|
||||||
#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G
|
|
||||||
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R
|
|
||||||
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G
|
|
||||||
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W
|
|
||||||
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R
|
|
||||||
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W
|
|
||||||
|
|
||||||
// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device.
|
|
||||||
// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is
|
|
||||||
// the default if unspecified. Because flash space is very limited on ATtiny
|
|
||||||
// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on
|
|
||||||
// those chips, though it can be enabled by removing the ifndef/endif below,
|
|
||||||
// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on
|
|
||||||
// other MCUs to remove v1 support and save a little space.
|
|
||||||
|
|
||||||
#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission
|
|
||||||
#ifndef __AVR_ATtiny85__
|
|
||||||
#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If 400 KHz support is enabled, the third parameter to the constructor
|
|
||||||
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
|
|
||||||
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
|
|
||||||
// is sufficient to encode pixel color order, saving some space.
|
|
||||||
|
|
||||||
#ifdef NEO_KHZ400
|
|
||||||
typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
|
|
||||||
#else
|
|
||||||
typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// These two tables are declared outside the Adafruit_NeoPixel class
|
|
||||||
// because some boards may require oldschool compilers that don't
|
|
||||||
// handle the C++11 constexpr keyword.
|
|
||||||
|
|
||||||
/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255).
|
|
||||||
Copy & paste this snippet into a Python REPL to regenerate:
|
|
||||||
import math
|
|
||||||
for x in range(256):
|
|
||||||
print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))),
|
|
||||||
if x&15 == 15: print
|
|
||||||
*/
|
|
||||||
static const uint8_t PROGMEM _NeoPixelSineTable[256] = {
|
|
||||||
128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170,
|
|
||||||
173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211,
|
|
||||||
213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240,
|
|
||||||
241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254,
|
|
||||||
254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251,
|
|
||||||
250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232,
|
|
||||||
230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198,
|
|
||||||
196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155,
|
|
||||||
152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109,
|
|
||||||
106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65,
|
|
||||||
62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29,
|
|
||||||
27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6,
|
|
||||||
5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11,
|
|
||||||
12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37,
|
|
||||||
40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,
|
|
||||||
79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121,
|
|
||||||
124};
|
|
||||||
|
|
||||||
/* Similar to above, but for an 8-bit gamma-correction table.
|
|
||||||
Copy & paste this snippet into a Python REPL to regenerate:
|
|
||||||
import math
|
|
||||||
gamma=2.6
|
|
||||||
for x in range(256):
|
|
||||||
print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
|
|
||||||
if x&15 == 15: print
|
|
||||||
*/
|
|
||||||
static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
|
||||||
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,
|
|
||||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
|
|
||||||
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
|
||||||
11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17,
|
|
||||||
17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
|
|
||||||
25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35,
|
|
||||||
36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48,
|
|
||||||
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
|
||||||
64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81,
|
|
||||||
82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102,
|
|
||||||
103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125,
|
|
||||||
127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152,
|
|
||||||
154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182,
|
|
||||||
184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
|
|
||||||
218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
|
|
||||||
255};
|
|
||||||
|
|
||||||
/* Declare external methods required by the Adafruit_NeoPixel implementation
|
|
||||||
for specific hardware/library versions
|
|
||||||
*/
|
|
||||||
#if defined(ESP32)
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
extern "C" void espInit();
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Class that stores state and functions for interacting with
|
|
||||||
Adafruit NeoPixels and compatible devices.
|
|
||||||
*/
|
|
||||||
class Adafruit_NeoPixel {
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor: number of LEDs, pin number, LED type
|
|
||||||
Adafruit_NeoPixel(uint16_t n, int16_t pin = 6,
|
|
||||||
neoPixelType type = NEO_GRB + NEO_KHZ800);
|
|
||||||
Adafruit_NeoPixel(void);
|
|
||||||
~Adafruit_NeoPixel();
|
|
||||||
|
|
||||||
bool begin(void);
|
|
||||||
void show(void);
|
|
||||||
void setPin(int16_t p);
|
|
||||||
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
|
|
||||||
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
|
|
||||||
void setPixelColor(uint16_t n, uint32_t c);
|
|
||||||
void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0);
|
|
||||||
void setBrightness(uint8_t);
|
|
||||||
void clear(void);
|
|
||||||
void updateLength(uint16_t n);
|
|
||||||
void updateType(neoPixelType t);
|
|
||||||
/*!
|
|
||||||
@brief Check whether a call to show() will start sending data
|
|
||||||
immediately or will 'block' for a required interval. NeoPixels
|
|
||||||
require a short quiet time (about 300 microseconds) after the
|
|
||||||
last bit is received before the data 'latches' and new data can
|
|
||||||
start being received. Usually one's sketch is implicitly using
|
|
||||||
this time to generate a new frame of animation...but if it
|
|
||||||
finishes very quickly, this function could be used to see if
|
|
||||||
there's some idle time available for some low-priority
|
|
||||||
concurrent task.
|
|
||||||
@return 1 or true if show() will start sending immediately, 0 or false
|
|
||||||
if show() would block (meaning some idle time is available).
|
|
||||||
*/
|
|
||||||
bool canShow(void) {
|
|
||||||
// It's normal and possible for endTime to exceed micros() if the
|
|
||||||
// 32-bit clock counter has rolled over (about every 70 minutes).
|
|
||||||
// Since both are uint32_t, a negative delta correctly maps back to
|
|
||||||
// positive space, and it would seem like the subtraction below would
|
|
||||||
// suffice. But a problem arises if code invokes show() very
|
|
||||||
// infrequently...the micros() counter may roll over MULTIPLE times in
|
|
||||||
// that interval, the delta calculation is no longer correct and the
|
|
||||||
// next update may stall for a very long time. The check below resets
|
|
||||||
// the latch counter if a rollover has occurred. This can cause an
|
|
||||||
// extra delay of up to 300 microseconds in the rare case where a
|
|
||||||
// show() call happens precisely around the rollover, but that's
|
|
||||||
// neither likely nor especially harmful, vs. other code that might
|
|
||||||
// stall for 30+ minutes, or having to document and frequently remind
|
|
||||||
// and/or provide tech support explaining an unintuitive need for
|
|
||||||
// show() calls at least once an hour.
|
|
||||||
uint32_t now = micros();
|
|
||||||
if (endTime > now) {
|
|
||||||
endTime = now;
|
|
||||||
}
|
|
||||||
return (now - endTime) >= 300L;
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
@brief Get a pointer directly to the NeoPixel data buffer in RAM.
|
|
||||||
Pixel data is stored in a device-native format (a la the NEO_*
|
|
||||||
constants) and is not translated here. Applications that access
|
|
||||||
this buffer will need to be aware of the specific data format
|
|
||||||
and handle colors appropriately.
|
|
||||||
@return Pointer to NeoPixel buffer (uint8_t* array).
|
|
||||||
@note This is for high-performance applications where calling
|
|
||||||
setPixelColor() on every single pixel would be too slow (e.g.
|
|
||||||
POV or light-painting projects). There is no bounds checking
|
|
||||||
on the array, creating tremendous potential for mayhem if one
|
|
||||||
writes past the ends of the buffer. Great power, great
|
|
||||||
responsibility and all that.
|
|
||||||
*/
|
|
||||||
uint8_t *getPixels(void) const { return pixels; };
|
|
||||||
uint8_t getBrightness(void) const;
|
|
||||||
/*!
|
|
||||||
@brief Retrieve the pin number used for NeoPixel data output.
|
|
||||||
@return Arduino pin number (-1 if not set).
|
|
||||||
*/
|
|
||||||
int16_t getPin(void) const { return pin; };
|
|
||||||
/*!
|
|
||||||
@brief Return the number of pixels in an Adafruit_NeoPixel strip object.
|
|
||||||
@return Pixel count (0 if not set).
|
|
||||||
*/
|
|
||||||
uint16_t numPixels(void) const { return numLEDs; }
|
|
||||||
uint32_t getPixelColor(uint16_t n) const;
|
|
||||||
/*!
|
|
||||||
@brief An 8-bit integer sine wave function, not directly compatible
|
|
||||||
with standard trigonometric units like radians or degrees.
|
|
||||||
@param x Input angle, 0-255; 256 would loop back to zero, completing
|
|
||||||
the circle (equivalent to 360 degrees or 2 pi radians).
|
|
||||||
One can therefore use an unsigned 8-bit variable and simply
|
|
||||||
add or subtract, allowing it to overflow/underflow and it
|
|
||||||
still does the expected contiguous thing.
|
|
||||||
@return Sine result, 0 to 255, or -128 to +127 if type-converted to
|
|
||||||
a signed int8_t, but you'll most likely want unsigned as this
|
|
||||||
output is often used for pixel brightness in animation effects.
|
|
||||||
*/
|
|
||||||
static uint8_t sine8(uint8_t x) {
|
|
||||||
return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
@brief An 8-bit gamma-correction function for basic pixel brightness
|
|
||||||
adjustment. Makes color transitions appear more perceptially
|
|
||||||
correct.
|
|
||||||
@param x Input brightness, 0 (minimum or off/black) to 255 (maximum).
|
|
||||||
@return Gamma-adjusted brightness, can then be passed to one of the
|
|
||||||
setPixelColor() functions. This uses a fixed gamma correction
|
|
||||||
exponent of 2.6, which seems reasonably okay for average
|
|
||||||
NeoPixels in average tasks. If you need finer control you'll
|
|
||||||
need to provide your own gamma-correction function instead.
|
|
||||||
*/
|
|
||||||
static uint8_t gamma8(uint8_t x) {
|
|
||||||
return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
@brief Convert separate red, green and blue values into a single
|
|
||||||
"packed" 32-bit RGB color.
|
|
||||||
@param r Red brightness, 0 to 255.
|
|
||||||
@param g Green brightness, 0 to 255.
|
|
||||||
@param b Blue brightness, 0 to 255.
|
|
||||||
@return 32-bit packed RGB value, which can then be assigned to a
|
|
||||||
variable for later use or passed to the setPixelColor()
|
|
||||||
function. Packed RGB format is predictable, regardless of
|
|
||||||
LED strand color order.
|
|
||||||
*/
|
|
||||||
static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) {
|
|
||||||
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
@brief Convert separate red, green, blue and white values into a
|
|
||||||
single "packed" 32-bit WRGB color.
|
|
||||||
@param r Red brightness, 0 to 255.
|
|
||||||
@param g Green brightness, 0 to 255.
|
|
||||||
@param b Blue brightness, 0 to 255.
|
|
||||||
@param w White brightness, 0 to 255.
|
|
||||||
@return 32-bit packed WRGB value, which can then be assigned to a
|
|
||||||
variable for later use or passed to the setPixelColor()
|
|
||||||
function. Packed WRGB format is predictable, regardless of
|
|
||||||
LED strand color order.
|
|
||||||
*/
|
|
||||||
static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
|
|
||||||
return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
|
||||||
}
|
|
||||||
static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
|
|
||||||
/*!
|
|
||||||
@brief A gamma-correction function for 32-bit packed RGB or WRGB
|
|
||||||
colors. Makes color transitions appear more perceptially
|
|
||||||
correct.
|
|
||||||
@param x 32-bit packed RGB or WRGB color.
|
|
||||||
@return Gamma-adjusted packed color, can then be passed in one of the
|
|
||||||
setPixelColor() functions. Like gamma8(), this uses a fixed
|
|
||||||
gamma correction exponent of 2.6, which seems reasonably okay
|
|
||||||
for average NeoPixels in average tasks. If you need finer
|
|
||||||
control you'll need to provide your own gamma-correction
|
|
||||||
function instead.
|
|
||||||
*/
|
|
||||||
static uint32_t gamma32(uint32_t x);
|
|
||||||
|
|
||||||
void rainbow(uint16_t first_hue = 0, int8_t reps = 1,
|
|
||||||
uint8_t saturation = 255, uint8_t brightness = 255,
|
|
||||||
bool gammify = true);
|
|
||||||
|
|
||||||
static neoPixelType str2order(const char *v);
|
|
||||||
|
|
||||||
private:
|
|
||||||
#if defined(ARDUINO_ARCH_RP2040)
|
|
||||||
bool rp2040claimPIO(void);
|
|
||||||
void rp2040releasePIO(void);
|
|
||||||
void rp2040Show(uint8_t *pixels, uint32_t numBytes);
|
|
||||||
PIO pio = NULL;
|
|
||||||
uint pio_sm = -1;
|
|
||||||
uint pio_program_offset = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled...
|
|
||||||
bool is800KHz; ///< true if 800 KHz pixels
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool begun; ///< true if begin() previously called successfully
|
|
||||||
uint16_t numLEDs; ///< Number of RGB LEDs in strip
|
|
||||||
uint16_t numBytes; ///< Size of 'pixels' buffer below
|
|
||||||
int16_t pin; ///< Output pin number (-1 if not yet set)
|
|
||||||
uint8_t brightness; ///< Strip brightness 0-255 (stored as +1)
|
|
||||||
uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each)
|
|
||||||
uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel
|
|
||||||
uint8_t gOffset; ///< Index of green byte
|
|
||||||
uint8_t bOffset; ///< Index of blue byte
|
|
||||||
uint8_t wOffset; ///< Index of white (==rOffset if no white)
|
|
||||||
uint32_t endTime; ///< Latch timing reference
|
|
||||||
|
|
||||||
#ifdef __AVR__
|
|
||||||
volatile uint8_t *port; ///< Output PORT register
|
|
||||||
uint8_t pinMask; ///< Output PORT bitmask
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_STM32) || \
|
|
||||||
defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) || \
|
|
||||||
defined(ARDUINO_ARCH_CH32) || \
|
|
||||||
defined(_PY32_DEF_)
|
|
||||||
GPIO_TypeDef *gpioPort; ///< Output GPIO PORT
|
|
||||||
uint32_t gpioPin; ///< Output GPIO PIN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ADAFRUIT_NEOPIXEL_H
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#if defined(ARDUINO_ARCH_RP2040)// RP2040 specific driver
|
|
||||||
|
|
||||||
#include "Adafruit_NeoPixel.h"
|
|
||||||
|
|
||||||
bool Adafruit_NeoPixel::rp2040claimPIO(void) {
|
|
||||||
// Find a PIO with enough available space in its instruction memory
|
|
||||||
pio = NULL;
|
|
||||||
|
|
||||||
if (! pio_claim_free_sm_and_add_program_for_gpio_range(&ws2812_program,
|
|
||||||
&pio, &pio_sm, &pio_program_offset,
|
|
||||||
pin, 1, true)) {
|
|
||||||
pio = NULL;
|
|
||||||
pio_sm = -1;
|
|
||||||
pio_program_offset = 0;
|
|
||||||
return false; // No PIO available
|
|
||||||
}
|
|
||||||
|
|
||||||
// yay ok!
|
|
||||||
|
|
||||||
if (is800KHz) {
|
|
||||||
// 800kHz, 8 bit transfers
|
|
||||||
ws2812_program_init(pio, pio_sm, pio_program_offset, pin, 800000, 8);
|
|
||||||
} else {
|
|
||||||
// 400kHz, 8 bit transfers
|
|
||||||
ws2812_program_init(pio, pio_sm, pio_program_offset, pin, 400000, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Adafruit_NeoPixel::rp2040releasePIO(void) {
|
|
||||||
if (pio == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pio_remove_program_and_unclaim_sm(&ws2812_program, pio, pio_sm, pio_program_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Private, called from show()
|
|
||||||
void Adafruit_NeoPixel::rp2040Show(uint8_t *pixels, uint32_t numBytes)
|
|
||||||
{
|
|
||||||
// verify we have a valid PIO and state machine
|
|
||||||
if (! pio || (pio_sm < 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(numBytes--)
|
|
||||||
// Bits for transmission must be shifted to top 8 bits
|
|
||||||
pio_sm_put_blocking(pio, pio_sm, ((uint32_t)*pixels++)<< 24);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Contribution Guidelines
|
|
||||||
|
|
||||||
This library is the culmination of the expertise of many members of the open source community who have dedicated their time and hard work. The best way to ask for help or propose a new idea is to [create a new issue](https://github.com/adafruit/Adafruit_NeoPixel/issues/new) while creating a Pull Request with your code changes allows you to share your own innovations with the rest of the community.
|
|
||||||
|
|
||||||
The following are some guidelines to observe when creating issues or PRs:
|
|
||||||
|
|
||||||
- Be friendly; it is important that we can all enjoy a safe space as we are all working on the same project and it is okay for people to have different ideas
|
|
||||||
|
|
||||||
- [Use code blocks](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code); it helps us help you when we can read your code! On that note also refrain from pasting more than 30 lines of code in a post, instead [create a gist](https://gist.github.com/) if you need to share large snippets
|
|
||||||
|
|
||||||
- Use reasonable titles; refrain from using overly long or capitalized titles as they are usually annoying and do little to encourage others to help :smile:
|
|
||||||
|
|
||||||
- Be detailed; refrain from mentioning code problems without sharing your source code and always give information regarding your board and version of the library
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
|
|
||||||
This version of the GNU Lesser General Public License incorporates
|
|
||||||
the terms and conditions of version 3 of the GNU General Public
|
|
||||||
License, supplemented by the additional permissions listed below.
|
|
||||||
|
|
||||||
0. Additional Definitions.
|
|
||||||
|
|
||||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
|
||||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
|
||||||
General Public License.
|
|
||||||
|
|
||||||
"The Library" refers to a covered work governed by this License,
|
|
||||||
other than an Application or a Combined Work as defined below.
|
|
||||||
|
|
||||||
An "Application" is any work that makes use of an interface provided
|
|
||||||
by the Library, but which is not otherwise based on the Library.
|
|
||||||
Defining a subclass of a class defined by the Library is deemed a mode
|
|
||||||
of using an interface provided by the Library.
|
|
||||||
|
|
||||||
A "Combined Work" is a work produced by combining or linking an
|
|
||||||
Application with the Library. The particular version of the Library
|
|
||||||
with which the Combined Work was made is also called the "Linked
|
|
||||||
Version".
|
|
||||||
|
|
||||||
The "Minimal Corresponding Source" for a Combined Work means the
|
|
||||||
Corresponding Source for the Combined Work, excluding any source code
|
|
||||||
for portions of the Combined Work that, considered in isolation, are
|
|
||||||
based on the Application, and not on the Linked Version.
|
|
||||||
|
|
||||||
The "Corresponding Application Code" for a Combined Work means the
|
|
||||||
object code and/or source code for the Application, including any data
|
|
||||||
and utility programs needed for reproducing the Combined Work from the
|
|
||||||
Application, but excluding the System Libraries of the Combined Work.
|
|
||||||
|
|
||||||
1. Exception to Section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
You may convey a covered work under sections 3 and 4 of this License
|
|
||||||
without being bound by section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
2. Conveying Modified Versions.
|
|
||||||
|
|
||||||
If you modify a copy of the Library, and, in your modifications, a
|
|
||||||
facility refers to a function or data to be supplied by an Application
|
|
||||||
that uses the facility (other than as an argument passed when the
|
|
||||||
facility is invoked), then you may convey a copy of the modified
|
|
||||||
version:
|
|
||||||
|
|
||||||
a) under this License, provided that you make a good faith effort to
|
|
||||||
ensure that, in the event an Application does not supply the
|
|
||||||
function or data, the facility still operates, and performs
|
|
||||||
whatever part of its purpose remains meaningful, or
|
|
||||||
|
|
||||||
b) under the GNU GPL, with none of the additional permissions of
|
|
||||||
this License applicable to that copy.
|
|
||||||
|
|
||||||
3. Object Code Incorporating Material from Library Header Files.
|
|
||||||
|
|
||||||
The object code form of an Application may incorporate material from
|
|
||||||
a header file that is part of the Library. You may convey such object
|
|
||||||
code under terms of your choice, provided that, if the incorporated
|
|
||||||
material is not limited to numerical parameters, data structure
|
|
||||||
layouts and accessors, or small macros, inline functions and templates
|
|
||||||
(ten or fewer lines in length), you do both of the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the object code that the
|
|
||||||
Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
4. Combined Works.
|
|
||||||
|
|
||||||
You may convey a Combined Work under terms of your choice that,
|
|
||||||
taken together, effectively do not restrict modification of the
|
|
||||||
portions of the Library contained in the Combined Work and reverse
|
|
||||||
engineering for debugging such modifications, if you also do each of
|
|
||||||
the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the Combined Work that
|
|
||||||
the Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
c) For a Combined Work that displays copyright notices during
|
|
||||||
execution, include the copyright notice for the Library among
|
|
||||||
these notices, as well as a reference directing the user to the
|
|
||||||
copies of the GNU GPL and this license document.
|
|
||||||
|
|
||||||
d) Do one of the following:
|
|
||||||
|
|
||||||
0) Convey the Minimal Corresponding Source under the terms of this
|
|
||||||
License, and the Corresponding Application Code in a form
|
|
||||||
suitable for, and under terms that permit, the user to
|
|
||||||
recombine or relink the Application with a modified version of
|
|
||||||
the Linked Version to produce a modified Combined Work, in the
|
|
||||||
manner specified by section 6 of the GNU GPL for conveying
|
|
||||||
Corresponding Source.
|
|
||||||
|
|
||||||
1) Use a suitable shared library mechanism for linking with the
|
|
||||||
Library. A suitable mechanism is one that (a) uses at run time
|
|
||||||
a copy of the Library already present on the user's computer
|
|
||||||
system, and (b) will operate properly with a modified version
|
|
||||||
of the Library that is interface-compatible with the Linked
|
|
||||||
Version.
|
|
||||||
|
|
||||||
e) Provide Installation Information, but only if you would otherwise
|
|
||||||
be required to provide such information under section 6 of the
|
|
||||||
GNU GPL, and only to the extent that such information is
|
|
||||||
necessary to install and execute a modified version of the
|
|
||||||
Combined Work produced by recombining or relinking the
|
|
||||||
Application with a modified version of the Linked Version. (If
|
|
||||||
you use option 4d0, the Installation Information must accompany
|
|
||||||
the Minimal Corresponding Source and Corresponding Application
|
|
||||||
Code. If you use option 4d1, you must provide the Installation
|
|
||||||
Information in the manner specified by section 6 of the GNU GPL
|
|
||||||
for conveying Corresponding Source.)
|
|
||||||
|
|
||||||
5. Combined Libraries.
|
|
||||||
|
|
||||||
You may place library facilities that are a work based on the
|
|
||||||
Library side by side in a single library together with other library
|
|
||||||
facilities that are not Applications and are not covered by this
|
|
||||||
License, and convey such a combined library under terms of your
|
|
||||||
choice, if you do both of the following:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work based
|
|
||||||
on the Library, uncombined with any other library facilities,
|
|
||||||
conveyed under the terms of this License.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library that part of it
|
|
||||||
is a work based on the Library, and explaining where to find the
|
|
||||||
accompanying uncombined form of the same work.
|
|
||||||
|
|
||||||
6. Revised Versions of the GNU Lesser General Public License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the GNU Lesser General Public License from time to time. Such new
|
|
||||||
versions will be similar in spirit to the present version, but may
|
|
||||||
differ in detail to address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Library as you received it specifies that a certain numbered version
|
|
||||||
of the GNU Lesser General Public License "or any later version"
|
|
||||||
applies to it, you have the option of following the terms and
|
|
||||||
conditions either of that published version or of any later version
|
|
||||||
published by the Free Software Foundation. If the Library as you
|
|
||||||
received it does not specify a version number of the GNU Lesser
|
|
||||||
General Public License, you may choose any version of the GNU Lesser
|
|
||||||
General Public License ever published by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Library as you received it specifies that a proxy can decide
|
|
||||||
whether future versions of the GNU Lesser General Public License shall
|
|
||||||
apply, that proxy's public statement of acceptance of any version is
|
|
||||||
permanent authorization for you to choose that version for the
|
|
||||||
Library.
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
# Adafruit NeoPixel Library [](https://github.com/adafruit/Adafruit_NeoPixel/actions)[](http://adafruit.github.io/Adafruit_NeoPixel/html/index.html)
|
|
||||||
|
|
||||||
Arduino library for controlling single-wire-based LED pixels and strip such as the [Adafruit 60 LED/meter Digital LED strip][strip], the [Adafruit FLORA RGB Smart Pixel][flora], the [Adafruit Breadboard-friendly RGB Smart Pixel][pixel], the [Adafruit NeoPixel Stick][stick], and the [Adafruit NeoPixel Shield][shield].
|
|
||||||
|
|
||||||
After downloading, rename folder to 'Adafruit_NeoPixel' and install in Arduino Libraries folder. Restart Arduino IDE, then open File->Sketchbook->Library->Adafruit_NeoPixel->strandtest sketch.
|
|
||||||
|
|
||||||
Compatibility notes: Port A is not supported on any AVR processors at this time
|
|
||||||
|
|
||||||
[flora]: http://adafruit.com/products/1060
|
|
||||||
[strip]: http://adafruit.com/products/1138
|
|
||||||
[pixel]: http://adafruit.com/products/1312
|
|
||||||
[stick]: http://adafruit.com/products/1426
|
|
||||||
[shield]: http://adafruit.com/products/1430
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### First Method
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
1. In the Arduino IDE, navigate to Sketch > Include Library > Manage Libraries
|
|
||||||
1. Then the Library Manager will open and you will find a list of libraries that are already installed or ready for installation.
|
|
||||||
1. Then search for Neopixel strip using the search bar.
|
|
||||||
1. Click on the text area and then select the specific version and install it.
|
|
||||||
|
|
||||||
### Second Method
|
|
||||||
|
|
||||||
1. Navigate to the [Releases page](https://github.com/adafruit/Adafruit_NeoPixel/releases).
|
|
||||||
1. Download the latest release.
|
|
||||||
1. Extract the zip file
|
|
||||||
1. In the Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- ### Simple to use
|
|
||||||
|
|
||||||
Controlling NeoPixels “from scratch” is quite a challenge, so we provide a library letting you focus on the fun and interesting bits.
|
|
||||||
|
|
||||||
- ### Give back
|
|
||||||
|
|
||||||
The library is free; you don’t have to pay for anything. Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
|
|
||||||
|
|
||||||
- ### Supported Chipsets
|
|
||||||
|
|
||||||
We have included code for the following chips - sometimes these break for exciting reasons that we can't control in which case please open an issue!
|
|
||||||
|
|
||||||
- AVR ATmega and ATtiny (any 8-bit) - 8 MHz, 12 MHz and 16 MHz
|
|
||||||
- Teensy 3.x and LC
|
|
||||||
- Arduino Due
|
|
||||||
- Arduino 101
|
|
||||||
- Arm® Cortex®-M7/M4 - RENESAS/STM (Arduino UNO R4, Arduino Portenta H7, Arduino Giga R1)
|
|
||||||
- ATSAMD21 (Arduino Zero/M0 and other SAMD21 boards) @ 48 MHz
|
|
||||||
- ATSAMD51 @ 120 MHz
|
|
||||||
- Adafruit STM32 Feather @ 120 MHz
|
|
||||||
- ESP8266 any speed
|
|
||||||
- ESP32 any speed
|
|
||||||
- WCH CH32 @ 48 MHz and higher speeds
|
|
||||||
- Nordic nRF52 (Adafruit Feather nRF52), nRF51 (micro:bit)
|
|
||||||
- Infineon XMC1100 BootKit @ 32 MHz
|
|
||||||
- Infineon XMC1100 2Go @ 32 MHz
|
|
||||||
- Infineon XMC1300 BootKit @ 32 MHz
|
|
||||||
- Infineon XMC1400 2Go @ 48 MHz
|
|
||||||
- Infineon XMC4700 RelaxKit, XMC4800 RelaxKit, XMC4800 IoT Amazon FreeRTOS Kit @ 144 MHz
|
|
||||||
- Sipeed Maix Bit (K210 processor)
|
|
||||||
|
|
||||||
Check forks for other architectures not listed here!
|
|
||||||
|
|
||||||
- ### GNU Lesser General Public License
|
|
||||||
|
|
||||||
Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
|
|
||||||
- begin()
|
|
||||||
- updateLength()
|
|
||||||
- updateType()
|
|
||||||
- show()
|
|
||||||
- delay_ns()
|
|
||||||
- setPin()
|
|
||||||
- setPixelColor()
|
|
||||||
- fill()
|
|
||||||
- ColorHSV()
|
|
||||||
- getPixelColor()
|
|
||||||
- setBrightness()
|
|
||||||
- getBrightness()
|
|
||||||
- clear()
|
|
||||||
- gamma32()
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
There are many examples implemented in this library. One of the examples is below. You can find other examples [here](https://github.com/adafruit/Adafruit_NeoPixel/tree/master/examples)
|
|
||||||
|
|
||||||
### Simple
|
|
||||||
|
|
||||||
```Cpp
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h>
|
|
||||||
#endif
|
|
||||||
#define PIN 6
|
|
||||||
#define NUMPIXELS 16
|
|
||||||
|
|
||||||
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
#define DELAYVAL 500
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pixels.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
pixels.clear();
|
|
||||||
|
|
||||||
for(int i=0; i<NUMPIXELS; i++) {
|
|
||||||
|
|
||||||
pixels.setPixelColor(i, pixels.Color(0, 150, 0));
|
|
||||||
pixels.show();
|
|
||||||
delay(DELAYVAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
If you want to contribute to this project:
|
|
||||||
|
|
||||||
- Report bugs and errors
|
|
||||||
- Ask for enhancements
|
|
||||||
- Create issues and pull requests
|
|
||||||
- Tell others about this library
|
|
||||||
- Contribute new protocols
|
|
||||||
|
|
||||||
Please read [CONTRIBUTING.md](https://github.com/adafruit/Adafruit_NeoPixel/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
|
|
||||||
|
|
||||||
### Roadmap
|
|
||||||
|
|
||||||
The PRIME DIRECTIVE is to maintain backward compatibility with existing Arduino sketches -- many are hosted elsewhere and don't track changes here, some are in print and can never be changed!
|
|
||||||
|
|
||||||
Please don't reformat code for the sake of reformatting code. The resulting large "visual diff" makes it impossible to untangle actual bug fixes from merely rearranged lines. Also, don't bother with PRs for timing adjustments "to better match the datasheet," because the datasheet isn't really true to begin with.
|
|
||||||
|
|
||||||
Things I'd Like To Do But There's No Official Timeline So Please Don't Count On Any Of This Ever Being Canonical:
|
|
||||||
|
|
||||||
- 400 KHz support can be removed, turns out it was never actually necessary; even the earliest NeoPixels can ingest 800 KHz data. Of course the #defines should remain so old sketches still compile, but both can be set to 0 and would have no effect on anything.
|
|
||||||
- For the show() function (with all the delicate pixel timing stuff), break out each architecture into separate source files rather than the current unmaintainable tangle of #ifdef statements!
|
|
||||||
- Please don't use updateLength() or updateType() in new code. They should not have been implemented this way (use the C++ 'new' operator with the regular constructor instead) and are only sticking around because of the Prime Directive. setPin() is OK for now though, it's a trick we can use to 'recycle' pixel memory across multiple strips.
|
|
||||||
- In the M0 and M4 code, use the hardware systick counter for bit timing rather than hand-tweaked NOPs (a temporary kludge at the time because I wasn't reading systick correctly). (As of 1.4.2, systick is used on M4 devices and it appears to be overclock-compatible. Not for M0 yet, which is why this item is still here.)
|
|
||||||
- As currently written, brightness scaling is still a "destructive" operation -- pixel values are altered in RAM and the original value as set can't be accurately read back, only approximated, which has been confusing and frustrating to users. It was done this way at the time because NeoPixel timing is strict, AVR microcontrollers (all we had at the time) are limited, and assembly language is hard. All the 32-bit architectures should have no problem handling nondestructive brightness scaling -- calculating each byte immediately before it's sent out the wire, maintaining the original set value in RAM -- the work just hasn't been done. There's a fair chance even the AVR code could manage it with some intense focus. (The DotStar library achieves nondestructive brightness scaling because it doesn't have to manage data timing so carefully...every architecture, even ATtiny, just takes whatever cycles it needs for the multiply/shift operations.)
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
This library is written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, with contributions by PJRC, Michael Miller and other members of the open source community.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
||||||
Adafruit_NeoPixel is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.en.html) for more details.
|
|
||||||
You should have received a copy of the GNU Lesser General Public License along with NeoPixel. If not, see [this](https://www.gnu.org/licenses/)
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
// Implements the RMT peripheral on Espressif SoCs
|
|
||||||
// Copyright (c) 2020 Lucian Copeland for Adafruit Industries
|
|
||||||
|
|
||||||
/* Uses code from Espressif RGB LED Strip demo and drivers
|
|
||||||
* Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(ESP32)
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#if defined(ESP_IDF_VERSION)
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
|
|
||||||
#define HAS_ESP_IDF_4
|
|
||||||
#endif
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
#define HAS_ESP_IDF_5
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAS_ESP_IDF_5
|
|
||||||
|
|
||||||
static SemaphoreHandle_t show_mutex = NULL;
|
|
||||||
|
|
||||||
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
|
|
||||||
// Note: Because rmtPin is shared between all instances, we will
|
|
||||||
// end up releasing/initializing the RMT channels each time we
|
|
||||||
// invoke on different pins. This is probably ok, just not
|
|
||||||
// efficient. led_data is shared between all instances but will
|
|
||||||
// be allocated with enough space for the largest instance; data
|
|
||||||
// is not used beyond the mutex lock so this should be fine.
|
|
||||||
|
|
||||||
#define SEMAPHORE_TIMEOUT_MS 50
|
|
||||||
|
|
||||||
static rmt_data_t *led_data = NULL;
|
|
||||||
static uint32_t led_data_size = 0;
|
|
||||||
static int rmtPin = -1;
|
|
||||||
|
|
||||||
if (show_mutex && xSemaphoreTake(show_mutex, SEMAPHORE_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
|
|
||||||
uint32_t requiredSize = numBytes * 8;
|
|
||||||
if (requiredSize > led_data_size) {
|
|
||||||
free(led_data);
|
|
||||||
led_data = (rmt_data_t *)malloc(requiredSize * sizeof(rmt_data_t));
|
|
||||||
if (led_data != NULL) {
|
|
||||||
led_data_size = requiredSize;
|
|
||||||
} else {
|
|
||||||
led_data_size = 0;
|
|
||||||
}
|
|
||||||
} else if (requiredSize == 0) {
|
|
||||||
// To release RMT resources (RMT channels and led_data), call
|
|
||||||
// .updateLength(0) to set number of pixels/bytes to zero,
|
|
||||||
// then call .show() to invoke this code and free resources.
|
|
||||||
free(led_data);
|
|
||||||
led_data = NULL;
|
|
||||||
if (rmtPin >= 0) {
|
|
||||||
rmtDeinit(rmtPin);
|
|
||||||
rmtPin = -1;
|
|
||||||
}
|
|
||||||
led_data_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (led_data_size > 0 && requiredSize <= led_data_size) {
|
|
||||||
if (pin != rmtPin) {
|
|
||||||
if (rmtPin >= 0) {
|
|
||||||
rmtDeinit(rmtPin);
|
|
||||||
rmtPin = -1;
|
|
||||||
}
|
|
||||||
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) {
|
|
||||||
log_e("Failed to init RMT TX mode on pin %d", pin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rmtPin = pin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rmtPin >= 0) {
|
|
||||||
int i=0;
|
|
||||||
for (int b=0; b < numBytes; b++) {
|
|
||||||
for (int bit=0; bit<8; bit++){
|
|
||||||
if ( pixels[b] & (1<<(7-bit)) ) {
|
|
||||||
led_data[i].level0 = 1;
|
|
||||||
led_data[i].duration0 = 8;
|
|
||||||
led_data[i].level1 = 0;
|
|
||||||
led_data[i].duration1 = 4;
|
|
||||||
} else {
|
|
||||||
led_data[i].level0 = 1;
|
|
||||||
led_data[i].duration0 = 4;
|
|
||||||
led_data[i].level1 = 0;
|
|
||||||
led_data[i].duration1 = 8;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xSemaphoreGive(show_mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// To avoid race condition initializing the mutex, all instances of
|
|
||||||
// Adafruit_NeoPixel must be constructed before launching and child threads
|
|
||||||
void espInit() {
|
|
||||||
if (!show_mutex) {
|
|
||||||
show_mutex = xSemaphoreCreateMutex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include "driver/rmt.h"
|
|
||||||
|
|
||||||
|
|
||||||
// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered
|
|
||||||
// to work with the Arduino version of the ESP-IDF (3.2)
|
|
||||||
|
|
||||||
#define WS2812_T0H_NS (400)
|
|
||||||
#define WS2812_T0L_NS (850)
|
|
||||||
#define WS2812_T1H_NS (800)
|
|
||||||
#define WS2812_T1L_NS (450)
|
|
||||||
|
|
||||||
#define WS2811_T0H_NS (500)
|
|
||||||
#define WS2811_T0L_NS (2000)
|
|
||||||
#define WS2811_T1H_NS (1200)
|
|
||||||
#define WS2811_T1L_NS (1300)
|
|
||||||
|
|
||||||
static uint32_t t0h_ticks = 0;
|
|
||||||
static uint32_t t1h_ticks = 0;
|
|
||||||
static uint32_t t0l_ticks = 0;
|
|
||||||
static uint32_t t1l_ticks = 0;
|
|
||||||
|
|
||||||
// Limit the number of RMT channels available for the Neopixels. Defaults to all
|
|
||||||
// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free
|
|
||||||
// any channels with a higher number for other uses, such as IR send-and-recieve
|
|
||||||
// libraries. Redefine as 1 to restrict Neopixels to only a single channel.
|
|
||||||
#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX
|
|
||||||
|
|
||||||
#define RMT_LL_HW_BASE (&RMT)
|
|
||||||
|
|
||||||
bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX];
|
|
||||||
|
|
||||||
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
|
|
||||||
size_t wanted_num, size_t *translated_size, size_t *item_num)
|
|
||||||
{
|
|
||||||
if (src == NULL || dest == NULL) {
|
|
||||||
*translated_size = 0;
|
|
||||||
*item_num = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rmt_item32_t bit0 = {{{ t0h_ticks, 1, t0l_ticks, 0 }}}; //Logical 0
|
|
||||||
const rmt_item32_t bit1 = {{{ t1h_ticks, 1, t1l_ticks, 0 }}}; //Logical 1
|
|
||||||
size_t size = 0;
|
|
||||||
size_t num = 0;
|
|
||||||
uint8_t *psrc = (uint8_t *)src;
|
|
||||||
rmt_item32_t *pdest = dest;
|
|
||||||
while (size < src_size && num < wanted_num) {
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
// MSB first
|
|
||||||
if (*psrc & (1 << (7 - i))) {
|
|
||||||
pdest->val = bit1.val;
|
|
||||||
} else {
|
|
||||||
pdest->val = bit0.val;
|
|
||||||
}
|
|
||||||
num++;
|
|
||||||
pdest++;
|
|
||||||
}
|
|
||||||
size++;
|
|
||||||
psrc++;
|
|
||||||
}
|
|
||||||
*translated_size = size;
|
|
||||||
*item_num = num;
|
|
||||||
}
|
|
||||||
|
|
||||||
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
|
|
||||||
// Reserve channel
|
|
||||||
rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX;
|
|
||||||
for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) {
|
|
||||||
if (!rmt_reserved_channels[i]) {
|
|
||||||
rmt_reserved_channels[i] = true;
|
|
||||||
channel = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (channel == ADAFRUIT_RMT_CHANNEL_MAX) {
|
|
||||||
// Ran out of channels!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(HAS_ESP_IDF_4)
|
|
||||||
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
|
|
||||||
config.clk_div = 2;
|
|
||||||
#else
|
|
||||||
// Match default TX config from ESP-IDF version 3.4
|
|
||||||
rmt_config_t config = {
|
|
||||||
.rmt_mode = RMT_MODE_TX,
|
|
||||||
.channel = channel,
|
|
||||||
.gpio_num = pin,
|
|
||||||
.clk_div = 2,
|
|
||||||
.mem_block_num = 1,
|
|
||||||
.tx_config = {
|
|
||||||
.carrier_freq_hz = 38000,
|
|
||||||
.carrier_level = RMT_CARRIER_LEVEL_HIGH,
|
|
||||||
.idle_level = RMT_IDLE_LEVEL_LOW,
|
|
||||||
.carrier_duty_percent = 33,
|
|
||||||
.carrier_en = false,
|
|
||||||
.loop_en = false,
|
|
||||||
.idle_output_en = true,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
rmt_config(&config);
|
|
||||||
rmt_driver_install(config.channel, 0, 0);
|
|
||||||
|
|
||||||
// Convert NS timings to ticks
|
|
||||||
uint32_t counter_clk_hz = 0;
|
|
||||||
|
|
||||||
#if defined(HAS_ESP_IDF_4)
|
|
||||||
rmt_get_counter_clock(channel, &counter_clk_hz);
|
|
||||||
#else
|
|
||||||
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
|
|
||||||
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
|
|
||||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
|
||||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
|
||||||
counter_clk_hz = REF_CLK_FREQ / (div);
|
|
||||||
} else {
|
|
||||||
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
|
|
||||||
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
|
|
||||||
counter_clk_hz = APB_CLK_FREQ / (div);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NS to tick converter
|
|
||||||
float ratio = (float)counter_clk_hz / 1e9;
|
|
||||||
|
|
||||||
if (is800KHz) {
|
|
||||||
t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
|
|
||||||
t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
|
|
||||||
t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
|
|
||||||
t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
|
|
||||||
} else {
|
|
||||||
t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
|
|
||||||
t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
|
|
||||||
t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
|
|
||||||
t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize automatic timing translator
|
|
||||||
rmt_translator_init(config.channel, ws2812_rmt_adapter);
|
|
||||||
|
|
||||||
// Write and wait to finish
|
|
||||||
rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
|
|
||||||
rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
|
|
||||||
|
|
||||||
// Free channel again
|
|
||||||
rmt_driver_uninstall(config.channel);
|
|
||||||
rmt_reserved_channels[channel] = false;
|
|
||||||
|
|
||||||
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // ifndef IDF5
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ifdef(ESP32)
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// This is a mash-up of the Due show() code + insights from Michael Miller's
|
|
||||||
// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus
|
|
||||||
// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution.
|
|
||||||
|
|
||||||
#if defined(ESP8266)
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef ESP8266
|
|
||||||
#include <eagle_soc.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static uint32_t _getCycleCount(void) __attribute__((always_inline));
|
|
||||||
static inline uint32_t _getCycleCount(void) {
|
|
||||||
uint32_t ccount;
|
|
||||||
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
|
|
||||||
return ccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
IRAM_ATTR void espShow(
|
|
||||||
uint8_t pin, uint8_t *pixels, uint32_t numBytes, __attribute__((unused)) boolean is800KHz) {
|
|
||||||
#else
|
|
||||||
void espShow(
|
|
||||||
uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CYCLES_800_T0H (F_CPU / 2500001) // 0.4us
|
|
||||||
#define CYCLES_800_T1H (F_CPU / 1250001) // 0.8us
|
|
||||||
#define CYCLES_800 (F_CPU / 800001) // 1.25us per bit
|
|
||||||
#define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS
|
|
||||||
#define CYCLES_400_T1H (F_CPU / 833333) // 1.2us
|
|
||||||
#define CYCLES_400 (F_CPU / 400000) // 2.5us per bit
|
|
||||||
|
|
||||||
uint8_t *p, *end, pix, mask;
|
|
||||||
uint32_t t, time0, time1, period, c, startTime;
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint32_t pinMask;
|
|
||||||
pinMask = _BV(pin);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
p = pixels;
|
|
||||||
end = p + numBytes;
|
|
||||||
pix = *p++;
|
|
||||||
mask = 0x80;
|
|
||||||
startTime = 0;
|
|
||||||
|
|
||||||
#ifdef NEO_KHZ400
|
|
||||||
if(is800KHz) {
|
|
||||||
#endif
|
|
||||||
time0 = CYCLES_800_T0H;
|
|
||||||
time1 = CYCLES_800_T1H;
|
|
||||||
period = CYCLES_800;
|
|
||||||
#ifdef NEO_KHZ400
|
|
||||||
} else { // 400 KHz bitstream
|
|
||||||
time0 = CYCLES_400_T0H;
|
|
||||||
time1 = CYCLES_400_T1H;
|
|
||||||
period = CYCLES_400;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for(t = time0;; t = time0) {
|
|
||||||
if(pix & mask) t = time1; // Bit high duration
|
|
||||||
while(((c = _getCycleCount()) - startTime) < period); // Wait for bit start
|
|
||||||
#ifdef ESP8266
|
|
||||||
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high
|
|
||||||
#else
|
|
||||||
gpio_set_level(pin, HIGH);
|
|
||||||
#endif
|
|
||||||
startTime = c; // Save start time
|
|
||||||
while(((c = _getCycleCount()) - startTime) < t); // Wait high duration
|
|
||||||
#ifdef ESP8266
|
|
||||||
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low
|
|
||||||
#else
|
|
||||||
gpio_set_level(pin, LOW);
|
|
||||||
#endif
|
|
||||||
if(!(mask >>= 1)) { // Next bit/byte
|
|
||||||
if(p >= end) break;
|
|
||||||
pix = *p++;
|
|
||||||
mask = 0x80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while((_getCycleCount() - startTime) < period); // Wait for last bit
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
// NeoPixel test program showing use of the WHITE channel for RGBW
|
|
||||||
// pixels only (won't look correct on regular RGB NeoPixel strips).
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Which pin on the Arduino is connected to the NeoPixels?
|
|
||||||
// On a Trinket or Gemma we suggest changing this to 1:
|
|
||||||
#define LED_PIN 6
|
|
||||||
|
|
||||||
// How many NeoPixels are attached to the Arduino?
|
|
||||||
#define LED_COUNT 60
|
|
||||||
|
|
||||||
// NeoPixel brightness, 0 (min) to 255 (max)
|
|
||||||
#define BRIGHTNESS 50 // Set BRIGHTNESS to about 1/5 (max = 255)
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
|
|
||||||
// Any other board, you can remove this part (but no harm leaving it):
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// END of Trinket-specific code.
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
strip.setBrightness(BRIGHTNESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Fill along the length of the strip in various colors...
|
|
||||||
colorWipe(strip.Color(255, 0, 0) , 50); // Red
|
|
||||||
colorWipe(strip.Color( 0, 255, 0) , 50); // Green
|
|
||||||
colorWipe(strip.Color( 0, 0, 255) , 50); // Blue
|
|
||||||
colorWipe(strip.Color( 0, 0, 0, 255), 50); // True white (not RGB white)
|
|
||||||
|
|
||||||
whiteOverRainbow(75, 5);
|
|
||||||
|
|
||||||
pulseWhite(5);
|
|
||||||
|
|
||||||
rainbowFade2White(3, 3, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void whiteOverRainbow(int whiteSpeed, int whiteLength) {
|
|
||||||
|
|
||||||
if(whiteLength >= strip.numPixels()) whiteLength = strip.numPixels() - 1;
|
|
||||||
|
|
||||||
int head = whiteLength - 1;
|
|
||||||
int tail = 0;
|
|
||||||
int loops = 3;
|
|
||||||
int loopNum = 0;
|
|
||||||
uint32_t lastTime = millis();
|
|
||||||
uint32_t firstPixelHue = 0;
|
|
||||||
|
|
||||||
for(;;) { // Repeat forever (or until a 'break' or 'return')
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
if(((i >= tail) && (i <= head)) || // If between head & tail...
|
|
||||||
((tail > head) && ((i >= tail) || (i <= head)))) {
|
|
||||||
strip.setPixelColor(i, strip.Color(0, 0, 0, 255)); // Set white
|
|
||||||
} else { // else set rainbow
|
|
||||||
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
// There's no delay here, it just runs full-tilt until the timer and
|
|
||||||
// counter combination below runs out.
|
|
||||||
|
|
||||||
firstPixelHue += 40; // Advance just a little along the color wheel
|
|
||||||
|
|
||||||
if((millis() - lastTime) > whiteSpeed) { // Time to update head/tail?
|
|
||||||
if(++head >= strip.numPixels()) { // Advance head, wrap around
|
|
||||||
head = 0;
|
|
||||||
if(++loopNum >= loops) return;
|
|
||||||
}
|
|
||||||
if(++tail >= strip.numPixels()) { // Advance tail, wrap around
|
|
||||||
tail = 0;
|
|
||||||
}
|
|
||||||
lastTime = millis(); // Save time of last movement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pulseWhite(uint8_t wait) {
|
|
||||||
for(int j=0; j<256; j++) { // Ramp up from 0 to 255
|
|
||||||
// Fill entire strip with white at gamma-corrected brightness level 'j':
|
|
||||||
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int j=255; j>=0; j--) { // Ramp down from 255 to 0
|
|
||||||
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rainbowFade2White(int wait, int rainbowLoops, int whiteLoops) {
|
|
||||||
int fadeVal=0, fadeMax=100;
|
|
||||||
|
|
||||||
// Hue of first pixel runs 'rainbowLoops' complete loops through the color
|
|
||||||
// wheel. Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to rainbowLoops*65536, using steps of 256 so we
|
|
||||||
// advance around the wheel at a decent clip.
|
|
||||||
for(uint32_t firstPixelHue = 0; firstPixelHue < rainbowLoops*65536;
|
|
||||||
firstPixelHue += 256) {
|
|
||||||
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
|
|
||||||
// Offset pixel hue by an amount to make one full revolution of the
|
|
||||||
// color wheel (range of 65536) along the length of the strip
|
|
||||||
// (strip.numPixels() steps):
|
|
||||||
uint32_t pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
|
|
||||||
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
|
|
||||||
// optionally add saturation and value (brightness) (each 0 to 255).
|
|
||||||
// Here we're using just the three-argument variant, though the
|
|
||||||
// second value (saturation) is a constant 255.
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue, 255,
|
|
||||||
255 * fadeVal / fadeMax)));
|
|
||||||
}
|
|
||||||
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
|
|
||||||
if(firstPixelHue < 65536) { // First loop,
|
|
||||||
if(fadeVal < fadeMax) fadeVal++; // fade in
|
|
||||||
} else if(firstPixelHue >= ((rainbowLoops-1) * 65536)) { // Last loop,
|
|
||||||
if(fadeVal > 0) fadeVal--; // fade out
|
|
||||||
} else {
|
|
||||||
fadeVal = fadeMax; // Interim loop, make sure fade is at max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int k=0; k<whiteLoops; k++) {
|
|
||||||
for(int j=0; j<256; j++) { // Ramp up 0 to 255
|
|
||||||
// Fill entire strip with white at gamma-corrected brightness level 'j':
|
|
||||||
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
|
|
||||||
strip.show();
|
|
||||||
}
|
|
||||||
delay(1000); // Pause 1 second
|
|
||||||
for(int j=255; j>=0; j--) { // Ramp down 255 to 0
|
|
||||||
strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));
|
|
||||||
strip.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(500); // Pause 1/2 second
|
|
||||||
}
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* This example is based on StrandtestBLE example and adapts it to use
|
|
||||||
* the new ArduinoBLE library.
|
|
||||||
*
|
|
||||||
* https://github.com/arduino-libraries/ArduinoBLE
|
|
||||||
*
|
|
||||||
* Supported boards:
|
|
||||||
* Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
|
|
||||||
Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.
|
|
||||||
*
|
|
||||||
* You can use a generic BLE central app, like LightBlue (iOS and Android) or
|
|
||||||
* nRF Connect (Android), to interact with the services and characteristics
|
|
||||||
* created in this sketch.
|
|
||||||
*
|
|
||||||
* This example code is in the public domain.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
#define PIN 15 // Pin where NeoPixels are connected
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
uint8_t rgb_values[3];
|
|
||||||
|
|
||||||
#include <ArduinoBLE.h>
|
|
||||||
|
|
||||||
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service
|
|
||||||
|
|
||||||
// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central
|
|
||||||
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.println("Hello World!");
|
|
||||||
|
|
||||||
// custom services and characteristics can be added as well
|
|
||||||
// begin initialization
|
|
||||||
if (!BLE.begin())
|
|
||||||
{
|
|
||||||
Serial.println("starting BLE failed!");
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("Peripheral address: ");
|
|
||||||
Serial.println(BLE.address());
|
|
||||||
|
|
||||||
// set advertised local name and service UUID:
|
|
||||||
BLE.setLocalName("LED");
|
|
||||||
BLE.setAdvertisedService(ledService);
|
|
||||||
|
|
||||||
// add the characteristic to the service
|
|
||||||
ledService.addCharacteristic(switchCharacteristic);
|
|
||||||
|
|
||||||
// add service
|
|
||||||
BLE.addService(ledService);
|
|
||||||
|
|
||||||
// set the initial value for the characeristic:
|
|
||||||
switchCharacteristic.writeValue(0);
|
|
||||||
|
|
||||||
// start advertising
|
|
||||||
BLE.advertise();
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
|
|
||||||
pinMode(PIN, OUTPUT);
|
|
||||||
digitalWrite(PIN, LOW);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
BLEDevice central = BLE.central();
|
|
||||||
|
|
||||||
// if a central is connected to peripheral:
|
|
||||||
if (central)
|
|
||||||
{
|
|
||||||
Serial.print("Connected to central: ");
|
|
||||||
// print the central's MAC address:
|
|
||||||
Serial.println(central.address());
|
|
||||||
|
|
||||||
// while the central is still connected to peripheral:
|
|
||||||
while (central.connected())
|
|
||||||
{
|
|
||||||
// if the remote device wrote to the characteristic,
|
|
||||||
// use the value to control the LED:
|
|
||||||
if (switchCharacteristic.written())
|
|
||||||
{
|
|
||||||
switch (switchCharacteristic.value())
|
|
||||||
{
|
|
||||||
case 'a':
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
colorWipe(strip.Color(0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
colorWipe(strip.Color(0, 0, 255), 20); // Blue
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
theaterChase(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
theaterChase(strip.Color(0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
theaterChase(strip.Color(255, 0, 255), 20); // Cyan
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
rainbow(10);
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
theaterChaseRainbow(20);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < strip.numPixels(); i++)
|
|
||||||
{ // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait)
|
|
||||||
{
|
|
||||||
for (int a = 0; a < 10; a++)
|
|
||||||
{ // Repeat 10 times...
|
|
||||||
for (int b = 0; b < 3; b++)
|
|
||||||
{ // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in steps of 3...
|
|
||||||
for (int c = b; c < strip.numPixels(); c += 3)
|
|
||||||
{
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(int wait)
|
|
||||||
{
|
|
||||||
// Hue of first pixel runs 5 complete loops through the color wheel.
|
|
||||||
// Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
|
|
||||||
// means we'll make 5*65536/256 = 1280 passes through this outer loop:
|
|
||||||
for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < strip.numPixels(); i++)
|
|
||||||
{ // For each pixel in strip...
|
|
||||||
// Offset pixel hue by an amount to make one full revolution of the
|
|
||||||
// color wheel (range of 65536) along the length of the strip
|
|
||||||
// (strip.numPixels() steps):
|
|
||||||
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
|
|
||||||
// optionally add saturation and value (brightness) (each 0 to 255).
|
|
||||||
// Here we're using just the single-argument hue variant. The result
|
|
||||||
// is passed through strip.gamma32() to provide 'truer' colors
|
|
||||||
// before assigning to each pixel:
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
|
|
||||||
void theaterChaseRainbow(int wait)
|
|
||||||
{
|
|
||||||
int firstPixelHue = 0; // First pixel starts at red (hue 0)
|
|
||||||
for (int a = 0; a < 30; a++)
|
|
||||||
{ // Repeat 30 times...
|
|
||||||
for (int b = 0; b < 3; b++)
|
|
||||||
{ // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in increments of 3...
|
|
||||||
for (int c = b; c < strip.numPixels(); c += 3)
|
|
||||||
{
|
|
||||||
// hue of pixel 'c' is offset by an amount to make one full
|
|
||||||
// revolution of the color wheel (range 65536) along the length
|
|
||||||
// of the strip (strip.numPixels() steps):
|
|
||||||
int hue = firstPixelHue + c * 65536L / strip.numPixels();
|
|
||||||
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-239
@@ -1,239 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* This example is based on StrandtestArduinoBLE example to make use of
|
|
||||||
* callbacks features of the ArduinoBLE library.
|
|
||||||
*
|
|
||||||
* https://github.com/arduino-libraries/ArduinoBLE
|
|
||||||
*
|
|
||||||
* Supported boards:
|
|
||||||
* Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
|
|
||||||
Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.
|
|
||||||
*
|
|
||||||
* You can use a generic BLE central app, like LightBlue (iOS and Android) or
|
|
||||||
* nRF Connect (Android), to interact with the services and characteristics
|
|
||||||
* created in this sketch.
|
|
||||||
*
|
|
||||||
* This example code is in the public domain.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
#define PIN 15 // Pin where NeoPixels are connected
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
uint8_t rgb_values[3];
|
|
||||||
|
|
||||||
#include <ArduinoBLE.h>
|
|
||||||
|
|
||||||
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service
|
|
||||||
|
|
||||||
// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central
|
|
||||||
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.println("Hello World!");
|
|
||||||
|
|
||||||
// custom services and characteristics can be added as well
|
|
||||||
// begin initialization
|
|
||||||
if (!BLE.begin())
|
|
||||||
{
|
|
||||||
Serial.println("starting BLE failed!");
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("Peripheral address: ");
|
|
||||||
Serial.println(BLE.address());
|
|
||||||
|
|
||||||
// set advertised local name and service UUID:
|
|
||||||
BLE.setLocalName("LEDCallback");
|
|
||||||
BLE.setAdvertisedService(ledService);
|
|
||||||
|
|
||||||
// add the characteristic to the service
|
|
||||||
ledService.addCharacteristic(switchCharacteristic);
|
|
||||||
|
|
||||||
// add service
|
|
||||||
BLE.addService(ledService);
|
|
||||||
// assign event handlers for connected, disconnected to peripheral
|
|
||||||
BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
|
|
||||||
BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
|
|
||||||
|
|
||||||
// assign event handlers for characteristic
|
|
||||||
switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten);
|
|
||||||
// set the initial value for the characeristic:
|
|
||||||
switchCharacteristic.writeValue(0);
|
|
||||||
|
|
||||||
// start advertising
|
|
||||||
BLE.advertise();
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
|
|
||||||
pinMode(PIN, OUTPUT);
|
|
||||||
digitalWrite(PIN, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
// poll for BLE events
|
|
||||||
BLE.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void blePeripheralConnectHandler(BLEDevice central)
|
|
||||||
{
|
|
||||||
// central connected event handler
|
|
||||||
Serial.print("Connected event, central: ");
|
|
||||||
Serial.println(central.address());
|
|
||||||
}
|
|
||||||
|
|
||||||
void blePeripheralDisconnectHandler(BLEDevice central)
|
|
||||||
{
|
|
||||||
// central disconnected event handler
|
|
||||||
Serial.print("Disconnected event, central: ");
|
|
||||||
Serial.println(central.address());
|
|
||||||
}
|
|
||||||
|
|
||||||
void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic)
|
|
||||||
{
|
|
||||||
// central wrote new value to characteristic, update LED
|
|
||||||
Serial.print("Characteristic event, written: ");
|
|
||||||
|
|
||||||
switch (switchCharacteristic.value())
|
|
||||||
{
|
|
||||||
case 'a':
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
colorWipe(strip.Color(0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
colorWipe(strip.Color(0, 0, 255), 20); // Blue
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
theaterChase(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
theaterChase(strip.Color(0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
theaterChase(strip.Color(255, 0, 255), 20); // Cyan
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
rainbow(10);
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
theaterChaseRainbow(20);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < strip.numPixels(); i++)
|
|
||||||
{ // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait)
|
|
||||||
{
|
|
||||||
for (int a = 0; a < 10; a++)
|
|
||||||
{ // Repeat 10 times...
|
|
||||||
for (int b = 0; b < 3; b++)
|
|
||||||
{ // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in steps of 3...
|
|
||||||
for (int c = b; c < strip.numPixels(); c += 3)
|
|
||||||
{
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(int wait)
|
|
||||||
{
|
|
||||||
// Hue of first pixel runs 5 complete loops through the color wheel.
|
|
||||||
// Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
|
|
||||||
// means we'll make 5*65536/256 = 1280 passes through this outer loop:
|
|
||||||
for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < strip.numPixels(); i++)
|
|
||||||
{ // For each pixel in strip...
|
|
||||||
// Offset pixel hue by an amount to make one full revolution of the
|
|
||||||
// color wheel (range of 65536) along the length of the strip
|
|
||||||
// (strip.numPixels() steps):
|
|
||||||
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
|
|
||||||
// optionally add saturation and value (brightness) (each 0 to 255).
|
|
||||||
// Here we're using just the single-argument hue variant. The result
|
|
||||||
// is passed through strip.gamma32() to provide 'truer' colors
|
|
||||||
// before assigning to each pixel:
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
|
|
||||||
void theaterChaseRainbow(int wait)
|
|
||||||
{
|
|
||||||
int firstPixelHue = 0; // First pixel starts at red (hue 0)
|
|
||||||
for (int a = 0; a < 30; a++)
|
|
||||||
{ // Repeat 30 times...
|
|
||||||
for (int b = 0; b < 3; b++)
|
|
||||||
{ // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in increments of 3...
|
|
||||||
for (int c = b; c < strip.numPixels(); c += 3)
|
|
||||||
{
|
|
||||||
// hue of pixel 'c' is offset by an amount to make one full
|
|
||||||
// revolution of the color wheel (range 65536) along the length
|
|
||||||
// of the strip (strip.numPixels() steps):
|
|
||||||
int hue = firstPixelHue + c * 65536L / strip.numPixels();
|
|
||||||
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#include "BLESerial.h"
|
|
||||||
|
|
||||||
// #define BLE_SERIAL_DEBUG
|
|
||||||
|
|
||||||
BLESerial* BLESerial::_instance = NULL;
|
|
||||||
|
|
||||||
BLESerial::BLESerial(unsigned char req, unsigned char rdy, unsigned char rst) :
|
|
||||||
BLEPeripheral(req, rdy, rst)
|
|
||||||
{
|
|
||||||
this->_txCount = 0;
|
|
||||||
this->_rxHead = this->_rxTail = 0;
|
|
||||||
this->_flushed = 0;
|
|
||||||
BLESerial::_instance = this;
|
|
||||||
|
|
||||||
addAttribute(this->_uartService);
|
|
||||||
addAttribute(this->_uartNameDescriptor);
|
|
||||||
setAdvertisedServiceUuid(this->_uartService.uuid());
|
|
||||||
addAttribute(this->_rxCharacteristic);
|
|
||||||
addAttribute(this->_rxNameDescriptor);
|
|
||||||
this->_rxCharacteristic.setEventHandler(BLEWritten, BLESerial::_received);
|
|
||||||
addAttribute(this->_txCharacteristic);
|
|
||||||
addAttribute(this->_txNameDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::begin(...) {
|
|
||||||
BLEPeripheral::begin();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.println(F("BLESerial::begin()"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::poll() {
|
|
||||||
if (millis() < this->_flushed + 100) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
} else {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::end() {
|
|
||||||
this->_rxCharacteristic.setEventHandler(BLEWritten, NULL);
|
|
||||||
this->_rxHead = this->_rxTail = 0;
|
|
||||||
flush();
|
|
||||||
BLEPeripheral::disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::available(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
int retval = (this->_rxHead - this->_rxTail + sizeof(this->_rxBuffer)) % sizeof(this->_rxBuffer);
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::available() = "));
|
|
||||||
Serial.println(retval);
|
|
||||||
#endif
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::peek(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_rxTail == this->_rxHead) return -1;
|
|
||||||
uint8_t byte = this->_rxBuffer[this->_rxTail];
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::peek() = "));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.println(byte, HEX);
|
|
||||||
#endif
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::read(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_rxTail == this->_rxHead) return -1;
|
|
||||||
this->_rxTail = (this->_rxTail + 1) % sizeof(this->_rxBuffer);
|
|
||||||
uint8_t byte = this->_rxBuffer[this->_rxTail];
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::read() = "));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.println(byte, HEX);
|
|
||||||
#endif
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::flush(void) {
|
|
||||||
if (this->_txCount == 0) return;
|
|
||||||
this->_txCharacteristic.setValue(this->_txBuffer, this->_txCount);
|
|
||||||
this->_flushed = millis();
|
|
||||||
this->_txCount = 0;
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.println(F("BLESerial::flush()"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t BLESerial::write(uint8_t byte) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_txCharacteristic.subscribed() == false) return 0;
|
|
||||||
this->_txBuffer[this->_txCount++] = byte;
|
|
||||||
if (this->_txCount == sizeof(this->_txBuffer)) flush();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::write("));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.print(byte, HEX);
|
|
||||||
Serial.println(F(") = 1"));
|
|
||||||
#endif
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLESerial::operator bool() {
|
|
||||||
bool retval = BLEPeripheral::connected();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::operator bool() = "));
|
|
||||||
Serial.println(retval);
|
|
||||||
#endif
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::_received(const uint8_t* data, size_t size) {
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
this->_rxHead = (this->_rxHead + 1) % sizeof(this->_rxBuffer);
|
|
||||||
this->_rxBuffer[this->_rxHead] = data[i];
|
|
||||||
}
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::received("));
|
|
||||||
for (int i = 0; i < size; i++) Serial.print((char) data[i]);
|
|
||||||
Serial.println(F(")"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::_received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic) {
|
|
||||||
BLESerial::_instance->_received(rxCharacteristic.value(), rxCharacteristic.valueLength());
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#ifndef _BLE_SERIAL_H_
|
|
||||||
#define _BLE_SERIAL_H_
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <BLEPeripheral.h>
|
|
||||||
|
|
||||||
class BLESerial : public BLEPeripheral, public Stream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BLESerial(unsigned char req, unsigned char rdy, unsigned char rst);
|
|
||||||
|
|
||||||
void begin(...);
|
|
||||||
void poll();
|
|
||||||
void end();
|
|
||||||
|
|
||||||
virtual int available(void);
|
|
||||||
virtual int peek(void);
|
|
||||||
virtual int read(void);
|
|
||||||
virtual void flush(void);
|
|
||||||
virtual size_t write(uint8_t byte);
|
|
||||||
using Print::write;
|
|
||||||
virtual operator bool();
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned long _flushed;
|
|
||||||
static BLESerial* _instance;
|
|
||||||
|
|
||||||
size_t _rxHead;
|
|
||||||
size_t _rxTail;
|
|
||||||
size_t _rxCount() const;
|
|
||||||
uint8_t _rxBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH];
|
|
||||||
size_t _txCount;
|
|
||||||
uint8_t _txBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH];
|
|
||||||
|
|
||||||
BLEService _uartService = BLEService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
|
|
||||||
BLEDescriptor _uartNameDescriptor = BLEDescriptor("2901", "UART");
|
|
||||||
BLECharacteristic _rxCharacteristic = BLECharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse, BLE_ATTRIBUTE_MAX_VALUE_LENGTH);
|
|
||||||
BLEDescriptor _rxNameDescriptor = BLEDescriptor("2901", "RX - Receive Data (Write)");
|
|
||||||
BLECharacteristic _txCharacteristic = BLECharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLENotify, BLE_ATTRIBUTE_MAX_VALUE_LENGTH);
|
|
||||||
BLEDescriptor _txNameDescriptor = BLEDescriptor("2901", "TX - Transfer Data (Notify)");
|
|
||||||
|
|
||||||
void _received(const uint8_t* data, size_t size);
|
|
||||||
static void _received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* This example was developed by the Hackerspace San Salvador to demonstrate
|
|
||||||
* the simultaneous use of the NeoPixel library and the Bluetooth SoftDevice.
|
|
||||||
* To compile this example you'll need to add support for the NRF52 based
|
|
||||||
* following the instructions at:
|
|
||||||
* https://github.com/sandeepmistry/arduino-nRF5
|
|
||||||
* Or adding the following URL to the board manager URLs on Arduino preferences:
|
|
||||||
* https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json
|
|
||||||
* Then you can install the BLEPeripheral library avaiable at:
|
|
||||||
* https://github.com/sandeepmistry/arduino-BLEPeripheral
|
|
||||||
* To test it, compile this example and use the UART module from the nRF
|
|
||||||
* Toolbox App for Android. Edit the interface and send the characters
|
|
||||||
* 'a' to 'i' to switch the animation.
|
|
||||||
* There is a delay because this example blocks the thread of execution but
|
|
||||||
* the change will be shown after the current animation ends. (This might
|
|
||||||
* take a couple of seconds)
|
|
||||||
* For more info write us at: info _at- teubi.co
|
|
||||||
*/
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <BLEPeripheral.h>
|
|
||||||
#include "BLESerial.h"
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
#define PIN 15 // Pin where NeoPixels are connected
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
// define pins (varies per shield/board)
|
|
||||||
#define BLE_REQ 10
|
|
||||||
#define BLE_RDY 2
|
|
||||||
#define BLE_RST 9
|
|
||||||
|
|
||||||
// create ble serial instance, see pinouts above
|
|
||||||
BLESerial BLESerial(BLE_REQ, BLE_RDY, BLE_RST);
|
|
||||||
|
|
||||||
uint8_t current_state = 0;
|
|
||||||
uint8_t rgb_values[3];
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.println("Hello World!");
|
|
||||||
// custom services and characteristics can be added as well
|
|
||||||
BLESerial.setLocalName("UART_HS");
|
|
||||||
BLESerial.begin();
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
|
|
||||||
//pinMode(PIN, OUTPUT);
|
|
||||||
//digitalWrite(PIN, LOW);
|
|
||||||
|
|
||||||
current_state = 'a';
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
while(BLESerial.available()) {
|
|
||||||
uint8_t character = BLESerial.read();
|
|
||||||
switch(character) {
|
|
||||||
case 'a':
|
|
||||||
case 'b':
|
|
||||||
case 'c':
|
|
||||||
case 'd':
|
|
||||||
case 'e':
|
|
||||||
case 'f':
|
|
||||||
case 'g':
|
|
||||||
case 'h':
|
|
||||||
current_state = character;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
switch(current_state) {
|
|
||||||
case 'a':
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
colorWipe(strip.Color( 0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
colorWipe(strip.Color( 0, 0, 255), 20); // Blue
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
theaterChase(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
theaterChase(strip.Color( 0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
theaterChase(strip.Color(255, 0, 255), 20); // Cyan
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
rainbow(10);
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
theaterChaseRainbow(20);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait) {
|
|
||||||
for(int a=0; a<10; a++) { // Repeat 10 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in steps of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(int wait) {
|
|
||||||
// Hue of first pixel runs 5 complete loops through the color wheel.
|
|
||||||
// Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
|
|
||||||
// means we'll make 5*65536/256 = 1280 passes through this outer loop:
|
|
||||||
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
// Offset pixel hue by an amount to make one full revolution of the
|
|
||||||
// color wheel (range of 65536) along the length of the strip
|
|
||||||
// (strip.numPixels() steps):
|
|
||||||
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
|
|
||||||
// optionally add saturation and value (brightness) (each 0 to 255).
|
|
||||||
// Here we're using just the single-argument hue variant. The result
|
|
||||||
// is passed through strip.gamma32() to provide 'truer' colors
|
|
||||||
// before assigning to each pixel:
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
|
|
||||||
void theaterChaseRainbow(int wait) {
|
|
||||||
int firstPixelHue = 0; // First pixel starts at red (hue 0)
|
|
||||||
for(int a=0; a<30; a++) { // Repeat 30 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in increments of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
// hue of pixel 'c' is offset by an amount to make one full
|
|
||||||
// revolution of the color wheel (range 65536) along the length
|
|
||||||
// of the strip (strip.numPixels() steps):
|
|
||||||
int hue = firstPixelHue + c * 65536L / strip.numPixels();
|
|
||||||
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#include "BLESerial.h"
|
|
||||||
|
|
||||||
// #define BLE_SERIAL_DEBUG
|
|
||||||
|
|
||||||
BLESerial* BLESerial::_instance = NULL;
|
|
||||||
|
|
||||||
BLESerial::BLESerial(unsigned char req, unsigned char rdy, unsigned char rst) :
|
|
||||||
BLEPeripheral(req, rdy, rst)
|
|
||||||
{
|
|
||||||
this->_txCount = 0;
|
|
||||||
this->_rxHead = this->_rxTail = 0;
|
|
||||||
this->_flushed = 0;
|
|
||||||
BLESerial::_instance = this;
|
|
||||||
|
|
||||||
addAttribute(this->_uartService);
|
|
||||||
addAttribute(this->_uartNameDescriptor);
|
|
||||||
setAdvertisedServiceUuid(this->_uartService.uuid());
|
|
||||||
addAttribute(this->_rxCharacteristic);
|
|
||||||
addAttribute(this->_rxNameDescriptor);
|
|
||||||
this->_rxCharacteristic.setEventHandler(BLEWritten, BLESerial::_received);
|
|
||||||
addAttribute(this->_txCharacteristic);
|
|
||||||
addAttribute(this->_txNameDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::begin(...) {
|
|
||||||
BLEPeripheral::begin();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.println(F("BLESerial::begin()"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::poll() {
|
|
||||||
if (millis() < this->_flushed + 100) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
} else {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::end() {
|
|
||||||
this->_rxCharacteristic.setEventHandler(BLEWritten, NULL);
|
|
||||||
this->_rxHead = this->_rxTail = 0;
|
|
||||||
flush();
|
|
||||||
BLEPeripheral::disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::available(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
int retval = (this->_rxHead - this->_rxTail + sizeof(this->_rxBuffer)) % sizeof(this->_rxBuffer);
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::available() = "));
|
|
||||||
Serial.println(retval);
|
|
||||||
#endif
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::peek(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_rxTail == this->_rxHead) return -1;
|
|
||||||
uint8_t byte = this->_rxBuffer[this->_rxTail];
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::peek() = "));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.println(byte, HEX);
|
|
||||||
#endif
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
int BLESerial::read(void) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_rxTail == this->_rxHead) return -1;
|
|
||||||
this->_rxTail = (this->_rxTail + 1) % sizeof(this->_rxBuffer);
|
|
||||||
uint8_t byte = this->_rxBuffer[this->_rxTail];
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::read() = "));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.println(byte, HEX);
|
|
||||||
#endif
|
|
||||||
return byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::flush(void) {
|
|
||||||
if (this->_txCount == 0) return;
|
|
||||||
this->_txCharacteristic.setValue(this->_txBuffer, this->_txCount);
|
|
||||||
this->_flushed = millis();
|
|
||||||
this->_txCount = 0;
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.println(F("BLESerial::flush()"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t BLESerial::write(uint8_t byte) {
|
|
||||||
BLEPeripheral::poll();
|
|
||||||
if (this->_txCharacteristic.subscribed() == false) return 0;
|
|
||||||
this->_txBuffer[this->_txCount++] = byte;
|
|
||||||
if (this->_txCount == sizeof(this->_txBuffer)) flush();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::write("));
|
|
||||||
Serial.print((char) byte);
|
|
||||||
Serial.print(F(" 0x"));
|
|
||||||
Serial.print(byte, HEX);
|
|
||||||
Serial.println(F(") = 1"));
|
|
||||||
#endif
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLESerial::operator bool() {
|
|
||||||
bool retval = BLEPeripheral::connected();
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::operator bool() = "));
|
|
||||||
Serial.println(retval);
|
|
||||||
#endif
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::_received(const uint8_t* data, size_t size) {
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
this->_rxHead = (this->_rxHead + 1) % sizeof(this->_rxBuffer);
|
|
||||||
this->_rxBuffer[this->_rxHead] = data[i];
|
|
||||||
}
|
|
||||||
#ifdef BLE_SERIAL_DEBUG
|
|
||||||
Serial.print(F("BLESerial::received("));
|
|
||||||
for (int i = 0; i < size; i++) Serial.print((char) data[i]);
|
|
||||||
Serial.println(F(")"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLESerial::_received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic) {
|
|
||||||
BLESerial::_instance->_received(rxCharacteristic.value(), rxCharacteristic.valueLength());
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#ifndef _BLE_SERIAL_H_
|
|
||||||
#define _BLE_SERIAL_H_
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <BLEPeripheral.h>
|
|
||||||
|
|
||||||
class BLESerial : public BLEPeripheral, public Stream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BLESerial(unsigned char req, unsigned char rdy, unsigned char rst);
|
|
||||||
|
|
||||||
void begin(...);
|
|
||||||
void poll();
|
|
||||||
void end();
|
|
||||||
|
|
||||||
virtual int available(void);
|
|
||||||
virtual int peek(void);
|
|
||||||
virtual int read(void);
|
|
||||||
virtual void flush(void);
|
|
||||||
virtual size_t write(uint8_t byte);
|
|
||||||
using Print::write;
|
|
||||||
virtual operator bool();
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned long _flushed;
|
|
||||||
static BLESerial* _instance;
|
|
||||||
|
|
||||||
size_t _rxHead;
|
|
||||||
size_t _rxTail;
|
|
||||||
size_t _rxCount() const;
|
|
||||||
uint8_t _rxBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH];
|
|
||||||
size_t _txCount;
|
|
||||||
uint8_t _txBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH];
|
|
||||||
|
|
||||||
BLEService _uartService = BLEService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
|
|
||||||
BLEDescriptor _uartNameDescriptor = BLEDescriptor("2901", "UART");
|
|
||||||
BLECharacteristic _rxCharacteristic = BLECharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse, BLE_ATTRIBUTE_MAX_VALUE_LENGTH);
|
|
||||||
BLEDescriptor _rxNameDescriptor = BLEDescriptor("2901", "RX - Receive Data (Write)");
|
|
||||||
BLECharacteristic _txCharacteristic = BLECharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLENotify, BLE_ATTRIBUTE_MAX_VALUE_LENGTH);
|
|
||||||
BLEDescriptor _txNameDescriptor = BLEDescriptor("2901", "TX - Transfer Data (Notify)");
|
|
||||||
|
|
||||||
void _received(const uint8_t* data, size_t size);
|
|
||||||
static void _received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* This example was developed by the Hackerspace San Salvador to demonstrate
|
|
||||||
* the simultaneous use of the NeoPixel library and the Bluetooth SoftDevice.
|
|
||||||
* To compile this example you'll need to add support for the NRF52 based
|
|
||||||
* following the instructions at:
|
|
||||||
* https://github.com/sandeepmistry/arduino-nRF5
|
|
||||||
* Or adding the following URL to the board manager URLs on Arduino preferences:
|
|
||||||
* https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json
|
|
||||||
* Then you can install the BLEPeripheral library avaiable at:
|
|
||||||
* https://github.com/sandeepmistry/arduino-BLEPeripheral
|
|
||||||
* To test it, compile this example and use the UART module from the nRF
|
|
||||||
* Toolbox App for Android. Edit the interface and send the characters
|
|
||||||
* 'a' to 'i' to switch the animation.
|
|
||||||
* There is a no delay because this example does not block the threads execution
|
|
||||||
* so the change will be shown immediately and will not need to wait for the current
|
|
||||||
* animation to end.
|
|
||||||
* For more info write us at: info _at- teubi.co
|
|
||||||
*/
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <BLEPeripheral.h>
|
|
||||||
#include "BLESerial.h"
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
#define PIN 15 // Pin where NeoPixels are connected
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
// define pins (varies per shield/board)
|
|
||||||
#define BLE_REQ 10
|
|
||||||
#define BLE_RDY 2
|
|
||||||
#define BLE_RST 9
|
|
||||||
|
|
||||||
// create ble serial instance, see pinouts above
|
|
||||||
BLESerial BLESerial(BLE_REQ, BLE_RDY, BLE_RST);
|
|
||||||
|
|
||||||
uint8_t current_state = 0;
|
|
||||||
uint8_t rgb_values[3];
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.println("Hello World!");
|
|
||||||
// custom services and characteristics can be added as well
|
|
||||||
BLESerial.setLocalName("UART_HS");
|
|
||||||
BLESerial.begin();
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
|
|
||||||
//pinMode(PIN, OUTPUT);
|
|
||||||
//digitalWrite(PIN, LOW);
|
|
||||||
|
|
||||||
current_state = 'a';
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
while(BLESerial.available()) {
|
|
||||||
uint8_t character = BLESerial.read();
|
|
||||||
switch(character) {
|
|
||||||
case 'a':
|
|
||||||
case 'b':
|
|
||||||
case 'c':
|
|
||||||
case 'd':
|
|
||||||
case 'e':
|
|
||||||
case 'f':
|
|
||||||
case 'g':
|
|
||||||
case 'h':
|
|
||||||
current_state = character;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
switch(current_state) {
|
|
||||||
case 'a':
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
colorWipe(strip.Color( 0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
colorWipe(strip.Color( 0, 0, 255), 20); // Blue
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
theaterChase(strip.Color(255, 0, 0), 20); // Red
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
theaterChase(strip.Color( 0, 255, 0), 20); // Green
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
theaterChase(strip.Color(255, 0, 255), 20); // Cyan
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
rainbow(10);
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
theaterChaseRainbow(20);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some functions of our own for creating animated effects -----------------
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
strip.setPixelColor(pixelCurrent, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
pixelCurrent++; // Advance current pixel
|
|
||||||
if(pixelCurrent >= pixelNumber) // Loop the pattern from the first LED
|
|
||||||
pixelCurrent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
for(int i = 0; i < pixelNumber; i++) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, color); // Set pixel's color (in RAM)
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
for(int i=0; i < pixelNumber; i+3) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Set pixel's color (in RAM)
|
|
||||||
}
|
|
||||||
pixelQueue++; // Advance current pixel
|
|
||||||
if(pixelQueue >= 3)
|
|
||||||
pixelQueue = 0; // Loop the pattern from the first LED
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(uint8_t wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait;
|
|
||||||
for(uint16_t i=0; i < pixelNumber; i++) {
|
|
||||||
strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
pixelCycle++; // Advance current cycle
|
|
||||||
if(pixelCycle >= 256)
|
|
||||||
pixelCycle = 0; // Loop the cycle back to the begining
|
|
||||||
}
|
|
||||||
|
|
||||||
//Theatre-style crawling lights with rainbow effect
|
|
||||||
void theaterChaseRainbow(uint8_t wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
for(int i=0; i < pixelNumber; i+3) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
for(int i=0; i < pixelNumber; i+3) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time
|
|
||||||
}
|
|
||||||
pixelQueue++; // Advance current queue
|
|
||||||
pixelCycle++; // Advance current cycle
|
|
||||||
if(pixelQueue >= 3)
|
|
||||||
pixelQueue = 0; // Loop
|
|
||||||
if(pixelCycle >= 256)
|
|
||||||
pixelCycle = 0; // Loop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input a value 0 to 255 to get a color value.
|
|
||||||
// The colours are a transition r - g - b - back to r.
|
|
||||||
uint32_t Wheel(byte WheelPos) {
|
|
||||||
WheelPos = 255 - WheelPos;
|
|
||||||
if(WheelPos < 85) {
|
|
||||||
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
|
|
||||||
}
|
|
||||||
if(WheelPos < 170) {
|
|
||||||
WheelPos -= 85;
|
|
||||||
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
|
|
||||||
}
|
|
||||||
WheelPos -= 170;
|
|
||||||
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
// Simple demonstration on using an input device to trigger changes on your
|
|
||||||
// NeoPixels. Wire a momentary push button to connect from ground to a
|
|
||||||
// digital IO pin. When the button is pressed it will change to a new pixel
|
|
||||||
// animation. Initial state has all pixels off -- press the button once to
|
|
||||||
// start the first animation. As written, the button does not interrupt an
|
|
||||||
// animation in-progress, it works only when idle.
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Digital IO pin connected to the button. This will be driven with a
|
|
||||||
// pull-up resistor so the switch pulls the pin to ground momentarily.
|
|
||||||
// On a high -> low transition the button press logic will execute.
|
|
||||||
#define BUTTON_PIN 2
|
|
||||||
|
|
||||||
#define PIXEL_PIN 6 // Digital IO pin connected to the NeoPixels.
|
|
||||||
|
|
||||||
#define PIXEL_COUNT 16 // Number of NeoPixels
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
boolean oldState = HIGH;
|
|
||||||
int mode = 0; // Currently-active animation mode, 0-9
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
||||||
strip.begin(); // Initialize NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Initialize all pixels to 'off'
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Get current button state.
|
|
||||||
boolean newState = digitalRead(BUTTON_PIN);
|
|
||||||
|
|
||||||
// Check if state changed from high to low (button press).
|
|
||||||
if((newState == LOW) && (oldState == HIGH)) {
|
|
||||||
// Short delay to debounce button.
|
|
||||||
delay(20);
|
|
||||||
// Check if button is still low after debounce.
|
|
||||||
newState = digitalRead(BUTTON_PIN);
|
|
||||||
if(newState == LOW) { // Yes, still low
|
|
||||||
if(++mode > 8) mode = 0; // Advance to next mode, wrap around after #8
|
|
||||||
switch(mode) { // Start the new animation...
|
|
||||||
case 0:
|
|
||||||
colorWipe(strip.Color( 0, 0, 0), 50); // Black/off
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 50); // Red
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
colorWipe(strip.Color( 0, 255, 0), 50); // Green
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
colorWipe(strip.Color( 0, 0, 255), 50); // Blue
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
theaterChase(strip.Color(127, 127, 127), 50); // White
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
theaterChase(strip.Color(127, 0, 0), 50); // Red
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
theaterChase(strip.Color( 0, 0, 127), 50); // Blue
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
rainbow(10);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
theaterChaseRainbow(50);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the last-read button state to the old state.
|
|
||||||
oldState = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait) {
|
|
||||||
for(int a=0; a<10; a++) { // Repeat 10 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in steps of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(int wait) {
|
|
||||||
// Hue of first pixel runs 3 complete loops through the color wheel.
|
|
||||||
// Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
|
|
||||||
// means we'll make 3*65536/256 = 768 passes through this outer loop:
|
|
||||||
for(long firstPixelHue = 0; firstPixelHue < 3*65536; firstPixelHue += 256) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
// Offset pixel hue by an amount to make one full revolution of the
|
|
||||||
// color wheel (range of 65536) along the length of the strip
|
|
||||||
// (strip.numPixels() steps):
|
|
||||||
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
|
|
||||||
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
|
|
||||||
// optionally add saturation and value (brightness) (each 0 to 255).
|
|
||||||
// Here we're using just the single-argument hue variant. The result
|
|
||||||
// is passed through strip.gamma32() to provide 'truer' colors
|
|
||||||
// before assigning to each pixel:
|
|
||||||
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
|
|
||||||
void theaterChaseRainbow(int wait) {
|
|
||||||
int firstPixelHue = 0; // First pixel starts at red (hue 0)
|
|
||||||
for(int a=0; a<30; a++) { // Repeat 30 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in increments of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
// hue of pixel 'c' is offset by an amount to make one full
|
|
||||||
// revolution of the color wheel (range 65536) along the length
|
|
||||||
// of the strip (strip.numPixels() steps):
|
|
||||||
int hue = firstPixelHue + c * 65536L / strip.numPixels();
|
|
||||||
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
|
|
||||||
// Released under the GPLv3 license to match the rest of the
|
|
||||||
// Adafruit NeoPixel library
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Which pin on the Arduino is connected to the NeoPixels?
|
|
||||||
#define PIN 6 // On Trinket or Gemma, suggest changing this to 1
|
|
||||||
|
|
||||||
// How many NeoPixels are attached to the Arduino?
|
|
||||||
#define NUMPIXELS 16 // Popular NeoPixel ring size
|
|
||||||
|
|
||||||
// When setting up the NeoPixel library, we tell it how many pixels,
|
|
||||||
// and which pin to use to send signals. Note that for older NeoPixel
|
|
||||||
// strips you might need to change the third parameter -- see the
|
|
||||||
// strandtest example for more information on possible values.
|
|
||||||
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
|
|
||||||
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
|
|
||||||
// Any other board, you can remove this part (but no harm leaving it):
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// END of Trinket-specific code.
|
|
||||||
|
|
||||||
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
pixels.clear(); // Set all pixel colors to 'off'
|
|
||||||
|
|
||||||
// The first NeoPixel in a strand is #0, second is 1, all the way up
|
|
||||||
// to the count of pixels minus one.
|
|
||||||
for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
|
|
||||||
|
|
||||||
// pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
|
|
||||||
// Here we're using a moderately bright green color:
|
|
||||||
pixels.setPixelColor(i, pixels.Color(0, 150, 0));
|
|
||||||
|
|
||||||
pixels.show(); // Send the updated pixel colors to the hardware.
|
|
||||||
|
|
||||||
delay(DELAYVAL); // Pause before next pass through loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
|
|
||||||
// Released under the GPLv3 license to match the rest of the
|
|
||||||
// Adafruit NeoPixel library
|
|
||||||
// This sketch shows use of the "new" operator with Adafruit_NeoPixel.
|
|
||||||
// It's helpful if you don't know NeoPixel settings at compile time or
|
|
||||||
// just want to store this settings in EEPROM or a file on an SD card.
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Which pin on the Arduino is connected to the NeoPixels?
|
|
||||||
int pin = 6; // On Trinket or Gemma, suggest changing this to 1
|
|
||||||
|
|
||||||
// How many NeoPixels are attached to the Arduino?
|
|
||||||
int numPixels = 16; // Popular NeoPixel ring size
|
|
||||||
|
|
||||||
// NeoPixel color format & data rate. See the strandtest example for
|
|
||||||
// information on possible values.
|
|
||||||
int pixelFormat = NEO_GRB + NEO_KHZ800;
|
|
||||||
|
|
||||||
// Rather than declaring the whole NeoPixel object here, we just create
|
|
||||||
// a pointer for one, which we'll then allocate later...
|
|
||||||
Adafruit_NeoPixel *pixels;
|
|
||||||
|
|
||||||
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
|
|
||||||
// Any other board, you can remove this part (but no harm leaving it):
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// END of Trinket-specific code.
|
|
||||||
|
|
||||||
// Right about here is where we could read 'pin', 'numPixels' and/or
|
|
||||||
// 'pixelFormat' from EEPROM or a file on SD or whatever. This is a simple
|
|
||||||
// example and doesn't do that -- those variables are just set to fixed
|
|
||||||
// values at the top of this code -- but this is where it would happen.
|
|
||||||
|
|
||||||
// Then create a new NeoPixel object dynamically with these values:
|
|
||||||
pixels = new Adafruit_NeoPixel(numPixels, pin, pixelFormat);
|
|
||||||
|
|
||||||
// Going forward from here, code works almost identically to any other
|
|
||||||
// NeoPixel example, but instead of the dot operator on function calls
|
|
||||||
// (e.g. pixels.begin()), we instead use pointer indirection (->) like so:
|
|
||||||
pixels->begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
// You'll see more of this in the loop() function below.
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
pixels->clear(); // Set all pixel colors to 'off'
|
|
||||||
|
|
||||||
// The first NeoPixel in a strand is #0, second is 1, all the way up
|
|
||||||
// to the count of pixels minus one.
|
|
||||||
for(int i=0; i<numPixels; i++) { // For each pixel...
|
|
||||||
|
|
||||||
// pixels->Color() takes RGB values, from 0,0,0 up to 255,255,255
|
|
||||||
// Here we're using a moderately bright green color:
|
|
||||||
pixels->setPixelColor(i, pixels->Color(0, 150, 0));
|
|
||||||
|
|
||||||
pixels->show(); // Send the updated pixel colors to the hardware.
|
|
||||||
|
|
||||||
delay(DELAYVAL); // Pause before next pass through loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
// A basic everyday NeoPixel strip test program.
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Which pin on the Arduino is connected to the NeoPixels?
|
|
||||||
// On a Trinket or Gemma we suggest changing this to 1:
|
|
||||||
#define LED_PIN 6
|
|
||||||
|
|
||||||
// How many NeoPixels are attached to the Arduino?
|
|
||||||
#define LED_COUNT 60
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
|
|
||||||
// setup() function -- runs once at startup --------------------------------
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
|
|
||||||
// Any other board, you can remove this part (but no harm leaving it):
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// END of Trinket-specific code.
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// loop() function -- runs repeatedly as long as board is on ---------------
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Fill along the length of the strip in various colors...
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 50); // Red
|
|
||||||
colorWipe(strip.Color( 0, 255, 0), 50); // Green
|
|
||||||
colorWipe(strip.Color( 0, 0, 255), 50); // Blue
|
|
||||||
|
|
||||||
// Do a theater marquee effect in various colors...
|
|
||||||
theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
|
|
||||||
theaterChase(strip.Color(127, 0, 0), 50); // Red, half brightness
|
|
||||||
theaterChase(strip.Color( 0, 0, 127), 50); // Blue, half brightness
|
|
||||||
|
|
||||||
rainbow(10); // Flowing rainbow cycle along the whole strip
|
|
||||||
theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Some functions of our own for creating animated effects -----------------
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
|
|
||||||
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait) {
|
|
||||||
for(int a=0; a<10; a++) { // Repeat 10 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in steps of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(int wait) {
|
|
||||||
// Hue of first pixel runs 5 complete loops through the color wheel.
|
|
||||||
// Color wheel has a range of 65536 but it's OK if we roll over, so
|
|
||||||
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
|
|
||||||
// means we'll make 5*65536/256 = 1280 passes through this loop:
|
|
||||||
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
|
|
||||||
// strip.rainbow() can take a single argument (first pixel hue) or
|
|
||||||
// optionally a few extras: number of rainbow repetitions (default 1),
|
|
||||||
// saturation and value (brightness) (both 0-255, similar to the
|
|
||||||
// ColorHSV() function, default 255), and a true/false flag for whether
|
|
||||||
// to apply gamma correction to provide 'truer' colors (default true).
|
|
||||||
strip.rainbow(firstPixelHue);
|
|
||||||
// Above line is equivalent to:
|
|
||||||
// strip.rainbow(firstPixelHue, 1, 255, 255, true);
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
|
|
||||||
void theaterChaseRainbow(int wait) {
|
|
||||||
int firstPixelHue = 0; // First pixel starts at red (hue 0)
|
|
||||||
for(int a=0; a<30; a++) { // Repeat 30 times...
|
|
||||||
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
|
|
||||||
strip.clear(); // Set all pixels in RAM to 0 (off)
|
|
||||||
// 'c' counts up from 'b' to end of strip in increments of 3...
|
|
||||||
for(int c=b; c<strip.numPixels(); c += 3) {
|
|
||||||
// hue of pixel 'c' is offset by an amount to make one full
|
|
||||||
// revolution of the color wheel (range 65536) along the length
|
|
||||||
// of the strip (strip.numPixels() steps):
|
|
||||||
int hue = firstPixelHue + c * 65536L / strip.numPixels();
|
|
||||||
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
|
|
||||||
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip with new contents
|
|
||||||
delay(wait); // Pause for a moment
|
|
||||||
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
// A non-blocking everyday NeoPixel strip test program.
|
|
||||||
|
|
||||||
// NEOPIXEL BEST PRACTICES for most reliable operation:
|
|
||||||
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
|
|
||||||
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
|
|
||||||
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
|
|
||||||
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
|
|
||||||
// connect GROUND (-) first, then +, then data.
|
|
||||||
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
|
|
||||||
// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
|
|
||||||
// (Skipping these may work OK on your workbench but can fail in the field)
|
|
||||||
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Which pin on the Arduino is connected to the NeoPixels?
|
|
||||||
// On a Trinket or Gemma we suggest changing this to 1:
|
|
||||||
#ifdef ESP32
|
|
||||||
// Cannot use 6 as output for ESP. Pins 6-11 are connected to SPI flash. Use 16 instead.
|
|
||||||
#define LED_PIN 16
|
|
||||||
#else
|
|
||||||
#define LED_PIN 6
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// How many NeoPixels are attached to the Arduino?
|
|
||||||
#define LED_COUNT 60
|
|
||||||
|
|
||||||
// Declare our NeoPixel strip object:
|
|
||||||
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
// Argument 1 = Number of pixels in NeoPixel strip
|
|
||||||
// Argument 2 = Arduino pin number (most are valid)
|
|
||||||
// Argument 3 = Pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
|
|
||||||
unsigned long pixelPrevious = 0; // Previous Pixel Millis
|
|
||||||
unsigned long patternPrevious = 0; // Previous Pattern Millis
|
|
||||||
int patternCurrent = 0; // Current Pattern Number
|
|
||||||
int patternInterval = 5000; // Pattern Interval (ms)
|
|
||||||
bool patternComplete = false;
|
|
||||||
|
|
||||||
int pixelInterval = 50; // Pixel Interval (ms)
|
|
||||||
int pixelQueue = 0; // Pattern Pixel Queue
|
|
||||||
int pixelCycle = 0; // Pattern Pixel Cycle
|
|
||||||
uint16_t pixelNumber = LED_COUNT; // Total Number of Pixels
|
|
||||||
|
|
||||||
// setup() function -- runs once at startup --------------------------------
|
|
||||||
void setup() {
|
|
||||||
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
|
|
||||||
// Any other board, you can remove this part (but no harm leaving it):
|
|
||||||
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
|
|
||||||
clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// END of Trinket-specific code.
|
|
||||||
|
|
||||||
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
|
||||||
strip.show(); // Turn OFF all pixels ASAP
|
|
||||||
strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop() function -- runs repeatedly as long as board is on ---------------
|
|
||||||
void loop() {
|
|
||||||
unsigned long currentMillis = millis(); // Update current time
|
|
||||||
if( patternComplete || (currentMillis - patternPrevious) >= patternInterval) { // Check for expired time
|
|
||||||
patternComplete = false;
|
|
||||||
patternPrevious = currentMillis;
|
|
||||||
patternCurrent++; // Advance to next pattern
|
|
||||||
if(patternCurrent >= 7)
|
|
||||||
patternCurrent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time
|
|
||||||
pixelPrevious = currentMillis; // Run current frame
|
|
||||||
switch (patternCurrent) {
|
|
||||||
case 7:
|
|
||||||
theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
rainbow(10); // Flowing rainbow cycle along the whole strip
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
theaterChase(strip.Color(0, 0, 127), 50); // Blue
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
theaterChase(strip.Color(127, 0, 0), 50); // Red
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
theaterChase(strip.Color(127, 127, 127), 50); // White
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
colorWipe(strip.Color(0, 0, 255), 50); // Blue
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
colorWipe(strip.Color(0, 255, 0), 50); // Green
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 50); // Red
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some functions of our own for creating animated effects -----------------
|
|
||||||
|
|
||||||
// Fill strip pixels one after another with a color. Strip is NOT cleared
|
|
||||||
// first; anything there will be covered pixel by pixel. Pass in color
|
|
||||||
// (as a single 'packed' 32-bit value, which you can get by calling
|
|
||||||
// strip.Color(red, green, blue) as shown in the loop() function above),
|
|
||||||
// and a delay time (in milliseconds) between pixels.
|
|
||||||
void colorWipe(uint32_t color, int wait) {
|
|
||||||
static uint16_t current_pixel = 0;
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
strip.setPixelColor(current_pixel++, color); // Set pixel's color (in RAM)
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
if(current_pixel >= pixelNumber) { // Loop the pattern from the first LED
|
|
||||||
current_pixel = 0;
|
|
||||||
patternComplete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
|
|
||||||
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
|
|
||||||
// between frames.
|
|
||||||
void theaterChase(uint32_t color, int wait) {
|
|
||||||
static uint32_t loop_count = 0;
|
|
||||||
static uint16_t current_pixel = 0;
|
|
||||||
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
|
|
||||||
strip.clear();
|
|
||||||
|
|
||||||
for(int c=current_pixel; c < pixelNumber; c += 3) {
|
|
||||||
strip.setPixelColor(c, color);
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
|
|
||||||
current_pixel++;
|
|
||||||
if (current_pixel >= 3) {
|
|
||||||
current_pixel = 0;
|
|
||||||
loop_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loop_count >= 10) {
|
|
||||||
current_pixel = 0;
|
|
||||||
loop_count = 0;
|
|
||||||
patternComplete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
|
|
||||||
void rainbow(uint8_t wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait;
|
|
||||||
for(uint16_t i=0; i < pixelNumber; i++) {
|
|
||||||
strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time
|
|
||||||
}
|
|
||||||
strip.show(); // Update strip to match
|
|
||||||
pixelCycle++; // Advance current cycle
|
|
||||||
if(pixelCycle >= 256)
|
|
||||||
pixelCycle = 0; // Loop the cycle back to the begining
|
|
||||||
}
|
|
||||||
|
|
||||||
//Theatre-style crawling lights with rainbow effect
|
|
||||||
void theaterChaseRainbow(uint8_t wait) {
|
|
||||||
if(pixelInterval != wait)
|
|
||||||
pixelInterval = wait; // Update delay time
|
|
||||||
for(int i=0; i < pixelNumber; i+=3) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
for(int i=0; i < pixelNumber; i+=3) {
|
|
||||||
strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time
|
|
||||||
}
|
|
||||||
pixelQueue++; // Advance current queue
|
|
||||||
pixelCycle++; // Advance current cycle
|
|
||||||
if(pixelQueue >= 3)
|
|
||||||
pixelQueue = 0; // Loop
|
|
||||||
if(pixelCycle >= 256)
|
|
||||||
pixelCycle = 0; // Loop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input a value 0 to 255 to get a color value.
|
|
||||||
// The colours are a transition r - g - b - back to r.
|
|
||||||
uint32_t Wheel(byte WheelPos) {
|
|
||||||
WheelPos = 255 - WheelPos;
|
|
||||||
if(WheelPos < 85) {
|
|
||||||
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
|
|
||||||
}
|
|
||||||
if(WheelPos < 170) {
|
|
||||||
WheelPos -= 85;
|
|
||||||
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
|
|
||||||
}
|
|
||||||
WheelPos -= 170;
|
|
||||||
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/power.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PIN 6
|
|
||||||
|
|
||||||
// Parameter 1 = number of pixels in strip
|
|
||||||
// Parameter 2 = Arduino pin number (most are valid)
|
|
||||||
// Parameter 3 = pixel type flags, add together as needed:
|
|
||||||
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
|
|
||||||
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
|
|
||||||
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
|
|
||||||
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
|
|
||||||
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
|
|
||||||
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
|
|
||||||
// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
|
|
||||||
// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
|
|
||||||
// and minimize distance between Arduino and first pixel. Avoid connecting
|
|
||||||
// on a live circuit...if you must, connect GND first.
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
|
|
||||||
#if defined (__AVR_ATtiny85__)
|
|
||||||
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
|
|
||||||
#endif
|
|
||||||
// End of trinket special code
|
|
||||||
|
|
||||||
strip.begin();
|
|
||||||
strip.setBrightness(50);
|
|
||||||
strip.show(); // Initialize all pixels to 'off'
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Some example procedures showing how to display to the pixels:
|
|
||||||
colorWipe(strip.Color(255, 0, 0), 50); // Red
|
|
||||||
colorWipe(strip.Color(0, 255, 0), 50); // Green
|
|
||||||
colorWipe(strip.Color(0, 0, 255), 50); // Blue
|
|
||||||
//colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
|
|
||||||
// Send a theater pixel chase in...
|
|
||||||
theaterChase(strip.Color(127, 127, 127), 50); // White
|
|
||||||
theaterChase(strip.Color(127, 0, 0), 50); // Red
|
|
||||||
theaterChase(strip.Color(0, 0, 127), 50); // Blue
|
|
||||||
|
|
||||||
rainbow(20);
|
|
||||||
rainbowCycle(20);
|
|
||||||
theaterChaseRainbow(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the dots one after the other with a color
|
|
||||||
void colorWipe(uint32_t c, uint8_t wait) {
|
|
||||||
for(uint16_t i=0; i<strip.numPixels(); i++) {
|
|
||||||
strip.setPixelColor(i, c);
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rainbow(uint8_t wait) {
|
|
||||||
uint16_t i, j;
|
|
||||||
|
|
||||||
for(j=0; j<256; j++) {
|
|
||||||
for(i=0; i<strip.numPixels(); i++) {
|
|
||||||
strip.setPixelColor(i, Wheel((i+j) & 255));
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slightly different, this makes the rainbow equally distributed throughout
|
|
||||||
void rainbowCycle(uint8_t wait) {
|
|
||||||
uint16_t i, j;
|
|
||||||
|
|
||||||
for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
|
|
||||||
for(i=0; i< strip.numPixels(); i++) {
|
|
||||||
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
delay(wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Theatre-style crawling lights.
|
|
||||||
void theaterChase(uint32_t c, uint8_t wait) {
|
|
||||||
for (int j=0; j<10; j++) { //do 10 cycles of chasing
|
|
||||||
for (int q=0; q < 3; q++) {
|
|
||||||
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
|
|
||||||
strip.setPixelColor(i+q, c); //turn every third pixel on
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
|
|
||||||
delay(wait);
|
|
||||||
|
|
||||||
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
|
|
||||||
strip.setPixelColor(i+q, 0); //turn every third pixel off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Theatre-style crawling lights with rainbow effect
|
|
||||||
void theaterChaseRainbow(uint8_t wait) {
|
|
||||||
for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel
|
|
||||||
for (int q=0; q < 3; q++) {
|
|
||||||
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
|
|
||||||
strip.setPixelColor(i+q, Wheel( (i+j) % 255)); //turn every third pixel on
|
|
||||||
}
|
|
||||||
strip.show();
|
|
||||||
|
|
||||||
delay(wait);
|
|
||||||
|
|
||||||
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
|
|
||||||
strip.setPixelColor(i+q, 0); //turn every third pixel off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input a value 0 to 255 to get a color value.
|
|
||||||
// The colours are a transition r - g - b - back to r.
|
|
||||||
uint32_t Wheel(byte WheelPos) {
|
|
||||||
WheelPos = 255 - WheelPos;
|
|
||||||
if(WheelPos < 85) {
|
|
||||||
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
|
|
||||||
}
|
|
||||||
if(WheelPos < 170) {
|
|
||||||
WheelPos -= 85;
|
|
||||||
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
|
|
||||||
}
|
|
||||||
WheelPos -= 170;
|
|
||||||
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// This is a mash-up of the Due show() code + insights from Michael Miller's
|
|
||||||
// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus
|
|
||||||
// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution.
|
|
||||||
#if defined(K210)
|
|
||||||
#define KENDRYTE_K210 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(KENDRYTE_K210)
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "sysctl.h"
|
|
||||||
|
|
||||||
void k210Show(
|
|
||||||
uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
|
|
||||||
{
|
|
||||||
|
|
||||||
#define CYCLES_800_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2500000) // 0.4us
|
|
||||||
#define CYCLES_800_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 1250000) // 0.8us
|
|
||||||
#define CYCLES_800 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 800000) // 1.25us per bit
|
|
||||||
#define CYCLES_400_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2000000) // 0.5uS
|
|
||||||
#define CYCLES_400_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 833333) // 1.2us
|
|
||||||
#define CYCLES_400 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 400000) // 2.5us per bit
|
|
||||||
|
|
||||||
uint8_t *p, *end, pix, mask;
|
|
||||||
uint32_t t, time0, time1, period, c, startTime;
|
|
||||||
|
|
||||||
p = pixels;
|
|
||||||
end = p + numBytes;
|
|
||||||
pix = *p++;
|
|
||||||
mask = 0x80;
|
|
||||||
startTime = 0;
|
|
||||||
|
|
||||||
#ifdef NEO_KHZ400
|
|
||||||
if (is800KHz)
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
time0 = CYCLES_800_T0H;
|
|
||||||
time1 = CYCLES_800_T1H;
|
|
||||||
period = CYCLES_800;
|
|
||||||
#ifdef NEO_KHZ400
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // 400 KHz bitstream
|
|
||||||
time0 = CYCLES_400_T0H;
|
|
||||||
time1 = CYCLES_400_T1H;
|
|
||||||
period = CYCLES_400;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (t = time0;; t = time0)
|
|
||||||
{
|
|
||||||
if (pix & mask)
|
|
||||||
t = time1; // Bit high duration
|
|
||||||
while (((c = read_cycle()) - startTime) < period)
|
|
||||||
; // Wait for bit start
|
|
||||||
digitalWrite(pin, HIGH);
|
|
||||||
startTime = c; // Save start time
|
|
||||||
while (((c = read_cycle()) - startTime) < t)
|
|
||||||
; // Wait high duration
|
|
||||||
digitalWrite(pin, LOW);
|
|
||||||
|
|
||||||
if (!(mask >>= 1))
|
|
||||||
{ // Next bit/byte
|
|
||||||
if (p >= end)
|
|
||||||
break;
|
|
||||||
pix = *p++;
|
|
||||||
mask = 0x80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ((read_cycle() - startTime) < period)
|
|
||||||
; // Wait for last bit
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // KENDRYTE_K210
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#######################################
|
|
||||||
# Syntax Coloring Map For Adafruit_NeoPixel
|
|
||||||
#######################################
|
|
||||||
# Class
|
|
||||||
#######################################
|
|
||||||
|
|
||||||
Adafruit_NeoPixel KEYWORD1
|
|
||||||
|
|
||||||
#######################################
|
|
||||||
# Methods and Functions
|
|
||||||
#######################################
|
|
||||||
|
|
||||||
begin KEYWORD2
|
|
||||||
show KEYWORD2
|
|
||||||
setPin KEYWORD2
|
|
||||||
setPixelColor KEYWORD2
|
|
||||||
fill KEYWORD2
|
|
||||||
setBrightness KEYWORD2
|
|
||||||
clear KEYWORD2
|
|
||||||
updateLength KEYWORD2
|
|
||||||
updateType KEYWORD2
|
|
||||||
canShow KEYWORD2
|
|
||||||
getPixels KEYWORD2
|
|
||||||
getBrightness KEYWORD2
|
|
||||||
getPin KEYWORD2
|
|
||||||
numPixels KEYWORD2
|
|
||||||
getPixelColor KEYWORD2
|
|
||||||
sine8 KEYWORD2
|
|
||||||
gamma8 KEYWORD2
|
|
||||||
Color KEYWORD2
|
|
||||||
ColorHSV KEYWORD2
|
|
||||||
gamma32 KEYWORD2
|
|
||||||
|
|
||||||
#######################################
|
|
||||||
# Constants
|
|
||||||
#######################################
|
|
||||||
|
|
||||||
NEO_COLMASK LITERAL1
|
|
||||||
NEO_SPDMASK LITERAL1
|
|
||||||
NEO_KHZ800 LITERAL1
|
|
||||||
NEO_KHZ400 LITERAL1
|
|
||||||
NEO_RGB LITERAL1
|
|
||||||
NEO_RBG LITERAL1
|
|
||||||
NEO_GRB LITERAL1
|
|
||||||
NEO_GBR LITERAL1
|
|
||||||
NEO_BRG LITERAL1
|
|
||||||
NEO_BGR LITERAL1
|
|
||||||
NEO_WRGB LITERAL1
|
|
||||||
NEO_WRBG LITERAL1
|
|
||||||
NEO_WGRB LITERAL1
|
|
||||||
NEO_WGBR LITERAL1
|
|
||||||
NEO_WBRG LITERAL1
|
|
||||||
NEO_WBGR LITERAL1
|
|
||||||
NEO_RWGB LITERAL1
|
|
||||||
NEO_RWBG LITERAL1
|
|
||||||
NEO_RGWB LITERAL1
|
|
||||||
NEO_RGBW LITERAL1
|
|
||||||
NEO_RBWG LITERAL1
|
|
||||||
NEO_RBGW LITERAL1
|
|
||||||
NEO_GWRB LITERAL1
|
|
||||||
NEO_GWBR LITERAL1
|
|
||||||
NEO_GRWB LITERAL1
|
|
||||||
NEO_GRBW LITERAL1
|
|
||||||
NEO_GBWR LITERAL1
|
|
||||||
NEO_GBRW LITERAL1
|
|
||||||
NEO_BWRG LITERAL1
|
|
||||||
NEO_BWGR LITERAL1
|
|
||||||
NEO_BRWG LITERAL1
|
|
||||||
NEO_BRGW LITERAL1
|
|
||||||
NEO_BGWR LITERAL1
|
|
||||||
NEO_BGRW LITERAL1
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
name=Adafruit NeoPixel
|
|
||||||
version=1.15.4
|
|
||||||
author=Adafruit
|
|
||||||
maintainer=Adafruit <info@adafruit.com>
|
|
||||||
sentence=Arduino library for controlling single-wire-based LED pixels and strip.
|
|
||||||
paragraph=Arduino library for controlling single-wire-based LED pixels and strip.
|
|
||||||
category=Display
|
|
||||||
url=https://github.com/adafruit/Adafruit_NeoPixel
|
|
||||||
architectures=*
|
|
||||||
includes=Adafruit_NeoPixel.h
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#if defined(ARDUINO_ARCH_PSOC6)
|
|
||||||
|
|
||||||
#include "cyhal_gpio.h"
|
|
||||||
#include "cyhal_system.h"
|
|
||||||
#include "cy_syslib.h"
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
void psoc6_show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHZ)
|
|
||||||
{
|
|
||||||
if (!pixels) return;
|
|
||||||
noInterrupts();
|
|
||||||
GPIO_PRT_Type *base = CYHAL_GET_PORTADDR(mapping_gpio_pin[pin]);
|
|
||||||
uint32_t pinNum = CYHAL_GET_PIN(mapping_gpio_pin[pin]);
|
|
||||||
if(is800KHZ){
|
|
||||||
for (uint16_t i = 0; i < numBytes; i++) {
|
|
||||||
uint8_t b = pixels[i];
|
|
||||||
for (uint8_t j = 0; j < 8; j++) {
|
|
||||||
bool bit = (b & 0x80) != 0;
|
|
||||||
b <<= 1;
|
|
||||||
switch (bit) {
|
|
||||||
case 1:
|
|
||||||
// Send a 1-bit
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
// Send a 0-bit
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
for (size_t i = 0; i < numBytes; i++){
|
|
||||||
uint8_t b = pixels[i];
|
|
||||||
for (uint8_t j = 0; j < 8; j++) {
|
|
||||||
bool bit = (b & 0x80) != 0;
|
|
||||||
b <<= 1;
|
|
||||||
switch (bit){
|
|
||||||
case 1:
|
|
||||||
// Send a 1-bit
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
// Send a 0-bit
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 1);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
Cy_GPIO_Write(base, pinNum, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interrupts();
|
|
||||||
delayMicroseconds(50);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// -------------------------------------------------- //
|
|
||||||
// This file is autogenerated by pioasm; do not edit! //
|
|
||||||
// -------------------------------------------------- //
|
|
||||||
|
|
||||||
// Unless you know what you are doing...
|
|
||||||
// Lines 47 and 52 have been edited to set transmit bit count
|
|
||||||
|
|
||||||
#if !PICO_NO_HARDWARE
|
|
||||||
#include "hardware/pio.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ------ //
|
|
||||||
// ws2812 //
|
|
||||||
// ------ //
|
|
||||||
|
|
||||||
#define ws2812_wrap_target 0
|
|
||||||
#define ws2812_wrap 3
|
|
||||||
|
|
||||||
#define ws2812_T1 2
|
|
||||||
#define ws2812_T2 5
|
|
||||||
#define ws2812_T3 3
|
|
||||||
|
|
||||||
static const uint16_t ws2812_program_instructions[] = {
|
|
||||||
// .wrap_target
|
|
||||||
0x6221, // 0: out x, 1 side 0 [2]
|
|
||||||
0x1123, // 1: jmp !x, 3 side 1 [1]
|
|
||||||
0x1400, // 2: jmp 0 side 1 [4]
|
|
||||||
0xa442, // 3: nop side 0 [4]
|
|
||||||
// .wrap
|
|
||||||
};
|
|
||||||
|
|
||||||
#if !PICO_NO_HARDWARE
|
|
||||||
static const struct pio_program ws2812_program = {
|
|
||||||
.instructions = ws2812_program_instructions,
|
|
||||||
.length = 4,
|
|
||||||
.origin = -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
|
|
||||||
pio_sm_config c = pio_get_default_sm_config();
|
|
||||||
sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
|
|
||||||
sm_config_set_sideset(&c, 1, false, false);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "hardware/clocks.h"
|
|
||||||
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin,
|
|
||||||
float freq, uint bits) {
|
|
||||||
pio_gpio_init(pio, pin);
|
|
||||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
|
||||||
pio_sm_config c = ws2812_program_get_default_config(offset);
|
|
||||||
sm_config_set_sideset_pins(&c, pin);
|
|
||||||
sm_config_set_out_shift(&c, false, true,
|
|
||||||
bits); // <----<<< Length changed to "bits"
|
|
||||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
|
||||||
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
|
|
||||||
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
|
|
||||||
sm_config_set_clkdiv(&c, div);
|
|
||||||
pio_sm_init(pio, sm, offset, &c);
|
|
||||||
pio_sm_set_enabled(pio, sm, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
|
|
||||||
#include "src/ArduinoJson.h"
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
ArduinoJson: change log
|
|
||||||
=======================
|
|
||||||
|
|
||||||
v7.4.2 (2025-06-20)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix truncated strings on Arduino Due (issue #2181)
|
|
||||||
|
|
||||||
v7.4.1 (2025-04-11)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix crash with tiny Flash strings (issue #2170)
|
|
||||||
|
|
||||||
v7.4.0 (2025-04-09)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Optimize storage of tiny strings (up to 3 characters)
|
|
||||||
* Fix support for `const char[]` (issue #2166)
|
|
||||||
|
|
||||||
v7.3.1 (2025-02-27)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix conversion from static string to number
|
|
||||||
* Slightly reduce code size
|
|
||||||
|
|
||||||
v7.3.0 (2024-12-29)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix support for NUL characters in `deserializeJson()`
|
|
||||||
* Make `ElementProxy` and `MemberProxy` non-copyable
|
|
||||||
* Change string copy policy: only string literal are stored by pointer
|
|
||||||
* `JsonString` is now stored by copy, unless specified otherwise
|
|
||||||
* Replace undocumented `JsonString::Ownership` with `bool`
|
|
||||||
* Rename undocumented `JsonString::isLinked()` to `isStatic()`
|
|
||||||
* Move public facing SFINAEs to template declarations
|
|
||||||
|
|
||||||
> ### BREAKING CHANGES
|
|
||||||
>
|
|
||||||
> In previous versions, `MemberProxy` (the class returned by `operator[]`) could lead to dangling pointers when used with a temporary string.
|
|
||||||
> To prevent this issue, `MemberProxy` and `ElementProxy` are now non-copyable.
|
|
||||||
>
|
|
||||||
> Your code is likely to be affected if you use `auto` to store the result of `operator[]`. For example, the following line won't compile anymore:
|
|
||||||
>
|
|
||||||
> ```cpp
|
|
||||||
> auto value = doc["key"];
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> To fix the issue, you must append either `.as<T>()` or `.to<T>()`, depending on the situation.
|
|
||||||
>
|
|
||||||
> For example, if you are extracting values from a JSON document, you should update like this:
|
|
||||||
>
|
|
||||||
> ```diff
|
|
||||||
> - auto config = doc["config"];
|
|
||||||
> + auto config = doc["config"].as<JsonObject>();
|
|
||||||
> const char* name = config["name"];
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> However, if you are building a JSON document, you should update like this:
|
|
||||||
>
|
|
||||||
> ```diff
|
|
||||||
> - auto config = doc["config"];
|
|
||||||
> + auto config = doc["config"].to<JsonObject>();
|
|
||||||
> config["name"] = "ArduinoJson";
|
|
||||||
> ```
|
|
||||||
|
|
||||||
v7.2.1 (2024-11-15)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Forbid `deserializeJson(JsonArray|JsonObject, ...)` (issue #2135)
|
|
||||||
* Fix VLA support in `JsonDocument::set()`
|
|
||||||
* Fix `operator[](variant)` ignoring NUL characters
|
|
||||||
|
|
||||||
v7.2.0 (2024-09-18)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Store object members with two slots: one for the key and one for the value
|
|
||||||
* Store 64-bit numbers (`double` and `long long`) in an additional slot
|
|
||||||
* Reduce the slot size (see table below)
|
|
||||||
* Improve message when user forgets third arg of `serializeJson()` et al.
|
|
||||||
* Set `ARDUINOJSON_USE_DOUBLE` to `0` by default on 8-bit architectures
|
|
||||||
* Deprecate `containsKey()` in favor of `doc["key"].is<T>()`
|
|
||||||
* Add support for escape sequence `\'` (issue #2124)
|
|
||||||
|
|
||||||
| Architecture | before | after |
|
|
||||||
|--------------|----------|----------|
|
|
||||||
| 8-bit | 8 bytes | 6 bytes |
|
|
||||||
| 32-bit | 16 bytes | 8 bytes |
|
|
||||||
| 64-bit | 24 bytes | 16 bytes |
|
|
||||||
|
|
||||||
> ### BREAKING CHANGES
|
|
||||||
>
|
|
||||||
> After being on the death row for years, the `containsKey()` method has finally been deprecated.
|
|
||||||
> You should replace `doc.containsKey("key")` with `doc["key"].is<T>()`, which not only checks that the key exists but also that the value is of the expected type.
|
|
||||||
>
|
|
||||||
> ```cpp
|
|
||||||
> // Before
|
|
||||||
> if (doc.containsKey("value")) {
|
|
||||||
> int value = doc["value"];
|
|
||||||
> // ...
|
|
||||||
> }
|
|
||||||
>
|
|
||||||
> // After
|
|
||||||
> if (doc["value"].is<int>()) {
|
|
||||||
> int value = doc["value"];
|
|
||||||
> // ...
|
|
||||||
> }
|
|
||||||
> ```
|
|
||||||
|
|
||||||
v7.1.0 (2024-06-27)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Add `ARDUINOJSON_STRING_LENGTH_SIZE` to the namespace name
|
|
||||||
* Add support for MsgPack binary (PR #2078 by @Sanae6)
|
|
||||||
* Add support for MsgPack extension
|
|
||||||
* Make string support even more generic (PR #2084 by @d-a-v)
|
|
||||||
* Optimize `deserializeMsgPack()`
|
|
||||||
* Allow using a `JsonVariant` as a key or index (issue #2080)
|
|
||||||
Note: works only for reading, not for writing
|
|
||||||
* Support `ElementProxy` and `MemberProxy` in `JsonDocument`'s constructor
|
|
||||||
* Don't add partial objects when allocation fails (issue #2081)
|
|
||||||
* Read MsgPack's 64-bit integers even if `ARDUINOJSON_USE_LONG_LONG` is `0`
|
|
||||||
(they are set to `null` if they don't fit in a `long`)
|
|
||||||
|
|
||||||
v7.0.4 (2024-03-12)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Make `JSON_STRING_SIZE(N)` return `N+1` to fix third-party code (issue #2054)
|
|
||||||
|
|
||||||
v7.0.3 (2024-02-05)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Improve error messages when using `char` or `char*` (issue #2043)
|
|
||||||
* Reduce stack consumption (issue #2046)
|
|
||||||
* Fix compatibility with GCC 4.8 (issue #2045)
|
|
||||||
|
|
||||||
v7.0.2 (2024-01-19)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix assertion `poolIndex < count_` after `JsonDocument::clear()` (issue #2034)
|
|
||||||
|
|
||||||
v7.0.1 (2024-01-10)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fix "no matching function" with `JsonObjectConst::operator[]` (issue #2019)
|
|
||||||
* Remove unused files in the PlatformIO package
|
|
||||||
* Fix `volatile bool` serialized as `1` or `0` instead of `true` or `false` (issue #2029)
|
|
||||||
|
|
||||||
v7.0.0 (2024-01-03)
|
|
||||||
------
|
|
||||||
|
|
||||||
* Remove `BasicJsonDocument`
|
|
||||||
* Remove `StaticJsonDocument`
|
|
||||||
* Add abstract `Allocator` class
|
|
||||||
* Merge `DynamicJsonDocument` with `JsonDocument`
|
|
||||||
* Remove `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()`
|
|
||||||
* Remove `ARDUINOJSON_ENABLE_STRING_DEDUPLICATION` (string deduplication cannot be disabled anymore)
|
|
||||||
* Remove `JsonDocument::capacity()`
|
|
||||||
* Store the strings in the heap
|
|
||||||
* Reference-count shared strings
|
|
||||||
* Always store `serialized("string")` by copy (#1915)
|
|
||||||
* Remove the zero-copy mode of `deserializeJson()` and `deserializeMsgPack()`
|
|
||||||
* Fix double lookup in `to<JsonVariant>()`
|
|
||||||
* Fix double call to `size()` in `serializeMsgPack()`
|
|
||||||
* Include `ARDUINOJSON_SLOT_OFFSET_SIZE` in the namespace name
|
|
||||||
* Remove `JsonVariant::shallowCopy()`
|
|
||||||
* `JsonDocument`'s capacity grows as needed, no need to pass it to the constructor anymore
|
|
||||||
* `JsonDocument`'s allocator is not monotonic anymore, removed values get recycled
|
|
||||||
* Show a link to the documentation when user passes an unsupported input type
|
|
||||||
* Remove `JsonDocument::memoryUsage()`
|
|
||||||
* Remove `JsonDocument::garbageCollect()`
|
|
||||||
* Add `deserializeJson(JsonVariant, ...)` and `deserializeMsgPack(JsonVariant, ...)` (#1226)
|
|
||||||
* Call `shrinkToFit()` in `deserializeJson()` and `deserializeMsgPack()`
|
|
||||||
* `serializeJson()` and `serializeMsgPack()` replace the content of `std::string` and `String` instead of appending to it
|
|
||||||
* Replace `add()` with `add<T>()` (`add(T)` is still supported)
|
|
||||||
* Remove `createNestedArray()` and `createNestedObject()` (use `to<JsonArray>()` and `to<JsonObject>()` instead)
|
|
||||||
|
|
||||||
> ### BREAKING CHANGES
|
|
||||||
>
|
|
||||||
> As every major release, ArduinoJson 7 introduces several breaking changes.
|
|
||||||
> I added some stubs so that most existing programs should compile, but I highty recommend you upgrade your code.
|
|
||||||
>
|
|
||||||
> #### `JsonDocument`
|
|
||||||
>
|
|
||||||
> In ArduinoJson 6, you could allocate the memory pool on the stack (with `StaticJsonDocument`) or in the heap (with `DynamicJsonDocument`).
|
|
||||||
> In ArduinoJson 7, the memory pool is always allocated in the heap, so `StaticJsonDocument` and `DynamicJsonDocument` have been merged into `JsonDocument`.
|
|
||||||
>
|
|
||||||
> In ArduinoJson 6, `JsonDocument` had a fixed capacity; in ArduinoJson 7, it has an elastic capacity that grows as needed.
|
|
||||||
> Therefore, you don't need to specify the capacity anymore, so the macros `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()` have been removed.
|
|
||||||
>
|
|
||||||
> ```c++
|
|
||||||
> // ArduinoJson 6
|
|
||||||
> StaticJsonDocument<256> doc;
|
|
||||||
> // or
|
|
||||||
> DynamicJsonDocument doc(256);
|
|
||||||
>
|
|
||||||
> // ArduinoJson 7
|
|
||||||
> JsonDocument doc;
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> In ArduinoJson 7, `JsonDocument` reuses released memory, so `garbageCollect()` has been removed.
|
|
||||||
> `shrinkToFit()` is still available and releases the over-allocated memory.
|
|
||||||
>
|
|
||||||
> Due to a change in the implementation, it's not possible to store a pointer to a variant from another `JsonDocument`, so `shallowCopy()` has been removed.
|
|
||||||
>
|
|
||||||
> In ArduinoJson 6, the meaning of `memoryUsage()` was clear: it returned the number of bytes used in the memory pool.
|
|
||||||
> In ArduinoJson 7, the meaning of `memoryUsage()` would be ambiguous, so it has been removed.
|
|
||||||
>
|
|
||||||
> #### Custom allocators
|
|
||||||
>
|
|
||||||
> In ArduinoJson 6, you could specify a custom allocator class as a template parameter of `BasicJsonDocument`.
|
|
||||||
> In ArduinoJson 7, you must inherit from `ArduinoJson::Allocator` and pass a pointer to an instance of your class to the constructor of `JsonDocument`.
|
|
||||||
>
|
|
||||||
> ```c++
|
|
||||||
> // ArduinoJson 6
|
|
||||||
> class MyAllocator {
|
|
||||||
> // ...
|
|
||||||
> };
|
|
||||||
> BasicJsonDocument<MyAllocator> doc(256);
|
|
||||||
>
|
|
||||||
> // ArduinoJson 7
|
|
||||||
> class MyAllocator : public ArduinoJson::Allocator {
|
|
||||||
> // ...
|
|
||||||
> };
|
|
||||||
> MyAllocator myAllocator;
|
|
||||||
> JsonDocument doc(&myAllocator);
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> #### `createNestedArray()` and `createNestedObject()`
|
|
||||||
>
|
|
||||||
> In ArduinoJson 6, you could create a nested array or object with `createNestedArray()` and `createNestedObject()`.
|
|
||||||
> In ArduinoJson 7, you must use `add<T>()` or `to<T>()` instead.
|
|
||||||
>
|
|
||||||
> For example, to create `[[],{}]`, you would write:
|
|
||||||
>
|
|
||||||
> ```c++
|
|
||||||
> // ArduinoJson 6
|
|
||||||
> arr.createNestedArray();
|
|
||||||
> arr.createNestedObject();
|
|
||||||
>
|
|
||||||
> // ArduinoJson 7
|
|
||||||
> arr.add<JsonArray>();
|
|
||||||
> arr.add<JsonObject>();
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> And to create `{"array":[],"object":{}}`, you would write:
|
|
||||||
>
|
|
||||||
> ```c++
|
|
||||||
> // ArduinoJson 6
|
|
||||||
> obj.createNestedArray("array");
|
|
||||||
> obj.createNestedObject("object");
|
|
||||||
>
|
|
||||||
> // ArduinoJson 7
|
|
||||||
> obj["array"].to<JsonArray>();
|
|
||||||
> obj["object"].to<JsonObject>();
|
|
||||||
> ```
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# ArduinoJson - https://arduinojson.org
|
|
||||||
# Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
# MIT License
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15)
|
|
||||||
|
|
||||||
if(ESP_PLATFORM)
|
|
||||||
# Build ArduinoJson as an ESP-IDF component
|
|
||||||
idf_component_register(INCLUDE_DIRS src)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
project(ArduinoJson VERSION 7.4.2)
|
|
||||||
|
|
||||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
|
||||||
include(CTest)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
|
||||||
|
|
||||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
|
||||||
include(extras/CompileOptions.cmake)
|
|
||||||
add_subdirectory(extras/tests)
|
|
||||||
add_subdirectory(extras/fuzzing)
|
|
||||||
endif()
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Contribution to ArduinoJson
|
|
||||||
|
|
||||||
First, thank you for taking the time to contribute to this project.
|
|
||||||
|
|
||||||
You can submit changes via GitHub Pull Requests.
|
|
||||||
|
|
||||||
Please:
|
|
||||||
|
|
||||||
1. Update the test suite for any change of behavior
|
|
||||||
2. Use clang-format in "file" mode to format the code
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<a href="https://arduinojson.org/"><img alt="ArduinoJson" src="https://arduinojson.org/images/logo.svg" width="200" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A7.x)
|
|
||||||
[](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
|
|
||||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
|
||||||
[](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
|
|
||||||
[](https://github.com/bblanchon/ArduinoJson/stargazers)
|
|
||||||
[](https://github.com/sponsors/bblanchon)
|
|
||||||
|
|
||||||
ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* [JSON deserialization](https://arduinojson.org/v7/api/json/deserializejson/)
|
|
||||||
* [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v7/api/config/decode_unicode/)
|
|
||||||
* [Optionally supports comments in the input](https://arduinojson.org/v7/api/config/enable_comments/)
|
|
||||||
* [Optionally filters the input to keep only desired values](https://arduinojson.org/v7/api/json/deserializejson/#filtering)
|
|
||||||
* Supports single quotes as a string delimiter
|
|
||||||
* Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/)
|
|
||||||
* [JSON serialization](https://arduinojson.org/v7/api/json/serializejson/)
|
|
||||||
* [Can write to a buffer or a stream](https://arduinojson.org/v7/api/json/serializejson/)
|
|
||||||
* [Optionally indents the document (prettified JSON)](https://arduinojson.org/v7/api/json/serializejsonpretty/)
|
|
||||||
* [MessagePack serialization](https://arduinojson.org/v7/api/msgpack/serializemsgpack/)
|
|
||||||
* [MessagePack deserialization](https://arduinojson.org/v7/api/msgpack/deserializemsgpack/)
|
|
||||||
* Efficient
|
|
||||||
* [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
|
||||||
* [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
|
||||||
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
|
||||||
* [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
|
|
||||||
* Versatile
|
|
||||||
* Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v7/how-to/use-external-ram-on-esp32/)
|
|
||||||
* Supports [`String`](https://arduinojson.org/v7/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v7/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v7/api/config/enable_string_view/)
|
|
||||||
* Supports [`Stream`](https://arduinojson.org/v7/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v7/api/config/enable_std_stream/)
|
|
||||||
* Supports [Flash strings](https://arduinojson.org/v7/api/config/enable_progmem/)
|
|
||||||
* Supports [custom readers](https://arduinojson.org/v7/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v7/api/json/serializejson/#custom-writer)
|
|
||||||
* Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/)
|
|
||||||
* Portable
|
|
||||||
* Usable on any C++ project (not limited to Arduino)
|
|
||||||
* Compatible with C++11, C++14 and C++17
|
|
||||||
* Support for C++98/C++03 available on [ArduinoJson 6.20.x](https://github.com/bblanchon/ArduinoJson/tree/6.20.x)
|
|
||||||
* Zero warnings with `-Wall -Wextra -pedantic` and `/W4`
|
|
||||||
* [Header-only library](https://en.wikipedia.org/wiki/Header-only)
|
|
||||||
* Works with virtually any board
|
|
||||||
* Arduino boards: [Uno](https://amzn.to/38aL2ik), [Due](https://amzn.to/36YkWi2), [Micro](https://amzn.to/35WkdwG), [Nano](https://amzn.to/2QTvwRX), [Mega](https://amzn.to/36XWhuf), [Yun](https://amzn.to/30odURc), [Leonardo](https://amzn.to/36XWjlR)...
|
|
||||||
* Espressif chips: [ESP8266](https://amzn.to/36YluV8), [ESP32](https://amzn.to/2G4pRCB)
|
|
||||||
* Lolin (WeMos) boards: [D1 mini](https://amzn.to/2QUpz7q), [D1 Mini Pro](https://amzn.to/36UsGSs)...
|
|
||||||
* Teensy boards: [4.0](https://amzn.to/30ljXGq), [3.2](https://amzn.to/2FT0EuC), [2.0](https://amzn.to/2QXUMXj)
|
|
||||||
* Particle boards: [Argon](https://amzn.to/2FQHa9X), [Boron](https://amzn.to/36WgLUd), [Electron](https://amzn.to/30vEc4k), [Photon](https://amzn.to/387F9Cd)...
|
|
||||||
* Texas Instruments boards: [MSP430](https://amzn.to/30nJWgg)...
|
|
||||||
* Soft cores: [Nios II](https://en.wikipedia.org/wiki/Nios_II)...
|
|
||||||
* Tested on all major development environments
|
|
||||||
* [Arduino IDE](https://www.arduino.cc/en/Main/Software)
|
|
||||||
* [Atmel Studio](http://www.atmel.com/microsite/atmel-studio/)
|
|
||||||
* [Atollic TrueSTUDIO](https://atollic.com/truestudio/)
|
|
||||||
* [Energia](http://energia.nu/)
|
|
||||||
* [IAR Embedded Workbench](https://www.iar.com/iar-embedded-workbench/)
|
|
||||||
* [Keil uVision](http://www.keil.com/)
|
|
||||||
* [MPLAB X IDE](http://www.microchip.com/mplab/mplab-x-ide)
|
|
||||||
* [Particle](https://www.particle.io/)
|
|
||||||
* [PlatformIO](http://platformio.org/)
|
|
||||||
* [Sloeber plugin for Eclipse](https://eclipse.baeyens.it/)
|
|
||||||
* [Visual Micro](http://www.visualmicro.com/)
|
|
||||||
* [Visual Studio](https://www.visualstudio.com/)
|
|
||||||
* [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN)
|
|
||||||
* [CMake friendly](https://arduinojson.org/v7/how-to/use-arduinojson-with-cmake/)
|
|
||||||
* Well designed
|
|
||||||
* [Elegant API](http://arduinojson.org/v7/example/)
|
|
||||||
* [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety)
|
|
||||||
* Self-contained (no external dependency)
|
|
||||||
* `const` friendly
|
|
||||||
* [`for` friendly](https://arduinojson.org/v7/api/jsonobject/begin_end/)
|
|
||||||
* [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming)
|
|
||||||
* Handles [integer overflows](https://arduinojson.org/v7/api/jsonvariant/as/#integer-overflows)
|
|
||||||
* Well tested
|
|
||||||
* [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
|
|
||||||
* Continuously tested on
|
|
||||||
* [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
|
|
||||||
* [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
|
||||||
* [Clang 7 to 19](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
|
||||||
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
|
||||||
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
|
|
||||||
* Well documented
|
|
||||||
* [Tutorials](https://arduinojson.org/v7/doc/deserialization/)
|
|
||||||
* [Examples](https://arduinojson.org/v7/example/)
|
|
||||||
* [How-tos](https://arduinojson.org/v7/example/)
|
|
||||||
* [FAQ](https://arduinojson.org/v7/faq/)
|
|
||||||
* [Troubleshooter](https://arduinojson.org/v7/troubleshooter/)
|
|
||||||
* [Book](https://arduinojson.org/book/)
|
|
||||||
* [Changelog](CHANGELOG.md)
|
|
||||||
* Vibrant user community
|
|
||||||
* Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories)
|
|
||||||
* [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson)
|
|
||||||
* [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed)
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
### Deserialization
|
|
||||||
|
|
||||||
Here is a program that parses a JSON document with ArduinoJson.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
const char* json = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
deserializeJson(doc, json);
|
|
||||||
|
|
||||||
const char* sensor = doc["sensor"];
|
|
||||||
long time = doc["time"];
|
|
||||||
double latitude = doc["data"][0];
|
|
||||||
double longitude = doc["data"][1];
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/deserialization/)
|
|
||||||
|
|
||||||
### Serialization
|
|
||||||
|
|
||||||
Here is a program that generates a JSON document with ArduinoJson:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
doc["sensor"] = "gps";
|
|
||||||
doc["time"] = 1351824120;
|
|
||||||
doc["data"][0] = 48.756080;
|
|
||||||
doc["data"][1] = 2.302038;
|
|
||||||
|
|
||||||
serializeJson(doc, Serial);
|
|
||||||
// This prints:
|
|
||||||
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/serialization/)
|
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
|
|
||||||
ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it!
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/1technophile" rel="sponsored">
|
|
||||||
<img alt="1technophile" src="https://avatars.githubusercontent.com/u/12672732?s=40&v=4">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/LArkema" rel="sponsored">
|
|
||||||
<img alt="LArkema" src="https://avatars.githubusercontent.com/u/38381313?s=40&v=4">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
If you run a commercial project that embeds ArduinoJson, think about [sponsoring the library's development](https://github.com/sponsors/bblanchon): it ensures the code that your products rely on stays actively maintained. It can also give your project some exposure to the makers' community.
|
|
||||||
|
|
||||||
If you are an individual user and want to support the development (or give a sign of appreciation), consider purchasing the book [Mastering ArduinoJson](https://arduinojson.org/book/) ❤, or simply [cast a star](https://github.com/bblanchon/ArduinoJson/stargazers) ⭐.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# ArduinoJson Support
|
|
||||||
|
|
||||||
First off, thank you very much for using ArduinoJson.
|
|
||||||
|
|
||||||
We'll be very happy to help you, but first please read the following.
|
|
||||||
|
|
||||||
## Before asking for help
|
|
||||||
|
|
||||||
1. Read the [FAQ](https://arduinojson.org/faq/?utm_source=github&utm_medium=support)
|
|
||||||
2. Search in the [API Reference](https://arduinojson.org/api/?utm_source=github&utm_medium=support)
|
|
||||||
|
|
||||||
If you did not find the answer, please create a [new issue on GitHub](https://github.com/bblanchon/ArduinoJson/issues/new).
|
|
||||||
|
|
||||||
It is OK to add a comment to a currently opened issue, but please avoid adding comments to a closed issue.
|
|
||||||
|
|
||||||
## Before hitting the Submit button
|
|
||||||
|
|
||||||
Please provide all the relevant information:
|
|
||||||
|
|
||||||
* Good title
|
|
||||||
* Short description of the problem
|
|
||||||
* Target platform
|
|
||||||
* Compiler model and version
|
|
||||||
* [MVCE](https://stackoverflow.com/help/mcve)
|
|
||||||
* Compiler output
|
|
||||||
|
|
||||||
Good questions get fast answers!
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
version: 7.4.2.{build}
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
|
||||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
|
||||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
CMAKE_GENERATOR: Visual Studio 15 2017
|
|
||||||
- CMAKE_GENERATOR: Ninja
|
|
||||||
MINGW32: i686-6.3.0-posix-dwarf-rt_v5-rev1 # MinGW-w64 6.3.0 i686
|
|
||||||
- CMAKE_GENERATOR: Ninja
|
|
||||||
MINGW64: x86_64-6.3.0-posix-seh-rt_v5-rev1 # MinGW-w64 6.3.0 x86_64
|
|
||||||
- CMAKE_GENERATOR: Ninja
|
|
||||||
MINGW64: x86_64-7.3.0-posix-seh-rt_v5-rev0 # MinGW-w64 7.3.0 x86_64
|
|
||||||
- CMAKE_GENERATOR: Ninja
|
|
||||||
MINGW64: x86_64-8.1.0-posix-seh-rt_v6-rev0 # MinGW-w64 8.1.0 x86_64
|
|
||||||
configuration: Debug
|
|
||||||
before_build:
|
|
||||||
- set PATH=%PATH:C:\Program Files\Git\usr\bin;=% # Workaround for CMake not wanting sh.exe on PATH for MinGW
|
|
||||||
- if defined MINGW set PATH=C:\%MINGW%\bin;%PATH%
|
|
||||||
- if defined MINGW32 set PATH=C:\mingw-w64\%MINGW32%\mingw32\bin;%PATH%
|
|
||||||
- if defined MINGW64 set PATH=C:\mingw-w64\%MINGW64%\mingw64\bin;%PATH%
|
|
||||||
- cmake -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" .
|
|
||||||
build_script:
|
|
||||||
- cmake --build . --config %CONFIGURATION%
|
|
||||||
test_script:
|
|
||||||
- ctest -C %CONFIGURATION% --output-on-failure .
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
COMPONENT_ADD_INCLUDEDIRS := src
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to store your project configuration in a file.
|
|
||||||
// It uses the SD library but can be easily modified for any other file-system.
|
|
||||||
//
|
|
||||||
// The file contains a JSON document with the following content:
|
|
||||||
// {
|
|
||||||
// "hostname": "examples.com",
|
|
||||||
// "port": 2731
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// To run this program, you need an SD card connected to the SPI bus as follows:
|
|
||||||
// * MOSI <-> pin 11
|
|
||||||
// * MISO <-> pin 12
|
|
||||||
// * CLK <-> pin 13
|
|
||||||
// * CS <-> pin 4
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/config/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <SD.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
|
|
||||||
// Our configuration structure.
|
|
||||||
struct Config {
|
|
||||||
char hostname[64];
|
|
||||||
int port;
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* filename = "/config.txt"; // <- SD library uses 8.3 filenames
|
|
||||||
Config config; // <- global configuration object
|
|
||||||
|
|
||||||
// Loads the configuration from a file
|
|
||||||
void loadConfiguration(const char* filename, Config& config) {
|
|
||||||
// Open file for reading
|
|
||||||
File file = SD.open(filename);
|
|
||||||
|
|
||||||
// Allocate a temporary JsonDocument
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Deserialize the JSON document
|
|
||||||
DeserializationError error = deserializeJson(doc, file);
|
|
||||||
if (error)
|
|
||||||
Serial.println(F("Failed to read file, using default configuration"));
|
|
||||||
|
|
||||||
// Copy values from the JsonDocument to the Config
|
|
||||||
config.port = doc["port"] | 2731;
|
|
||||||
strlcpy(config.hostname, // <- destination
|
|
||||||
doc["hostname"] | "example.com", // <- source
|
|
||||||
sizeof(config.hostname)); // <- destination's capacity
|
|
||||||
|
|
||||||
// Close the file (Curiously, File's destructor doesn't close the file)
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saves the configuration to a file
|
|
||||||
void saveConfiguration(const char* filename, const Config& config) {
|
|
||||||
// Delete existing file, otherwise the configuration is appended to the file
|
|
||||||
SD.remove(filename);
|
|
||||||
|
|
||||||
// Open file for writing
|
|
||||||
File file = SD.open(filename, FILE_WRITE);
|
|
||||||
if (!file) {
|
|
||||||
Serial.println(F("Failed to create file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a temporary JsonDocument
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Set the values in the document
|
|
||||||
doc["hostname"] = config.hostname;
|
|
||||||
doc["port"] = config.port;
|
|
||||||
|
|
||||||
// Serialize JSON to file
|
|
||||||
if (serializeJson(doc, file) == 0) {
|
|
||||||
Serial.println(F("Failed to write to file"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the file
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints the content of a file to the Serial
|
|
||||||
void printFile(const char* filename) {
|
|
||||||
// Open file for reading
|
|
||||||
File file = SD.open(filename);
|
|
||||||
if (!file) {
|
|
||||||
Serial.println(F("Failed to read file"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract each characters by one by one
|
|
||||||
while (file.available()) {
|
|
||||||
Serial.print((char)file.read());
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
// Close the file
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize SD library
|
|
||||||
const int chipSelect = 4;
|
|
||||||
while (!SD.begin(chipSelect)) {
|
|
||||||
Serial.println(F("Failed to initialize SD library"));
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should load default config if run for the first time
|
|
||||||
Serial.println(F("Loading configuration..."));
|
|
||||||
loadConfiguration(filename, config);
|
|
||||||
|
|
||||||
// Create configuration file
|
|
||||||
Serial.println(F("Saving configuration..."));
|
|
||||||
saveConfiguration(filename, config);
|
|
||||||
|
|
||||||
// Dump config file
|
|
||||||
Serial.println(F("Print config file..."));
|
|
||||||
printFile(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// not used in this example
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance issue?
|
|
||||||
// ------------------
|
|
||||||
//
|
|
||||||
// File is an unbuffered stream, which is not optimal for ArduinoJson.
|
|
||||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// serialization or deserialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a case study of a project that has
|
|
||||||
// a complex configuration with nested members.
|
|
||||||
// Contrary to this example, the project in the book uses the SPIFFS filesystem.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to use DeserializationOption::Filter
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/filter/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// The huge input: an extract from OpenWeatherMap response
|
|
||||||
auto input_json = F(
|
|
||||||
"{\"cod\":\"200\",\"message\":0,\"list\":[{\"dt\":1581498000,\"main\":{"
|
|
||||||
"\"temp\":3.23,\"feels_like\":-3.63,\"temp_min\":3.23,\"temp_max\":4.62,"
|
|
||||||
"\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":"
|
|
||||||
"58,\"temp_kf\":-1.39},\"weather\":[{\"id\":800,\"main\":\"Clear\","
|
|
||||||
"\"description\":\"clear "
|
|
||||||
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":6."
|
|
||||||
"19,\"deg\":266},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
|
|
||||||
"09:00:00\"},{\"dt\":1581508800,\"main\":{\"temp\":6.09,\"feels_like\":-"
|
|
||||||
"1.07,\"temp_min\":6.09,\"temp_max\":7.13,\"pressure\":1015,\"sea_"
|
|
||||||
"level\":1015,\"grnd_level\":1011,\"humidity\":48,\"temp_kf\":-1.04},"
|
|
||||||
"\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear "
|
|
||||||
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":9},\"wind\":{\"speed\":6."
|
|
||||||
"64,\"deg\":268},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
|
|
||||||
"12:00:00\"}],\"city\":{\"id\":2643743,\"name\":\"London\",\"coord\":{"
|
|
||||||
"\"lat\":51.5085,\"lon\":-0.1257},\"country\":\"GB\",\"population\":"
|
|
||||||
"1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}");
|
|
||||||
|
|
||||||
// The filter: it contains "true" for each value we want to keep
|
|
||||||
JsonDocument filter;
|
|
||||||
filter["list"][0]["dt"] = true;
|
|
||||||
filter["list"][0]["main"]["temp"] = true;
|
|
||||||
|
|
||||||
// Deserialize the document
|
|
||||||
JsonDocument doc;
|
|
||||||
deserializeJson(doc, input_json, DeserializationOption::Filter(filter));
|
|
||||||
|
|
||||||
// Print the result
|
|
||||||
serializeJsonPretty(doc, Serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// not used in this example
|
|
||||||
}
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// deserialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization.
|
|
||||||
// It begins with a simple example, like the one above, and then adds more
|
|
||||||
// features like deserializing directly from a file or an HTTP request.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to generate a JSON document with ArduinoJson.
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/generator/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize Serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Allocate the JSON document
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Add values in the document
|
|
||||||
doc["sensor"] = "gps";
|
|
||||||
doc["time"] = 1351824120;
|
|
||||||
|
|
||||||
// Add an array
|
|
||||||
JsonArray data = doc["data"].to<JsonArray>();
|
|
||||||
data.add(48.756080);
|
|
||||||
data.add(2.302038);
|
|
||||||
|
|
||||||
// Generate the minified JSON and send it to the Serial port
|
|
||||||
serializeJson(doc, Serial);
|
|
||||||
// The above line prints:
|
|
||||||
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
|
||||||
|
|
||||||
// Start a new line
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
// Generate the prettified JSON and send it to the Serial port
|
|
||||||
serializeJsonPretty(doc, Serial);
|
|
||||||
// The above line prints:
|
|
||||||
// {
|
|
||||||
// "sensor": "gps",
|
|
||||||
// "time": 1351824120,
|
|
||||||
// "data": [
|
|
||||||
// 48.756080,
|
|
||||||
// 2.302038
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// not used in this example
|
|
||||||
}
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// serialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
|
||||||
// It begins with a simple example, like the one above, and then adds more
|
|
||||||
// features like serializing directly to a file or an HTTP request.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to parse a JSON document in an HTTP response.
|
|
||||||
// It uses the Ethernet library, but can be easily adapted for Wifi.
|
|
||||||
//
|
|
||||||
// It performs a GET resquest on https://arduinojson.org/example.json
|
|
||||||
// Here is the expected response:
|
|
||||||
// {
|
|
||||||
// "sensor": "gps",
|
|
||||||
// "time": 1351824120,
|
|
||||||
// "data": [
|
|
||||||
// 48.756080,
|
|
||||||
// 2.302038
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/http-client/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <Ethernet.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize Serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize Ethernet library
|
|
||||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
|
||||||
if (!Ethernet.begin(mac)) {
|
|
||||||
Serial.println(F("Failed to configure Ethernet"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
Serial.println(F("Connecting..."));
|
|
||||||
|
|
||||||
// Connect to HTTP server
|
|
||||||
EthernetClient client;
|
|
||||||
client.setTimeout(10000);
|
|
||||||
if (!client.connect("arduinojson.org", 80)) {
|
|
||||||
Serial.println(F("Connection failed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println(F("Connected!"));
|
|
||||||
|
|
||||||
// Send HTTP request
|
|
||||||
client.println(F("GET /example.json HTTP/1.0"));
|
|
||||||
client.println(F("Host: arduinojson.org"));
|
|
||||||
client.println(F("Connection: close"));
|
|
||||||
if (client.println() == 0) {
|
|
||||||
Serial.println(F("Failed to send request"));
|
|
||||||
client.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check HTTP status
|
|
||||||
char status[32] = {0};
|
|
||||||
client.readBytesUntil('\r', status, sizeof(status));
|
|
||||||
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
|
|
||||||
if (strcmp(status + 9, "200 OK") != 0) {
|
|
||||||
Serial.print(F("Unexpected response: "));
|
|
||||||
Serial.println(status);
|
|
||||||
client.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip HTTP headers
|
|
||||||
char endOfHeaders[] = "\r\n\r\n";
|
|
||||||
if (!client.find(endOfHeaders)) {
|
|
||||||
Serial.println(F("Invalid response"));
|
|
||||||
client.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate the JSON document
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Parse JSON object
|
|
||||||
DeserializationError error = deserializeJson(doc, client);
|
|
||||||
if (error) {
|
|
||||||
Serial.print(F("deserializeJson() failed: "));
|
|
||||||
Serial.println(error.f_str());
|
|
||||||
client.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract values
|
|
||||||
Serial.println(F("Response:"));
|
|
||||||
Serial.println(doc["sensor"].as<const char*>());
|
|
||||||
Serial.println(doc["time"].as<long>());
|
|
||||||
Serial.println(doc["data"][0].as<float>(), 6);
|
|
||||||
Serial.println(doc["data"][1].as<float>(), 6);
|
|
||||||
|
|
||||||
// Disconnect
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// not used in this example
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance issue?
|
|
||||||
// ------------------
|
|
||||||
//
|
|
||||||
// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
|
|
||||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// serialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization
|
|
||||||
// showing how to parse the response from GitHub's API. In the last chapter,
|
|
||||||
// it shows how to parse the huge documents from OpenWeatherMap
|
|
||||||
// and Reddit.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to deserialize a JSON document with ArduinoJson.
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/parser/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Allocate the JSON document
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// JSON input string.
|
|
||||||
const char* json =
|
|
||||||
"{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
|
||||||
|
|
||||||
// Deserialize the JSON document
|
|
||||||
DeserializationError error = deserializeJson(doc, json);
|
|
||||||
|
|
||||||
// Test if parsing succeeds
|
|
||||||
if (error) {
|
|
||||||
Serial.print(F("deserializeJson() failed: "));
|
|
||||||
Serial.println(error.f_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the values
|
|
||||||
//
|
|
||||||
// Most of the time, you can rely on the implicit casts.
|
|
||||||
// In other case, you can do doc["time"].as<long>();
|
|
||||||
const char* sensor = doc["sensor"];
|
|
||||||
long time = doc["time"];
|
|
||||||
double latitude = doc["data"][0];
|
|
||||||
double longitude = doc["data"][1];
|
|
||||||
|
|
||||||
// Print the values
|
|
||||||
Serial.println(sensor);
|
|
||||||
Serial.println(time);
|
|
||||||
Serial.println(latitude, 6);
|
|
||||||
Serial.println(longitude, 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// not used in this example
|
|
||||||
}
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// deserialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization.
|
|
||||||
// It begins with a simple example, like the one above, and then adds more
|
|
||||||
// features like deserializing directly from a file or an HTTP request.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to implement an HTTP server that sends a JSON document
|
|
||||||
// in the response.
|
|
||||||
// It uses the Ethernet library but can be easily adapted for Wifi.
|
|
||||||
//
|
|
||||||
// The JSON document contains the values of the analog and digital pins.
|
|
||||||
// It looks like that:
|
|
||||||
// {
|
|
||||||
// "analog": [0, 76, 123, 158, 192, 205],
|
|
||||||
// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/http-server/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <Ethernet.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
|
|
||||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
|
||||||
EthernetServer server(80);
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize Ethernet libary
|
|
||||||
if (!Ethernet.begin(mac)) {
|
|
||||||
Serial.println(F("Failed to initialize Ethernet library"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start to listen
|
|
||||||
server.begin();
|
|
||||||
|
|
||||||
Serial.println(F("Server is ready."));
|
|
||||||
Serial.print(F("Please connect to http://"));
|
|
||||||
Serial.println(Ethernet.localIP());
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Wait for an incomming connection
|
|
||||||
EthernetClient client = server.available();
|
|
||||||
|
|
||||||
// Do we have a client?
|
|
||||||
if (!client)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Serial.println(F("New client"));
|
|
||||||
|
|
||||||
// Read the request (we ignore the content in this example)
|
|
||||||
while (client.available())
|
|
||||||
client.read();
|
|
||||||
|
|
||||||
// Allocate a temporary JsonDocument
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Create the "analog" array
|
|
||||||
JsonArray analogValues = doc["analog"].to<JsonArray>();
|
|
||||||
for (int pin = 0; pin < 6; pin++) {
|
|
||||||
// Read the analog input
|
|
||||||
int value = analogRead(pin);
|
|
||||||
|
|
||||||
// Add the value at the end of the array
|
|
||||||
analogValues.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the "digital" array
|
|
||||||
JsonArray digitalValues = doc["digital"].to<JsonArray>();
|
|
||||||
for (int pin = 0; pin < 14; pin++) {
|
|
||||||
// Read the digital input
|
|
||||||
int value = digitalRead(pin);
|
|
||||||
|
|
||||||
// Add the value at the end of the array
|
|
||||||
digitalValues.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print(F("Sending: "));
|
|
||||||
serializeJson(doc, Serial);
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
// Write response headers
|
|
||||||
client.println(F("HTTP/1.0 200 OK"));
|
|
||||||
client.println(F("Content-Type: application/json"));
|
|
||||||
client.println(F("Connection: close"));
|
|
||||||
client.print(F("Content-Length: "));
|
|
||||||
client.println(measureJsonPretty(doc));
|
|
||||||
client.println();
|
|
||||||
|
|
||||||
// Write JSON document
|
|
||||||
serializeJsonPretty(doc, client);
|
|
||||||
|
|
||||||
// Disconnect
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance issue?
|
|
||||||
// ------------------
|
|
||||||
//
|
|
||||||
// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
|
|
||||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// serialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
|
||||||
// It begins with a simple example, then adds more features like serializing
|
|
||||||
// directly to a file or an HTTP client.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
// ArduinoJson - https://arduinojson.org
|
|
||||||
// Copyright © 2014-2025, Benoit BLANCHON
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// This example shows how to send a JSON document to a UDP socket.
|
|
||||||
// At regular interval, it sends a UDP packet that contains the status of
|
|
||||||
// analog and digital pins.
|
|
||||||
// It looks like that:
|
|
||||||
// {
|
|
||||||
// "analog": [0, 76, 123, 158, 192, 205],
|
|
||||||
// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// If you want to test this program, you need to be able to receive the UDP
|
|
||||||
// packets.
|
|
||||||
// For example, you can run netcat on your computer
|
|
||||||
// $ ncat -ulp 8888
|
|
||||||
// See https://nmap.org/ncat/
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/v7/example/udp-beacon/
|
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <Ethernet.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
|
|
||||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
|
||||||
IPAddress remoteIp(192, 168, 0, 108); // <- EDIT!!!!
|
|
||||||
unsigned short remotePort = 8888;
|
|
||||||
unsigned short localPort = 8888;
|
|
||||||
EthernetUDP udp;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Initialize serial port
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize Ethernet libary
|
|
||||||
if (!Ethernet.begin(mac)) {
|
|
||||||
Serial.println(F("Failed to initialize Ethernet library"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable UDP
|
|
||||||
udp.begin(localPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Allocate a temporary JsonDocument
|
|
||||||
JsonDocument doc;
|
|
||||||
|
|
||||||
// Create the "analog" array
|
|
||||||
JsonArray analogValues = doc["analog"].to<JsonArray>();
|
|
||||||
for (int pin = 0; pin < 6; pin++) {
|
|
||||||
// Read the analog input
|
|
||||||
int value = analogRead(pin);
|
|
||||||
|
|
||||||
// Add the value at the end of the array
|
|
||||||
analogValues.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the "digital" array
|
|
||||||
JsonArray digitalValues = doc["digital"].to<JsonArray>();
|
|
||||||
for (int pin = 0; pin < 14; pin++) {
|
|
||||||
// Read the digital input
|
|
||||||
int value = digitalRead(pin);
|
|
||||||
|
|
||||||
// Add the value at the end of the array
|
|
||||||
digitalValues.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log
|
|
||||||
Serial.print(F("Sending to "));
|
|
||||||
Serial.print(remoteIp);
|
|
||||||
Serial.print(F(" on port "));
|
|
||||||
Serial.println(remotePort);
|
|
||||||
serializeJson(doc, Serial);
|
|
||||||
|
|
||||||
// Send UDP packet
|
|
||||||
udp.beginPacket(remoteIp, remotePort);
|
|
||||||
serializeJson(doc, udp);
|
|
||||||
udp.println();
|
|
||||||
udp.endPacket();
|
|
||||||
|
|
||||||
// Wait
|
|
||||||
delay(10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance issue?
|
|
||||||
// ------------------
|
|
||||||
//
|
|
||||||
// EthernetUDP is an unbuffered stream, which is not optimal for ArduinoJson.
|
|
||||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
|
||||||
|
|
||||||
// See also
|
|
||||||
// --------
|
|
||||||
//
|
|
||||||
// https://arduinojson.org/ contains the documentation for all the functions
|
|
||||||
// used above. It also includes an FAQ that will help you solve any
|
|
||||||
// serialization problem.
|
|
||||||
//
|
|
||||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
|
||||||
// It begins with a simple example, then adds more features like serializing
|
|
||||||
// directly to a file or any stream.
|
|
||||||
// Learn more at https://arduinojson.org/book/
|
|
||||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user