mirror of
https://github.com/wled/WLED.git
synced 2026-04-22 23:22:44 +00:00
Compare commits
3 Commits
16_x
...
copilot/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37e4e48f2b | ||
|
|
7166473d3b | ||
|
|
bc68a5e2db |
113
.coderabbit.yaml
113
.coderabbit.yaml
@@ -1,113 +0,0 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
#
|
||||
# CodeRabbit configuration — references existing guideline files to avoid
|
||||
# duplicating conventions. See:
|
||||
# .github/copilot-instructions.md — project overview & general rules
|
||||
# docs/cpp.instructions.md — C++ coding conventions
|
||||
# docs/web.instructions.md — Web UI coding conventions
|
||||
# docs/cicd.instructions.md — GitHub Actions / CI-CD conventions
|
||||
#
|
||||
# NOTE: This file must be committed (tracked by git) for CodeRabbit to read
|
||||
# it from the repository. If it is listed in .gitignore, CodeRabbit will
|
||||
# not see it and these settings will have no effect.
|
||||
|
||||
language: en-US
|
||||
|
||||
reviews:
|
||||
# generic review setting, see https://docs.coderabbit.ai/reference/configuration#reference
|
||||
auto_apply_labels: true
|
||||
# abort_on_close: false
|
||||
high_level_summary: true
|
||||
review_status: true
|
||||
collapse_walkthrough: false
|
||||
poem: false
|
||||
# sequence_diagrams: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
ignore_title_keywords:
|
||||
- WIP
|
||||
|
||||
path_instructions:
|
||||
- path: "**/*.{cpp,h,hpp,ino}"
|
||||
instructions: >
|
||||
Follow the C++ coding conventions documented in docs/cpp.instructions.md
|
||||
and the general project guidelines in .github/copilot-instructions.md.
|
||||
|
||||
Key rules: 2-space indentation (no tabs), camelCase functions/variables,
|
||||
PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
|
||||
Hot-path optimization guidelines (attributes, uint_fast types, caching,
|
||||
unsigned range checks) apply from pixel set/get operations and strip.show() downward —
|
||||
NOT to effect functions in FX.cpp, which have diverse contributor styles.
|
||||
# disabled - the below instruction has no effect
|
||||
# When initially reviewing a PR, summarize good practices (top 5) and create a prioritized list of suggested improvements (focus on major ones).
|
||||
|
||||
- path: "wled00/data/**"
|
||||
instructions: >
|
||||
Follow the web UI conventions documented in docs/web.instructions.md.
|
||||
|
||||
Key rules: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
Files here are built into wled00/html_*.h and wled00/js_*.h by tools/cdata.js — never
|
||||
edit those generated headers directly.
|
||||
# disabled - the below instruction has no effect
|
||||
# When initially reviewing a PR, summarize good practices (top 5) and create a prioritized list of suggested improvements (focus on major ones).
|
||||
|
||||
- path: "wled00/html_*.h"
|
||||
instructions: >
|
||||
These files are auto-generated from wled00/data/ by tools/cdata.js.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "wled00/js_*.h"
|
||||
instructions: >
|
||||
These files are auto-generated from wled00/data/ by tools/cdata.js.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "usermods/**"
|
||||
instructions: >
|
||||
Usermods are community add-ons.
|
||||
Each usermod lives in its own directory under usermods/ and is implemented
|
||||
as a .cpp file with a dedicated library.json file to manage dependencies.
|
||||
Follow the same C++ conventions as the core firmware (docs/cpp.instructions.md).
|
||||
# disabled - the below instruction has no effect
|
||||
# When initially reviewing a PR, summarize good practices (top 6) and create a prioritized list of suggested improvements (skip minor ones).
|
||||
|
||||
- path: ".github/workflows/*.{yml,yaml}"
|
||||
instructions: >
|
||||
Follow the CI/CD conventions documented in docs/cicd.instructions.md.
|
||||
|
||||
Key rules: 2-space indentation, descriptive name: on every workflow/job/step.
|
||||
Third-party actions must be pinned to a specific version tag — branch pins
|
||||
such as @main or @master are not allowed. Declare explicit permissions: blocks
|
||||
scoped to least privilege. Never interpolate github.event.* values directly
|
||||
into run: steps — pass them through an env: variable to prevent script
|
||||
injection. Do not use pull_request_target unless fully justified.
|
||||
# disabled - the below instruction has no effect
|
||||
# When initially reviewing a PR, summarize good practices (top 6) and create a prioritized list of suggested improvements.
|
||||
|
||||
- path: "**/*.instructions.md"
|
||||
instructions: |
|
||||
This file contains both AI-facing rules and human-only reference sections.
|
||||
Human-only sections are enclosed in `<!-- HUMAN_ONLY_START -->` /
|
||||
`<!-- HUMAN_ONLY_END -->` HTML comment markers and should not be used as
|
||||
actionable review criteria.
|
||||
|
||||
When this file is modified in a PR, perform the following alignment check:
|
||||
1. For each `<!-- HUMAN_ONLY_START --> ... <!-- HUMAN_ONLY_END -->` block,
|
||||
verify that its examples and guidance are consistent with (and do not
|
||||
contradict) the AI-facing rules stated in the same file.
|
||||
2. Flag any HUMAN_ONLY section whose content has drifted from the surrounding
|
||||
AI-facing rules due to edits introduced in this PR.
|
||||
3. If new AI-facing rules were added without updating a related HUMAN_ONLY
|
||||
reference section, note this as a suggestion (not a required fix).
|
||||
|
||||
finishing_touches:
|
||||
# Docstrings | Options for generating Docstrings for your PRs/MRs.
|
||||
docstrings:
|
||||
# Docstrings | Allow CodeRabbit to generate docstrings for PRs/MRs.
|
||||
# default: true - disabled in WLED: has caused confusion in the past
|
||||
enabled: false
|
||||
unit_tests:
|
||||
# default: true - disabled in WLED: we don't have a unit test framework, this option just confuses contributors
|
||||
enabled: false
|
||||
120
.github/agent-build.instructions.md
vendored
120
.github/agent-build.instructions.md
vendored
@@ -1,120 +0,0 @@
|
||||
---
|
||||
applyTo: "**"
|
||||
---
|
||||
# Agent-Mode Build & Test Instructions
|
||||
|
||||
Detailed build workflow, timeouts, and troubleshooting for making code changes in agent mode. Always reference these instructions first when running builds or validating changes.
|
||||
|
||||
## Build Timing and Timeouts
|
||||
|
||||
Use these timeout values when running builds:
|
||||
|
||||
| Command | Typical Time | Minimum Timeout | Notes |
|
||||
|---|---|---|---|
|
||||
| `npm run build` | ~3 s | 30 s | Web UI → `wled00/html_*.h` `wled00/js_*.h` headers |
|
||||
| `npm test` | ~40 s | 2 min | Validates build system |
|
||||
| `npm run dev` | continuous | — | Watch mode, auto-rebuilds on changes |
|
||||
| `pio run -e <env>` | 15–20 min | 30 min | First build downloads toolchains; subsequent builds are faster |
|
||||
|
||||
**NEVER cancel long-running builds.** PlatformIO downloads and compilation require patience.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Code Style Summary
|
||||
- **C++** files in `wled00/` and `usermods/`: 2-space indentation (no tabs), camelCase functions/variables, PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
- **Web UI** files in `wled00/data`: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
- **CI/CD workflows** in `.github/workflows`: 2-space indentation, descriptive `name:` on every workflow/job/step. Third-party actions must be pinned to a specific version tag — branch pins such as `@main` or `@master` are not allowed. SHA pinning recommended.
|
||||
|
||||
### Web UI Changes
|
||||
|
||||
1. Edit files in `wled00/data/`
|
||||
2. Run `npm run build` to regenerate `wled00/html_*.h` `wled00/js_*.h` headers
|
||||
3. Test with local HTTP server (see Manual Testing below)
|
||||
4. Run `npm test` to validate
|
||||
|
||||
### Firmware Changes
|
||||
|
||||
1. Edit files in `wled00/` (but **never** `html_*.h` and `js_*.h` files)
|
||||
2. Ensure web UI is built first: `npm run build`
|
||||
3. Build firmware: `pio run -e esp32dev` (set timeout ≥ 30 min)
|
||||
4. Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
### Combined Web + Firmware Changes
|
||||
|
||||
1. Always build web UI first
|
||||
2. Test web interface manually
|
||||
3. Then build and test firmware
|
||||
|
||||
|
||||
## Before Finishing Work - Testing
|
||||
|
||||
**You MUST complete ALL of these before marking work as done:**
|
||||
|
||||
1. **Run tests**: `npm test` — must pass
|
||||
2. **Build firmware**: `pio run -e esp32dev` — must succeed after source code changes, **never skip this step**.
|
||||
- Set timeout to 30+ minutes, **never cancel**
|
||||
- Choose `esp32dev` as a common, representative environment
|
||||
- If the build fails, fix the issue before proceeding
|
||||
3. **For web UI changes**: manually test the interface (see below)
|
||||
|
||||
If any step fails, fix the issue. **Do NOT mark work complete with failing builds or tests.**
|
||||
|
||||
## Manual Web UI Testing
|
||||
|
||||
Start a local server:
|
||||
|
||||
```sh
|
||||
cd wled00/data && python3 -m http.server 8080
|
||||
# Open http://localhost:8080/index.htm
|
||||
```
|
||||
|
||||
Test these scenarios after every web UI change:
|
||||
|
||||
- **Load**: `index.htm` loads without JavaScript errors (check browser console)
|
||||
- **Navigation**: switching between main page and settings pages works
|
||||
- **Color controls**: color picker and brightness controls function correctly
|
||||
- **Effects**: effect selection and parameter changes work
|
||||
- **Settings**: form submission and validation work
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Build Issues
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| Missing `html_*.h` | Run `npm ci; npm run build` |
|
||||
| Web UI looks broken | Check browser console for JS errors |
|
||||
| PlatformIO network errors | Retry — downloads can be flaky |
|
||||
| Node.js version mismatch | Ensure Node.js 20+ (check `.nvmrc`) |
|
||||
|
||||
### Recovery Steps
|
||||
|
||||
- **Force web UI rebuild**: `npm run build -- -f`
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h wled00/js_*.h` then `npm run build`
|
||||
- **Clean PlatformIO build artifacts**: `pio run --target clean`
|
||||
- **Reinstall Node deps**: `rm -rf node_modules && npm ci`
|
||||
|
||||
## CI/CD Validation
|
||||
|
||||
The GitHub Actions CI workflow will:
|
||||
1. Install Node.js and Python dependencies
|
||||
2. Run `npm test`
|
||||
3. Build web UI (automatic via PlatformIO)
|
||||
4. Compile firmware for **all** `default_envs` targets
|
||||
|
||||
**To ensure CI success, always validate locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common firmware environment, see next section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
Match this workflow in local development to catch failures before pushing.
|
||||
|
||||
## Important Reminders
|
||||
|
||||
- Always **commit source code**
|
||||
- Every pull request MUST include a clear description of *what* changed and *why*.
|
||||
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`
|
||||
- After modifying source code files, check that any **previous comments have been preserved** or updated to reflect the new behaviour.
|
||||
- Web UI rebuild is part of the PlatformIO firmware compilation pipeline
|
||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`
|
||||
- List all PlatformIO targets: `pio run --list-targets`
|
||||
247
.github/copilot-instructions.md
vendored
247
.github/copilot-instructions.md
vendored
@@ -4,136 +4,173 @@ WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver
|
||||
|
||||
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
## Working Effectively
|
||||
|
||||
## Setup
|
||||
### Initial Setup
|
||||
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||
- Install dependencies: `npm ci` (takes ~5 seconds)
|
||||
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||
|
||||
- Node.js 20+ (see `.nvmrc`)
|
||||
- Install dependencies: `npm ci`
|
||||
- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`
|
||||
### Build and Test Workflow
|
||||
- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
|
||||
- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
|
||||
- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
|
||||
- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
|
||||
|
||||
## Build and Test
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Build Process Details
|
||||
The build has two main phases:
|
||||
1. **Web UI Generation** (`npm run build`):
|
||||
- Processes files in `wled00/data/` (HTML, CSS, JS)
|
||||
- Minifies and compresses web content
|
||||
- Generates `wled00/html_*.h` files with embedded web content
|
||||
- **CRITICAL**: Must be done before any hardware build
|
||||
|
||||
| Command | Purpose | Typical Time |
|
||||
|---|---|---|
|
||||
| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |
|
||||
| `npm test` | Run test suite | ~40 s |
|
||||
| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |
|
||||
| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |
|
||||
2. **Hardware Compilation** (`pio run`):
|
||||
- Compiles C++ firmware for various ESP32/ESP8266 targets
|
||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
||||
- List all targets: `pio run --list-targets`
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
## Before Finishing Work
|
||||
|
||||
- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).
|
||||
- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.
|
||||
- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.
|
||||
- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`
|
||||
**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
|
||||
|
||||
For detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).
|
||||
1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
|
||||
- All tests MUST pass
|
||||
- If tests fail, fix the issue before proceeding
|
||||
|
||||
### Usermod Guidelines
|
||||
2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
|
||||
- Choose `esp32dev` as it's a common, representative environment
|
||||
- See "Hardware Compilation" section above for the full list of common environments
|
||||
- The build MUST complete successfully without errors
|
||||
- If the build fails, fix the issue before proceeding
|
||||
- **DO NOT skip this step** - it validates that firmware compiles with your changes
|
||||
|
||||
- New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.
|
||||
- Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!
|
||||
- New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).
|
||||
- To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.
|
||||
3. **For web UI changes only**: Manually test the interface
|
||||
- See "Manual Testing Scenarios" section below
|
||||
- Verify the UI loads and functions correctly
|
||||
|
||||
## Project Structure Overview
|
||||
**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
|
||||
|
||||
### Project Branch / Release Structure
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## Validation and Testing
|
||||
|
||||
```text
|
||||
main # Main development trunk (daily/nightly) 17.0.0-dev
|
||||
├── V5 # special branch: code rework for esp-idf 5.5.x (unstable)
|
||||
├── V5-C6 # special branch: integration of new MCU types: esp32-c5, esp32-c6, esp32-p4 (unstable)
|
||||
16_x # current beta, preparations for next release 16.0.0
|
||||
0_15_x # maintenance (bugfixes only) for current release 0.15.4
|
||||
(tag) v0.14.4 # previous version 0.14.4 (no maintenance)
|
||||
(tag) v0.13.3 # old version 0.13.3 (no maintenance)
|
||||
(tag) v0. ... . ... # historical versions 0.12.x and before
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Web UI Testing
|
||||
- **ALWAYS validate web UI changes manually**:
|
||||
- Start local server: `cd wled00/data && python3 -m http.server 8080`
|
||||
- Open `http://localhost:8080/index.htm` in browser
|
||||
- Test basic functionality: color picker, effects, settings pages
|
||||
- **Check for JavaScript errors** in browser console
|
||||
|
||||
- ``main``: development trunk (daily/nightly)
|
||||
- ``V5`` and ``V5-C6``: code rework for esp-idf 5.5.x (unstable) - branched from ``main``.
|
||||
- ``0_15_x``: bugfixing / maintenance for release 0.15.x
|
||||
### Code Validation
|
||||
- **No automated linting configured** - follow existing code style in files you edit
|
||||
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
||||
- **Language**: The repository language is English (british, american, canadian, or australian). If you find other languages, suggest a translation into English.
|
||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||
- **Always run tests before finishing**: `npm test`
|
||||
- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
|
||||
|
||||
### Manual Testing Scenarios
|
||||
After making changes to web UI, always test:
|
||||
- **Load main interface**: Verify index.htm loads without errors
|
||||
- **Navigation**: Test switching between main page and settings pages
|
||||
- **Color controls**: Verify color picker and brightness controls work
|
||||
- **Effects**: Test effect selection and parameter changes
|
||||
- **Settings**: Test form submission and validation
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Repository Structure
|
||||
|
||||
tl;dr:
|
||||
* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.
|
||||
* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.
|
||||
* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.
|
||||
* Usermods: `usermods/` (C++, with individual library.json).
|
||||
* Contributor docs: `docs/` (coding guidelines, etc).
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Detailed overview:
|
||||
|
||||
```text
|
||||
wled00/ # Main firmware source (C++) "WLED core"
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
```
|
||||
wled00/ # Main firmware source (C++)
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
│ ├── settings*.htm # Settings pages
|
||||
│ └── *.js/*.css # Frontend resources
|
||||
├── *.cpp/*.h # Firmware source files
|
||||
├── html_*.h # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)
|
||||
├── src/ # Modules used by the WLED core (C++)
|
||||
│ ├── fonts/ # Font libraries for scrolling text effect
|
||||
└ └── dependencies/ # Utility functions - some of them have their own licensing terms
|
||||
lib/ # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them
|
||||
platformio.ini # Hardware build configuration
|
||||
|
||||
platformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.
|
||||
# platformio_override.ini is _not_ stored in the WLED repository!
|
||||
usermods/ # User-contributed addons to the WLED core, maintained by individual contributors (C++, with individual library.json)
|
||||
package.json # Node.js dependencies and scripts, release identification
|
||||
pio-scripts/ # Build tools (PlatformIO)
|
||||
tools/ # Build tools (Node.js), partition files, and generic utilities
|
||||
│ └── *.js/*.css # Frontend resources
|
||||
├── *.cpp/*.h # Firmware source files
|
||||
└── html_*.h # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)
|
||||
tools/ # Build tools (Node.js)
|
||||
├── cdata.js # Web UI build script
|
||||
└── cdata-test.js # Test suite
|
||||
docs/ # Contributor docs, coding guidelines
|
||||
platformio.ini # Hardware build configuration
|
||||
package.json # Node.js dependencies and scripts
|
||||
.github/workflows/ # CI/CD pipelines
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
## General Guidelines
|
||||
### Key Files and Their Purpose
|
||||
- `wled00/data/index.htm` - Main web interface
|
||||
- `wled00/data/settings*.htm` - Configuration pages
|
||||
- `tools/cdata.js` - Converts web files to C++ headers
|
||||
- `wled00/wled.h` - Main firmware configuration
|
||||
- `platformio.ini` - Hardware build targets and settings
|
||||
|
||||
- **Repository language is English.** Suggest translations for non-English content.
|
||||
- **Use VS Code with PlatformIO extension** for best development experience.
|
||||
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.
|
||||
- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.
|
||||
- **When unsure, say so.** Gather more information rather than guessing.
|
||||
- **Acknowledge good patterns** when you see them. Positive feedback always helps.
|
||||
- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.
|
||||
- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.
|
||||
- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.
|
||||
- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.
|
||||
<br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`
|
||||
<br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`
|
||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||
- No automated linting is configured — match existing code style in files you edit.
|
||||
### Development Workflow (applies to agent mode only)
|
||||
1. **For web UI changes**:
|
||||
- Edit files in `wled00/data/`
|
||||
- Run `npm run build` to regenerate headers
|
||||
- Test with local HTTP server
|
||||
- Run `npm test` to validate build system
|
||||
|
||||
Refer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.
|
||||
2. **For firmware changes**:
|
||||
- Edit files in `wled00/` (but NOT `html_*.h` files)
|
||||
- Ensure web UI is built first (`npm run build`)
|
||||
- Build firmware: `pio run -e [target]`
|
||||
- Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
### Attribution for AI-generated code
|
||||
Using AI-generated code can hide the source of the inspiration / knowledge / sources it used.
|
||||
- Document attribution of inspiration / knowledge / sources used in the code, e.g. link to GitHub repositories or other websites describing the principles / algorithms used.
|
||||
- When a larger block of code is generated by an AI tool, embed it into `// AI: below section was generated by an AI` ... `// AI: end` comments (see C++ guidelines).
|
||||
- Every non-trivial AI-generated function should have a brief comment describing what it does. Explain parameters when their names alone are not self-explanatory.
|
||||
- AI-generated code must be well documented with meaningful comments that explain intent, assumptions, and non-obvious logic. Do not rephrase source code; explain concepts and reasoning.
|
||||
3. **For both web and firmware**:
|
||||
- Always build web UI first
|
||||
- Test web interface manually
|
||||
- Build and test firmware if making firmware changes
|
||||
|
||||
### Pull Request Expectations
|
||||
## Build Timing and Timeouts
|
||||
|
||||
- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.
|
||||
- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.
|
||||
- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.
|
||||
**IMPORTANT: Use these timeout values when running builds:**
|
||||
|
||||
### Supporting Reviews and Discussions
|
||||
- **For "is it worth doing?" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).
|
||||
Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.
|
||||
- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
|
||||
- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
|
||||
- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
|
||||
- Subsequent builds are faster due to caching
|
||||
- First builds download toolchains and dependencies which takes significant time
|
||||
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
|
||||
|
||||
**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Build fails with missing html_*.h**: Run `npm run build` first
|
||||
- **Web UI looks broken**: Check browser console for JavaScript errors
|
||||
- **PlatformIO network errors**: Try again, downloads can be flaky
|
||||
- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
|
||||
|
||||
### When Things Go Wrong
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
|
||||
- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
|
||||
- **Clean PlatformIO cache**: `pio run --target clean`
|
||||
- **Reinstall dependencies**: `rm -rf node_modules && npm install`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Always commit source files**
|
||||
- **Web UI re-built is part of the platformio firmware compilation**
|
||||
- **do not commit generated html_*.h files**
|
||||
- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated. If needed, modify Web UI files in `wled00/data/`.
|
||||
- **Test web interface manually after any web UI changes**
|
||||
- When reviewing a PR: the PR author does not need to update/commit generated html_*.h files - these files will be auto-generated when building the firmware binary.
|
||||
- If updating Web UI files in `wled00/data/`, make use of common functions availeable in `wled00/data/common.js` where possible.
|
||||
- **Use VS Code with PlatformIO extension for best development experience**
|
||||
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
**The GitHub Actions CI workflow will:**
|
||||
1. Installs Node.js and Python dependencies
|
||||
2. Runs `npm test` to validate build system (MUST pass)
|
||||
3. Builds web UI with `npm run build` (automatically run by PlatformIO)
|
||||
4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
|
||||
5. Uploads build artifacts
|
||||
|
||||
**To ensure CI success, you MUST locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
|
||||
|
||||
184
AGENTS.md
184
AGENTS.md
@@ -1,184 +0,0 @@
|
||||
# AGENTS.md — WLED Coding Agent Reference
|
||||
|
||||
WLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,
|
||||
with a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.
|
||||
|
||||
See also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,
|
||||
`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`.
|
||||
|
||||
## Build Commands
|
||||
|
||||
| Command | Purpose | Timeout |
|
||||
|---|---|---|
|
||||
| `npm ci` | Install Node.js deps (required first) | 30s |
|
||||
| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |
|
||||
| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |
|
||||
| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |
|
||||
| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 30 min |
|
||||
| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 30 min |
|
||||
|
||||
**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates
|
||||
required C headers for firmware compilation.
|
||||
|
||||
### Running a Single Test
|
||||
|
||||
Tests use Node.js built-in test runner (`node:test`). The single test file is
|
||||
`tools/cdata-test.js`. Run it with:
|
||||
|
||||
```sh
|
||||
npm test # runs all tests via `node --test`
|
||||
node --test tools/cdata-test.js # run just that file directly
|
||||
```
|
||||
|
||||
There are no C++ unit tests. Firmware is validated by successful compilation across
|
||||
target environments. Always build after code changes: `pio run -e esp32dev`.
|
||||
|
||||
### Common Firmware Environments
|
||||
|
||||
`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`
|
||||
|
||||
### Recovery / Troubleshooting
|
||||
|
||||
```sh
|
||||
npm run build -- -f # force web UI rebuild
|
||||
rm -f wled00/html_*.h wled00/js_*.h && npm run build # clean + rebuild UI
|
||||
pio run --target clean # clean PlatformIO build artifacts
|
||||
rm -rf node_modules && npm ci # reinstall Node.js deps
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
wled00/ # Main firmware source (C++)
|
||||
data/ # Web UI source (HTML/JS/CSS) — tabs for indentation
|
||||
html_*.h, js_*.h # Auto-generated (NEVER edit or commit)
|
||||
src/ # Sub-modules: fonts, bundled dependencies (ArduinoJSON)
|
||||
usermods/ # Community usermods (each has library.json + .cpp/.h)
|
||||
platformio.ini # Build configuration and environments
|
||||
pio-scripts/ # PlatformIO build scripts (Python)
|
||||
tools/ # Node.js build tools (cdata.js) and tests
|
||||
docs/ # Coding convention docs
|
||||
.github/workflows/ # CI/CD (GitHub Actions)
|
||||
```
|
||||
|
||||
## C++ Code Style (wled00/, usermods/)
|
||||
|
||||
### Formatting
|
||||
- **2-space indentation** (no tabs in C++ files)
|
||||
- K&R brace style preferred (opening brace on same line)
|
||||
- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`
|
||||
- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)
|
||||
- No enforced line-length limit
|
||||
|
||||
### Naming Conventions
|
||||
| Kind | Convention | Examples |
|
||||
|---|---|---|
|
||||
| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |
|
||||
| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |
|
||||
| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |
|
||||
| Private members | _camelCase | `_type`, `_bri`, `_len` |
|
||||
| Enum values | PascalCase | `PinOwner::BusDigital` |
|
||||
|
||||
### Includes
|
||||
- Include `"wled.h"` as the primary project header
|
||||
- Project headers first, then platform/Arduino, then third-party
|
||||
- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`
|
||||
|
||||
### Types and Const
|
||||
- Prefer `const &` for read-only function parameters
|
||||
- Mark getter/query methods `const`; use `static` for methods not accessing instance state
|
||||
- Prefer `constexpr` over `#define` for compile-time constants when possible
|
||||
- Use `static_assert` over `#if ... #error`
|
||||
- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code
|
||||
|
||||
### Error Handling
|
||||
- **No C++ exceptions** — some builds disable them
|
||||
- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)
|
||||
- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`
|
||||
- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (compiled out unless `-D WLED_DEBUG`)
|
||||
|
||||
### Strings and Memory
|
||||
- Use `F("string")` for string constants (saves RAM on ESP8266)
|
||||
- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings
|
||||
- Avoid `String` in hot paths; acceptable in config/setup code
|
||||
- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation
|
||||
- No VLAs — use fixed arrays or heap allocation
|
||||
- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation
|
||||
|
||||
### Preprocessor / Feature Flags
|
||||
- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)
|
||||
- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.
|
||||
- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.
|
||||
- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`
|
||||
|
||||
### Comments
|
||||
- `//` for inline (always space after), `/* */` for block comments
|
||||
- AI-generated blocks: mark with `// AI: below section was generated by an AI` / `// AI: end`
|
||||
|
||||
### Math Functions
|
||||
- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)
|
||||
- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`
|
||||
- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`
|
||||
|
||||
### Hot-Path Code (Pixel Pipeline)
|
||||
- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`
|
||||
- Cache class members to locals before loops
|
||||
- Pre-compute invariants outside loops; use reciprocals to avoid division
|
||||
- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`
|
||||
|
||||
### ESP32 Tasks
|
||||
- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog
|
||||
- Do not use `delay()` in effects (FX.cpp) or hot pixel path
|
||||
|
||||
## Web UI Code Style (wled00/data/)
|
||||
|
||||
- **Tab indentation** for HTML, JS, and CSS
|
||||
- camelCase for JS functions/variables
|
||||
- Reuse helpers from `common.js` — do not duplicate utilities
|
||||
- After editing, run `npm run build` to regenerate headers
|
||||
- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly
|
||||
|
||||
## Usermod Pattern
|
||||
|
||||
Usermods live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.
|
||||
|
||||
```cpp
|
||||
class MyUsermod : public Usermod {
|
||||
private:
|
||||
bool enabled = false;
|
||||
static const char _name[];
|
||||
public:
|
||||
void setup() override { /* ... */ }
|
||||
void loop() override { /* ... */ }
|
||||
void addToConfig(JsonObject& root) override { /* ... */ }
|
||||
bool readFromConfig(JsonObject& root) override { /* ... */ }
|
||||
uint16_t getId() override { return USERMOD_ID_MYMOD; }
|
||||
};
|
||||
const char MyUsermod::_name[] PROGMEM = "MyUsermod";
|
||||
static MyUsermod myUsermod;
|
||||
REGISTER_USERMOD(myUsermod);
|
||||
```
|
||||
|
||||
- Add usermod IDs to `wled00/const.h`
|
||||
- Activate via `custom_usermods` in platformio build config
|
||||
- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)
|
||||
- Store repeated strings as `static const char[] PROGMEM`
|
||||
|
||||
## CI/CD
|
||||
|
||||
CI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):
|
||||
1. `npm test` (web UI build validation)
|
||||
2. Firmware compilation for all default environments (~22 targets)
|
||||
3. Post-link validation of usermod linkage (`validate_modules.py`)
|
||||
|
||||
No automated linting is configured. Match existing code style in files you edit.
|
||||
|
||||
## General Rules
|
||||
|
||||
- Repository language is English
|
||||
- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`
|
||||
- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.
|
||||
- No force-push on open PRs
|
||||
- Changes to `platformio.ini` require maintainer approval
|
||||
- Remove dead/unused code — justify or delete it
|
||||
- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor)
|
||||
@@ -9,14 +9,6 @@ We'll work with you to refine your contribution, but we'll also push back if som
|
||||
|
||||
Here are a few suggestions to make it easier for you to contribute:
|
||||
|
||||
### Important Developer Infos
|
||||
* [Project Structure, Files and Directories](.github/copilot-instructions.md#project-structure-overview) (in our AI instructions)
|
||||
* [Instructions for creating usermods](.github/copilot-instructions.md#usermod-guidelines) (in our AI instructions)
|
||||
* KB: [Compiling WLED](https://kno.wled.ge/advanced/compiling-wled/) - slightly outdated but still helpful :-)
|
||||
* Arduino IDE is not supported any more. Use VSCode with the PlatformIO extension.
|
||||
* [Compiling in VSCode/Platformio](https://github.com/wled/WLED-Docs/issues/161) - modern way without command line or platformio.ini changes.
|
||||
* If you add a new feature, consider making a PR to [``wled-docs``](https://github.com/wled/WLED-Docs) for updating our official documentation.
|
||||
|
||||
### PR from a branch in your own fork
|
||||
Start your pull request (PR) in a branch of your own fork. Don't make a PR directly from your main branch.
|
||||
This lets you update your PR if needed, while you can work on other tasks in 'main' or in other branches.
|
||||
@@ -28,10 +20,10 @@ This lets you update your PR if needed, while you can work on other tasks in 'ma
|
||||
>
|
||||
> <img width="295" height="134" alt="image: fork and edit" src="https://github.com/user-attachments/assets/f0dc7567-edcb-4409-a530-cd621ae9661f" />
|
||||
|
||||
|
||||
### Target branch for pull requests
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please make all PRs against the `main` branch.
|
||||
Please make all PRs against the `main` branch.
|
||||
|
||||
### Describing your PR
|
||||
|
||||
@@ -168,19 +160,14 @@ AI tools are powerful but "often wrong" - your judgment is essential! 😊
|
||||
|
||||
- ✅ **Understand the code** - As the person contributing to WLED, make sure you understand exactly what the AI-generated source code does
|
||||
- ✅ **Review carefully** - AI can lose comments, introduce bugs, or make unnecessary changes
|
||||
- ✅ **Be transparent** - Add comments `// AI: below section was generated by an AI` ... `// AI: end` around larger chunks
|
||||
- ✅ **Be transparent** - Add a comment like `// This section was AI-generated` for larger chunks
|
||||
- ✅ **Use AI for translation** - AI is great for translating comments to English (but verify technical terms!)
|
||||
|
||||
### Code style
|
||||
|
||||
Don't stress too much about style! When in doubt, just match the style in the files you're editing. 😊
|
||||
|
||||
Our review bot (coderabbit) has learned lots of detailed guides and hints - it will suggest them automatically when you submit a PR for review.
|
||||
If you are curious, these are the detailed guides:
|
||||
* [C++ Coding](docs/cpp.instructions.md)
|
||||
* [WebUi: HTML, JS, CSS](docs/web.instructions.md)
|
||||
|
||||
Below are the main rules used in the WLED repository:
|
||||
Here are our main guidelines:
|
||||
|
||||
#### Indentation
|
||||
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
---
|
||||
applyTo: ".github/workflows/*.yml,.github/workflows/*.yaml"
|
||||
---
|
||||
# CI/CD Conventions — GitHub Actions Workflows
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## YAML Style
|
||||
|
||||
- Indent with **2 spaces** (no tabs)
|
||||
- Every workflow, job, and step must have a `name:` field that clearly describes its purpose
|
||||
- Group related steps logically; separate unrelated groups with a blank line
|
||||
- Comments (`#`) are encouraged for non-obvious decisions (e.g., why `fail-fast: false` is set, what a cron expression means)
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
### Triggers
|
||||
|
||||
- Declare `on:` triggers explicitly; avoid bare `on: push` without branch filters on long-running or expensive jobs
|
||||
- Prefer `workflow_call` for shared build logic (see `build.yml`) to avoid duplicating steps across workflows
|
||||
- Document scheduled triggers (`cron:`) with a human-readable comment:
|
||||
|
||||
```yaml
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC daily
|
||||
```
|
||||
|
||||
### Jobs
|
||||
|
||||
- Express all inter-job dependencies with `needs:` — never rely on implicit ordering
|
||||
- Use job `outputs:` + step `id:` to pass structured data between jobs (see `get_default_envs` in `build.yml`)
|
||||
- Set `fail-fast: false` on matrix builds so that a single failing environment does not cancel others
|
||||
|
||||
### Runners
|
||||
|
||||
- Pin to a specific Ubuntu version (`ubuntu-22.04`, `ubuntu-24.04`) rather than `ubuntu-latest` for reproducible builds
|
||||
- Only use `ubuntu-latest` in jobs where exact environment reproducibility is not required (e.g., trivial download/publish steps)
|
||||
|
||||
### Tool and Language Versions
|
||||
|
||||
- Pin tool versions explicitly:
|
||||
```yaml
|
||||
python-version: '3.12'
|
||||
```
|
||||
- Do not rely on the runner's pre-installed tool versions — always install via a versioned setup action
|
||||
|
||||
### Caching
|
||||
|
||||
- Always cache package managers and build tool directories when the job installs dependencies:
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
```
|
||||
- Include the environment name or a relevant identifier in cache keys when building multiple targets
|
||||
|
||||
### Artifacts
|
||||
|
||||
- Name artifacts with enough context to be unambiguous (e.g., `firmware-${{ matrix.environment }}`)
|
||||
- Avoid uploading artifacts that will never be consumed downstream
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
Important: Several current workflows still violate parts of the baseline below - migration is in progress.
|
||||
|
||||
### Permissions — Least Privilege
|
||||
|
||||
Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read # for checkout
|
||||
```
|
||||
|
||||
For jobs that publish releases or write to the repository:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: write # create/update releases
|
||||
```
|
||||
|
||||
A common safe baseline for build-only jobs:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read
|
||||
```
|
||||
|
||||
### Supply Chain — Action Pinning
|
||||
|
||||
**Third-party actions** (anything outside the `actions/` and `github/` namespaces) should be pinned to a specific release tag. Branch pins (`@main`, `@master`) are **not allowed** — they can be updated by the action author at any time without notice:
|
||||
|
||||
```yaml
|
||||
# ✅ Acceptable — specific version tag. SHA pinning recommended for more security, as @v2 is still a mutable tag.
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
# ❌ Not acceptable — mutable branch reference
|
||||
uses: andelf/nightly-release@main
|
||||
```
|
||||
|
||||
SHA pinning (e.g., `uses: someorg/some-action@abc1234`) is the most secure option for third-party actions; it is recommended when auditing supply-chain risk is a priority. At minimum, always use a specific version tag.
|
||||
|
||||
**First-party actions** (`actions/checkout`, `actions/cache`, `actions/upload-artifact`, etc.) pinned to a major version tag (e.g., `@v4`) are acceptable because GitHub maintains and audits these.
|
||||
|
||||
When adding a new third-party action:
|
||||
1. Check that the action's repository is actively maintained
|
||||
2. Review the action's source before adding it
|
||||
3. Prefer well-known, widely-used actions over obscure ones
|
||||
|
||||
### Credentials and Secrets
|
||||
|
||||
- Use `${{ secrets.GITHUB_TOKEN }}` for operations within the same repository — it is automatically scoped and rotated
|
||||
- Never commit secrets, tokens, or passwords into workflow files or any tracked file
|
||||
- Never print secrets in `run:` steps, even with `echo` — GitHub masks known secrets but derived values are not automatically masked
|
||||
- Scope secrets to the narrowest step that needs them using `env:` at the step level, not at the workflow level:
|
||||
|
||||
```yaml
|
||||
# ✅ Scoped to the step that needs it
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ❌ Unnecessarily broad
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
- Personal Access Tokens (PATs, stored as repository secrets) should have the minimum required scopes and should be rotated periodically
|
||||
|
||||
### Script Injection
|
||||
|
||||
`${{ }}` expressions are evaluated before the shell script runs. If an expression comes from untrusted input (PR titles, issue bodies, branch names from forks), it can inject arbitrary shell commands.
|
||||
|
||||
**Never** interpolate `github.event.*` values directly into a `run:` step:
|
||||
|
||||
```yaml
|
||||
# ❌ Injection risk — PR title is attacker-controlled
|
||||
- run: echo "${{ github.event.pull_request.title }}"
|
||||
|
||||
# ✅ Safe — value passed through an environment variable
|
||||
- env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: echo "$PR_TITLE"
|
||||
```
|
||||
|
||||
This rule applies to any value that originates outside the repository (issue bodies, labels, comments, commit messages from forks).
|
||||
|
||||
### Pull Request Workflows
|
||||
|
||||
- Workflows triggered by `pull_request` from a fork run with **read-only** token permissions and no access to repository secrets — this is intentional and correct
|
||||
- Do not use `pull_request_target` unless you fully understand the security implications; it runs in the context of the base branch and *does* have secret access, making it a common attack surface
|
||||
@@ -1,526 +0,0 @@
|
||||
---
|
||||
applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
|
||||
---
|
||||
# C++ Coding Conventions
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hiding this reference, to avoid cyclic "include" loops -->
|
||||
See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines that apply to all contributors.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent with **2 spaces** (no tabs in C++ files)
|
||||
- Opening braces on the same line is preferred (K&R style). Brace on a separate line (Allman style) is acceptable
|
||||
- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`
|
||||
- Space between keyword and parenthesis: `if (...)`, `for (...)`. No space between function name and parenthesis: `doStuff(a)`
|
||||
- No enforced line-length limit; wrap when a line exceeds your editor width
|
||||
|
||||
## Naming
|
||||
|
||||
- **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent`
|
||||
- **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig`
|
||||
- **PascalCase** for enum values: `PinOwner::BusDigital`
|
||||
- **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID`
|
||||
|
||||
## General
|
||||
|
||||
- Follow the existing style in the file you are editing
|
||||
- If possible, use `static` for local (C-style) variables and functions (keeps the global namespace clean)
|
||||
- Avoid unexplained "magic numbers". Prefer named constants (`constexpr`) or C-style `#define` constants for repeated numbers that have the same meaning
|
||||
- Include `"wled.h"` as the primary project header where needed
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## Header Guards
|
||||
|
||||
Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma once` before the guard:
|
||||
|
||||
```cpp
|
||||
#ifndef WLED_EXAMPLE_H
|
||||
#define WLED_EXAMPLE_H
|
||||
// ...
|
||||
#endif // WLED_EXAMPLE_H
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Comments
|
||||
|
||||
- `//` for inline comments, `/* ... */` for block comments. Always put a space after `//`
|
||||
- **AI attribution:** When a larger block of code is generated by an AI tool, mark it with an `// AI:` comment so reviewers know to scrutinize it:
|
||||
|
||||
```cpp
|
||||
// AI: below section was generated by an AI
|
||||
void calculateCRC(const uint8_t* data, size_t len) {
|
||||
...
|
||||
}
|
||||
// AI: end
|
||||
```
|
||||
|
||||
Single-line AI-assisted edits do not need the marker — use it when the AI produced a contiguous block that a human did not write line-by-line.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now, as it created too many "please add a description" review findings in my first tests -->
|
||||
- **Function & feature comments:** Every non-trivial function should have a brief comment above it describing what it does. Include a note about each parameter when the names alone are not self-explanatory:
|
||||
|
||||
```cpp
|
||||
/* *****
|
||||
* Apply gamma correction to a single color channel.
|
||||
* @param value raw 8-bit channel value (0–255)
|
||||
* @param gamma gamma exponent (typically 2.8)
|
||||
* @return corrected 8-bit value
|
||||
***** */
|
||||
uint8_t gammaCorrect(uint8_t value, float gamma);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Short accessor-style functions (getters/setters, one-liners) may skip this if their purpose is obvious from the name.
|
||||
|
||||
## Preprocessor & Feature Flags
|
||||
|
||||
- Prefer compile-time feature flags (`#ifdef` / `#ifndef`) over runtime checks where possible
|
||||
- Platform differentiation: `ARDUINO_ARCH_ESP32` vs `ESP8266`
|
||||
- PSRAM availability: `BOARD_HAS_PSRAM`
|
||||
|
||||
## Error Handling
|
||||
|
||||
- `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for developer diagnostics (compiled out unless `-D WLED_DEBUG`)
|
||||
- Don't rely on C++ exceptions — use return codes (`-1` / `false` for errors) and global flags (e.g. `errorFlag = ERR_LOW_MEM`). Some builds don't support C++ exceptions.
|
||||
|
||||
## Strings
|
||||
|
||||
- Use `const char*` for temporary/parsed strings
|
||||
- Avoid `String` (Arduino heap-allocated string) in hot paths; acceptable in config/setup code
|
||||
- Use `F("string")` for string constants (major RAM win on ESP8266; mostly overload/type compatibility on ESP32)
|
||||
- Store repeated strings as `static const char[] PROGMEM`
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
On **ESP8266** this explicitly stores the string in flash (PROGMEM), saving precious RAM — every byte counts on that platform.
|
||||
On **ESP32**, `PROGMEM` is a no-op and string literals already reside in flash/rodata, so `F()` yields little RAM benefit but remains harmless (it satisfies `__FlashStringHelper*` overloads that some APIs expect).
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
```cpp
|
||||
DEBUG_PRINTLN(F("WS client connected.")); // string stays in flash, not RAM
|
||||
DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType); // format string stays in flash
|
||||
```
|
||||
|
||||
## Memory
|
||||
|
||||
- **PSRAM-aware allocation**: use `d_malloc()` (prefer DRAM), `p_malloc()` (prefer PSRAM) from `fcn_declare.h`
|
||||
- **Avoid Variable Length Arrays (VLAs)**: FreeRTOS task stacks are typically 2–8 KB. A runtime-sized VLA can silently exhaust the stack. Use fixed-size arrays or heap allocation (`d_malloc` / `p_malloc`). Any VLA must be explicitly justified in source or PR.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
GCC/Clang support VLAs as an extension (they are not part of the C++ standard), so they look like a legitimate feature — but they are allocated on the stack at runtime. On ESP32/ESP8266, a VLA whose size depends on a runtime parameter (segment dimensions, pixel counts, etc.) can silently exhaust the stack and cause the program to behave in unexpected ways or crash.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- **Larger buffers** (LED data, JSON documents) should use PSRAM when available and technically feasible
|
||||
- **Hot-path**: some data should stay in DRAM or IRAM for performance reasons
|
||||
- Memory efficiency matters, but is less critical on boards with PSRAM
|
||||
|
||||
Heap fragmentation is a concern:
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
- Fragmentation can lead to crashes, even when the overall amount of available heap is still good. The C++ runtime doesn't do any "garbage collection".
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Avoid frequent `d_malloc` and `d_free` inside a function, especially for small sizes.
|
||||
- Avoid frequent creation / destruction of objects.
|
||||
- Allocate buffers early, and try to re-use them.
|
||||
- Instead of incrementally appending to a `String`, reserve the expected max buffer upfront by using the `reserve()` method.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
```cpp
|
||||
String result;
|
||||
result.reserve(65); // pre-allocate to avoid realloc fragmentation
|
||||
```
|
||||
|
||||
```cpp
|
||||
// prefer DRAM; falls back gracefully and enforces MIN_HEAP_SIZE guard
|
||||
_ledsDirty = (byte*) d_malloc(getBitArrayBytes(_len));
|
||||
```
|
||||
|
||||
```cpp
|
||||
_mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
_modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## `const` and `constexpr`
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`const` is a promise to the compiler that a value (or object) will not change - a function declared with a `const char* message` parameter is not allowed to modify the content of `message`.
|
||||
This pattern enables optimizations and makes intent clear to reviewers.
|
||||
|
||||
`constexpr` allows to define constants that are *guaranteed* to be evaluated by the compiler (zero run-time costs).
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- For function parameters that are read-only, prefer `const &` or `const`.
|
||||
|
||||
### `const` locals
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* Adding `const` to a local variable that is only assigned once is optional, but *not* strictly necessary.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
* In hot-path code, `const` on cached locals may help the compiler keep values in registers.
|
||||
```cpp
|
||||
const uint_fast16_t cols = vWidth();
|
||||
const uint_fast16_t rows = vHeight();
|
||||
```
|
||||
|
||||
### `const` references to avoid copies
|
||||
- Pass objects by `const &` (or `&`) instead of copying them implicitly.
|
||||
- Use `const &` (or `&`) inside loops - This avoids constructing temporary objects on every access.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const auto &m = _mappings[i]; // reference, not a copy (bus_manager.cpp)
|
||||
Segment& sourcesegment = strip.getSegment(sourceid); // alias — avoids creating a temporary Segment instance
|
||||
```
|
||||
|
||||
For function parameters that are read-only, prefer `const &`:
|
||||
```cpp
|
||||
BusManager::add(const BusConfig &bc, bool placeholder) {
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Class **Data Members:** Avoid reference data members (`T&` or `const T&`) in a class.
|
||||
A reference member can outlive the object it refers to, causing **dangling reference** bugs that are hard to diagnose. Prefer value storage or use a pointer and document the expected lifetime.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now - codebase is not compliant to this rule (slowly migrating) -->
|
||||
### `constexpr` over `#define`
|
||||
|
||||
- Prefer `constexpr` for compile-time constants. Unlike `#define`, `constexpr` respects scope and type safety, keeping the global namespace clean.
|
||||
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
|
||||
// Avoid (when possible):
|
||||
#define TWO_CHANNEL_MASK 0x00FF00FF
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### `static_assert` over `#error`
|
||||
|
||||
- Use `static_assert` instead of the C-style `#if … #error … #endif` pattern when validating compile-time constants. It provides a clear message and works with `constexpr` values.
|
||||
- `#define` and `#if ... #else ... #endif` is still needed for conditional-compilation guards and build-flag-overridable values.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
|
||||
// Avoid:
|
||||
#if (WLED_MAX_BUSSES > 32)
|
||||
#error "WLED_MAX_BUSSES exceeds hard limit"
|
||||
#endif
|
||||
```
|
||||
|
||||
```cpp
|
||||
// using static_assert() to validate enumerated types (zero cost at runtime)
|
||||
static_assert(0u == static_cast<uint8_t>(PinOwner::None),
|
||||
"PinOwner::None must be zero, so default array initialization works as expected");
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### `static` and `const` class methods
|
||||
|
||||
#### `const` member functions
|
||||
|
||||
Marking a member function `const` tells the compiler that it does not modify the object's state:
|
||||
|
||||
```cpp
|
||||
uint16_t length() const { return _len; }
|
||||
bool isActive() const { return _active; }
|
||||
```
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Benefits for GCC/Xtensa/RISC-V:
|
||||
- The compiler knows the method cannot write to `this`, so it is free to **keep member values in registers** across the call and avoid reload barriers.
|
||||
- `const` methods can be called on `const` objects and `const` references — essential when passing large objects as `const &` to avoid copying.
|
||||
- `const` allows the compiler to **eliminate redundant loads**: if a caller already has a member value cached, the compiler can prove the `const` call cannot invalidate it.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Declare getter, query, or inspection methods `const`. If you need to mark a member `mutable` to work around this (e.g. for a cache or counter), document the reason.
|
||||
|
||||
#### `static` member functions
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
A `static` member function has no implicit `this` pointer. This has two distinct advantages:
|
||||
|
||||
1. **Smaller code, faster calls**: no `this` is passed in a register. On Xtensa and RISC-V, this removes one register argument from every call site and prevents the compiler from emitting `this`-preservation code around inlined blocks.
|
||||
2. **Better inlining**: GCC can inline a `static` method with more certainty because it cannot be overridden by a derived class (no virtual dispatch ambiguity) and has no aliasing concern through `this`.
|
||||
|
||||
Use `static` for any method that does not need access to instance members:
|
||||
|
||||
```cpp
|
||||
// Factory / utility — no instance needed:
|
||||
static BusConfig fromJson(JsonObject obj);
|
||||
|
||||
// Pure computation helpers:
|
||||
static uint8_t gamma8(uint8_t val);
|
||||
static uint32_t colorBalance(uint32_t color, uint8_t r, uint8_t g, uint8_t b);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
`static` communicates intent clearly: a reviewer immediately knows the method is stateless and safe to call without a fully constructed object.
|
||||
|
||||
> **Rule of thumb**: if a method does not read or write any member variable, make it `static`. If it only reads member variables, make it `const`. Note: `static` methods cannot also be `const`-qualified because there is no implicit `this` pointer to be const — just use `static`. Both qualifiers reduce coupling and improve generated code on all ESP32 targets.
|
||||
|
||||
---
|
||||
|
||||
## Hot-Path Optimization
|
||||
|
||||
The hot path is the per-frame pixel pipeline: **Segment → Strip → BusManager → Bus(Digital,HUB75,Network) or PolyBus → LED driver, plus ``WS2812FX::show()`` and below**.
|
||||
Speed is the priority here. The patterns below are taken from existing hot-path code (`FX_fcn.cpp`, `FX_2Dfcn.cpp`, `bus_manager.cpp`, `colors.cpp`) and should be followed when modifying these files.
|
||||
|
||||
Note: `FX.cpp` (effect functions) is written by many contributors and has diverse styles — that is acceptable.
|
||||
|
||||
### Function Attributes
|
||||
|
||||
Stack the appropriate attributes on hot-path functions. Defined in `const.h`:
|
||||
|
||||
| Attribute | Meaning | When to use |
|
||||
|---|---|---|
|
||||
| `__attribute__((hot))` | Branch-prediction hint | hot-path functions with complex logic |
|
||||
| `IRAM_ATTR` | Place in fast IRAM (ESP32) | Critical per-pixel functions (e.g. `BusDigital::setPixelColor`) |
|
||||
| `IRAM_ATTR_YN` | IRAM on ESP32, no-op on ESP8266 | Hot functions that ESP8266 can't fit in IRAM |
|
||||
| `WLED_O2_ATTR` | Force `-O2` optimization | Most hot-path functions |
|
||||
| `WLED_O3_ATTR` | Force `-O3,fast-math` | Innermost color math (e.g. `color_blend`) |
|
||||
| `[[gnu::hot]] inline` | Modern C++ attribute + inline | Header-defined accessors (e.g. `progress()`, `currentBri()`) |
|
||||
|
||||
Note: `WLED_O3_ATTR` sometimes causes performance loss compared to `WLED_O2_ATTR`. Choose optimization levels based on test results.
|
||||
|
||||
Example signature:
|
||||
|
||||
```cpp
|
||||
void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(unsigned i, uint32_t c)
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Cache Members to Locals Before Loops
|
||||
|
||||
Copy class members and virtual-call results to local variables before entering a loop:
|
||||
|
||||
```cpp
|
||||
uint_fast8_t count = numBusses; // avoid repeated member access
|
||||
for (uint_fast8_t i = 0; i < count; i++) {
|
||||
Bus* const b = busses[i]; // const pointer hints to compiler
|
||||
uint_fast16_t bstart = b->getStart();
|
||||
uint_fast16_t blen = b->getLength();
|
||||
...
|
||||
}
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Unsigned Range Check
|
||||
|
||||
Replace two-comparison range tests with a single unsigned subtraction:
|
||||
|
||||
```cpp
|
||||
// Instead of: if (pix >= bstart && pix < bstart + blen)
|
||||
if ((uint_fast16_t)(pix - bstart) < blen) // also catches negative pix via unsigned underflow
|
||||
```
|
||||
|
||||
### Early Returns
|
||||
|
||||
Guard every hot-path function with the cheapest necessary checks first:
|
||||
|
||||
```cpp
|
||||
if (!isActive()) return; // inactive segment
|
||||
if (unsigned(i) >= vLength()) return; // bounds check (catches negative i too)
|
||||
```
|
||||
|
||||
### Avoid Nested Calls — Fast Path / Complex Path
|
||||
|
||||
Avoid calling non-inline functions or making complex decisions inside per-pixel hot-path code. When a function has both a common simple case and a rare complex case, split it into two variants and choose once per frame rather than per pixel.
|
||||
|
||||
General rules:
|
||||
- Keep fast-path functions free of non-inline calls, multi-way branches, and complex switch-case decisions.
|
||||
- Hoist per-frame decisions (e.g. simple vs. complex segment) out of the per-pixel loop.
|
||||
- Code duplication between fast/slow variants is acceptable to keep the fast path lean.
|
||||
|
||||
### Function Pointers to Eliminate Repeated Decisions
|
||||
|
||||
When the same decision (e.g. "which drawing routine?") would be evaluated for every pixel, assign the chosen variant to a function pointer once and let the inner loop call through the pointer. This removes the branch entirely — the calling code (e.g. the GIF decoder loop) only ever invokes one function per frame, with no per-pixel decision.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`image_loader.cpp` demonstrates the pattern: `calculateScaling()` picks the best drawing callback once per frame based on segment dimensions and GIF size, then passes it to the decoder via `setDrawPixelCallback()`:
|
||||
|
||||
```cpp
|
||||
// calculateScaling() — called once per frame
|
||||
if ((perPixelX < 2) && (perPixelY < 2))
|
||||
decoder.setDrawPixelCallback(drawPixelCallbackDownScale2D); // downscale-only variant
|
||||
else
|
||||
decoder.setDrawPixelCallback(drawPixelCallback2D); // full-scaling variant
|
||||
```
|
||||
|
||||
Each callback is a small, single-purpose function with no internal branching — the decoder's per-pixel loop never re-evaluates which strategy to use.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Template Specialization (Advanced)
|
||||
|
||||
Templates can eliminate runtime decisions by generating separate code paths at compile time. For example, a pixel setter could be templated on color order or channel count so the compiler removes dead branches and produces tight, specialized machine code:
|
||||
|
||||
```cpp
|
||||
template<bool hasWhite>
|
||||
void setChannel(uint8_t* out, uint32_t col) {
|
||||
out[0] = R(col); out[1] = G(col); out[2] = B(col);
|
||||
if constexpr (hasWhite) out[3] = W(col); // compiled out when hasWhite==false
|
||||
}
|
||||
```
|
||||
|
||||
Use sparingly — each instantiation duplicates code in flash. On ESP8266 and small-flash ESP32 boards this can exhaust IRAM/flash. Prefer templates only when the hot path is measurably faster and the number of instantiations is small (2–4).
|
||||
|
||||
### RAII Lock-Free Synchronization (Advanced)
|
||||
|
||||
Where contention is rare and the critical section is short, consider replacing mutex-based locking with lock-free techniques using `std::atomic` and RAII scoped guards. A scoped guard sets a flag on construction and clears it on destruction, guaranteeing cleanup even on early return:
|
||||
|
||||
```cpp
|
||||
struct ScopedBusyFlag {
|
||||
std::atomic<bool>& flag;
|
||||
bool acquired;
|
||||
ScopedBusyFlag(std::atomic<bool>& f) : flag(f), acquired(false) {
|
||||
bool expected = false;
|
||||
acquired = flag.compare_exchange_strong(expected, true);
|
||||
}
|
||||
~ScopedBusyFlag() { if (acquired) flag.store(false); }
|
||||
explicit operator bool() const { return acquired; }
|
||||
};
|
||||
|
||||
// Usage
|
||||
static std::atomic<bool> busySending{false};
|
||||
ScopedBusyFlag guard(busySending);
|
||||
if (!guard) return; // another task is already sending
|
||||
// ... do work — flag auto-clears when guard goes out of scope
|
||||
```
|
||||
|
||||
This avoids FreeRTOS semaphore overhead and the risk of forgetting to return a semaphore. There are no current examples of this pattern in the codebase — consult with maintainers before introducing it in new code, to ensure it aligns with the project's synchronization conventions.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Pre-Compute Outside Loops
|
||||
|
||||
Move invariant calculations before the loop. Pre-compute reciprocals to replace division with multiplication.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const uint_fast16_t cols = virtualWidth();
|
||||
const uint_fast16_t rows = virtualHeight();
|
||||
uint_fast8_t fadeRate = (255U - rate) >> 1;
|
||||
float mappedRate_r = 1.0f / (float(fadeRate) + 1.1f); // reciprocal — avoid division inside loop
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Parallel Channel Processing
|
||||
|
||||
Process R+B and W+G channels simultaneously using the two-channel mask pattern:
|
||||
|
||||
```cpp
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK;
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK;
|
||||
return rb | wg;
|
||||
```
|
||||
|
||||
### Bit Shifts Over Division (mainly for RISC-V boards)
|
||||
|
||||
ESP32 and ESP32-S3 (Xtensa core) have a fast "integer divide" instruction, so manual shifts rarely help.
|
||||
On RISC-V targets (ESP32-C3/C6/P4) and ESP8266, prefer explicit bit-shifts for power-of-two arithmetic — the compiler does **not** always convert divisions to shifts.
|
||||
Always use unsigned operands for right shifts; signed right-shift is implementation-defined.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
On RISC-V-based boards (ESP32-C3, ESP32-C6, ESP32-C5) explicit shifts can be beneficial.
|
||||
```cpp
|
||||
position >> 3 // instead of position / 8
|
||||
(255U - rate) >> 1 // instead of (255 - rate) / 2
|
||||
i & 0x0007 // instead of i % 8
|
||||
```
|
||||
|
||||
**Important**: The bit-shifted expression should be unsigned. On some MCUs, "signed right-shift" is implemented by an "arithmetic shift right" that duplicates the sign bit: ``0b1010 >> 1 = 0b1101``.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Static Caching for Expensive Computations
|
||||
|
||||
Cache results in static locals when the input rarely changes between calls:
|
||||
|
||||
```cpp
|
||||
static uint16_t lastKelvin = 0;
|
||||
static byte correctionRGB[4] = {255,255,255,0};
|
||||
if (lastKelvin != kelvin) {
|
||||
colorKtoRGB(kelvin, correctionRGB); // expensive — only recalculate when input changes
|
||||
lastKelvin = kelvin;
|
||||
}
|
||||
```
|
||||
|
||||
### Inlining Strategy
|
||||
|
||||
- Move frequently-called small functions to headers for inlining (e.g. `Segment::setPixelColorRaw` is in `FX.h`)
|
||||
- Use `static inline` for file-local helpers
|
||||
|
||||
### Math & Trigonometric Functions
|
||||
|
||||
- WLED uses a custom `fastled_slim` library. The old FastLED trig aliases (`sin8`, `cos8`, `sin16`, `cos16`) **no longer exist and cause a compile error** — use `sin8_t()`, `cos8_t()`, `sin16_t()`, `cos16_t()` instead. For float approximations use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`. Replace FastLED noise aliases (`inoise8`, `inoise16`) with `perlin8`, `perlin16`.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
| ❌ Do not use (compile error) | ✅ Use instead | Source |
|
||||
|---|---|---|
|
||||
| `sin8()`, `cos8()` | `sin8_t()`, `cos8_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sin16()`, `cos16()` | `sin16_t()`, `cos16_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sinf()`, `cosf()` | `sin_approx()`, `cos_approx()` | `wled_math.cpp` |
|
||||
| `atan2f()`, `atan2()` | `atan2_t()` | `wled_math.cpp` |
|
||||
| `sqrt()` on integers | `sqrt32_bw()` | `fcn_declare.h` → `wled_math.cpp` |
|
||||
| `sqrtf()` on floats | `sqrtf()` (acceptable) | — no WLED replacement |
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
---
|
||||
|
||||
## `delay()` vs `yield()` in ESP32 Tasks
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* On ESP32, `delay(ms)` calls `vTaskDelay(ms / portTICK_PERIOD_MS)`, which **suspends only the calling task**. The FreeRTOS scheduler immediately runs all other ready tasks.
|
||||
* The Arduino `loop()` function runs inside `loopTask`. Calling `delay()` there does *not* block the network stack, audio FFT, LED DMA, nor any other FreeRTOS task.
|
||||
* This differs from ESP8266, where `delay()` stalls the entire system unless `yield()` was called inside.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
- On ESP32, `delay()` is generally allowed, as it helps to efficiently manage CPU usage of all tasks.
|
||||
- On ESP8266, only use `delay()` and `yield()` in the main `loop()` context. If not sure, protect with `if (can_yield()) ...`.
|
||||
- Do *not* use `delay()` in effects (FX.cpp) or in the hot pixel path.
|
||||
- `delay()` on the bus-level is allowed, it might be needed to achieve exact timing in LED drivers.
|
||||
|
||||
### IDLE Watchdog and Custom Tasks on ESP32
|
||||
|
||||
- In arduino-esp32, `yield()` calls `vTaskDelay(0)`, which only switches to tasks at equal or higher priority — the IDLE task (priority 0) is never reached.
|
||||
- **Do not use `yield()` to pace ESP32 tasks or assume it feeds any watchdog**.
|
||||
- **Custom `xTaskCreate()` tasks must call `delay(1)` in their loop, not `yield()`.** Without a real blocking call, the IDLE task is starved. The IDLE watchdog panic is the first visible symptom — but the damage starts earlier: deleted task memory leaks, software timers stop firing, light sleep is disabled, and Wi-Fi/BT idle hooks don't run. Structure custom tasks like this:
|
||||
```cpp
|
||||
// WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog.
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
// CORRECT — delay(1) suspends the task for ≥1 ms, IDLE task runs, IDLE watchdog is fed
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
delay(1); // DO NOT REMOVE — lets IDLE(0) run and feeds its watchdog
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed.
|
||||
- **Watchdog note.** WLED disables the Task Watchdog by default (`WLED_WATCHDOG_TIMEOUT 0` in `wled.h`). When enabled, `esp_task_wdt_reset()` is called at the end of each `loop()` iteration. Long blocking operations inside `loop()` — such as OTA downloads or slow file I/O — must call `esp_task_wdt_reset()` periodically, or be restructured so the main loop is not blocked for longer than the configured timeout.
|
||||
|
||||
## Caveats and Pitfalls
|
||||
|
||||
- **LittleFS filenames**: File paths passed to `file.open()` must not exceed 255 bytes (`LFS_NAME_MAX`). Validate constructed paths (e.g., `/ledmap_` + segment name + `.json`) stay within this limit (assume standard configurations, like WLED_MAX_SEGNAME_LEN = 64).
|
||||
|
||||
- **Float-to-unsigned conversion is undefined behavior when the value is out of range.** Converting a negative `float` directly to an unsigned integer type (`uint8_t`, `uint16_t`, …) is UB per the C++ standard — the Xtensa (ESP32) toolchain may silently wrap, but RISC-V (ESP32-C3/C5/C6/P4) can produce different results due to clamping. Cast through a signed integer first:
|
||||
```cpp
|
||||
// Undefined behavior — avoid:
|
||||
uint8_t angle = 40.74f * atan2f(dy, dx); // negative float → uint8_t is UB
|
||||
|
||||
// Correct — cast through int first:
|
||||
// atan2f returns [-π..+π], scaled ≈ [-128..+128] as int; uint8_t wraps negative ints via 2's complement (e.g. -1 → 255)
|
||||
uint8_t angle = int(40.74f * atan2f(dy, dx)); // float→int (defined), int→uint8_t (defined)
|
||||
```
|
||||
@@ -1,859 +0,0 @@
|
||||
---
|
||||
applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
|
||||
---
|
||||
# ESP-IDF Coding Guide (within arduino-esp32)
|
||||
|
||||
WLED runs on the Arduino-ESP32 framework, which wraps ESP-IDF. Understanding the ESP-IDF layer is essential when writing chip-specific code, managing peripherals, or preparing for the IDF v5.x migration. This guide documents patterns already used in the codebase and best practices derived from Espressif's official examples.
|
||||
|
||||
> **Scope**: This file is an optional review guideline. It applies when touching chip-specific code, peripheral drivers, memory allocation, or platform conditionals.
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
---
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## Identifying the Build Target: `CONFIG_IDF_TARGET_*`
|
||||
|
||||
Use `CONFIG_IDF_TARGET_*` macros to gate chip-specific code at compile time. These are set by the build system and are mutually exclusive — exactly one is defined per build.
|
||||
|
||||
| Macro | Chip | Architecture | Notes |
|
||||
|---|---|---|---|
|
||||
| `CONFIG_IDF_TARGET_ESP32` | ESP32 (classic) | Xtensa dual-core | Primary target. Has DAC, APLL, I2S ADC mode |
|
||||
| `CONFIG_IDF_TARGET_ESP32S2` | ESP32-S2 | Xtensa single-core | Limited peripherals. 13-bit ADC |
|
||||
| `CONFIG_IDF_TARGET_ESP32S3` | ESP32-S3 | Xtensa dual-core | Preferred for large installs. Octal PSRAM, USB-OTG |
|
||||
| `CONFIG_IDF_TARGET_ESP32C3` | ESP32-C3 | RISC-V single-core | Minimal peripherals. RISC-V clamps out-of-range float→unsigned casts |
|
||||
| `CONFIG_IDF_TARGET_ESP32C5` | ESP32-C5 | RISC-V single-core | Wi-Fi 2.4Ghz + 5Ghz, Thread/Zigbee. Future target |
|
||||
| `CONFIG_IDF_TARGET_ESP32C6` | ESP32-C6 | RISC-V single-core | Wi-Fi 6, Thread/Zigbee. Future target |
|
||||
| `CONFIG_IDF_TARGET_ESP32P4` | ESP32-P4 | RISC-V dual-core | High performance. Future target |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Build-time validation
|
||||
WLED validates at compile time that exactly one target is defined and that it is a supported chip (`wled.cpp` lines 39–61). Follow this pattern when adding new chip-specific branches:
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
// classic ESP32 path
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// S3-specific path
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
// RISC-V common path
|
||||
#else
|
||||
#warning "Untested chip — review peripheral availability"
|
||||
#endif
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Guidelines
|
||||
|
||||
- **Always test on the actual chip** before claiming support. Simulators and cross-compilation can hide peripheral differences.
|
||||
- **Prefer `#elif` chains** over nested `#ifdef` for readability.
|
||||
- **Do not use `CONFIG_IDF_TARGET_*` for feature detection.** Use `SOC_*` capability macros instead (see next section). For example, use `SOC_I2S_SUPPORTS_ADC` instead of `CONFIG_IDF_TARGET_ESP32` to check for I2S ADC support.
|
||||
- When a feature must be disabled on certain chips, use explicit `static_assert()` or `#warning` directives so the build clearly reports what is missing.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Capability Detection: `SOC_*` Macros
|
||||
|
||||
`SOC_*` macros (from `soc/soc_caps.h`) describe what the current chip supports. They are the correct way to check for peripheral features — they stay accurate when new chips are added, unlike `CONFIG_IDF_TARGET_*` checks.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Important `SOC_*` macros used in WLED
|
||||
|
||||
| Macro | Type | Used in | Purpose |
|
||||
|---|---|---|---|
|
||||
| `SOC_I2S_NUM` | `int` | `audio_source.h` | Number of I2S peripherals (1 or 2) |
|
||||
| `SOC_I2S_SUPPORTS_ADC` | `bool` | `usermods/audioreactive/audio_source.h` | I2S ADC sampling mode (ESP32 only) |
|
||||
| `SOC_I2S_SUPPORTS_APLL` | `bool` | `usermods/audioreactive/audio_source.h` | Audio PLL for precise sample rates |
|
||||
| `SOC_I2S_SUPPORTS_PDM_RX` | `bool` | `usermods/audioreactive/audio_source.h` | PDM microphone input |
|
||||
| `SOC_ADC_MAX_BITWIDTH` | `int` | `util.cpp` | ADC resolution (12 or 13 bits). Renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5 |
|
||||
| `SOC_ADC_CHANNEL_NUM(unit)` | `int` | `pin_manager.cpp` | ADC channels per unit |
|
||||
| `SOC_UART_NUM` | `int` | `dmx_input.cpp` | Number of UART peripherals |
|
||||
| `SOC_DRAM_LOW` / `SOC_DRAM_HIGH` | `addr` | `util.cpp` | DRAM address boundaries for validation |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Key pitfall
|
||||
`SOC_ADC_MAX_BITWIDTH` (ADC resolution 12 or 13 bits) was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Less commonly used but valuable
|
||||
|
||||
| Macro | Purpose |
|
||||
|---|---|
|
||||
| `SOC_RMT_TX_CANDIDATES_PER_GROUP` | Number of RMT TX channels (varies 2–8 by chip) |
|
||||
| `SOC_LEDC_CHANNEL_NUM` | Number of LEDC (PWM) channels |
|
||||
| `SOC_GPIO_PIN_COUNT` | Total GPIO pin count |
|
||||
| `SOC_DAC_SUPPORTED` | Whether the chip has a DAC (ESP32/S2 only) |
|
||||
| `SOC_SPIRAM_SUPPORTED` | Whether PSRAM interface exists |
|
||||
| `SOC_CPU_CORES_NUM` | Core count (1 or 2) — useful for task pinning decisions |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Best practices
|
||||
|
||||
```cpp
|
||||
// Good: feature-based detection
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
|
||||
#else
|
||||
#warning "PDM microphones not supported on this chip"
|
||||
#endif
|
||||
|
||||
// Avoid: chip-name-based detection
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// happens to be correct today, but breaks when a new chip adds PDM support
|
||||
#endif
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### PSRAM capability macros
|
||||
|
||||
For PSRAM presence, mode, and DMA access patterns:
|
||||
|
||||
| Macro | Meaning |
|
||||
|---|---|
|
||||
| `CONFIG_SPIRAM` / `BOARD_HAS_PSRAM` | PSRAM is present in the build configuration |
|
||||
| `CONFIG_SPIRAM_MODE_QUAD` | Quad-SPI PSRAM (standard, used on ESP32 classic and some S2/S3 boards) |
|
||||
| `CONFIG_SPIRAM_MODE_OCT` | Octal-SPI PSRAM — 8 data lines, DTR mode. Used on ESP32-S3 with octal PSRAM (e.g. N8R8 / N16R8 modules). Reserves GPIO 33–37 for the PSRAM bus — **do not allocate these pins** when this macro is defined. `wled.cpp` uses this to gate GPIO reservation. |
|
||||
| `CONFIG_SPIRAM_MODE_HEX` | Hex-SPI (16-line) PSRAM — future interface on ESP32-P4 running at up to 200 MHz. Used in `json.cpp` to report the PSRAM mode. |
|
||||
| `CONFIG_SOC_PSRAM_DMA_CAPABLE` | PSRAM buffers can be used with DMA (ESP32-S3 with octal PSRAM) |
|
||||
| `CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT` | SPI flash and PSRAM on separate buses (no speed contention) |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
#### Detecting octal/hex flash
|
||||
|
||||
On ESP32-S3 modules with OPI flash (e.g. N8R8 modules where the SPI flash itself runs in Octal-PI mode), the build system sets:
|
||||
|
||||
| Macro | Meaning |
|
||||
|---|---|
|
||||
| `CONFIG_ESPTOOLPY_FLASHMODE_OPI` | Octal-PI flash mode. On S3, implies GPIO 33–37 are used by the flash/PSRAM interface — the same GPIO block as octal PSRAM. `wled.cpp` uses `CONFIG_ESPTOOLPY_FLASHMODE_OPI \|\| (CONFIG_SPIRAM_MODE_OCT && BOARD_HAS_PSRAM)` to decide whether to reserve these GPIOs. `json.cpp` uses this to report the flash mode string as `"🚀OPI"`. |
|
||||
| `CONFIG_ESPTOOLPY_FLASHMODE_HEX` | Hex flash mode (ESP32-P4). Reported as `"🚀🚀HEX"` in `json.cpp`. |
|
||||
|
||||
**Pattern used in WLED** (from `wled.cpp`) to reserve the octal-bus GPIOs on S3:
|
||||
```cpp
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if CONFIG_ESPTOOLPY_FLASHMODE_OPI || (CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM))
|
||||
// S3: GPIO 33-37 are used by the octal PSRAM/flash bus
|
||||
managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} };
|
||||
pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM);
|
||||
#endif
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ESP-IDF Version Conditionals
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Checking the IDF version
|
||||
|
||||
```cpp
|
||||
#include <esp_idf_version.h>
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
// IDF v5+ code path
|
||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
// IDF v4.4+ code path
|
||||
#else
|
||||
// Legacy IDF v3/v4.x path
|
||||
#endif
|
||||
```
|
||||
|
||||
### Key ESP-IDF version thresholds for WLED
|
||||
|
||||
| Version | What changed |
|
||||
|---|---|
|
||||
| **4.0.0** | Filesystem API (`SPIFFS`/`LittleFS`), GPIO driver overhaul |
|
||||
| **4.2.0** | ADC/GPIO API updates; `esp_adc_cal` introduced |
|
||||
| **4.4.0** | I2S driver refactored (legacy API remains); `adc_deprecated.h` headers appear for newer targets |
|
||||
| **4.4.4–4.4.8** | Known I2S channel-swap regression on ESP32 (workaround in `audio_source.h`) |
|
||||
| **5.0.0** | **Major breaking changes** — RMT, I2S, ADC, SPI flash APIs replaced (see migration section) |
|
||||
| **5.1.0** | Matter protocol support; new `esp_flash` API stable |
|
||||
| **5.3+** | arduino-esp32 v3.x compatibility; C6/P4 support |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Guidelines
|
||||
|
||||
- When adding a version guard, **always include a comment** explaining *what* changed and *why* the guard is needed.
|
||||
- Avoid version ranges that silently break — prefer `>=` over exact version matches.
|
||||
- Known regressions should use explicit range guards:
|
||||
```cpp
|
||||
// IDF 4.4.4–4.4.8 swapped I2S left/right channels (fixed in 4.4.9)
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 4)) && \
|
||||
(ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 8))
|
||||
#define I2S_CHANNELS_SWAPPED
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migrating from ESP-IDF v4.4.x to v5.x
|
||||
|
||||
The jump from IDF v4.4 (arduino-esp32 v2.x) to IDF v5.x (arduino-esp32 v3.x) is the largest API break in ESP-IDF history. This section documents the critical changes and recommended migration patterns based on the upstream WLED `V5-C6` branch (`https://github.com/wled/WLED/tree/V5-C6`). Note: WLED has not yet migrated to IDF v5 — these patterns prepare for the future migration.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Compiler changes
|
||||
|
||||
IDF v5.x ships a much newer GCC toolchain. Key versions:
|
||||
|
||||
| ESP-IDF | GCC | C++ default | Notes |
|
||||
|---|---|---|---|
|
||||
| 4.4.x (current) | **8.4.0** | C++17 (gnu++17) | Xtensa + RISC-V |
|
||||
| 5.1–5.3 | **13.2** | C++20 (gnu++2b) | Significant warning changes |
|
||||
| 5.4–5.5 | **14.2** | C++23 (gnu++2b) | Latest; stricter diagnostics |
|
||||
|
||||
Notable behavioral differences:
|
||||
|
||||
| Change | Impact | Action |
|
||||
|---|---|---|
|
||||
| Stricter `-Werror=enum-conversion` | Implicit int-to-enum casts now error | Use explicit `static_cast<>` or typed enums |
|
||||
| C++20/23 features available | `consteval`, `concepts`, `std::span`, `std::expected` | Use judiciously — ESP8266 builds still require GCC 10.x with C++17 |
|
||||
| `-Wdeprecated-declarations` enforced | Deprecated API calls become warnings/errors | Migrate to new APIs (see below) |
|
||||
| `-Wdangling-reference` (GCC 13+) | Warns when a reference binds to a temporary that will be destroyed | Fix the lifetime issue; do not suppress the warning |
|
||||
| `-fno-common` default (GCC 12+) | Duplicate tentative definitions across translation units cause linker errors | Use `extern` declarations in headers, define in exactly one `.cpp` |
|
||||
| RISC-V codegen improvements | C3/C6/P4 benefit from better register allocation | No action needed — automatic |
|
||||
|
||||
### C++ language features: GCC 8 → GCC 14
|
||||
|
||||
The jump from GCC 8.4 to GCC 14.2 spans six major compiler releases. This section lists features that become available and patterns that need updating.
|
||||
|
||||
#### Features safe to use after migration
|
||||
|
||||
These work in GCC 13+/14+ but **not** in GCC 8.4. Guard with `#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)` if the code must compile on both IDF v4 and v5.
|
||||
|
||||
| Feature | Standard | Example | Benefit |
|
||||
|---|---|---|---|
|
||||
| Designated initializers (C++20) | C++20 | `gpio_config_t cfg = { .mode = GPIO_MODE_OUTPUT };` | Already used as a GNU extension in GCC 8; becomes standard and portable in C++20 |
|
||||
| `[[likely]]` / `[[unlikely]]` | C++20 | `if (err != ESP_OK) [[unlikely]] { ... }` | Hints for branch prediction; useful in hot paths |
|
||||
| `[[nodiscard("reason")]]` | C++20 | `[[nodiscard("leak if ignored")]] void* allocBuffer();` | Enforces checking return values — helpful for `esp_err_t` wrappers |
|
||||
| `std::span<T>` | C++20 | `void process(std::span<uint8_t> buf)` | Safe, non-owning view of contiguous memory — replaces raw pointer + length pairs |
|
||||
| `consteval` | C++20 | `consteval uint32_t packColor(...)` | Guarantees compile-time evaluation; useful for color constants |
|
||||
| `constinit` | C++20 | `constinit static int counter = 0;` | Prevents static initialization order fiasco |
|
||||
| Concepts / `requires` | C++20 | `template<typename T> requires std::integral<T>` | Clearer constraints than SFINAE; improves error messages |
|
||||
| Three-way comparison (`<=>`) | C++20 | `auto operator<=>(const Version&) const = default;` | Less boilerplate for comparable types |
|
||||
| `std::bit_cast` | C++20 | `float f = std::bit_cast<float>(uint32_val);` | Type-safe reinterpretation — replaces `memcpy` or `union` tricks |
|
||||
| `if consteval` | C++23 | `if consteval { /* compile-time */ } else { /* runtime */ }` | Cleaner than `std::is_constant_evaluated()` |
|
||||
| `std::expected<T, E>` | C++23 | `std::expected<int, esp_err_t> readSensor()` | Monadic error handling — cleaner than returning error codes |
|
||||
| `std::to_underlying` | C++23 | `auto val = std::to_underlying(myEnum);` | Replaces `static_cast<int>(myEnum)` |
|
||||
|
||||
#### Features already available in GCC 8 (C++17)
|
||||
|
||||
These work on both IDF v4.4 and v5.x — prefer them now:
|
||||
|
||||
| Feature | Example | Notes |
|
||||
|---|---|---|
|
||||
| `if constexpr` | `if constexpr (sizeof(T) == 4) { ... }` | Compile-time branching; already used in WLED |
|
||||
| `std::optional<T>` | `std::optional<uint8_t> pin;` | Nullable value without sentinel values like `-1` |
|
||||
| `std::string_view` | `void log(std::string_view msg)` | Non-owning, non-allocating string reference |
|
||||
| Structured bindings | `auto [err, value] = readSensor();` | Useful with `std::pair` / `std::tuple` returns |
|
||||
| Fold expressions | `(addSegment(args), ...);` | Variadic template expansion |
|
||||
| Inline variables | `inline constexpr int MAX_PINS = 50;` | Avoids ODR issues with header-defined constants |
|
||||
| `[[maybe_unused]]` | `[[maybe_unused]] int debug_only = 0;` | Suppresses unused-variable warnings cleanly |
|
||||
| `[[fallthrough]]` | `case 1: doA(); [[fallthrough]]; case 2:` | Documents intentional switch fallthrough |
|
||||
| Nested namespaces | `namespace wled::audio { }` | Shorter than nested `namespace` blocks |
|
||||
|
||||
#### Patterns that break or change behavior
|
||||
|
||||
| Pattern | GCC 8 behavior | GCC 14 behavior | Fix |
|
||||
|---|---|---|---|
|
||||
| `int x; enum E e = x;` | Warning (often ignored) | Error with `-Werror=enum-conversion` | `E e = static_cast<E>(x);` |
|
||||
| `int g;` in two `.cpp` files | Both compile, linker merges (tentative definition) | Error: multiple definitions (`-fno-common`) | `extern int g;` in header, `int g;` in one `.cpp` |
|
||||
| `const char* ref = std::string(...).c_str();` | Silent dangling pointer | Warning (`-Wdangling-reference`) | Extend lifetime: store the `std::string` in a local variable |
|
||||
| `register int x;` | Accepted (ignored) | Warning or error (`register` removed in C++17) | Remove `register` keyword |
|
||||
| Narrowing in aggregate init | Warning | Error | Use explicit cast or wider type |
|
||||
| Implicit `this` capture in lambdas | Accepted in `[=]` | Deprecated warning; error in C++20 mode | Use `[=, this]` or `[&]` |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
#### Recommendations
|
||||
|
||||
- **Do not raise the minimum C++ standard yet.** WLED must still build on IDF v4.4 (GCC 8.4, C++17). Use `#if __cplusplus > 201703L` to gate C++20 features.
|
||||
- **Mark intentional fallthrough** with `[[fallthrough]]` — GCC 14 warns on unmarked fallthrough by default.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
- **Prefer `std::optional` over sentinel values** (e.g., `-1` for "no pin") in new code — it works on both compilers.
|
||||
- **Use `std::string_view`** for read-only string parameters instead of `const char*` or `const String&` — zero-copy and works on GCC 8+.
|
||||
- **Avoid raw `union` type punning** — prefer `memcpy` (GCC 8) or `std::bit_cast` (GCC 13+) for strict-aliasing safety.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Deprecated and removed APIs
|
||||
|
||||
#### RMT (Remote Control Transceiver)
|
||||
|
||||
The legacy `rmt_*` functions are removed in IDF v5. Do not introduce new legacy RMT calls.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
The new API is channel-based:
|
||||
|
||||
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|
||||
|---|---|---|
|
||||
| `rmt_config()` + `rmt_driver_install()` | `rmt_new_tx_channel()` / `rmt_new_rx_channel()` | Channels are now objects |
|
||||
| `rmt_write_items()` | `rmt_transmit()` with encoder | Requires `rmt_encoder_t` |
|
||||
| `rmt_set_idle_level()` | Configure in channel config | Set at creation time |
|
||||
| `rmt_item32_t` | `rmt_symbol_word_t` | Different struct layout |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
**WLED impact**: NeoPixelBus LED output and IR receiver both use legacy RMT. The upstream `V5-C6` branch adds `-D WLED_USE_SHARED_RMT` and disables IR until the library is ported.
|
||||
|
||||
#### I2S (Inter-IC Sound)
|
||||
|
||||
Legacy `i2s_driver_install()` + `i2s_read()` API is deprecated. When touching audio source code, wrap legacy I2S init and reading in `#if ESP_IDF_VERSION_MAJOR < 5` / `#else`.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
The new API uses channel handles:
|
||||
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|
||||
|---|---|---|
|
||||
| `i2s_driver_install()` | `i2s_channel_init_std_mode()` | Separate STD/PDM/TDM modes |
|
||||
| `i2s_set_pin()` | Pin config in `i2s_std_gpio_config_t` | Set at init time |
|
||||
| `i2s_read()` | `i2s_channel_read()` | Uses channel handle |
|
||||
| `i2s_set_clk()` | `i2s_channel_reconfig_std_clk()` | Reconfigure running channel |
|
||||
| `i2s_config_t` | `i2s_std_config_t` | Separate config for each mode |
|
||||
|
||||
**Migration pattern** (from Espressif examples):
|
||||
```cpp
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#include "driver/i2s_std.h"
|
||||
i2s_chan_handle_t rx_handle;
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(22050),
|
||||
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = { .din = GPIO_NUM_32, .mclk = I2S_GPIO_UNUSED, ... },
|
||||
};
|
||||
i2s_channel_init_std_mode(rx_handle, &std_cfg);
|
||||
i2s_channel_enable(rx_handle);
|
||||
#else
|
||||
// Legacy i2s_driver_install() path
|
||||
#endif
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
**WLED impact**: The audioreactive usermod (`audio_source.h`) heavily uses legacy I2S. Migration requires rewriting the `I2SSource` class for channel-based API.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
#### ADC (Analog-to-Digital Converter)
|
||||
|
||||
Legacy `adc1_get_raw()` and `esp_adc_cal_*` are deprecated:
|
||||
|
||||
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|
||||
|---|---|---|
|
||||
| `adc1_config_width()` + `adc1_get_raw()` | `adc_oneshot_new_unit()` + `adc_oneshot_read()` | Object-based API |
|
||||
| `esp_adc_cal_characterize()` | `adc_cali_create_scheme_*()` | Calibration is now scheme-based |
|
||||
| `adc_continuous_*` (old) | `adc_continuous_*` (restructured) | Config struct changes |
|
||||
|
||||
#### SPI Flash
|
||||
|
||||
| IDF v4 (legacy) | IDF v5 (new) |
|
||||
|---|---|
|
||||
| `spi_flash_read()` | `esp_flash_read()` |
|
||||
| `spi_flash_write()` | `esp_flash_write()` |
|
||||
| `spi_flash_erase_range()` | `esp_flash_erase_region()` |
|
||||
|
||||
WLED already has a compatibility shim in `ota_update.cpp` that maps old names to new ones.
|
||||
|
||||
#### GPIO
|
||||
|
||||
| IDF v4 (legacy) | IDF v5 (recommended) |
|
||||
|---|---|
|
||||
| `gpio_pad_select_gpio()` | `esp_rom_gpio_pad_select_gpio()` (or use `gpio_config()`) |
|
||||
| `gpio_set_direction()` + `gpio_set_pull_mode()` | `gpio_config()` with `gpio_config_t` struct |
|
||||
|
||||
### Features disabled in IDF v5 builds
|
||||
|
||||
The upstream `V5-C6` branch explicitly disables features with incompatible library dependencies:
|
||||
|
||||
```ini
|
||||
# platformio.ini [esp32_idf_V5]
|
||||
-D WLED_DISABLE_INFRARED # IR library uses legacy RMT
|
||||
-D WLED_DISABLE_MQTT # AsyncMqttClient incompatible with IDF v5
|
||||
-D ESP32_ARDUINO_NO_RGB_BUILTIN # Prevents RMT driver conflict with built-in LED
|
||||
-D WLED_USE_SHARED_RMT # Use new shared RMT driver for NeoPixel output
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Migration checklist for new code
|
||||
|
||||
1. **Never use a removed API without a version guard.** Always provide both old and new paths, or disable the feature on IDF v5.
|
||||
2. **Test on both IDF v4.4 and v5.x builds** if the code must be backward-compatible.
|
||||
3. **Prefer the newer API** when writing new code — wrap the old API in an `#else` block.
|
||||
4. **Mark migration TODOs** with `// TODO(idf5):` so they are easy to find later.
|
||||
|
||||
---
|
||||
|
||||
## Memory Management: `heap_caps_*` Best Practices
|
||||
|
||||
ESP32 has multiple memory regions with different capabilities. Using the right allocator is critical for performance and stability.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Memory regions
|
||||
|
||||
| Region | Flag | Speed | DMA | Size | Use for |
|
||||
|---|---|---|---|---|---|
|
||||
| DRAM | `MALLOC_CAP_INTERNAL \| MALLOC_CAP_8BIT` | Fast | Yes (ESP32) | 200–320 KB | Hot-path buffers, task stacks, small allocations |
|
||||
| IRAM | `MALLOC_CAP_EXEC` | Fastest | No | 32–128 KB | Code (automatic via `IRAM_ATTR`) |
|
||||
| PSRAM (SPIRAM) | `MALLOC_CAP_SPIRAM \| MALLOC_CAP_8BIT` | Slower | Chip-dependent | 2–16 MB | Large buffers, JSON documents, image data |
|
||||
| RTC RAM | `MALLOC_CAP_RTCRAM` | Moderate | No | 8 KB | Data surviving deep sleep; small persistent buffers |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### WLED allocation wrappers
|
||||
|
||||
WLED provides convenience wrappers with automatic fallback. **Always prefer these over raw `heap_caps_*` calls**:
|
||||
|
||||
| Function | Allocation preference | Use case |
|
||||
|---|---|---|
|
||||
| `d_malloc(size)` | RTC → DRAM → PSRAM | General-purpose; prefers fast memory |
|
||||
| `d_calloc(n, size)` | Same as `d_malloc`, zero-initialized | Arrays, structs |
|
||||
| `p_malloc(size)` | PSRAM → DRAM | Large buffers; prefers abundant memory |
|
||||
| `p_calloc(n, size)` | Same as `p_malloc`, zero-initialized | Large arrays |
|
||||
| `d_malloc_only(size)` | RTC → DRAM (no PSRAM fallback) | DMA buffers, time-critical data |
|
||||
|
||||
### PSRAM guidelines
|
||||
|
||||
- **Check availability**: always test `psramFound()` before assuming PSRAM is present.
|
||||
- **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable** — use `d_malloc_only()` to allocate DMA buffers in DRAM only. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.
|
||||
- **JSON documents**: use the `PSRAMDynamicJsonDocument` allocator (defined in `wled.h`) to put large JSON documents in PSRAM:
|
||||
```cpp
|
||||
PSRAMDynamicJsonDocument doc(16384); // allocated in PSRAM if available
|
||||
```
|
||||
- **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity.
|
||||
- **Heap validation**: use `d_measureHeap()` and `d_measureContiguousFreeHeap()` to monitor remaining DRAM. Allocations that would drop free DRAM below `MIN_HEAP_SIZE` should go to PSRAM instead.
|
||||
- **Performance**: Keep hot-path data in DRAM. Prefer PSRAM for capacity-oriented buffers and monitor contiguous DRAM headroom.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
PSRAM access is up to 15× slower than DRAM on ESP32, 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel at 80 MHz (120 MHz is possible with CONFIG_SPIRAM_SPEED_120M, which requires enabling experimental ESP-IDF features). On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. Keep hot-path data in DRAM regardless, but consider that ESP32 often crashes when the largest DRAM chunk gets below 10 KB.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Pattern: preference-based allocation
|
||||
|
||||
When you need a buffer that works on boards with or without PSRAM:
|
||||
|
||||
```cpp
|
||||
// Prefer PSRAM for large buffers, fall back to DRAM
|
||||
uint8_t* buf = (uint8_t*)heap_caps_malloc_prefer(bufSize, 2,
|
||||
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, // first choice: PSRAM
|
||||
MALLOC_CAP_DEFAULT); // fallback: any available
|
||||
// Or simply:
|
||||
uint8_t* buf = (uint8_t*)p_malloc(bufSize);
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
---
|
||||
|
||||
## I2S Audio: Best Practices
|
||||
|
||||
The audioreactive usermod uses I2S for microphone input. Key patterns:
|
||||
|
||||
### Port selection
|
||||
|
||||
```cpp
|
||||
constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0;
|
||||
// I2S_NUM_1 has limitations: no MCLK routing, no ADC support, no PDM support
|
||||
```
|
||||
|
||||
Always use `I2S_NUM_0` unless you have a specific reason and have verified support on all target chips.
|
||||
|
||||
### DMA buffer tuning
|
||||
|
||||
DMA buffer size controls latency vs. reliability:
|
||||
|
||||
| Scenario | `dma_buf_count` | `dma_buf_len` | Latency | Notes |
|
||||
|---|---|---|---|---|
|
||||
| With HUB75 matrix | 18 | 128 | ~100 ms | Higher count prevents I2S starvation during matrix DMA |
|
||||
| Without PSRAM | 24 | 128 | ~140 ms | More buffers compensate for slower interrupt response |
|
||||
| Default | 8 | 128 | ~46 ms | Acceptable for most setups |
|
||||
|
||||
### Interrupt priority
|
||||
|
||||
Choose interrupt priority based on coexistence with other drivers:
|
||||
|
||||
```cpp
|
||||
#ifdef WLED_ENABLE_HUB75MATRIX
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1, // level 1 (lowest) to avoid starving HUB75
|
||||
#else
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_LEVEL3, // accept level 2 or 3 (allocator picks available)
|
||||
#endif
|
||||
```
|
||||
|
||||
### APLL (Audio PLL) usage
|
||||
|
||||
The ESP32 has an audio PLL for precise sample rates. Rules:
|
||||
|
||||
- Enable APLL when an MCLK pin is provided and precision matters.
|
||||
- **Disable APLL** when Ethernet or HUB75 is active — they also use the APLL.
|
||||
- APLL is broken on ESP32 revision 0 silicon.
|
||||
- Not all chips have APLL — gate with `SOC_I2S_SUPPORTS_APLL`.
|
||||
|
||||
```cpp
|
||||
#if !defined(SOC_I2S_SUPPORTS_APLL)
|
||||
_config.use_apll = false;
|
||||
#elif defined(WLED_USE_ETHERNET) || defined(WLED_ENABLE_HUB75MATRIX)
|
||||
_config.use_apll = false; // APLL conflict
|
||||
#endif
|
||||
```
|
||||
|
||||
### PDM microphone caveats
|
||||
|
||||
- Not supported on ESP32-C3 (`SOC_I2S_SUPPORTS_PDM_RX` not defined).
|
||||
- ESP32-S3 PDM has known issues: sample rate at 50% of expected, very low amplitude.
|
||||
- **16-bit data width**: Espressif's IDF documentation states that in PDM mode the data unit width is always 16 bits, regardless of the configured `bits_per_sample`.
|
||||
- See [espressif/esp-idf#8660](https://github.com/espressif/esp-idf/issues/8660) for the upstream issue.
|
||||
- **Flag `bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT` in PDM mode** — this causes the S3 low-amplitude symptom.
|
||||
- No clock pin (`I2S_CKPIN = -1`) triggers PDM mode in WLED.
|
||||
|
||||
---
|
||||
|
||||
## HUB75 LED Matrix: Best Practices
|
||||
|
||||
WLED uses the `ESP32-HUB75-MatrixPanel-I2S-DMA` library for HUB75 matrix output.
|
||||
|
||||
### Chip-specific panel limits
|
||||
|
||||
```cpp
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
|
||||
maxChainLength = 6; // S3 + PSRAM: up to 6 panels (DMA-capable PSRAM)
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
maxChainLength = 2; // S2: limited DMA channels
|
||||
#else
|
||||
maxChainLength = 4; // Classic ESP32: default
|
||||
#endif
|
||||
```
|
||||
|
||||
### Color depth vs. pixel count
|
||||
|
||||
The driver dynamically reduces color depth for larger displays to stay within DMA buffer limits:
|
||||
|
||||
| Pixel count | Color depth | Bits per pixel |
|
||||
|---|---|---|
|
||||
| ≤ `MAX_PIXELS_10BIT` | 10-bit (30-bit color) | High quality (experimental) |
|
||||
| ≤ `MAX_PIXELS_8BIT` | 8-bit (24-bit color) | Full quality |
|
||||
| ≤ `MAX_PIXELS_6BIT` | 6-bit (18-bit color) | Slight banding |
|
||||
| ≤ `MAX_PIXELS_4BIT` | 4-bit (12-bit color) | Visible banding |
|
||||
| larger | 3-bit (9-bit color) | Minimal color range |
|
||||
|
||||
### Resource conflicts
|
||||
|
||||
- **APLL**: HUB75 I2S DMA uses the APLL. Disable APLL in the audio I2S driver when HUB75 is active.
|
||||
- **I2S peripheral**: HUB75 uses `I2S_NUM_1` (or `I2S_NUM_0` on single-I2S chips). Audio must use the other port.
|
||||
- **Pin count**: HUB75 requires 13–14 GPIO pins. On ESP32-S2 this severely limits remaining GPIO.
|
||||
- **Reboot required**: on ESP32-S3, changing HUB75 driver options requires a full reboot — the I2S DMA cannot be reconfigured at runtime.
|
||||
|
||||
---
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## GPIO Best Practices
|
||||
|
||||
### Prefer `gpio_config()` over individual calls
|
||||
|
||||
```cpp
|
||||
// Preferred: single struct-based configuration
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << pin),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// Avoid: multiple separate calls (more error-prone, deprecated in IDF v5)
|
||||
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
|
||||
gpio_set_pull_mode(pin, GPIO_FLOATING);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Pin manager integration
|
||||
|
||||
Always allocate pins through WLED's `pinManager` before using GPIO APIs:
|
||||
|
||||
```cpp
|
||||
if (!pinManager.allocatePin(myPin, true, PinOwner::UM_MyUsermod)) {
|
||||
return; // pin in use by another module
|
||||
}
|
||||
// Now safe to configure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timer Best Practices
|
||||
|
||||
### Microsecond timing
|
||||
|
||||
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
```cpp
|
||||
#include <esp_timer.h>
|
||||
int64_t now_us = esp_timer_get_time(); // monotonic, not affected by NTP
|
||||
```
|
||||
|
||||
> **Note**: In arduino-esp32, both `millis()` and `micros()` are thin wrappers around `esp_timer_get_time()` — they share the same monotonic clock source. Prefer the direct call when you need the full 64-bit value or ISR-safe access without truncation:
|
||||
> ```cpp
|
||||
> // arduino-esp32 internals (cores/esp32/esp32-hal-misc.c):
|
||||
> // unsigned long micros() { return (unsigned long)(esp_timer_get_time()); }
|
||||
> // unsigned long millis() { return (unsigned long)(esp_timer_get_time() / 1000ULL); }
|
||||
> ```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Periodic timers
|
||||
|
||||
For periodic tasks with sub-millisecond precision, use `esp_timer`:
|
||||
|
||||
```cpp
|
||||
esp_timer_handle_t timer;
|
||||
esp_timer_create_args_t args = {
|
||||
.callback = myCallback,
|
||||
.arg = nullptr,
|
||||
.dispatch_method = ESP_TIMER_TASK, // run in timer task (not ISR)
|
||||
.name = "my_timer",
|
||||
};
|
||||
esp_timer_create(&args, &timer);
|
||||
esp_timer_start_periodic(timer, 1000); // 1 ms period
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Always prefer `ESP_TIMER_TASK` dispatch over `ESP_TIMER_ISR` unless you need ISR-level latency — ISR callbacks have severe restrictions (no logging, no heap allocation, no FreeRTOS API calls).
|
||||
|
||||
### Precision waiting: coarse delay then spin-poll
|
||||
|
||||
When waiting for a precise future deadline (e.g., FPS limiting, protocol timing), avoid spinning the entire duration — that wastes CPU and starves other tasks. Instead, yield to FreeRTOS while time allows, then spin only for the final window.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Wait until 'target_us' (a micros() / esp_timer_get_time() timestamp)
|
||||
long time_to_wait = (long)(target_us - micros());
|
||||
// Coarse phase: yield to FreeRTOS while we have more than ~2 ms remaining.
|
||||
// vTaskDelay(1) suspends the task for one RTOS tick, letting other task run freely.
|
||||
while (time_to_wait > 2000) {
|
||||
vTaskDelay(1);
|
||||
time_to_wait = (long)(target_us - micros());
|
||||
}
|
||||
// Fine phase: busy-poll the last ≤2 ms for microsecond accuracy.
|
||||
// micros() wraps esp_timer_get_time() so this is low-overhead.
|
||||
while ((long)(target_us - micros()) > 0) { /* spin */ }
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
> The threshold (2000 µs as an example) should be at least one RTOS tick (default 1 ms on ESP32) plus some margin. A value of 1500–3000 µs works well in practice.
|
||||
|
||||
---
|
||||
|
||||
## ADC Best Practices
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Version-aware ADC code
|
||||
|
||||
ADC is one of the most fragmented APIs across IDF versions:
|
||||
|
||||
```cpp
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
// IDF v5: oneshot driver
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
adc_oneshot_unit_handle_t adc_handle;
|
||||
adc_oneshot_unit_init_cfg_t unit_cfg = { .unit_id = ADC_UNIT_1 };
|
||||
adc_oneshot_new_unit(&unit_cfg, &adc_handle);
|
||||
#else
|
||||
// IDF v4: legacy driver
|
||||
#include "driver/adc.h"
|
||||
#include "esp_adc_cal.h"
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
int raw = adc1_get_raw(ADC1_CHANNEL_0);
|
||||
#endif
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Bit width portability
|
||||
|
||||
Not all chips have 12-bit ADC. `SOC_ADC_MAX_BITWIDTH` reports the maximum resolution (12 or 13 bits). Note that in IDF v5, this macro was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH`. Write version-aware guards:
|
||||
|
||||
```cpp
|
||||
// IDF v4: SOC_ADC_MAX_BITWIDTH IDF v5: CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
|
||||
#if defined(CONFIG_SOC_ADC_RTC_MAX_BITWIDTH) // IDF v5+
|
||||
#define MY_ADC_MAX_BITWIDTH CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
|
||||
#elif defined(SOC_ADC_MAX_BITWIDTH) // IDF v4
|
||||
#define MY_ADC_MAX_BITWIDTH SOC_ADC_MAX_BITWIDTH
|
||||
#else
|
||||
#define MY_ADC_MAX_BITWIDTH 12 // safe fallback
|
||||
#endif
|
||||
|
||||
#if MY_ADC_MAX_BITWIDTH == 13
|
||||
adc1_config_width(ADC_WIDTH_BIT_13); // ESP32-S2
|
||||
#else
|
||||
adc1_config_width(ADC_WIDTH_BIT_12); // ESP32, S3, C3, etc.
|
||||
#endif
|
||||
```
|
||||
|
||||
WLED's `util.cpp` uses the IDF v4 form (`SOC_ADC_MAX_BITWIDTH`) — this will need updating when the codebase migrates to IDF v5.
|
||||
|
||||
---
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## RMT Best Practices
|
||||
|
||||
### Current usage in WLED
|
||||
|
||||
RMT drives NeoPixel LED output (via NeoPixelBus) and IR receiver input. Both use the legacy API that is removed in IDF v5.
|
||||
|
||||
### Migration notes
|
||||
|
||||
- The upstream `V5-C6` branch uses `-D WLED_USE_SHARED_RMT` to switch to the new RMT driver for NeoPixel output.
|
||||
- IR is disabled on IDF v5 until the IR library is ported.
|
||||
- New chips (C6, P4) have different RMT channel counts — use `SOC_RMT_TX_CANDIDATES_PER_GROUP` to check availability.
|
||||
- The new RMT API requires an "encoder" object (`rmt_encoder_t`) to translate data formats — this is more flexible but requires more setup code.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
---
|
||||
|
||||
## Espressif Best Practices (from official examples)
|
||||
|
||||
### Error handling
|
||||
|
||||
Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Initialization — crash early on failure
|
||||
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &config, 0, nullptr));
|
||||
|
||||
// Runtime — log and recover
|
||||
esp_err_t err = i2s_read(I2S_NUM_0, buf, len, &bytes_read, portMAX_DELAY);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("I2S read failed: %s\n", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
For situations between these two extremes — where you want the `ESP_ERROR_CHECK` formatted log message (file, line, error name) but must not abort — use `ESP_ERROR_CHECK_WITHOUT_ABORT()`.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Logs in the same format as ESP_ERROR_CHECK, but returns the error code instead of aborting.
|
||||
// Useful for non-fatal driver calls where you want visibility without crashing.
|
||||
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_set_clk(AR_I2S_PORT, rate, bits, ch));
|
||||
if (err != ESP_OK) return; // handle as needed
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Logging
|
||||
|
||||
WLED uses its own logging macros — **not** `ESP_LOGx()`. For application-level code, always use the WLED macros defined in `wled.h`:
|
||||
|
||||
| Macro family | Defined in | Controlled by | Use for |
|
||||
|---|---|---|---|
|
||||
| `DEBUG_PRINT` / `DEBUG_PRINTLN` / `DEBUG_PRINTF` | `wled.h` | `WLED_DEBUG` build flag | Development/diagnostic output; compiled out in release builds |
|
||||
|
||||
All of these wrap `Serial` output through the `DEBUGOUT` / `DEBUGOUTLN` / `DEBUGOUTF` macros.
|
||||
|
||||
**Exception — low-level driver code**: When writing code that interacts directly with ESP-IDF APIs (e.g., I2S initialization, RMT setup), use `ESP_LOGx()` macros instead. They support tag-based filtering and compile-time log level control:
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
static const char* TAG = "my_module";
|
||||
ESP_LOGI(TAG, "Initialized with %d buffers", count);
|
||||
ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM");
|
||||
ESP_LOGE(TAG, "Failed to allocate %u bytes", size);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Task creation and pinning
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
On dual-core chips (ESP32, S3, P4), pin latency-sensitive tasks to a specific core:
|
||||
|
||||
```cpp
|
||||
xTaskCreatePinnedToCore(
|
||||
audioTask, // function
|
||||
"audio", // name
|
||||
4096, // stack size
|
||||
nullptr, // parameter
|
||||
5, // priority (higher = more important)
|
||||
&audioTaskHandle, // handle
|
||||
0 // core ID (0 = protocol core, 1 = app core)
|
||||
);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Guidelines:
|
||||
- Pin network/protocol tasks to core 0 (where Wi-Fi runs).
|
||||
- Pin real-time tasks (audio, LED output) to core 1.
|
||||
- On single-core chips (S2, C3, C5, C6), only core 0 exists — pinning to core 1 will fail. Use `SOC_CPU_CORES_NUM > 1` guards or `tskNO_AFFINITY`.
|
||||
- Use `SOC_CPU_CORES_NUM` to conditionally pin tasks:
|
||||
```cpp
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
xTaskCreatePinnedToCore(audioTask, "audio", 4096, nullptr, 5, &handle, 1);
|
||||
#else
|
||||
xTaskCreate(audioTask, "audio", 4096, nullptr, 5, &handle);
|
||||
#endif
|
||||
```
|
||||
|
||||
**Tip: use xTaskCreateUniversal()** - from arduino-esp32 - to avoid the conditional on `SOC_CPU_CORES_NUM`. This function has the same signature as ``xTaskCreatePinnedToCore()``, but automatically falls back to ``xTaskCreate()`` on single-core MCUs.
|
||||
|
||||
### `delay()`, `yield()`, and the IDLE task
|
||||
|
||||
FreeRTOS on ESP32 is **preemptive** — all tasks are scheduled by priority regardless of `yield()` calls. This is fundamentally different from ESP8266 cooperative multitasking.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
| Call | What it does | Reaches IDLE (priority 0)? |
|
||||
|---|---|---|
|
||||
| `delay(ms)` / `vTaskDelay(ticks)` | Suspends calling task; scheduler runs all other ready tasks | ✅ Yes |
|
||||
| `yield()` / `vTaskDelay(0)` | Hint to switch to tasks at **equal or higher** priority only | ❌ No |
|
||||
| `taskYIELD()` | Same as `vTaskDelay(0)` | ❌ No |
|
||||
| Blocking API (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) | Suspends task until event or timeout; IDLE runs freely | ✅ Yes |
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
**`delay()` in `loopTask` is safe.** Arduino's `loop()` runs inside `loopTask`. Calling `delay()` suspends only `loopTask` — all other FreeRTOS tasks (Wi-Fi stack, audio FFT, LED DMA) continue uninterrupted on either core.
|
||||
|
||||
**`yield()` does not yield to IDLE.** Any task that loops with only `yield()` calls will starve the IDLE task, causing the IDLE watchdog to fire. Always use `delay(1)` (or a blocking FreeRTOS call) in tight task loops. Note: WLED redefines `yield()` as an empty macro on ESP32 WLEDMM_FASTPATH builds.
|
||||
|
||||
#### Why the IDLE task is not optional
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
The FreeRTOS IDLE task (one per core on dual-core ESP32 and ESP32-S3; single instance on single-core chips) is not idle in the casual sense — it performs essential system housekeeping:
|
||||
|
||||
- **Frees deleted task memory**: when a task calls `vTaskDelete()`, the IDLE task reclaims its TCB and stack. Without IDLE running, deleted tasks leak memory permanently.
|
||||
- **Runs the idle hook**: when `configUSE_IDLE_HOOK = 1`, the IDLE task calls `vApplicationIdleHook()` on every iteration — some ESP-IDF components register low-priority background work here.
|
||||
- **Implements tickless idle / light sleep**: on battery-powered devices, IDLE is the entry point for low-power sleep. A permanently starved IDLE task disables light sleep entirely.
|
||||
- **Runs registered idle hooks**: ESP-IDF components register callbacks via `esp_register_freertos_idle_hook()` (e.g., Wi-Fi background maintenance, Bluetooth housekeeping). These only fire when IDLE runs.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
In short: **starving IDLE corrupts memory cleanup, breaks background activities, disables low-power sleep, and prevents Wi-Fi/BT maintenance.** The IDLE watchdog panic is a symptom — the real damage happens before the watchdog fires.
|
||||
|
||||
### Watchdog management
|
||||
|
||||
Long-running operations may trigger the task watchdog. Feed it explicitly:
|
||||
|
||||
```cpp
|
||||
#include <esp_task_wdt.h>
|
||||
esp_task_wdt_reset(); // feed the watchdog in long loops
|
||||
```
|
||||
|
||||
For tasks that intentionally block for extended periods, consider subscribing/unsubscribing from the TWDT:
|
||||
|
||||
```cpp
|
||||
esp_task_wdt_delete(NULL); // remove current task from TWDT (IDF v4.4)
|
||||
// ... long blocking operation ...
|
||||
esp_task_wdt_add(NULL); // re-register
|
||||
```
|
||||
|
||||
> **IDF v5 note**: In IDF v5, `esp_task_wdt_add()` and `esp_task_wdt_delete()` require an explicit `TaskHandle_t`. Use `xTaskGetCurrentTaskHandle()` instead of `NULL`.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
---
|
||||
|
||||
## Quick Reference: IDF v4 → v5 API Mapping
|
||||
|
||||
| Component | IDF v4 Header | IDF v5 Header | Key Change |
|
||||
|---|---|---|---|
|
||||
| I2S | `driver/i2s.h` | `driver/i2s_std.h` | Channel-based API |
|
||||
| ADC (oneshot) | `driver/adc.h` | `esp_adc/adc_oneshot.h` | Unit/channel handles |
|
||||
| ADC (calibration) | `esp_adc_cal.h` | `esp_adc/adc_cali.h` | Scheme-based calibration |
|
||||
| RMT | `driver/rmt.h` | `driver/rmt_tx.h` / `rmt_rx.h` | Encoder-based transmit |
|
||||
| SPI Flash | `spi_flash.h` | `esp_flash.h` | `esp_flash_*` functions |
|
||||
| GPIO | `driver/gpio.h` | `driver/gpio.h` | `gpio_pad_select_gpio()` removed |
|
||||
| Timer | `driver/timer.h` | `driver/gptimer.h` | General-purpose timer handles |
|
||||
| PCNT | `driver/pcnt.h` | `driver/pulse_cnt.h` | Handle-based API |
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
applyTo: "wled00/data/**"
|
||||
---
|
||||
# Web UI Coding Conventions
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent **HTML and JavaScript** with **tabs**
|
||||
- Indent **CSS** with **tabs**
|
||||
|
||||
## JavaScript Style
|
||||
|
||||
- **camelCase** for functions and variables: `gId()`, `selectedFx`, `currentPreset`
|
||||
- Abbreviated helpers are common: `d` for `document`, `gId()` for `getElementById()`
|
||||
|
||||
## Key Files
|
||||
|
||||
- `index.htm` — main interface
|
||||
- `index.js` — functions that manage / update the main interface
|
||||
- `settings*.htm` — configuration pages
|
||||
- `*.css` — stylesheets (inlined during build)
|
||||
- `common.js` — helper functions
|
||||
|
||||
**Reuse shared helpers from `common.js` whenever possible** instead of duplicating utilities in page-local scripts.
|
||||
|
||||
## Build Integration
|
||||
|
||||
Files in this directory are processed by `tools/cdata.js` into generated headers
|
||||
(`wled00/html_*.h`, `wled00/js_*.h`).
|
||||
Run `npm run build` after any change. **Never edit generated headers directly.**
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "16.0.0-beta",
|
||||
"version": "16.0.0-alpha",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wled",
|
||||
"version": "16.0.0-beta",
|
||||
"version": "16.0.0-alpha",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "16.0.0-beta",
|
||||
"version": "16.0.0-alpha",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
@@ -4,77 +4,16 @@
|
||||
Import("env")
|
||||
from pathlib import Path
|
||||
|
||||
# Linker script fragment injected into the rodata output section of whichever
|
||||
# platform we're building for. Placed just before the end-of-rodata marker so
|
||||
# that the dynarray entries land in flash rodata and are correctly sorted.
|
||||
DYNARRAY_INJECTION = (
|
||||
"\n /* dynarray: WLED dynamic module arrays */\n"
|
||||
" . = ALIGN(0x10);\n"
|
||||
" KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))\n"
|
||||
" "
|
||||
)
|
||||
|
||||
|
||||
def inject_before_marker(path, marker):
|
||||
"""Patch a linker script file in-place, inserting DYNARRAY_INJECTION before marker."""
|
||||
original = path.read_text()
|
||||
path.write_text(original.replace(marker, DYNARRAY_INJECTION + marker, 1))
|
||||
|
||||
|
||||
if env.get("PIOPLATFORM") == "espressif32":
|
||||
# Find sections.ld on the linker search path (LIBPATH).
|
||||
sections_ld_path = None
|
||||
for ld_dir in env.get("LIBPATH", []):
|
||||
candidate = Path(str(ld_dir)) / "sections.ld"
|
||||
if candidate.exists():
|
||||
sections_ld_path = candidate
|
||||
break
|
||||
|
||||
if sections_ld_path is not None:
|
||||
# Inject inside the existing .flash.rodata output section, just before
|
||||
# _rodata_end. IDF v5 enforces zero gaps between adjacent output
|
||||
# sections via ASSERT statements, so INSERT AFTER .flash.rodata would
|
||||
# fail. Injecting inside the section creates no new output section and
|
||||
# leaves the ASSERTs satisfied.
|
||||
build_dir = Path(env.subst("$BUILD_DIR"))
|
||||
patched_path = build_dir / "dynarray_sections.ld"
|
||||
import shutil
|
||||
shutil.copy(sections_ld_path, patched_path)
|
||||
inject_before_marker(patched_path, "_rodata_end = ABSOLUTE(.);")
|
||||
|
||||
# Replace "sections.ld" in LINKFLAGS with an absolute path to our
|
||||
# patched copy. The flag may appear as a bare token, combined as
|
||||
# "-Tsections.ld", or split across two tokens ("-T", "sections.ld").
|
||||
patched_str = str(patched_path)
|
||||
new_flags = []
|
||||
skip_next = False
|
||||
for flag in env.get("LINKFLAGS", []):
|
||||
if skip_next:
|
||||
new_flags.append(patched_str if flag == "sections.ld" else flag)
|
||||
skip_next = False
|
||||
elif flag == "-T":
|
||||
new_flags.append(flag)
|
||||
skip_next = True
|
||||
else:
|
||||
new_flags.append(flag.replace("sections.ld", patched_str))
|
||||
env.Replace(LINKFLAGS=new_flags)
|
||||
platform = env.get("PIOPLATFORM")
|
||||
script_file = Path(f"tools/dynarray_{platform}.ld")
|
||||
if script_file.is_file():
|
||||
linker_script = f"-T{script_file}"
|
||||
if platform == "espressif32":
|
||||
# For ESP32, the script must be added at the right point in the list
|
||||
linkflags = env.get("LINKFLAGS", [])
|
||||
idx = linkflags.index("memory.ld")
|
||||
linkflags.insert(idx+1, linker_script)
|
||||
env.Replace(LINKFLAGS=linkflags)
|
||||
else:
|
||||
# Assume sections.ld will be built (ESP-IDF format); add a post-action to patch it
|
||||
# TODO: consider using ESP-IDF linker fragment (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/linker-script-generation.html)
|
||||
# For now, patch after building
|
||||
sections_ld = Path(env.subst("$BUILD_DIR")) / "sections.ld"
|
||||
def patch_sections_ld(target, source, env):
|
||||
inject_before_marker(sections_ld, "_rodata_end = ABSOLUTE(.);")
|
||||
env.AddPostAction(str(sections_ld), patch_sections_ld)
|
||||
|
||||
elif env.get("PIOPLATFORM") == "espressif8266":
|
||||
# The ESP8266 framework preprocesses eagle.app.v6.common.ld.h into
|
||||
# local.eagle.app.v6.common.ld in $BUILD_DIR/ld/ at build time. Register
|
||||
# a post-action on that generated file so the injection happens after
|
||||
# C-preprocessing but before linking.
|
||||
build_ld = Path(env.subst("$BUILD_DIR")) / "ld" / "local.eagle.app.v6.common.ld"
|
||||
|
||||
def patch_esp8266_ld(target, source, env):
|
||||
inject_before_marker(build_ld, "_irom0_text_end = ABSOLUTE(.);")
|
||||
|
||||
env.AddPostAction(str(build_ld), patch_esp8266_ld)
|
||||
# For other platforms, put it in last
|
||||
env.Append(LINKFLAGS=[linker_script])
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from pathlib import Path # For OS-agnostic path manipulation
|
||||
from click import secho
|
||||
from SCons.Script import Action, Exit
|
||||
Import("env")
|
||||
|
||||
_ATTR = re.compile(r'\bDW_AT_(name|comp_dir)\b')
|
||||
|
||||
|
||||
def read_lines(p: Path):
|
||||
""" Read in the contents of a file for analysis """
|
||||
@@ -14,129 +13,86 @@ def read_lines(p: Path):
|
||||
return f.readlines()
|
||||
|
||||
|
||||
def _get_readelf_path(env) -> str:
|
||||
""" Derive the readelf tool path from the build environment """
|
||||
# Derive from the C compiler: xtensa-esp32-elf-gcc → xtensa-esp32-elf-readelf
|
||||
cc = Path(env.subst("$CC"))
|
||||
return str(cc.with_name(re.sub(r'(gcc|g\+\+)$', 'readelf', cc.name)))
|
||||
def _get_nm_path(env) -> str:
|
||||
""" Derive the nm tool path from the build environment """
|
||||
if "NM" in env:
|
||||
return env.subst("$NM")
|
||||
# Derive from the C compiler: xtensa-esp32-elf-gcc → xtensa-esp32-elf-nm
|
||||
cc = env.subst("$CC")
|
||||
nm = re.sub(r'(gcc|g\+\+)$', 'nm', os.path.basename(cc))
|
||||
return os.path.join(os.path.dirname(cc), nm)
|
||||
|
||||
|
||||
def check_elf_modules(elf_path: Path, env, module_lib_builders) -> set[str]:
|
||||
""" Check which modules have at least one compilation unit in the ELF.
|
||||
""" Check which modules have at least one defined symbol placed in the ELF.
|
||||
|
||||
The map file is not a reliable source for this: with LTO, original object
|
||||
file paths are replaced by temporary ltrans.o partitions in all output
|
||||
sections, making per-module attribution impossible from the map alone.
|
||||
Instead we invoke readelf --debug-dump=info --dwarf-depth=1 on the ELF,
|
||||
which reads only the top-level compilation-unit DIEs from .debug_info.
|
||||
Each CU corresponds to one source file; matching DW_AT_comp_dir +
|
||||
DW_AT_name against the module src_dirs is sufficient to confirm a module
|
||||
was compiled into the ELF. The output volume is proportional to the
|
||||
number of source files, not the number of symbols.
|
||||
Instead we invoke nm --defined-only -l on the ELF, which uses DWARF debug
|
||||
info to attribute each placed symbol to its original source file.
|
||||
|
||||
Requires usermod libraries to be compiled with -g so that DWARF sections
|
||||
are present in the ELF. load_usermods.py injects -g for all WLED modules
|
||||
via dep.env.AppendUnique(CCFLAGS=["-g"]).
|
||||
|
||||
Returns the set of build_dir basenames for confirmed modules.
|
||||
"""
|
||||
readelf_path = _get_readelf_path(env)
|
||||
secho(f"INFO: Checking for usermod compilation units...")
|
||||
|
||||
nm_path = _get_nm_path(env)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[readelf_path, "--debug-dump=info", "--dwarf-depth=1", str(elf_path)],
|
||||
[nm_path, "--defined-only", "-l", str(elf_path)],
|
||||
capture_output=True, text=True, errors="ignore", timeout=120,
|
||||
)
|
||||
output = result.stdout
|
||||
nm_output = result.stdout
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
|
||||
secho(f"WARNING: readelf failed ({e}); skipping per-module validation", fg="yellow", err=True)
|
||||
secho(f"WARNING: nm failed ({e}); skipping per-module validation", fg="yellow", err=True)
|
||||
return {Path(b.build_dir).name for b in module_lib_builders} # conservative pass
|
||||
|
||||
# Match placed symbols against builders as we parse nm output, exiting early
|
||||
# once all builders are accounted for.
|
||||
# nm --defined-only still includes debugging symbols (type 'N') such as the
|
||||
# per-CU markers GCC emits in .debug_info (e.g. "usermod_example_cpp_6734d48d").
|
||||
# These live at address 0x00000000 in their debug section — not in any load
|
||||
# segment — so filtering them out leaves only genuinely placed symbols.
|
||||
# nm -l appends a tab-separated "file:lineno" location to each symbol line.
|
||||
remaining = {Path(str(b.src_dir)): Path(b.build_dir).name for b in module_lib_builders}
|
||||
found = set()
|
||||
project_dir = Path(env.subst("$PROJECT_DIR"))
|
||||
|
||||
def _flush_cu(comp_dir: str | None, name: str | None) -> None:
|
||||
"""Match one completed CU against remaining builders."""
|
||||
if not name or not remaining:
|
||||
return
|
||||
p = Path(name)
|
||||
src_path = (Path(comp_dir) / p) if (comp_dir and not p.is_absolute()) else p
|
||||
# In arduino+espidf dual-framework builds the IDF toolchain sets DW_AT_comp_dir
|
||||
# to the virtual path "/IDF_PROJECT" rather than the real project root, so
|
||||
# src_path won't match. Pre-compute a fallback using $PROJECT_DIR and check
|
||||
# both candidates in a single pass.
|
||||
use_fallback = not p.is_absolute() and comp_dir and Path(comp_dir) != project_dir
|
||||
src_path_real = project_dir / p if use_fallback else None
|
||||
for src_dir in list(remaining):
|
||||
if src_path.is_relative_to(src_dir) or (src_path_real and src_path_real.is_relative_to(src_dir)):
|
||||
found.add(remaining.pop(src_dir))
|
||||
return
|
||||
|
||||
# readelf emits one DW_TAG_compile_unit DIE per source file. Attributes
|
||||
# of interest:
|
||||
# DW_AT_name — source file (absolute, or relative to comp_dir)
|
||||
# DW_AT_comp_dir — compile working directory
|
||||
# Both appear as either a direct string or an indirect string:
|
||||
# DW_AT_name : foo.cpp
|
||||
# DW_AT_name : (indirect string, offset: 0x…): foo.cpp
|
||||
# Taking the portion after the *last* ": " on the line handles both forms.
|
||||
|
||||
comp_dir = name = None
|
||||
for line in output.splitlines():
|
||||
if 'Compilation Unit @' in line:
|
||||
_flush_cu(comp_dir, name)
|
||||
comp_dir = name = None
|
||||
continue
|
||||
for line in nm_output.splitlines():
|
||||
if not remaining:
|
||||
break # all builders matched
|
||||
m = _ATTR.search(line)
|
||||
if m:
|
||||
_, _, val = line.rpartition(': ')
|
||||
val = val.strip()
|
||||
if m.group(1) == 'name':
|
||||
name = val
|
||||
else:
|
||||
comp_dir = val
|
||||
_flush_cu(comp_dir, name) # flush the last CU
|
||||
addr, _, _ = line.partition(' ')
|
||||
if not addr.lstrip('0'):
|
||||
continue # zero address — skip debug-section marker
|
||||
if '\t' not in line:
|
||||
continue
|
||||
loc = line.rsplit('\t', 1)[1]
|
||||
# Strip trailing :lineno (e.g. "/path/to/foo.cpp:42" → "/path/to/foo.cpp")
|
||||
src_path = Path(loc.rsplit(':', 1)[0])
|
||||
# Path.is_relative_to() handles OS-specific separators correctly without
|
||||
# any regex, avoiding Windows path escaping issues.
|
||||
for src_dir in list(remaining):
|
||||
if src_path.is_relative_to(src_dir):
|
||||
found.add(remaining.pop(src_dir))
|
||||
break
|
||||
|
||||
return found
|
||||
|
||||
|
||||
DYNARRAY_SECTION = ".dtors" if env.get("PIOPLATFORM") == "espressif8266" else ".dynarray"
|
||||
USERMODS_SECTION = f"{DYNARRAY_SECTION}.usermods.1"
|
||||
|
||||
def count_usermod_objects(map_file: list[str]) -> int:
|
||||
""" Returns the number of usermod objects in the usermod list.
|
||||
|
||||
Computes the count from the address span between the .dynarray.usermods.0
|
||||
and .dynarray.usermods.99999 sentinel sections. This mirrors the
|
||||
DYNARRAY_LENGTH macro and is reliable under LTO, where all entries are
|
||||
merged into a single ltrans partition so counting section occurrences
|
||||
always yields 1 regardless of the true count.
|
||||
"""
|
||||
ENTRY_SIZE = 4 # sizeof(Usermod*) on 32-bit targets
|
||||
addr_begin = None
|
||||
addr_end = None
|
||||
|
||||
for i, line in enumerate(map_file):
|
||||
stripped = line.strip()
|
||||
if stripped == '.dynarray.usermods.0':
|
||||
if i + 1 < len(map_file):
|
||||
m = re.search(r'0x([0-9a-fA-F]+)', map_file[i + 1])
|
||||
if m:
|
||||
addr_begin = int(m.group(1), 16)
|
||||
elif stripped == '.dynarray.usermods.99999':
|
||||
if i + 1 < len(map_file):
|
||||
m = re.search(r'0x([0-9a-fA-F]+)', map_file[i + 1])
|
||||
if m:
|
||||
addr_end = int(m.group(1), 16)
|
||||
if addr_begin is not None and addr_end is not None:
|
||||
break
|
||||
|
||||
if addr_begin is None or addr_end is None:
|
||||
return 0
|
||||
return (addr_end - addr_begin) // ENTRY_SIZE
|
||||
""" Returns the number of usermod objects in the usermod list """
|
||||
# Count the number of entries in the usermods table section
|
||||
return len([x for x in map_file if USERMODS_SECTION in x])
|
||||
|
||||
|
||||
def validate_map_file(source, target, env):
|
||||
""" Validate that all modules appear in the output build """
|
||||
build_dir = Path(env.subst("$BUILD_DIR"))
|
||||
map_file_path = build_dir / env.subst("${PROGNAME}.map")
|
||||
map_file_path = build_dir / env.subst("${PROGNAME}.map")
|
||||
|
||||
if not map_file_path.exists():
|
||||
secho(f"ERROR: Map file not found: {map_file_path}", fg="red", err=True)
|
||||
@@ -155,7 +111,6 @@ def validate_map_file(source, target, env):
|
||||
secho(f"INFO: {usermod_object_count} usermod object entries")
|
||||
|
||||
elf_path = build_dir / env.subst("${PROGNAME}.elf")
|
||||
|
||||
confirmed_modules = check_elf_modules(elf_path, env, module_lib_builders)
|
||||
|
||||
missing_modules = [modname for mdir, modname in modules.items() if mdir not in confirmed_modules]
|
||||
@@ -165,6 +120,7 @@ def validate_map_file(source, target, env):
|
||||
fg="red",
|
||||
err=True)
|
||||
Exit(1)
|
||||
return None
|
||||
|
||||
env.Append(LINKFLAGS=[env.subst("-Wl,--Map=${BUILD_DIR}/${PROGNAME}.map")])
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", Action(validate_map_file, cmdstr='Checking linked optional modules (usermods) in map file'))
|
||||
|
||||
@@ -161,6 +161,7 @@ upload_speed = 115200
|
||||
# ------------------------------------------------------------------------------
|
||||
lib_compat_mode = strict
|
||||
lib_deps =
|
||||
fastled/FastLED @ 3.6.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
https://github.com/Makuna/NeoPixelBus.git#a0919d1c10696614625978dd6fb750a1317a14ce
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
||||
@@ -248,6 +249,7 @@ lib_deps_compat =
|
||||
ESPAsyncTCP @ 1.2.2
|
||||
ESPAsyncUDP
|
||||
ESP8266PWM
|
||||
fastled/FastLED @ 3.6.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
makuna/NeoPixelBus @ 2.7.9
|
||||
https://github.com/blazoncek/QuickESPNow.git#optional-debug
|
||||
@@ -430,7 +432,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
|
||||
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
-D WLED_DISABLE_PIXELFORGE
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp01_1m_full_compat]
|
||||
@@ -441,7 +442,6 @@ platform_packages = ${esp8266.platform_packages_compat}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
-D WLED_DISABLE_PIXELFORGE
|
||||
|
||||
[env:esp01_1m_full_160]
|
||||
extends = env:esp01_1m_full
|
||||
@@ -450,7 +450,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
|
||||
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
-D WLED_DISABLE_PIXELFORGE
|
||||
custom_usermods = audioreactive
|
||||
|
||||
[env:esp32dev]
|
||||
@@ -542,7 +541,6 @@ platform_packages = ${esp32c3.platform_packages}
|
||||
framework = arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
custom_usermods = audioreactive
|
||||
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\"
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||
|
||||
@@ -47,7 +47,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -D WLED_DISABLE_MQTT
|
||||
; -D WLED_DISABLE_ADALIGHT
|
||||
; -D WLED_DISABLE_2D
|
||||
; -D WLED_DISABLE_PIXELFORGE
|
||||
; -D WLED_DISABLE_PXMAGIC
|
||||
; -D WLED_DISABLE_ESPNOW
|
||||
; -D WLED_DISABLE_BROWNOUT_DET
|
||||
;
|
||||
|
||||
10
tools/dynarray_espressif32.ld
Normal file
10
tools/dynarray_espressif32.ld
Normal file
@@ -0,0 +1,10 @@
|
||||
/* ESP32 linker script fragment to add dynamic array section to binary */
|
||||
SECTIONS
|
||||
{
|
||||
.dynarray :
|
||||
{
|
||||
. = ALIGN(0x10);
|
||||
KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))
|
||||
} > default_rodata_seg
|
||||
}
|
||||
INSERT AFTER .flash.rodata;
|
||||
@@ -1,696 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WBF ↔ C Header Bi-Directional Converter</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.card {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
background: #16213e;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 242, 255, .15);
|
||||
border: 1px solid rgba(0, 242, 255, .1);
|
||||
}
|
||||
h1 {
|
||||
color: #00f2ff;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2rem;
|
||||
text-shadow: 0 0 25px #ffffff;
|
||||
}
|
||||
.mode-selector {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mode-btn {
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
border: 2px solid rgba(0, 242, 255, .3);
|
||||
background: rgba(0, 0, 0, .4);
|
||||
color: #888;
|
||||
transition: all .3s;
|
||||
}
|
||||
.mode-btn.active {
|
||||
background: linear-gradient(135deg, #0095b3 0%, #00d4ff 100%);
|
||||
border-color: #00f2ff;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 14px rgba(0, 242, 255, .6);
|
||||
}
|
||||
.mode-btn:hover:not(.active) {
|
||||
border-color: #00f2ff;
|
||||
color: #00f2ff;
|
||||
}
|
||||
.controls {
|
||||
background: rgba(255, 255, 255, .03);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid rgba(0, 242, 255, .1);
|
||||
display: none;
|
||||
}
|
||||
.controls.active {
|
||||
display: block;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
color: #00d4ff;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .5px;
|
||||
}
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
.file-label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 242, 255, .2);
|
||||
background: rgba(0, 0, 0, .4);
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: all .3s;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.file-label:hover {
|
||||
border-color: #00f2ff;
|
||||
color: #00f2ff;
|
||||
}
|
||||
.file-label.has-file {
|
||||
color: #00f2ff;
|
||||
}
|
||||
input[type="text"], textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 242, 255, .2);
|
||||
background: rgba(0, 0, 0, .4);
|
||||
color: #fff;
|
||||
margin-bottom: 15px;
|
||||
transition: all .3s;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
textarea {
|
||||
min-height: 300px;
|
||||
resize: vertical;
|
||||
font-size: .85rem;
|
||||
}
|
||||
input[type="text"]:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #00f2ff;
|
||||
box-shadow: 0 0 8px rgba(0, 242, 255, .3);
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #0095b3 0%, #00d4ff 100%);
|
||||
box-shadow: 0 0 14px rgba(0, 242, 255, .6);
|
||||
color: #fff;
|
||||
transition: all .3s;
|
||||
}
|
||||
button:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 18px rgba(0, 242, 255, .9);
|
||||
}
|
||||
button:disabled {
|
||||
opacity: .3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.info {
|
||||
background: rgba(0, 242, 255, .05);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid rgba(0, 242, 255, .2);
|
||||
font-size: .85rem;
|
||||
}
|
||||
.output {
|
||||
background: rgba(0, 0, 0, .6);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0, 242, 255, .2);
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
.output.active {
|
||||
display: block;
|
||||
}
|
||||
pre {
|
||||
color: #00f2ff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: .85rem;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.copy-btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>WBF ↔ C Header Converter</h1>
|
||||
|
||||
<div class="mode-selector">
|
||||
<button class="mode-btn active" onclick="switchMode('wbf-to-header')">WBF → C Header</button>
|
||||
<button class="mode-btn" onclick="switchMode('header-to-wbf')">C Header → WBF</button>
|
||||
</div>
|
||||
|
||||
<!-- WBF to Header Mode -->
|
||||
<div id="wbf-to-header" class="controls active">
|
||||
<div class="info">
|
||||
Load a WLED Bitmap Font (.wbf) file and convert it to a C/C++ header file.
|
||||
</div>
|
||||
|
||||
<label>Select WBF Font File</label>
|
||||
<label for="wbfFile" class="file-label" id="wbfFileLabel">Choose .wbf file</label>
|
||||
<input type="file" id="wbfFile" accept=".wbf">
|
||||
|
||||
<label>Array Name</label>
|
||||
<input type="text" id="arrayName" placeholder="console_font_4x6" value="console_font_4x6">
|
||||
|
||||
<button id="convertToHeaderBtn" disabled>Convert to Header</button>
|
||||
</div>
|
||||
|
||||
<!-- Header to WBF Mode -->
|
||||
<div id="header-to-wbf" class="controls">
|
||||
<div class="info">
|
||||
Paste a C/C++ header file containing a WLED font array and convert it to a .wbf file.
|
||||
</div>
|
||||
|
||||
<label>Paste C Header Code</label>
|
||||
<textarea id="headerInput" placeholder="Paste your C header code here (e.g., static const unsigned char font[] PROGMEM = {...};)"></textarea>
|
||||
|
||||
<label>Output Filename (without .wbf extension)</label>
|
||||
<input type="text" id="wbfFilename" placeholder="console_font_4x6" value="console_font_4x6">
|
||||
|
||||
<button id="convertToWbfBtn" disabled>Convert to WBF</button>
|
||||
</div>
|
||||
|
||||
<div id="output" class="output">
|
||||
<pre id="outputCode"></pre>
|
||||
<button class="copy-btn" onclick="copyToClipboard(event)">Copy to Clipboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Elements for WBF to Header
|
||||
const wbfFileInput = document.getElementById('wbfFile');
|
||||
const wbfFileLabel = document.getElementById('wbfFileLabel');
|
||||
const arrayNameInput = document.getElementById('arrayName');
|
||||
const convertToHeaderBtn = document.getElementById('convertToHeaderBtn');
|
||||
|
||||
// Elements for Header to WBF
|
||||
const headerInput = document.getElementById('headerInput');
|
||||
const wbfFilenameInput = document.getElementById('wbfFilename');
|
||||
const convertToWbfBtn = document.getElementById('convertToWbfBtn');
|
||||
|
||||
// Output elements
|
||||
const output = document.getElementById('output');
|
||||
const outputCode = document.getElementById('outputCode');
|
||||
|
||||
let wbfData = null;
|
||||
let fileName = '';
|
||||
|
||||
// Mode switching
|
||||
function switchMode(mode) {
|
||||
document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelectorAll('.controls').forEach(ctrl => ctrl.classList.remove('active'));
|
||||
|
||||
event.target.classList.add('active');
|
||||
document.getElementById(mode).classList.add('active');
|
||||
output.classList.remove('active');
|
||||
}
|
||||
|
||||
// ==================== WBF to Header ====================
|
||||
wbfFileInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
fileName = file.name.replace(/\.[^.]+$/, '');
|
||||
wbfFileLabel.textContent = file.name;
|
||||
wbfFileLabel.classList.add('has-file');
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (ev) => {
|
||||
wbfData = new Uint8Array(ev.target.result);
|
||||
convertToHeaderBtn.disabled = false;
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
|
||||
convertToHeaderBtn.addEventListener('click', () => {
|
||||
if (!wbfData) return;
|
||||
|
||||
const header = parseWBF(wbfData);
|
||||
if (header) {
|
||||
generateHeader(header);
|
||||
}
|
||||
});
|
||||
|
||||
function parseWBF(data) {
|
||||
if (data.length < 12) {
|
||||
alert('Invalid WBF file: too short');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse header
|
||||
if (data[0] !== 0x57) { // 'W'
|
||||
alert('Invalid WBF file: missing magic byte');
|
||||
return null;
|
||||
}
|
||||
|
||||
const height = data[1];
|
||||
const maxWidth = data[2];
|
||||
const spacing = data[3];
|
||||
const flags = data[4];
|
||||
const first = data[5];
|
||||
const last = data[6];
|
||||
const reserved = data[7];
|
||||
|
||||
// Unicode offset is 32-bit little-endian
|
||||
const unicodeOffset = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
|
||||
|
||||
const numChars = last - first + 1;
|
||||
const isVariableWidth = (flags & 0x01) !== 0;
|
||||
|
||||
let offset = 12; // Start after header
|
||||
let widthTable = null;
|
||||
|
||||
// If variable width, read width table
|
||||
if (isVariableWidth) {
|
||||
if (data.length < 12 + numChars) {
|
||||
alert('Invalid WBF file: missing width table');
|
||||
return null;
|
||||
}
|
||||
widthTable = Array.from(data.slice(12, 12 + numChars));
|
||||
offset = 12 + numChars;
|
||||
}
|
||||
|
||||
// Calculate expected data size
|
||||
let expectedDataSize = 0;
|
||||
if (isVariableWidth) {
|
||||
for (let w of widthTable) {
|
||||
expectedDataSize += Math.ceil((w * height) / 8);
|
||||
}
|
||||
} else {
|
||||
expectedDataSize = numChars * Math.ceil((maxWidth * height) / 8);
|
||||
}
|
||||
|
||||
if (data.length < offset + expectedDataSize) {
|
||||
alert(`Invalid WBF file: expected ${offset + expectedDataSize} bytes, got ${data.length}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
height,
|
||||
maxWidth,
|
||||
spacing,
|
||||
flags,
|
||||
first,
|
||||
last,
|
||||
reserved,
|
||||
unicodeOffset,
|
||||
data: data,
|
||||
isVariableWidth,
|
||||
widthTable,
|
||||
dataOffset: offset
|
||||
};
|
||||
}
|
||||
|
||||
function generateHeader(header) {
|
||||
const arrayName = arrayNameInput.value || 'console_font';
|
||||
let code = '';
|
||||
|
||||
// Header comment
|
||||
code += `// Font: ${fileName}\n`;
|
||||
code += `// Height: ${header.height}, Max Width: ${header.maxWidth}, Spacing: ${header.spacing}\n`;
|
||||
code += `// Characters: ${header.first}-${header.last} (${header.last - header.first + 1} glyphs)\n`;
|
||||
code += `// Unicode Offset: 0x${header.unicodeOffset.toString(16).padStart(8, '0').toUpperCase()}\n`;
|
||||
code += `// Variable Width: ${header.isVariableWidth ? 'Yes' : 'No'}\n\n`;
|
||||
|
||||
// Array declaration
|
||||
code += `static const unsigned char ${arrayName}[] PROGMEM = {\n`;
|
||||
|
||||
// Header bytes (12 bytes)
|
||||
code += ' ';
|
||||
code += `0x${header.data[0].toString(16).padStart(2, '0').toUpperCase()}, `; // Magic 'W'
|
||||
code += `0x${header.height.toString(16).padStart(2, '0').toUpperCase()}, `; // Height
|
||||
code += `0x${header.maxWidth.toString(16).padStart(2, '0').toUpperCase()}, `; // Max Width
|
||||
code += `0x${header.spacing.toString(16).padStart(2, '0').toUpperCase()}, `; // Spacing
|
||||
code += `0x${header.flags.toString(16).padStart(2, '0').toUpperCase()}, `; // Flags
|
||||
code += `0x${header.first.toString(16).padStart(2, '0').toUpperCase()}, `; // First char
|
||||
code += `0x${header.last.toString(16).padStart(2, '0').toUpperCase()}, `; // Last char
|
||||
code += `0x${header.reserved.toString(16).padStart(2, '0').toUpperCase()}, `; // Reserved
|
||||
|
||||
// Unicode offset (4 bytes, little-endian)
|
||||
code += `0x${header.data[8].toString(16).padStart(2, '0').toUpperCase()}, `;
|
||||
code += `0x${header.data[9].toString(16).padStart(2, '0').toUpperCase()}, `;
|
||||
code += `0x${header.data[10].toString(16).padStart(2, '0').toUpperCase()}, `;
|
||||
code += `0x${header.data[11].toString(16).padStart(2, '0').toUpperCase()}`;
|
||||
|
||||
code += ', // Header: \'W\', H, W, S, Flags, First, Last, Reserved, UnicodeOffset (32bit)\n\n';
|
||||
|
||||
// Width table (if variable width)
|
||||
if (header.isVariableWidth) {
|
||||
code += ' // Width table\n';
|
||||
const numChars = header.last - header.first + 1;
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
if (i % 16 === 0) {
|
||||
if (i > 0) code += '\n';
|
||||
code += ' ';
|
||||
}
|
||||
code += `0x${header.widthTable[i].toString(16).padStart(2, '0').toUpperCase()}`;
|
||||
if (i < numChars - 1) {
|
||||
code += ', ';
|
||||
}
|
||||
}
|
||||
code += ',\n\n';
|
||||
}
|
||||
|
||||
// Character data
|
||||
const numChars = header.last - header.first + 1;
|
||||
let offset = header.dataOffset;
|
||||
|
||||
// First pass: calculate max byte width for alignment
|
||||
let maxByteWidth = 0;
|
||||
let tempOffset = header.dataOffset;
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
const charWidth = header.isVariableWidth ? header.widthTable[i] : header.maxWidth;
|
||||
const bytesPerChar = Math.ceil((charWidth * header.height) / 8);
|
||||
const byteStr = Array(bytesPerChar).fill('0x00').join(', ');
|
||||
maxByteWidth = Math.max(maxByteWidth, byteStr.length);
|
||||
tempOffset += bytesPerChar;
|
||||
}
|
||||
|
||||
// Second pass: generate output with aligned comments
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
const charCode = header.first + i;
|
||||
const charStr = getAsciiString(charCode);
|
||||
|
||||
// Calculate bytes for this character
|
||||
const charWidth = header.isVariableWidth ? header.widthTable[i] : header.maxWidth;
|
||||
const bytesPerChar = Math.ceil((charWidth * header.height) / 8);
|
||||
|
||||
code += ' ';
|
||||
let byteStr = '';
|
||||
for (let b = 0; b < bytesPerChar; b++) {
|
||||
const byte = header.data[offset++];
|
||||
byteStr += `0x${byte.toString(16).padStart(2, '0').toUpperCase()}`;
|
||||
if (b < bytesPerChar - 1) {
|
||||
byteStr += ', ';
|
||||
}
|
||||
}
|
||||
code += byteStr;
|
||||
code += ',';
|
||||
|
||||
// Pad to align comments
|
||||
const padding = ' '.repeat(maxByteWidth - byteStr.length + 5);
|
||||
|
||||
const widthInfo = header.isVariableWidth ? `, w=${charWidth}` : '';
|
||||
code += `${padding}/* code=${charCode}, hex=0x${charCode.toString(16).padStart(2, '0').toUpperCase()}, ascii="${charStr}"${widthInfo} */\n`;
|
||||
}
|
||||
|
||||
code += '};\n';
|
||||
|
||||
// Display output
|
||||
outputCode.textContent = code;
|
||||
output.classList.add('active');
|
||||
}
|
||||
|
||||
// ==================== Header to WBF ====================
|
||||
headerInput.addEventListener('input', () => {
|
||||
convertToWbfBtn.disabled = headerInput.value.trim().length === 0;
|
||||
});
|
||||
|
||||
convertToWbfBtn.addEventListener('click', () => {
|
||||
const headerText = headerInput.value.trim();
|
||||
if (!headerText) return;
|
||||
|
||||
const wbfData = parseHeaderToWBF(headerText);
|
||||
if (wbfData) {
|
||||
downloadWBF(wbfData);
|
||||
}
|
||||
});
|
||||
|
||||
function parseHeaderToWBF(headerText) {
|
||||
try {
|
||||
// Properly remove C-style comments
|
||||
let cleanedText = removeComments(headerText);
|
||||
|
||||
// Extract all hex values from the cleaned text
|
||||
const hexPattern = /0x[0-9a-fA-F]{2}/g;
|
||||
const hexValues = cleanedText.match(hexPattern);
|
||||
|
||||
if (!hexValues || hexValues.length < 12) {
|
||||
alert('Invalid header: Could not find enough hex values (need at least 12 for header)\nFound: ' + (hexValues ? hexValues.length : 0) + ' bytes');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert hex strings to bytes
|
||||
const allBytes = hexValues.map(hex => parseInt(hex, 16));
|
||||
|
||||
// Parse the header (first 12 bytes)
|
||||
const height = allBytes[1];
|
||||
const maxWidth = allBytes[2];
|
||||
const spacing = allBytes[3];
|
||||
const flags = allBytes[4];
|
||||
const first = allBytes[5];
|
||||
const last = allBytes[6];
|
||||
const reserved = allBytes[7];
|
||||
const unicodeOffset = allBytes[8] | (allBytes[9] << 8) | (allBytes[10] << 16) | (allBytes[11] << 24);
|
||||
|
||||
// Validate magic byte
|
||||
if (allBytes[0] !== 0x57) {
|
||||
alert('Invalid header: First byte should be 0x57 (magic \'W\'), found 0x' + allBytes[0].toString(16).padStart(2, '0').toUpperCase());
|
||||
return null;
|
||||
}
|
||||
|
||||
const numChars = last - first + 1;
|
||||
const isVariableWidth = (flags & 0x01) !== 0;
|
||||
|
||||
// Now we need to separate header, width table (if present), and bitmap data
|
||||
let dataStartIndex = 12; // After the 12-byte header
|
||||
let widthTable = null;
|
||||
|
||||
if (isVariableWidth) {
|
||||
// Extract width table
|
||||
widthTable = allBytes.slice(dataStartIndex, dataStartIndex + numChars);
|
||||
dataStartIndex += numChars;
|
||||
}
|
||||
|
||||
// Calculate expected bitmap data size
|
||||
let expectedBitmapSize = 0;
|
||||
if (isVariableWidth) {
|
||||
for (let w of widthTable) {
|
||||
expectedBitmapSize += Math.ceil((w * height) / 8);
|
||||
}
|
||||
} else {
|
||||
expectedBitmapSize = numChars * Math.ceil((maxWidth * height) / 8);
|
||||
}
|
||||
|
||||
// Extract bitmap data
|
||||
const bitmapData = allBytes.slice(dataStartIndex, dataStartIndex + expectedBitmapSize);
|
||||
|
||||
// Validate we have all the data
|
||||
const totalExpected = dataStartIndex + expectedBitmapSize;
|
||||
if (allBytes.length < totalExpected) {
|
||||
alert(`Invalid header: Expected at least ${totalExpected} bytes, found ${allBytes.length}\n\n` +
|
||||
`Header shows: ${numChars} characters from ${first} to ${last}\n` +
|
||||
`${isVariableWidth ? 'Variable width font' : 'Fixed width font'}\n` +
|
||||
`Height: ${height}, Max Width: ${maxWidth}\n` +
|
||||
`Expected bitmap size: ${expectedBitmapSize} bytes`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reconstruct the WBF file
|
||||
let wbfSize = 12; // Header
|
||||
if (isVariableWidth) {
|
||||
wbfSize += numChars; // Width table
|
||||
}
|
||||
wbfSize += expectedBitmapSize; // Bitmap data
|
||||
|
||||
const wbfData = new Uint8Array(wbfSize);
|
||||
let offset = 0;
|
||||
|
||||
// Write header
|
||||
wbfData[offset++] = 0x57; // Magic 'W'
|
||||
wbfData[offset++] = height;
|
||||
wbfData[offset++] = maxWidth;
|
||||
wbfData[offset++] = spacing;
|
||||
wbfData[offset++] = flags;
|
||||
wbfData[offset++] = first;
|
||||
wbfData[offset++] = last;
|
||||
wbfData[offset++] = reserved;
|
||||
wbfData[offset++] = unicodeOffset & 0xFF;
|
||||
wbfData[offset++] = (unicodeOffset >> 8) & 0xFF;
|
||||
wbfData[offset++] = (unicodeOffset >> 16) & 0xFF;
|
||||
wbfData[offset++] = (unicodeOffset >> 24) & 0xFF;
|
||||
|
||||
// Write width table if variable width
|
||||
if (isVariableWidth) {
|
||||
for (let w of widthTable) {
|
||||
wbfData[offset++] = w;
|
||||
}
|
||||
}
|
||||
|
||||
// Write bitmap data
|
||||
for (let byte of bitmapData) {
|
||||
wbfData[offset++] = byte;
|
||||
}
|
||||
|
||||
// Show success message
|
||||
outputCode.textContent = `Successfully parsed header!\n\n` +
|
||||
`Height: ${height}\n` +
|
||||
`Max Width: ${maxWidth}\n` +
|
||||
`Spacing: ${spacing}\n` +
|
||||
`Flags: 0x${flags.toString(16).padStart(2, '0').toUpperCase()} (${isVariableWidth ? 'Variable Width' : 'Fixed Width'})\n` +
|
||||
`Characters: ${first}-${last} (${numChars} glyphs)\n` +
|
||||
`Unicode Offset: 0x${unicodeOffset.toString(16).padStart(8, '0').toUpperCase()}\n` +
|
||||
`Total Size: ${wbfData.length} bytes\n` +
|
||||
` Header: 12 bytes\n` +
|
||||
(isVariableWidth ? ` Width table: ${numChars} bytes\n` : '') +
|
||||
` Bitmap data: ${expectedBitmapSize} bytes\n` +
|
||||
`Parsed ${allBytes.length} total hex values\n\n` +
|
||||
`Your .wbf file is ready to download!`;
|
||||
output.classList.add('active');
|
||||
|
||||
return wbfData;
|
||||
} catch (error) {
|
||||
alert('Error parsing header: ' + error.message);
|
||||
console.error('Parse error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Proper C-style comment removal
|
||||
function removeComments(code) {
|
||||
let result = '';
|
||||
let i = 0;
|
||||
|
||||
while (i < code.length) {
|
||||
// Check for // comment (single-line)
|
||||
if (code[i] === '/' && code[i + 1] === '/') {
|
||||
// Skip until end of line
|
||||
i += 2;
|
||||
while (i < code.length && code[i] !== '\n') {
|
||||
i++;
|
||||
}
|
||||
// Keep the newline
|
||||
if (i < code.length) {
|
||||
result += '\n';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// Check for /* comment (multi-line)
|
||||
else if (code[i] === '/' && code[i + 1] === '*') {
|
||||
// Skip until we find */
|
||||
i += 2;
|
||||
while (i < code.length - 1) {
|
||||
if (code[i] === '*' && code[i + 1] === '/') {
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
// Preserve newlines in multi-line comments (for line counting if needed)
|
||||
if (code[i] === '\n') {
|
||||
result += '\n';
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// Regular character
|
||||
else {
|
||||
result += code[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function downloadWBF(data) {
|
||||
const filename = wbfFilenameInput.value.trim() || 'font';
|
||||
const blob = new Blob([data], { type: 'application/octet-stream' });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${filename}.wbf`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
// ==================== Utility Functions ====================
|
||||
function getAsciiString(code) {
|
||||
if (code < 32) {
|
||||
// Control characters
|
||||
return '^' + String.fromCharCode(64 + code);
|
||||
} else if (code === 127) {
|
||||
return '^?';
|
||||
} else if (code >= 32 && code <= 126) {
|
||||
// Printable ASCII
|
||||
return String.fromCharCode(code);
|
||||
} else {
|
||||
// Extended ASCII
|
||||
return '\\x' + code.toString(16).padStart(2, '0');
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(event) {
|
||||
const text = outputCode.textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Clipboard error:', err);
|
||||
// Fallback method for older browsers
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
alert('Failed to copy to clipboard: ' + e.message);
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -115,7 +115,7 @@ private:
|
||||
);
|
||||
}
|
||||
|
||||
static inline uint32_t scale32(uint32_t c, uint8_t scale) {
|
||||
static inline uint32_t scale32(uint32_t c, fract8 scale) {
|
||||
return RGBW32(
|
||||
scale8(R(c), scale),
|
||||
scale8(G(c), scale),
|
||||
|
||||
@@ -20,26 +20,6 @@
|
||||
* ....
|
||||
*/
|
||||
|
||||
#define FFT_PREFER_EXACT_PEAKS // use Blackman-Harris FFT windowing instead of Flat Top -> results in "sharper" peaks and less "leaking" into other frequencies (credits to @softhack)
|
||||
|
||||
/*
|
||||
* Note on FFT variants:
|
||||
* - ArduinoFFT: uses floating point calculations, very slow on S2 and C3 (no FPU)
|
||||
* - ESP-IDF DSP library:
|
||||
- faster but uses ~13k of extra flash on ESP32 and S3
|
||||
* - uses integer math on S2 and C3: slightly less accurate but over 10x faster than ArduinoFFT and uses less flash
|
||||
- not available in IDF < 4.4
|
||||
* - ArduinoFFT is used by default on ESP32 and S3
|
||||
* - ESP-IDF DSP FFT with integer math is used by default on S2 and C3
|
||||
* - defines:
|
||||
* - UM_AUDIOREACTIVE_USE_ARDUINO_FFT: use ArduinoFFT library for FFT
|
||||
* - UM_AUDIOREACTIVE_USE_ESPDSP_FFT: use ESP-IDF DSP for FFT
|
||||
*/
|
||||
|
||||
//#define UM_AUDIOREACTIVE_USE_ESPDSP_FFT // default on S2 and C3
|
||||
//#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // use integer FFT if using ESP-IDF DSP library, always used on S2 and C3 (UM_AUDIOREACTIVE_USE_ARDUINO_FFT takes priority)
|
||||
//#define UM_AUDIOREACTIVE_USE_ARDUINO_FFT // default on ESP32 and S3
|
||||
|
||||
#if !defined(FFTTASK_PRIORITY)
|
||||
#define FFTTASK_PRIORITY 1 // standard: looptask prio
|
||||
//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp
|
||||
@@ -119,46 +99,6 @@ static uint8_t maxVol = 31; // (was 10) Reasonable value for constant v
|
||||
static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated)
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_ESPDSP_FFT) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32))
|
||||
#define UM_AUDIOREACTIVE_USE_ARDUINO_FFT // use ArduinoFFT library for FFT instead of ESP-IDF DSP library by default on ESP32 and S3
|
||||
#endif
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
#define UM_AUDIOREACTIVE_USE_ARDUINO_FFT // DSP FFT library is not available in ESP-IDF < 4.4
|
||||
#endif
|
||||
|
||||
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
#include <arduinoFFT.h> // ArduinoFFT library for FFT and window functions
|
||||
#undef UM_AUDIOREACTIVE_USE_INTEGER_FFT // arduinoFFT has not integer support
|
||||
#else
|
||||
#include "dsps_fft2r.h" // ESP-IDF DSP library for FFT and window functions
|
||||
#ifdef FFT_PREFER_EXACT_PEAKS
|
||||
#include "dsps_wind_blackman_harris.h"
|
||||
#else
|
||||
#include "dsps_wind_flat_top.h"
|
||||
#endif
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on ESP32-S2 and ESP32-C3
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
using FFTsampleType = float;
|
||||
using FFTmathType = float;
|
||||
#define FFTabs fabsf
|
||||
#else
|
||||
using FFTsampleType = int16_t;
|
||||
using FFTmathType = int32_t;
|
||||
#define FFTabs abs
|
||||
#endif
|
||||
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
||||
static FFTsampleType* valFFT = nullptr;
|
||||
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
static float* vImag = nullptr; // imaginary part of FFT results
|
||||
#endif
|
||||
|
||||
// pre-computed window function
|
||||
static FFTsampleType* windowFFT = nullptr;
|
||||
|
||||
// use audio source class (ESP32 specific)
|
||||
#include "audio_source.h"
|
||||
@@ -168,14 +108,14 @@ constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples)
|
||||
// globals
|
||||
static uint8_t inputLevel = 128; // UI slider value
|
||||
#ifndef SR_SQUELCH
|
||||
static uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value)
|
||||
uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value)
|
||||
#else
|
||||
static uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value)
|
||||
uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value)
|
||||
#endif
|
||||
#ifndef SR_GAIN
|
||||
static uint8_t sampleGain = 60; // sample gain (config value)
|
||||
uint8_t sampleGain = 60; // sample gain (config value)
|
||||
#else
|
||||
static uint8_t sampleGain = SR_GAIN; // sample gain (config value)
|
||||
uint8_t sampleGain = SR_GAIN; // sample gain (config value)
|
||||
#endif
|
||||
// user settable options for FFTResult scaling
|
||||
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root
|
||||
@@ -200,8 +140,8 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; //
|
||||
// AGC presets end
|
||||
|
||||
static AudioSource *audioSource = nullptr;
|
||||
static bool useBandPassFilter = false; // if true, enables a hard cutoff bandpass filter. Applies after FFT.
|
||||
static bool useMicFilter = false; // if true, enables a IIR bandpass filter 80Hz-20Khz to remove noise. Applies before FFT.
|
||||
static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT.
|
||||
|
||||
////////////////////
|
||||
// Begin FFT Code //
|
||||
////////////////////
|
||||
@@ -209,7 +149,7 @@ static bool useMicFilter = false; // if true, enables a
|
||||
// some prototypes, to ensure consistent interfaces
|
||||
static float fftAddAvg(int from, int to); // average of several FFT result bins
|
||||
void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results
|
||||
static void runMicFilter(uint16_t numSamples, FFTsampleType *sampleBuffer);
|
||||
static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass)
|
||||
static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels
|
||||
|
||||
static TaskHandle_t FFT_Task = nullptr;
|
||||
@@ -245,13 +185,13 @@ constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - Thi
|
||||
constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information.
|
||||
// the following are observed values, supported by a bit of "educated guessing"
|
||||
//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels
|
||||
#ifdef FFT_PREFER_EXACT_PEAKS
|
||||
#define FFT_DOWNSCALE 0.40f // downscaling factor for FFT results, RMS averaging for "Blackman-Harris" Window @22kHz (credit to MM)
|
||||
#else
|
||||
#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
||||
#endif
|
||||
#define LOG_256 5.54517744f // log(256)
|
||||
|
||||
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
||||
static float* vReal = nullptr; // FFT sample inputs / freq output - these are our raw result bins
|
||||
static float* vImag = nullptr; // imaginary parts
|
||||
|
||||
// Create FFT object
|
||||
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
|
||||
// these options actually cause slow-downs on all esp32 processors, don't use them.
|
||||
@@ -260,20 +200,16 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul
|
||||
// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt()
|
||||
// #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - since v2.0.0 this must be done in build_flags
|
||||
|
||||
#include <arduinoFFT.h> // FFT object is created in FFTcode
|
||||
// Helper functions
|
||||
|
||||
// compute average of several FFT result bins
|
||||
static float fftAddAvg(int from, int to) {
|
||||
FFTmathType result = 0;
|
||||
float result = 0.0f;
|
||||
for (int i = from; i <= to; i++) {
|
||||
result += valFFT[i];
|
||||
result += vReal[i];
|
||||
}
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
result = result * 0.0625; // divide by 16 to reduce magnitude. Want end result to be scaled linear and ~4096 max.
|
||||
#else
|
||||
result *= 32; // scale result to match float values. note: raw scaling value between float and int is 512, float version is scaled down by 16
|
||||
#endif
|
||||
return float(result) / float(to - from + 1); // return average as float
|
||||
return result / float(to - from + 1);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -282,65 +218,18 @@ static float fftAddAvg(int from, int to) {
|
||||
void FFTcode(void * parameter)
|
||||
{
|
||||
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
|
||||
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
|
||||
// allocate FFT buffers on first call
|
||||
if (valFFT == nullptr) valFFT = (float*) calloc(samplesFFT, sizeof(float));
|
||||
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
|
||||
if ((valFFT == nullptr) || (vImag == nullptr)) {
|
||||
if (vReal == nullptr) vReal = (float*) calloc(samplesFFT, sizeof(float));
|
||||
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
|
||||
if ((vReal == nullptr) || (vImag == nullptr)) {
|
||||
// something went wrong
|
||||
if (valFFT) free(valFFT); valFFT = nullptr;
|
||||
if (vReal) free(vReal); vReal = nullptr;
|
||||
if (vImag) free(vImag); vImag = nullptr;
|
||||
return;
|
||||
}
|
||||
// Create FFT object with weighing factor storage
|
||||
ArduinoFFT<float> FFT = ArduinoFFT<float>(valFFT, vImag, samplesFFT, SAMPLE_RATE, true);
|
||||
#elif !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
// allocate and initialize FFT buffers on first call
|
||||
if (valFFT == nullptr) {
|
||||
valFFT = (float*)heap_caps_aligned_calloc(16, 2 * samplesFFT, sizeof(float), MALLOC_CAP_8BIT); // SIMD requires aligned memory to 16-byte boundary. note in IDF5 there is MALLOC_CAP_SIMD available
|
||||
if ((valFFT == nullptr)) return; // something went wrong
|
||||
}
|
||||
// create window
|
||||
if (windowFFT == nullptr) {
|
||||
windowFFT = (float*)heap_caps_aligned_calloc(16, samplesFFT, sizeof(float), MALLOC_CAP_8BIT); // SIMD requires aligned memory to 16-byte boundary. note in IDF5 there is MALLOC_CAP_SIMD available
|
||||
if ((windowFFT == nullptr)) {
|
||||
heap_caps_free(valFFT); valFFT = nullptr;
|
||||
return; // something went wrong
|
||||
}
|
||||
}
|
||||
if (dsps_fft2r_init_fc32(NULL, samplesFFT) != ESP_OK) return; // initialize FFT tables
|
||||
// create window function for FFT
|
||||
#ifdef FFT_PREFER_EXACT_PEAKS
|
||||
dsps_wind_blackman_harris_f32(windowFFT, samplesFFT);
|
||||
#else
|
||||
dsps_wind_flat_top_f32(windowFFT, samplesFFT);
|
||||
#endif
|
||||
#else
|
||||
// use integer FFT - allocate and initialize integer FFT buffers on first call, 4 bytes aligned (just in case, even if not strictly needed for int16_t)
|
||||
if (valFFT == nullptr) valFFT = (int16_t*) heap_caps_aligned_calloc(4, samplesFFT * 2, sizeof(int16_t), MALLOC_CAP_8BIT);
|
||||
// create window
|
||||
if (windowFFT == nullptr) windowFFT = (int16_t*) heap_caps_aligned_calloc(4, samplesFFT, sizeof(int16_t), MALLOC_CAP_8BIT);
|
||||
// create window function for FFT
|
||||
float *windowFloat = (float*) heap_caps_aligned_calloc(4, samplesFFT, sizeof(float), MALLOC_CAP_8BIT); // temporary buffer for window function
|
||||
if (windowFloat == nullptr || windowFFT == nullptr || valFFT == nullptr) { // something went wrong
|
||||
if (windowFloat) heap_caps_free(windowFloat);
|
||||
if (windowFFT) heap_caps_free(windowFFT); windowFFT = nullptr;
|
||||
if (valFFT) heap_caps_free(valFFT); valFFT = nullptr;
|
||||
return;
|
||||
}
|
||||
if (dsps_fft2r_init_sc16(NULL, samplesFFT) != ESP_OK) return; // initialize FFT tables
|
||||
|
||||
#ifdef FFT_PREFER_EXACT_PEAKS
|
||||
dsps_wind_blackman_harris_f32(windowFloat, samplesFFT);
|
||||
#else
|
||||
dsps_wind_flat_top_f32(windowFloat, samplesFFT);
|
||||
#endif
|
||||
// convert float window to 16-bit int
|
||||
for (int i = 0; i < samplesFFT; i++) {
|
||||
windowFFT[i] = (int16_t)(windowFloat[i] * 32767.0f);
|
||||
}
|
||||
heap_caps_free(windowFloat); // free temporary buffer
|
||||
#endif
|
||||
ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, true);
|
||||
|
||||
// see https://www.freertos.org/vtaskdelayuntil.html
|
||||
const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS;
|
||||
@@ -362,7 +251,8 @@ void FFTcode(void * parameter)
|
||||
#endif
|
||||
|
||||
// get a fresh batch of samples from I2S
|
||||
if (audioSource) audioSource->getSamples(valFFT, samplesFFT); // note: valFFT is used as a int16_t buffer on C3 and S2, could optimize RAM use by only allocating half the size (but makes code harder to read)
|
||||
if (audioSource) audioSource->getSamples(vReal, samplesFFT);
|
||||
memset(vImag, 0, samplesFFT * sizeof(float)); // set imaginary parts to 0
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
|
||||
if (start < esp_timer_get_time()) { // filter out overflows
|
||||
@@ -374,15 +264,16 @@ void FFTcode(void * parameter)
|
||||
|
||||
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
||||
|
||||
// band pass filter - can reduce noise floor by a factor of 50 and avoid aliasing effects to base & high frequency bands
|
||||
// band pass filter - can reduce noise floor by a factor of 50
|
||||
// downside: frequencies below 100Hz will be ignored
|
||||
if (useMicFilter) runMicFilter(samplesFFT, valFFT);
|
||||
if (useBandPassFilter) runMicFilter(samplesFFT, vReal);
|
||||
|
||||
// find highest sample in the batch
|
||||
FFTsampleType maxSample = 0; // max sample from FFT batch
|
||||
float maxSample = 0.0f; // max sample from FFT batch
|
||||
for (int i=0; i < samplesFFT; i++) {
|
||||
// pick our our current mic sample - we take the max value from all samples that go into FFT
|
||||
if ((valFFT[i] <= (INT16_MAX - 1024)) && (valFFT[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
|
||||
if (FFTabs(valFFT[i]) > maxSample) maxSample = FFTabs(valFFT[i]);
|
||||
if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
|
||||
if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]);
|
||||
}
|
||||
// release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function
|
||||
// early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results.
|
||||
@@ -394,96 +285,32 @@ void FFTcode(void * parameter)
|
||||
if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed.
|
||||
#endif
|
||||
|
||||
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
// run Arduino FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2, ~20ms on ESP32-C3)
|
||||
memset(vImag, 0, samplesFFT * sizeof(float)); // set imaginary parts to 0
|
||||
// run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2)
|
||||
FFT.dcRemoval(); // remove DC offset
|
||||
#ifdef FFT_PREFER_EXACT_PEAKS
|
||||
FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection
|
||||
#else
|
||||
FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy
|
||||
#endif
|
||||
//FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection
|
||||
FFT.compute( FFTDirection::Forward ); // Compute FFT
|
||||
FFT.complexToMagnitude(); // Compute magnitudes
|
||||
valFFT[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues.
|
||||
FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
|
||||
// note: scaling is done in fftAddAvg(), so we don't scale here
|
||||
#else
|
||||
// run run float DSP FFT (takes ~x ms on ESP32, ~x ms on ESP32-S2, , ~x ms on ESP32-C3) TODO: test and fill in these values
|
||||
// remove DC offset
|
||||
FFTmathType sum = 0;
|
||||
for (int i = 0; i < samplesFFT; i++) sum += valFFT[i];
|
||||
FFTmathType mean = sum / (FFTmathType)samplesFFT;
|
||||
for (int i = 0; i < samplesFFT; i++) valFFT[i] -= mean;
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
//apply window function to samples and fill buffer with interleaved complex values [Re,Im,Re,Im,...]
|
||||
for (int i = samplesFFT - 1; i >= 0 ; i--) {
|
||||
// fill the buffer back to front to avoid overwriting samples
|
||||
float windowed_sample = valFFT[i] * windowFFT[i];
|
||||
valFFT[i * 2] = windowed_sample;
|
||||
valFFT[i * 2 + 1] = 0.0; // set imaginary part to zero
|
||||
}
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
dsps_fft2r_fc32_aes3(valFFT, samplesFFT); // ESP32 S3 optimized version of FFT
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
dsps_fft2r_fc32_ae32(valFFT, samplesFFT); // ESP32 optimized version of FFT
|
||||
#else
|
||||
dsps_fft2r_fc32_ansi(valFFT, samplesFFT); // perform FFT using ANSI C implementation
|
||||
#endif
|
||||
dsps_bit_rev_fc32(valFFT, samplesFFT); // bit reverse
|
||||
valFFT[0] = 0; // set DC bin to 0, as it is not needed and can cause issues
|
||||
// convert to magnitude & find FFT_MajorPeak and FFT_Magnitude
|
||||
FFT_MajorPeak = 0;
|
||||
FFT_Magnitude = 0;
|
||||
for (int i = 1; i < samplesFFT_2; i++) { // skip [0] as it is DC offset
|
||||
float real_part = valFFT[i * 2];
|
||||
float imag_part = valFFT[i * 2 + 1];
|
||||
valFFT[i] = sqrtf(real_part * real_part + imag_part * imag_part);
|
||||
if (valFFT[i] > FFT_Magnitude) {
|
||||
FFT_Magnitude = valFFT[i];
|
||||
FFT_MajorPeak = i*(SAMPLE_RATE/samplesFFT);
|
||||
}
|
||||
// note: scaling is done in fftAddAvg(), so we don't scale here
|
||||
}
|
||||
#else
|
||||
// run integer DSP FFT (takes ~x ms on ESP32, ~x ms on ESP32-S2, , ~1.5 ms on ESP32-C3) TODO: test and fill in these values
|
||||
//apply window function to samples and fill buffer with interleaved complex values [Re,Im,Re,Im,...]
|
||||
for (int i = samplesFFT - 1; i >= 0 ; i--) {
|
||||
// fill the buffer back to front to avoid overwriting samples
|
||||
int16_t windowed_sample = ((int32_t)valFFT[i] * (int32_t)windowFFT[i]) >> 15; // both values are ±15bit
|
||||
valFFT[i * 2] = windowed_sample;
|
||||
valFFT[i * 2 + 1] = 0; // set imaginary part to zero
|
||||
}
|
||||
dsps_fft2r_sc16_ansi(valFFT, samplesFFT); // perform FFT on complex value pairs (Re,Im)
|
||||
dsps_bit_rev_sc16_ansi(valFFT, samplesFFT); // bit reverse i.e. "unshuffle" the results
|
||||
valFFT[0] = 0; // set DC bin to 0, as it is not needed and can cause issues
|
||||
// convert to magnitude, FFT returns interleaved complex values [Re,Im,Re,Im,...]
|
||||
int FFT_MajorPeak_int = 0;
|
||||
int FFT_Magnitude_int = 0;
|
||||
for (int i = 1; i < samplesFFT_2; i++) { // skip [0], it is DC offset
|
||||
int32_t real_part = valFFT[i * 2];
|
||||
int32_t imag_part = valFFT[i * 2 + 1];
|
||||
valFFT[i] = sqrt32_bw(real_part * real_part + imag_part * imag_part); // note: this should never overflow as Re and Im form a vector of maximum length 32767
|
||||
if (valFFT[i] > FFT_Magnitude_int) {
|
||||
FFT_Magnitude_int = valFFT[i];
|
||||
FFT_MajorPeak_int = ((i * SAMPLE_RATE)/samplesFFT);
|
||||
}
|
||||
// note: scaling is done in fftAddAvg(), so we don't scale here
|
||||
}
|
||||
FFT_Magnitude = FFT_Magnitude_int * 512; // scale to match raw float value
|
||||
FFT_MajorPeak = FFT_MajorPeak_int;
|
||||
#endif
|
||||
#endif
|
||||
vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues.
|
||||
|
||||
FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
|
||||
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
|
||||
haveDoneFFT = true;
|
||||
#endif
|
||||
} else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this -> set all samples to 0
|
||||
memset(valFFT, 0, samplesFFT * sizeof(FFTsampleType));
|
||||
|
||||
} else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this.
|
||||
memset(vReal, 0, samplesFFT * sizeof(float));
|
||||
FFT_MajorPeak = 1;
|
||||
FFT_Magnitude = 0.001;
|
||||
}
|
||||
|
||||
for (int i = 0; i < samplesFFT; i++) {
|
||||
float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way
|
||||
vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max.
|
||||
} // for()
|
||||
|
||||
// mapping of FFT result bins to frequency channels
|
||||
if (fabsf(sampleAvg) > 0.5f) { // noise gate open
|
||||
#if 0
|
||||
@@ -514,7 +341,7 @@ void FFTcode(void * parameter)
|
||||
fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate
|
||||
#else
|
||||
/* new mapping, optimized for 22050 Hz by softhack007 */
|
||||
// bins frequency range
|
||||
// bins frequency range
|
||||
if (useBandPassFilter) {
|
||||
// skip frequencies below 100hz
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(3,4);
|
||||
@@ -576,15 +403,12 @@ void FFTcode(void * parameter)
|
||||
// Pre / Postprocessing //
|
||||
///////////////////////////
|
||||
|
||||
static void runMicFilter(uint16_t numSamples, FFTsampleType *sampleBuffer) // pre-filtering of raw samples (band-pass)
|
||||
static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass)
|
||||
{
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
// low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency (alpha = 2π × fc / fs)
|
||||
// low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency
|
||||
//constexpr float alpha = 0.04f; // 150Hz
|
||||
//constexpr float alpha = 0.03f; // 110Hz
|
||||
//constexpr float alpha = 0.0285f; //100Hz
|
||||
constexpr float alpha = 0.0256f; //90Hz
|
||||
//constexpr float alpha = 0.0225f; // 80hz
|
||||
constexpr float alpha = 0.0225f; // 80hz
|
||||
//constexpr float alpha = 0.01693f;// 60hz
|
||||
// high frequency cutoff parameter
|
||||
//constexpr float beta1 = 0.75f; // 11Khz
|
||||
@@ -608,39 +432,6 @@ static void runMicFilter(uint16_t numSamples, FFTsampleType *sampleBuffer)
|
||||
lowfilt += alpha * (sampleBuffer[i] - lowfilt);
|
||||
sampleBuffer[i] = sampleBuffer[i] - lowfilt;
|
||||
}
|
||||
#else
|
||||
// low frequency cutoff parameter 17.15 fixed point format
|
||||
//constexpr int32_t ALPHA_FP = 1311; // 0.04f * (1<<15) (150Hz)
|
||||
//constexpr int32_t ALPHA_FP = 983; // 0.03f * (1<<15) (110Hz)
|
||||
//constexpr int32_t ALPHA_FP = 934; // 0.0285f * (1<<15) (100Hz)
|
||||
constexpr int32_t ALPHA_FP = 840; // 0.0256f * (1<<15) (90Hz)
|
||||
//constexpr int32_t ALPHA_FP = 737; // 0.0225f * (1<<15) (80Hz)
|
||||
//constexpr int32_t ALPHA_FP = 555; // 0.01693f * (1<<15) (60Hz)
|
||||
|
||||
// high frequency cutoff parameters 16.16 fixed point format
|
||||
//constexpr int32_t BETA1_FP = 49152; // 0.75f * (1<<16) (11KHz)
|
||||
//constexpr int32_t BETA1_FP = 53740; // 0.82f * (1<<16) (15KHz)
|
||||
//constexpr int32_t BETA1_FP = 54297; // 0.8285f * (1<<16) (18KHz)
|
||||
constexpr int32_t BETA1_FP = 55706; // 0.85f * (1<<16) (20KHz)
|
||||
constexpr int32_t BETA2_FP = (65536 - BETA1_FP) / 2; // ((1.0f - beta1) / 2.0f) * (1<<16)
|
||||
|
||||
static int32_t last_vals[2] = { 0 }; // FIR high freq cutoff filter (scaled by sample range)
|
||||
static int32_t lowfilt_fp = 0; // IIR low frequency cutoff filter (16.16 fixed point)
|
||||
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
// FIR lowpass filter to remove high frequency noise
|
||||
int32_t highFilteredSample_fp;
|
||||
|
||||
if (i < (numSamples - 1))
|
||||
highFilteredSample_fp = (BETA1_FP * (int32_t)sampleBuffer[i] + BETA2_FP * last_vals[0] + BETA2_FP * (int32_t)sampleBuffer[i + 1]) >> 16; // smooth out spikes
|
||||
else
|
||||
highFilteredSample_fp = (BETA1_FP * (int32_t)sampleBuffer[i] + BETA2_FP * last_vals[0] + BETA2_FP * last_vals[1]) >> 16; // special handling for last sample in array
|
||||
last_vals[1] = last_vals[0];
|
||||
last_vals[0] = (int32_t)sampleBuffer[i];
|
||||
lowfilt_fp += ALPHA_FP * (highFilteredSample_fp - (lowfilt_fp >> 15)); // low pass filter in 17.15 fixed point format
|
||||
sampleBuffer[i] = highFilteredSample_fp - (lowfilt_fp >> 15);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels
|
||||
@@ -729,7 +520,7 @@ static void detectSamplePeak(void) {
|
||||
// Poor man's beat detection by seeing if sample > Average + some value.
|
||||
// This goes through ALL of the 255 bins - but ignores stupid settings
|
||||
// Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync.
|
||||
if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (valFFT[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) {
|
||||
if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) {
|
||||
havePeak = true;
|
||||
}
|
||||
|
||||
@@ -1374,8 +1165,8 @@ class AudioReactive : public Usermod {
|
||||
periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3
|
||||
#endif
|
||||
delay(100); // Give that poor microphone some time to setup.
|
||||
useBandPassFilter = false; // filter cuts lowest and highest frequency bands from FFT result (use on very noisy mic inputs)
|
||||
useMicFilter = true; // filter fixes aliasing to base & highest frequency bands and reduces noise floor (recommended for all mic inputs)
|
||||
|
||||
useBandPassFilter = false;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone
|
||||
@@ -1410,7 +1201,6 @@ class AudioReactive : public Usermod {
|
||||
case 4:
|
||||
DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
|
||||
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f);
|
||||
useMicFilter = false; // I2S with Master Clock is mostly used for line-in, skip sample filtering
|
||||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
@@ -1426,7 +1216,6 @@ class AudioReactive : public Usermod {
|
||||
case 6:
|
||||
DEBUGSR_PRINTLN(F("AR: ES8388 Source"));
|
||||
audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE);
|
||||
useMicFilter = false;
|
||||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
@@ -2219,12 +2008,12 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) {
|
||||
case 2:
|
||||
b = map(x, 0, 255, 0, NUM_GEQ_CHANNELS/2); // convert palette position to lower half of freq band
|
||||
hsv = CHSV(fftResult[b], 255, x);
|
||||
value = hsv; // convert to R,G,B
|
||||
hsv2rgb_rainbow(hsv, value); // convert to R,G,B
|
||||
break;
|
||||
case 1:
|
||||
b = map(x, 1, 255, 0, 10); // convert palette position to lower half of freq band
|
||||
hsv = CHSV(fftResult[b], 255, map(fftResult[b], 0, 255, 30, 255)); // pick hue
|
||||
value = hsv; // convert to R,G,B
|
||||
hsv2rgb_rainbow(hsv, value); // convert to R,G,B
|
||||
break;
|
||||
default:
|
||||
if (x == 1) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents
|
||||
// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||
// there are two things in these MCUs that could lead to problems with audio processing:
|
||||
// * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x)
|
||||
// * single core, so FFT task might slow down other things like LED updates
|
||||
@@ -134,7 +134,7 @@ class AudioSource {
|
||||
Read num_samples from the microphone, and store them in the provided
|
||||
buffer
|
||||
*/
|
||||
virtual void getSamples(FFTsampleType *buffer, uint16_t num_samples) = 0;
|
||||
virtual void getSamples(float *buffer, uint16_t num_samples) = 0;
|
||||
|
||||
/* check if the audio source driver was initialized successfully */
|
||||
virtual bool isInitialized(void) {return(_initialized);}
|
||||
@@ -316,7 +316,7 @@ class I2SSource : public AudioSource {
|
||||
if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive);
|
||||
}
|
||||
|
||||
virtual void getSamples(FFTsampleType *buffer, uint16_t num_samples) {
|
||||
virtual void getSamples(float *buffer, uint16_t num_samples) {
|
||||
if (_initialized) {
|
||||
esp_err_t err;
|
||||
size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
|
||||
@@ -334,36 +334,19 @@ class I2SSource : public AudioSource {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store samples in sample buffer
|
||||
#if defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
//constexpr int32_t FIXEDSHIFT = 8; // shift by 8 bits for fixed point math (no loss at 24bit input sample resolution)
|
||||
//int32_t intSampleScale = _sampleScale * (1<<FIXEDSHIFT); // _sampleScale <= 1.0f, shift for fixed point math
|
||||
#endif
|
||||
|
||||
// Store samples in sample buffer and update DC offset
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples)
|
||||
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
float currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places
|
||||
#else
|
||||
float currSample = (float) newSamples[i]; // 16bit input -> use as-is
|
||||
#endif
|
||||
buffer[i] = currSample;
|
||||
buffer[i] *= _sampleScale; // scale samples
|
||||
newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples)
|
||||
|
||||
float currSample = 0.0f;
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places
|
||||
#else
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
// note on sample scaling: scaling is only used for inputs with master clock and those are better suited for ESP32 or S3
|
||||
// execution speed is critical on single core MCUs
|
||||
//int32_t currSample = newSamples[i] >> FIXEDSHIFT; // shift to avoid overlow in multiplication
|
||||
//currSample = (currSample * intSampleScale) >> 16; // scale samples, shift down to 16bit
|
||||
int16_t currSample = newSamples[i] >> 16; // no sample scaling, just shift down to 16bit (not scaling saves ~0.4ms on C3)
|
||||
#else
|
||||
//int32_t currSample = (newSamples[i] * intSampleScale) >> FIXEDSHIFT; // scale samples, shift back down to 16bit
|
||||
int16_t currSample = newSamples[i]; // 16bit input -> use as-is
|
||||
#endif
|
||||
buffer[i] = (int16_t)currSample;
|
||||
currSample = (float) newSamples[i]; // 16bit input -> use as-is
|
||||
#endif
|
||||
buffer[i] = currSample;
|
||||
buffer[i] *= _sampleScale; // scale samples
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -706,7 +689,7 @@ class I2SAdcSource : public I2SSource {
|
||||
}
|
||||
|
||||
|
||||
void getSamples(FFTsampleType *buffer, uint16_t num_samples) {
|
||||
void getSamples(float *buffer, uint16_t num_samples) {
|
||||
/* Enable ADC. This has to be enabled and disabled directly before and
|
||||
* after sampling, otherwise Wifi dies
|
||||
*/
|
||||
|
||||
@@ -89,29 +89,28 @@ class DeepSleepUsermod : public Usermod {
|
||||
int currentWeekday = weekdayMondayFirst(); // 1=Monday ... 7=Sunday
|
||||
int minDifference = INT_MAX;
|
||||
|
||||
for (size_t i = 0; i < timers.size(); i++) {
|
||||
const Timer& t = timers[i];
|
||||
// only regular enabled timers with valid date range can be used for wake scheduling
|
||||
if (!t.isEnabled() || !t.isRegular()) continue;
|
||||
if (!isTodayInDateRange(t.monthStart, t.dayStart, t.monthEnd, t.dayEnd)) continue;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
// check if timer is enabled and date is in range, also wakes up if no macro is used
|
||||
if ((timerWeekday[i] & 0x01) && isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i])) {
|
||||
|
||||
// check all weekdays for the current timer, starting from today
|
||||
for (int dayOffset = 0; dayOffset < 7; dayOffset++) {
|
||||
int checkWeekday = (currentWeekday + dayOffset) % 7; // 1-7, check all weekdays starting from today
|
||||
if (checkWeekday == 0) {
|
||||
checkWeekday = 7; // sunday is 7 not 0
|
||||
}
|
||||
// if timer is enabled (bit0 of timerWeekday) and date is in range, check all weekdays it is set for
|
||||
for (int dayOffset = 0; dayOffset < 7; dayOffset++) {
|
||||
int checkWeekday = ((currentWeekday + dayOffset) % 7); // 1-7, check all weekdays starting from today
|
||||
if (checkWeekday == 0) {
|
||||
checkWeekday = 7; // sunday is 7 not 0
|
||||
}
|
||||
|
||||
int targetHour = t.hour;
|
||||
int targetMinute = t.minute;
|
||||
if ((t.weekdays >> checkWeekday) & 0x01) {
|
||||
if (dayOffset == 0 && (targetHour < currentHour || (targetHour == currentHour && targetMinute <= currentMinute)))
|
||||
continue; // skip if time has already passed today
|
||||
int targetHour = timerHours[i];
|
||||
int targetMinute = timerMinutes[i];
|
||||
if ((timerWeekday[i] >> (checkWeekday)) & 0x01) {
|
||||
if (dayOffset == 0 && (targetHour < currentHour || (targetHour == currentHour && targetMinute <= currentMinute)))
|
||||
continue; // skip if time has already passed today
|
||||
|
||||
int timeDifference = calculateTimeDifference(currentHour, currentMinute, targetHour + (dayOffset * 24), targetMinute);
|
||||
if (timeDifference < minDifference) {
|
||||
minDifference = timeDifference;
|
||||
wakeupPreset = t.preset;
|
||||
int timeDifference = calculateTimeDifference(currentHour, currentMinute, targetHour + (dayOffset * 24), targetMinute);
|
||||
if (timeDifference < minDifference) {
|
||||
minDifference = timeDifference;
|
||||
wakeupPreset = timerMacro[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
byte currentColors[3];
|
||||
byte lastKnownBri = 0;
|
||||
|
||||
inline uint32_t colorFromRgb(byte* rgb) { return uint32_t((byte(rgb[0]) << 16) | (byte(rgb[1]) << 8) | (byte(rgb[2]))); }
|
||||
|
||||
void initRotaryEncoder()
|
||||
{
|
||||
@@ -80,7 +79,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
for (int i = 0; i < currentPos / incrementPerClick - 1; i++) {
|
||||
ledBus->setPixelColor(i, 0);
|
||||
}
|
||||
ledBus->setPixelColor(currentPos / incrementPerClick - 1, colorFromRgb(currentColors));
|
||||
ledBus->setPixelColor(currentPos / incrementPerClick - 1, colorFromRgbw(currentColors));
|
||||
for (int i = currentPos / incrementPerClick; i < numLeds; i++) {
|
||||
ledBus->setPixelColor(i, 0);
|
||||
}
|
||||
@@ -96,7 +95,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
if (ledMode == 3) {
|
||||
hsv2rgb((i) / float(numLeds), 1, .25);
|
||||
}
|
||||
ledBus->setPixelColor(i, colorFromRgb(currentColors));
|
||||
ledBus->setPixelColor(i, colorFromRgbw(currentColors));
|
||||
}
|
||||
for (int i = currentPos / incrementPerClick; i < numLeds; i++) {
|
||||
ledBus->setPixelColor(i, 0);
|
||||
|
||||
@@ -117,7 +117,7 @@ static const char _data_FX_MODE_DIFFUSIONFIRE[] PROGMEM = "Diffusion Fire@!,Spar
|
||||
|
||||
static void mode_spinning_wheel(void) {
|
||||
if (SEGLEN < 1) FX_FALLBACK_STATIC;
|
||||
|
||||
|
||||
unsigned strips = SEGMENT.nrOfVStrips();
|
||||
if (strips == 0) FX_FALLBACK_STATIC;
|
||||
|
||||
@@ -148,6 +148,7 @@ static void mode_spinning_wheel(void) {
|
||||
|
||||
// Handle random seeding globally (outside the virtual strip)
|
||||
if (SEGENV.call == 0) {
|
||||
random16_set_seed(hw_random16());
|
||||
SEGENV.aux1 = (255 << 8) / SEGLEN; // Cache the color scaling
|
||||
}
|
||||
|
||||
@@ -155,6 +156,7 @@ static void mode_spinning_wheel(void) {
|
||||
uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom3 + SEGMENT.check1 + SEGMENT.check3;
|
||||
bool settingsChanged = (SEGENV.aux0 != settingssum);
|
||||
if (settingsChanged) {
|
||||
random16_add_entropy(hw_random16());
|
||||
SEGENV.aux0 = settingssum;
|
||||
}
|
||||
|
||||
@@ -164,7 +166,7 @@ static void mode_spinning_wheel(void) {
|
||||
uint8_t spinnerSize = map(SEGMENT.custom1, 0, 255, 1, 10);
|
||||
uint16_t spin_delay = map(SEGMENT.custom3, 0, 31, 2000, 15000);
|
||||
uint32_t now = strip.now;
|
||||
|
||||
|
||||
for (unsigned stripNr = 0; stripNr < strips; stripNr += spinnerSize) {
|
||||
uint32_t* stripState = &state[stripNr * stateVarsPerStrip];
|
||||
// Check if this spinner is stopped AND has waited its delay
|
||||
@@ -209,23 +211,23 @@ static void mode_spinning_wheel(void) {
|
||||
// Initialize or restart
|
||||
if (needsReset && SEGMENT.check1) { // spin the spinner(s) only if the "Spin me!" checkbox is enabled
|
||||
state[CUR_POS_IDX] = 0;
|
||||
|
||||
|
||||
// Set velocity
|
||||
uint16_t speed = map(SEGMENT.speed, 0, 255, 300, 800);
|
||||
if (speed == 300) { // random speed (user selected 0 on speed slider)
|
||||
state[VELOCITY_IDX] = hw_random16(200, 900) * 655; // fixed-point velocity scaling (approx. 65536/100)
|
||||
state[VELOCITY_IDX] = random16(200, 900) * 655; // fixed-point velocity scaling (approx. 65536/100)
|
||||
} else {
|
||||
state[VELOCITY_IDX] = hw_random16(speed - 100, speed + 100) * 655;
|
||||
state[VELOCITY_IDX] = random16(speed - 100, speed + 100) * 655;
|
||||
}
|
||||
|
||||
|
||||
// Set slowdown start time
|
||||
uint16_t slowdown = map(SEGMENT.intensity, 0, 255, 3000, 5000);
|
||||
if (slowdown == 3000) { // random slowdown start time (user selected 0 on intensity slider)
|
||||
state[SLOWDOWN_TIME_IDX] = now + hw_random16(2000, 6000);
|
||||
state[SLOWDOWN_TIME_IDX] = now + random16(2000, 6000);
|
||||
} else {
|
||||
state[SLOWDOWN_TIME_IDX] = now + hw_random16(slowdown - 1000, slowdown + 1000);
|
||||
state[SLOWDOWN_TIME_IDX] = now + random16(slowdown - 1000, slowdown + 1000);
|
||||
}
|
||||
|
||||
|
||||
state[PHASE_IDX] = 0;
|
||||
state[STOP_TIME_IDX] = 0;
|
||||
state[WOBBLE_STEP_IDX] = 0;
|
||||
@@ -248,10 +250,10 @@ static void mode_spinning_wheel(void) {
|
||||
// Slowing phase - apply deceleration
|
||||
uint32_t decel = velocity / 80;
|
||||
if (decel < 100) decel = 100;
|
||||
|
||||
|
||||
velocity = (velocity > decel) ? velocity - decel : 0;
|
||||
state[VELOCITY_IDX] = velocity;
|
||||
|
||||
|
||||
// Check if stopped
|
||||
if (velocity < 2000) {
|
||||
velocity = 0;
|
||||
@@ -268,7 +270,7 @@ static void mode_spinning_wheel(void) {
|
||||
uint32_t wobble_step = state[WOBBLE_STEP_IDX];
|
||||
uint16_t stop_pos = state[STOP_POS_IDX];
|
||||
uint32_t elapsed = now - state[WOBBLE_TIME_IDX];
|
||||
|
||||
|
||||
if (wobble_step == 0 && elapsed >= 200) {
|
||||
// Move back one LED from stop position
|
||||
uint16_t back_pos = (stop_pos == 0) ? SEGLEN - 1 : stop_pos - 1;
|
||||
@@ -289,13 +291,13 @@ static void mode_spinning_wheel(void) {
|
||||
state[STOP_TIME_IDX] = now;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update position (phases 0 and 1 only)
|
||||
if (phase == 0 || phase == 1) {
|
||||
pos_fixed += velocity;
|
||||
state[CUR_POS_IDX] = pos_fixed;
|
||||
}
|
||||
|
||||
|
||||
// Draw LED for all phases
|
||||
uint16_t pos = (pos_fixed >> 16) % SEGLEN;
|
||||
|
||||
@@ -312,14 +314,14 @@ static void mode_spinning_wheel(void) {
|
||||
hue = (SEGENV.aux1 * pos) >> 8;
|
||||
}
|
||||
|
||||
uint32_t color = ColorFromPalette(SEGPALETTE, hue, 255, LINEARBLEND);
|
||||
uint32_t color = ColorFromPaletteWLED(SEGPALETTE, hue, 255, LINEARBLEND);
|
||||
|
||||
// Draw the spinner with configurable size (1-10 LEDs)
|
||||
for (int8_t x = 0; x < spinnerSize; x++) {
|
||||
for (uint8_t y = 0; y < spinnerSize; y++) {
|
||||
uint16_t drawPos = (pos + y) % SEGLEN;
|
||||
int16_t drawStrip = stripNr + x;
|
||||
|
||||
|
||||
// Wrap horizontally if needed, or skip if out of bounds
|
||||
if (drawStrip >= 0 && drawStrip < strips) {
|
||||
SEGMENT.setPixelColor(indexToVStrip(drawPos, drawStrip), color);
|
||||
@@ -368,7 +370,7 @@ typedef struct LavaParticle {
|
||||
|
||||
static void mode_2D_lavalamp(void) {
|
||||
if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up
|
||||
|
||||
|
||||
const uint16_t cols = SEG_W;
|
||||
const uint16_t rows = SEG_H;
|
||||
constexpr float MAX_BLOB_RADIUS = 20.0f; // cap to prevent frame rate drops on large matrices
|
||||
@@ -403,7 +405,7 @@ static void mode_2D_lavalamp(void) {
|
||||
|
||||
uint8_t size = currentSize;
|
||||
uint8_t numParticles = currentNumParticles;
|
||||
|
||||
|
||||
// blob size based on matrix width
|
||||
const float minSize = cols * 0.15f; // Minimum 15% of width
|
||||
const float maxSize = cols * 0.4f; // Maximum 40% of width
|
||||
@@ -425,7 +427,7 @@ static void mode_2D_lavalamp(void) {
|
||||
lavaParticles[i].y = rows - 1;
|
||||
lavaParticles[i].vx = (hw_random16(7) - 3) / 250.0f;
|
||||
lavaParticles[i].vy = -(hw_random16(20) + 10) / 100.0f * 0.3f;
|
||||
|
||||
|
||||
lavaParticles[i].size = minSize + (float)hw_random16(rangeInt);
|
||||
if (lavaParticles[i].size > MAX_BLOB_RADIUS) lavaParticles[i].size = MAX_BLOB_RADIUS;
|
||||
|
||||
@@ -442,7 +444,7 @@ static void mode_2D_lavalamp(void) {
|
||||
|
||||
// Fade background slightly for trailing effect
|
||||
SEGMENT.fadeToBlackBy(40);
|
||||
|
||||
|
||||
// Update and draw particles
|
||||
int activeCount = 0;
|
||||
unsigned long currentMillis = strip.now;
|
||||
@@ -458,21 +460,21 @@ static void mode_2D_lavalamp(void) {
|
||||
}
|
||||
|
||||
LavaParticle *p = &lavaParticles[i];
|
||||
|
||||
|
||||
// Physics update
|
||||
p->x += p->vx;
|
||||
p->y += p->vy;
|
||||
|
||||
|
||||
// Optional particle/blob attraction
|
||||
if (SEGMENT.check2) {
|
||||
for (int j = 0; j < MAX_LAVA_PARTICLES; j++) {
|
||||
if (i == j || !lavaParticles[j].active) continue;
|
||||
|
||||
|
||||
LavaParticle *other = &lavaParticles[j];
|
||||
|
||||
|
||||
// Skip attraction if moving in same vertical direction (both up or both down)
|
||||
if ((p->vy < 0 && other->vy < 0) || (p->vy > 0 && other->vy > 0)) continue;
|
||||
|
||||
|
||||
float dx = other->x - p->x;
|
||||
float dy = other->y - p->y;
|
||||
|
||||
@@ -578,7 +580,7 @@ static void mode_2D_lavalamp(void) {
|
||||
// Get color
|
||||
uint32_t color;
|
||||
color = SEGMENT.color_from_palette(p->hue, true, PALETTE_SOLID_WRAP, 0);
|
||||
|
||||
|
||||
// Extract RGB and apply life/opacity
|
||||
uint8_t w = (W(color) * 255) >> 8;
|
||||
uint8_t r = (R(color) * 255) >> 8;
|
||||
@@ -598,7 +600,7 @@ static void mode_2D_lavalamp(void) {
|
||||
for (int dx = -(int)p->size - 1; dx <= (int)p->size + 1; dx++) {
|
||||
int px = centerX + dx;
|
||||
int py = centerY + dy;
|
||||
|
||||
|
||||
if (px < 0 || px >= cols || py < 0 || py >= rows) continue;
|
||||
|
||||
// Sub-pixel distance: measure from true float center to pixel center
|
||||
@@ -665,23 +667,23 @@ static void drawMagma(const uint16_t width, const uint16_t height, float *ff_y,
|
||||
static void drawLavaBombs(const uint16_t width, const uint16_t height, float *particleData, float gravity, uint8_t particleCount) {
|
||||
for (uint16_t i = 0; i < particleCount; i++) {
|
||||
uint16_t idx = i * 4;
|
||||
|
||||
|
||||
particleData[idx + 3] -= gravity;
|
||||
particleData[idx + 0] += particleData[idx + 2];
|
||||
particleData[idx + 1] += particleData[idx + 3];
|
||||
|
||||
|
||||
float posX = particleData[idx + 0];
|
||||
float posY = particleData[idx + 1];
|
||||
|
||||
|
||||
if (posY > height + height / 4) {
|
||||
particleData[idx + 3] = -particleData[idx + 3] * 0.8f;
|
||||
}
|
||||
|
||||
|
||||
if (posY < (float)(height / 8) - 1.0f || posX < 0 || posX >= width) {
|
||||
particleData[idx + 0] = hw_random(0, width * 100) / 100.0f;
|
||||
particleData[idx + 1] = hw_random(0, height * 25) / 100.0f;
|
||||
particleData[idx + 2] = hw_random(-75, 75) / 100.0f;
|
||||
|
||||
|
||||
float baseVelocity = hw_random(60, 120) / 100.0f;
|
||||
if (hw_random8() < 50) {
|
||||
baseVelocity *= 1.6f;
|
||||
@@ -689,38 +691,38 @@ static void drawLavaBombs(const uint16_t width, const uint16_t height, float *pa
|
||||
particleData[idx + 3] = baseVelocity;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int16_t xi = (int16_t)posX;
|
||||
int16_t yi = (int16_t)posY;
|
||||
|
||||
|
||||
if (xi >= 0 && xi < width && yi >= 0 && yi < height) {
|
||||
// Get a random color from the current palette
|
||||
uint8_t randomIndex = hw_random8(64, 128);
|
||||
CRGB pcolor = ColorFromPalette(SEGPALETTE, randomIndex, 255, LINEARBLEND);
|
||||
CRGB pcolor = ColorFromPaletteWLED(SEGPALETTE, randomIndex, 255, LINEARBLEND);
|
||||
|
||||
// Pre-calculate anti-aliasing weights
|
||||
float xf = posX - xi;
|
||||
float yf = posY - yi;
|
||||
float ix = 1.0f - xf;
|
||||
float iy = 1.0f - yf;
|
||||
|
||||
|
||||
uint8_t w0 = 255 * ix * iy;
|
||||
uint8_t w1 = 255 * xf * iy;
|
||||
uint8_t w2 = 255 * ix * yf;
|
||||
uint8_t w3 = 255 * xf * yf;
|
||||
|
||||
|
||||
int16_t yFlipped = height - 1 - yi; // Flip Y coordinate
|
||||
|
||||
|
||||
SEGMENT.addPixelColorXY(xi, yFlipped, pcolor.scale8(w0));
|
||||
if (xi + 1 < width)
|
||||
SEGMENT.addPixelColorXY(xi + 1, yFlipped, pcolor.scale8(w1));
|
||||
if (yFlipped - 1 >= 0)
|
||||
SEGMENT.addPixelColorXY(xi, yFlipped - 1, pcolor.scale8(w2));
|
||||
if (xi + 1 < width && yFlipped - 1 >= 0)
|
||||
if (xi + 1 < width && yFlipped - 1 >= 0)
|
||||
SEGMENT.addPixelColorXY(xi + 1, yFlipped - 1, pcolor.scale8(w3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mode_2D_magma(void) {
|
||||
if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up
|
||||
@@ -744,7 +746,7 @@ static void mode_2D_magma(void) {
|
||||
uint32_t settingsKey = (uint32_t)SEGMENT.speed | ((uint32_t)SEGMENT.intensity << 8) |
|
||||
((uint32_t)SEGMENT.custom1 << 16) | ((uint32_t)SEGMENT.custom2 << 24);
|
||||
bool settingsChanged = (*settingsSumPtr != settingsKey);
|
||||
|
||||
|
||||
if (SEGENV.call == 0 || settingsChanged) {
|
||||
// Intensity slider controls magma height
|
||||
uint16_t intensity = SEGMENT.intensity;
|
||||
@@ -770,7 +772,7 @@ static void mode_2D_magma(void) {
|
||||
particleData[idx + 0] = hw_random(0, width * 100) / 100.0f;
|
||||
particleData[idx + 1] = hw_random(0, height * 25) / 100.0f;
|
||||
particleData[idx + 2] = hw_random(-75, 75) / 100.0f;
|
||||
|
||||
|
||||
float baseVelocity = hw_random(60, 120) / 100.0f;
|
||||
if (hw_random8() < 50) {
|
||||
baseVelocity *= 1.6f;
|
||||
@@ -791,7 +793,7 @@ static void mode_2D_magma(void) {
|
||||
|
||||
// Gravity control
|
||||
float gravity = map(SEGMENT.custom2, 0, 255, 5, 20) / 100.0f;
|
||||
|
||||
|
||||
// Number of particles (lava bombs)
|
||||
uint8_t particleCount = map(SEGMENT.custom1, 0, 255, 0, MAGMA_MAX_PARTICLES);
|
||||
particleCount = constrain(particleCount, 0, MAGMA_MAX_PARTICLES);
|
||||
@@ -1033,7 +1035,7 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s
|
||||
// Build morse code pattern into a buffer
|
||||
static void build_morsecode_pattern(const char *morse_code, uint8_t *pattern, uint8_t *wordIndex, uint16_t &index, uint8_t currentWord, int maxSize) {
|
||||
const char *c = morse_code;
|
||||
|
||||
|
||||
// Build the dots and dashes into pattern array
|
||||
while (*c != '\0') {
|
||||
// it's a dot which is 1 pixel
|
||||
@@ -1080,7 +1082,7 @@ static void build_morsecode_pattern(const char *morse_code, uint8_t *pattern, ui
|
||||
|
||||
static void mode_morsecode(void) {
|
||||
if (SEGLEN < 1) FX_FALLBACK_STATIC;
|
||||
|
||||
|
||||
// A-Z in Morse Code
|
||||
static const char * letters[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
|
||||
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
|
||||
@@ -1284,7 +1286,7 @@ class UserFxUsermod : public Usermod {
|
||||
// strip.addEffect(255, &mode_your_effect3, _data_FX_MODE_YOUR_EFFECT3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// If you want configuration options in the usermod settings page, implement these methods //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -96,14 +96,14 @@ public:
|
||||
fastled_col.red = colPri[0];
|
||||
fastled_col.green = colPri[1];
|
||||
fastled_col.blue = colPri[2];
|
||||
prim_hsv = rgb2hsv(fastled_col);
|
||||
prim_hsv = rgb2hsv_approximate(fastled_col);
|
||||
new_val = (int16_t)prim_hsv.h + fadeAmount;
|
||||
if (new_val > 255)
|
||||
new_val -= 255; // roll-over if bigger than 255
|
||||
if (new_val < 0)
|
||||
new_val += 255; // roll-over if smaller than 0
|
||||
prim_hsv.h = (byte)new_val;
|
||||
fastled_col = prim_hsv ;
|
||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||
colPri[0] = fastled_col.red;
|
||||
colPri[1] = fastled_col.green;
|
||||
colPri[2] = fastled_col.blue;
|
||||
@@ -121,14 +121,14 @@ public:
|
||||
fastled_col.red = colPri[0];
|
||||
fastled_col.green = colPri[1];
|
||||
fastled_col.blue = colPri[2];
|
||||
prim_hsv = rgb2hsv(fastled_col);
|
||||
prim_hsv = rgb2hsv_approximate(fastled_col);
|
||||
new_val = (int16_t)prim_hsv.h - fadeAmount;
|
||||
if (new_val > 255)
|
||||
new_val -= 255; // roll-over if bigger than 255
|
||||
if (new_val < 0)
|
||||
new_val += 255; // roll-over if smaller than 0
|
||||
prim_hsv.h = (byte)new_val;
|
||||
fastled_col = prim_hsv;
|
||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||
colPri[0] = fastled_col.red;
|
||||
colPri[1] = fastled_col.green;
|
||||
colPri[2] = fastled_col.blue;
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"name": "animartrix",
|
||||
"build": { "libArchive": false },
|
||||
"dependencies": {
|
||||
"Animartrix": "https://github.com/netmindz/animartrix.git#81eb09b91c8c9c8c01f8ea442787f8127d56c72f"
|
||||
"Animartrix": "https://github.com/netmindz/animartrix.git#b172586"
|
||||
}
|
||||
}
|
||||
|
||||
407
wled00/FX.cpp
407
wled00/FX.cpp
@@ -12,10 +12,7 @@
|
||||
|
||||
#include "wled.h"
|
||||
#include "FX.h"
|
||||
#include "fontmanager.h"
|
||||
#include "fcn_declare.h"
|
||||
#include "colors.h"
|
||||
#include "prng.h"
|
||||
|
||||
#define FX_FALLBACK_STATIC { mode_static(); return; }
|
||||
|
||||
@@ -84,14 +81,17 @@
|
||||
//#define MAX_FREQUENCY 5120
|
||||
//#define MAX_FREQ_LOG10 3.71f
|
||||
|
||||
static PRNG prng(hw_random()); // pseudo-random number generator class, seed = hardware random number
|
||||
|
||||
// effect utility functions
|
||||
static uint8_t sin_gap(uint16_t in) {
|
||||
if (in & 0x100) return 0;
|
||||
return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0
|
||||
}
|
||||
|
||||
static uint16_t triwave16(uint16_t in) {
|
||||
if (in < 0x8000) return in *2;
|
||||
return 0xFFFF - (in - 0x8000)*2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates a tristate square wave w/ attac & decay
|
||||
* @param x input value 0-255
|
||||
@@ -149,7 +149,8 @@ void mode_copy_segment(void) {
|
||||
Segment& sourcesegment = strip.getSegment(sourceid);
|
||||
|
||||
if (sourcesegment.isActive()) {
|
||||
CRGBW color;
|
||||
uint32_t sourcecolor;
|
||||
uint32_t destcolor;
|
||||
if(sourcesegment.is2D()) { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX)
|
||||
for (unsigned y = 0; y < SEGMENT.vHeight(); y++) {
|
||||
for (unsigned x = 0; x < SEGMENT.vWidth(); x++) {
|
||||
@@ -157,29 +158,29 @@ void mode_copy_segment(void) {
|
||||
unsigned sy = y;
|
||||
if(SEGMENT.check1) std::swap(sx, sy); // flip axis
|
||||
if(SEGMENT.check2) {
|
||||
color = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame)
|
||||
sourcecolor = strip.getPixelColorXY(sx + sourcesegment.start, sy + sourcesegment.startY); // read from global buffer (reads the last rendered frame)
|
||||
}
|
||||
else {
|
||||
sourcesegment.setDrawDimensions(); // set to source segment dimensions
|
||||
color = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer
|
||||
sourcecolor = sourcesegment.getPixelColorXY(sx, sy); // read from segment buffer
|
||||
}
|
||||
adjust_color(color, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // hue shif, sat change, value change
|
||||
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
|
||||
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
|
||||
SEGMENT.setPixelColorXY(x, y, color);
|
||||
SEGMENT.setPixelColorXY(x, y, destcolor);
|
||||
}
|
||||
}
|
||||
} else { // 1D source, source can be expanded into 2D
|
||||
for (unsigned i = 0; i < SEGMENT.vLength(); i++) {
|
||||
if(SEGMENT.check2) {
|
||||
color = strip.getPixelColorNoMap(i + sourcesegment.start); // read from global buffer (reads the last rendered frame)
|
||||
sourcecolor = strip.getPixelColorNoMap(i + sourcesegment.start); // read from global buffer (reads the last rendered frame)
|
||||
}
|
||||
else {
|
||||
sourcesegment.setDrawDimensions(); // set to source segment dimensions
|
||||
color = sourcesegment.getPixelColor(i);
|
||||
sourcecolor = sourcesegment.getPixelColor(i);
|
||||
}
|
||||
adjust_color(color, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // hue shif, sat change, value change
|
||||
destcolor = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2);
|
||||
SEGMENT.setDrawDimensions(); // reset to current segment dimensions
|
||||
SEGMENT.setPixelColor(i, color);
|
||||
SEGMENT.setPixelColor(i, destcolor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1812,30 +1813,30 @@ static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet@!,Fade;!,!;
|
||||
*/
|
||||
void mode_random_chase(void) {
|
||||
if (SEGENV.call == 0) {
|
||||
SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
|
||||
SEGENV.aux0 = prng.random16();
|
||||
SEGENV.step = RGBW32(random8(), random8(), random8(), 0);
|
||||
SEGENV.aux0 = random16();
|
||||
}
|
||||
unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
|
||||
unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function
|
||||
uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed));
|
||||
uint32_t it = strip.now / cycleTime;
|
||||
uint32_t color = SEGENV.step;
|
||||
prng.setSeed(SEGENV.aux0);
|
||||
random16_set_seed(SEGENV.aux0);
|
||||
|
||||
for (int i = SEGLEN -1; i >= 0; i--) {
|
||||
uint8_t r = prng.random8(6) != 0 ? (color >> 16 & 0xFF) : prng.random8();
|
||||
uint8_t g = prng.random8(6) != 0 ? (color >> 8 & 0xFF) : prng.random8();
|
||||
uint8_t b = prng.random8(6) != 0 ? (color & 0xFF) : prng.random8();
|
||||
uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8();
|
||||
uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8();
|
||||
uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8();
|
||||
color = RGBW32(r, g, b, 0);
|
||||
SEGMENT.setPixelColor(i, color);
|
||||
if (i == SEGLEN -1U && SEGENV.aux1 != (it & 0xFFFFU)) { //new first color in next frame
|
||||
SEGENV.step = color;
|
||||
SEGENV.aux0 = prng.getSeed();
|
||||
SEGENV.aux0 = random16_get_seed();
|
||||
}
|
||||
}
|
||||
|
||||
SEGENV.aux1 = it & 0xFFFF;
|
||||
|
||||
prng.setSeed(prevSeed); // restore original seed so other effects can use "random" PRNG
|
||||
random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG
|
||||
}
|
||||
static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2@!;;";
|
||||
|
||||
@@ -2307,8 +2308,8 @@ void mode_colortwinkle() {
|
||||
SEGENV.step = strip.now;
|
||||
|
||||
CRGBW col, prev;
|
||||
uint8_t fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness();
|
||||
uint8_t fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness();
|
||||
fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness();
|
||||
fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness();
|
||||
for (unsigned i = 0; i < SEGLEN; i++) {
|
||||
CRGBW cur = SEGMENT.getPixelColor(i);
|
||||
prev = cur;
|
||||
@@ -2324,13 +2325,14 @@ void mode_colortwinkle() {
|
||||
bitWrite(SEGENV.data[index], bitNum, false);
|
||||
}
|
||||
|
||||
if (col == cur) { // color_add did nothing, fix "stuck" pixels by adding the color to itself
|
||||
if (cur == prev) { //fix "stuck" pixels
|
||||
col = color_add(col, col);
|
||||
SEGMENT.setPixelColor(i, col);
|
||||
}
|
||||
SEGMENT.setPixelColor(i, col);
|
||||
else SEGMENT.setPixelColor(i, col);
|
||||
}
|
||||
else {
|
||||
col = color_fade(cur, 255 - fadeDownAmount, false);
|
||||
col = color_fade(cur, 255 - fadeDownAmount);
|
||||
SEGMENT.setPixelColor(i, col);
|
||||
}
|
||||
}
|
||||
@@ -2575,7 +2577,7 @@ static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow@!,Wav
|
||||
//
|
||||
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
|
||||
// Colors are chosen from a palette. Read more about this effect using the link above!
|
||||
static CRGBW twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
|
||||
static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
|
||||
{
|
||||
// Overall twinkle speed (changed)
|
||||
unsigned ticks = ms / SEGENV.aux0;
|
||||
@@ -2612,7 +2614,7 @@ static CRGBW twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
|
||||
}
|
||||
|
||||
unsigned hue = slowcycle8 - salt;
|
||||
CRGBW c;
|
||||
CRGB c;
|
||||
if (bright > 0) {
|
||||
c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND);
|
||||
if (!SEGMENT.check1) {
|
||||
@@ -2627,7 +2629,7 @@ static CRGBW twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c = 0; // black
|
||||
c = CRGB::Black;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -2650,16 +2652,15 @@ static void twinklefox_base(bool cat)
|
||||
else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1);
|
||||
|
||||
// Set up the background color, "bg".
|
||||
CRGBW bg = SEGCOLOR(1);
|
||||
CRGB bg = CRGB(SEGCOLOR(1));
|
||||
unsigned bglight = bg.getAverageLight();
|
||||
if (bglight > 64) {
|
||||
bg = color_fade(bg, 16, true); // very bright, so scale to 1/16th
|
||||
bg.nscale8_video(16); // very bright, so scale to 1/16th
|
||||
} else if (bglight > 16) {
|
||||
bg = color_fade(bg, 64, true); // not that bright, so scale to 1/4th
|
||||
bg.nscale8_video(64); // not that bright, so scale to 1/4th
|
||||
} else {
|
||||
bg = color_fade(bg, 86, true); // dim, scale to 1/3rd.
|
||||
bg.nscale8_video(86); // dim, scale to 1/3rd.
|
||||
}
|
||||
bg = gamma32inv(bg); // need to invert gamma as the FX was written without any gamma correction and it will dim down too much otherwise
|
||||
|
||||
unsigned backgroundBrightness = bg.getAverageLight();
|
||||
|
||||
@@ -2676,18 +2677,18 @@ static void twinklefox_base(bool cat)
|
||||
// We now have the adjusted 'clock' for this pixel, now we call
|
||||
// the function that computes what color the pixel should be based
|
||||
// on the "brightness = f( time )" idea.
|
||||
CRGBW c = twinklefox_one_twinkle(myclock30, myunique8, cat);
|
||||
CRGB c = twinklefox_one_twinkle(myclock30, myunique8, cat);
|
||||
|
||||
unsigned cbright = c.getAverageLight();
|
||||
int deltabright = cbright - backgroundBrightness;
|
||||
if (deltabright >= 32 || (bg==0)) {
|
||||
if (deltabright >= 32 || (!bg)) {
|
||||
// If the new pixel is significantly brighter than the background color,
|
||||
// use the new color.
|
||||
SEGMENT.setPixelColor(i, c);
|
||||
} else if (deltabright > 0) {
|
||||
// If the new pixel is just slightly brighter than the background color,
|
||||
// mix a blend of the new color and the background color
|
||||
SEGMENT.setPixelColor(i, color_blend(bg, c, uint8_t(deltabright * 8)));
|
||||
SEGMENT.setPixelColor(i, color_blend(RGBW32(bg.r,bg.g,bg.b,0), RGBW32(c.r,c.g,c.b,0), uint8_t(deltabright * 8)));
|
||||
} else {
|
||||
// if the new pixel is not at all brighter than the background color,
|
||||
// just use the background color.
|
||||
@@ -3498,14 +3499,11 @@ void candle(bool multi)
|
||||
{
|
||||
if (multi && SEGLEN > 1) {
|
||||
//allocate segment data
|
||||
unsigned dataSize = sizeof(uint32_t) + max(1, (int)SEGLEN -1) *3;
|
||||
unsigned dataSize = sizeof(uint32_t) + max(1, (int)SEGLEN -1) *3; //max. 1365 pixels (ESP8266)
|
||||
if (!SEGENV.allocateData(dataSize)) candle(false); //allocation failed
|
||||
} else {
|
||||
unsigned dataSize = sizeof(uint32_t); // for last call timestamp
|
||||
if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed
|
||||
}
|
||||
uint32_t* lastcall = reinterpret_cast<uint32_t*>(SEGENV.data);
|
||||
uint8_t* candleData = reinterpret_cast<uint8_t*>(SEGENV.data + sizeof(uint32_t)); // only used for multi-candle
|
||||
uint8_t* candleData = reinterpret_cast<uint8_t*>(SEGENV.data + sizeof(uint32_t));
|
||||
|
||||
//limit update rate
|
||||
if (strip.now - *lastcall < FRAMETIME_FIXED) return;
|
||||
@@ -4340,18 +4338,18 @@ void mode_phased_noise(void) {
|
||||
static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise@!,!;!,!;!";
|
||||
|
||||
|
||||
void mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline.
|
||||
unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
|
||||
prng.setSeed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through.
|
||||
void mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline.
|
||||
unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function
|
||||
random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through.
|
||||
|
||||
for (unsigned i = 0; i < SEGLEN; i++) {
|
||||
unsigned ranstart = prng.random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work.
|
||||
unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work.
|
||||
unsigned pixBri = sin8_t(ranstart + 16 * strip.now/(256-SEGMENT.speed));
|
||||
if (prng.random8() > SEGMENT.intensity) pixBri = 0;
|
||||
SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(prng.random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri));
|
||||
if (random8() > SEGMENT.intensity) pixBri = 0;
|
||||
SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri));
|
||||
}
|
||||
|
||||
prng.setSeed(prevSeed); // restore original seed so other effects can use "random" PRNG
|
||||
random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG
|
||||
}
|
||||
static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,!;!;;m12=0";
|
||||
|
||||
@@ -4372,7 +4370,6 @@ void mode_noisepal(void) { // Slow noise pale
|
||||
SEGENV.step = strip.now;
|
||||
|
||||
unsigned baseI = hw_random8();
|
||||
//palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255)));
|
||||
palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255)));
|
||||
}
|
||||
|
||||
@@ -4428,8 +4425,7 @@ void mode_flow(void)
|
||||
if (zones & 0x01) zones++; //zones must be even
|
||||
if (zones < 2) zones = 2;
|
||||
int zoneLen = SEGLEN / zones;
|
||||
int requiredZones = (SEGLEN + zoneLen - 1) / zoneLen;
|
||||
zones = requiredZones + 2; //add extra zones to cover beginning and end of segment (compensate integer truncation)
|
||||
zones += 2; //add two extra zones to cover beginning and end of segment (compensate integer truncation)
|
||||
int offset = ((int)SEGLEN - (zones * zoneLen)) / 2; // center the zones on the segment (can not use bit shift on negative number)
|
||||
|
||||
for (int z = 0; z < zones; z++)
|
||||
@@ -5012,7 +5008,7 @@ void mode_ColorClouds()
|
||||
|
||||
uint32_t pixel;
|
||||
if (SEGMENT.palette) { pixel = SEGMENT.color_from_palette(hue, false, true, 0, vol); }
|
||||
else { pixel = CRGBW(CHSV32(hue, 255, vol)); }
|
||||
else { hsv2rgb(CHSV32(hue, 255, vol), pixel); }
|
||||
|
||||
// Suppress extremely dark pixels to avoid flickering of plain r/g/b.
|
||||
if (int(R(pixel)) + G(pixel) + B(pixel) <= 2) {
|
||||
@@ -5221,7 +5217,7 @@ void mode_2DColoredBursts() { // By: ldirko https://editor.soulma
|
||||
uint8_t rate = j * 255 / steps;
|
||||
byte dx = lerp8by8(x1, y1, rate);
|
||||
byte dy = lerp8by8(x2, y2, rate);
|
||||
//SEGMENT.setPixelColorXY(dx, dy, grad ? color_fade(color, (255-rate), true) : color); // use addPixelColorXY for different look
|
||||
//SEGMENT.setPixelColorXY(dx, dy, grad ? color.nscale8_video(255-rate) : color); // use addPixelColorXY for different look
|
||||
SEGMENT.addPixelColorXY(dx, dy, color); // use setPixelColorXY for different look
|
||||
if (grad) SEGMENT.fadePixelColorXY(dx, dy, rate);
|
||||
}
|
||||
@@ -5345,6 +5341,7 @@ void mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Ye
|
||||
unsigned yscale = SEGMENT.speed*8;
|
||||
unsigned indexx = 0;
|
||||
|
||||
//CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : SEGMENT.loadPalette(pal, 35);
|
||||
CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : CRGBPalette16(CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black,
|
||||
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange,
|
||||
CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange,
|
||||
@@ -5451,7 +5448,6 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Repeat detection
|
||||
@@ -5504,7 +5500,7 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na
|
||||
|
||||
if (aliveParents) {
|
||||
// Set color based on random neighbor
|
||||
unsigned parentIndex = parentIdx[hw_random8(aliveParents)];
|
||||
unsigned parentIndex = parentIdx[random8(aliveParents)];
|
||||
birthColor = SEGMENT.getPixelColor(parentIndex);
|
||||
}
|
||||
newColor = birthColor;
|
||||
@@ -6015,7 +6011,8 @@ void mode_2DSunradiation(void) { // By: ldirko https://editor.
|
||||
uint8_t someVal = SEGMENT.speed/4; // Was 25.
|
||||
for (int j = 0; j < (rows + 2); j++) {
|
||||
for (int i = 0; i < (cols + 2); i++) {
|
||||
byte col = ((int16_t)perlin8(i * someVal, j * someVal, t) - 127) >> 2; // about +/- 32
|
||||
//byte col = (inoise8_raw(i * someVal, j * someVal, t)) / 2;
|
||||
byte col = ((int16_t)perlin8(i * someVal, j * someVal, t) - 0x7F) / 3;
|
||||
bump[index++] = col;
|
||||
}
|
||||
}
|
||||
@@ -6135,10 +6132,10 @@ void mode_2Dcrazybees(void) {
|
||||
uint8_t posX, posY, aimX, aimY, hue;
|
||||
int8_t deltaX, deltaY, signX, signY, error;
|
||||
void aimed(uint16_t w, uint16_t h) {
|
||||
//prng.setSeed(millis());
|
||||
aimX = prng.random8(0, w);
|
||||
aimY = prng.random8(0, h);
|
||||
hue = prng.random8();
|
||||
//random16_set_seed(millis());
|
||||
aimX = random8(0, w);
|
||||
aimY = random8(0, h);
|
||||
hue = random8();
|
||||
deltaX = abs(aimX - posX);
|
||||
deltaY = abs(aimY - posY);
|
||||
signX = posX < aimX ? 1 : -1;
|
||||
@@ -6151,10 +6148,10 @@ void mode_2Dcrazybees(void) {
|
||||
bee_t *bee = reinterpret_cast<bee_t*>(SEGENV.data);
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
prng.setSeed(strip.now);
|
||||
random16_set_seed(strip.now);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
bee[i].posX = prng.random8(0, cols);
|
||||
bee[i].posY = prng.random8(0, rows);
|
||||
bee[i].posX = random8(0, cols);
|
||||
bee[i].posY = random8(0, rows);
|
||||
bee[i].aimed(cols, rows);
|
||||
}
|
||||
}
|
||||
@@ -6322,7 +6319,7 @@ void mode_2Dfloatingblobs(void) {
|
||||
|
||||
// Bounce balls around
|
||||
for (size_t i = 0; i < Amount; i++) {
|
||||
if (SEGENV.step < strip.now) blob->color[i] += 4; // slowly change color
|
||||
if (SEGENV.step < strip.now) blob->color[i] = add8(blob->color[i], 4); // slowly change color
|
||||
// change radius if needed
|
||||
if (blob->grow[i]) {
|
||||
// enlarge radius until it is >= 4
|
||||
@@ -6380,11 +6377,30 @@ static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail;
|
||||
////////////////////////////
|
||||
void mode_2Dscrollingtext(void) {
|
||||
if (!strip.isMatrix || !SEGMENT.is2D()) FX_FALLBACK_STATIC; // not a 2D set-up
|
||||
FontManager fontManager(&SEGMENT);
|
||||
|
||||
const int cols = SEG_W;
|
||||
const int rows = SEG_H;
|
||||
|
||||
// generate time/date if there are any # tokens or no segment name set
|
||||
unsigned letterWidth, rotLW;
|
||||
unsigned letterHeight, rotLH;
|
||||
switch (map(SEGMENT.custom2, 0, 255, 1, 5)) {
|
||||
default:
|
||||
case 1: letterWidth = 4; letterHeight = 6; break;
|
||||
case 2: letterWidth = 5; letterHeight = 8; break;
|
||||
case 3: letterWidth = 6; letterHeight = 8; break;
|
||||
case 4: letterWidth = 7; letterHeight = 9; break;
|
||||
case 5: letterWidth = 5; letterHeight = 12; break;
|
||||
}
|
||||
// letters are rotated
|
||||
const int8_t rotate = map(SEGMENT.custom3, 0, 31, -2, 2);
|
||||
if (rotate == 1 || rotate == -1) {
|
||||
rotLH = letterWidth;
|
||||
rotLW = letterHeight;
|
||||
} else {
|
||||
rotLW = letterWidth;
|
||||
rotLH = letterHeight;
|
||||
}
|
||||
|
||||
char text[WLED_MAX_SEGNAME_LEN+1] = {'\0'};
|
||||
size_t result_pos = 0;
|
||||
char sec[5];
|
||||
@@ -6398,13 +6414,10 @@ void mode_2Dscrollingtext(void) {
|
||||
sprintf_P(sec, PSTR(":%02d"), second(localTime));
|
||||
}
|
||||
|
||||
// prepare text string from segment name
|
||||
size_t len = 0;
|
||||
if (SEGMENT.name) len = strlen(SEGMENT.name); // note: SEGMENT.name is limited to WLED_MAX_SEGNAME_LEN
|
||||
if (len == 0) {
|
||||
// fallback if empty segment name: display date and time "#MON #DD #YYYY #TIME"
|
||||
if (len == 0) { // fallback if empty segment name: display date and time
|
||||
sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec);
|
||||
fontManager.cacheNumbers(true); // cache all numbers when using clock to avoid frequent re-caching
|
||||
} else {
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
@@ -6447,7 +6460,7 @@ void mode_2Dscrollingtext(void) {
|
||||
strcpy(text + result_pos, temp);
|
||||
result_pos += temp_len;
|
||||
}
|
||||
fontManager.cacheNumbers(true); // cache all numbers when using clocks to avoid frequent re-caching
|
||||
|
||||
i += advance;
|
||||
}
|
||||
else {
|
||||
@@ -6459,45 +6472,11 @@ void mode_2Dscrollingtext(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Font selection
|
||||
bool useCustomFont = SEGMENT.check2;
|
||||
uint8_t fontNum = map(SEGMENT.custom2, 0, 255, 0, 4);
|
||||
|
||||
// letters orientation: -2/+2 = upside down, -1 = 90° clockwise, 0 = normal, 1 = 90° counterclockwise
|
||||
const int8_t rotate = map(SEGMENT.custom3, 0, 31, -2, 2);
|
||||
const bool isRotated = (rotate == 1 || rotate == -1); // +/- 90° rotated, swap width and height for calculations
|
||||
|
||||
// Load the font
|
||||
if (!fontManager.loadFont(fontNum, text, useCustomFont)) return; // note: FontManageraccess can lead to crashes if font loading fails due to low heap
|
||||
|
||||
// Get font dimensions
|
||||
uint8_t fontHeight = fontManager.getFontHeight();
|
||||
uint8_t fontWidth = fontManager.getFontWidth(); // for fonts with variable width, this is the max letter width
|
||||
uint8_t letterSpacing = isRotated ? 1 : fontManager.getFontSpacing(); // when rotated use spacing of 1, otherwise use font defined spacing
|
||||
|
||||
// Calculate total text width
|
||||
int totalTextWidth = 0;
|
||||
int idx = 0;
|
||||
const int numberOfChars = utf8_strlen(text);
|
||||
|
||||
for (int c = 0; c < numberOfChars; c++) {
|
||||
uint8_t charLen;
|
||||
uint32_t unicode = utf8_decode(&text[idx], &charLen);
|
||||
idx += charLen;
|
||||
|
||||
if (isRotated) {
|
||||
totalTextWidth += fontHeight + letterSpacing; // use height when rotated, spacing of 1
|
||||
} else {
|
||||
totalTextWidth += fontManager.getGlyphWidth(unicode) + letterSpacing;
|
||||
}
|
||||
}
|
||||
totalTextWidth -= letterSpacing; // remove spacing after last character
|
||||
|
||||
// y-offset calculation
|
||||
int yoffset = map(SEGMENT.intensity, 0, 255, -rows / 2, rows / 2);
|
||||
|
||||
if (totalTextWidth <= cols) {
|
||||
// if text fits matrix width, scroll vertically
|
||||
const int numberOfLetters = strlen(text);
|
||||
int width = (numberOfLetters * rotLW);
|
||||
int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-rotLH)/2;
|
||||
if (width <= cols) {
|
||||
// scroll vertically (e.g. ^^ Way out ^^) if it fits
|
||||
int speed = map(SEGMENT.speed, 0, 255, 5000, 1000);
|
||||
int frac = strip.now % speed + 1;
|
||||
if (SEGMENT.intensity == 255) {
|
||||
@@ -6507,26 +6486,21 @@ void mode_2Dscrollingtext(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// scroll step (AUX0 is current scrolling offset)
|
||||
if (SEGENV.step < strip.now) {
|
||||
if (totalTextWidth > cols) {
|
||||
if (SEGMENT.check3) { // reverse direction
|
||||
if (SEGENV.aux0 == 0) SEGENV.aux0 = totalTextWidth + cols - 1;
|
||||
else --SEGENV.aux0;
|
||||
} else {
|
||||
++SEGENV.aux0 %= totalTextWidth + cols;
|
||||
}
|
||||
} else {
|
||||
SEGENV.aux0 = (cols + totalTextWidth) / 2; // text fits, position it at the center
|
||||
}
|
||||
// calculate start offset
|
||||
if (width > cols) {
|
||||
if (SEGMENT.check3) {
|
||||
if (SEGENV.aux0 == 0) SEGENV.aux0 = width + cols - 1;
|
||||
else --SEGENV.aux0;
|
||||
} else ++SEGENV.aux0 %= width + cols;
|
||||
} else SEGENV.aux0 = (cols + width)/2;
|
||||
++SEGENV.aux1 &= 0xFF; // color shift
|
||||
SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50);
|
||||
SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms
|
||||
}
|
||||
|
||||
SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail
|
||||
uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0);
|
||||
uint32_t col2 = BLACK;
|
||||
|
||||
// if gradient is selected and palette is default (0) drawCharacter() uses gradient from SEGCOLOR(0) to SEGCOLOR(2)
|
||||
// otherwise col2 == BLACK means use currently selected palette for gradient
|
||||
// if gradient is not selected set both colors the same
|
||||
@@ -6537,33 +6511,13 @@ void mode_2Dscrollingtext(void) {
|
||||
}
|
||||
} else col2 = col1; // force characters to use single color (from palette)
|
||||
|
||||
// Draw characters
|
||||
idx = 0;
|
||||
int currentXOffset = 0; // offset of current glyph from text start
|
||||
|
||||
for (int c = 0; c < numberOfChars; c++) {
|
||||
uint8_t charLen;
|
||||
uint32_t unicode = utf8_decode(&text[idx], &charLen);
|
||||
idx += charLen;
|
||||
int unrotatedWidth = fontManager.getGlyphWidth(unicode);
|
||||
int glyphWidth = isRotated ? fontHeight : unrotatedWidth; // use font height for width if 90° rotated
|
||||
int glyphHeight = isRotated ? unrotatedWidth : fontHeight; // use (variable) glyph-width for height if 90° rotated
|
||||
int drawX = int(cols) - int(SEGENV.aux0) + currentXOffset; // aux0 is (scrolling) offset, no offset position is right side boarder (cols)
|
||||
if (drawX >= cols) break; // skip if character is off-screen on the right
|
||||
int advance = glyphWidth + letterSpacing;
|
||||
|
||||
if (drawX + advance < 0) {
|
||||
currentXOffset += advance;
|
||||
continue; // Skip if off-screen on the left
|
||||
}
|
||||
|
||||
int16_t drawY = yoffset + (rows - glyphHeight) / 2; // center glyph vertically
|
||||
|
||||
fontManager.drawCharacter(unicode, drawX, drawY, col1, col2, rotate);
|
||||
currentXOffset += advance;
|
||||
for (int i = 0; i < numberOfLetters; i++) {
|
||||
int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i;
|
||||
if (xoffset + rotLW < 0) continue; // don't draw characters off-screen
|
||||
SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, rotate);
|
||||
}
|
||||
}
|
||||
static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,Custom Font,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0";
|
||||
static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0";
|
||||
|
||||
|
||||
////////////////////////////
|
||||
@@ -7812,7 +7766,7 @@ void mode_2Ddistortionwaves() {
|
||||
SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP));
|
||||
} else {
|
||||
// color mapping: calculate hue from pixel color, map it to palette index
|
||||
CHSV hsvclr = rgb2hsv(CRGB(valueR>>2, valueG>>2, valueB>>2)); // scale colors down to not saturate for better hue extraction
|
||||
CHSV hsvclr = rgb2hsv_approximate(CRGB(valueR>>2, valueG>>2, valueB>>2)); // scale colors down to not saturate for better hue extraction
|
||||
SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hsvclr.h, brightness));
|
||||
}
|
||||
}
|
||||
@@ -7871,7 +7825,7 @@ static void soapPixels(bool isRow, uint8_t *noise3d, CRGB *pixels) {
|
||||
else PixelA = ColorFromPalette(SEGPALETTE, ~noise3d[indxA]*3);
|
||||
if ((zF >= 0) && (zF < tCR)) PixelB = pixels[indxB];
|
||||
else PixelB = ColorFromPalette(SEGPALETTE, ~noise3d[indxB]*3);
|
||||
ledsbuff[j] = (PixelA.nscale8(ease8InOutCubic(255 - fraction))) + (PixelB.nscale8(ease8InOutCubic(fraction)));
|
||||
ledsbuff[j] = (PixelA.nscale8(ease8InOutApprox(255 - fraction))) + (PixelB.nscale8(ease8InOutApprox(fraction)));
|
||||
}
|
||||
for (int j = 0; j < tCR; j++) {
|
||||
CRGB c = ledsbuff[j];
|
||||
@@ -8219,7 +8173,7 @@ void mode_particlefireworks(void) {
|
||||
emitparticles = hw_random16(SEGMENT.intensity >> 2) + (SEGMENT.intensity >> 2) + 5; // defines the size of the explosion
|
||||
#endif
|
||||
|
||||
if (hw_random() & 1) { // 50% chance for circular explosion
|
||||
if (random16() & 1) { // 50% chance for circular explosion
|
||||
circularexplosion = true;
|
||||
speed = 2 + hw_random16(3) + ((SEGMENT.intensity >> 6));
|
||||
currentspeed = speed;
|
||||
@@ -8731,7 +8685,7 @@ void mode_particleperlin(void) {
|
||||
|
||||
PartSys->update(); // update and render
|
||||
}
|
||||
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5";
|
||||
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o3=1";
|
||||
|
||||
/*
|
||||
Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with
|
||||
@@ -9478,28 +9432,20 @@ void mode_particleDrip(void) {
|
||||
PartSys->sprayEmit(PartSys->sources[0]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // check all particles
|
||||
if (PartSys->particles[i].ttl) {
|
||||
if (PartSys->particleFlags[i].collide == false) { // use collision flag to identify splash particles
|
||||
if (PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) { // reached bottom
|
||||
if (PartSys->particles[i].ttl > 120) // short life: make drop particle fade out and die
|
||||
PartSys->particles[i].ttl = 120;
|
||||
if (SEGMENT.custom1 > 0) { // splash enabled
|
||||
PartSys->particles[i].ttl = 0; // kill drop particle, replace with splash
|
||||
PartSys->sources[0].maxLife = 160;
|
||||
PartSys->sources[0].minLife = 40;
|
||||
PartSys->sources[0].var = 10 + (SEGMENT.custom1 >> 3);
|
||||
PartSys->sources[0].v = 0;
|
||||
PartSys->sources[0].source.hue = PartSys->particles[i].hue;
|
||||
PartSys->sources[0].source.x = PS_P_RADIUS_1D;
|
||||
PartSys->sources[0].sourceFlags.collide = true; //splashes do collide if enabled
|
||||
for (int j = 0; j < 2 + (SEGMENT.custom1 >> 2); j++) {
|
||||
PartSys->sprayEmit(PartSys->sources[0]);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) { //check all particles
|
||||
if (PartSys->particles[i].ttl && PartSys->particleFlags[i].collide == false) { // use collision flag to identify splash particles
|
||||
if (SEGMENT.custom1 > 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) { //splash enabled and reached bottom
|
||||
PartSys->particles[i].ttl = 0; //kill origin particle
|
||||
PartSys->sources[0].maxLife = 80;
|
||||
PartSys->sources[0].minLife = 20;
|
||||
PartSys->sources[0].var = 10 + (SEGMENT.custom1 >> 3);
|
||||
PartSys->sources[0].v = 0;
|
||||
PartSys->sources[0].source.hue = PartSys->particles[i].hue;
|
||||
PartSys->sources[0].source.x = PS_P_RADIUS_1D;
|
||||
PartSys->sources[0].sourceFlags.collide = true; //splashes do collide if enabled
|
||||
for (int j = 0; j < 2 + (SEGMENT.custom1 >> 2); j++) {
|
||||
PartSys->sprayEmit(PartSys->sources[0]);
|
||||
}
|
||||
} else {
|
||||
PartSys->particles[i].ttl--; // age splash particles faster (allows for higher splash brightness)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9837,7 +9783,6 @@ void mode_particleFireworks1D(void) {
|
||||
PartSys->setColorByPosition(false); // disable
|
||||
for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles
|
||||
int idx = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
|
||||
if (idx < 0) break; // no more particles available
|
||||
if(SEGMENT.custom3 > 23) {
|
||||
if(SEGMENT.custom3 == 31) { // highest slider value
|
||||
PartSys->setColorByAge(SEGMENT.check1); // color by age if colorful mode is enabled
|
||||
@@ -9862,7 +9807,7 @@ void mode_particleFireworks1D(void) {
|
||||
PartSys->applyFriction(1); // apply friction to all particles
|
||||
|
||||
PartSys->update(); // update and render
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
if (PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan
|
||||
else PartSys->particles[i].ttl = 0;
|
||||
@@ -9912,7 +9857,7 @@ void mode_particleSparkler(void) {
|
||||
PartSys->sources[i].source.ttl = 400; // replenish its life (setting it perpetual uses more code)
|
||||
PartSys->sources[i].sat = SEGMENT.custom1; // color saturation
|
||||
if (SEGMENT.speed == 255) // random position at highest speed setting
|
||||
PartSys->sources[i].source.x = hw_random(PartSys->maxX);
|
||||
PartSys->sources[i].source.x = hw_random16(PartSys->maxX);
|
||||
else
|
||||
PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &sparklersettings); //move sparkler
|
||||
}
|
||||
@@ -10515,8 +10460,6 @@ void mode_particle1DsonicStream(void) {
|
||||
uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14);
|
||||
|
||||
loudness = fftResult[baseBin];// + fftResult[baseBin + 1];
|
||||
int mids = 0;
|
||||
if (SEGMENT.check1) mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
if (baseBin > 12)
|
||||
loudness = loudness << 2; // double loudness for high frequencies (better detecion)
|
||||
|
||||
@@ -10540,6 +10483,7 @@ void mode_particle1DsonicStream(void) {
|
||||
else PartSys->particles[i].ttl = 0;
|
||||
}
|
||||
if (SEGMENT.check1) { // modulate colors by mid frequencies
|
||||
int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
PartSys->particles[i].hue += (mids * perlin8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies
|
||||
}
|
||||
}
|
||||
@@ -10613,8 +10557,6 @@ void mode_particle1DsonicBoom(void) {
|
||||
uint32_t loudness;
|
||||
uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14);
|
||||
loudness = fftResult[baseBin];// + fftResult[baseBin + 1];
|
||||
int mids = 0;
|
||||
if (SEGMENT.check1) mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
|
||||
if (baseBin > 12)
|
||||
loudness = loudness << 2; // double loudness for high frequencies (better detecion)
|
||||
@@ -10627,6 +10569,7 @@ void mode_particle1DsonicBoom(void) {
|
||||
// particle manipulation
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
if (SEGMENT.check1) { // modulate colors by mid frequencies
|
||||
int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
PartSys->particles[i].hue += (mids * perlin8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies
|
||||
}
|
||||
if (PartSys->particles[i].ttl > 16) {
|
||||
@@ -10844,103 +10787,6 @@ static const char _data_FX_MODE_PS_SPRINGY[] PROGMEM = "PS Springy@Stiffness,Dam
|
||||
|
||||
#endif // WLED_DISABLE_PARTICLESYSTEM1D
|
||||
|
||||
/*
|
||||
* Slow Transition effect
|
||||
* Displays the currently selected palette/color with a very slow transition
|
||||
* speed slider controls the number of minutes for the transition (0 = 10s)
|
||||
* by DedeHai
|
||||
*/
|
||||
typedef struct SlowTransitionData {
|
||||
CRGBPalette16 startPalette; // initial palette
|
||||
CRGBPalette16 currentPalette; // blended palette for current frame, need permanent storage so we can start from this if target changes mid transition
|
||||
CRGBPalette16 endPalette; // target palette
|
||||
uint8_t startWhite;
|
||||
uint8_t currentWhite;
|
||||
uint8_t endWhite;
|
||||
uint8_t startCCT;
|
||||
uint8_t currentCCT;
|
||||
uint8_t endCCT;
|
||||
} slow_transition_data;
|
||||
|
||||
void mode_slow_transition(void) {
|
||||
// aliases
|
||||
uint32_t* startTime = &SEGMENT.step; // use step to store start time of transition
|
||||
uint16_t* stepsDone = &SEGMENT.aux0;
|
||||
uint16_t* startSpeed = &SEGMENT.aux1; // speed setting at the start of the transition, used to detect changes
|
||||
|
||||
size_t dataSize = sizeof(slow_transition_data);
|
||||
if (!SEGMENT.allocateData(dataSize)) FX_FALLBACK_STATIC;
|
||||
slow_transition_data* data = reinterpret_cast<slow_transition_data*>(SEGMENT.data);
|
||||
// Note: compare currentCCT (not endCCT). SEGMENT.cct is set to currentCCT at the end of each call, if they differ, it was changed externally
|
||||
bool changed = (data->endPalette != SEGPALETTE || *startSpeed != SEGMENT.speed || data->endWhite != W(SEGCOLOR(0)) || data->currentCCT != SEGMENT.cct); // detect changes in target color or speed setting
|
||||
|
||||
// (re) init
|
||||
if (changed || SEGMENT.call == 0) {
|
||||
if (SEGMENT.call == 0) {
|
||||
data->startPalette = SEGPALETTE;
|
||||
data->currentPalette = SEGPALETTE;
|
||||
data->endPalette = SEGPALETTE;
|
||||
data->startWhite = data->currentWhite = data->endWhite = W(SEGCOLOR(0));
|
||||
data->startCCT = data->currentCCT = data->endCCT = SEGMENT.cct;
|
||||
*stepsDone = 0xFFFF; // set to max, fading will start once a change is detected
|
||||
}
|
||||
else {
|
||||
data->startPalette = data->currentPalette;
|
||||
data->endPalette = SEGPALETTE;
|
||||
data->startWhite = data->currentWhite;
|
||||
data->endWhite = W(SEGCOLOR(0));
|
||||
data->startCCT = data->currentCCT;
|
||||
data->endCCT = SEGMENT.cct;
|
||||
*stepsDone = 0; // reset counter
|
||||
}
|
||||
*startSpeed = SEGMENT.speed;
|
||||
*startTime = millis(); // set start time note: intentionally not using strip.now as this is based on real time
|
||||
}
|
||||
|
||||
uint32_t totalSteps = SEGMENT.check2 ? 16 * 255 : 255;
|
||||
uint32_t duration = (SEGMENT.speed == 0) ? 10000 : (uint32_t)SEGMENT.speed * 60000; // 10s if zero (good for testing), otherwise map 1-255 to 1-255 minutes
|
||||
uint32_t elapsed = millis() - *startTime; // note: will overflow after ~50 days if just left alone (edge case unhandled)
|
||||
uint32_t expectedSteps = (uint64_t)elapsed * totalSteps / duration;
|
||||
expectedSteps = min(expectedSteps, totalSteps); // limit to total steps
|
||||
|
||||
if (*stepsDone > expectedSteps)
|
||||
*stepsDone = expectedSteps;// in case sweep was disabled mid transition
|
||||
|
||||
if (*stepsDone < expectedSteps) {
|
||||
*stepsDone = expectedSteps; // jump to expected steps to make sure timing is correct (need up to 4080 frames, at 20fps that is ~200 seconds)
|
||||
uint8_t blendAmount;
|
||||
if (SEGMENT.check2) {
|
||||
// sweep: one palette entry at a time
|
||||
uint8_t i = *stepsDone % 16;
|
||||
blendAmount = *stepsDone / 16;
|
||||
data->currentPalette[i] = CRGB(color_blend(CRGBW(data->startPalette[i]), CRGBW(data->endPalette[i]), blendAmount));
|
||||
} else {
|
||||
// full palette at once
|
||||
blendAmount = (uint8_t)*stepsDone;
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
data->currentPalette[i] = CRGB(color_blend(CRGBW(data->startPalette[i]), CRGBW(data->endPalette[i]), blendAmount));
|
||||
}
|
||||
}
|
||||
data->currentWhite = (data->startWhite * (255 - blendAmount) + data->endWhite * blendAmount) / 255;
|
||||
data->currentCCT = (data->startCCT * (255 - blendAmount) + data->endCCT * blendAmount) / 255;
|
||||
if (*stepsDone >= totalSteps) {
|
||||
// transition complete, apply end palette
|
||||
data->currentPalette = data->endPalette; // set to end palette (sweep may not have set all entries)
|
||||
data->currentWhite = data->endWhite;
|
||||
data->currentCCT = data->endCCT;
|
||||
}
|
||||
}
|
||||
// display current palette (plus white) over segment
|
||||
for (unsigned i = 0; i < SEGLEN; i++) {
|
||||
uint8_t paletteIndex = (i * 255) / SEGLEN;
|
||||
CRGBW palcol = ColorFromPalette(data->currentPalette, paletteIndex, 255, LINEARBLEND_NOWRAP);
|
||||
palcol.w = data->currentWhite; // TODO: currently "sweep mode" does not support white sweep
|
||||
SEGMENT.setPixelColor(i, palcol.color32);
|
||||
}
|
||||
SEGMENT.cct = data->currentCCT;
|
||||
}
|
||||
static const char _data_FX_MODE_SLOW_TRANSITION[] PROGMEM = "Slow Transition@Time (min),,,,,,Sweep;!;!;1;pal=2,sx=0,ix=0";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// mode data
|
||||
static const char _data_RESERVED[] PROGMEM = "RSVD";
|
||||
@@ -11104,7 +10950,6 @@ void WS2812FX::setupEffectData() {
|
||||
addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR);
|
||||
addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH);
|
||||
addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN);
|
||||
addEffect(FX_MODE_SLOW_TRANSITION, &mode_slow_transition, _data_FX_MODE_SLOW_TRANSITION);
|
||||
|
||||
// --- 1D audio effects ---
|
||||
addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS);
|
||||
|
||||
111
wled00/FX.h
111
wled00/FX.h
@@ -18,7 +18,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "wled.h"
|
||||
#include "colors.h"
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
// enable additional debug output
|
||||
#if defined(WLED_DEBUG_HOST)
|
||||
@@ -38,6 +38,10 @@
|
||||
#define DEBUGFX_PRINTF_P(x...)
|
||||
#endif
|
||||
|
||||
#define FASTLED_INTERNAL //remove annoying pragma messages
|
||||
#define USE_GET_MILLISECOND_TIMER
|
||||
#include "FastLED.h"
|
||||
|
||||
#define DEFAULT_BRIGHTNESS (uint8_t)127
|
||||
#define DEFAULT_MODE (uint8_t)0
|
||||
#define DEFAULT_SPEED (uint8_t)128
|
||||
@@ -54,6 +58,11 @@
|
||||
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||
#endif
|
||||
|
||||
//color mangling macros
|
||||
#ifndef RGBW32
|
||||
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
|
||||
#endif
|
||||
|
||||
extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex()
|
||||
extern byte realtimeMode; // used in getMappedPixelIndex()
|
||||
|
||||
@@ -371,37 +380,36 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
|
||||
#define FX_MODE_PS1DSPRINGY 216
|
||||
#define FX_MODE_PARTICLEGALAXY 217
|
||||
#define FX_MODE_COLORCLOUDS 218
|
||||
#define FX_MODE_SLOW_TRANSITION 219
|
||||
#define MODE_COUNT 220
|
||||
#define MODE_COUNT 219
|
||||
|
||||
|
||||
#define TRANSITION_FADE 0x00 // universal
|
||||
#define TRANSITION_FAIRY_DUST 0x01 // universal
|
||||
#define TRANSITION_SWIPE_RIGHT 0x02 // 1D or 2D
|
||||
#define TRANSITION_SWIPE_LEFT 0x03 // 1D or 2D
|
||||
#define TRANSITION_OUTSIDE_IN 0x04 // 1D or 2D
|
||||
#define TRANSITION_INSIDE_OUT 0x05 // 1D or 2D
|
||||
#define TRANSITION_SWIPE_UP 0x06 // 2D
|
||||
#define TRANSITION_SWIPE_DOWN 0x07 // 2D
|
||||
#define TRANSITION_OPEN_H 0x08 // 2D
|
||||
#define TRANSITION_OPEN_V 0x09 // 2D
|
||||
#define TRANSITION_SWIPE_TL 0x0A // 2D
|
||||
#define TRANSITION_SWIPE_TR 0x0B // 2D
|
||||
#define TRANSITION_SWIPE_BR 0x0C // 2D
|
||||
#define TRANSITION_SWIPE_BL 0x0D // 2D
|
||||
#define TRANSITION_CIRCULAR_OUT 0x0E // 2D
|
||||
#define TRANSITION_CIRCULAR_IN 0x0F // 2D
|
||||
#define BLEND_STYLE_FADE 0x00 // universal
|
||||
#define BLEND_STYLE_FAIRY_DUST 0x01 // universal
|
||||
#define BLEND_STYLE_SWIPE_RIGHT 0x02 // 1D or 2D
|
||||
#define BLEND_STYLE_SWIPE_LEFT 0x03 // 1D or 2D
|
||||
#define BLEND_STYLE_OUTSIDE_IN 0x04 // 1D or 2D
|
||||
#define BLEND_STYLE_INSIDE_OUT 0x05 // 1D or 2D
|
||||
#define BLEND_STYLE_SWIPE_UP 0x06 // 2D
|
||||
#define BLEND_STYLE_SWIPE_DOWN 0x07 // 2D
|
||||
#define BLEND_STYLE_OPEN_H 0x08 // 2D
|
||||
#define BLEND_STYLE_OPEN_V 0x09 // 2D
|
||||
#define BLEND_STYLE_SWIPE_TL 0x0A // 2D
|
||||
#define BLEND_STYLE_SWIPE_TR 0x0B // 2D
|
||||
#define BLEND_STYLE_SWIPE_BR 0x0C // 2D
|
||||
#define BLEND_STYLE_SWIPE_BL 0x0D // 2D
|
||||
#define BLEND_STYLE_CIRCULAR_OUT 0x0E // 2D
|
||||
#define BLEND_STYLE_CIRCULAR_IN 0x0F // 2D
|
||||
// as there are many push variants to optimise if statements they are groupped together
|
||||
#define TRANSITION_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_UP 0x12 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_DOWN 0x13 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_TL 0x14 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_TR 0x15 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_BR 0x16 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_BL 0x17 // 2D (& 0b00010000)
|
||||
#define TRANSITION_PUSH_MASK 0x10
|
||||
#define TRANSITION_COUNT 18
|
||||
#define BLEND_STYLE_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_UP 0x12 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_DOWN 0x13 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_TL 0x14 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_TR 0x15 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_BR 0x16 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_BL 0x17 // 2D (& 0b00010000)
|
||||
#define BLEND_STYLE_PUSH_MASK 0x10
|
||||
#define BLEND_STYLE_COUNT 18
|
||||
|
||||
|
||||
typedef enum mapping1D2D {
|
||||
@@ -413,12 +421,10 @@ typedef enum mapping1D2D {
|
||||
} mapping1D2D_t;
|
||||
|
||||
class WS2812FX;
|
||||
class FontManager;
|
||||
|
||||
// segment, 76 bytes
|
||||
class Segment {
|
||||
public:
|
||||
friend class FontManager; // Allow FontManager to access protected members
|
||||
uint32_t colors[NUM_COLORS];
|
||||
uint16_t start; // start index / start X coordinate 2D (left)
|
||||
uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0
|
||||
@@ -455,8 +461,9 @@ class Segment {
|
||||
bool check1 : 1; // checkmark 1
|
||||
bool check2 : 1; // checkmark 2
|
||||
bool check3 : 1; // checkmark 3
|
||||
//uint8_t blendMode : 4; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn
|
||||
};
|
||||
uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, average, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn, stencil
|
||||
uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn
|
||||
char *name; // segment name
|
||||
|
||||
// runtime data
|
||||
@@ -514,7 +521,7 @@ class Segment {
|
||||
, _start(millis())
|
||||
, _colors{0,0,0}
|
||||
#ifndef WLED_SAVE_RAM
|
||||
, _palT(CRGBPalette16())
|
||||
, _palT(CRGBPalette16(CRGB::Black))
|
||||
#endif
|
||||
, _dur(dur)
|
||||
, _progress(0)
|
||||
@@ -531,7 +538,7 @@ class Segment {
|
||||
|
||||
protected:
|
||||
|
||||
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData = max(0, int(Segment::_usedSegmentData) + len); } // clamp negative results to 0
|
||||
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
|
||||
|
||||
inline uint32_t *getPixels() const { return pixels; }
|
||||
inline void setPixelColorRaw(unsigned i, uint32_t c) const { pixels[i] = c; }
|
||||
@@ -541,7 +548,7 @@ class Segment {
|
||||
inline uint32_t getPixelColorXYRaw(unsigned x, unsigned y) const { auto XY = [](unsigned X, unsigned Y){ return X + Y*Segment::vWidth(); }; return pixels[XY(x,y)]; };
|
||||
#endif
|
||||
void resetIfRequired(); // sets all SEGENV variables to 0 and clears data buffer
|
||||
void loadPalette(CRGBPalette16 &tgt, uint8_t pal);
|
||||
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
|
||||
|
||||
// transition functions
|
||||
void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition)
|
||||
@@ -553,9 +560,9 @@ class Segment {
|
||||
inline uint16_t progress() const { return isInTransition() ? _t->_progress : 0xFFFFU; } // relies on handleTransition()/updateTransitionProgress() to update progression variable
|
||||
inline Segment *getOldSegment() const { return isInTransition() ? _t->_oldSegment : nullptr; }
|
||||
|
||||
inline static void modeBlend(bool blend) { Segment::_modeBlend = blend; } // for isPreviousMode()
|
||||
inline static void modeBlend(bool blend) { Segment::_modeBlend = blend; }
|
||||
inline static void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; };
|
||||
inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-TRANSITION_FADE transition
|
||||
inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-BLEND_STYLE_FADE transition
|
||||
|
||||
static void handleRandomPalette();
|
||||
|
||||
@@ -754,8 +761,8 @@ class Segment {
|
||||
{ addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); }
|
||||
inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) const { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); }
|
||||
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); }
|
||||
inline void blurCols(uint8_t blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur)
|
||||
inline void blurRows(uint8_t blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur)
|
||||
inline void blurCols(fract8 blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur)
|
||||
inline void blurRows(fract8 blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur)
|
||||
//void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur
|
||||
void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) const;
|
||||
void moveX(int delta, bool wrap = false) const;
|
||||
@@ -764,10 +771,12 @@ class Segment {
|
||||
void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const;
|
||||
void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const;
|
||||
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) const;
|
||||
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0) const;
|
||||
void wu_pixel(uint32_t x, uint32_t y, CRGB c) const;
|
||||
inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
|
||||
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); }
|
||||
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) const { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline
|
||||
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2 = CRGB::Black, int8_t rotate = 0) const { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline
|
||||
inline void fill_solid(CRGB c) const { fill(RGBW32(c.r,c.g,c.b,0)); }
|
||||
#else
|
||||
inline bool is2D() const { return false; }
|
||||
@@ -781,18 +790,18 @@ class Segment {
|
||||
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); }
|
||||
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); }
|
||||
#endif
|
||||
inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); }
|
||||
inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); }
|
||||
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
|
||||
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
|
||||
inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) { addPixelColor(x, color, preserveCR); }
|
||||
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColor(x, RGBW32(r,g,b,w), preserveCR); }
|
||||
inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), preserveCR); }
|
||||
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); }
|
||||
//inline void box_blur(unsigned i, bool vertical, uint8_t blur_amount) {}
|
||||
inline bool isPixelXYClipped(int x, int y) const { return isPixelClipped(x); }
|
||||
inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(x); }
|
||||
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) const { blendPixelColor(x, c, blend); }
|
||||
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
|
||||
inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) const { addPixelColor(x, color, saturate); }
|
||||
inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) const { addPixelColor(x, RGBW32(r,g,b,w), saturate); }
|
||||
inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) const { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); }
|
||||
inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { fadePixelColor(x, fade); }
|
||||
//inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {}
|
||||
inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {}
|
||||
inline void blurRows(uint8_t blur_amount, bool smear = false) {}
|
||||
inline void blurCols(uint8_t blur_amount, bool smear = false) {}
|
||||
inline void blurCols(fract8 blur_amount, bool smear = false) { blur(blur_amount, smear); } // blur all columns (50% faster than full 2D blur)
|
||||
inline void blurRows(fract8 blur_amount, bool smear = false) {}
|
||||
inline void moveX(int delta, bool wrap = false) {}
|
||||
inline void moveY(int delta, bool wrap = false) {}
|
||||
inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {}
|
||||
@@ -802,6 +811,8 @@ class Segment {
|
||||
inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {}
|
||||
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {}
|
||||
inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {}
|
||||
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {}
|
||||
inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {}
|
||||
inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {}
|
||||
#endif
|
||||
friend class WS2812FX;
|
||||
|
||||
@@ -149,14 +149,14 @@ void WS2812FX::setUpMatrix() {
|
||||
// pixel is clipped if it falls outside clipping range
|
||||
// if clipping start > stop the clipping range is inverted
|
||||
bool Segment::isPixelXYClipped(int x, int y) const {
|
||||
if (blendingStyle != TRANSITION_FADE && isInTransition() && _clipStart != _clipStop) {
|
||||
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
|
||||
const bool invertX = _clipStart > _clipStop;
|
||||
const bool invertY = _clipStartY > _clipStopY;
|
||||
const int cStartX = invertX ? _clipStop : _clipStart;
|
||||
const int cStopX = invertX ? _clipStart : _clipStop;
|
||||
const int cStartY = invertY ? _clipStopY : _clipStartY;
|
||||
const int cStopY = invertY ? _clipStartY : _clipStopY;
|
||||
if (blendingStyle == TRANSITION_FAIRY_DUST) {
|
||||
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
|
||||
const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth())
|
||||
const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight())
|
||||
if (len < 2) return false;
|
||||
@@ -164,10 +164,10 @@ bool Segment::isPixelXYClipped(int x, int y) const {
|
||||
const unsigned pos = (shuffled * 0xFFFFU) / len;
|
||||
return progress() <= pos;
|
||||
}
|
||||
if (blendingStyle == TRANSITION_CIRCULAR_IN || blendingStyle == TRANSITION_CIRCULAR_OUT) {
|
||||
if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) {
|
||||
const int cx = (cStopX-cStartX+1) / 2;
|
||||
const int cy = (cStopY-cStartY+1) / 2;
|
||||
const bool out = (blendingStyle == TRANSITION_CIRCULAR_OUT);
|
||||
const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT);
|
||||
const unsigned prog = out ? progress() : 0xFFFFU - progress();
|
||||
int radius2 = max(cx, cy) * prog / 0xFFFF;
|
||||
radius2 = 2 * radius2 * radius2;
|
||||
@@ -179,7 +179,7 @@ bool Segment::isPixelXYClipped(int x, int y) const {
|
||||
}
|
||||
bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside;
|
||||
bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside;
|
||||
const bool clip = blendingStyle == TRANSITION_OUTSIDE_IN ? xInside || yInside : xInside && yInside;
|
||||
const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside;
|
||||
return !clip;
|
||||
}
|
||||
return false;
|
||||
@@ -562,6 +562,51 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
|
||||
}
|
||||
}
|
||||
|
||||
#include "src/font/console_font_4x6.h"
|
||||
#include "src/font/console_font_5x8.h"
|
||||
#include "src/font/console_font_5x12.h"
|
||||
#include "src/font/console_font_6x8.h"
|
||||
#include "src/font/console_font_7x9.h"
|
||||
|
||||
// draws a raster font character on canvas
|
||||
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
|
||||
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) const {
|
||||
if (!isActive()) return; // not active
|
||||
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
|
||||
chr -= 32; // align with font table entries
|
||||
const int font = w*h;
|
||||
|
||||
// if col2 == BLACK then use currently selected palette for gradient otherwise create gradient from color and col2
|
||||
CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient
|
||||
|
||||
for (int i = 0; i<h; i++) { // character height
|
||||
uint8_t bits = 0;
|
||||
switch (font) {
|
||||
case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 4x6 font
|
||||
case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font
|
||||
case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font
|
||||
case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font
|
||||
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
|
||||
default: return;
|
||||
}
|
||||
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, 255, LINEARBLEND_NOWRAP); // NOBLEND is faster
|
||||
for (int j = 0; j<w; j++) { // character width
|
||||
int x0, y0;
|
||||
switch (rotate) {
|
||||
case -1: x0 = x + (h-1) - i; y0 = y + (w-1) - j; break; // -90 deg
|
||||
case -2:
|
||||
case 2: x0 = x + j; y0 = y + (h-1) - i; break; // 180 deg
|
||||
case 1: x0 = x + i; y0 = y + j; break; // +90 deg
|
||||
default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
|
||||
}
|
||||
if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
|
||||
if (((bits>>(j+(8-w))) & 0x01)) { // bit set
|
||||
setPixelColorXYRaw(x0, y0, c.color32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
|
||||
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu_pixel procedure by reddit u/sutaburosu
|
||||
if (!isActive()) return; // not active
|
||||
@@ -584,6 +629,4 @@ void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu
|
||||
}
|
||||
#undef WU_WEIGHT
|
||||
|
||||
|
||||
|
||||
#endif // WLED_DISABLE_2D
|
||||
#endif // WLED_DISABLE_2D
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
#include "wled.h"
|
||||
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
|
||||
#include "colors.h"
|
||||
|
||||
/*
|
||||
Custom per-LED mapping has moved!
|
||||
@@ -44,7 +43,7 @@ unsigned Segment::_vLength = 0;
|
||||
unsigned Segment::_vWidth = 0;
|
||||
unsigned Segment::_vHeight = 0;
|
||||
uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0};
|
||||
CRGBPalette16 Segment::_currentPalette = CRGBPalette16();
|
||||
CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
|
||||
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
|
||||
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
|
||||
uint16_t Segment::_lastPaletteChange = 0; // in seconds; perhaps it should be per segment
|
||||
@@ -225,8 +224,8 @@ void Segment::resetIfRequired() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
// there is one randomly generated palette (1) followed by 4 palettes created from segment colors (2-5)
|
||||
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
// there is one randomy generated palette (1) followed by 4 palettes created from segment colors (2-5)
|
||||
// those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
|
||||
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
|
||||
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
|
||||
@@ -236,7 +235,7 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
|
||||
switch (pal) {
|
||||
case 0: //default palette. Exceptions for specific effects above
|
||||
targetPalette = PartyColors_gc22;
|
||||
targetPalette = PartyColors_p;
|
||||
break;
|
||||
case 1: //randomly generated palette
|
||||
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
|
||||
@@ -278,6 +277,7 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
return targetPalette;
|
||||
}
|
||||
|
||||
// starting a transition has to occur before change so we get current values 1st
|
||||
@@ -343,7 +343,7 @@ void Segment::updateTransitionProgress() const {
|
||||
uint8_t Segment::currentCCT() const {
|
||||
unsigned prog = progress();
|
||||
if (prog < 0xFFFFU) {
|
||||
if (blendingStyle == TRANSITION_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU;
|
||||
if (blendingStyle == BLEND_STYLE_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU;
|
||||
//else return Segment::isPreviousMode() ? _t->_cct : cct;
|
||||
}
|
||||
return cct;
|
||||
@@ -355,7 +355,7 @@ uint8_t Segment::currentBri() const {
|
||||
unsigned curBri = on ? opacity : 0;
|
||||
if (prog < 0xFFFFU) {
|
||||
// this will blend opacity in new mode if style is FADE (single effect call)
|
||||
if (blendingStyle == TRANSITION_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU;
|
||||
if (blendingStyle == BLEND_STYLE_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU;
|
||||
else curBri = Segment::isPreviousMode() ? _t->_bri : curBri;
|
||||
}
|
||||
return curBri;
|
||||
@@ -371,7 +371,7 @@ void Segment::beginDraw(uint16_t prog) {
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = colors[i];
|
||||
// load palette into _currentPalette
|
||||
loadPalette(Segment::_currentPalette, palette);
|
||||
if (isInTransition() && prog < 0xFFFFU && blendingStyle == TRANSITION_FADE) {
|
||||
if (isInTransition() && prog < 0xFFFFU && blendingStyle == BLEND_STYLE_FADE) {
|
||||
// blend colors
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = color_blend16(_t->_colors[i], colors[i], prog);
|
||||
// blend palettes
|
||||
@@ -379,7 +379,7 @@ void Segment::beginDraw(uint16_t prog) {
|
||||
// minimum blend time is 100ms maximum is 65535ms
|
||||
#ifndef WLED_SAVE_RAM
|
||||
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
|
||||
if (noOfBlends > 255) noOfBlends = 255; // safety check
|
||||
if(noOfBlends > 255) noOfBlends = 255; // safety check
|
||||
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48);
|
||||
Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette
|
||||
#else
|
||||
@@ -506,7 +506,7 @@ Segment &Segment::setColor(uint8_t slot, uint32_t c) {
|
||||
if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black
|
||||
}
|
||||
//DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c);
|
||||
startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change
|
||||
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change
|
||||
colors[slot] = c;
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
return *this;
|
||||
@@ -530,7 +530,7 @@ Segment &Segment::setCCT(uint16_t k) {
|
||||
Segment &Segment::setOpacity(uint8_t o) {
|
||||
if (opacity != o) {
|
||||
//DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o);
|
||||
startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change
|
||||
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change
|
||||
opacity = o;
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
}
|
||||
@@ -541,7 +541,7 @@ Segment &Segment::setOption(uint8_t n, bool val) {
|
||||
bool prev = (options >> n) & 0x01;
|
||||
if (val == prev) return *this;
|
||||
//DEBUG_PRINTF_P(PSTR("- Starting option transition: %d\n"), n);
|
||||
if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change
|
||||
if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change
|
||||
if (val) options |= 0x01 << n;
|
||||
else options &= ~(0x01 << n);
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
@@ -577,7 +577,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
|
||||
sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette
|
||||
if (sOpt >= 0 && loadDefaults) setPalette(sOpt);
|
||||
if (sOpt <= 0) sOpt = 6; // partycolors if zero or not set
|
||||
_default_palette = sOpt; // _default_palette is loaded into pal0 in loadPalette() (if selected)
|
||||
_default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected)
|
||||
markForReset();
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
}
|
||||
@@ -588,7 +588,7 @@ Segment &Segment::setPalette(uint8_t pal) {
|
||||
if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
|
||||
if (pal != palette) {
|
||||
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
|
||||
startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change (no need to copy segment)
|
||||
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment)
|
||||
palette = pal;
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
}
|
||||
@@ -692,11 +692,11 @@ uint16_t Segment::maxMappingLength() const {
|
||||
// pixel is clipped if it falls outside clipping range
|
||||
// if clipping start > stop the clipping range is inverted
|
||||
bool Segment::isPixelClipped(int i) const {
|
||||
if (blendingStyle != TRANSITION_FADE && isInTransition() && _clipStart != _clipStop) {
|
||||
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
|
||||
bool invert = _clipStart > _clipStop; // ineverted start & stop
|
||||
int start = invert ? _clipStop : _clipStart;
|
||||
int stop = invert ? _clipStart : _clipStop;
|
||||
if (blendingStyle == TRANSITION_FAIRY_DUST) {
|
||||
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
|
||||
unsigned len = stop - start;
|
||||
if (len < 2) return false;
|
||||
unsigned shuffled = hashInt(i) % len;
|
||||
@@ -1104,9 +1104,9 @@ void Segment::blur(uint8_t blur_amount, bool smear) const {
|
||||
uint32_t Segment::color_wheel(uint8_t pos) const {
|
||||
if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set
|
||||
uint8_t w = W(getCurrentColor(0));
|
||||
CRGBW rgb;
|
||||
rgb = CHSV32(static_cast<uint16_t>(pos << 8), 255, 255);
|
||||
return rgb.color32 | (w << 24); // add white channel
|
||||
uint32_t rgb;
|
||||
hsv2rgb(CHSV32(static_cast<uint16_t>(pos << 8), 255, 255), rgb);
|
||||
return rgb | (w << 24); // add white channel
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1266,30 +1266,33 @@ void WS2812FX::finalizeInit() {
|
||||
|
||||
void WS2812FX::service() {
|
||||
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
|
||||
now = nowUp + timebase;
|
||||
unsigned long elapsed = nowUp - _lastServiceShow;
|
||||
bool timeToShow = (elapsed >= _frametime); // all segments are running at the same speed
|
||||
if (_triggered || _targetFps == FPS_UNLIMITED) timeToShow = true; // unlimited mode = no frametime; strip.trigger() can overrule timing
|
||||
|
||||
now = nowUp + timebase; // common time base for all effects
|
||||
if (!timeToShow) return; // too early for service
|
||||
if (_suspend || elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited
|
||||
if (!_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime
|
||||
if (elapsed < _frametime) return; // too early for service
|
||||
}
|
||||
|
||||
bool doShow = false;
|
||||
|
||||
_isServicing = true;
|
||||
bool doShow = _triggered; // true if ≥1 active segment was processed (and strip was not suspended mid-loop), or trigger received → triggers show()
|
||||
for (size_t i = 0; i < _segments.size(); i++) {
|
||||
Segment &seg = _segments[i];
|
||||
_segment_index = i;
|
||||
if (_suspend) break; // abort processing segments if suspend requested during service()
|
||||
_segment_index = 0;
|
||||
|
||||
for (Segment &seg : _segments) {
|
||||
if (_suspend) break; // immediately stop processing segments if suspend requested during service()
|
||||
|
||||
// process transition (also pre-calculates progress value)
|
||||
seg.handleTransition();
|
||||
// reset the segment runtime data if needed
|
||||
seg.resetIfRequired();
|
||||
|
||||
if (seg.isActive()) {
|
||||
// current segment is active -> re-run effect, and remember that show() call is necessary
|
||||
// if we arrive here, its always showtime (timeToShow == true)
|
||||
if (!seg.isActive()) continue;
|
||||
|
||||
// last condition ensures all solid segments are updated at the same time
|
||||
if (nowUp > _lastServiceShow + _frametime || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
|
||||
{
|
||||
doShow = true;
|
||||
|
||||
if (!seg.freeze) { //only run effect function if not frozen
|
||||
// Effect blending
|
||||
uint16_t prog = seg.progress();
|
||||
@@ -1301,21 +1304,20 @@ void WS2812FX::service() {
|
||||
// if segment is in transition and no old segment exists we don't need to run the old mode
|
||||
// (blendSegments() takes care of On/Off transitions and clipping)
|
||||
Segment *segO = seg.getOldSegment();
|
||||
if (segO && segO->isActive() && (seg.mode != segO->mode || blendingStyle != TRANSITION_FADE ||
|
||||
if (segO && segO->isActive() && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE ||
|
||||
(segO->name != seg.name && segO->name && seg.name && strncmp(segO->name, seg.name, WLED_MAX_SEGNAME_LEN) != 0))) {
|
||||
Segment::modeBlend(true); // set flag for beginDraw() to blend colors and palette
|
||||
Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette
|
||||
segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress
|
||||
_currentSegment = segO; // set current segment
|
||||
// workaround for on/off transition to respect blending style
|
||||
_mode[segO->mode](); // run old mode (needed for bri workaround; semaphore!!)
|
||||
segO->call++; // increment old mode run counter
|
||||
Segment::modeBlend(false); // unset flag
|
||||
Segment::modeBlend(false); // unset semaphore
|
||||
}
|
||||
}
|
||||
}
|
||||
_segment_index++;
|
||||
}
|
||||
_segment_index = 0; // segment index is only valid while effects are serviced
|
||||
_currentSegment = &_segments[0]; // safe fallback to prevent stale pointer - SEGMENT/SEGENV should not be used outside of the service loop
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
|
||||
@@ -1330,14 +1332,14 @@ void WS2812FX::service() {
|
||||
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
|
||||
#endif
|
||||
|
||||
if (!_suspend) _triggered = false; // avoid losing "trigger" events if suspend requested during effect service()
|
||||
_triggered = false;
|
||||
_isServicing = false;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer
|
||||
static uint8_t _top (uint8_t a, uint8_t b) { return a; } // function unused
|
||||
static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } // function unused
|
||||
static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } // function unused
|
||||
static uint8_t _top (uint8_t a, uint8_t b) { return a; }
|
||||
static uint8_t _bottom (uint8_t a, uint8_t b) { return b; }
|
||||
static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; }
|
||||
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
||||
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
||||
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
||||
@@ -1359,41 +1361,24 @@ static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a)
|
||||
#endif
|
||||
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
|
||||
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
|
||||
static uint8_t _stencil (uint8_t a, uint8_t b) { return a ? a : b; } // function unused
|
||||
static uint8_t _dummy (uint8_t a, uint8_t b) { return a; } // dummy (same as _top) to fill the function list and make it safe from OOB access
|
||||
|
||||
#define BLENDMODES 17 // number of blend modes must match "bm" in index.js, all cases must be handled in segblend() @ blendSegment()
|
||||
|
||||
void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
|
||||
typedef uint8_t(*FuncType)(uint8_t, uint8_t);
|
||||
// function pointer array: fill with _dummy if using special case: avoid OOB access and always provide a valid path
|
||||
FuncType funcs[] = {
|
||||
_dummy, _dummy, _dummy, _subtract,
|
||||
_difference, _average, _dummy, _divide,
|
||||
_lighten, _darken, _screen, _overlay,
|
||||
_hardlight, _softlight, _dodge, _burn,
|
||||
_dummy
|
||||
_top, _bottom,
|
||||
_add, _subtract, _difference, _average,
|
||||
_multiply, _divide, _lighten, _darken, _screen, _overlay,
|
||||
_hardlight, _softlight, _dodge, _burn
|
||||
};
|
||||
|
||||
const size_t blendMode = topSegment.blendMode < BLENDMODES ? topSegment.blendMode : 0; // default to top if unsupported mode
|
||||
const auto segblend = [&](uint32_t t, uint32_t b){
|
||||
// use direct calculations/returns for simple/frequent modes (faster)
|
||||
switch (blendMode) {
|
||||
case 0 : return t; // top
|
||||
case 1 : return b; // bottom
|
||||
case 2 : return color_add(t,b,true); // add with preserve color ratio to avoid color clipping
|
||||
case 6 : return RGBW32(_multiply(R(t),R(b)), _multiply(G(t),G(b)), _multiply(B(t),B(b)), _multiply(W(t),W(b))); // multiply (7% faster than lambda at 100bytes flash cost)
|
||||
case 16: return t ? t : b; // stencil (use top layer if not black, else bottom)
|
||||
}
|
||||
// default: use function pointer from array
|
||||
const auto func = funcs[blendMode];
|
||||
return RGBW32(func(R(t),R(b)), func(G(t),G(b)), func(B(t),B(b)), func(W(t),W(b)));
|
||||
};
|
||||
const size_t blendMode = topSegment.blendMode < (sizeof(funcs) / sizeof(FuncType)) ? topSegment.blendMode : 0;
|
||||
const auto func = funcs[blendMode]; // blendMode % (sizeof(funcs) / sizeof(FuncType))
|
||||
const auto blend = [&](uint32_t top, uint32_t bottom){ return RGBW32(func(R(top),R(bottom)), func(G(top),G(bottom)), func(B(top),B(bottom)), func(W(top),W(bottom))); };
|
||||
|
||||
const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment)
|
||||
const int width = topSegment.width();
|
||||
const int height = topSegment.height();
|
||||
//const uint32_t bgColor = topSegment.colors[1]; // background color (unused, could add it to stencil mode if requested)
|
||||
const auto XY = [](int x, int y){ return x + y*Segment::maxWidth; };
|
||||
const size_t matrixSize = Segment::maxWidth * Segment::maxHeight;
|
||||
const size_t startIndx = XY(topSegment.start, topSegment.startY);
|
||||
@@ -1406,58 +1391,58 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
|
||||
Segment::setClippingRect(0, 0); // disable clipping by default
|
||||
|
||||
const unsigned dw = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1;
|
||||
const unsigned dh = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1;
|
||||
const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1;
|
||||
const unsigned dh = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1;
|
||||
const unsigned orgBS = blendingStyle;
|
||||
if (width*height == 1) blendingStyle = TRANSITION_FADE; // disable style for single pixel segments (use fade instead)
|
||||
if (width*height == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead)
|
||||
switch (blendingStyle) {
|
||||
case TRANSITION_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped())
|
||||
case TRANSITION_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped())
|
||||
case TRANSITION_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
|
||||
case BLEND_STYLE_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped())
|
||||
case BLEND_STYLE_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped())
|
||||
case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
|
||||
Segment::setClippingRect(0, width, 0, height);
|
||||
break;
|
||||
case TRANSITION_SWIPE_RIGHT: // left-to-right
|
||||
case TRANSITION_PUSH_RIGHT: // left-to-right
|
||||
case BLEND_STYLE_SWIPE_RIGHT: // left-to-right
|
||||
case BLEND_STYLE_PUSH_RIGHT: // left-to-right
|
||||
Segment::setClippingRect(0, dw, 0, height);
|
||||
break;
|
||||
case TRANSITION_SWIPE_LEFT: // right-to-left
|
||||
case TRANSITION_PUSH_LEFT: // right-to-left
|
||||
case BLEND_STYLE_SWIPE_LEFT: // right-to-left
|
||||
case BLEND_STYLE_PUSH_LEFT: // right-to-left
|
||||
Segment::setClippingRect(width - dw, width, 0, height);
|
||||
break;
|
||||
case TRANSITION_OUTSIDE_IN: // corners
|
||||
case BLEND_STYLE_OUTSIDE_IN: // corners
|
||||
Segment::setClippingRect((width + dw)/2, (width - dw)/2, (height + dh)/2, (height - dh)/2); // inverted!!
|
||||
break;
|
||||
case TRANSITION_INSIDE_OUT: // outward
|
||||
case BLEND_STYLE_INSIDE_OUT: // outward
|
||||
Segment::setClippingRect((width - dw)/2, (width + dw)/2, (height - dh)/2, (height + dh)/2);
|
||||
break;
|
||||
case TRANSITION_SWIPE_DOWN: // top-to-bottom (2D)
|
||||
case TRANSITION_PUSH_DOWN: // top-to-bottom (2D)
|
||||
case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D)
|
||||
case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D)
|
||||
Segment::setClippingRect(0, width, 0, dh);
|
||||
break;
|
||||
case TRANSITION_SWIPE_UP: // bottom-to-top (2D)
|
||||
case TRANSITION_PUSH_UP: // bottom-to-top (2D)
|
||||
case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D)
|
||||
case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D)
|
||||
Segment::setClippingRect(0, width, height - dh, height);
|
||||
break;
|
||||
case TRANSITION_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D
|
||||
case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D
|
||||
Segment::setClippingRect((width - dw)/2, (width + dw)/2, 0, height);
|
||||
break;
|
||||
case TRANSITION_OPEN_V: // vertical-outward (2D)
|
||||
case BLEND_STYLE_OPEN_V: // vertical-outward (2D)
|
||||
Segment::setClippingRect(0, width, (height - dh)/2, (height + dh)/2);
|
||||
break;
|
||||
case TRANSITION_SWIPE_TL: // TL-to-BR (2D)
|
||||
case TRANSITION_PUSH_TL: // TL-to-BR (2D)
|
||||
case BLEND_STYLE_SWIPE_TL: // TL-to-BR (2D)
|
||||
case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D)
|
||||
Segment::setClippingRect(0, dw, 0, dh);
|
||||
break;
|
||||
case TRANSITION_SWIPE_TR: // TR-to-BL (2D)
|
||||
case TRANSITION_PUSH_TR: // TR-to-BL (2D)
|
||||
case BLEND_STYLE_SWIPE_TR: // TR-to-BL (2D)
|
||||
case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D)
|
||||
Segment::setClippingRect(width - dw, width, 0, dh);
|
||||
break;
|
||||
case TRANSITION_SWIPE_BR: // BR-to-TL (2D)
|
||||
case TRANSITION_PUSH_BR: // BR-to-TL (2D)
|
||||
case BLEND_STYLE_SWIPE_BR: // BR-to-TL (2D)
|
||||
case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D)
|
||||
Segment::setClippingRect(width - dw, width, height - dh, height);
|
||||
break;
|
||||
case TRANSITION_SWIPE_BL: // BL-to-TR (2D)
|
||||
case TRANSITION_PUSH_BL: // BL-to-TR (2D)
|
||||
case BLEND_STYLE_SWIPE_BL: // BL-to-TR (2D)
|
||||
case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D)
|
||||
Segment::setClippingRect(0, dw, height - dh, height);
|
||||
break;
|
||||
}
|
||||
@@ -1474,18 +1459,18 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
const int baseX = topSegment.start + x;
|
||||
const int baseY = topSegment.startY + y;
|
||||
size_t indx = XY(baseX, baseY); // absolute address on strip
|
||||
_pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o);
|
||||
_pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o);
|
||||
if (_pixelCCT) _pixelCCT[indx] = cct;
|
||||
// Apply mirroring if enabled
|
||||
// Apply mirroring
|
||||
if (topSegment.mirror || topSegment.mirror_y) {
|
||||
const int mirrorX = topSegment.start + width - x - 1;
|
||||
const int mirrorY = topSegment.startY + height - y - 1;
|
||||
const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY);
|
||||
const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY);
|
||||
const size_t idxMM = XY(mirrorX, mirrorY);
|
||||
if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], segblend(c, _pixels[idxMX]), o);
|
||||
if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], segblend(c, _pixels[idxMY]), o);
|
||||
if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], segblend(c, _pixels[idxMM]), o);
|
||||
if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], blend(c, _pixels[idxMX]), o);
|
||||
if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], blend(c, _pixels[idxMY]), o);
|
||||
if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], blend(c, _pixels[idxMM]), o);
|
||||
if (_pixelCCT) {
|
||||
if (topSegment.mirror) _pixelCCT[idxMX] = cct;
|
||||
if (topSegment.mirror_y) _pixelCCT[idxMY] = cct;
|
||||
@@ -1495,22 +1480,9 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
};
|
||||
|
||||
// if we blend using "push" style we need to "shift" canvas to left/right/up/down
|
||||
unsigned offsetX = (blendingStyle == TRANSITION_PUSH_UP || blendingStyle == TRANSITION_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU;
|
||||
unsigned offsetY = (blendingStyle == TRANSITION_PUSH_LEFT || blendingStyle == TRANSITION_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU;
|
||||
const unsigned groupLen = topSegment.groupLength();
|
||||
bool applyReverse = topSegment.reverse || topSegment.reverse_y || topSegment.transpose;
|
||||
int pushOffsetX = 0, pushOffsetY = 0;
|
||||
// if we blend using "push" style we need to "shift" canvas to left/right/up/down
|
||||
switch (blendingStyle) {
|
||||
case TRANSITION_PUSH_RIGHT: pushOffsetX = offsetX; break;
|
||||
case TRANSITION_PUSH_LEFT: pushOffsetX = -offsetX + nCols; break;
|
||||
case TRANSITION_PUSH_DOWN: pushOffsetY = offsetY; break;
|
||||
case TRANSITION_PUSH_UP: pushOffsetY = -offsetY + nRows; break;
|
||||
case TRANSITION_PUSH_TL: pushOffsetX = offsetX; pushOffsetY = offsetY; break; // unused
|
||||
case TRANSITION_PUSH_TR: pushOffsetX = -offsetX + nCols; pushOffsetY = offsetY; break; // unused
|
||||
case TRANSITION_PUSH_BR: pushOffsetX = -offsetX + nCols; pushOffsetY = -offsetY + nRows; break; // unused
|
||||
case TRANSITION_PUSH_BL: pushOffsetX = offsetX; pushOffsetY = -offsetY + nRows; break; // unused
|
||||
}
|
||||
unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU;
|
||||
unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU;
|
||||
|
||||
// we only traverse new segment, not old one
|
||||
for (int r = 0; r < nRows; r++) for (int c = 0; c < nCols; c++) {
|
||||
const bool clipped = topSegment.isPixelXYClipped(c, r);
|
||||
@@ -1520,16 +1492,21 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
int vRows = seg == segO ? oRows : nRows; // old segment may have different dimensions
|
||||
int x = c;
|
||||
int y = r;
|
||||
if (pushOffsetX != 0) x = (x + pushOffsetX) % nCols;
|
||||
if (pushOffsetY != 0) y = (y + pushOffsetY) % nRows;
|
||||
// if we blend using "push" style we need to "shift" canvas to left/right/up/down
|
||||
switch (blendingStyle) {
|
||||
case BLEND_STYLE_PUSH_RIGHT: x = (x + offsetX) % nCols; break;
|
||||
case BLEND_STYLE_PUSH_LEFT: x = (x - offsetX + nCols) % nCols; break;
|
||||
case BLEND_STYLE_PUSH_DOWN: y = (y + offsetY) % nRows; break;
|
||||
case BLEND_STYLE_PUSH_UP: y = (y - offsetY + nRows) % nRows; break;
|
||||
}
|
||||
uint32_t c_a = BLACK;
|
||||
if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment
|
||||
if (segO && blendingStyle == TRANSITION_FADE
|
||||
if (segO && blendingStyle == BLEND_STYLE_FADE
|
||||
&& (topSegment.mode != segO->mode || (segO->name != topSegment.name && segO->name && topSegment.name && strncmp(segO->name, topSegment.name, WLED_MAX_SEGNAME_LEN) != 0))
|
||||
&& x < oCols && y < oRows) {
|
||||
// we need to blend old segment using fade as pixels are not clipped
|
||||
c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv);
|
||||
} else if (blendingStyle != TRANSITION_FADE) {
|
||||
} else if (blendingStyle != BLEND_STYLE_FADE) {
|
||||
// if we have global brightness change (not On/Off change) we will ignore transition style and just fade brightness (see led.cpp)
|
||||
// workaround for On/Off transition
|
||||
// (bri != briT) && !bri => from On to Off
|
||||
@@ -1539,12 +1516,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
// map it into frame buffer
|
||||
x = c; // restore coordiates if we were PUSHing
|
||||
y = r;
|
||||
if (applyReverse) {
|
||||
if (topSegment.reverse ) x = nCols - x - 1;
|
||||
if (topSegment.reverse_y) y = nRows - y - 1;
|
||||
if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed
|
||||
}
|
||||
if (topSegment.reverse ) x = nCols - x - 1;
|
||||
if (topSegment.reverse_y) y = nRows - y - 1;
|
||||
if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed
|
||||
// expand pixel
|
||||
const unsigned groupLen = topSegment.groupLength();
|
||||
if (groupLen == 1) {
|
||||
setMirroredPixel(x, y, c_a, opacity);
|
||||
} else {
|
||||
@@ -1573,12 +1549,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
unsigned indxM = topSegment.stop - i - 1;
|
||||
indxM += topSegment.offset; // offset/phase
|
||||
if (indxM >= topSegment.stop) indxM -= length; // wrap
|
||||
_pixels[indxM] = color_blend(_pixels[indxM], segblend(c, _pixels[indxM]), o);
|
||||
_pixels[indxM] = color_blend(_pixels[indxM], blend(c, _pixels[indxM]), o);
|
||||
if (_pixelCCT) _pixelCCT[indxM] = cct;
|
||||
}
|
||||
indx += topSegment.offset; // offset/phase
|
||||
if (indx >= topSegment.stop) indx -= length; // wrap
|
||||
_pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o);
|
||||
_pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o);
|
||||
if (_pixelCCT) _pixelCCT[indx] = cct;
|
||||
};
|
||||
|
||||
@@ -1593,15 +1569,15 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
int i = k;
|
||||
// if we blend using "push" style we need to "shift" canvas to left or right
|
||||
switch (blendingStyle) {
|
||||
case TRANSITION_PUSH_RIGHT: i = (i + offsetI) % nLen; break;
|
||||
case TRANSITION_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break;
|
||||
case BLEND_STYLE_PUSH_RIGHT: i = (i + offsetI) % nLen; break;
|
||||
case BLEND_STYLE_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break;
|
||||
}
|
||||
uint32_t c_a = BLACK;
|
||||
if (i < vLen) c_a = seg->getPixelColorRaw(i); // will get clipped pixel from old segment or unclipped pixel from new segment
|
||||
if (segO && blendingStyle == TRANSITION_FADE && topSegment.mode != segO->mode && i < oLen) {
|
||||
if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && i < oLen) {
|
||||
// we need to blend old segment using fade as pixels are not clipped
|
||||
c_a = color_blend16(c_a, segO->getPixelColorRaw(i), progInv);
|
||||
} else if (blendingStyle != TRANSITION_FADE) {
|
||||
} else if (blendingStyle != BLEND_STYLE_FADE) {
|
||||
// if we have global brightness change (not On/Off change) we will ignore transition style and just fade brightness (see led.cpp)
|
||||
// workaround for On/Off transition
|
||||
// (bri != briT) && !bri => from On to Off
|
||||
@@ -1666,7 +1642,7 @@ void WS2812FX::show() {
|
||||
}
|
||||
|
||||
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
|
||||
if (c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||
if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), c);
|
||||
}
|
||||
@@ -1752,7 +1728,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
|
||||
BusManager::setBrightness(scaledBri(b));
|
||||
if (!direct) {
|
||||
unsigned long t = millis();
|
||||
if (t - _lastShow > min(_frametime, uint16_t(FRAMETIME_FIXED))) trigger(); //apply brightness change immediately if no refresh soon, but don't speed up above 42fps
|
||||
if (t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -595,15 +595,16 @@ void ParticleSystem2D::render() {
|
||||
if (fireIntesity) { // fire mode
|
||||
brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 5;
|
||||
brightness = min(brightness, (uint32_t)255);
|
||||
baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP);
|
||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP); // map hue to brightness for fire effect
|
||||
}
|
||||
else {
|
||||
brightness = min((particles[i].ttl << 1), (int)255);
|
||||
baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, blend);
|
||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
|
||||
if (particles[i].sat < 255) {
|
||||
CHSV32 baseHSV = baseRGB;
|
||||
CHSV32 baseHSV;
|
||||
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
|
||||
baseHSV.s = min(baseHSV.s, particles[i].sat); // set the saturation but don't increase it
|
||||
hsv2rgb_spectrum(baseHSV, baseRGB); // convert back to RGB
|
||||
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
|
||||
}
|
||||
}
|
||||
if (gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
||||
@@ -804,6 +805,7 @@ void WLED_O2_ATTR ParticleSystem2D::renderLargeParticle(const uint32_t size, con
|
||||
if (gammaCorrectCol) {
|
||||
pixel_brightness = gamma8inv(pixel_brightness); // invert brigthess so brightness distribution is linear after gamma correction
|
||||
}
|
||||
|
||||
// Render pixel
|
||||
uint32_t idx = render_x + (maxYpixel - render_y) * matrixX; // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer)
|
||||
framebuffer[idx] = fast_color_scaleAdd(framebuffer[idx], color, pixel_brightness);
|
||||
@@ -1455,12 +1457,14 @@ void ParticleSystem1D::render() {
|
||||
|
||||
// generate RGB values for particle
|
||||
brightness = min(particles[i].ttl << 1, (int)255);
|
||||
baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, blend);
|
||||
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
|
||||
|
||||
if (advPartProps != nullptr) { //saturation is advanced property in 1D system
|
||||
if (advPartProps[i].sat < 255) {
|
||||
CHSV32 baseHSV = baseRGB;
|
||||
baseHSV.s = advPartProps[i].sat; // set the saturation
|
||||
hsv2rgb_spectrum(baseHSV, baseRGB); // convert back to RGB
|
||||
CHSV32 baseHSV;
|
||||
rgb2hsv(baseRGB.color32, baseHSV); // convert to HSV
|
||||
baseHSV.s = min(baseHSV.s, advPartProps[i].sat); // set the saturation but don't increase it
|
||||
hsv2rgb(baseHSV, baseRGB.color32); // convert back to RGB
|
||||
}
|
||||
}
|
||||
if (gammaCorrectCol) brightness = gamma8(brightness); // apply gamma correction, used for gamma-inverted brightness distribution
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#pragma once
|
||||
/*
|
||||
asyncDNS.h - wrapper class for asynchronous DNS lookups using lwIP
|
||||
by @dedehai, C++ improvements & hardening by @willmmiles
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <lwip/dns.h>
|
||||
#include <lwip/err.h>
|
||||
|
||||
class AsyncDNS {
|
||||
|
||||
// C++14 shim
|
||||
#if __cplusplus < 201402L
|
||||
// Really simple C++11 shim for non-array case; implementation from cppreference.com
|
||||
template<class T, class... Args>
|
||||
static std::unique_ptr<T>
|
||||
make_unique(Args&&... args)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
// note: passing the IP as a pointer to query() is not implemented because it is not thread-safe without mutexes
|
||||
// with the IDF V4 bug external error handling is required anyway or dns can just stay stuck
|
||||
enum class result { Idle, Busy, Success, Error };
|
||||
|
||||
// non-blocking query function to start DNS lookup
|
||||
static std::shared_ptr<AsyncDNS> query(const char* hostname, std::shared_ptr<AsyncDNS> current = {}) {
|
||||
if (!current || (current->_status == result::Busy)) {
|
||||
current.reset(new AsyncDNS());
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
using std::make_unique;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<std::shared_ptr<AsyncDNS>> callback_state = make_unique<std::shared_ptr<AsyncDNS> >(current);
|
||||
if (!callback_state) return {};
|
||||
|
||||
current->_status = result::Busy;
|
||||
err_t err = dns_gethostbyname(hostname, ¤t->_raw_addr, _dns_callback, callback_state.get());
|
||||
if (err == ERR_OK) {
|
||||
current->_status = result::Success; // result already in cache
|
||||
} else if (err == ERR_INPROGRESS) {
|
||||
callback_state.release(); // belongs to the callback now
|
||||
} else {
|
||||
current->_status = result::Error;
|
||||
current->_errorcount++;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
// get the IP once Success is returned
|
||||
const IPAddress getIP() {
|
||||
if (_status != result::Success) return IPAddress(0,0,0,0);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
return IPAddress(_raw_addr.u_addr.ip4.addr);
|
||||
#else
|
||||
return IPAddress(_raw_addr.addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset() { _errorcount = 0; } // reset status and error count
|
||||
const result status() { return _status; }
|
||||
const uint16_t getErrorCount() { return _errorcount; }
|
||||
|
||||
private:
|
||||
ip_addr_t _raw_addr;
|
||||
std::atomic<result> _status { result::Idle };
|
||||
uint16_t _errorcount = 0;
|
||||
|
||||
AsyncDNS(){}; // may not be explicitly instantiated - use query()
|
||||
|
||||
// callback for dns_gethostbyname(), called when lookup is complete or timed out
|
||||
static void _dns_callback(const char *name, const ip_addr_t *ipaddr, void *arg) {
|
||||
std::shared_ptr<AsyncDNS>* instance_ptr = reinterpret_cast<std::shared_ptr<AsyncDNS>*>(arg);
|
||||
AsyncDNS& instance = **instance_ptr;
|
||||
if (ipaddr) {
|
||||
instance._raw_addr = *ipaddr;
|
||||
instance._status = result::Success;
|
||||
} else {
|
||||
instance._status = result::Error; // note: if query timed out (~5s), DNS lookup is broken until WiFi connection is reset (IDF V4 bug)
|
||||
instance._errorcount++;
|
||||
}
|
||||
delete instance_ptr;
|
||||
}
|
||||
};
|
||||
@@ -744,23 +744,13 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void BusNetwork::resolveHostname() {
|
||||
static std::shared_ptr<AsyncDNS> DNSlookup; // TODO: make this dynamic? requires to handle the callback properly
|
||||
if (Network.isConnected()) {
|
||||
static unsigned long nextResolve = 0;
|
||||
if (Network.isConnected() && millis() > nextResolve && _hostname.length() > 0) {
|
||||
nextResolve = millis() + 600000; // resolve only every 10 minutes
|
||||
IPAddress clnt;
|
||||
if (strlen(cmDNS) > 0) {
|
||||
clnt = MDNS.queryHost(_hostname);
|
||||
if (clnt != IPAddress()) _client = clnt; // update client IP if not null
|
||||
}
|
||||
else {
|
||||
int timeout = 5000; // 5 seconds timeout
|
||||
DNSlookup = AsyncDNS::query(_hostname.c_str(), DNSlookup); // start async DNS query
|
||||
while (DNSlookup->status() == AsyncDNS::result::Busy && timeout-- > 0) {
|
||||
delay(1);
|
||||
}
|
||||
if (DNSlookup->status() == AsyncDNS::result::Success) {
|
||||
_client = DNSlookup->getIP(); // update client IP
|
||||
}
|
||||
}
|
||||
if (strlen(cmDNS) > 0) clnt = MDNS.queryHost(_hostname);
|
||||
else WiFi.hostByName(_hostname.c_str(), clnt);
|
||||
if (clnt != IPAddress()) _client = clnt;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1302,17 +1292,12 @@ void BusManager::on() {
|
||||
}
|
||||
}
|
||||
#else
|
||||
static uint32_t nextResolve = 0; // initial resolve is done on bus creation
|
||||
bool resolveNow = (millis() - nextResolve >= 600000); // wait at least 10 minutes between hostname resolutions (blocking call)
|
||||
for (auto &bus : busses) if (bus->isVirtual()) {
|
||||
// virtual/network bus should check for IP change if hostname is specified
|
||||
// otherwise there are no endpoints to force DNS resolution
|
||||
BusNetwork &b = static_cast<BusNetwork&>(*bus);
|
||||
if (resolveNow)
|
||||
b.resolveHostname();
|
||||
b.resolveHostname();
|
||||
}
|
||||
if (resolveNow)
|
||||
nextResolve = millis();
|
||||
#endif
|
||||
#ifdef ESP32_DATA_IDLE_HIGH
|
||||
esp32RMTInvertIdle();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
|
||||
#include "src/dependencies/fastled_slim/fastled_slim.h"
|
||||
#include <FastLED.h>
|
||||
|
||||
#endif
|
||||
/*
|
||||
@@ -17,9 +17,6 @@
|
||||
#include "pin_manager.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "asyncDNS.h"
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
using std::make_unique;
|
||||
|
||||
@@ -142,7 +142,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
getStringFromJson(apPass, ap["psk"] , 65); //normally not present due to security
|
||||
//int ap_pskl = ap[F("pskl")];
|
||||
CJSON(apChannel, ap[F("chan")]);
|
||||
if (apChannel > 13 || apChannel < 1) apChannel = 6; // reset to default if invalid
|
||||
if (apChannel > 13 || apChannel < 1) apChannel = 1;
|
||||
CJSON(apHide, ap[F("hide")]);
|
||||
if (apHide > 1) apHide = 1;
|
||||
CJSON(apBehavior, ap[F("behav")]);
|
||||
@@ -690,28 +690,37 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
CJSON(macroCountdown, cntdwn["macro"]);
|
||||
setCountdown();
|
||||
|
||||
JsonArray timersArray = tm["ins"];
|
||||
if (!timersArray.isNull()) {
|
||||
clearTimers();
|
||||
for (JsonObject timer : timersArray) {
|
||||
uint8_t h = timer[F("hour")] | 0;
|
||||
int8_t m = timer[F("min")] | 0;
|
||||
uint8_t p = timer[F("macro")] | 0;
|
||||
uint8_t dow = timer[F("dow")] | 127;
|
||||
uint8_t wd = (dow << 1) | ((timer[F("en")] | 0) ? 1 : 0);
|
||||
uint8_t ms = 1, me = 12, ds = 1, de = 31;
|
||||
JsonObject start = timer[F("start")];
|
||||
if (!start.isNull()) {
|
||||
ms = start[F("mon")] | 1;
|
||||
ds = start[F("day")] | 1;
|
||||
}
|
||||
JsonObject end = timer[F("end")];
|
||||
if (!end.isNull()) {
|
||||
me = end[F("mon")] | 12;
|
||||
de = end[F("day")] | 31;
|
||||
}
|
||||
addTimer(p, h, m, wd, ms, me, ds, de);
|
||||
JsonArray timers = tm["ins"];
|
||||
uint8_t it = 0;
|
||||
for (JsonObject timer : timers) {
|
||||
if (it > 9) break;
|
||||
if (it<8 && timer[F("hour")]==255) it=8; // hour==255 -> sunrise/sunset
|
||||
CJSON(timerHours[it], timer[F("hour")]);
|
||||
CJSON(timerMinutes[it], timer["min"]);
|
||||
CJSON(timerMacro[it], timer["macro"]);
|
||||
|
||||
byte dowPrev = timerWeekday[it];
|
||||
//note: act is currently only 0 or 1.
|
||||
//the reason we are not using bool is that the on-disk type in 0.11.0 was already int
|
||||
int actPrev = timerWeekday[it] & 0x01;
|
||||
CJSON(timerWeekday[it], timer[F("dow")]);
|
||||
if (timerWeekday[it] != dowPrev) { //present in JSON
|
||||
timerWeekday[it] <<= 1; //add active bit
|
||||
int act = timer["en"] | actPrev;
|
||||
if (act) timerWeekday[it]++;
|
||||
}
|
||||
if (it<8) {
|
||||
JsonObject start = timer["start"];
|
||||
byte startm = start["mon"];
|
||||
if (startm) timerMonth[it] = (startm << 4);
|
||||
CJSON(timerDay[it], start["day"]);
|
||||
JsonObject end = timer["end"];
|
||||
CJSON(timerDayEnd[it], end["day"]);
|
||||
byte endm = end["mon"];
|
||||
if (startm) timerMonth[it] += endm & 0x0F;
|
||||
if (!(timerMonth[it] & 0x0F)) timerMonth[it] += 12; //default end month to 12
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
JsonObject ota = doc["ota"];
|
||||
@@ -1208,21 +1217,23 @@ void serializeConfig(JsonObject root) {
|
||||
cntdwn["macro"] = macroCountdown;
|
||||
|
||||
JsonArray timers_ins = timers.createNestedArray("ins");
|
||||
for (size_t i = 0; i < ::timers.size(); i++) {
|
||||
const Timer& t = ::timers[i];
|
||||
if (t.preset == 0 && t.hour == 0 && t.minute == 0) continue;
|
||||
JsonObject ti = timers_ins.createNestedObject();
|
||||
ti[F("en")] = t.isEnabled() ? 1 : 0;
|
||||
ti[F("hour")] = t.hour;
|
||||
ti[F("min")] = t.minute;
|
||||
ti[F("macro")] = t.preset;
|
||||
ti[F("dow")] = t.weekdays >> 1;
|
||||
JsonObject start = ti.createNestedObject(F("start"));
|
||||
start[F("mon")] = t.monthStart;
|
||||
start[F("day")] = t.dayStart;
|
||||
JsonObject end = ti.createNestedObject(F("end"));
|
||||
end[F("mon")] = t.monthEnd;
|
||||
end[F("day")] = t.dayEnd;
|
||||
|
||||
for (unsigned i = 0; i < 10; i++) {
|
||||
if (timerMacro[i] == 0 && timerHours[i] == 0 && timerMinutes[i] == 0) continue; // sunrise/sunset get saved always (timerHours=255)
|
||||
JsonObject timers_ins0 = timers_ins.createNestedObject();
|
||||
timers_ins0["en"] = (timerWeekday[i] & 0x01);
|
||||
timers_ins0[F("hour")] = timerHours[i];
|
||||
timers_ins0["min"] = timerMinutes[i];
|
||||
timers_ins0["macro"] = timerMacro[i];
|
||||
timers_ins0[F("dow")] = timerWeekday[i] >> 1;
|
||||
if (i<8) {
|
||||
JsonObject start = timers_ins0.createNestedObject("start");
|
||||
start["mon"] = (timerMonth[i] >> 4) & 0xF;
|
||||
start["day"] = timerDay[i];
|
||||
JsonObject end = timers_ins0.createNestedObject("end");
|
||||
end["mon"] = timerMonth[i] & 0xF;
|
||||
end["day"] = timerDayEnd[i];
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject ota = root.createNestedObject("ota");
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
#include "wled.h"
|
||||
#include "fcn_declare.h"
|
||||
#include "colors.h"
|
||||
|
||||
/*
|
||||
* Color conversion & utility methods
|
||||
*/
|
||||
|
||||
/*
|
||||
* FastLED Reference
|
||||
* -----------------
|
||||
* functions in this file derived from FastLED @ 3.6.0 (https://github.com/FastLED/FastLED) are marked with a comment containing "derived from FastLED"
|
||||
* those functions are therefore licensed under the MIT license See /src/dependencies/fastled_slim/LICENSE.txt for details.
|
||||
*/
|
||||
|
||||
/*
|
||||
* color blend function
|
||||
* the calculation for each color is: result = (C1*(256-blend)+C2+C2*blend) / 256
|
||||
* color blend function, based on FastLED blend function
|
||||
* the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB
|
||||
*/
|
||||
uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
|
||||
// min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance
|
||||
@@ -34,7 +25,7 @@ uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, ui
|
||||
* original idea: https://github.com/wled-dev/WLED/pull/2465 by https://github.com/Proto-molecule
|
||||
* speed optimisations by @dedehai
|
||||
*/
|
||||
uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR)
|
||||
uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //1212558 | 1212598 | 1212576 | 1212530
|
||||
{
|
||||
if (c1 == BLACK) return c2;
|
||||
if (c2 == BLACK) return c1;
|
||||
@@ -49,9 +40,10 @@ uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR)
|
||||
uint32_t b = rb & 0xFFFF;
|
||||
uint32_t w = wg >> 16;
|
||||
uint32_t g = wg & 0xFFFF;
|
||||
uint32_t maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // find max value. note: faster than using max() function or on par
|
||||
maxval = (w > maxval) ? w : maxval; // check white channel as well to avoid division by zero in pure white input
|
||||
const uint32_t scale = (uint32_t(255)<<8) / maxval; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
|
||||
uint32_t max = std::max(r,g);
|
||||
max = std::max(max,b);
|
||||
max = std::max(max,w);
|
||||
const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
|
||||
rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK;
|
||||
wg = (wg * scale) & ~TWO_CHANNEL_MASK;
|
||||
} else wg <<= 8; //shift white and green back to correct position
|
||||
@@ -68,54 +60,48 @@ uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR)
|
||||
|
||||
/*
|
||||
* fades color toward black
|
||||
* if using "video" method the resulting color will not become black unless it is already black or distorts the hue
|
||||
* if using "video" method the resulting color will never become black unless it is already black
|
||||
*/
|
||||
uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) {
|
||||
if (c1 == BLACK || amount == 0) return 0; // black or full fade
|
||||
if (amount == 255) return c1; // no change
|
||||
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
uint32_t rb = c1 & TWO_CHANNEL_MASK; // extract R and B channels
|
||||
uint32_t wg = (c1 >> 8) & TWO_CHANNEL_MASK; // extract W and G channels (shifted for multiplication)
|
||||
uint32_t rb_scaled;
|
||||
uint32_t wg_scaled;
|
||||
uint32_t addRemains = 0;
|
||||
|
||||
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
|
||||
if (video) {
|
||||
rb_scaled = ((rb * amount + 0x007F007F) >> 8) & TWO_CHANNEL_MASK; // scale red and blue, add 0.5 for rounding
|
||||
wg_scaled = (wg * amount + 0x007F007F) & ~TWO_CHANNEL_MASK; // scale white and green, add 0.5 for rounding
|
||||
uint8_t r = byte(rb>>16), g = byte(wg), b = byte(rb), w = byte(wg>>16); // extract r, g, b, w channels from original color (wg is shifted)
|
||||
if (!video) amount++; // add one for correct scaling using bitshifts
|
||||
else {
|
||||
// video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue
|
||||
uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels
|
||||
uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation
|
||||
maxc = (maxc>>2) + 1; // divide by 4 to get ~25% threshold for hue preservation, add 1 to prevent "washout" of very dark colors (prevents them becoming gray)
|
||||
rb_scaled |= r > maxc ? 0x00010000 : 0;
|
||||
wg_scaled |= g > maxc ? 0x00000100 : 0;
|
||||
rb_scaled |= b > maxc ? 0x00000001 : 0;
|
||||
wg_scaled |= w ? 0x01000000 : 0; // preserve white if it is present
|
||||
} else {
|
||||
rb_scaled = ((rb * (amount + 1)) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
||||
wg_scaled = ((wg * (amount + 1)) & ~TWO_CHANNEL_MASK); // scale white and green
|
||||
addRemains = r && (r<<5) > maxc ? 0x00010000 : 0; // note: setting color preservation threshold too high results in flickering and
|
||||
addRemains |= g && (g<<5) > maxc ? 0x00000100 : 0; // jumping colors in low brightness gradients. Multiplying the color preserves
|
||||
addRemains |= b && (b<<5) > maxc ? 0x00000001 : 0; // better accuracy than dividing the maxc. Shifting by 5 is a good compromise
|
||||
addRemains |= w ? 0x01000000 : 0; // i.e. remove color channel if <13% of max
|
||||
}
|
||||
|
||||
return (rb_scaled | wg_scaled);
|
||||
const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green
|
||||
return (rb | wg) + addRemains;
|
||||
}
|
||||
|
||||
/*
|
||||
* color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate!
|
||||
* shifts hue, increase brightness, decreases saturation (if not black)
|
||||
* note: inputs are 32bit to speed up the function, useful input value ranges are -255 to +255
|
||||
* note2: if only one hue change is needed, use CRGBW.adjust_hue() instead (much faster)
|
||||
shifts hue, increase brightness, decreases saturation (if not black)
|
||||
note: inputs are 32bit to speed up the function, useful input value ranges are 0-255
|
||||
*/
|
||||
WLED_O3_ATTR void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange, int32_t valueChange) {
|
||||
if(rgb.color32 == 0 && valueChange <= 0) return; // black and no value change -> return black
|
||||
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) {
|
||||
if (rgb == 0 || hueShift + lighten + brighten == 0) return rgb; // black or no change
|
||||
CHSV32 hsv;
|
||||
rgb2hsv(rgb, hsv); //convert to HSV
|
||||
hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
|
||||
hsv.s = (int)hsv.s + satChange < 0 ? 0 : ((int)hsv.s + satChange > 255 ? 255 : (int)hsv.s + satChange);
|
||||
hsv.v = (int)hsv.v + valueChange < 0 ? 0 : ((int)hsv.v + valueChange > 255 ? 255 : (int)hsv.v + valueChange);
|
||||
hsv2rgb_spectrum(hsv, rgb); // convert back to RGB
|
||||
hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate
|
||||
hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness
|
||||
uint32_t rgb_adjusted;
|
||||
hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion
|
||||
return rgb_adjusted;
|
||||
}
|
||||
|
||||
// derived from FastLED: replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
|
||||
uint32_t ColorFromPalette(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) {
|
||||
// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes)
|
||||
uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) {
|
||||
if (blendType == LINEARBLEND_NOWRAP) {
|
||||
index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping
|
||||
}
|
||||
@@ -309,103 +295,64 @@ void loadCustomPalettes() {
|
||||
}
|
||||
}
|
||||
|
||||
// convert HSV (16bit hue) to RGB (32bit with white = 0), optimized for speed
|
||||
WLED_O2_ATTR void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb) {
|
||||
unsigned p, q, t;
|
||||
unsigned region = ((unsigned)hsv.h * 6) >> 16; // h / (65536 / 6)
|
||||
unsigned remainder = (hsv.h - (region * 10923)) * 6; // 10923 = (65536 / 6)
|
||||
|
||||
// check for zero saturation
|
||||
if (hsv.s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = hsv.v;
|
||||
return;
|
||||
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0)
|
||||
{
|
||||
unsigned int remainder, region, p, q, t;
|
||||
unsigned int h = hsv.h;
|
||||
unsigned int s = hsv.s;
|
||||
unsigned int v = hsv.v;
|
||||
if (s == 0) {
|
||||
rgb = v << 16 | v << 8 | v;
|
||||
return;
|
||||
}
|
||||
|
||||
p = (hsv.v * (255 - hsv.s)) >> 8;
|
||||
q = (hsv.v * (255 - ((hsv.s * remainder) >> 16))) >> 8;
|
||||
t = (hsv.v * (255 - ((hsv.s * (65535 - remainder)) >> 16))) >> 8;
|
||||
region = h / 10923; // 65536 / 6 = 10923
|
||||
remainder = (h - (region * 10923)) * 6;
|
||||
p = (v * (255 - s)) >> 8;
|
||||
q = (v * (255 - ((s * remainder) >> 16))) >> 8;
|
||||
t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8;
|
||||
switch (region) {
|
||||
case 0:
|
||||
rgb.r = hsv.v;
|
||||
rgb.g = t;
|
||||
rgb.b = p;
|
||||
break;
|
||||
rgb = v << 16 | t << 8 | p; break;
|
||||
case 1:
|
||||
rgb.r = q;
|
||||
rgb.g = hsv.v;
|
||||
rgb.b = p;
|
||||
break;
|
||||
rgb = q << 16 | v << 8 | p; break;
|
||||
case 2:
|
||||
rgb.r = p;
|
||||
rgb.g = hsv.v;
|
||||
rgb.b = t;
|
||||
break;
|
||||
rgb = p << 16 | v << 8 | t; break;
|
||||
case 3:
|
||||
rgb.r = p;
|
||||
rgb.g = q;
|
||||
rgb.b = hsv.v;
|
||||
break;
|
||||
rgb = p << 16 | q << 8 | v; break;
|
||||
case 4:
|
||||
rgb.r = t;
|
||||
rgb.g = p;
|
||||
rgb.b = hsv.v;
|
||||
break;
|
||||
rgb = t << 16 | p << 8 | v; break;
|
||||
default:
|
||||
rgb.r = hsv.v;
|
||||
rgb.g = p;
|
||||
rgb.b = q;
|
||||
break;
|
||||
rgb = v << 16 | p << 8 | q; break;
|
||||
}
|
||||
}
|
||||
|
||||
// CHSV to CRGB wrapper conversion: slower so this should not be used in time critical code, use rainbow version instead
|
||||
void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb) {
|
||||
CHSV32 hsv32(hsv);
|
||||
CRGBW rgb32;
|
||||
hsv2rgb_spectrum(hsv32, rgb32);
|
||||
rgb = CRGB(rgb32);
|
||||
}
|
||||
|
||||
// convert RGB to HSV (16bit hue), not 100% color accurate. note: using "O3" makes it ~5% faster at minimal flash cost (~20 bytes)
|
||||
WLED_O3_ATTR void rgb2hsv(const CRGBW& rgb, CHSV32& hsv) {
|
||||
int32_t r = rgb.r; // note: using 32bit variables tested faster than 8bit
|
||||
int32_t g = rgb.g;
|
||||
int32_t b = rgb.b;
|
||||
uint32_t minval, maxval;
|
||||
int32_t delta;
|
||||
// find min/max value. note: faster than using min/max functions (lets compiler optimize more when using "O3"), other variants (nested ifs, xor) tested slower
|
||||
maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
|
||||
if (maxval == 0) {
|
||||
hsv.hsv32 = 0;
|
||||
return; // black, avoids division by zero
|
||||
}
|
||||
minval = (r < g) ? ((r < b) ? r : b) : ((g < b) ? g : b);
|
||||
hsv.v = maxval;
|
||||
delta = maxval - minval;
|
||||
if (delta != 0) {
|
||||
void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version
|
||||
{
|
||||
hsv.raw = 0;
|
||||
int32_t r = (rgb>>16)&0xFF;
|
||||
int32_t g = (rgb>>8)&0xFF;
|
||||
int32_t b = rgb&0xFF;
|
||||
int32_t minval, maxval, delta;
|
||||
minval = min(r, g);
|
||||
minval = min(minval, b);
|
||||
maxval = max(r, g);
|
||||
maxval = max(maxval, b);
|
||||
if (maxval == 0) return; // black
|
||||
hsv.v = maxval;
|
||||
delta = maxval - minval;
|
||||
hsv.s = (255 * delta) / maxval;
|
||||
// note: early return if s==0 is omitted here to increase speed as gray values are rarely used
|
||||
if (maxval == r) hsv.h = (uint16_t)((10923 * (g - b)) / delta);
|
||||
else if (maxval == g) hsv.h = (uint16_t)(21845 + (10923 * (b - r)) / delta);
|
||||
else hsv.h = (uint16_t)(43690 + (10923 * (r - g)) / delta);
|
||||
} else {
|
||||
hsv.s = 0;
|
||||
hsv.h = 0; // gray, hue is undefined but set to 0 for consistency
|
||||
}
|
||||
}
|
||||
|
||||
CHSV rgb2hsv(const CRGB c) { // CRGB to CHSV
|
||||
CHSV32 hsv;
|
||||
rgb2hsv(CRGBW(c), hsv);
|
||||
return CHSV(hsv);
|
||||
if (hsv.s == 0) return; // gray value
|
||||
if (maxval == r) hsv.h = (10923 * (g - b)) / delta;
|
||||
else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta;
|
||||
else hsv.h = 43690 + (10923 * (r - g)) / delta;
|
||||
}
|
||||
|
||||
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb
|
||||
CRGBW crgb;
|
||||
hsv2rgb_spectrum(CHSV32(hue, sat, 255), crgb);
|
||||
rgb[0] = crgb.r;
|
||||
rgb[1] = crgb.g;
|
||||
rgb[2] = crgb.b;
|
||||
uint32_t crgb;
|
||||
hsv2rgb(CHSV32(hue, sat, 255), crgb);
|
||||
rgb[0] = byte((crgb) >> 16);
|
||||
rgb[1] = byte((crgb) >> 8);
|
||||
rgb[2] = byte(crgb);
|
||||
}
|
||||
|
||||
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
|
||||
|
||||
231
wled00/colors.h
231
wled00/colors.h
@@ -1,34 +1,96 @@
|
||||
#pragma once
|
||||
#ifndef WLED_COLORS_H
|
||||
#define WLED_COLORS_H
|
||||
#include <vector>
|
||||
#include "src/dependencies/fastled_slim/fastled_slim.h"
|
||||
|
||||
/*
|
||||
* Color structs and color utility functions
|
||||
*/
|
||||
/*
|
||||
Note on color types and conversions:
|
||||
- WLED uses 32bit colors (RGBW), if possible, use CRGBW instead of CRGB for better performance (no conversion in setPixelColor)
|
||||
- use CRGB if RAM usage is of concern (i.e. for larger color arrays)
|
||||
- direct conversion (assignment or construction) from CHSV/CHSV32 to CRGB/CRGBW use the "rainbow" method (nicer colors, see fastled documentation)
|
||||
- converting CRGB(W) to HSV32 color is quite accurate but still not 100% (but much more accurate than fastled's "hsv2rgb_approximate" function)
|
||||
- when converting CRGB(W) to HSV32 and back, "hsv2rgb_spectrum" preserves the colors better than the _rainbow version
|
||||
- to manipulate an RGB color in HSV space, use the adjust_color function or the CRGBW.adjust_hue method
|
||||
#include <vector>
|
||||
#include "FastLED.h"
|
||||
|
||||
Some functions in this file are derived from FastLED (https://github.com/FastLED/FastLED) licensed under the MIT license.
|
||||
See /src/dependencies/fastled_slim/LICENSE.txt for details.
|
||||
*/
|
||||
#define ColorFromPalette ColorFromPaletteWLED // override fastled version
|
||||
|
||||
// 32bit color mangling macros
|
||||
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
|
||||
#define R(c) (byte((c) >> 16))
|
||||
#define G(c) (byte((c) >> 8))
|
||||
#define B(c) (byte(c))
|
||||
#define W(c) (byte((c) >> 24))
|
||||
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
||||
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
||||
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
||||
struct CRGBW {
|
||||
union {
|
||||
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
||||
struct {
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
uint8_t w;
|
||||
};
|
||||
uint8_t raw[4]; // Access as an array in the order B, G, R, W
|
||||
};
|
||||
|
||||
struct CRGBW; // forward declations
|
||||
struct CHSV32;
|
||||
// Default constructor
|
||||
inline CRGBW() __attribute__((always_inline)) = default;
|
||||
|
||||
// Constructor from a 32-bit color (0xWWRRGGBB)
|
||||
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
||||
|
||||
// Constructor with r, g, b, w values
|
||||
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
||||
|
||||
// Constructor from CRGB
|
||||
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
||||
|
||||
// Access as an array
|
||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// Assignment from 32-bit color
|
||||
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
||||
|
||||
// Assignment from r, g, b, w
|
||||
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
||||
|
||||
// Conversion operator to uint32_t
|
||||
inline operator uint32_t() const __attribute__((always_inline)) {
|
||||
return color32;
|
||||
}
|
||||
/*
|
||||
// Conversion operator to CRGB
|
||||
inline operator CRGB() const __attribute__((always_inline)) {
|
||||
return CRGB(r, g, b);
|
||||
}
|
||||
|
||||
CRGBW& scale32 (uint8_t scaledown) // 32bit math
|
||||
{
|
||||
if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit
|
||||
uint32_t scale = scaledown + 1;
|
||||
uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue
|
||||
uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green
|
||||
color32 = rb | wg;
|
||||
return *this;
|
||||
}*/
|
||||
|
||||
};
|
||||
|
||||
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
||||
union {
|
||||
struct {
|
||||
uint16_t h; // hue
|
||||
uint8_t s; // saturation
|
||||
uint8_t v; // value
|
||||
};
|
||||
uint32_t raw; // 32bit access
|
||||
};
|
||||
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
||||
|
||||
/// Allow construction from hue, saturation, and value
|
||||
/// @param ih input hue
|
||||
/// @param is input saturation
|
||||
/// @param iv input value
|
||||
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
||||
: h(ih), s(is), v(iv) {}
|
||||
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
||||
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
||||
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
||||
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
||||
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
||||
};
|
||||
extern bool gammaCorrectCol;
|
||||
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
|
||||
class NeoGammaWLEDMethod {
|
||||
@@ -56,20 +118,18 @@ class NeoGammaWLEDMethod {
|
||||
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
|
||||
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
|
||||
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
|
||||
void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueChange);
|
||||
|
||||
[[gnu::hot, gnu::pure]] uint32_t ColorFromPalette(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
|
||||
[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten);
|
||||
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
|
||||
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
|
||||
CRGBPalette16 generateRandomPalette();
|
||||
void loadCustomPalettes();
|
||||
extern std::vector<CRGBPalette16> customPalettes;
|
||||
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
|
||||
|
||||
void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb);
|
||||
void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb);
|
||||
void rgb2hsv(const CRGBW& rgb, CHSV32& hsv);
|
||||
CHSV rgb2hsv(const CRGB c);
|
||||
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
|
||||
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
|
||||
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
|
||||
void rgb2hsv(const uint32_t rgb, CHSV32& hsv);
|
||||
inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv
|
||||
void colorKtoRGB(uint16_t kelvin, byte* rgb);
|
||||
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
|
||||
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
|
||||
@@ -89,118 +149,7 @@ static inline uint32_t fast_color_scale(const uint32_t c, const uint8_t scale) {
|
||||
}
|
||||
|
||||
// palettes
|
||||
extern const TProgmemRGBPalette16 PartyColors_gc22 PROGMEM;
|
||||
extern const TProgmemRGBPalette16* const fastledPalettes[];
|
||||
extern const uint8_t* const gGradientPalettes[];
|
||||
#endif
|
||||
|
||||
|
||||
struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions
|
||||
union {
|
||||
struct {
|
||||
uint16_t h; // hue
|
||||
uint8_t s; // saturation
|
||||
uint8_t v; // value
|
||||
};
|
||||
uint32_t hsv32; // 32bit access
|
||||
};
|
||||
inline CHSV32() __attribute__((always_inline)) = default; // default constructor
|
||||
|
||||
// allow construction from hue (ih), saturation (is), and value (iv)
|
||||
inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v
|
||||
: h(ih), s(is), v(iv) {}
|
||||
|
||||
inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v
|
||||
: h((uint16_t)ih << 8), s(is), v(iv) {}
|
||||
|
||||
inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV
|
||||
: h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {}
|
||||
|
||||
inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV
|
||||
|
||||
// construction from a 32bit rgb color (white channel is ignored)
|
||||
inline CHSV32(const CRGBW& rgb) __attribute__((always_inline));
|
||||
|
||||
inline CHSV32& operator= (const CRGBW& rgb) __attribute__((always_inline)); // assignment from 32bit rgb color (white channel is ignored)
|
||||
};
|
||||
|
||||
// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color
|
||||
// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts
|
||||
// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB
|
||||
struct CRGBW {
|
||||
union {
|
||||
uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB)
|
||||
struct {
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
uint8_t w;
|
||||
};
|
||||
uint8_t raw[4]; // Access as an array in the order B, G, R, W (matches 32 bit colors)
|
||||
};
|
||||
|
||||
// Default constructor
|
||||
inline CRGBW() __attribute__((always_inline)) = default;
|
||||
|
||||
// Constructor from a 32-bit color (0xWWRRGGBB)
|
||||
constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {}
|
||||
|
||||
// Constructor with r, g, b, w values
|
||||
constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {}
|
||||
|
||||
// Constructor from CRGB
|
||||
constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {}
|
||||
|
||||
// Constructor from CHSV32
|
||||
inline CRGBW(CHSV32 hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h, hsv.s, hsv.v, raw, true); }
|
||||
|
||||
// Constructor from CHSV
|
||||
inline CRGBW(CHSV hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h<<8, hsv.s, hsv.v, raw, true); }
|
||||
|
||||
// Access as an array
|
||||
inline const uint8_t& operator[](uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// Assignment from 32-bit color
|
||||
inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; }
|
||||
|
||||
// Assignment from CHSV32
|
||||
inline CRGBW& operator=(CHSV32 hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h, hsv.s, hsv.v, raw, true); return *this; }
|
||||
|
||||
// Assignment from CHSV
|
||||
inline CRGBW& operator=(CHSV hsv) __attribute__((always_inline)) { hsv2rgb_rainbow(hsv.h<<8, hsv.s, hsv.v, raw, true); return *this; }
|
||||
|
||||
// Assignment from r, g, b, w
|
||||
inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; }
|
||||
|
||||
// Conversion operator to uint32_t
|
||||
inline operator uint32_t() const __attribute__((always_inline)) {
|
||||
return color32;
|
||||
}
|
||||
|
||||
// adjust hue: input range is 256 for full color cycle, input can be negative
|
||||
inline void adjust_hue(int hueshift) __attribute__((always_inline)) {
|
||||
CHSV32 hsv = *this;
|
||||
hsv.h += hueshift << 8;
|
||||
hsv2rgb_spectrum(hsv, *this);
|
||||
}
|
||||
|
||||
// get the average of the R, G, B and W values
|
||||
uint8_t getAverageLight() const {
|
||||
return (r + g + b + w) >> 2;
|
||||
}
|
||||
};
|
||||
|
||||
inline CHSV32::CHSV32(const CRGBW& rgb) {
|
||||
rgb2hsv(rgb, *this);
|
||||
}
|
||||
|
||||
inline CHSV32& CHSV32::operator= (const CRGBW& rgb) { // assignment from 32bit rgb color (white channel is ignored)
|
||||
rgb2hsv(rgb, *this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// explicit hsv2rgb conversions for compatibility
|
||||
inline CRGBW hsv2rgb(const CHSV32& hsv) { return CRGBW(hsv); }
|
||||
inline void hsv2rgb(const CHSV32& hsv, CRGBW& rgb) { rgb = CRGBW(hsv); }
|
||||
inline void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) { rgb = CRGBW(hsv).color32; }
|
||||
|
||||
#endif
|
||||
@@ -59,7 +59,6 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
||||
#define WLED_MAX_RMT_CHANNELS 0 // ESP8266 does not have RMT nor I2S
|
||||
#define WLED_MAX_I2S_CHANNELS 0
|
||||
#define WLED_MAX_ANALOG_CHANNELS 5
|
||||
#define WLED_MAX_TIMERS 16 // reduced limit for ESP8266 due to memory constraints
|
||||
#define WLED_PLATFORM_ID 0 // used in UI to distinguish ESP types, needs a proper fix!
|
||||
#else
|
||||
#if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX)
|
||||
@@ -87,7 +86,6 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 16
|
||||
#define WLED_PLATFORM_ID 4 // used in UI to distinguish ESP type in UI, needs a proper fix!
|
||||
#endif
|
||||
#define WLED_MAX_TIMERS 64 // maximum number of timers
|
||||
#define WLED_MAX_DIGITAL_CHANNELS (WLED_MAX_RMT_CHANNELS + WLED_MAX_I2S_CHANNELS)
|
||||
#endif
|
||||
// WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed
|
||||
@@ -738,6 +736,5 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#endif
|
||||
|
||||
#define WLED_O2_ATTR __attribute__((optimize("O2")))
|
||||
#define WLED_O3_ATTR __attribute__((optimize("O3")))
|
||||
|
||||
#endif
|
||||
|
||||
@@ -90,7 +90,7 @@ function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefine
|
||||
scE.setAttribute("type", "text/javascript");
|
||||
scE.setAttribute("async", async);
|
||||
d.body.appendChild(scE);
|
||||
// success event
|
||||
// success event
|
||||
scE.addEventListener("load", () => {
|
||||
//console.log("File loaded");
|
||||
if (preGetV) preGetV();
|
||||
@@ -126,10 +126,6 @@ function getLoc() {
|
||||
}
|
||||
}
|
||||
function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; }
|
||||
// HTML entity escaper – use on any remote/user-supplied text inserted into innerHTML
|
||||
function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||
// URL sanitizer – blocks javascript: and data: URIs, use for externally supplied URLs for some basic safety
|
||||
function safeUrl(u) { return /^https?:\/\//.test(u) ? u : '#'; }
|
||||
function B() { window.open(getURL("/settings"),"_self"); }
|
||||
var timeout;
|
||||
function showToast(text, error = false) {
|
||||
@@ -201,7 +197,7 @@ function sendDDP(ws, start, len, colors) {
|
||||
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
|
||||
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
|
||||
pkt[2] = 0x00; // reserved
|
||||
pkt[3] = 0x0B; // RGB, 8bit per channel
|
||||
pkt[3] = 0x01; // 1 = RGB (currently only supported mode)
|
||||
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
|
||||
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
|
||||
pkt[6] = (off >> 16) & 255;
|
||||
|
||||
@@ -71,8 +71,6 @@
|
||||
let copyColor = '#000';
|
||||
let ws = null;
|
||||
let maxCol; // max colors to send out in one chunk, ESP8266 is limited to ~50 (500 bytes), ESP32 can do ~128 (1340 bytes)
|
||||
let _applySeq = 0; // incremented each time applyLED fires; used to cancel stale in-flight previews
|
||||
let _httpQueue = [], _httpRun = 0;
|
||||
|
||||
// load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded
|
||||
(function loadFiles() {
|
||||
@@ -623,16 +621,21 @@
|
||||
|
||||
async function requestJson(cmd)
|
||||
{
|
||||
if (ws && ws.readyState == 1 && ws.bufferedAmount < 32768) {
|
||||
if (ws && ws.readyState == 1) {
|
||||
try {
|
||||
ws.send(JSON.stringify(cmd));
|
||||
await new Promise(r => setTimeout(r, 15)); // short delay to give ESP time to process (fewer packets dropped)
|
||||
return 1;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// HTTP fallback
|
||||
if (_httpQueue.length >= 5) return -1; // queue full; applyLED cancels stale queues before sending
|
||||
if (!window._httpQueue) {
|
||||
window._httpQueue = [];
|
||||
window._httpRun = 0;
|
||||
}
|
||||
if (_httpQueue.length >= 5) {
|
||||
return Promise.resolve(-1); // reject if too many queued requests
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
_httpQueue.push({ cmd, resolve });
|
||||
(async function run() {
|
||||
@@ -647,7 +650,7 @@
|
||||
cache: 'no-store'
|
||||
});
|
||||
} catch (e) {}
|
||||
await new Promise(r => setTimeout(r, 120)); // delay between requests (go slow, this is the http fallback if WS fails)
|
||||
await new Promise(r => setTimeout(r, 120));
|
||||
q.resolve(0);
|
||||
}
|
||||
_httpRun = 0;
|
||||
@@ -659,12 +662,8 @@
|
||||
async function applyLED()
|
||||
{
|
||||
if (!palCache.length) return;
|
||||
const seq = ++_applySeq;
|
||||
// discard pending HTTP chunks from any previous preview so stale data doesn't drain slowly
|
||||
while (_httpQueue.length) _httpQueue.shift().resolve(-1); // resolve dropped entries so their awaiters can observe the seq change and exit
|
||||
try {
|
||||
let st = await (await fetch(getURL('/json/state'), { cache: 'no-store' })).json();
|
||||
if (seq !== _applySeq) return; // superseded by a newer preview request
|
||||
if (!st.seg || !st.seg.length) return;
|
||||
|
||||
// get selected segments, use main segment if none selected
|
||||
@@ -681,7 +680,6 @@
|
||||
arr.push(palCache[len > 1 ? Math.round(i * 255 / (len - 1)) : 0]);
|
||||
// send colors in chunks
|
||||
for (let j = 0; j < arr.length; j += maxCol) {
|
||||
if (seq !== _applySeq) return; // superseded mid-send
|
||||
let chunk = [s.start + j, ...arr.slice(j, j + maxCol)];
|
||||
await requestJson({ seg: { id: s.id, i: chunk } });
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ p {
|
||||
font-size: 16px;
|
||||
}
|
||||
.fs1 {
|
||||
font-size: 32px;
|
||||
font-size: 48px;
|
||||
}
|
||||
.fs2 {
|
||||
font-size: 28px;
|
||||
|
||||
@@ -9,24 +9,10 @@
|
||||
<link rel="stylesheet" href="style.css"></head>
|
||||
<body>
|
||||
<div class="bgc1 clearfix">
|
||||
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs: 26)</small></h1>
|
||||
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs: 25)</small></h1>
|
||||
</div>
|
||||
<div class="clearfix mhl ptl">
|
||||
<h1 class="mvm mtn fgc1">Grid Size: 16</h1>
|
||||
<div class="glyph fs1">
|
||||
<div class="clearfix bshadow0 pbs">
|
||||
<span class="i-search"></span>
|
||||
<span class="mls"> i-search</span>
|
||||
</div>
|
||||
<fieldset class="fs0 size1of1 clearfix hidden-false">
|
||||
<input type="text" readonly value="e0a1" class="unit size1of2" />
|
||||
<input type="text" maxlength="1" readonly value="" class="unitRight size1of2 talign-right" />
|
||||
</fieldset>
|
||||
<div class="fs0 bshadow0 clearfix hidden-true">
|
||||
<span class="unit pvs fgc1">liga: </span>
|
||||
<input type="text" readonly value="search, magnifier" class="liga unitRight" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="glyph fs1">
|
||||
<div class="clearfix bshadow0 pbs">
|
||||
<span class="i-pixelforge"></span>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<glyph unicode="" glyph-name="presets" d="M704 704c131.413 0 234.667-103.253 234.667-234.667 0-161.28-145.067-292.693-364.8-491.946l-61.867-56.32-61.867 55.893c-219.733 199.68-364.8 331.093-364.8 492.373 0 131.414 103.254 234.667 234.667 234.667 74.24 0 145.493-34.56 192-89.173 46.507 54.613 117.76 89.173 192 89.173zM516.267 40.533c203.093 183.894 337.066 305.494 337.066 428.8 0 85.334-64 149.334-149.333 149.334-65.707 0-129.707-42.24-151.893-100.694h-79.787c-22.613 58.454-86.613 100.694-152.32 100.694-85.333 0-149.333-64-149.333-149.334 0-123.306 133.973-244.906 337.066-428.8l4.267-4.266z" />
|
||||
<glyph unicode="" glyph-name="info" d="M512 746.667c235.52 0 426.667-191.147 426.667-426.667s-191.147-426.667-426.667-426.667-426.667 191.147-426.667 426.667 191.147 426.667 426.667 426.667zM554.667 106.667v256h-85.334v-256h85.334zM554.667 448v85.333h-85.334v-85.333h85.334z" />
|
||||
<glyph unicode="" glyph-name="power" d="M554.667 704v-426.667h-85.334v426.667h85.334zM760.747 611.413c82.773-70.4 135.253-174.506 135.253-291.413 0-212.053-171.947-384-384-384s-384 171.947-384 384c0 116.907 52.48 221.013 135.253 291.413l60.16-60.16c-66.986-54.613-110.080-137.813-110.080-231.253 0-165.12 133.547-298.667 298.667-298.667s298.667 133.547 298.667 298.667c0 93.44-43.094 176.64-110.507 230.827z" />
|
||||
<glyph unicode="" glyph-name="search" d="M902.213 27.99l-197.073 167.615c-20.373 18.336-42.161 26.752-59.761 25.941 46.52 54.492 74.622 125.188 74.622 202.455 0 172.313-139.687 312-312 312-172.311 0-312-139.687-312-312s139.687-312 312-312c77.266 0 147.962 28.1 202.455 74.624-0.812-17.6 7.605-39.388 25.941-59.761l167.615-197.073c28.698-31.887 75.58-34.574 104.178-5.976s25.913 75.48-5.974 104.178zM408 216c-114.874 0-208 93.125-208 208s93.125 208 208 208 208-93.125 208-208-93.125-208-208-208z" />
|
||||
<glyph unicode="" glyph-name="settings" d="M816.64 280.064l85.504-67.584c8.192-6.144 10.24-16.896 5.12-26.112l-81.92-141.824c-5.12-9.216-15.872-12.8-25.088-9.216l-101.888 40.96c-20.992-15.872-44.032-29.696-69.12-39.936l-15.36-108.544c-1.024-10.24-9.728-17.408-19.968-17.408h-163.84c-10.24 0-18.432 7.168-20.48 17.408l-15.36 108.544c-25.088 10.24-47.616 23.552-69.12 39.936l-101.888-40.96c-9.216-3.072-19.968 0-25.088 9.216l-81.92 141.824c-4.608 8.704-2.56 19.968 5.12 26.112l86.528 67.584c-2.048 12.8-3.072 26.624-3.072 39.936s1.536 27.136 3.584 39.936l-86.528 67.584c-8.192 6.144-10.24 16.896-5.12 26.112l81.92 141.824c5.12 9.216 15.872 12.8 25.088 9.216l101.888-40.96c20.992 15.872 44.032 29.696 69.12 39.936l15.36 108.544c1.536 10.24 9.728 17.408 19.968 17.408h163.84c10.24 0 18.944-7.168 20.48-17.408l15.36-108.544c25.088-10.24 47.616-23.552 69.12-39.936l101.888 40.96c9.216 3.072 19.968 0 25.088-9.216l81.92-141.824c4.608-8.704 2.56-19.968-5.12-26.112l-86.528-67.584c2.048-12.8 3.072-26.112 3.072-39.936s-1.024-27.136-2.56-39.936zM512 166.4c84.48 0 153.6 69.12 153.6 153.6s-69.12 153.6-153.6 153.6-153.6-69.12-153.6-153.6 69.12-153.6 153.6-153.6z" />
|
||||
<glyph unicode="" glyph-name="eye" d="M512 640c213.333 0 395.52-132.693 469.333-320-73.813-187.307-256-320-469.333-320s-395.52 132.693-469.333 320c73.813 187.307 256 320 469.333 320zM512 106.667c117.76 0 213.333 95.573 213.333 213.333s-95.573 213.333-213.333 213.333-213.333-95.573-213.333-213.333 95.573-213.333 213.333-213.333zM512 448c70.827 0 128-57.173 128-128s-57.173-128-128-128-128 57.173-128 128 57.173 128 128 128z" />
|
||||
<glyph unicode="" glyph-name="sync" d="M512 661.333c188.587 0 341.333-152.746 341.333-341.333 0-66.987-19.626-129.28-52.906-181.76l-62.294 62.293c19.2 35.414 29.867 76.374 29.867 119.467 0 141.227-114.773 256-256 256v-128l-170.667 170.667 170.667 170.666v-128zM512 64v128l170.667-170.667-170.667-170.666v128c-188.587 0-341.333 152.746-341.333 341.333 0 66.987 19.626 129.28 52.906 181.76l62.294-62.293c-19.2-35.414-29.867-76.374-29.867-119.467 0-141.227 114.773-256 256-256z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: 'wled122';
|
||||
src:
|
||||
url('fonts/wled122.ttf?2tjc6') format('truetype'),
|
||||
url('fonts/wled122.woff?2tjc6') format('woff'),
|
||||
url('fonts/wled122.svg?2tjc6#wled122') format('svg');
|
||||
url('fonts/wled122.ttf?yzxblb') format('truetype'),
|
||||
url('fonts/wled122.woff?yzxblb') format('woff'),
|
||||
url('fonts/wled122.svg?yzxblb#wled122') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -24,9 +24,6 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.i-search:before {
|
||||
content: "\e0a1";
|
||||
}
|
||||
.i-pixelforge:before {
|
||||
content: "\e900";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "WIcons";
|
||||
src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAtwAA0AAAAAFzwAAAsYAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACEHhEICp8QmBQLQAABNgIkA3wEIAWDGweCQBueEhEVqmUn+JmQuYW8aYRI8pyRytrpySK/jJBk9v+nbf19MKDMoI0Jwi77VwXMwsTIUSbEKvy5Veevw/8NG9XGRiobEQNstPsjEh74P+bfFxu1zQc2m+Aa+1eIVHCCRR1PoA82/wK/+v91bBjrxhAh4ktd/2kus/ntTjFbOEASKufInVMvm8JMpgyzruVsD5AssAcF1PIhpKsOUGhW6IGMOOvPDZlh7zJjzjrI26wbrOpZbwBAVfDQuPpH/fDP0TubwXIgpEa+JEjFsKsLM0KLQAQE14LrALAQORH0B0lBAopZoRo0lge+QsIkt7t+gH1ODaQKTBViUo7GA2pIRP8bMf8CyVQwgRDVMu155fOe5wufb3n+5YugFxkvrC9jXua83PHy4MuLo6+NJo1WjC4aXf5K8sr3K+Q2AIDnqTcbt7CFHniq7lfKm8oR5WblLABlM+L3CD7iA8VLxT0AxURRLf9RPiq/DiA/kPvLpQUwrWTSrYAbx6aq0GK5CHxuIQmcHz2lvxChFCIiUFFHKRJF8CoU/zMIYJApEPQffEglRnqtOgL79YqvGCYRhokEXIhkfaAnREw0kRC6CCNxkuxFQzw93OKS1xQKDBMlauQ1UrlcGhaKYZjEUyJRSqViQtRxRbSQ1FPtK/eKlmJsMY5hKgXG5TMXt6G2rDgyAMcjgoMR7poV+WDcbVxqvTsDvAORS+QQM2wZw3diBoGL38dToxuyzsk4nYkHkCVu770sO2Fz5JxyEU0u4z1XZVFkceJWXocPaTOA+92Io/YOLXvTRjscz0HAUTw/CLcyTucgZGFdrosTzUJc0IhwmBFxIUHvjr52qVjv4YUrN7ri+rW113ngEvY7yq4QtoprrqSDyOKsnsOs2IGfciFL4gFn+VXcWnqZj9/HvBpXLrdaRu++oO3foumwpo7cjd2ALMq997KOEraU4Tsx64GTD9xOPzSHXr48kFq2LIhZdmycVzhuXaHidowEmWhkxcpgdsUaZFkbvGorAleulNEr7ZFQBGFbruK2D8tMtL58WSC1fDVwqwLbbK4CKQbOkQsT5J+h+bzToxWcZVyF5zrfbEuz34vbiCwRQ3czj+DW5GO3deuAUwzeyTjcaHMA929BJ7K43rSVXXFcNw8cr6tbuxVZYoecmSM4MCPAAk0h62CVTLUrrnbVpsJvmDE8haaEtPppMQK24RCfXnfKVQLUAy/Ht7py69c7YsiTzrGEyTjsSIGLTv2Ugi+m0KUCTszXkmLkqMCLzykJcKEUeQkJgReLCMA5BNkNdBZQqM90YRZnfcA7YMjsRKx5A0gVV+Zqy/DDG60nkeUUYTsB3HEpdJomq5Px4nk8hLqL46/V3PI1iOqb5TsvEXNoCvA+gyOGRQRLE722LHtdTSzEuazpx3jdQKGtlOucGKNpB9r47WaOh1XHgVstq+BNHM9IQb3tlJAnne14goWMhJH+i7BmugtHUyiGXWmT0cu5OSANzUJCYD5TUGfV5qgybt8d2ptjQRNvkoyxHvBgBjy0U3Vsk5EmGwnAV20Zw46YwcwQ38CaOeKMHWq9mutb0IwI6BjVUp2ddFcP295j7qa66k0xbB3FkDRVy9Kd5touxjilliqq6Dl3au+49vURZaWX8eZYoN7q2qvjAjkH0R2y0Mz6TsrXxBkxwBed54FzzKFMHW2NTGejqZ5mWIahKJqaw6ywBkYxrJQKwkHWp1k6ahkno5fbgtmVlqA53LJAahePS607kGVlMNvvIrxs24EhPeV1lGUFElruEYTtDoBmRUWcO5BS19UMu9dsoRl6xRrnmhVw6lTC5s0/hEUnZq/a2TK1QF+YW1vucrEq1BfkaufOVdmJ0WE/JB0J6V1xUVF2k+n2FRWFXz9ge+bOjc2j6LwEOp0gidFOMOSxVcX2PPi6sclkcH9dXzWmSjVliqrMqK9neChRNQJrYn0xTR8w6D7N0KD56GfoFm40fbWQG1u26hMdqShSGHp7DWUmSb5xxSgUoyJ1n6zKNcYt/MoekjWjWD+tZ6nqYfV6Vl8wTV8c3UwUpE/k53d++613ZKQ5M//jmRTFKj9/5scGAInwr840R072vnlz53n2nIHKnvadXdtKzhD9ptX+JppBtmop95BUD6EKjbGptfPGv2rvjvpGHen9psduz22euz183owM+OfOaudtfcxiC5nCUCGOB0hodycVSuIn5TY05KovAu4+sjS4NGgW9fXXYYYJJwQkgs2Tll9cE6ib0LZa/LtO97t4ddsEHXZMLZVSXp6idgfuZSUPHjY1Da4vSQxv/PjpV9+qUvQ5BsO90RRuz6NkN0dr18R2G6C7f7fZfYqztKOYOTcE6ajlKbX2+u+T439nX3AH1FQVOyF+8gSP9Cq18eB1toItjLiuzUbxrJtjZUZFqbysZ6JhnEFd48l+0dxU09Brys839Zpqav6CjRAjgxyyyC9jhMRXGQCjvDhQmxpuD6dhgrQabZCmjysVs4fui/zjK0yVm7PSVOVwDxjCYJoEYg3r6MxMlYjyTFAMBSXrB33EwwergBt51BdGhYfRYeGGZ0SRAUoa2AWp2bKKqcbQ/EOGstEKsLxkK1SVqomTVF1RBcsSUKLSGmfHPHjwi4GpMre2FhczfAFj+CVwjoGUzo358yjz8uWVzx9XhNqhqJTRewZDjj6l2lYFHyd5T0lnZ4n614H7FFkUZgjrbXzytFgnFn/a1qarDKPCpR+aZ34oDS8K72ArdrzMNX0ZczW/HBhOrd6TyQ1/zHz2mj3OZJgXJ/SNXa6NG9mxadMvB6uqBODKrr7W2XlcDVFJRgPdjwYGhE51s4pqFIYp02xeCyJI0uChfBZZNaQuu9fc9W+uaJY22C59cruX1jNmkuyeptdP7/YRZsaCtemeaTcDyn17ZeW1qByao9275jJzl+4YHPaKyooP+lkdj1rKxVlUdJ5FFukzcvTe5vNQ8FrLIT2wg+Su9wXBHdX+1ZDSpyF/UXLmx94E9Lru5wr5wlWPTdg1F4G+i3Nn7e7ff38Nzt1+MY3bK1eBcUn9Iia2dJE/GUAyTa3gYUkJW03vYqnvva9uXWv7zbugvrmgoAYPq0j5MWA/3poasufX3zQxBH3lnzdTbbNzDclpZDVkvh758opB67FhwgnhTwydEWYPVbWpTXN4/uwRn9KB0v5hn3mzo49A+rLgx5/2qPb4LVny44+wvz+Smtq/bWtfH6Mi/OKgfvqxX9VvMo2oRiAn4J4LDeuGS0pUyv+kIz4Swy7FJWNuxXfjpooPdW3a/RvtQpHwjPaJdgVn+vw/rrjo/8rvSa6/ewdbfbJ/8yQw2NwGgrtZOCqMnqQnanm2tqWDAGsBIGWJKym6KTP0RAMwI+FgRhNHbTSJtNS/CdZpJCqQYkMOJW0qh9JiktoPV/fkRGhP1FClRpLdCCiqvQ0BwmoRD74QCVgUCVkeYax+uAhhphDHbFklLszd3/EoS8g7ASMBCyEhiyeMlQ4XCdImxMY+6Awu2TqY4S3duiRIlAhmbHd3JSReou4oNJuWqm03GfxIeUvA2xpaC6a/l8mmeN0E71BJECsNXL7sYUJsWhCy2KuYOgtzTr4TIG/XlhdY4x1mUSnTaYIqtrCiWHDPZKW9+aKyzgl/7/MoAAQUd6oEKIIgCEEYMCRCYuSBPJFkA/DIpMgLeSMf5Iv8kD8KQIFIhoJQMApBoSgMhSM5UnjIae98IFEfwn4XmrpvPULP5216LPpmNzJ0VXR2N7PD5G3zhL4UvaObbOiS7kvP5y2Ejm6OiiZiCjYBAAAA);
|
||||
src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAsAAA0AAAAAFlgAAAqqAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACEGhEICp08lnkLPgABNgIkA3gEIAWDGweCNhvxEVGUcFI3wBeFsYOmlCFXadeSCl4PGhMTwyMh0q9d2MXuDaeszCMkmT3Abd0Eu2ijAIMUa1IDbaQmRj/wndtnJB+d8BHN/+ZKv+zJJpUCCAsMA5IcArBbtlteAg6ToYi3nPp6KxH97fd9OQgssMYTSymghAPMMODmLNpvv/P8BPzeodosVKppyCRNZE0QEqlTCp0SqP9T4O4gAMzzFuTJg2RPa6/23s/f4IYKREKfr6tTc/cLu7dh2JTwmhJdUiSLQqZVQFvmy6mScazQAwlZ7apjDAOl7l8dYEyN5azo7xRYCTCz7gCAzIa7hoI38uBn9/NfQMIrA5RCyCOfOtya0oEneAKP2+M8AEzujgX5QIQYkXEhC5nk4BVC6f6L4cmN4YazURxLPmVQjD4XkFWhNcfmv38+EMNisJkOyOKfgx6n/2z9efLjZY9fPol6EvJEdaY7I5y1zu3Ok64kl58r7bcprplPfZ+GvELuPwEAiGmvZJPj8ErdT9kXF+1jV7AvsG3seNY31uuFw5m/LLgKwNzGLGd8mO+cfw6A8S5jCsM/9wfEH8iWrJEYBLUxMHfsLJpcHQqzOuDEFhQjM1otoVvVg94O/zMIoCJtI1ACwThSfr8yQL1KvQ5rAApCJOJJKBSl4cdB3IwhcY7A5i3/hNDuIJq7NmfVLJNq2Z1hACMTkEpSDwPzGMtL6Qj7EFl8BemVw4zAppSRHW5ZhSxVZIZwKIDXduoxP57T0cgYeukZbC1afoHHq6/OwUSERJEC0lcLGXjp0QKyd7tOLYzdaXLTFHYixavtddgQ0YyI6xbZbLleW+DKSDGxqrvjTWIRtNNgOF6yGYc0ZhihH0R1vR9WuWn2Q4pkWdcmW0QsbEIYzglYJKxhzbvPBSWhn9uiMsuraZ3jiQ75dBgpD4mW9tgSdSHFzLzEcnLiNDvb59n9lVxzrObWObWDviOG3Dwt5RZCKdLLyl04L0+xvKG6aEG0nJFTM6AcuXROdpzmFJCcH9+uWfmohYxDH0Nxk+nRN4ZT3uJW3O32b9GChl57lSFlYeur0F6s+ve/cC8GeUHLy5CeTZoB7XGeFaxDWspDQ9CBaXdnUZU9hGerGTqIgUtgQxhFauojOOdYXo78csyahwycYlRk/FVxQdrYrQc7r1tJQJv1+Xi5FbW+xPCwj5pLicU1YATAPRM9hVc9RfdxWc2300x9lIgM3K/9xgtYHI8miESYICECeMSQt3EtAdq7jhUlLE2CiYgNqUeZNrzc9nLTTg+EeckP9Kz28vnwTeoolOtCGyF5WOonuVZNPkHX/RKff2/l48rnCUbIfJZad59cYhSwkWPEJUQkRvZrYUMVbAKCS6jB/bp7M2ItABfEMpgBinhBFLgze5jkAlW62xjORdV67XqlRsPsObLU7cI+/4ss97HdGJ2iXMrTFMuRTzAe2SISYd9NlE6rZmS4ahqS+8GKTA7ZuWs9YGQfYGQHdUqbXcy+iQC2aiEDhkdLTkhvpoYOmp6tTc6yvgVbEIGdkoPu2sV275V27N23h7awKFxyUm5n1CGxXfscu7nrlINyF7v00vEyotuwG5If4LpYtazK+s3xmi4bpC2UoPNVnRa9JubCZj3+jg4Zl+iGnds38V2bNqxnXOKcUkYv8Vw1vppL4+lMDDMok9jqbFmxHE1LeYp/Sc6O03odj1droeRpqckiE7Qa4jB+nO5OlVyIymtCtJdACJKcTKe3Kct4DL+2knGWW/gpzKXr191XULH0Ay1NmD9ndUMvJaoqrCq5dStqFaosxPyr8/N902gfWD5BcFtmaqreo8wxq1T1+g6+d8iQDLnRJBeYZP4jf/MEBpHR0Lj1zmvSecXw/+vqjLhyTs+enLSoujoiRy3LDbIhvmtxCTAzTZPZBNzr683+Pi7U/TOZjE+Z8yHfzlQzMbsdS4t1ulIwTTJN6/hj5nBM5GevHDFhfTVob+tnthVHUVyu6o1q8GeQCn5TYowqQ4a0asLK33fsSX2zLCVo473WZ4XPWu/gTUr4n1nSfH38mHmqzKpYCucxNo9yXJO1toU1NYX8GuAm7EXRRVH9ja9f0zCPBxUQoNvXeb64MoLftWmu23d39+9eBU1d+UObPKOkpETCw4F7hvbO3brNG0u0Qnrt6B9fveVI0AIMu+aSkOtc3VrSJG5IwMsAv2Rwvfs6ObS5xyXIGfFGlW5cxjv/b4+s7/gTclsCLce7ZvXo6i3rJxi2P9ln4irW+XW89OtSmD3FBmYRo9jaDUvEEip98Bf1mytr7BaFwmJXXVf/AsfRQx8c8MBdywDCjkgAM7s2GDeXXEdyeRSPy4viEmSqzesYgTclp1nKvv50S/kNN+Me01EF0wbWprFZyoBXWACDKu3Cljz17p1WbIZ7xFwjnWai0bGQqsZQK2xf3jggsrSXIVaxQ5EaS2GoE0/jlHG6deccNaU4PqGWZWrG4+588wUzl9saGzWaiLzKjH1B/XEQ4LgwcYIvPn16iYkW1K9gpBLXayyhAJWUWWu2o4jXaVtbtfzXgfuQTk3DaPbaBw817l7OvamJX0Yz0gPOtn4jx9N79MYQbCTF84i+sxz6kXTj3MYcbvkx56XzGsMoWng/EOvWrcWLo2/Jki/by8srSCjHsse7du1fBqtFNQfTTAMOYnfw+6srmZgvttlWFUunU+SoXWtJpU4qtduaqVndnxftCHhw2c5Qs43pa9cbRfu0y1Nt5oTN6hPvfS+w6LgjvexcaoGJZO76IeYh02unz5FWVjqiKer9q78ieyU0Da60eLSoAM296/BJHbMKCIXs4Xs17vLgTs35ikkIrh9BLc4dTXAxNvU5UvV1Vb7bhkO4BhD/9lGHO+/fn4NjlwtHhQO8BSSK5a9HRtGUqfZwnbmeTb2rTbpb764lHTY8Ydt87VtQbHW9UlkZn5WaPRqobxB3qLN+/cb18J+f+dNROn5AISbO1lVAbseul2ewdd4vjwdVkzC2L02fKWdJE3fnxAH7JhVtSF4/EDxhQNoukP0c++bTOk5j6JfTPn6EbndfYOD6FcsJIgKUob1Inz6u5zRZLPsWD0IB4t4DWzCg1XLY/wIGg30NHTTauPsJKVtSOtJ9O2/rYgfF03zzHqybNYqD/yx4tforP6Ld9vAr2ybl/3yIRTcdrwzuetFFSSMAH0LMxI2+fkDdCcDYJyA3ipitETmBOLi8EZmJSOpOPFq/DTxMGhrE3JLs83kymayp5Uh8Ms2xDiHtOJqBLNjEdz8eyLwgrYDkpX6syTp5sNVEYdFEZesHeyLOS68ey57lZy682pmLOqOJ4wcS2GwSmTfZWPLgMWbCdumm7N32YP5QDH110k4bAfiCL0Df065NIHyl/q626c2Y16wHeIviHYE4G+iT5oGtK/bUXlddcGyeJwQBPKxxgIKM7PhKE0/2uuQ+juqSmmzG3PDQFXfqjwMpWpmyPLpjTQbA8zda3OddU9za9W/xDBTYht7SfiikklBMEosFGw5ceGBX1J+TRABBhBBGBFHEEIeCD40EkkghjYx77NI+y02QY4JeWJYom4tVXCrlMg1XCDMwWSeBQMpFORkyRSehUM1EmQphXMqVogyVtJNIKOEiERcruUyTmZOVJOkkzsrJEGRl5WR5AgA=);
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -25,7 +25,6 @@ var pN = "", pI = 0, pNum = 0;
|
||||
var pmt = 1, pmtLS = 0;
|
||||
var lastinfo = {};
|
||||
var isM = false, mw = 0, mh=0;
|
||||
var bsOpts = null; // blending style options snapshot, used for dynamic filtering based on matrix mode (iOS compatibility)
|
||||
var ws, wsRpt=0;
|
||||
var cfg = {
|
||||
theme:{base:"dark", bg:{url:"", rnd: false, rndGrayscale: false, rndBlur: false}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
|
||||
@@ -311,21 +310,6 @@ function onLoad()
|
||||
sl.addEventListener('touchstart', toggleBubble);
|
||||
sl.addEventListener('touchend', toggleBubble);
|
||||
});
|
||||
// limiter for all number inputs except segment inputs: limit inputs instantly note: segment inputs are special if matrix is enabled, they allow for trailing strips, need a lot of special cases to handle that
|
||||
d.addEventListener("input", function(e) {
|
||||
const t = e.target;
|
||||
if (t.tagName === "INPUT" && t.type === "number" && !(t.id && t.id.startsWith("seg"))) {
|
||||
let val = parseFloat(t.value);
|
||||
const max = parseFloat(t.max);
|
||||
const min = parseFloat(t.min);
|
||||
|
||||
if (!isNaN(val)) {
|
||||
if (val > max) t.value = max;
|
||||
if (val < min) t.value = min;
|
||||
if (t.oninput) t.oninput(); // refresh UI labels
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
function updateTablinks(tabI)
|
||||
@@ -661,14 +645,12 @@ function parseInfo(i) {
|
||||
mw = i.leds.matrix ? i.leds.matrix.w : 0;
|
||||
mh = i.leds.matrix ? i.leds.matrix.h : 0;
|
||||
isM = mw>0 && mh>0;
|
||||
if (!bsOpts) bsOpts = Array.from(gId('bs').options).map(o => o.cloneNode(true)); // snapshot all options on first call
|
||||
const bsSel = gId('bs');
|
||||
// note: style.display='none' for option elements is not supported on all browsers (notably iOS)
|
||||
bsSel.replaceChildren(...bsOpts.filter(o => isM || o.dataset.type !== "2D").map(o => o.cloneNode(true))); // allow all in matrix mode, filter 2D blends otherwise
|
||||
if (!isM) {
|
||||
gId("filter2D").classList.add('hide'); // hide 2D effects in non-matrix mode
|
||||
gId("filter2D").classList.add('hide');
|
||||
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';});
|
||||
} else {
|
||||
gId("filter2D").classList.remove('hide');
|
||||
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
|
||||
}
|
||||
gId("updBt").style.display = (i.opt & 1) ? '':'none';
|
||||
// if (i.noaudio) {
|
||||
@@ -816,7 +798,6 @@ function populateSegments(s)
|
||||
`<option value="13" ${inst.bm==13?' selected':''}>Soft Light</option>`+
|
||||
`<option value="14" ${inst.bm==14?' selected':''}>Dodge</option>`+
|
||||
`<option value="15" ${inst.bm==15?' selected':''}>Burn</option>`+
|
||||
`<option value="16" ${inst.bm==16?' selected':''}>Stencil</option>`+
|
||||
`</select></div>`+
|
||||
`</div>`;
|
||||
let sndSim = `<div data-snd="si" class="lbl-s hide">Sound sim<br>`+
|
||||
@@ -1185,7 +1166,7 @@ function updateLen(s)
|
||||
let mySD = gId("mkSYD");
|
||||
if (isM) {
|
||||
// do we have 1D segment *after* the matrix?
|
||||
if (start >= mw*mh && s > 0) {
|
||||
if (start >= mw*mh) {
|
||||
if (sY) { sY.value = 0; sY.max = 0; sY.min = 0; }
|
||||
if (eY) { eY.value = 1; eY.max = 1; eY.min = 0; }
|
||||
sX.min = mw*mh; sX.max = ledCount-1;
|
||||
@@ -1460,9 +1441,7 @@ function readState(s,command=false)
|
||||
|
||||
tr = s.transition;
|
||||
gId('tt').value = tr/10;
|
||||
const bsSel = gId('bs');
|
||||
bsSel.value = s.bs || 0; // assign blending style
|
||||
if (!bsSel.value) bsSel.value = 0; // fall back to Fade if option does not exist
|
||||
gId('bs').value = s.bs || 0;
|
||||
if (tr===0) gId('bsp').classList.add('hide')
|
||||
else gId('bsp').classList.remove('hide')
|
||||
|
||||
@@ -1976,12 +1955,12 @@ function pleDur(p,i,field)
|
||||
function pleTr(p,i,field)
|
||||
{
|
||||
const du = gId(`pl${p}du${i}`);
|
||||
const dv = parseFloat(du.value); // duaration value in seconds
|
||||
const max = parseFloat(field.max);
|
||||
let val = parseFloat(field.value);
|
||||
if (isNaN(val)) return;
|
||||
val = Math.min(val, max, dv > 0 ? dv : max); // limit to max or duration, whichever is smaller
|
||||
field.value = val;
|
||||
const dv = parseFloat(du.value);
|
||||
if (dv > 0) {
|
||||
field.max = dv;
|
||||
if (parseFloat(field.value) > dv)
|
||||
field.value = du.value;
|
||||
}
|
||||
if (field.validity.valid)
|
||||
plJson[p].transition[i] = Math.floor(field.value*10);
|
||||
}
|
||||
@@ -2137,8 +2116,8 @@ function makePlEntry(p,i)
|
||||
<td class="c">#${i+1}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="c" width="40%"><input class="segn" type="number" style="width:7ch" placeholder="Duration" max=4294967 min=0.0 step=0.1 oninput="pleDur(${p},${i},this)" value="${plJson[p].dur[i]/10.0}" id="pl${p}du${i}" ${man?"readonly":""}>s</td>
|
||||
<td class="c" width="40%"><input class="segn" type="number" style="width:4ch" placeholder="Transition" max=65.5 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" onfocus="pleTr(${p},${i},this)" value="${plJson[p].transition[i]/10.0}" id="pl${p}tr${i}">s</td>
|
||||
<td class="c" width="40%"><input class="segn" type="number" placeholder="Duration" max=6553.0 min=0.0 step=0.1 oninput="pleDur(${p},${i},this)" value="${plJson[p].dur[i]/10.0}" id="pl${p}du${i}" ${man?"readonly":""}>s</td>
|
||||
<td class="c" width="40%"><input class="segn" type="number" placeholder="Transition" max=65.0 min=0.0 step=0.1 oninput="pleTr(${p},${i},this)" onfocus="pleTr(${p},${i},this)" value="${plJson[p].transition[i]/10.0}">s</td>
|
||||
<td class="c"><button class="btn btn-pl-del" onclick="delPl(${p},${i})"><i class="icons btn-icon"></i></button></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -2342,7 +2321,7 @@ function setSi(s)
|
||||
|
||||
function setBm(s)
|
||||
{
|
||||
var value = gId(`seg${s}bm`).value;
|
||||
var value = gId(`seg${s}bm`).selectedIndex;
|
||||
var obj = {"seg": {"id": s, "bm": value}};
|
||||
requestJson(obj);
|
||||
}
|
||||
@@ -2509,10 +2488,6 @@ function saveP(i,pl)
|
||||
obj.o = true;
|
||||
} else {
|
||||
if (pl) {
|
||||
plJson[i].ps.forEach((_,idx) => {
|
||||
const trField = gId(`pl${i}tr${idx}`);
|
||||
if (trField) pleTr(i, idx, trField); // make sure transition time is not longer than duration
|
||||
});
|
||||
obj.playlist = plJson[i];
|
||||
obj.on = true;
|
||||
obj.o = true;
|
||||
@@ -3418,23 +3393,13 @@ function showVersionUpgradePrompt(info, oldVersion, newVersion) {
|
||||
|
||||
function reportUpgradeEvent(info, oldVersion, alwaysReport) {
|
||||
showToast('Reporting upgrade...');
|
||||
const IR_TYPES = {
|
||||
0: null, // not configured — omit field entirely
|
||||
1: "24-key", // white 24-key remote
|
||||
2: "24-key-ct", // white 24-key with CW, WW, CT+, CT- keys
|
||||
3: "40-key", // blue 40-key remote
|
||||
4: "44-key", // white 44-key remote
|
||||
5: "21-key", // white 21-key remote
|
||||
6: "6-key", // black 6-key learning remote
|
||||
7: "9-key", // 9-key remote
|
||||
8: "json-remote", // ir.json configurable remote
|
||||
};
|
||||
|
||||
// Reuse the info argument and fetch only /json/cfg (serialize requests to avoid 503s on low-heap devices)
|
||||
const infoData = info;
|
||||
fetch(getURL('/json/cfg'), {method: 'get'})
|
||||
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch /json/cfg')))
|
||||
.then(cfgData => {
|
||||
// Fetch fresh data from /json/info endpoint as requested
|
||||
fetch(getURL('/json/info'), {
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(infoData => {
|
||||
// Map to UpgradeEventRequest structure per OpenAPI spec
|
||||
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
|
||||
const upgradeData = {
|
||||
@@ -3449,58 +3414,13 @@ function reportUpgradeEvent(info, oldVersion, alwaysReport) {
|
||||
brand: infoData.brand, // Device brand (always present)
|
||||
product: infoData.product, // Product name (always present)
|
||||
flashSize: infoData.flash, // Flash size (always present)
|
||||
repo: infoData.repo, // GitHub repository (always present)
|
||||
fsUsed: infoData.fs?.u, // Filesystem used space in kB
|
||||
fsTotal: infoData.fs?.t, // Filesystem total space in kB
|
||||
|
||||
// LED hardware
|
||||
busCount: cfgData.hw?.led?.ins?.length ?? 1,
|
||||
busTypes: (cfgData.hw?.led?.ins ?? []).map(b => busTypeToString(b.type)),
|
||||
matrixWidth: infoData.leds?.matrix?.w,
|
||||
matrixHeight: infoData.leds?.matrix?.h,
|
||||
ledFeatures: [
|
||||
...(infoData.leds?.lc & 0x02 ? ["rgbw"] : []),
|
||||
...(infoData.leds?.lc & 0x04 ? ["cct"] : []),
|
||||
...((infoData.leds?.maxpwr ?? 0) > 0 ? ["abl"] : []),
|
||||
...(cfgData.hw?.led?.cr ? ["cct-from-rgb"] : []),
|
||||
...(cfgData.hw?.led?.cct ? ["white-balance"] : []),
|
||||
...((cfgData.light?.gc?.col ?? 1.0) > 1.0 || (cfgData.light?.gc?.bri ?? 1.0) > 1.0 ? ["gamma"] : []),
|
||||
...(cfgData.light?.aseg ? ["auto-segments"] : []),
|
||||
...((cfgData.light?.nl?.mode ?? 0) > 0 ? ["nightlight"] : []),
|
||||
],
|
||||
|
||||
// peripherals (note: i2c/spi may reflect board defaults, not user-configured hardware)
|
||||
peripherals: [
|
||||
...((cfgData.hw?.relay?.pin ?? -1) >= 0 ? ["relay"] : []),
|
||||
...((cfgData.hw?.btn?.ins ?? []).filter(b => b.type !== 0).length > 0 ? ["buttons"] : []),
|
||||
...((cfgData.eth?.type ?? 0) > 0 ? ["ethernet"] : []),
|
||||
...((cfgData.if?.live?.dmx?.inputRxPin ?? 0) > 0 ? ["dmx-input"] : []),
|
||||
...((cfgData.hw?.ir?.type ?? 0) > 0 ? ["ir-remote"] : []),
|
||||
],
|
||||
buttonCount: (cfgData.hw?.btn?.ins ?? []).filter(b => b.type !== 0).length,
|
||||
|
||||
// integrations
|
||||
integrations: [
|
||||
...(cfgData.if?.hue?.en ? ["hue"] : []),
|
||||
...(cfgData.if?.mqtt?.en ? ["mqtt"] : []),
|
||||
...(cfgData.if?.va?.alexa ? ["alexa"] : []),
|
||||
...(cfgData.if?.sync?.send?.en ? ["wled-sync"] : []),
|
||||
...(cfgData.nw?.espnow ? ["esp-now"] : []),
|
||||
...(cfgData.if?.sync?.espnow ? ["esp-now-sync"] : []),
|
||||
],
|
||||
|
||||
// usermods
|
||||
usermods: Object.keys(cfgData.um ?? {}),
|
||||
usermodIds: infoData.um ?? [],
|
||||
};
|
||||
|
||||
// IR remote — only include if configured
|
||||
const irType = IR_TYPES[cfgData.hw?.ir?.type ?? 0];
|
||||
if (irType) upgradeData.irRemoteType = irType;
|
||||
repo: infoData.repo // GitHub repository (always present)
|
||||
};
|
||||
|
||||
// Add optional fields if available
|
||||
if (infoData.psrSz !== undefined) upgradeData.psramSize = infoData.psrSz; // Total PSRAM size in MB; can be 0
|
||||
|
||||
// Note: partitionSizes not currently available in /json/info endpoint
|
||||
|
||||
// Make AJAX call to postUpgradeEvent API
|
||||
return fetch('https://usage.wled.me/api/usage/upgrade', {
|
||||
@@ -3526,22 +3446,11 @@ function reportUpgradeEvent(info, oldVersion, alwaysReport) {
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('Failed to report upgrade', e);
|
||||
showToast('Report failed', true);
|
||||
updateVersionInfo(info.ver, false, !!alwaysReport);
|
||||
showToast('Report failed. Please try again later.', true);
|
||||
// Do NOT update version info on error - user will be prompted again
|
||||
});
|
||||
}
|
||||
|
||||
function busTypeToString(t) {
|
||||
if (t === 0) return "none";
|
||||
if (t === 40) return "on-off";
|
||||
if (t >= 16 && t <= 39) return "digital"; // WS2812, SK6812, etc.
|
||||
if (t >= 41 && t <= 47) return "pwm"; // analog RGB/CCT/single
|
||||
if (t >= 48 && t <= 63) return "digital-spi"; // APA102, WS2801, etc.
|
||||
if (t >= 64 && t <= 71) return "hub75"; // HUB75 matrix panels
|
||||
if (t >= 80 && t <= 95) return "network"; // DDP, E1.31, ArtNet
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function updateVersionInfo(version, neverAsk, alwaysReport) {
|
||||
const versionInfo = {
|
||||
version: version,
|
||||
|
||||
@@ -94,7 +94,7 @@ h3 {
|
||||
background: #555;
|
||||
}
|
||||
.cm button.danger {
|
||||
color: #b21;
|
||||
color: #f44;
|
||||
}
|
||||
|
||||
/* Editor styles */
|
||||
@@ -332,25 +332,30 @@ button, .btn {
|
||||
<input type="text" id="txt" placeholder="Enter text" maxlength="64" style="margin-left:15px;flex:1;min-width:300px;">
|
||||
<button class="btn" id="aTxt">✓</button>
|
||||
</div>
|
||||
<small style="display:block;text-align:center;">Preview - actual display may differ</small>
|
||||
<canvas id="tpv" width="100" height="50" style="background:#000;display:block;margin:10px auto;border-radius:8px;width:300px;height:150px;"></canvas>
|
||||
|
||||
<h3>Settings</h3>
|
||||
<div class="cs">
|
||||
<div class="fr">Speed <input type="range" id="sx" min="0" max="255"></div>
|
||||
<div class="fr">Y Offset <input type="range" id="ix" min="0" max="255"></div>
|
||||
<div class="fr">Trail <input type="range" id="c1" min="0" max="255" value="0"></div>
|
||||
<div class="fr">Font <input type="range" id="c2" min="3" max="255" step="63"></div>
|
||||
<div class="fr">Rotate <input type="range" id="c3" min="3" max="31" step="7"></div>
|
||||
<div class="fr">
|
||||
Speed <input type="range" id="sx" min="0" max="255">
|
||||
</div>
|
||||
<div class="fr">
|
||||
Y Offset <input type="range" id="ix" min="0" max="255">
|
||||
</div>
|
||||
<div class="fr">
|
||||
Trail <input type="range" id="c1" min="0" max="255">
|
||||
</div>
|
||||
<div class="fr">
|
||||
Font Size <input type="range" id="c2" min="0" max="255">
|
||||
</div>
|
||||
<div class="fr">
|
||||
Rotate <input type="range" id="c3" min="0" max="31">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="display:flex;gap:20px;justify-content:center;">
|
||||
<label style="display:flex;align-items:center;gap:5px">
|
||||
<input type="checkbox" id="o1"> Gradient
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:5px">
|
||||
<input type="checkbox" id="o2"> Custom Font
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:5px">
|
||||
<input type="checkbox" id="o3"> Reverse
|
||||
</label>
|
||||
@@ -376,24 +381,40 @@ button, .btn {
|
||||
<div><a href="#" class="tk" data-t="#DAY">#DAY</a> - Weekday (Mon)</div>
|
||||
<div><a href="#" class="tk" data-t="#DDDD">#DDDD</a> - Weekday (Monday)</div>
|
||||
</div>
|
||||
<div style="margin:10px;">
|
||||
<div style="margin:10px;padding-top:10px;border-top:1px solid #444">
|
||||
<strong>Tips:</strong></small><br>
|
||||
• Mix text and tokens: "It's #HHMM O'Clock" or "#HH:#MM:#SS"<br>
|
||||
• Add '0' suffix for leading zeros: #TIME0, #HH0, etc.
|
||||
</div>
|
||||
<hr>
|
||||
<h3>Custom Fonts</h3>
|
||||
<small>FX uses first 5 fonts only</small><br>
|
||||
<div id="fList" style="display:flex; flex-wrap:wrap; gap:8px; padding:10px; justify-content:center;"></div>
|
||||
<button class="btn sml" id="dcf" style="margin:5px auto; display:block; font-size:11px;">Download Classic WLED Fonts</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ti1D" style="display:none;">Not available in 1D</div>
|
||||
</div>
|
||||
<div id="oTab" class="tabc">
|
||||
<div id="tools">
|
||||
<div style="padding:20px;text-align:center;">Loading tools...</div>
|
||||
<div class="ed active">
|
||||
<div>
|
||||
<h3>Pixel Paint</h3>
|
||||
<div><small>Interactive painting tool</small></div>
|
||||
<button class="btn" id="t1" style="display:none"></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="ed active">
|
||||
<div>
|
||||
<h3>Video Lab</h3>
|
||||
<div><small>Stream video and generate animated GIFs (beta)</small></div>
|
||||
<button class="btn" id="t2" style="display:none"></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="ed active">
|
||||
<div>
|
||||
<h3>PIXEL MAGIC Tool</h3>
|
||||
<div><small>Legacy pixel art editor</small></div>
|
||||
<button class="btn" id="t3" style="display:none"></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div style="margin:20px 0">
|
||||
@@ -421,13 +442,6 @@ let pan=false,psX=0,psY=0,poX=0,poY=0;
|
||||
let iL=[]; // image list
|
||||
let gF=null,gI=null,aT=null;
|
||||
let fL; // file list
|
||||
const classics=['console_font_4x6.wbf','console_font_5x12.wbf','console_font_5x8.wbf','console_font_6x8.wbf','console_font_7x9.wbf']; // classic WLED fonts list
|
||||
let pT = []; // local tools list from JSON
|
||||
let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility
|
||||
const remoteURL = 'https://dedehai.github.io/pftools.json'; // Change to your actual repo
|
||||
const toolsjson = 'pftools.json';
|
||||
// note: the pftools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work
|
||||
// also the code assumes that the tool url points to a gz file
|
||||
|
||||
// load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded
|
||||
(function loadFiles() {
|
||||
@@ -445,19 +459,21 @@ async function init() {
|
||||
getLoc();
|
||||
// create off screen canvas
|
||||
rv = cE('canvas');
|
||||
rvc = rv.getContext('2d',{willReadFrequently:true});
|
||||
rvc = rv.getContext('2d',{willReadFrequently:true});
|
||||
rv.width = cv.width; rv.height = cv.height;
|
||||
await flU(); // update file list
|
||||
tabSw(localStorage.tab||'img'); // switch to last open tab or image tab by default
|
||||
|
||||
await segLoad(); // load available segments
|
||||
await fsMem(); // update & show file system memory info, also updates wled version (wv)
|
||||
await loadTools(); // load additional tools list from pftools.json
|
||||
await flU(); // update file list
|
||||
toolChk('pixelpaint.htm','t1'); // update buttons of additional tools
|
||||
toolChk('videolab.htm','t2');
|
||||
toolChk('pxmagic.htm','t3');
|
||||
await fsMem(); // show file system memory info
|
||||
}
|
||||
|
||||
/* update file list */
|
||||
async function flU(){
|
||||
try{
|
||||
const r = await fetch(getURL('/edit?list=/&cb=' + Date.now()));
|
||||
const r = await fetch(getURL('/edit?list=/'));
|
||||
fL = await r.json();
|
||||
}catch(e){console.error(e);}
|
||||
}
|
||||
@@ -492,7 +508,6 @@ function segLoad(){
|
||||
}
|
||||
if(v1) s1.value=v1; if(v2) s2.value=v2;
|
||||
s2.onchange(); // trigger on load to toggle show/hide of text tool
|
||||
txtSegLoad(); // load settings for currently selected segment
|
||||
const o=s1.options[s1.selectedIndex];
|
||||
if(o){ getId('w').value=o.dataset.w||16; getId('h').value=o.dataset.h||16; }
|
||||
}).catch(console.error);
|
||||
@@ -519,7 +534,6 @@ getId('segT').onchange = () => {
|
||||
const is2D = (getId('segT').selectedOptions[0].dataset.h || 1) > 1;
|
||||
getId('ti').style.display = is2D ? 'block' : 'none';
|
||||
getId('ti1D').style.display = is2D ? 'none' : 'block';
|
||||
txtSegLoad(); // update controls to match selected segment
|
||||
};
|
||||
|
||||
/* image list */
|
||||
@@ -548,7 +562,7 @@ async function imgLoad(){
|
||||
}catch(e){console.error(e);}
|
||||
}
|
||||
|
||||
/* load images into grid */
|
||||
/* load images into grid TODO: when switching tabs, it can throw 503 and have unloaded images, tried to fix it but all my attempts failed*/
|
||||
async function imgLoad2(imgs){
|
||||
const grid=getId('gr');
|
||||
for(const f of imgs){
|
||||
@@ -562,14 +576,14 @@ async function imgLoad2(imgs){
|
||||
await new Promise(res=>{
|
||||
const im=new Image();
|
||||
im.onload=()=>{
|
||||
it.style.backgroundImage=`url('${encodeURI(url)}?cb=${Date.now()}')`;
|
||||
it.style.backgroundImage=`url(${url}?cb=${Date.now()})`;
|
||||
if(!isGif) it.style.border="5px solid red";
|
||||
it.classList.remove('loading'); res();
|
||||
const kb=Math.round(f.size/1024);
|
||||
it.title=`${name}\n${im.width}x${im.height}\n${kb} KB`;
|
||||
};
|
||||
im.onerror=()=>{it.classList.remove('loading');it.style.background='#222';res();};
|
||||
im.src=encodeURI(url)+'?cb='+Date.now();
|
||||
im.src=url+'?cb='+Date.now();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -583,130 +597,45 @@ function imgRm(nm){
|
||||
//}
|
||||
}
|
||||
|
||||
// check for tool compatibility with current wled version (wled_min requirement in pftools.json)
|
||||
function compTool(t) {
|
||||
if (!t.wled_min || !wv) return true;
|
||||
const m = t.wled_min.match(/\d+/g);
|
||||
const a = wv, b = [m[0]|0, m[1]|0];
|
||||
return a[0] > b[0] || (a[0] === b[0] && a[1] >= b[1]);
|
||||
}
|
||||
|
||||
/* additional tools: loaded from pftools.json, store json locally for offline use*/
|
||||
async function loadTools() {
|
||||
/* additional tools: check if present, install if not */
|
||||
function toolChk(file, btnId) {
|
||||
try {
|
||||
const res = await fetch(getURL('/' + toolsjson + '?cb=' + Date.now())); // load local tools list
|
||||
pT = res.ok ? await res.json() : [];
|
||||
} catch (e) {}
|
||||
|
||||
renderTools(); // render whatever we have
|
||||
|
||||
try {
|
||||
const rT = await (await fetch(remoteURL + '?cb=' + Date.now())).json();
|
||||
let changed = false;
|
||||
rT.forEach(rt => {
|
||||
let lt = pT.find(t => t.id === rt.id);
|
||||
if (!lt) {
|
||||
pT.push(rt); // new tool available
|
||||
changed = true;
|
||||
} else {
|
||||
// check version
|
||||
if (isNewer(rt.ver, lt.ver)) {
|
||||
lt.pending = rt; // mark update as pending, keep old info until user clicks update button
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
await saveToolsjson(); // save updated json
|
||||
renderTools();
|
||||
const has = fL.some(f => f.name.includes(file));
|
||||
const b = getId(btnId);
|
||||
b.style.display = 'block';
|
||||
b.style.margin = '10px auto';
|
||||
if (has) {
|
||||
b.textContent = 'Open';
|
||||
b.onclick = () => window.open(getURL(`/${file}`), '_blank'); // open tool: remove gz to not trigger download
|
||||
} else {
|
||||
b.textContent = 'Download';
|
||||
b.onclick = async () => {
|
||||
const fileGz = file + '.gz'; // use gz version
|
||||
const url = `https://dedehai.github.io/${fileGz}`; // always download gz version
|
||||
if (!confirm(`Download ${url}?`)) return;
|
||||
try {
|
||||
const f = await fetch(url);
|
||||
if (!f.ok) throw new Error("Download failed " + f.status);
|
||||
const blob = await f.blob(), fd = new FormData();
|
||||
fd.append("data", blob, fileGz);
|
||||
const u = await fetch(getURL("/upload"), { method: "POST", body: fd });
|
||||
alert(u.ok ? "Tool installed!" : "Upload failed");
|
||||
await flU(); // update file list
|
||||
toolChk(file, btnId); // re-check and update button (must pass non-gz file name)
|
||||
} catch (e) { alert("Error " + e.message); }
|
||||
};
|
||||
}
|
||||
} catch(e){console.error(e);}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
async function saveToolsjson() {
|
||||
const fd = new FormData();
|
||||
fd.append("data", new Blob([JSON.stringify(pT)], {type:'application/json'}), toolsjson);
|
||||
await fetch(getURL("/upload"), { method: "POST", body: fd });
|
||||
}
|
||||
|
||||
// tool versions must be in format major.minor (e.g. 0.95 or 1.1)
|
||||
function isNewer(vN, vO) {
|
||||
return parseFloat(vN) > parseFloat(vO);
|
||||
}
|
||||
|
||||
function renderTools() {
|
||||
let h = '';
|
||||
pT.forEach(t => {
|
||||
const installed = fL.some(f => f.name.includes(t.file)); // check if tool file exists (either .htm or .htm.gz)
|
||||
const target = (installed && t.pending) ? t.pending : t; // if update pending and tool is installed, show update info
|
||||
const compatible = compTool(target); // check if compatible with current wled version
|
||||
h += `<div class="ed active" style="margin-bottom:10px; border-radius:20px; text-align:left;">
|
||||
<div style="display:flex; justify-content:space-between;">
|
||||
<h3>${esc(t.name)} <small style="font-size:10px">v${esc(t.ver)}</small></h3>
|
||||
${installed ? `<button class="sml" style="height:40px;" onclick="deleteFile('${esc(t.file)}')">✕</button>` : ''}
|
||||
</div>
|
||||
${t.desc}
|
||||
<div style="font-size:10px; color:#888;">
|
||||
by ${esc(t.author)} | <a href="${safeUrl(t.source)}" target="_blank">${safeUrl(t.source)}</a>
|
||||
</div>
|
||||
${
|
||||
compatible ?
|
||||
`<div class="crw">
|
||||
${installed ?
|
||||
`<button class="btn" onclick="window.location.href=getURL('/${esc(t.file)}')">Open</button>`
|
||||
: `<button class="btn" onclick="insT('${esc(t.id)}')">Install</button>`
|
||||
}
|
||||
${t.pending && installed ?
|
||||
`<button class="btn" style="color:#fb2" onclick="insT('${esc(t.id)}')">Update v${esc(t.pending.ver)}</button>`
|
||||
: ''
|
||||
}
|
||||
</div>`
|
||||
: `<div style="color:#f44;font-size:12px;"> Requires WLED ${esc(target.wled_min)} </div>`
|
||||
}
|
||||
</div>`;
|
||||
});
|
||||
getId('tools').innerHTML = h || 'No tools found (offline?).';
|
||||
}
|
||||
|
||||
// install or update tool
|
||||
async function insT(id) {
|
||||
const t = pT.find(x => x.id == id);
|
||||
ovShow();
|
||||
try {
|
||||
const src = t.pending || t;
|
||||
const f = await fetch(src.url); // url in json must be pointing to a gz file
|
||||
if (!f.ok) throw new Error("Download failed " + f.status);
|
||||
const fd = new FormData();
|
||||
fd.append("data", await f.blob(), src.file + '.gz'); // always use gz for file name (source MUST be gz)
|
||||
const u = await fetch(getURL("/upload"), { method: "POST", body: fd });
|
||||
alert(u.ok ? "Tool installed!" : "Install failed");
|
||||
if (u.ok && t.pending) {
|
||||
// save and remove update info after successful update
|
||||
Object.assign(t, t.pending);
|
||||
delete t.pending;
|
||||
}
|
||||
await saveToolsjson();
|
||||
await flU(); // refresh file list
|
||||
renderTools();
|
||||
} catch(e) { alert("Error " + e.message); }
|
||||
fsMem(); // refresh memory info after upload
|
||||
ovHide();
|
||||
}
|
||||
|
||||
/* fs/mem info & wled version */
|
||||
/* fs/mem info */
|
||||
async function fsMem(){
|
||||
try{
|
||||
const r=await fetch(getURL('/json/info'));
|
||||
const info=await r.json();
|
||||
if (info){
|
||||
if (info.fs) {
|
||||
getId("mem").textContent=`by @dedehai | Memory: ${info.fs.u} KB / ${info.fs.t} KB`;
|
||||
getId("mem").style.display="block";
|
||||
}
|
||||
if (info.ver) {
|
||||
const m = info.ver.match(/\d+/g); // extract all numbers from version string (e.g. "16.1.0-beta" → [16, 1])
|
||||
wv = [parseInt(m[0]) || 0, parseInt(m[1]) || 0];
|
||||
}
|
||||
if(info&&info.fs){
|
||||
getId("mem").textContent=`by @dedehai | Memory: ${info.fs.u} KB / ${info.fs.t} KB`;
|
||||
getId("mem").style.display="block";
|
||||
}
|
||||
}catch(e){console.error(e);}
|
||||
}
|
||||
@@ -1178,7 +1107,6 @@ getId('up').onclick = async () => {
|
||||
} catch (e) {
|
||||
msg(`Error: ${e.message}`, 'err');
|
||||
} finally {
|
||||
fsMem(); // refresh memory info after upload
|
||||
ovHide();
|
||||
}
|
||||
};
|
||||
@@ -1195,7 +1123,7 @@ async function imgPlay(url,name){
|
||||
on:true,
|
||||
seg: cur!==null && cur!==tgt
|
||||
? [{id:cur,fx:0,n:""},{id:tgt,fx:53,frz:false,sx:128,n:name}]
|
||||
: {id:tgt,fx:53,frz:false,sx:128,ix:0,n:name}
|
||||
: {id:tgt,fx:53,frz:false,sx:128,n:name}
|
||||
};
|
||||
const r=await fetch(getURL('/json/state'),{method:'POST',body:JSON.stringify(j)});
|
||||
const out=await r.json();
|
||||
@@ -1215,7 +1143,7 @@ function menuShow(x,y){
|
||||
m.style.left=x+'px'; m.style.top=y+'px';
|
||||
m.innerHTML=`
|
||||
<button onclick="imgDl()">Download</button>
|
||||
<button class="danger" onclick="deleteFile(sI.name)">Delete</button>`;
|
||||
<button class="danger" onclick="imgDel()">Delete</button>`;
|
||||
d.body.appendChild(m);
|
||||
setTimeout(()=>{
|
||||
const h=e=>{
|
||||
@@ -1235,26 +1163,16 @@ async function imgDl(){
|
||||
}catch(e){msg('Download failed','err');}
|
||||
menuClose();
|
||||
}
|
||||
|
||||
async function deleteFile(name){
|
||||
name = name.replace('/',''); // remove leading slash if present (just in case)
|
||||
if (fL.some(f => f.name.replace('/','') === `${name}.gz`))
|
||||
name += '.gz'; // if .gz version of file exists, delete that (handles tools which are stored gzipped on device)
|
||||
if(!confirm(`Delete ${name}?`))return;
|
||||
async function imgDel(){
|
||||
if(!confirm(`Delete ${sI.name}?`))return;
|
||||
ovShow();
|
||||
try{
|
||||
const r = await fetch(getURL(`/edit?func=delete&path=/${name}`));
|
||||
if(r.ok){
|
||||
msg('Deleted');
|
||||
imgRm(name); // remove image from grid (if this was not an image, this does nothing)
|
||||
}
|
||||
const r = await fetch(getURL(`/edit?func=delete&path=/${sI.name}`));
|
||||
if(r.ok){ msg('Deleted'); imgRm(sI.name); }
|
||||
else msg('Delete failed! File in use?','err');
|
||||
}catch(e){msg('Delete failed','err');}
|
||||
finally{ovHide();}
|
||||
fsMem(); // refresh memory info after delete
|
||||
menuClose(); // close menu (used for image delete button)
|
||||
await flU(); // update file list
|
||||
renderTools(); // re-render tools list
|
||||
menuClose();
|
||||
}
|
||||
|
||||
/* tab select and additional tools */
|
||||
@@ -1263,11 +1181,12 @@ function tabSw(tab) {
|
||||
getId(id).classList.toggle('active', tab===['img','txt','oth'][i%3]);
|
||||
});
|
||||
localStorage.tab=tab;
|
||||
({txt:()=>{txtSegLoad(); scanFonts();}, img:imgLoad}[tab]||(()=>{}))(); // on tab switch, load images and available fonts
|
||||
({txt:txtSegLoad,img:imgLoad}[tab]||(()=>{}))(); // functions to execute on tab switch (currently none for oth)
|
||||
}
|
||||
'Img,Txt,Oth'.split(',').forEach((s,i)=>{
|
||||
getId('t'+s).onclick=()=>tabSw(['img','txt','oth'][i]);
|
||||
});
|
||||
tabSw(localStorage.tab||'img');
|
||||
|
||||
/* tokens insert */
|
||||
function txtIns(el,t){
|
||||
@@ -1290,13 +1209,12 @@ async function txtSegLoad(){
|
||||
const r=await fetch(getURL('/json/state')),j=await r.json();
|
||||
if(j.seg&&j.seg[id]){
|
||||
const s=j.seg[id];
|
||||
if(s.fx!==txtFX) return; // only update when this segment uses scrolling text effect
|
||||
getId('txt').value=s.n||'';
|
||||
getId('sx').value=s.sx||128;
|
||||
getId('ix').value=s.ix||128;
|
||||
getId('c1').value=s.c1||0;
|
||||
getId('c2').value=s.c2||128;
|
||||
getId('c3').value=s.c3||16;
|
||||
getId('c2').value=s.c2||0;
|
||||
getId('c3').value=s.c3||0;
|
||||
getId('o1').checked=!(!s.o1);
|
||||
getId('o3').checked=!(!s.o3);
|
||||
}
|
||||
@@ -1304,12 +1222,12 @@ async function txtSegLoad(){
|
||||
}
|
||||
|
||||
/* auto apply on change */
|
||||
['sx','ix','c1','c2','c3','o1','o2','o3'].forEach(id=>{ getId(id).onchange=txtUp; });
|
||||
['sx','ix','c1','c2','c3','o1','o3'].forEach(id=>{ getId(id).onchange=txtUp; });
|
||||
|
||||
/* send text settings */
|
||||
function txtUp(){
|
||||
const id=+getId('segT').value,txt=getId('txt').value.trim().slice(0,64);
|
||||
const j={on:true,seg:{id,fx:122,n:txt,sx:+getId('sx').value,ix:+getId('ix').value,c1:+getId('c1').value,c2:+getId('c2').value,c3:+getId('c3').value,o1:getId('o1').checked,o2:getId('o2').checked,o3:getId('o3').checked}};
|
||||
const j={on:true,seg:{id,fx:122,n:txt,sx:+getId('sx').value,ix:+getId('ix').value,c1:+getId('c1').value,c2:+getId('c2').value,c3:+getId('c3').value,o1:getId('o1').checked,o3:getId('o3').checked}};
|
||||
fetch(getURL('/json/state'),{method:'POST',body:JSON.stringify(j)})
|
||||
.then(r => { if(r.ok) segLoad(); })
|
||||
.catch(console.error);
|
||||
@@ -1328,85 +1246,6 @@ getId('aTxt').onclick=async()=>{
|
||||
}catch(e){ msg(`Error: ${e.message}`,'err'); }
|
||||
finally{ ovHide(); }
|
||||
};
|
||||
|
||||
// load available fonts (files ending with .wbf) and display in list
|
||||
function scanFonts(){
|
||||
const wbf=(fL||[]).filter(f=>f.name.endsWith('.wbf'));
|
||||
getId('fList').innerHTML=wbf.map(f=>
|
||||
`<span>${f.name} <b onclick="delFont('${f.name}')" style="cursor:pointer;color:#8cf;padding:0 3px">✖</b></span>`
|
||||
).join('')||'<span style="color:#888">None</span>';
|
||||
getId('dcf').style.display=classics.every(fn=>wbf.some(f=>f.name===fn))?'none':'block'; // hide "Download classic fonts" button if all are present
|
||||
}
|
||||
|
||||
// delete font file
|
||||
async function delFont(name){
|
||||
if(!confirm(`Delete ${name}?`))return;
|
||||
const r=await fetch(getURL(`/edit?func=delete&path=/${name}`));
|
||||
r.ok?msg('Deleted'):msg('Delete failed','err');
|
||||
await flU(); scanFonts();
|
||||
}
|
||||
|
||||
// download classic WLED fonts from github and upload to FS
|
||||
getId('dcf').onclick=async()=>{
|
||||
if(!confirm('Download classic WLED fonts?'))return;
|
||||
ovShow(); let ok=0,fail=0;
|
||||
for(const fn of classics){
|
||||
try{
|
||||
const r=await fetch(`https://dedehai.github.io/wbfFonts/${fn}`);
|
||||
if(!r.ok){fail++;continue;}
|
||||
const fd=new FormData();
|
||||
fd.append('data',await r.blob(),fn);
|
||||
(await fetch(getURL('/upload'),{method:'POST',body:fd})).ok?ok++:fail++;
|
||||
}catch(e){msg(`Failed ${fn}: ${e.message}`,'err');}
|
||||
}
|
||||
ovHide(); msg('Fonts installed');
|
||||
await flU(); scanFonts();
|
||||
};
|
||||
|
||||
/* preview canvas - actual display may differ */
|
||||
const tpv = getId('tpv'), tpx = tpv.getContext('2d');
|
||||
function draw() {
|
||||
// fade out to simulate "trail"
|
||||
let tr = 1 - (getId('c1').value / 280); // c1 controls trail length
|
||||
//tpx.fillStyle = `rgba(0,0,0,${tr.toFixed(2)})`;
|
||||
tpx.fillStyle = `rgba(0,0,0,${tr})`;
|
||||
tpx.fillRect(0,0,100,50);
|
||||
|
||||
let txt = getId('txt').value || 'WLED'; // note: decoding tokens is not implemented as it just takes a lot of code for little benefit
|
||||
let sx = getId('sx').value;
|
||||
let ix = getId('ix').value, c2 = getId('c2').value, c3 = getId('c3').value;
|
||||
|
||||
let fzs = [8, 12, 14, 20, 28][Math.floor(c2/52)] || 8; // Font sizes 1-5
|
||||
let rot = -(Math.round((c3)/7.5)-2) * Math.PI/2; // -2 to 2 maps to 180 to -180
|
||||
let t = (Date.now() * (0.02 + (sx / 255) * 0.1)) % (100 + txt.length * fzs);
|
||||
let x = getId('o3').checked ? (t - (txt.length * fzs)) : 100 - t;
|
||||
let y = 25 + (ix - 128) / 5;
|
||||
|
||||
// draw char-by-char just like the FX
|
||||
tpx.fillStyle = "#fff";
|
||||
tpx.font = fzs + "px monospace";
|
||||
tpx.textAlign = "center";
|
||||
tpx.textBaseline = "middle";
|
||||
|
||||
for(let i=0; i<txt.length; i++) {
|
||||
let charX = x + (i * fzs); // simple letter spacing
|
||||
if(charX < -20 || charX > 120) continue;
|
||||
tpx.save();
|
||||
tpx.translate(charX, y);
|
||||
tpx.rotate(rot);
|
||||
tpx.fillStyle = "#fff";
|
||||
if (getId('o1').checked) { // gradient color
|
||||
let g = tpx.createLinearGradient(0, -fzs/2, 0, fzs/2);
|
||||
g.addColorStop(0, "#f5f"); g.addColorStop(1, "#8cf");
|
||||
tpx.fillStyle = g;
|
||||
}
|
||||
tpx.fillText(txt[i], 0, 0);
|
||||
tpx.restore();
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
draw();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,13 +8,6 @@
|
||||
<script>
|
||||
var el=false;
|
||||
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||
var timerCount = 0;
|
||||
var maxTimers;
|
||||
var presets = {};
|
||||
var sortedPresetOptions = '';
|
||||
const TIMER_SUNRISE = 255;
|
||||
const TIMER_SUNSET = 254;
|
||||
const TIMER_WEEKDAYS_ALL = 255;
|
||||
// load common.js with retry on error
|
||||
(function loadFiles() {
|
||||
const l = document.createElement('script');
|
||||
@@ -26,63 +19,13 @@
|
||||
|
||||
function S() {
|
||||
getLoc();
|
||||
populatePresets(true);
|
||||
loadJS(getURL('/settings/s.js?p=5'), false, ()=>{BTa();}, ()=>{
|
||||
updLatLon();
|
||||
Cs();
|
||||
});
|
||||
FC();
|
||||
}); // If we set async false, file is loaded and executed, then next statement is processed
|
||||
if (loc) d.Sf.action = getURL('/settings/time');
|
||||
}
|
||||
function populatePresets(fromls) {
|
||||
let json = null;
|
||||
if (fromls) {
|
||||
try {
|
||||
json = JSON.parse(localStorage.getItem("wledP"));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
showToast("Please visit the main UI first to load presets", true);
|
||||
return;
|
||||
}
|
||||
|
||||
onPresetsLoaded(json);
|
||||
}
|
||||
function onPresetsLoaded(json) {
|
||||
presets = json;
|
||||
delete presets["0"];
|
||||
bSPO();
|
||||
pMP();
|
||||
rTOPO();
|
||||
rBPO();
|
||||
}
|
||||
function bSPO() { // buildSortedPresetOptions
|
||||
sortedPresetOptions = '';
|
||||
for (var p of Object.entries(presets)) {
|
||||
var id = p[0];
|
||||
var name = p[1].n || ("Preset " + id);
|
||||
sortedPresetOptions += `<option value="${id}">${id}: ${name}</option>`;
|
||||
}
|
||||
}
|
||||
function sPSV(sel, value, attrName) { // setPresetSelectValue
|
||||
var v = (value === undefined || value === null || value === "") ? "0" : String(value);
|
||||
sel.setAttribute(attrName, v);
|
||||
sel.value = v;
|
||||
if (sel.value === "") sel.value = "0";
|
||||
}
|
||||
function rPS(sel, presetOpts, attrName) { // refreshPresetSelect
|
||||
var currentVal = sel.value;
|
||||
var initialVal = sel.getAttribute(attrName);
|
||||
var val = currentVal;
|
||||
if ((val === "" || val === "0") && initialVal !== null && initialVal !== "" && initialVal !== "0") {
|
||||
val = initialVal;
|
||||
}
|
||||
if (val === "") val = "0";
|
||||
sel.innerHTML = presetOpts;
|
||||
sel.value = val;
|
||||
if (sel.value === "" && initialVal !== null && initialVal !== "") sel.value = initialVal;
|
||||
if (sel.value === "") sel.value = "0";
|
||||
}
|
||||
function expand(o,i)
|
||||
{
|
||||
var t = gId("WD"+i);
|
||||
@@ -92,189 +35,79 @@
|
||||
function Cs() { gId("cac").style.display=(gN("OL").checked)?"block":"none"; }
|
||||
function BTa()
|
||||
{
|
||||
timerCount = 0;
|
||||
gId("TMT").innerHTML = "<thead><tr><th>En.</th><th>Type</th><th>Hour</th><th>Minute</th><th></th></tr></thead>";
|
||||
var ih="<thead><tr><th>En.</th><th>Hour</th><th>Minute</th><th>Preset</th><th></th></tr></thead>";
|
||||
for (i=0;i<8;i++) {
|
||||
ih+=`<tr><td><input name="W${i}" id="W${i}" type="hidden"><input id="W${i}0" type="checkbox"></td>
|
||||
<td><input name="H${i}" class="xs" type="number" min="0" max="24"></td>
|
||||
<td><input name="N${i}" class="xs" type="number" min="0" max="59"></td>
|
||||
<td><input name="T${i}" class="s" type="number" min="0" max="250"></td>
|
||||
<td><div id="CB${i}" onclick="expand(this,${i})" class="cal">📅</div></td></tr>`;
|
||||
ih+=`<tr><td colspan=5><div id="WD${i}" style="display:none;background-color:#444;"><hr>Run on weekdays`;
|
||||
ih+=`<table><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`
|
||||
for (j=1;j<8;j++) ih+=`<td><input id="W${i}${j}" type="checkbox"></td>`;
|
||||
ih+=`</tr></table>from <select name="M${i}">`;
|
||||
for (j=0;j<12;j++) ih+=`<option value="${j+1}">${ms[j]}</option>`;
|
||||
ih+=`</select><input name="D${i}" class="xs" type="number" min="1" max="31"></input> to <select name="P${i}">`;
|
||||
for (j=0;j<12;j++) ih+=`<option value="${j+1}">${ms[j]}</option>`;
|
||||
ih+=`</select><input name="E${i}" class="xs" type="number" min="1" max="31"></input>
|
||||
<hr></div></td></tr>`;
|
||||
}
|
||||
ih+=`<tr><td><input name="W8" id="W8" type="hidden"><input id="W80" type="checkbox"></td>
|
||||
<td>Sunrise<input name="H8" value="255" type="hidden"></td>
|
||||
<td><input name="N8" class="xs" type="number" min="-59" max="59"></td>
|
||||
<td><input name="T8" class="s" type="number" min="0" max="250"></td>
|
||||
<td><div id="CB8" onclick="expand(this,8)" class="cal">📅</div></td></tr><tr><td colspan=5>`;
|
||||
ih+=`<div id="WD8" style="display:none;background-color:#444;"><hr><table><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`;
|
||||
for (j=1;j<8;j++) ih+=`<td><input id="W8${j}" type="checkbox"></td>`;
|
||||
ih+="</tr></table><hr></div></td></tr>";
|
||||
ih+=`<tr><td><input name="W9" id="W9" type="hidden"><input id="W90" type="checkbox"></td>
|
||||
<td>Sunset<input name="H9" value="255" type="hidden"></td>
|
||||
<td><input name="N9" class="xs" type="number" min="-59" max="59"></td>
|
||||
<td><input name="T9" class="s" type="number" min="0" max="250"></td>
|
||||
<td><div id="CB9" onclick="expand(this,9)" class="cal">📅</div></td></tr><tr><td colspan=5>`;
|
||||
ih+=`<div id="WD9" style="display:none;background-color:#444;"><hr><table><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>`;
|
||||
for (j=1;j<8;j++) ih+=`<td><input id="W9${j}" type="checkbox"></td>`;
|
||||
ih+="</tr></table><hr></div></td></tr>";
|
||||
gId("TMT").innerHTML=ih;
|
||||
}
|
||||
function addTimerRow(hour, minute, preset, weekdays, monthStart, dayStart, monthEnd, dayEnd) {
|
||||
if (timerCount >= maxTimers) return;
|
||||
var i = timerCount++;
|
||||
var isSunrise = (hour === TIMER_SUNRISE);
|
||||
var isSunset = (hour === TIMER_SUNSET);
|
||||
var isSpecial = isSunrise || isSunset;
|
||||
if (hour === undefined) hour = 0;
|
||||
if (minute === undefined) minute = 0;
|
||||
if (preset === undefined) preset = 0;
|
||||
if (weekdays === undefined) weekdays = TIMER_WEEKDAYS_ALL;
|
||||
if (monthStart === undefined) monthStart = 1;
|
||||
if (dayStart === undefined) dayStart = 1;
|
||||
if (monthEnd === undefined) monthEnd = 12;
|
||||
if (dayEnd === undefined) dayEnd = 31;
|
||||
var enabled = weekdays & 1;
|
||||
var dow = weekdays >> 1;
|
||||
var container = gId("TMT");
|
||||
var hourVal = isSpecial ? 0 : hour;
|
||||
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
|
||||
|
||||
var weekdayTable = '<table class="tw"><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>';
|
||||
for (j=1;j<8;j++) weekdayTable += `<td><input id="W${i}${j}" type="checkbox" ${(dow>>(j-1))&1?'checked':''}></td>`;
|
||||
weekdayTable += '</tr></table>';
|
||||
|
||||
var dateRange = `<div id="DR${i}" class="tdr"><span class="tdf">from <select name="M${i}">`;
|
||||
for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthStart==j+1?'selected':''}>${ms[j]}</option>`;
|
||||
dateRange += `</select><input name="D${i}" class="xs" type="number" min="1" max="31" value="${dayStart}"></span><span class="tdf">to <select name="P${i}">`;
|
||||
for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthEnd==j+1?'selected':''}>${ms[j]}</option>`;
|
||||
dateRange += `</select><input name="E${i}" class="xs" type="number" min="1" max="31" value="${dayEnd}"></span></div>`;
|
||||
|
||||
var timerMain = `
|
||||
<tr>
|
||||
<td>
|
||||
<input name="W${i}" id="W${i}" type="hidden">
|
||||
<input id="W${i}0" type="checkbox" ${enabled ? "checked" : ""}>
|
||||
</td><td>
|
||||
<select id="TS${i}" class="s" onchange="TT(${i})">
|
||||
<option value="0" ${!isSpecial ? "selected" : ""}>Regular</option>
|
||||
<option value="${TIMER_SUNRISE}" ${isSunrise ? "selected" : ""}>Sunrise</option>
|
||||
<option value="${TIMER_SUNSET}" ${isSunset ? "selected" : ""}>Sunset</option>
|
||||
</select><br>
|
||||
</td><td>
|
||||
<input ${isSpecial ? "" : 'name="H'+i+'"'} id="H${i}" class="s" type="number" min="0" max="24" value="${hourVal}" ${isSpecial ? "disabled" : ""}>
|
||||
</td>
|
||||
<td><input name="N${i}" id="N${i}" class="l" type="number" min="${isSpecial ? -120 : 0}" max="${isSpecial ? 120 : 59}" value="${minute}"></td>
|
||||
<td><div id="CB${i}" onclick="expand(this,${i})" class="cal">📅</div></td>
|
||||
</tr><tr>
|
||||
<td colspan="5">
|
||||
<select name="T${i}" id="T${i}" class="s">${presetOpts}</select>
|
||||
</td>
|
||||
</tr><tr><td colspan="5"><hr></td></tr>
|
||||
`;
|
||||
|
||||
var timerExpanded = `
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div id="WD${i}" style="display:none;background-color:#444;">
|
||||
<hr>Run on weekdays
|
||||
${weekdayTable}
|
||||
${dateRange}
|
||||
<hr>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML("beforeend", timerMain + timerExpanded);
|
||||
var timerPresetSel = gId("T"+i);
|
||||
sPSV(timerPresetSel, preset, "data-preset");
|
||||
}
|
||||
function TT(i) {
|
||||
var sel = gId("TS"+i);
|
||||
var hour = gId("H"+i);
|
||||
var min = gId("N"+i);
|
||||
var isSpecial = sel.value != 0;
|
||||
hour.disabled = isSpecial;
|
||||
if (isSpecial) {
|
||||
// Save current hour value before switching to sunrise/sunset
|
||||
hour.setAttribute("data-regular-hour", hour.value);
|
||||
hour.removeAttribute("name");
|
||||
min.min = -120;
|
||||
min.max = 120;
|
||||
} else {
|
||||
// Restore saved hour value when switching back to regular
|
||||
var savedHour = hour.getAttribute("data-regular-hour");
|
||||
if (savedHour !== null && savedHour !== "") {
|
||||
hour.value = savedHour;
|
||||
function FC()
|
||||
{
|
||||
for(i=0;i<10;i++)
|
||||
{
|
||||
let wd = gId("W"+i).value;
|
||||
for(j=0;j<8;j++) {
|
||||
gId("W"+i+j).checked=wd>>j&1;
|
||||
}
|
||||
hour.setAttribute("name", "H"+i);
|
||||
min.min = 0;
|
||||
min.max = 59;
|
||||
if (min.value < 0 || min.value > 59) min.value = 0;
|
||||
}
|
||||
}
|
||||
function pMP() { // populateMacroPresets
|
||||
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
||||
var fields = ['A0','A1','MC','MN'];
|
||||
for (var f of fields) {
|
||||
var inp = gN(f);
|
||||
if (!inp) continue;
|
||||
var val = inp.getAttribute("data-preset-value");
|
||||
if (val === null || val === "") val = inp.value;
|
||||
if (inp.tagName === "SELECT") {
|
||||
sPSV(inp, val, "data-preset-value");
|
||||
rPS(inp, presetOpts, "data-preset-value");
|
||||
} else {
|
||||
var sel = document.createElement('select');
|
||||
sel.name = f;
|
||||
sel.className = inp.className;
|
||||
sel.required = inp.required;
|
||||
sPSV(sel, val, "data-preset-value");
|
||||
rPS(sel, presetOpts, "data-preset-value");
|
||||
inp.parentNode.replaceChild(sel, inp);
|
||||
if ((wd&254) != 254 || (i<8 && (gN("M"+i).value != 1 || gN("D"+i).value != 1 || gN("P"+i).value != 12 || gN("E"+i).value != 31))) {
|
||||
expand(gId("CB"+i),i); //expand macros with custom DOW or date range set
|
||||
}
|
||||
}
|
||||
}
|
||||
function rTOPO() { // refreshTimerPresetOptions
|
||||
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
|
||||
for (var i=0; i<timerCount; i++) {
|
||||
var sel = gId("T"+i);
|
||||
if (!sel) continue;
|
||||
rPS(sel, presetOpts, "data-preset");
|
||||
}
|
||||
}
|
||||
function rBPO() { // refreshButtonPresetOptions
|
||||
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
||||
var container = gId("macros");
|
||||
if (!container) return;
|
||||
var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]');
|
||||
for (var sel of sels) {
|
||||
rPS(sel, presetOpts, "data-preset");
|
||||
}
|
||||
}
|
||||
function Wd()
|
||||
{
|
||||
for (i=0; i<timerCount; i++) {
|
||||
var m=1, val=0;
|
||||
for(j=0;j<8;j++) { val+=gId(("W"+i)+j).checked*m; m*=2;}
|
||||
gId("W"+i).value=val;
|
||||
var sel = gId("TS"+i);
|
||||
var hour = gId("H"+i);
|
||||
if (sel && sel.value != 0) {
|
||||
// Re-add name attribute, remove disabled, and set value for sunrise/sunset before submission
|
||||
hour.setAttribute("name", "H"+i);
|
||||
hour.disabled = false;
|
||||
hour.value = sel.value;
|
||||
}
|
||||
a = [0,0,0,0,0,0,0,0,0,0];
|
||||
for (i=0; i<10; i++) {
|
||||
m=1;
|
||||
for(j=0;j<8;j++) { a[i]+=gId(("W"+i)+j).checked*m; m*=2;}
|
||||
gId("W"+i).value=a[i];
|
||||
}
|
||||
if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); }
|
||||
if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); }
|
||||
}
|
||||
function addRow(i,p,l,d) {
|
||||
var t = gId("macros"); // table
|
||||
var rCnt = t.rows.length; // get the number of rows.
|
||||
var tr = t.insertRow(rCnt); // table row.
|
||||
var b = String.fromCharCode((i<10?48:55)+i);
|
||||
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
||||
var buttonBlock = document.createElement('div');
|
||||
buttonBlock.className = 'bb';
|
||||
buttonBlock.innerHTML = `
|
||||
<div class="bh">Button ${i}</div>
|
||||
<div class="bs">
|
||||
<div class="ba">
|
||||
<label>Push/Switch</label>
|
||||
<select name="MP${b}" class="s" required>${presetOpts}</select>
|
||||
</div>
|
||||
<div class="ba">
|
||||
<label>Short (on→off)</label>
|
||||
<select name="ML${b}" class="s" required>${presetOpts}</select>
|
||||
</div>
|
||||
<div class="ba">
|
||||
<label>Long (off→on)</label>
|
||||
<select name="MD${b}" class="s" required>${presetOpts}</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr style="width:100%;margin:8px 0 0 0;">
|
||||
`;
|
||||
var buttonSels = buttonBlock.querySelectorAll("select");
|
||||
var buttonVals = [String(p), String(l), String(d)];
|
||||
for (var si=0; si<buttonSels.length; si++) {
|
||||
sPSV(buttonSels[si], buttonVals[si], "data-preset");
|
||||
}
|
||||
gId("macros").appendChild(buttonBlock);
|
||||
var td = document.createElement('td'); // TABLE DEFINITION.
|
||||
td = tr.insertCell(0);
|
||||
td.innerHTML = `Button ${i}:`;
|
||||
td = tr.insertCell(1);
|
||||
td.innerHTML = `<input name="MP${b}" type="number" class="s" min="0" max="250" value="${p}" required>`;
|
||||
td = tr.insertCell(2);
|
||||
td.innerHTML = `<input name="ML${b}" type="number" class="s" min="0" max="250" value="${l}" required>`;
|
||||
td = tr.insertCell(3);
|
||||
td.innerHTML = `<input name="MD${b}" type="number" class="s" min="0" max="250" value="${d}" required>`;
|
||||
}
|
||||
function getLatLon() {
|
||||
if (!el) {
|
||||
@@ -373,14 +206,24 @@
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>Button Action Presets</h3>
|
||||
<div id="macros"></div>
|
||||
<table style="margin: 0 auto;" id="macros">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>push<br>switch</td>
|
||||
<td>short<br>on->off</td>
|
||||
<td>long<br>off->on</td>
|
||||
<td>double<br>N/A</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="https://kno.wled.ge/features/macros/#analog-button" target="_blank">Analog Button setup</a>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>Time-Controlled Presets</h3>
|
||||
<div style="display: inline-block">
|
||||
<table id="TMT" style="min-width:330px;"></table>
|
||||
<button type="button" onclick="addTimerRow()">Add Timer</button>
|
||||
<table id="TMT" style="min-width:330px;"></table>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
@@ -99,9 +99,9 @@ input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
select {
|
||||
font-size: medium;
|
||||
margin: 2px;
|
||||
}
|
||||
font-size: medium;
|
||||
margin: 2px;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
@@ -20,4 +20,15 @@ Macros for generating a "dynamic array", a static array of objects declared in d
|
||||
#define DYNARRAY_END(array_name) array_name##_end
|
||||
#define DYNARRAY_LENGTH(array_name) (&DYNARRAY_END(array_name)[0] - &DYNARRAY_BEGIN(array_name)[0])
|
||||
|
||||
#ifdef ESP8266
|
||||
// ESP8266 linker script cannot be extended with a unique section for dynamic arrays.
|
||||
// We instead pack them in the ".dtors" section, as it's sorted and uploaded to the flash
|
||||
// (but will never be used in the embedded system)
|
||||
#define DYNARRAY_SECTION ".dtors"
|
||||
|
||||
#else /* ESP8266 */
|
||||
|
||||
// Use a unique named section; the linker script must be extended to ensure it's correctly placed.
|
||||
#define DYNARRAY_SECTION ".dynarray"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,13 +21,6 @@ static void handleDDPPacket(e131_packet_t* p) {
|
||||
static bool ddpSeenPush = false; // have we seen a push yet?
|
||||
int lastPushSeq = e131LastSequenceNumber[0];
|
||||
|
||||
// reject unsupported color data types (only RGB and RGBW are supported)
|
||||
uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
|
||||
if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;
|
||||
|
||||
// reject status and config packets (not implemented)
|
||||
if (p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
|
||||
|
||||
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
|
||||
if (e131SkipOutOfSequence && lastPushSeq) {
|
||||
int sn = p->sequenceNum & 0xF;
|
||||
@@ -48,7 +41,7 @@ static void handleDDPPacket(e131_packet_t* p) {
|
||||
unsigned stop = start + dataLen / ddpChannelsPerLed;
|
||||
uint8_t* data = p->data;
|
||||
unsigned c = 0;
|
||||
if (p->flags & DDP_FLAGS_TIME) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
|
||||
if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
|
||||
|
||||
unsigned numLeds = stop - start; // stop >= start is guaranteed
|
||||
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
|
||||
@@ -66,7 +59,7 @@ static void handleDDPPacket(e131_packet_t* p) {
|
||||
}
|
||||
}
|
||||
|
||||
bool push = p->flags & DDP_FLAGS_PUSH;
|
||||
bool push = p->flags & DDP_PUSH_FLAG;
|
||||
ddpSeenPush |= push;
|
||||
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
|
||||
e131NewData = true;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#define WLED_FCN_DECLARE_H
|
||||
#include <dynarray.h>
|
||||
|
||||
#include "colors.h"
|
||||
|
||||
/*
|
||||
* All globally accessible functions are declared here
|
||||
*/
|
||||
@@ -176,6 +174,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
|
||||
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
|
||||
void serializeInfo(JsonObject root);
|
||||
void serializeModeNames(JsonArray arr);
|
||||
void serializeModeData(JsonArray fxdata);
|
||||
void serializePins(JsonObject root);
|
||||
void serveJson(AsyncWebServerRequest* request);
|
||||
#ifdef WLED_ENABLE_JSONLIVE
|
||||
@@ -222,34 +221,6 @@ void checkTimers();
|
||||
void calculateSunriseAndSunset();
|
||||
void setTimeFromAPI(uint32_t timein);
|
||||
|
||||
const uint8_t TH_SUNRISE = 255;
|
||||
const uint8_t TH_SUNSET = 254;
|
||||
|
||||
struct Timer {
|
||||
uint8_t preset;
|
||||
uint8_t hour;
|
||||
int8_t minute;
|
||||
uint8_t weekdays;
|
||||
uint8_t monthStart;
|
||||
uint8_t monthEnd;
|
||||
uint8_t dayStart;
|
||||
uint8_t dayEnd;
|
||||
inline bool isEnabled() const { return (weekdays & 0x01) && (preset != 0); }
|
||||
inline bool isSunrise() const { return hour == TH_SUNRISE; }
|
||||
inline bool isSunset() const { return hour == TH_SUNSET; }
|
||||
inline bool isRegular() const { return hour < TH_SUNSET; }
|
||||
Timer() : preset(0), hour(0), minute(0), weekdays(255), monthStart(1), monthEnd(12), dayStart(1), dayEnd(31) {}
|
||||
Timer(uint8_t p, uint8_t h, int8_t m, uint8_t wd, uint8_t ms = 1, uint8_t me = 12, uint8_t ds = 1, uint8_t de = 31)
|
||||
: preset(p), hour(h), minute(m), weekdays(wd), monthStart(ms), monthEnd(me), dayStart(ds), dayEnd(de) {}
|
||||
};
|
||||
|
||||
void addTimer(uint8_t preset, uint8_t hour, int8_t minute, uint8_t weekdays,
|
||||
uint8_t monthStart = 1, uint8_t monthEnd = 12, uint8_t dayStart = 1, uint8_t dayEnd = 31);
|
||||
void removeTimer(size_t index);
|
||||
void clearTimers();
|
||||
size_t getTimerCount();
|
||||
void compactTimers();
|
||||
|
||||
//overlay.cpp
|
||||
void handleOverlayDraw();
|
||||
// void _overlayAnalogCountdown(); // local function, only used in overlay.cpp
|
||||
@@ -306,7 +277,6 @@ void fillMAC2Str(char *str, const uint8_t *mac);
|
||||
void fillStr2MAC(uint8_t *mac, const char *str);
|
||||
int findWiFi(bool doScan = false);
|
||||
bool isWiFiConfigured();
|
||||
void installIPv6RABlocker();
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
|
||||
//um_manager.cpp
|
||||
@@ -439,11 +409,8 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val);
|
||||
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val);
|
||||
size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
|
||||
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
|
||||
void getWLEDhostname(char* hostname, size_t maxLen, bool preferMDNS=false); // maxLen = hostname buffer size including \0; if preferMDNSname=true, use mdns name (sanitized)
|
||||
void prepareHostname(char* hostname, size_t maxLen); // legacy function - not recommended for new code
|
||||
void prepareHostname(char* hostname);
|
||||
[[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen);
|
||||
uint32_t utf8_decode(const char *s, uint8_t *len);
|
||||
size_t utf8_strlen(const char *s);
|
||||
bool requestJSONBufferLock(uint8_t moduleID=JSON_LOCK_UNKNOWN);
|
||||
void releaseJSONBufferLock();
|
||||
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
|
||||
@@ -453,13 +420,9 @@ void checkSettingsPIN(const char *pin);
|
||||
uint16_t crc16(const unsigned char* data_p, size_t length);
|
||||
String computeSHA1(const String& input);
|
||||
String getDeviceId();
|
||||
uint16_t beat88(uint16_t beats_per_minute_88, uint32_t timebase = 0);
|
||||
uint16_t beat16(uint16_t beats_per_minute, uint32_t timebase = 0);
|
||||
uint8_t beat8(uint16_t beats_per_minute, uint32_t timebase = 0);
|
||||
uint16_t beatsin88_t(uint16_t beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint16_t beatsin16_t(uint16_t beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint8_t beatsin8_t(uint16_t beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
|
||||
|
||||
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
|
||||
um_data_t* simulateSound(uint8_t simulationId);
|
||||
void enumerateLedmaps();
|
||||
[[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos);
|
||||
@@ -552,9 +515,6 @@ class JSONBufferGuard {
|
||||
};
|
||||
|
||||
//wled_math.cpp
|
||||
#define sin_t sin_approx
|
||||
#define cos_t cos_approx
|
||||
#define tan_t tan_approx
|
||||
//float cos_t(float phi); // use float math
|
||||
//float sin_t(float phi);
|
||||
//float tan_t(float x);
|
||||
@@ -572,6 +532,9 @@ template <typename T> T atan_t(T x);
|
||||
float floor_t(float x);
|
||||
float fmod_t(float num, float denom);
|
||||
uint32_t sqrt32_bw(uint32_t x);
|
||||
#define sin_t sin_approx
|
||||
#define cos_t cos_approx
|
||||
#define tan_t tan_approx
|
||||
|
||||
/*
|
||||
#include <math.h> // standard math functions. use a lot of flash
|
||||
@@ -584,7 +547,6 @@ uint32_t sqrt32_bw(uint32_t x);
|
||||
#define fmod_t fmodf
|
||||
#define floor_t floorf
|
||||
*/
|
||||
|
||||
//wled_serial.cpp
|
||||
void handleSerial();
|
||||
void updateBaudRate(uint32_t rate);
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* FontManager class for managing wbf format font loading, caching, and rendering
|
||||
* Supports up to 5 fonts (0-4) from flash or file system
|
||||
* Caches glyphs in segment data for fast access during rendering
|
||||
* Note: fontmanager relies on the Segment class for storing cached font data as well as rendering
|
||||
*
|
||||
* (c) 2026 by @dedehai
|
||||
*/
|
||||
|
||||
#include "wled.h"
|
||||
#include "fontmanager.h"
|
||||
|
||||
#include "src/font/font_tom_thumb_6px.h"
|
||||
#include "src/font/font_TinyUnicode_8px.h"
|
||||
#include "src/font/font_5x12.h"
|
||||
#include "src/font/console_font_6x8.h"
|
||||
#include "src/font/c64esque_9px.h"
|
||||
|
||||
// get metadata pointer
|
||||
SegmentFontMetadata* FontManager::getMetadata() {
|
||||
return (SegmentFontMetadata*)_segment->data;
|
||||
}
|
||||
|
||||
void FontManager::updateFontBase() {
|
||||
SegmentFontMetadata* meta = getMetadata();
|
||||
// font data (header + glyph bitmaps) starts after metadata + registry
|
||||
_fontBase = _segment->data + sizeof(SegmentFontMetadata) + (meta->glyphCount * sizeof(GlyphEntry));
|
||||
}
|
||||
|
||||
// scan file system for .wbf font files, if scanAll is set, also updates availableFonts
|
||||
void FontManager::getFontFileName(uint8_t fontNum, char* buffer, bool scanAll) {
|
||||
SegmentFontMetadata* meta = getMetadata();
|
||||
if (scanAll) {
|
||||
if (!meta) return;
|
||||
meta->availableFonts = 0; // reset available fonts before scanning
|
||||
}
|
||||
buffer[0] = '\0'; // invalidate
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
while (!BusManager::canAllShow()) yield(); // accessing FS causes glitches due to RMT issue on C3 TODO: remove this when fixed
|
||||
#endif
|
||||
File rootdir = WLED_FS.open("/", "r");
|
||||
File rootfile = rootdir.openNextFile();
|
||||
uint8_t i = 0;
|
||||
while (rootfile && i < MAX_FONTS) {
|
||||
String name = rootfile.name();
|
||||
if (name.endsWith(F(".wbf"))) {
|
||||
if (i == fontNum) {
|
||||
if (name.charAt(0) != '/') name = "/" + name; // need to add leading slash
|
||||
strncpy(buffer, name.c_str(), FONT_NAME_BUFFER_SIZE - 1);
|
||||
buffer[FONT_NAME_BUFFER_SIZE - 1] = '\0';
|
||||
if (!scanAll) {
|
||||
rootfile.close();
|
||||
rootdir.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (scanAll)
|
||||
meta->availableFonts |= (1 << i); // note: openNextFile() usually opens them in alphaetical order but there is no guarantee
|
||||
i++;
|
||||
}
|
||||
rootfile = rootdir.openNextFile();
|
||||
}
|
||||
rootfile.close();
|
||||
rootdir.close();
|
||||
}
|
||||
|
||||
// scan file system for available fonts
|
||||
void FontManager::scanAvailableFonts() {
|
||||
char buffer[FONT_NAME_BUFFER_SIZE];
|
||||
getFontFileName(0, buffer, true); // scan all fonts to update availableFonts in metadata
|
||||
}
|
||||
|
||||
// load font by number and prepare/validate font cache, must be called before using any other FontManager functions, must not use the font if this function returns false!
|
||||
bool FontManager::loadFont(uint8_t fontNum, const char* text, bool useFile) {
|
||||
_segment->allocateData(sizeof(SegmentFontMetadata)); // make sure at least metadata is available, sets to 0 if segment.call==0, does nothing if already allocated
|
||||
SegmentFontMetadata* meta = getMetadata();
|
||||
if (!meta)
|
||||
return false; // can not continue if no segment data
|
||||
|
||||
_fontNum = fontNum; // store font to be used
|
||||
_useFileFont = useFile;
|
||||
uint8_t fontRequested = fontNum | (useFile ? 0x80 : 0x00);
|
||||
|
||||
if (useFile) {
|
||||
if (meta->lastFontNum != fontRequested) {
|
||||
scanAvailableFonts(); // scan filesystem again if file font changes
|
||||
}
|
||||
// determine which font to actually use (with fallback): check if requested font is available - find first available font if not
|
||||
if (!(meta->availableFonts & (1 << fontNum))) {
|
||||
_fontNum = 0xFF; // invalidate
|
||||
for (int i = 0; i < MAX_FONTS; i++) {
|
||||
if (meta->availableFonts & (1 << i)) {
|
||||
_fontNum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_fontNum == 0xFF) {
|
||||
_fontNum = fontNum; // no custom fonts available, use flash font
|
||||
_useFileFont = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
meta->lastFontNum = fontRequested; // store last requested file font (only used to check file fonts)
|
||||
|
||||
uint8_t fontToUse = _fontNum | (_useFileFont ? 0x80 : 0x00); // highest bit indicates file vs flash
|
||||
if (fontToUse != meta->cachedFontNum) {
|
||||
meta->glyphCount = 0; // invalidate cache
|
||||
}
|
||||
cacheGlyphs(text); // prepare cache with needed glyphs
|
||||
meta = getMetadata(); // reload metadata after potential cache rebuild
|
||||
if (meta->glyphCount == 0) {
|
||||
errorFlag = ERR_NOT_IMPL; // TODO: need a better error code if more codes are added
|
||||
return false; // cache build failed (invalid font file)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if all glyphs needed for the given text are present in cache, rebuild cache if not
|
||||
void FontManager::cacheGlyphs(const char* text) {
|
||||
if (!text) return;
|
||||
|
||||
SegmentFontMetadata* meta = getMetadata(); // loadFont ensures pointer is valid
|
||||
// if glyphCount is 0, cache is empty - rebuild
|
||||
if (meta->glyphCount == 0) {
|
||||
rebuildCache(text);
|
||||
return; // cache built, we are done
|
||||
}
|
||||
|
||||
// if there is a cached font, update the pointers
|
||||
updateFontBase();
|
||||
FontHeader* hdr = reinterpret_cast<FontHeader*>(_fontBase);
|
||||
|
||||
// check if all needed glyphs for the text are present in cache
|
||||
uint8_t neededCodes[MAX_CACHED_GLYPHS];
|
||||
uint8_t neededCount = collectNeededCodes(text, hdr, neededCodes);
|
||||
|
||||
GlyphEntry* registry = (GlyphEntry*)(_segment->data + sizeof(SegmentFontMetadata));
|
||||
for (uint8_t k = 0; k < neededCount; k++) {
|
||||
// look up glyph in registry
|
||||
bool found = false;
|
||||
for (uint8_t j = 0; j < meta->glyphCount; j++) {
|
||||
if (registry[j].code == neededCodes[k]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rebuildCache(text); // missing glyph - rebuild cache
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FontManager::rebuildCache(const char* text) {
|
||||
if (!text) return;
|
||||
// preserve metadata (function is only called if segment data is allocated so no null check needed)
|
||||
SegmentFontMetadata savedMeta;
|
||||
SegmentFontMetadata* meta = getMetadata();
|
||||
meta->glyphCount = 0; // invalidates cached font
|
||||
memcpy(&savedMeta, meta, sizeof(SegmentFontMetadata));
|
||||
|
||||
File file;
|
||||
if (_useFileFont) {
|
||||
// build filename from font number
|
||||
char fileName[FONT_NAME_BUFFER_SIZE];
|
||||
getFontFileName(_fontNum, fileName);
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
while (!BusManager::canAllShow()) yield(); // accessing FS causes glitches due to RMT issue on C3 TODO: remove this when fixed
|
||||
#endif
|
||||
file = WLED_FS.open(fileName, "r");
|
||||
|
||||
// fallback logic - try other available fonts
|
||||
if (!file) {
|
||||
for (int i = 0; i < MAX_FONTS; i++) {
|
||||
if (meta->availableFonts & (1 << i)) {
|
||||
getFontFileName(i, fileName);
|
||||
file = WLED_FS.open(fileName, "r");
|
||||
if (file) {
|
||||
_fontNum = i; // update to fallback font
|
||||
//savedMeta.cachedFontNum = i | 0x80; // set highest bit to indicate file font
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
_useFileFont = false; // fallback straight to flash font
|
||||
//savedMeta.cachedFontNum = _fontNum;
|
||||
}
|
||||
}
|
||||
|
||||
savedMeta.cachedFontNum = _fontNum | (_useFileFont ? 0x80 : 0x00); // set highest bit to indicate file font
|
||||
|
||||
// determine flash font to use (not used if using file font)
|
||||
const uint8_t* flashFont;
|
||||
switch (_fontNum) {
|
||||
default:
|
||||
case 0: flashFont = font_tom_thumb_6px; break;
|
||||
case 1: flashFont = font_TinyUnicode_8px; break;
|
||||
case 2: flashFont = console_font_6x8; break;
|
||||
case 3: flashFont = c64esque_9px; break;
|
||||
case 4: flashFont = font_5x12; break;
|
||||
}
|
||||
|
||||
// read wbf font header
|
||||
FontHeader hdr;
|
||||
if (file) {
|
||||
if (file.read((uint8_t*)&hdr, FONT_HEADER_SIZE) != FONT_HEADER_SIZE) { file.close(); return; } // header incomplete
|
||||
if (hdr.magic != 0x57) { file.close(); return; } // invalid header
|
||||
if (hdr.last < hdr.first) { file.close(); return; } // Invalid header
|
||||
} else {
|
||||
memcpy_P(&hdr, flashFont, FONT_HEADER_SIZE); // assumes built in fonts are in a valid, compatible format
|
||||
}
|
||||
|
||||
// collect needed glyphs
|
||||
uint8_t neededCodes[MAX_CACHED_GLYPHS];
|
||||
uint8_t neededCount = collectNeededCodes(text, &hdr, neededCodes);
|
||||
uint32_t numGlyphs = hdr.last - hdr.first + 1;
|
||||
uint8_t widthTable[numGlyphs];
|
||||
|
||||
// read width table
|
||||
if (hdr.flags & 0x01) {
|
||||
if (file) {
|
||||
if (file.read(widthTable, numGlyphs) != numGlyphs) { file.close(); return; } // width table starts directly after header if used
|
||||
} else {
|
||||
memcpy_P(widthTable, flashFont + FONT_HEADER_SIZE, numGlyphs); // assumes built in fonts are in a valid, compatible format
|
||||
}
|
||||
} else {
|
||||
for (uint32_t k = 0; k < numGlyphs; k++) {
|
||||
widthTable[k] = hdr.width; // fixed width, fill with given width from header
|
||||
}
|
||||
}
|
||||
|
||||
// calculate total size for cache: metadata + registry + header + bitmaps
|
||||
size_t ramFontSize = sizeof(SegmentFontMetadata) + (neededCount * sizeof(GlyphEntry)) + FONT_HEADER_SIZE;
|
||||
|
||||
for (uint8_t k = 0; k < neededCount; k++) {
|
||||
uint8_t code = neededCodes[k];
|
||||
if (code < numGlyphs) {
|
||||
uint16_t bits = widthTable[code] * hdr.height;
|
||||
ramFontSize += (bits + 7) / 8; // add bitmap size for each needed glyph
|
||||
}
|
||||
}
|
||||
|
||||
if (!_segment->allocateData(ramFontSize)) {
|
||||
if (file) file.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// write metadata
|
||||
meta = getMetadata(); // get pointer again in case segment was reallocated
|
||||
memcpy(meta, &savedMeta, sizeof(SegmentFontMetadata));
|
||||
meta->glyphCount = neededCount; // glyph count is used to determine if cache is valid. If file is corrupted, ram cache is still large enough to not cause crashes
|
||||
|
||||
uint8_t* dataptr = _segment->data + sizeof(SegmentFontMetadata);
|
||||
|
||||
// write registry (GlyphEntry array)
|
||||
GlyphEntry* registry = (GlyphEntry*)dataptr;
|
||||
for (uint8_t k = 0; k < neededCount; k++) {
|
||||
uint8_t code = neededCodes[k];
|
||||
if (code >= numGlyphs) continue; // skip invalid codes (safety check if anything is corrupted)
|
||||
registry[k].code = code;
|
||||
registry[k].width = widthTable[code];
|
||||
registry[k].height = hdr.height;
|
||||
}
|
||||
dataptr += neededCount * sizeof(GlyphEntry);
|
||||
|
||||
// write font header
|
||||
memcpy(dataptr, &hdr, FONT_HEADER_SIZE);
|
||||
dataptr += FONT_HEADER_SIZE;
|
||||
|
||||
// write bitmap data to cache in registry order
|
||||
uint32_t dataStart = FONT_HEADER_SIZE + ((hdr.flags & 0x01) ? numGlyphs : 0); // bitmap data in wbf font starts after header and width table (if used)
|
||||
for (uint8_t k = 0; k < neededCount; k++) {
|
||||
uint8_t glyphIdx = neededCodes[k]; // neededCodes contais index of the glyph in the font, not the raw unicode value
|
||||
uint16_t bits = widthTable[glyphIdx] * hdr.height;
|
||||
uint16_t bytes = (bits + 7) / 8;
|
||||
// calculate file offset
|
||||
uint32_t offset = dataStart;
|
||||
for (uint8_t j = 0; j < glyphIdx; j++) {
|
||||
uint16_t b = widthTable[j] * hdr.height;
|
||||
offset += (b + 7) / 8;
|
||||
}
|
||||
// read from file or flash
|
||||
if (file) {
|
||||
file.seek(offset);
|
||||
file.read(dataptr, bytes);
|
||||
} else {
|
||||
memcpy_P(dataptr, flashFont + offset, bytes);
|
||||
}
|
||||
dataptr += bytes;
|
||||
}
|
||||
|
||||
if (file) file.close();
|
||||
updateFontBase(); // set pointer to cached header/bitmaps
|
||||
}
|
||||
|
||||
// glyph index calculator
|
||||
int32_t FontManager::getGlyphIndex(uint32_t unicode, FontHeader* hdr) {
|
||||
if (unicode <= LAST_ASCII_CHAR) {
|
||||
if (unicode >= hdr->first && unicode <= hdr->last) return unicode - hdr->first;
|
||||
} else if (hdr->firstUnicode > 0 && unicode >= hdr->firstUnicode) {
|
||||
uint32_t adjusted = unicode - hdr->firstUnicode + LAST_ASCII_CHAR + 1;
|
||||
if (adjusted >= hdr->first && adjusted <= hdr->last) return adjusted - hdr->first;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get glyph width
|
||||
uint8_t FontManager::getGlyphWidth(uint32_t unicode) {
|
||||
FontHeader* hdr = reinterpret_cast<FontHeader*>(_fontBase);
|
||||
int32_t idx = getGlyphIndex(unicode, hdr);
|
||||
if (idx < 0) return 0;
|
||||
|
||||
SegmentFontMetadata* meta = (SegmentFontMetadata*)_segment->data;
|
||||
GlyphEntry* registry = (GlyphEntry*)(_segment->data + sizeof(SegmentFontMetadata));
|
||||
|
||||
for (uint8_t k = 0; k < meta->glyphCount; k++) {
|
||||
if (registry[k].code == idx) {
|
||||
return registry[k].width;
|
||||
}
|
||||
}
|
||||
return 0; // Not found in cache
|
||||
}
|
||||
|
||||
// Get glyph bitmap
|
||||
uint8_t* FontManager::getGlyphBitmap(uint32_t unicode, uint8_t& outWidth, uint8_t& outHeight) {
|
||||
FontHeader* hdr = reinterpret_cast<FontHeader*>(_fontBase);
|
||||
int32_t idx = getGlyphIndex(unicode, hdr);
|
||||
if (idx < 0) return nullptr;
|
||||
SegmentFontMetadata* meta = (SegmentFontMetadata*)_segment->data;
|
||||
GlyphEntry* registry = (GlyphEntry*)(_segment->data + sizeof(SegmentFontMetadata));
|
||||
|
||||
uint32_t bitmapOffset = 0;
|
||||
for (uint8_t k = 0; k < meta->glyphCount; k++) {
|
||||
if (registry[k].code == idx) {
|
||||
outWidth = registry[k].width;
|
||||
outHeight = registry[k].height;
|
||||
return _fontBase + FONT_HEADER_SIZE + bitmapOffset;
|
||||
}
|
||||
// Accumulate offset to next glyph
|
||||
uint16_t bits = registry[k].width * registry[k].height;
|
||||
bitmapOffset += (bits + 7) / 8;
|
||||
}
|
||||
return nullptr; // Glyph not found in cache
|
||||
}
|
||||
|
||||
uint8_t FontManager::collectNeededCodes(const char* text, FontHeader* hdr, uint8_t* outCodes) {
|
||||
uint8_t count = 0;
|
||||
// add numbers to cache if needed (for clock use without constant re-caching)
|
||||
if (_cacheNumbers) {
|
||||
static const char s_nums[] PROGMEM = "0123456789:. ";
|
||||
for (const char* p = s_nums; *p && count < MAX_CACHED_GLYPHS; p++) {
|
||||
int32_t idx = getGlyphIndex(*p, hdr);
|
||||
if (idx >= 0 && idx < 256) {
|
||||
outCodes[count++] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
// parse text
|
||||
size_t i = 0, len = strlen(text);
|
||||
while (i < len && count < MAX_CACHED_GLYPHS) {
|
||||
uint8_t charLen;
|
||||
uint32_t unicode = utf8_decode(&text[i], &charLen);
|
||||
if (!charLen) break; // invalid input, stop processing
|
||||
i += charLen;
|
||||
int32_t idx = getGlyphIndex(unicode, hdr);
|
||||
if (idx < 0) {
|
||||
idx = getGlyphIndex('?', hdr);
|
||||
}
|
||||
if (idx >= 0 && idx < 256) {
|
||||
// add if unique
|
||||
bool exists = false;
|
||||
for (uint8_t k = 0; k < count; k++) {
|
||||
if (outCodes[k] == idx) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) outCodes[count++] = idx;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void FontManager::drawCharacter(uint32_t unicode, int16_t x, int16_t y, uint32_t color, uint32_t col2, int8_t rotate) {
|
||||
uint8_t w, h;
|
||||
const uint8_t* bitmap = getGlyphBitmap(unicode, w, h);
|
||||
if (!bitmap || w == 0) return;
|
||||
CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE;
|
||||
uint16_t bitIndex = 0;
|
||||
for (int row = 0; row < h; row++) {
|
||||
CRGBW c = ColorFromPalette(grad, (row + 1) * 255 / h, 255, LINEARBLEND_NOWRAP);
|
||||
for (int col = 0; col < w; col++) {
|
||||
uint16_t bytePos = bitIndex >> 3;
|
||||
uint8_t bitPos = 7 - (bitIndex & 7);
|
||||
uint8_t byteVal = bitmap[bytePos];
|
||||
if ((byteVal >> bitPos) & 1) {
|
||||
int x0, y0;
|
||||
switch (rotate) {
|
||||
case -1: x0 = x + row; y0 = y + col; break; // 90° CW
|
||||
case 1: x0 = x + (h-1) - row; y0 = y + (w-1) - col; break; // 90° CCW
|
||||
case -2:
|
||||
case 2: x0 = x + (w-1) - col; y0 = y + (h-1) - row; break;
|
||||
default: x0 = x + col; y0 = y + row; break;
|
||||
}
|
||||
_segment->setPixelColorXY(x0, y0, c.color32); // bounds checking is done in setPixelColorXY
|
||||
}
|
||||
bitIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
#pragma once
|
||||
/*
|
||||
* FontManager class for managing wbf format font loading, caching, and rendering
|
||||
* Supports up to 5 fonts (0-4) from flash or file system
|
||||
* Caches glyphs in segment data for fast access during rendering
|
||||
*
|
||||
* (c) 2026 by @dedehai
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
class Segment; // forward declaration
|
||||
|
||||
#define LAST_ASCII_CHAR 127 // last standard ASCII char, higher chars are mapped to unicode offset set in font header and accessed via unicode values, not direct index, see getGlyphIndex()
|
||||
#define FONT_HEADER_SIZE 12
|
||||
/**
|
||||
* Font format:
|
||||
*
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height
|
||||
* [2] Fixed/max glyph width
|
||||
* [3] Spacing between chars
|
||||
* [4] Flags: (0x01 = variable width)
|
||||
* [5] First Char
|
||||
* [6] Last Char
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian)
|
||||
*
|
||||
* Followed by:
|
||||
* - Width table (if variable width): [first..last] byte array omitted for fixed width fonts i.e. glyphs start after header
|
||||
* - Bitmap data: bit-packed glyphs - top left to bottom right, row by row, MSB first, see src/font files for example
|
||||
*/
|
||||
|
||||
// Glyph entry in RAM cache
|
||||
struct GlyphEntry {
|
||||
uint8_t code; // Glyph index (0-255)
|
||||
uint8_t width; // Width in pixels
|
||||
uint8_t height; // Height in pixels
|
||||
uint8_t reserved; // Padding to keep FontHeader 4-byte aligned
|
||||
};
|
||||
|
||||
// Segment metadata (stored BEFORE the font data in segment data)
|
||||
struct SegmentFontMetadata {
|
||||
uint8_t availableFonts; // Bitflags for available fonts on FS: set to 1 << fontNum if font is available in FS (0-4)
|
||||
uint8_t cachedFontNum; // Currently cached font (0-4, 0xFF = none, highest bit set = file font)
|
||||
uint8_t lastFontNum; // font number requested in last call
|
||||
uint8_t glyphCount; // Number of glyphs cached
|
||||
};
|
||||
|
||||
// Memory layout of cached font in segment data:
|
||||
// [SegmentFontMetadata] - 4 bytes
|
||||
// [GlyphEntry array]
|
||||
// [12-byte font header] - copy of the relevant font header data
|
||||
// [Bitmap data] - sequential, matches registry order
|
||||
|
||||
static constexpr uint8_t MAX_CACHED_GLYPHS = 64; // max segment string length is 64 chars so this is absolute worst case
|
||||
static constexpr uint8_t MAX_FONTS = 5; // scrolli text supports font numbers 0-4
|
||||
static constexpr size_t FONT_NAME_BUFFER_SIZE = 64; // font names
|
||||
|
||||
// font header, identical to wbf header, size must be FONT_HEADER_SIZE
|
||||
struct FontHeader {
|
||||
uint8_t magic; // should be 'W' (0x57)
|
||||
uint8_t height; // TODO: should we use the padding bytes and store a full copy of the header? might make copying the header easier?
|
||||
uint8_t width;
|
||||
uint8_t spacing;
|
||||
uint8_t flags;
|
||||
uint8_t first;
|
||||
uint8_t last;
|
||||
uint8_t reserved; // should be 0x00
|
||||
uint32_t firstUnicode;
|
||||
};
|
||||
static_assert(sizeof(FontHeader) == FONT_HEADER_SIZE, "FontHeader size must be exactly FONT_HEADER_SIZE bytes");
|
||||
|
||||
class FontManager {
|
||||
public:
|
||||
FontManager(Segment* seg) :
|
||||
_segment(seg),
|
||||
_fontNum(0),
|
||||
_useFileFont(false),
|
||||
_cacheNumbers(false),
|
||||
_fontBase(nullptr) {}
|
||||
|
||||
bool loadFont(uint8_t fontNum, const char* text, bool useFile);
|
||||
void cacheNumbers(bool cache) { _cacheNumbers = cache; }
|
||||
void cacheGlyphs(const char* text);
|
||||
|
||||
// Get dimensions (use cached header)
|
||||
inline uint8_t getFontHeight() { return reinterpret_cast<FontHeader*>(_fontBase)->height; }
|
||||
inline uint8_t getFontWidth() { return reinterpret_cast<FontHeader*>(_fontBase)->width; }
|
||||
inline uint8_t getFontSpacing() { return reinterpret_cast<FontHeader*>(_fontBase)->spacing; }
|
||||
uint8_t getGlyphWidth(uint32_t unicode);
|
||||
|
||||
// Rendering
|
||||
void drawCharacter(uint32_t unicode, int16_t x, int16_t y, uint32_t color, uint32_t col2, int8_t rotate);
|
||||
|
||||
private:
|
||||
Segment* _segment;
|
||||
uint8_t _fontNum; // Font number (0-4)
|
||||
bool _useFileFont; // true = file, false = flash
|
||||
bool _cacheNumbers;
|
||||
uint8_t* _fontBase; // pointer to start of font data (header + bitmaps) in segment data
|
||||
|
||||
// get metadata pointer
|
||||
SegmentFontMetadata* getMetadata();
|
||||
|
||||
void updateFontBase();
|
||||
|
||||
uint8_t* getGlyphBitmap(uint32_t unicode, uint8_t& outWidth, uint8_t& outHeight);
|
||||
|
||||
// Glyph index calculation (pure function, inline for speed)
|
||||
int32_t getGlyphIndex(uint32_t unicode, FontHeader* hdr);
|
||||
|
||||
// File font management
|
||||
void getFontFileName(uint8_t fontNum, char* buffer, bool scanAll = false);
|
||||
void scanAvailableFonts();
|
||||
void rebuildCache(const char* text);
|
||||
uint8_t collectNeededCodes(const char* text, FontHeader* hdr, uint8_t* outCodes);
|
||||
};
|
||||
@@ -28,10 +28,6 @@ int fileReadCallback(void) {
|
||||
}
|
||||
|
||||
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
unsigned t0 = millis();
|
||||
while (strip.isUpdating() && (millis() - t0 < 15)) yield(); // be nice, but not too nice. Waits up to 15ms to avoid glitches
|
||||
#endif
|
||||
return file.read((uint8_t*)buffer, numberOfBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#ifndef WLED_DISABLE_INFRARED
|
||||
#include "ir_codes.h"
|
||||
#include "colors.h"
|
||||
|
||||
/*
|
||||
* Infrared sensor support for several generic RGB remotes and custom JSON remote
|
||||
@@ -129,18 +128,22 @@ static void changeEffectSpeed(int8_t amount)
|
||||
}
|
||||
} else { // if Effect == "solid Color", change the hue of the primary color
|
||||
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
|
||||
CRGBW newcolor = CRGBW(sseg.colors[0]);
|
||||
newcolor.adjust_hue(amount);
|
||||
newcolor.w = W(sseg.colors[0]);
|
||||
CRGB fastled_col = CRGB(sseg.colors[0]);
|
||||
CHSV prim_hsv = rgb2hsv(fastled_col);
|
||||
int16_t new_val = (int16_t)prim_hsv.h + amount;
|
||||
if (new_val > 255) new_val -= 255; // roll-over if bigger than 255
|
||||
if (new_val < 0) new_val += 255; // roll-over if smaller than 0
|
||||
prim_hsv.h = (byte)new_val;
|
||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||
if (irApplyToAllSelected) {
|
||||
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
|
||||
Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive() || !seg.isSelected()) continue;
|
||||
seg.colors[0] = newcolor.color32;
|
||||
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
|
||||
}
|
||||
setValuesFromFirstSelectedSeg();
|
||||
} else {
|
||||
strip.getMainSegment().colors[0] = newcolor.color32;
|
||||
strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
|
||||
setValuesFromMainSeg();
|
||||
}
|
||||
}
|
||||
@@ -169,21 +172,20 @@ static void changeEffectIntensity(int8_t amount)
|
||||
}
|
||||
} else { // if Effect == "solid Color", change the saturation of the primary color
|
||||
Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment();
|
||||
|
||||
CHSV32 prim_hsv = CRGBW(sseg.colors[0]);
|
||||
int32_t new_val = (int32_t)prim_hsv.s + amount;
|
||||
CRGB fastled_col = CRGB(sseg.colors[0]);
|
||||
CHSV prim_hsv = rgb2hsv(fastled_col);
|
||||
int16_t new_val = (int16_t) prim_hsv.s + amount;
|
||||
prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255
|
||||
CRGBW newcolor = prim_hsv;
|
||||
newcolor.w = W(sseg.colors[0]);
|
||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||
if (irApplyToAllSelected) {
|
||||
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
|
||||
Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive() || !seg.isSelected()) continue;
|
||||
seg.colors[0] = newcolor;
|
||||
seg.colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
|
||||
}
|
||||
setValuesFromFirstSelectedSeg();
|
||||
} else {
|
||||
strip.getMainSegment().colors[0] = newcolor;
|
||||
strip.getMainSegment().colors[0] = RGBW32(fastled_col.red, fastled_col.green, fastled_col.blue, W(sseg.colors[0]));
|
||||
setValuesFromMainSeg();
|
||||
}
|
||||
}
|
||||
|
||||
103
wled00/json.cpp
103
wled00/json.cpp
@@ -1,5 +1,6 @@
|
||||
#include "wled.h"
|
||||
|
||||
|
||||
#define JSON_PATH_STATE 1
|
||||
#define JSON_PATH_INFO 2
|
||||
#define JSON_PATH_STATE_INFO 3
|
||||
@@ -306,7 +307,9 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0)
|
||||
seg.check2 = getBoolVal(elem["o2"], seg.check2);
|
||||
seg.check3 = getBoolVal(elem["o3"], seg.check3);
|
||||
|
||||
getVal(elem["bm"], seg.blendMode);
|
||||
uint8_t blend = seg.blendMode;
|
||||
getVal(elem["bm"], blend, 0, 15); // we can't pass reference to bitfield
|
||||
seg.blendMode = constrain(blend, 0, 15);
|
||||
|
||||
JsonArray iarr = elem[F("i")]; //set individual LEDs
|
||||
if (!iarr.isNull()) {
|
||||
@@ -761,7 +764,6 @@ void serializeInfo(JsonObject root)
|
||||
case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break;
|
||||
case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break;
|
||||
case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break;
|
||||
case REALTIME_MODE_DMX: root["lm"] = F("DMX"); break;
|
||||
}
|
||||
|
||||
root[F("lip")] = realtimeIP[0] == 0 ? "" : realtimeIP.toString();
|
||||
@@ -961,7 +963,7 @@ void serializePalettes(JsonObject root, int page)
|
||||
JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i));
|
||||
switch (i) {
|
||||
case 0: //default palette
|
||||
setPaletteColors(curPalette, PartyColors_gc22);
|
||||
setPaletteColors(curPalette, PartyColors_p);
|
||||
break;
|
||||
case 1: //random
|
||||
for (int j = 0; j < 4; j++) curPalette.add("r");
|
||||
@@ -1161,6 +1163,21 @@ void serializePins(JsonObject root)
|
||||
}
|
||||
}
|
||||
|
||||
// deserializes mode data string into JsonArray
|
||||
void serializeModeData(JsonArray fxdata)
|
||||
{
|
||||
char lineBuffer[256];
|
||||
for (size_t i = 0; i < strip.getModeCount(); i++) {
|
||||
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
|
||||
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
|
||||
if (lineBuffer[0] != 0) {
|
||||
char* dataPtr = strchr(lineBuffer,'@');
|
||||
if (dataPtr) fxdata.add(dataPtr+1);
|
||||
else fxdata.add("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deserializes mode names string into JsonArray
|
||||
// also removes effect data extensions (@...) from deserialised names
|
||||
void serializeModeNames(JsonArray arr)
|
||||
@@ -1177,78 +1194,6 @@ void serializeModeNames(JsonArray arr)
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a JSON-escaped string (with surrounding quotes) into dest[0..maxLen-1].
|
||||
// Returns bytes written, or 0 if the buffer was too small.
|
||||
static size_t writeJSONString(uint8_t* dest, size_t maxLen, const char* src) {
|
||||
size_t pos = 0;
|
||||
|
||||
auto emit = [&](char c) -> bool {
|
||||
if (pos >= maxLen) return false;
|
||||
dest[pos++] = (uint8_t)c;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!emit('"')) return 0;
|
||||
|
||||
for (const char* p = src; *p; ++p) {
|
||||
char esc = ARDUINOJSON_NAMESPACE::EscapeSequence::escapeChar(*p);
|
||||
if (esc) {
|
||||
if (!emit('\\') || !emit(esc)) return 0;
|
||||
} else {
|
||||
if (!emit(*p)) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!emit('"')) return 0;
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Writes ,"<escaped_src>" into dest[0..maxLen-1] (no null terminator).
|
||||
// Returns bytes written, or 0 if the buffer was too small.
|
||||
static size_t writeJSONStringElement(uint8_t* dest, size_t maxLen, const char* src) {
|
||||
if (maxLen == 0) return 0;
|
||||
dest[0] = ',';
|
||||
size_t n = writeJSONString(dest + 1, maxLen - 1, src);
|
||||
if (n == 0) return 0;
|
||||
return 1 + n;
|
||||
}
|
||||
|
||||
// Generate a streamed JSON response for the mode data
|
||||
// This uses sendChunked to send the reply in blocks based on how much fit in the outbound
|
||||
// packet buffer, minimizing the required state (ie. just the next index to send). This
|
||||
// allows us to send an arbitrarily large response without using any significant amount of
|
||||
// memory (so no worries about buffer limits).
|
||||
void respondModeData(AsyncWebServerRequest* request) {
|
||||
size_t fx_index = 0;
|
||||
request->sendChunked(FPSTR(CONTENT_TYPE_JSON),
|
||||
[fx_index](uint8_t* data, size_t len, size_t) mutable {
|
||||
size_t bytes_written = 0;
|
||||
char lineBuffer[256];
|
||||
while (fx_index < strip.getModeCount()) {
|
||||
strncpy_P(lineBuffer, strip.getModeData(fx_index), sizeof(lineBuffer)-1); // Copy to stack buffer for strchr
|
||||
if (lineBuffer[0] != 0) {
|
||||
lineBuffer[sizeof(lineBuffer)-1] = '\0'; // terminate string (only needed if strncpy filled the buffer)
|
||||
const char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one
|
||||
size_t mode_bytes = writeJSONStringElement(data, len, dataPtr ? dataPtr + 1 : "");
|
||||
if (mode_bytes == 0) break; // didn't fit; break loop and try again next packet
|
||||
if (fx_index == 0) *data = '[';
|
||||
data += mode_bytes;
|
||||
len -= mode_bytes;
|
||||
bytes_written += mode_bytes;
|
||||
}
|
||||
++fx_index;
|
||||
}
|
||||
|
||||
if ((fx_index == strip.getModeCount()) && (len >= 1)) {
|
||||
*data = ']';
|
||||
++bytes_written;
|
||||
++fx_index; // we're really done
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
});
|
||||
}
|
||||
|
||||
// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed)
|
||||
class LockedJsonResponse: public AsyncJsonResponse {
|
||||
bool _holding_lock;
|
||||
@@ -1276,7 +1221,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
|
||||
void serveJson(AsyncWebServerRequest* request)
|
||||
{
|
||||
enum class json_target {
|
||||
all, state, info, state_info, nodes, effects, palettes, networks, config, pins
|
||||
all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config, pins
|
||||
};
|
||||
json_target subJson = json_target::all;
|
||||
|
||||
@@ -1287,7 +1232,7 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes;
|
||||
else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects;
|
||||
else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes;
|
||||
else if (url.indexOf(F("fxda")) > 0) { respondModeData(request); return; }
|
||||
else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata;
|
||||
else if (url.indexOf(F("net")) > 0) subJson = json_target::networks;
|
||||
else if (url.indexOf(F("cfg")) > 0) subJson = json_target::config;
|
||||
else if (url.indexOf(F("pins")) > 0) subJson = json_target::pins;
|
||||
@@ -1312,7 +1257,7 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
}
|
||||
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
|
||||
// make sure you delete "response" if no "request->send(response);" is made
|
||||
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
|
||||
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::fxdata || subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
|
||||
|
||||
JsonVariant lDoc = response->getRoot();
|
||||
|
||||
@@ -1328,6 +1273,8 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
|
||||
case json_target::effects:
|
||||
serializeModeNames(lDoc); break;
|
||||
case json_target::fxdata:
|
||||
serializeModeData(lDoc); break;
|
||||
case json_target::networks:
|
||||
serializeNetworks(lDoc); break;
|
||||
case json_target::config:
|
||||
|
||||
@@ -360,35 +360,6 @@ bool isWiFiConfigured() {
|
||||
#define ARDUINO_EVENT_ETH_DISCONNECTED SYSTEM_EVENT_ETH_DISCONNECTED
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(LWIP_IPV6)
|
||||
#include "lwip/raw.h"
|
||||
#include "lwip/icmp6.h"
|
||||
// This is a terrible workaround for a terrible bug: on ESP32 platforms, unsolicited IPv6 router
|
||||
// advertisements will cause LwIP to overwrite the IPv4 DNS servers with the IPv6 DNS servers
|
||||
// mentioned in the RA packet. As a workaround, we just blackhole those packets using the raw
|
||||
// callback, since we don't yet support IPv6.
|
||||
//
|
||||
// This may have been improved in IDF v5 -- Espressif has added a feature to store DNS servers
|
||||
// on a per interface basis in their LwIP fork.
|
||||
//
|
||||
// References:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/lwip.html (see the "Note" block under "Adapted APIs" -- though it very much undersells the problem.)
|
||||
// https://github.com/espressif/arduino-esp32/discussions/9988 - links to older discussions
|
||||
static u8_t blockRouterAdvertisements(void* arg, struct raw_pcb* pcb, struct pbuf* p, const ip_addr_t* addr) {
|
||||
// ICMPv6 type is the first byte of the payload, so we skip the header
|
||||
if (p->len > 0 && (pbuf_get_at(p, sizeof(struct ip6_hdr)) == ICMP6_TYPE_RA)) {
|
||||
pbuf_free(p);
|
||||
return 1; // claim the packet — lwIP will not pass it further
|
||||
}
|
||||
return 0; // not consumed, pass it on
|
||||
}
|
||||
|
||||
void installIPv6RABlocker() {
|
||||
struct raw_pcb* ra_blocker = raw_new_ip_type(IPADDR_TYPE_V6, IP6_NEXTH_ICMP6);
|
||||
raw_recv(ra_blocker, blockRouterAdvertisements, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
//handle Ethernet connection event
|
||||
void WiFiEvent(WiFiEvent_t event)
|
||||
{
|
||||
@@ -442,8 +413,9 @@ void WiFiEvent(WiFiEvent_t event)
|
||||
if (!apActive) {
|
||||
WiFi.disconnect(true); // disable WiFi entirely
|
||||
}
|
||||
char hostname[64] = {'\0'}; // any "hostname" within a Fully Qualified Domain Name (FQDN) must not exceed 63 characters
|
||||
getWLEDhostname(hostname, sizeof(hostname), true); // create DNS name based on mDNS name if set, or fall back to standard WLED server name
|
||||
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
|
||||
char hostname[64];
|
||||
prepareHostname(hostname);
|
||||
ETH.setHostname(hostname);
|
||||
showWelcomePage = false;
|
||||
break;
|
||||
|
||||
196
wled00/ntp.cpp
196
wled00/ntp.cpp
@@ -1,8 +1,6 @@
|
||||
#include "src/dependencies/timezone/Timezone.h"
|
||||
#include "wled.h"
|
||||
#include "fcn_declare.h"
|
||||
#include "asyncDNS.h"
|
||||
#include <memory>
|
||||
|
||||
// forward declarations
|
||||
static void sendNTPPacket();
|
||||
@@ -187,11 +185,8 @@ void handleTime() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handleNetworkTime()
|
||||
{
|
||||
static std::shared_ptr<AsyncDNS> ntpDNSlookup;
|
||||
|
||||
if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > (1000*NTP_SYNC_INTERVAL) && WLED_CONNECTED)
|
||||
{
|
||||
if (millis() - ntpPacketSentTime > 10000)
|
||||
@@ -199,41 +194,8 @@ void handleNetworkTime()
|
||||
#ifdef ARDUINO_ARCH_ESP32 // I had problems using udp.flush() on 8266
|
||||
while (ntpUdp.parsePacket() > 0) ntpUdp.flush(); // flush any existing packets
|
||||
#endif
|
||||
if (!ntpServerIP.fromString(ntpServerName)) // check if server is IP or domain
|
||||
{
|
||||
AsyncDNS::result res = ntpDNSlookup ? ntpDNSlookup->status() : AsyncDNS::result::Idle;
|
||||
switch (res) {
|
||||
case AsyncDNS::result::Idle:
|
||||
//DEBUG_PRINTF_P(PSTR("Resolving NTP server name: %s\n"), ntpServerName);
|
||||
ntpDNSlookup = AsyncDNS::query(ntpServerName, ntpDNSlookup); // start dnslookup asynchronously
|
||||
return;
|
||||
|
||||
case AsyncDNS::result::Busy:
|
||||
return; // still in progress
|
||||
|
||||
case AsyncDNS::result::Success:
|
||||
ntpServerIP = ntpDNSlookup->getIP();
|
||||
DEBUG_PRINTF_P(PSTR("NTP IP resolved: %s\n"), ntpServerIP.toString().c_str());
|
||||
sendNTPPacket();
|
||||
ntpDNSlookup.reset();
|
||||
break;
|
||||
|
||||
case AsyncDNS::result::Error:
|
||||
DEBUG_PRINTLN(F("NTP DNS failed"));
|
||||
if (ntpDNSlookup->getErrorCount() > 6) {
|
||||
// after 6 failed attempts (30min), reset network connection as dns is probably stuck (TODO: IDF bug, should be fixed in V5)
|
||||
if (offMode) forceReconnect = true; // do not disturb while LEDs are running
|
||||
ntpDNSlookup.reset();
|
||||
} else {
|
||||
// Retry
|
||||
ntpDNSlookup = AsyncDNS::query(ntpServerName, ntpDNSlookup);
|
||||
}
|
||||
ntpLastSyncTime = millis() - (1000*NTP_SYNC_INTERVAL - 300000); // pause for 5 minutes
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
sendNTPPacket();
|
||||
sendNTPPacket();
|
||||
ntpPacketSentTime = millis();
|
||||
}
|
||||
if (checkNTPResponse())
|
||||
{
|
||||
@@ -244,6 +206,14 @@ void handleNetworkTime()
|
||||
|
||||
static void sendNTPPacket()
|
||||
{
|
||||
if (!ntpServerIP.fromString(ntpServerName)) //see if server is IP or domain
|
||||
{
|
||||
#ifdef ESP8266
|
||||
WiFi.hostByName(ntpServerName, ntpServerIP, 750);
|
||||
#else
|
||||
WiFi.hostByName(ntpServerName, ntpServerIP);
|
||||
#endif
|
||||
}
|
||||
|
||||
DEBUG_PRINTLN(F("send NTP"));
|
||||
byte pbuf[NTP_PACKET_SIZE];
|
||||
@@ -262,7 +232,6 @@ static void sendNTPPacket()
|
||||
ntpUdp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123
|
||||
ntpUdp.write(pbuf, NTP_PACKET_SIZE);
|
||||
ntpUdp.endPacket();
|
||||
ntpPacketSentTime = millis();
|
||||
}
|
||||
|
||||
static bool isValidNtpResponse(const byte* ntpPacket) {
|
||||
@@ -272,7 +241,7 @@ static bool isValidNtpResponse(const byte* ntpPacket) {
|
||||
// if((ntpPacket[0] & 0b00111000) >> 3 < 0b100) return false; //reject Version < 4
|
||||
if((ntpPacket[0] & 0b00000111) != 0b100) return false; //reject Mode != Server
|
||||
if((ntpPacket[1] < 1) || (ntpPacket[1] > 15)) return false; //reject invalid Stratum
|
||||
if( ntpPacket[16] == 0 && ntpPacket[17] == 0 &&
|
||||
if( ntpPacket[16] == 0 && ntpPacket[17] == 0 &&
|
||||
ntpPacket[18] == 0 && ntpPacket[19] == 0 &&
|
||||
ntpPacket[20] == 0 && ntpPacket[21] == 0 &&
|
||||
ntpPacket[22] == 0 && ntpPacket[23] == 0) //reject ReferenceTimestamp == 0
|
||||
@@ -414,40 +383,53 @@ bool isTodayInDateRange(byte monthStart, byte dayStart, byte monthEnd, byte dayE
|
||||
|
||||
void checkTimers()
|
||||
{
|
||||
if (lastTimerMinute != minute(localTime)) {
|
||||
if (lastTimerMinute != minute(localTime)) //only check once a new minute begins
|
||||
{
|
||||
lastTimerMinute = minute(localTime);
|
||||
|
||||
// re-calculate sunrise and sunset just after midnight
|
||||
if (!hour(localTime) && minute(localTime)==1) calculateSunriseAndSunset();
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("Local time: %02d:%02d\n"), hour(localTime), minute(localTime));
|
||||
for (size_t i = 0; i < timers.size(); i++) {
|
||||
const Timer& t = timers[i];
|
||||
if (!t.isEnabled()) continue;
|
||||
time_t tt = 0;
|
||||
if (t.isSunrise()) {
|
||||
if (!sunrise) continue;
|
||||
tt = sunrise + t.minute * 60;
|
||||
} else if (t.isSunset()) {
|
||||
if (!sunset) continue;
|
||||
tt = sunset + t.minute * 60;
|
||||
} else {
|
||||
struct tm tim = {};
|
||||
tim.tm_year = year(localTime) - 1900;
|
||||
tim.tm_mon = month(localTime) - 1;
|
||||
tim.tm_mday = day(localTime);
|
||||
tim.tm_hour = t.hour;
|
||||
tim.tm_min = t.minute;
|
||||
tim.tm_sec = 0;
|
||||
tim.tm_isdst = -1;
|
||||
tt = mktime(&tim);
|
||||
for (unsigned i = 0; i < 8; i++)
|
||||
{
|
||||
if (timerMacro[i] != 0
|
||||
&& (timerWeekday[i] & 0x01) //timer is enabled
|
||||
&& (timerHours[i] == hour(localTime) || timerHours[i] == 24) //if hour is set to 24, activate every hour
|
||||
&& timerMinutes[i] == minute(localTime)
|
||||
&& ((timerWeekday[i] >> weekdayMondayFirst()) & 0x01) //timer should activate at current day of week
|
||||
&& isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i])
|
||||
)
|
||||
{
|
||||
applyPreset(timerMacro[i]);
|
||||
}
|
||||
if ((hour(tt) == hour(localTime) && minute(tt) == minute(localTime)) || (t.hour == 24 && t.minute == minute(localTime))) {
|
||||
if (!((t.weekdays >> weekdayMondayFirst()) & 0x01)) continue;
|
||||
if (!isTodayInDateRange(t.monthStart, t.dayStart, t.monthEnd, t.dayEnd)) continue;
|
||||
applyPreset(t.preset);
|
||||
#ifdef WLED_DEBUG
|
||||
if (t.isSunrise()) DEBUG_PRINTF_P(PSTR("Sunrise timer %d offset %d\n"), t.preset, t.minute);
|
||||
else if (t.isSunset()) DEBUG_PRINTF_P(PSTR("Sunset timer %d offset %d\n"), t.preset, t.minute);
|
||||
else DEBUG_PRINTF_P(PSTR("Timer %d: preset %d\n"), i, t.preset);
|
||||
#endif
|
||||
}
|
||||
// sunrise macro
|
||||
if (sunrise) {
|
||||
time_t tmp = sunrise + timerMinutes[8]*60; // NOTE: may not be ok
|
||||
DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp));
|
||||
if (timerMacro[8] != 0
|
||||
&& hour(tmp) == hour(localTime)
|
||||
&& minute(tmp) == minute(localTime)
|
||||
&& (timerWeekday[8] & 0x01) //timer is enabled
|
||||
&& ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
|
||||
{
|
||||
applyPreset(timerMacro[8]);
|
||||
DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."),timerMacro[8]);
|
||||
}
|
||||
}
|
||||
// sunset macro
|
||||
if (sunset) {
|
||||
time_t tmp = sunset + timerMinutes[9]*60; // NOTE: may not be ok
|
||||
DEBUG_PRINTF_P(PSTR("Trigger time: %02d:%02d\n"), hour(tmp), minute(tmp));
|
||||
if (timerMacro[9] != 0
|
||||
&& hour(tmp) == hour(localTime)
|
||||
&& minute(tmp) == minute(localTime)
|
||||
&& (timerWeekday[9] & 0x01) //timer is enabled
|
||||
&& ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
|
||||
{
|
||||
applyPreset(timerMacro[9]);
|
||||
DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."),timerMacro[9]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,7 +488,7 @@ static int getSunriseUTC(int year, int month, int day, float lat, float lon, boo
|
||||
return UT*60;
|
||||
}
|
||||
|
||||
#define SUNSET_MAX (24*60) // 1day = max expected absolute value for sun offset in minutes
|
||||
#define SUNSET_MAX (24*60) // 1day = max expected absolute value for sun offset in minutes
|
||||
// calculate sunrise and sunset (if longitude and latitude are set)
|
||||
void calculateSunriseAndSunset() {
|
||||
if ((int)(longitude*10.) || (int)(latitude*10.)) {
|
||||
@@ -575,70 +557,4 @@ void setTimeFromAPI(uint32_t timein) {
|
||||
calculateSunriseAndSunset();
|
||||
}
|
||||
if (presetsModifiedTime == 0) presetsModifiedTime = timein;
|
||||
}
|
||||
|
||||
void addTimer(uint8_t preset, uint8_t hour, int8_t minute, uint8_t weekdays,
|
||||
uint8_t monthStart, uint8_t monthEnd, uint8_t dayStart, uint8_t dayEnd) {
|
||||
if (hour > 24 && hour != TH_SUNSET && hour != TH_SUNRISE) {
|
||||
DEBUG_PRINTLN(F("Timer: Invalid hour value"));
|
||||
return;
|
||||
}
|
||||
if (hour == TH_SUNRISE || hour == TH_SUNSET) {
|
||||
if (minute < -120 || minute > 120) {
|
||||
DEBUG_PRINTLN(F("Timer: Clamping sunrise/sunset offset to [-120,120]"));
|
||||
if (minute < -120) minute = -120;
|
||||
else if (minute > 120) minute = 120;
|
||||
}
|
||||
} else {
|
||||
if (minute < 0 || minute > 59) {
|
||||
DEBUG_PRINTLN(F("Timer: Invalid minute value"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((monthStart != 0 && monthStart > 12) ||
|
||||
(monthEnd != 0 && monthEnd > 12)) {
|
||||
DEBUG_PRINTLN(F("Timer: Invalid month range"));
|
||||
return;
|
||||
}
|
||||
if ((dayStart != 0 && dayStart > 31) ||
|
||||
(dayEnd != 0 && dayEnd > 31)) {
|
||||
DEBUG_PRINTLN(F("Timer: Invalid day range"));
|
||||
return;
|
||||
}
|
||||
if (timers.size() >= WLED_MAX_TIMERS) {
|
||||
DEBUG_PRINTLN(F("Timer: Maximum number of timers reached"));
|
||||
return;
|
||||
}
|
||||
Timer t(preset, hour, minute, weekdays, monthStart, monthEnd, dayStart, dayEnd);
|
||||
timers.push_back(t);
|
||||
DEBUG_PRINTF("Timer added: preset=%d, hour=%d, minute=%d, count=%d\n", preset, hour, minute, timers.size());
|
||||
}
|
||||
|
||||
void removeTimer(size_t index) {
|
||||
if (index < timers.size()) {
|
||||
timers.erase(timers.begin() + index);
|
||||
DEBUG_PRINTF("Timer removed at index %d, count=%d\n", index, timers.size());
|
||||
}
|
||||
}
|
||||
|
||||
void clearTimers() {
|
||||
timers.clear();
|
||||
DEBUG_PRINTLN(F("All timers cleared"));
|
||||
}
|
||||
|
||||
size_t getTimerCount() {
|
||||
return timers.size();
|
||||
}
|
||||
|
||||
void compactTimers() {
|
||||
for (size_t i = 0; i < timers.size();) {
|
||||
const Timer& t = timers[i];
|
||||
if (t.preset == 0) {
|
||||
timers.erase(timers.begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
timers.shrink_to_fit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -451,9 +451,9 @@ String getBootloaderSHA256Hex() {
|
||||
String result;
|
||||
result.reserve(65);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
char b1 = bootloaderSHA256Cache[i];
|
||||
char b2 = b1 >> 4;
|
||||
b1 &= 0x0F; b2 &= 0x0F;
|
||||
unsigned char b1 = bootloaderSHA256Cache[i];
|
||||
unsigned char b2 = b1 >> 4;
|
||||
b1 &= 0x0F;
|
||||
b1 += '0'; b2 += '0';
|
||||
if (b1 > '9') b1 += 39;
|
||||
if (b2 > '9') b2 += 39;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "wled.h"
|
||||
#include "src/dependencies/fastled_slim/fastled_slim.h"
|
||||
|
||||
/*
|
||||
* WLED Color palettes
|
||||
@@ -10,103 +9,6 @@
|
||||
* Palettes from FastLED are intended to be used without gamma correction, an inverse gamma of 2.2 is applied to original colors
|
||||
*/
|
||||
|
||||
// FastLED Palettes
|
||||
// ----------------
|
||||
// Palettes imported from FastLED @ 3.6.0 (https://github.com/FastLED/FastLED) are licensed under the MIT license
|
||||
// See /src/dependencies/fastled_slim/LICENSE.txt for details
|
||||
|
||||
// Cloudy color palette
|
||||
const TProgmemRGBPalette16 CloudColors_p PROGMEM = {
|
||||
CRGB::Blue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::DarkBlue,
|
||||
|
||||
CRGB::Blue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::SkyBlue,
|
||||
CRGB::SkyBlue,
|
||||
|
||||
CRGB::LightBlue,
|
||||
CRGB::White,
|
||||
CRGB::LightBlue,
|
||||
CRGB::SkyBlue
|
||||
};
|
||||
|
||||
// Lava color palette
|
||||
const TProgmemRGBPalette16 LavaColors_p PROGMEM = {
|
||||
CRGB::Black,
|
||||
CRGB::Maroon,
|
||||
CRGB::Black,
|
||||
CRGB::Maroon,
|
||||
|
||||
CRGB::DarkRed,
|
||||
CRGB::DarkRed,
|
||||
CRGB::Maroon,
|
||||
CRGB::DarkRed,
|
||||
|
||||
CRGB::DarkRed,
|
||||
CRGB::DarkRed,
|
||||
CRGB::Red,
|
||||
CRGB::Orange,
|
||||
|
||||
CRGB::White,
|
||||
CRGB::Orange,
|
||||
CRGB::Red,
|
||||
CRGB::DarkRed
|
||||
};
|
||||
|
||||
// Ocean colors, blues and whites
|
||||
const TProgmemRGBPalette16 OceanColors_p PROGMEM = {
|
||||
CRGB::MidnightBlue,
|
||||
CRGB::DarkBlue,
|
||||
CRGB::MidnightBlue,
|
||||
CRGB::Navy,
|
||||
|
||||
CRGB::DarkBlue,
|
||||
CRGB::MediumBlue,
|
||||
CRGB::SeaGreen,
|
||||
CRGB::Teal,
|
||||
|
||||
CRGB::CadetBlue,
|
||||
CRGB::Blue,
|
||||
CRGB::DarkCyan,
|
||||
CRGB::CornflowerBlue,
|
||||
|
||||
CRGB::Aquamarine,
|
||||
CRGB::SeaGreen,
|
||||
CRGB::Aqua,
|
||||
CRGB::LightSkyBlue
|
||||
};
|
||||
|
||||
// Forest colors, greens
|
||||
const TProgmemRGBPalette16 ForestColors_p PROGMEM = {
|
||||
CRGB::DarkGreen,
|
||||
CRGB::DarkGreen,
|
||||
CRGB::DarkOliveGreen,
|
||||
CRGB::DarkGreen,
|
||||
|
||||
CRGB::Green,
|
||||
CRGB::ForestGreen,
|
||||
CRGB::OliveDrab,
|
||||
CRGB::Green,
|
||||
|
||||
CRGB::SeaGreen,
|
||||
CRGB::MediumAquamarine,
|
||||
CRGB::LimeGreen,
|
||||
CRGB::YellowGreen,
|
||||
|
||||
CRGB::LightGreen,
|
||||
CRGB::LawnGreen,
|
||||
CRGB::MediumAquamarine,
|
||||
CRGB::ForestGreen
|
||||
};
|
||||
|
||||
// Gradient palette "ib_jul01_gp", originally from
|
||||
// http://seaviewsensing.com/pub/cpt-city/ing/xmas/ib_jul01.c3g
|
||||
const uint8_t ib_jul01_gp[] PROGMEM = {
|
||||
@@ -769,21 +671,21 @@ const byte Aurora2_gp[] PROGMEM = {
|
||||
// FastLed palettes, corrected with inverse gamma of 2.2 to match original looks
|
||||
|
||||
// Party colors
|
||||
const TProgmemRGBPalette16 PartyColors_gc22 PROGMEM = {
|
||||
const TProgmemRGBPalette16 PartyColors_gc22 FL_PROGMEM = {
|
||||
0x9B00D5, 0xBD00B8, 0xDA0092, 0xF3005C,
|
||||
0xF45500, 0xDC8F00, 0xD5B400, 0xD5D500,
|
||||
0xD59B00, 0xEF6600, 0xF90044, 0xE10086,
|
||||
0xC400B0, 0xA300CF, 0x7600E8, 0x0032FC};
|
||||
|
||||
// Rainbow colors
|
||||
const TProgmemRGBPalette16 RainbowColors_gc22 PROGMEM = {
|
||||
const TProgmemRGBPalette16 RainbowColors_gc22 FL_PROGMEM = {
|
||||
0xFF0000, 0xEB7000, 0xD59B00, 0xD5BA00,
|
||||
0xD5D500, 0x9CEB00, 0x00FF00, 0x00EB70,
|
||||
0x00D59B, 0x009CD4, 0x0000FF, 0x7000EB,
|
||||
0x9B00D5, 0xBA00BB, 0xD5009B, 0xEB0072};
|
||||
|
||||
// Rainbow colors with alternatating stripes of black
|
||||
const TProgmemRGBPalette16 RainbowStripeColors_gc22 PROGMEM = {
|
||||
const TProgmemRGBPalette16 RainbowStripeColors_gc22 FL_PROGMEM = {
|
||||
0xFF0000, 0x000000, 0xD59B00, 0x000000,
|
||||
0xD5D500, 0x000000, 0x00FF00, 0x000000,
|
||||
0x00D59B, 0x000000, 0x0000FF, 0x000000,
|
||||
@@ -864,4 +766,4 @@ const uint8_t* const gGradientPalettes[] PROGMEM = {
|
||||
red_tide_gp, //69-56 Red Tide
|
||||
candy2_gp, //70-57 Candy2
|
||||
trafficlight_gp //71-58 Traffic Light
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
typedef struct PlaylistEntry {
|
||||
uint8_t preset; //ID of the preset to apply
|
||||
uint16_t dur; //Duration of the entry (in tenths of seconds)
|
||||
uint16_t tr; //Duration of the transition TO this entry (in tenths of seconds)
|
||||
uint32_t dur; //Duration of the entry (in milliseconds)
|
||||
} ple;
|
||||
|
||||
static byte playlistRepeat = 1; //how many times to repeat the playlist (0 = infinitely)
|
||||
@@ -17,7 +17,7 @@ static byte playlistOptions = 0; //bit 0: shuffle playlist after
|
||||
static PlaylistEntry *playlistEntries = nullptr;
|
||||
static byte playlistLen; //number of playlist entries
|
||||
static int8_t playlistIndex = -1;
|
||||
static uint32_t playlistEntryDur = 0; //duration of the current entry in milliseconds
|
||||
static uint16_t playlistEntryDur = 0; //duration of the current entry in tenths of seconds
|
||||
|
||||
//values we need to keep about the parent playlist while inside sub-playlist
|
||||
static int16_t parentPlaylistIndex = -1;
|
||||
@@ -48,9 +48,7 @@ void unloadPlaylist() {
|
||||
playlistEntries = nullptr;
|
||||
}
|
||||
currentPlaylist = playlistIndex = -1;
|
||||
playlistLen = 0;
|
||||
playlistOptions = 0;
|
||||
playlistEntryDur = 0;
|
||||
playlistLen = playlistEntryDur = playlistOptions = 0;
|
||||
DEBUG_PRINTLN(F("Playlist unloaded."));
|
||||
}
|
||||
|
||||
@@ -82,15 +80,12 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
|
||||
it = 0;
|
||||
JsonArray durations = playlistObj["dur"];
|
||||
if (durations.isNull()) {
|
||||
uint32_t durMs = playlistObj["dur"] | 100; // 10 seconds as fallback (tenths)
|
||||
durMs = constrain(durMs, 0L, 42949670L) * 100UL; // limit to max value and convert to ms
|
||||
playlistEntries[0].dur = (uint32_t)durMs;
|
||||
playlistEntries[0].dur = playlistObj["dur"] | 100; //10 seconds as fallback
|
||||
it = 1;
|
||||
} else {
|
||||
for (int dur : durations) {
|
||||
if (it >= playlistLen) break;
|
||||
uint32_t durMs = constrain(dur, 0L, 42949670L) * 100UL; // limit to max value and convert to ms
|
||||
playlistEntries[it].dur = (uint32_t)durMs;
|
||||
playlistEntries[it].dur = constrain(dur, 0, 65530);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
@@ -152,7 +147,7 @@ void handlePlaylist() {
|
||||
static unsigned long presetCycledTime = 0;
|
||||
if (currentPlaylist < 0 || playlistEntries == nullptr) return;
|
||||
|
||||
if ((playlistEntryDur < UINT32_MAX && millis() - presetCycledTime > playlistEntryDur) || doAdvancePlaylist) {
|
||||
if ((playlistEntryDur < UINT16_MAX && millis() - presetCycledTime > 100 * playlistEntryDur) || doAdvancePlaylist) {
|
||||
presetCycledTime = millis();
|
||||
if (bri == 0 || nightlightActive) return;
|
||||
|
||||
@@ -175,7 +170,7 @@ void handlePlaylist() {
|
||||
|
||||
jsonTransitionOnce = true;
|
||||
strip.setTransition(playlistEntries[playlistIndex].tr * 100);
|
||||
playlistEntryDur = playlistEntries[playlistIndex].dur > 0 ? playlistEntries[playlistIndex].dur : UINT32_MAX; // UINT32_MAX means infinite
|
||||
playlistEntryDur = playlistEntries[playlistIndex].dur > 0 ? playlistEntries[playlistIndex].dur : UINT16_MAX;
|
||||
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
|
||||
doAdvancePlaylist = false;
|
||||
}
|
||||
@@ -192,7 +187,7 @@ void serializePlaylist(JsonObject sObj) {
|
||||
playlist["r"] = playlistOptions & PL_OPTION_SHUFFLE;
|
||||
for (int i=0; i<playlistLen; i++) {
|
||||
ps.add(playlistEntries[i].preset);
|
||||
dur.add((playlistEntries[i].dur) / 100); // convert ms back to tenths of seconds (backwards compatibility)
|
||||
dur.add(playlistEntries[i].dur);
|
||||
transition.add(playlistEntries[i].tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#include "wled.h"
|
||||
|
||||
// Simple and fast Pseudo-Random-Number-Generator for 16bit and 8bit random numbers
|
||||
// allows the same sequence of random numbers to be generated by setting the 16bit-seed
|
||||
// values found by brute-force test algorithm: sequence has no repetitions and good randomness for its simplicity
|
||||
class PRNG {
|
||||
private:
|
||||
uint16_t seed;
|
||||
public:
|
||||
PRNG(uint16_t initialSeed = 0x1234) : seed(initialSeed) {}
|
||||
void setSeed(uint16_t s) { seed = s; }
|
||||
uint16_t getSeed() const { return seed; }
|
||||
uint16_t random16() {
|
||||
seed = seed * 3001 + 31683;
|
||||
seed ^= seed >> 7;
|
||||
return seed;
|
||||
}
|
||||
uint16_t random16(uint16_t lim) { return ((uint32_t)random16() * lim) >> 16; }
|
||||
uint16_t random16(uint16_t min, uint16_t lim) { uint16_t delta = lim - min; return random16(delta) + min; }
|
||||
uint8_t random8() { return random16(); }
|
||||
uint8_t random8(uint8_t lim) { return (uint8_t)(((uint16_t)random8() * lim) >> 8); }
|
||||
uint8_t random8(uint8_t min, uint8_t lim) { uint8_t delta = lim - min; return random8(delta) + min; }
|
||||
};
|
||||
@@ -339,7 +339,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), buttons[i].pin, i);
|
||||
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button);
|
||||
buttons[i].type = BTN_TYPE_NONE;
|
||||
}
|
||||
}
|
||||
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so
|
||||
else touchAttachInterrupt(buttons[i].pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
|
||||
#endif
|
||||
@@ -580,46 +580,29 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
ii++;
|
||||
}
|
||||
|
||||
clearTimers();
|
||||
char k[5]; k[4] = 0;
|
||||
for (int ti = 0; ti < (int)WLED_MAX_TIMERS; ti++) {
|
||||
if (ti < 10) {
|
||||
k[1] = ti + 48;
|
||||
k[2] = 0;
|
||||
} else {
|
||||
k[1] = '0' + (ti / 10);
|
||||
k[2] = '0' + (ti % 10);
|
||||
k[3] = 0;
|
||||
char k[3]; k[2] = 0;
|
||||
for (int i = 0; i<10; i++) {
|
||||
k[1] = i+48;//ascii 0,1,2,3,...
|
||||
k[0] = 'H'; //timer hours
|
||||
timerHours[i] = request->arg(k).toInt();
|
||||
k[0] = 'N'; //minutes
|
||||
timerMinutes[i] = request->arg(k).toInt();
|
||||
k[0] = 'T'; //macros
|
||||
timerMacro[i] = request->arg(k).toInt();
|
||||
k[0] = 'W'; //weekdays
|
||||
timerWeekday[i] = request->arg(k).toInt();
|
||||
if (i<8) {
|
||||
k[0] = 'M'; //start month
|
||||
timerMonth[i] = request->arg(k).toInt() & 0x0F;
|
||||
timerMonth[i] <<= 4;
|
||||
k[0] = 'P'; //end month
|
||||
timerMonth[i] += (request->arg(k).toInt() & 0x0F);
|
||||
k[0] = 'D'; //start day
|
||||
timerDay[i] = request->arg(k).toInt();
|
||||
k[0] = 'E'; //end day
|
||||
timerDayEnd[i] = request->arg(k).toInt();
|
||||
}
|
||||
k[0] = 'T';
|
||||
if (!request->hasArg(k)) continue;
|
||||
uint8_t p = request->arg(k).toInt();
|
||||
k[0] = 'H';
|
||||
uint8_t h = request->arg(k).toInt();
|
||||
k[0] = 'N';
|
||||
int minuteVal = request->arg(k).toInt();
|
||||
if (minuteVal < -120) minuteVal = -120;
|
||||
if (minuteVal > 120) minuteVal = 120;
|
||||
int8_t m = (int8_t)minuteVal;
|
||||
k[0] = 'W';
|
||||
uint8_t wd = request->arg(k).toInt();
|
||||
uint8_t ms = 1, me = 12, ds = 1, de = 31;
|
||||
k[0] = 'M';
|
||||
ms = request->arg(k).toInt();
|
||||
if (ms == 0) ms = 1;
|
||||
k[0] = 'P';
|
||||
me = request->arg(k).toInt();
|
||||
if (me == 0) me = 12;
|
||||
k[0] = 'D';
|
||||
ds = request->arg(k).toInt();
|
||||
if (ds == 0) ds = 1;
|
||||
k[0] = 'E';
|
||||
de = request->arg(k).toInt();
|
||||
if (de == 0) de = 31;
|
||||
addTimer(p, h, m, wd, ms, me, ds, de);
|
||||
}
|
||||
compactTimers();
|
||||
|
||||
}
|
||||
|
||||
//SECURITY
|
||||
@@ -1004,7 +987,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
|
||||
|
||||
pos = req.indexOf(F("NP")); //advances to next preset in a playlist
|
||||
if (pos > 0) doAdvancePlaylist = true;
|
||||
|
||||
|
||||
//set brightness
|
||||
updateVal(req.c_str(), "&A=", bri);
|
||||
|
||||
|
||||
@@ -49,26 +49,13 @@ typedef struct ip_addr ip4_addr_t;
|
||||
#define E131_DEFAULT_PORT 5568
|
||||
#define ARTNET_DEFAULT_PORT 6454
|
||||
#define DDP_DEFAULT_PORT 4048
|
||||
#define DDP_HEADER_LEN 10
|
||||
#define DDP_SYNCPACKET_LEN 10
|
||||
|
||||
#define DDP_FLAGS_VER 0xc0 // version mask
|
||||
#define DDP_FLAGS_VER1 0x40 // version=1
|
||||
#define DDP_FLAGS_PUSH 0x01
|
||||
#define DDP_FLAGS_QUERY 0x02
|
||||
#define DDP_FLAGS_REPLY 0x04
|
||||
#define DDP_FLAGS_STORAGE 0x08
|
||||
#define DDP_FLAGS_TIME 0x10
|
||||
|
||||
#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds
|
||||
#define DDP_PUSH_FLAG 0x01
|
||||
#define DDP_TIMECODE_FLAG 0x10
|
||||
|
||||
#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
|
||||
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)
|
||||
|
||||
#define DDP_ID_DISPLAY 1
|
||||
#define DDP_ID_CONFIG 250
|
||||
#define DDP_ID_STATUS 251
|
||||
|
||||
#define ARTNET_OPCODE_OPDMX 0x5000
|
||||
#define ARTNET_OPCODE_OPPOLL 0x2000
|
||||
#define ARTNET_OPCODE_OPPOLLREPLY 0x2100
|
||||
|
||||
@@ -85,7 +85,6 @@ private:
|
||||
IPAddress ipMulti;
|
||||
uint32_t mac24; //bottom 24 bits of mac
|
||||
String escapedMac=""; //lowercase mac address
|
||||
String bridgeId=""; //uppercase EUI-64 bridge ID (16 hex chars)
|
||||
|
||||
//private member functions
|
||||
const char* modeString(EspalexaColorMode m)
|
||||
@@ -298,13 +297,13 @@ private:
|
||||
|
||||
snprintf_P(buf, sizeof(buf), PSTR("HTTP/1.1 200 OK\r\n"
|
||||
"EXT:\r\n"
|
||||
"CACHE-CONTROL: max-age=86400\r\n" // SSDP_INTERVAL
|
||||
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
|
||||
"LOCATION: http://%s:80/description.xml\r\n"
|
||||
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
|
||||
"hue-bridgeid: %s\r\n"
|
||||
"ST: urn:schemas-upnp-org:device:Basic:1\r\n" // _deviceType
|
||||
"USN: uuid:2f402f80-da50-11e1-9b23-%s::urn:schemas-upnp-org:device:Basic:1\r\n" // _uuid::_deviceType
|
||||
"\r\n"),s,bridgeId.c_str(),escapedMac.c_str());
|
||||
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType
|
||||
"USN: uuid:2f402f80-da50-11e1-9b23-%s::upnp:rootdevice\r\n" // _uuid::_deviceType
|
||||
"\r\n"),s,escapedMac.c_str(),escapedMac.c_str());
|
||||
|
||||
espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort());
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -334,11 +333,6 @@ public:
|
||||
escapedMac.replace(":", "");
|
||||
escapedMac.toLowerCase();
|
||||
|
||||
// Compute EUI-64 bridge ID from MAC-48: insert standard "FFFE" padding between
|
||||
// the first 6 hex chars (OUI/manufacturer) and last 6 hex chars (device), then uppercase
|
||||
bridgeId = escapedMac.substring(0, 6) + "fffe" + escapedMac.substring(6);
|
||||
bridgeId.toUpperCase();
|
||||
|
||||
String macSubStr = escapedMac.substring(6, 12);
|
||||
mac24 = strtol(macSubStr.c_str(), 0, 16);
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 FastLED modified by @dedehai
|
||||
|
||||
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,259 +0,0 @@
|
||||
#include "fastled_slim.h"
|
||||
|
||||
// Code originally from FastLED version 3.6.0. Optimized for WLED use by @dedehai
|
||||
// Licensed unter MIT license, see LICENSE.txt for details
|
||||
|
||||
// convert HSV (16bit hue) to RGB (24bit), optimized for speed (integer types and function arguments were very carefully selected for best performance)
|
||||
// this does the same as the FastLED hsv2rgb_rainbow function but with 16bit hue and optimizations for use with CRGB as well as CRGBW
|
||||
// note: this function is used when converting CHSV->CRGB or CHSV32->CRGBW by assignment or constructor, there is no need to call it explicitly
|
||||
__attribute__((optimize("O2"))) void hsv2rgb_rainbow(uint16_t h, uint8_t s, uint8_t v, uint8_t* rgbdata, bool isRGBW) {
|
||||
uint8_t hue = h>>8;
|
||||
uint8_t sat = s;
|
||||
uint32_t val = v;
|
||||
uint32_t offset = h & 0x1FFF; // 0..31, shifted
|
||||
uint32_t third16 = (offset * 21846); // offset16 = offset * 1/3<<16
|
||||
uint8_t third = third16 >> 21; // max = 85
|
||||
uint8_t r, g, b; // note: making these 32bit is significantly slower
|
||||
|
||||
if (!(hue & 0x80)) {
|
||||
if (!(hue & 0x40)) { // section 0-1
|
||||
if (!(hue & 0x20)) {
|
||||
r = 255 - third;
|
||||
g = third;
|
||||
b = 0;
|
||||
} else {
|
||||
r = 171;
|
||||
g = 85 + third;
|
||||
b = 0;
|
||||
}
|
||||
} else { // section 2-3
|
||||
if (!(hue & 0x20)) {
|
||||
uint8_t twothirds = third16 >> 20; // max=170
|
||||
r = 171 - twothirds;
|
||||
g = 170 + third;
|
||||
b = 0;
|
||||
} else {
|
||||
r = 0;
|
||||
g = 255 - third;
|
||||
b = third;
|
||||
}
|
||||
}
|
||||
} else { // section 4-7
|
||||
if (!(hue & 0x40)) {
|
||||
if (!(hue & 0x20)) {
|
||||
r = 0;
|
||||
uint8_t twothirds = third16 >> 20; // max=170
|
||||
g = 171 - twothirds;
|
||||
b = 85 + twothirds;
|
||||
} else {
|
||||
r = third;
|
||||
g = 0;
|
||||
b = 255 - third;
|
||||
}
|
||||
} else {
|
||||
if (!(hue & 0x20)) {
|
||||
r = 85 + third;
|
||||
g = 0;
|
||||
b = 171 - third;
|
||||
} else {
|
||||
r = 170 + third;
|
||||
g = 0;
|
||||
b = 85 - third;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scale down colors if desaturated and add the brightness_floor to r, g, and b.
|
||||
if (sat != 255) {
|
||||
if (sat == 0) {
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
} else {
|
||||
//we know sat is < 255 and > 1, lets use that: scale8video is always +1, so drop the conditional
|
||||
uint32_t desat = 255 - sat;
|
||||
desat = (desat * desat); // scale8_video(desat, desat) but more accurate, dropped the "+1" for speed: visual difference is negligible
|
||||
uint8_t brightness_floor = desat >> 8;
|
||||
uint32_t satscale = 0xFFFF - desat;
|
||||
if (r) r = ((r * satscale) >> 16);
|
||||
if (g) g = ((g * satscale) >> 16);
|
||||
if (b) b = ((b * satscale) >> 16);
|
||||
|
||||
r += brightness_floor;
|
||||
g += brightness_floor;
|
||||
b += brightness_floor;
|
||||
}
|
||||
}
|
||||
|
||||
// scale everything down if value < 255.
|
||||
if (val != 255) {
|
||||
if (val == 0) {
|
||||
r = 0;
|
||||
g = 0;
|
||||
b = 0;
|
||||
} else {
|
||||
val = val*val + 512; // = scale8_video(val,val)+2;
|
||||
if (r) r = ((r * val) >> 16) + 1;
|
||||
if (g) g = ((g * val) >> 16) + 1;
|
||||
if (b) b = ((b * val) >> 16) + 1;
|
||||
}
|
||||
}
|
||||
if(isRGBW) {
|
||||
rgbdata[0] = b;
|
||||
rgbdata[1] = g;
|
||||
rgbdata[2] = r;
|
||||
//rgbdata[3] = 0; // white
|
||||
} else {
|
||||
rgbdata[0] = r;
|
||||
rgbdata[1] = g;
|
||||
rgbdata[2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// black body radiation to RGB
|
||||
CRGB HeatColor(uint8_t temperature) {
|
||||
CRGB heatcolor;
|
||||
uint8_t t192 = (((int)temperature * 191) >> 8) + (temperature ? 1 : 0); // scale down, but keep 1 as minimum
|
||||
// calculate a value that ramps up from zero to 255 in each 'third' of the scale.
|
||||
uint8_t heatramp = t192 & 0x3F; // 0..63
|
||||
heatramp <<= 2; // scale up to 0..252
|
||||
heatcolor.r = 255;
|
||||
heatcolor.b = 0;
|
||||
if(t192 & 0x80) { // we're in the hottest third
|
||||
heatcolor.g = 255; // full green
|
||||
heatcolor.b = heatramp; // ramp up blue
|
||||
} else if(t192 & 0x40) { // we're in the middle third
|
||||
heatcolor.g = heatramp; // ramp up green
|
||||
} else { // we're in the coolest third
|
||||
heatcolor.r = heatramp; // ramp up red
|
||||
heatcolor.g = 0; // no green
|
||||
}
|
||||
return heatcolor;
|
||||
}
|
||||
|
||||
// CRGB color fill functions (from fastled, used for color palettes)
|
||||
void fill_solid_RGB(CRGB* colors, uint32_t num, const CRGB& c1) {
|
||||
for(uint32_t i = 0; i < num; i++) {
|
||||
colors[i] = c1;
|
||||
}
|
||||
}
|
||||
|
||||
// fill CRGB array with a color gradient
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t startpos, CRGB startcolor, uint32_t endpos, CRGB endcolor) {
|
||||
if(endpos < startpos) { // if the points are in the wrong order, flip them
|
||||
unsigned t = endpos;
|
||||
CRGB tc = endcolor;
|
||||
endcolor = startcolor;
|
||||
endpos = startpos;
|
||||
startpos = t;
|
||||
startcolor = tc;
|
||||
}
|
||||
int rdistance = endcolor.r - startcolor.r;
|
||||
int gdistance = endcolor.g - startcolor.g;
|
||||
int bdistance = endcolor.b - startcolor.b;
|
||||
|
||||
int divisor = endpos - startpos;
|
||||
divisor = divisor == 0 ? 1 : divisor; // prevent division by zero
|
||||
|
||||
int rdelta = (rdistance << 16) / divisor;
|
||||
int gdelta = (gdistance << 16) / divisor;
|
||||
int bdelta = (bdistance << 16) / divisor;
|
||||
|
||||
int rshifted = startcolor.r << 16;
|
||||
int gshifted = startcolor.g << 16;
|
||||
int bshifted = startcolor.b << 16;
|
||||
|
||||
for (unsigned i = startpos; i <= endpos; i++) {
|
||||
colors[i] = CRGB(rshifted >> 16, gshifted >> 16, bshifted >> 16);
|
||||
rshifted += rdelta;
|
||||
gshifted += gdelta;
|
||||
bshifted += bdelta;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2) {
|
||||
uint32_t last = num - 1;
|
||||
fill_gradient_RGB(colors, 0, c1, last, c2);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3) {
|
||||
uint32_t half = (num / 2);
|
||||
uint32_t last = num - 1;
|
||||
fill_gradient_RGB(colors, 0, c1, half, c2);
|
||||
fill_gradient_RGB(colors, half, c2, last, c3);
|
||||
}
|
||||
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4) {
|
||||
uint32_t onethird = (num / 3);
|
||||
uint32_t twothirds = ((num * 2) / 3);
|
||||
uint32_t last = num - 1;
|
||||
fill_gradient_RGB(colors, 0, c1, onethird, c2);
|
||||
fill_gradient_RGB(colors, onethird, c2, twothirds, c3);
|
||||
fill_gradient_RGB(colors, twothirds, c3, last, c4);
|
||||
}
|
||||
|
||||
// palette blending
|
||||
void nblendPaletteTowardPalette(CRGBPalette16& current, CRGBPalette16& target, uint8_t maxChanges) {
|
||||
uint8_t* p1;
|
||||
uint8_t* p2;
|
||||
uint32_t changes = 0;
|
||||
p1 = (uint8_t*)current.entries;
|
||||
p2 = (uint8_t*)target.entries;
|
||||
const uint32_t totalChannels = sizeof(CRGBPalette16);
|
||||
for (uint32_t i = 0; i < totalChannels; ++i) {
|
||||
if (p1[i] == p2[i]) continue; // if the values are equal, no changes are needed
|
||||
if (p1[i] < p2[i]) { ++p1[i]; ++changes; } // if the current value is less than the target, increase it by one
|
||||
if (p1[i] > p2[i]) { // if the current value is greater than the target, increase it by one (or two if it's still greater).
|
||||
--p1[i]; ++changes;
|
||||
if (p1[i] > p2[i])
|
||||
--p1[i];
|
||||
}
|
||||
if(changes >= maxChanges)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cubic ease function (S-curve: 3x^2 - 2x^3 = x^2*(3-2x))
|
||||
// 8-bit
|
||||
uint8_t ease8InOutCubic(uint8_t i) {
|
||||
uint32_t ii = ((uint32_t)i * i);
|
||||
uint32_t factor = (3 << 8) - (((uint32_t)i << 1)); // 3 - 2i
|
||||
return (ii * factor) >> 16;
|
||||
}
|
||||
// 16-bit
|
||||
uint16_t ease16InOutCubic(uint16_t i) {
|
||||
uint32_t ii = ((uint32_t)i * i) >> 16;
|
||||
uint32_t factor = (3 << 16) - (((uint32_t)i << 1)); // 3 - 2i
|
||||
return (ii * factor) >> 16;
|
||||
}
|
||||
|
||||
// quadratic ease function (S-curve: x^2)
|
||||
uint8_t ease8InOutQuad(uint8_t i)
|
||||
{
|
||||
uint32_t j = i;
|
||||
if (j & 0x80) j = 255 - j; // mirror if > 127
|
||||
uint32_t jj = (j * j) >> 7;
|
||||
return (i & 0x80) ? (255 - jj) : jj;
|
||||
}
|
||||
|
||||
// triangular wave generator
|
||||
uint8_t triwave8(uint8_t in) {
|
||||
if (in & 0x80) in = 255 - in;
|
||||
return in << 1;
|
||||
}
|
||||
|
||||
uint16_t triwave16(uint16_t in) {
|
||||
if (in < 0x8000) return in *2;
|
||||
return 0xFFFF - (in - 0x8000)*2;
|
||||
}
|
||||
|
||||
// quadratic waveform generator. Spends just a little more time at the limits than "sine" does.
|
||||
uint8_t quadwave8(uint8_t in) {
|
||||
return ease8InOutQuad(triwave8(in));
|
||||
}
|
||||
|
||||
// cubic waveform generator. Spends visibly more time at the limits than "sine" does.
|
||||
uint8_t cubicwave8(uint8_t in) {
|
||||
return ease8InOutCubic(triwave8(in));
|
||||
}
|
||||
@@ -1,782 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstring> // for mem operations
|
||||
#include <pgmspace.h>
|
||||
|
||||
// Code originally from FastLED version 3.6.0. Optimized for WLED use by @dedehai
|
||||
// Licensed unter MIT license, see LICENSE.txt for details
|
||||
|
||||
// inline math functions
|
||||
__attribute__ ((always_inline)) inline uint8_t scale8(uint8_t i, uint8_t scale ) { return ((int)i * (1 + (int)scale)) >> 8; }
|
||||
__attribute__ ((always_inline)) inline uint8_t scale8_video(uint8_t i, uint8_t scale ) { return (((int)i * (int)scale) >> 8) + ((i&&scale)?1:0); }
|
||||
__attribute__ ((always_inline)) inline uint16_t scale16(uint16_t i, uint16_t scale ) { return ((uint32_t)i * (1 + (uint32_t)scale)) >> 16; }
|
||||
__attribute__ ((always_inline)) inline uint8_t qadd8(uint8_t i, uint8_t j) { unsigned t = i + j; return t > 255 ? 255 : t; }
|
||||
__attribute__ ((always_inline)) inline uint8_t qsub8(uint8_t i, uint8_t j) { int t = i - j; return t < 0 ? 0 : t; }
|
||||
__attribute__ ((always_inline)) inline uint8_t qmul8(uint8_t i, uint8_t j) { unsigned p = (unsigned)i * (unsigned)j; return p > 255 ? 255 : p; }
|
||||
__attribute__ ((always_inline)) inline int8_t abs8(int8_t i) { return i < 0 ? -i : i; }
|
||||
__attribute__ ((always_inline)) inline int8_t lerp8by8(uint8_t a, uint8_t b, uint8_t frac) { return a + ((((int32_t)b - (int32_t)a) * ((int32_t)frac+1)) >> 8); }
|
||||
|
||||
//forward declarations
|
||||
struct CRGB;
|
||||
struct CHSV;
|
||||
class CRGBPalette16;
|
||||
|
||||
typedef uint32_t TProgmemRGBPalette16[16];
|
||||
typedef uint8_t TDynamicRGBGradientPalette_byte; // Byte of an RGB gradient entry, stored in dynamic (heap) memory
|
||||
typedef const TDynamicRGBGradientPalette_byte *TDynamicRGBGradientPalette_bytes; // Pointer to bytes of an RGB gradient, stored in dynamic (heap) memory
|
||||
typedef TDynamicRGBGradientPalette_bytes TDynamicRGBGradientPalettePtr; // Alias of ::TDynamicRGBGradientPalette_bytes
|
||||
typedef const uint8_t TProgmemRGBGradientPalette_byte;
|
||||
typedef const TProgmemRGBGradientPalette_byte *TProgmemRGBGradientPalette_bytes;
|
||||
typedef TProgmemRGBGradientPalette_bytes TProgmemRGBGradientPalettePtr;
|
||||
|
||||
// color interpolation options for palette
|
||||
typedef enum {
|
||||
NOBLEND=0, // No interpolation between palette entries
|
||||
LINEARBLEND=1, // Linear interpolation between palette entries, with wrap-around from end to the beginning again
|
||||
LINEARBLEND_NOWRAP=2 // Linear interpolation between palette entries, but no wrap-around
|
||||
} TBlendType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t index; // index of the color entry in the gradient
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
uint32_t dword; // values packed as 32-bit
|
||||
uint8_t bytes[4]; // values as an array
|
||||
} TRGBGradientPaletteEntryUnion;
|
||||
|
||||
// function prototypes
|
||||
void hsv2rgb_rainbow(uint16_t h, uint8_t s, uint8_t v, uint8_t* rgbdata, bool isRGBW);
|
||||
CRGB HeatColor(uint8_t temperature); // black body radiation
|
||||
void fill_solid_RGB(CRGB* colors, uint32_t num, const CRGB& c1) ;
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t startpos, CRGB startcolor, uint32_t endpos, CRGB endcolor);
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2);
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3);
|
||||
void fill_gradient_RGB(CRGB* colors, uint32_t num, const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4);
|
||||
void nblendPaletteTowardPalette(CRGBPalette16& current, CRGBPalette16& target, uint8_t maxChanges);
|
||||
|
||||
uint8_t ease8InOutCubic(uint8_t i);
|
||||
uint16_t ease16InOutCubic(uint16_t i);
|
||||
uint8_t ease8InOutQuad(uint8_t i);
|
||||
uint8_t triwave8(uint8_t in);
|
||||
uint16_t triwave16(uint16_t in);
|
||||
uint8_t quadwave8(uint8_t in);
|
||||
uint8_t cubicwave8(uint8_t in);
|
||||
|
||||
// Representation of an HSV pixel (hue, saturation, value (aka brightness)).
|
||||
struct CHSV {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
uint8_t hue;
|
||||
uint8_t h;
|
||||
};
|
||||
union {
|
||||
uint8_t saturation;
|
||||
uint8_t sat;
|
||||
uint8_t s;
|
||||
};
|
||||
union {
|
||||
uint8_t value;
|
||||
uint8_t val;
|
||||
uint8_t v;
|
||||
};
|
||||
};
|
||||
uint8_t raw[3]; // order is: hue [0], saturation [1], value [2]
|
||||
};
|
||||
|
||||
inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) {
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) {
|
||||
return raw[x];
|
||||
}
|
||||
|
||||
// default constructor
|
||||
// @warning default values are UNINITIALIZED!
|
||||
inline CHSV() __attribute__((always_inline)) = default;
|
||||
|
||||
// allow construction from hue, saturation, and value
|
||||
inline CHSV(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline))
|
||||
: h(ih), s(is), v(iv) { }
|
||||
|
||||
// allow copy construction
|
||||
inline CHSV(const CHSV& rhs) __attribute__((always_inline)) = default;
|
||||
|
||||
// allow copy construction
|
||||
inline CHSV& operator= (const CHSV& rhs) __attribute__((always_inline)) = default;
|
||||
|
||||
// assign new HSV values
|
||||
inline CHSV& setHSV(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) {
|
||||
h = ih;
|
||||
s = is;
|
||||
v = iv;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// representation of an RGB pixel (Red, Green, Blue)
|
||||
struct CRGB {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
uint8_t r;
|
||||
uint8_t red;
|
||||
};
|
||||
union {
|
||||
uint8_t g;
|
||||
uint8_t green;
|
||||
};
|
||||
union {
|
||||
uint8_t b;
|
||||
uint8_t blue;
|
||||
};
|
||||
};
|
||||
uint8_t raw[3]; // order is: 0 = red, 1 = green, 2 = blue
|
||||
};
|
||||
|
||||
inline uint8_t& operator[] (uint8_t x) __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// array access operator to index into the CRGB object
|
||||
inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; }
|
||||
|
||||
// default constructor (uninitialized)
|
||||
inline CRGB() __attribute__((always_inline)) = default;
|
||||
|
||||
// allow construction from red, green, and blue
|
||||
inline CRGB(uint8_t ir, uint8_t ig, uint8_t ib) __attribute__((always_inline))
|
||||
: r(ir), g(ig), b(ib) { }
|
||||
|
||||
// allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code
|
||||
inline CRGB(uint32_t colorcode) __attribute__((always_inline))
|
||||
: r(uint8_t(colorcode>>16)), g(uint8_t(colorcode>>8)), b(uint8_t(colorcode)) { }
|
||||
|
||||
// allow copy construction
|
||||
inline CRGB(const CRGB& rhs) __attribute__((always_inline)) = default;
|
||||
|
||||
// allow construction from a CHSV color
|
||||
inline CRGB(const CHSV& rhs) __attribute__((always_inline)) {
|
||||
hsv2rgb_rainbow(rhs.h<<8, rhs.s, rhs.v, raw, false);
|
||||
}
|
||||
|
||||
// allow assignment from hue, saturation, and value
|
||||
inline CRGB& setHSV (uint8_t hue, uint8_t sat, uint8_t val) __attribute__((always_inline)) {
|
||||
hsv2rgb_rainbow(hue<<8, sat, val, raw, false); return *this;
|
||||
}
|
||||
|
||||
// allow assignment from just a hue, sat and val are set to max
|
||||
inline CRGB& setHue (uint8_t hue) __attribute__((always_inline)) {
|
||||
hsv2rgb_rainbow(hue<<8, 255, 255, raw, false); return *this;
|
||||
}
|
||||
|
||||
// allow assignment from HSV color
|
||||
inline CRGB& operator= (const CHSV& rhs) __attribute__((always_inline)) {
|
||||
hsv2rgb_rainbow(rhs.h<<8, rhs.s, rhs.v, raw, false); return *this;
|
||||
}
|
||||
// allow assignment from one RGB struct to another
|
||||
inline CRGB& operator= (const CRGB& rhs) __attribute__((always_inline)) = default;
|
||||
|
||||
// allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code
|
||||
inline CRGB& operator= (const uint32_t colorcode) __attribute__((always_inline)) {
|
||||
r = uint8_t(colorcode>>16);
|
||||
g = uint8_t(colorcode>>8);
|
||||
b = uint8_t(colorcode);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// allow assignment from red, green, and blue
|
||||
inline CRGB& setRGB (uint8_t nr, uint8_t ng, uint8_t nb) __attribute__((always_inline)) {
|
||||
r = nr;
|
||||
g = ng;
|
||||
b = nb;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// allow assignment from 32-bit (really 24-bit) 0xRRGGBB color code
|
||||
inline CRGB& setColorCode (uint32_t colorcode) __attribute__((always_inline)) {
|
||||
r = uint8_t(colorcode>>16);
|
||||
g = uint8_t(colorcode>>8);
|
||||
b = uint8_t(colorcode);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// add one CRGB to another, saturating at 0xFF for each channel
|
||||
inline CRGB& operator+= (const CRGB& rhs) {
|
||||
r = qadd8(r, rhs.r);
|
||||
g = qadd8(g, rhs.g);
|
||||
b = qadd8(b, rhs.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// add a constant to each channel, saturating at 0xFF
|
||||
inline CRGB& addToRGB (uint8_t d) {
|
||||
r = qadd8(r, d);
|
||||
g = qadd8(g, d);
|
||||
b = qadd8(b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// subtract one CRGB from another, saturating at 0x00 for each channel
|
||||
inline CRGB& operator-= (const CRGB& rhs) {
|
||||
r = qsub8(r, rhs.r);
|
||||
g = qsub8(g, rhs.g);
|
||||
b = qsub8(b, rhs.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// subtract a constant from each channel, saturating at 0x00
|
||||
inline CRGB& subtractFromRGB(uint8_t d) {
|
||||
r = qsub8(r, d);
|
||||
g = qsub8(g, d);
|
||||
b = qsub8(b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// subtract a constant of '1' from each channel, saturating at 0x00
|
||||
inline CRGB& operator-- () __attribute__((always_inline)) {
|
||||
subtractFromRGB(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// operator--
|
||||
inline CRGB operator-- (int) __attribute__((always_inline)) {
|
||||
CRGB retval(*this);
|
||||
--(*this);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// add a constant of '1' to each channel, saturating at 0xFF
|
||||
inline CRGB& operator++ () __attribute__((always_inline)) {
|
||||
addToRGB(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// operator++
|
||||
inline CRGB operator++ (int) __attribute__((always_inline)) {
|
||||
CRGB retval(*this);
|
||||
++(*this);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// divide each of the channels by a constant
|
||||
inline CRGB& operator/= (uint8_t d) {
|
||||
r /= d;
|
||||
g /= d;
|
||||
b /= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// right shift each of the channels by a constant
|
||||
inline CRGB& operator>>= (uint8_t d) {
|
||||
r >>= d;
|
||||
g >>= d;
|
||||
b >>= d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// multiply each of the channels by a constant, saturating each channel at 0xFF.
|
||||
inline CRGB& operator*= (uint8_t d) {
|
||||
r = qmul8(r, d);
|
||||
g = qmul8(g, d);
|
||||
b = qmul8(b, d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// scale down a RGB to N/256ths of its current brightness (will not scale all the way to black)
|
||||
inline CRGB& nscale8_video(uint8_t scaledown) {
|
||||
uint8_t nonzeroscale = (scaledown != 0) ? 1 : 0;
|
||||
r = (r == 0) ? 0 : (((int)r * (int)(scaledown)) >> 8) + nonzeroscale;
|
||||
g = (g == 0) ? 0 : (((int)g * (int)(scaledown)) >> 8) + nonzeroscale;
|
||||
b = (b == 0) ? 0 : (((int)b * (int)(scaledown)) >> 8) + nonzeroscale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// scale down a RGB to N/256ths of its current brightness (can scale to black)
|
||||
inline CRGB& nscale8(uint8_t scaledown) {
|
||||
uint32_t scale_fixed = scaledown + 1;
|
||||
r = (((uint32_t)r) * scale_fixed) >> 8;
|
||||
g = (((uint32_t)g) * scale_fixed) >> 8;
|
||||
b = (((uint32_t)b) * scale_fixed) >> 8;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CRGB& nscale8(const CRGB& scaledown) {
|
||||
r = ::scale8(r, scaledown.r);
|
||||
g = ::scale8(g, scaledown.g);
|
||||
b = ::scale8(b, scaledown.b);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// return a CRGB object that is a scaled down version of this object
|
||||
inline CRGB scale8(uint8_t scaledown) const {
|
||||
CRGB out = *this;
|
||||
uint32_t scale_fixed = scaledown + 1;
|
||||
out.r = (((uint32_t)out.r) * scale_fixed) >> 8;
|
||||
out.g = (((uint32_t)out.g) * scale_fixed) >> 8;
|
||||
out.b = (((uint32_t)out.b) * scale_fixed) >> 8;
|
||||
return out;
|
||||
}
|
||||
|
||||
// return a CRGB object that is a scaled down version of this object
|
||||
inline CRGB scale8(const CRGB& scaledown) const {
|
||||
CRGB out;
|
||||
out.r = ::scale8(r, scaledown.r);
|
||||
out.g = ::scale8(g, scaledown.g);
|
||||
out.b = ::scale8(b, scaledown.b);
|
||||
return out;
|
||||
}
|
||||
|
||||
// fadeToBlackBy is a synonym for nscale8(), as a fade instead of a scale
|
||||
inline CRGB& fadeToBlackBy(uint8_t fadefactor) {
|
||||
uint32_t scale_fixed = 256 - fadefactor;
|
||||
r = (((uint32_t)r) * scale_fixed) >> 8;
|
||||
g = (((uint32_t)g) * scale_fixed) >> 8;
|
||||
b = (((uint32_t)b) * scale_fixed) >> 8;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// "or" operator brings each channel up to the higher of the two values
|
||||
inline CRGB& operator|=(const CRGB& rhs) {
|
||||
if (rhs.r > r) r = rhs.r;
|
||||
if (rhs.g > g) g = rhs.g;
|
||||
if (rhs.b > b) b = rhs.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CRGB& operator|=(uint8_t d) {
|
||||
if (d > r) r = d;
|
||||
if (d > g) g = d;
|
||||
if (d > b) b = d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// "and" operator brings each channel down to the lower of the two values
|
||||
inline CRGB& operator&=(const CRGB& rhs) {
|
||||
if (rhs.r < r) r = rhs.r;
|
||||
if (rhs.g < g) g = rhs.g;
|
||||
if (rhs.b < b) b = rhs.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline CRGB& operator&=(uint8_t d) {
|
||||
if (d < r) r = d;
|
||||
if (d < g) g = d;
|
||||
if (d < b) b = d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// this allows testing a CRGB for zero-ness
|
||||
inline explicit operator bool() const __attribute__((always_inline)) {
|
||||
return r || g || b;
|
||||
}
|
||||
|
||||
// converts a CRGB to a 32-bit color with white = 0
|
||||
inline explicit operator uint32_t() const {
|
||||
return (uint32_t{r} << 16) |
|
||||
(uint32_t{g} << 8) |
|
||||
uint32_t{b};
|
||||
}
|
||||
|
||||
// invert each channel
|
||||
inline CRGB operator-() const {
|
||||
CRGB retval;
|
||||
retval.r = 255 - r;
|
||||
retval.g = 255 - g;
|
||||
retval.b = 255 - b;
|
||||
return retval;
|
||||
}
|
||||
|
||||
// get the average of the R, G, and B values
|
||||
inline uint8_t getAverageLight() const {
|
||||
return ((r + g + b) * 21846) >> 16; // x*21846>>16 is equal to "divide by 3"
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
AliceBlue=0xF0F8FF,
|
||||
Amethyst=0x9966CC,
|
||||
AntiqueWhite=0xFAEBD7,
|
||||
Aqua=0x00FFFF,
|
||||
Aquamarine=0x7FFFD4,
|
||||
Azure=0xF0FFFF,
|
||||
Beige=0xF5F5DC,
|
||||
Bisque=0xFFE4C4,
|
||||
Black=0x000000,
|
||||
BlanchedAlmond=0xFFEBCD,
|
||||
Blue=0x0000FF,
|
||||
BlueViolet=0x8A2BE2,
|
||||
Brown=0xA52A2A,
|
||||
BurlyWood=0xDEB887,
|
||||
CadetBlue=0x5F9EA0,
|
||||
Chartreuse=0x7FFF00,
|
||||
Chocolate=0xD2691E,
|
||||
Coral=0xFF7F50,
|
||||
CornflowerBlue=0x6495ED,
|
||||
Cornsilk=0xFFF8DC,
|
||||
Crimson=0xDC143C,
|
||||
Cyan=0x00FFFF,
|
||||
DarkBlue=0x00008B,
|
||||
DarkCyan=0x008B8B,
|
||||
DarkGoldenrod=0xB8860B,
|
||||
DarkGray=0xA9A9A9,
|
||||
DarkGrey=0xA9A9A9,
|
||||
DarkGreen=0x006400,
|
||||
DarkKhaki=0xBDB76B,
|
||||
DarkMagenta=0x8B008B,
|
||||
DarkOliveGreen=0x556B2F,
|
||||
DarkOrange=0xFF8C00,
|
||||
DarkOrchid=0x9932CC,
|
||||
DarkRed=0x8B0000,
|
||||
DarkSalmon=0xE9967A,
|
||||
DarkSeaGreen=0x8FBC8F,
|
||||
DarkSlateBlue=0x483D8B,
|
||||
DarkSlateGray=0x2F4F4F,
|
||||
DarkSlateGrey=0x2F4F4F,
|
||||
DarkTurquoise=0x00CED1,
|
||||
DarkViolet=0x9400D3,
|
||||
DeepPink=0xFF1493,
|
||||
DeepSkyBlue=0x00BFFF,
|
||||
DimGray=0x696969,
|
||||
DimGrey=0x696969,
|
||||
DodgerBlue=0x1E90FF,
|
||||
FireBrick=0xB22222,
|
||||
FloralWhite=0xFFFAF0,
|
||||
ForestGreen=0x228B22,
|
||||
Fuchsia=0xFF00FF,
|
||||
Gainsboro=0xDCDCDC,
|
||||
GhostWhite=0xF8F8FF,
|
||||
Gold=0xFFD700,
|
||||
Goldenrod=0xDAA520,
|
||||
Gray=0x808080,
|
||||
Grey=0x808080,
|
||||
Green=0x008000,
|
||||
GreenYellow=0xADFF2F,
|
||||
Honeydew=0xF0FFF0,
|
||||
HotPink=0xFF69B4,
|
||||
IndianRed=0xCD5C5C,
|
||||
Indigo=0x4B0082,
|
||||
Ivory=0xFFFFF0,
|
||||
Khaki=0xF0E68C,
|
||||
Lavender=0xE6E6FA,
|
||||
LavenderBlush=0xFFF0F5,
|
||||
LawnGreen=0x7CFC00,
|
||||
LemonChiffon=0xFFFACD,
|
||||
LightBlue=0xADD8E6,
|
||||
LightCoral=0xF08080,
|
||||
LightCyan=0xE0FFFF,
|
||||
LightGoldenrodYellow=0xFAFAD2,
|
||||
LightGreen=0x90EE90,
|
||||
LightGrey=0xD3D3D3,
|
||||
LightPink=0xFFB6C1,
|
||||
LightSalmon=0xFFA07A,
|
||||
LightSeaGreen=0x20B2AA,
|
||||
LightSkyBlue=0x87CEFA,
|
||||
LightSlateGray=0x778899,
|
||||
LightSlateGrey=0x778899,
|
||||
LightSteelBlue=0xB0C4DE,
|
||||
LightYellow=0xFFFFE0,
|
||||
Lime=0x00FF00,
|
||||
LimeGreen=0x32CD32,
|
||||
Linen=0xFAF0E6,
|
||||
Magenta=0xFF00FF,
|
||||
Maroon=0x800000,
|
||||
MediumAquamarine=0x66CDAA,
|
||||
MediumBlue=0x0000CD,
|
||||
MediumOrchid=0xBA55D3,
|
||||
MediumPurple=0x9370DB,
|
||||
MediumSeaGreen=0x3CB371,
|
||||
MediumSlateBlue=0x7B68EE,
|
||||
MediumSpringGreen=0x00FA9A,
|
||||
MediumTurquoise=0x48D1CC,
|
||||
MediumVioletRed=0xC71585,
|
||||
MidnightBlue=0x191970,
|
||||
MintCream=0xF5FFFA,
|
||||
MistyRose=0xFFE4E1,
|
||||
Moccasin=0xFFE4B5,
|
||||
NavajoWhite=0xFFDEAD,
|
||||
Navy=0x000080,
|
||||
OldLace=0xFDF5E6,
|
||||
Olive=0x808000,
|
||||
OliveDrab=0x6B8E23,
|
||||
Orange=0xFFA500,
|
||||
OrangeRed=0xFF4500,
|
||||
Orchid=0xDA70D6,
|
||||
PaleGoldenrod=0xEEE8AA,
|
||||
PaleGreen=0x98FB98,
|
||||
PaleTurquoise=0xAFEEEE,
|
||||
PaleVioletRed=0xDB7093,
|
||||
PapayaWhip=0xFFEFD5,
|
||||
PeachPuff=0xFFDAB9,
|
||||
Peru=0xCD853F,
|
||||
Pink=0xFFC0CB,
|
||||
Plaid=0xCC5533,
|
||||
Plum=0xDDA0DD,
|
||||
PowderBlue=0xB0E0E6,
|
||||
Purple=0x800080,
|
||||
Red=0xFF0000,
|
||||
RosyBrown=0xBC8F8F,
|
||||
RoyalBlue=0x4169E1,
|
||||
SaddleBrown=0x8B4513,
|
||||
Salmon=0xFA8072,
|
||||
SandyBrown=0xF4A460,
|
||||
SeaGreen=0x2E8B57,
|
||||
Seashell=0xFFF5EE,
|
||||
Sienna=0xA0522D,
|
||||
Silver=0xC0C0C0,
|
||||
SkyBlue=0x87CEEB,
|
||||
SlateBlue=0x6A5ACD,
|
||||
SlateGray=0x708090,
|
||||
SlateGrey=0x708090,
|
||||
Snow=0xFFFAFA,
|
||||
SpringGreen=0x00FF7F,
|
||||
SteelBlue=0x4682B4,
|
||||
Tan=0xD2B48C,
|
||||
Teal=0x008080,
|
||||
Thistle=0xD8BFD8,
|
||||
Tomato=0xFF6347,
|
||||
Turquoise=0x40E0D0,
|
||||
Violet=0xEE82EE,
|
||||
Wheat=0xF5DEB3,
|
||||
White=0xFFFFFF,
|
||||
WhiteSmoke=0xF5F5F5,
|
||||
Yellow=0xFFFF00,
|
||||
YellowGreen=0x9ACD32,
|
||||
FairyLight=0xFFE42D, // LED RGB color that roughly approximates the color of incandescent fairy lights
|
||||
FairyLightNCC=0xFF9D2A // if using no color correction, use this
|
||||
} HTMLColorCode;
|
||||
};
|
||||
|
||||
__attribute__((always_inline)) inline CRGB operator+(const CRGB& p1, const CRGB& p2) {
|
||||
return CRGB(qadd8(p1.r, p2.r), qadd8(p1.g, p2.g), qadd8(p1.b, p2.b));
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline CRGB operator-(const CRGB& p1, const CRGB& p2) {
|
||||
return CRGB(qsub8(p1.r, p2.r), qsub8(p1.g, p2.g), qsub8(p1.b, p2.b));
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline bool operator== (const CRGB& lhs, const CRGB& rhs) {
|
||||
return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline bool operator!= (const CRGB& lhs, const CRGB& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// RGB color palette with 16 discrete values
|
||||
class CRGBPalette16 {
|
||||
public:
|
||||
CRGB entries[16];
|
||||
CRGBPalette16() {
|
||||
memset(entries, 0, sizeof(entries)); // default constructor: set all to black
|
||||
}
|
||||
|
||||
// Create palette from 16 CRGB values
|
||||
CRGBPalette16(const CRGB& c00, const CRGB& c01, const CRGB& c02, const CRGB& c03,
|
||||
const CRGB& c04, const CRGB& c05, const CRGB& c06, const CRGB& c07,
|
||||
const CRGB& c08, const CRGB& c09, const CRGB& c10, const CRGB& c11,
|
||||
const CRGB& c12, const CRGB& c13, const CRGB& c14, const CRGB& c15) {
|
||||
entries[0] = c00; entries[1] = c01; entries[2] = c02; entries[3] = c03;
|
||||
entries[4] = c04; entries[5] = c05; entries[6] = c06; entries[7] = c07;
|
||||
entries[8] = c08; entries[9] = c09; entries[10] = c10; entries[11] = c11;
|
||||
entries[12] = c12; entries[13] = c13; entries[14] = c14; entries[15] = c15;
|
||||
};
|
||||
|
||||
// Copy constructor
|
||||
CRGBPalette16(const CRGBPalette16& rhs) {
|
||||
memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries));
|
||||
}
|
||||
|
||||
// Create palette from array of CRGB colors
|
||||
CRGBPalette16(const CRGB rhs[16]) {
|
||||
memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries));
|
||||
}
|
||||
|
||||
// Copy assignment operator
|
||||
CRGBPalette16& operator=(const CRGBPalette16& rhs) {
|
||||
memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Create palette from array of CRGB colors
|
||||
CRGBPalette16& operator=(const CRGB rhs[16]) {
|
||||
memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Create palette from palette stored in PROGMEM
|
||||
CRGBPalette16(const TProgmemRGBPalette16& rhs) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
entries[i] = pgm_read_dword(rhs + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy assignment operator for PROGMEM palette
|
||||
CRGBPalette16& operator=(const TProgmemRGBPalette16& rhs) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
entries[i] = pgm_read_dword(rhs + i);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Equality operator
|
||||
bool operator==(const CRGBPalette16& rhs) const {
|
||||
const uint8_t* p = (const uint8_t*)(&(this->entries[0]));
|
||||
const uint8_t* q = (const uint8_t*)(&(rhs.entries[0]));
|
||||
if (p == q) return true;
|
||||
for (unsigned i = 0; i < (sizeof(entries)); ++i) {
|
||||
if (*p != *q) return false;
|
||||
++p;
|
||||
++q;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inequality operator
|
||||
bool operator!=(const CRGBPalette16& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
// Array subscript operator
|
||||
inline CRGB& operator[](uint8_t x) __attribute__((always_inline)) {
|
||||
return entries[x];
|
||||
}
|
||||
|
||||
// Array subscript operator (const)
|
||||
inline const CRGB& operator[](uint8_t x) const __attribute__((always_inline)) {
|
||||
return entries[x];
|
||||
}
|
||||
|
||||
// Array subscript operator
|
||||
inline CRGB& operator[](int x) __attribute__((always_inline)) {
|
||||
return entries[(uint8_t)x];
|
||||
}
|
||||
|
||||
// Array subscript operator (const)
|
||||
inline const CRGB& operator[](int x) const __attribute__((always_inline)) {
|
||||
return entries[(uint8_t)x];
|
||||
}
|
||||
|
||||
// Get the underlying pointer to the CRGB entries making up the palette
|
||||
operator CRGB*() {
|
||||
return &(entries[0]);
|
||||
}
|
||||
|
||||
// Create palette from a single CRGB color
|
||||
CRGBPalette16(const CRGB& c1) {
|
||||
fill_solid_RGB(&(entries[0]), 16, c1);
|
||||
}
|
||||
|
||||
// Create palette from two CRGB colors
|
||||
CRGBPalette16(const CRGB& c1, const CRGB& c2) {
|
||||
fill_gradient_RGB(&(entries[0]), 16, c1, c2);
|
||||
}
|
||||
|
||||
// Create palette from three CRGB colors
|
||||
CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3) {
|
||||
fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3);
|
||||
}
|
||||
|
||||
// Create palette from four CRGB colors
|
||||
CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4) {
|
||||
fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3, c4);
|
||||
}
|
||||
|
||||
// Creates a palette from a gradient palette in PROGMEM.
|
||||
//
|
||||
// Gradient palettes are loaded into CRGBPalettes in such a way
|
||||
// that, if possible, every color represented in the gradient palette
|
||||
// is also represented in the CRGBPalette, this may not preserve original
|
||||
// color spacing, but will try to not omit small color bands.
|
||||
|
||||
CRGBPalette16(TProgmemRGBGradientPalette_bytes progpal) {
|
||||
*this = progpal;
|
||||
}
|
||||
|
||||
CRGBPalette16& operator=(TProgmemRGBGradientPalette_bytes progpal) {
|
||||
TRGBGradientPaletteEntryUnion* progent = (TRGBGradientPaletteEntryUnion*)(progpal);
|
||||
TRGBGradientPaletteEntryUnion u;
|
||||
|
||||
// Count entries
|
||||
int count = 0;
|
||||
do {
|
||||
u.dword = pgm_read_dword(progent + count);
|
||||
++count;
|
||||
} while (u.index != 255);
|
||||
|
||||
int lastSlotUsed = -1;
|
||||
|
||||
u.dword = pgm_read_dword(progent);
|
||||
CRGB rgbstart(u.r, u.g, u.b);
|
||||
|
||||
int indexstart = 0;
|
||||
int istart8 = 0;
|
||||
int iend8 = 0;
|
||||
while (indexstart < 255) {
|
||||
++progent;
|
||||
u.dword = pgm_read_dword(progent);
|
||||
int indexend = u.index;
|
||||
CRGB rgbend(u.r, u.g, u.b);
|
||||
istart8 = indexstart / 16;
|
||||
iend8 = indexend / 16;
|
||||
if (count < 16) {
|
||||
if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) {
|
||||
istart8 = lastSlotUsed + 1;
|
||||
if (iend8 < istart8) {
|
||||
iend8 = istart8;
|
||||
}
|
||||
}
|
||||
lastSlotUsed = iend8;
|
||||
}
|
||||
fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend);
|
||||
indexstart = indexend;
|
||||
rgbstart = rgbend;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Creates a palette from a gradient palette in dynamic (heap) memory.
|
||||
CRGBPalette16& loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) {
|
||||
TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(gpal);
|
||||
TRGBGradientPaletteEntryUnion u;
|
||||
|
||||
// Count entries
|
||||
unsigned count = 0;
|
||||
do {
|
||||
u = *(ent + count);
|
||||
++count;
|
||||
} while (u.index != 255);
|
||||
|
||||
int lastSlotUsed = -1;
|
||||
|
||||
u = *ent;
|
||||
CRGB rgbstart(u.r, u.g, u.b);
|
||||
|
||||
int indexstart = 0;
|
||||
int istart8 = 0;
|
||||
int iend8 = 0;
|
||||
while (indexstart < 255) {
|
||||
++ent;
|
||||
u = *ent;
|
||||
int indexend = u.index;
|
||||
CRGB rgbend(u.r, u.g, u.b);
|
||||
istart8 = indexstart / 16;
|
||||
iend8 = indexend / 16;
|
||||
if (count < 16) {
|
||||
if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) {
|
||||
istart8 = lastSlotUsed + 1;
|
||||
if (iend8 < istart8) {
|
||||
iend8 = istart8;
|
||||
}
|
||||
}
|
||||
lastSlotUsed = iend8;
|
||||
}
|
||||
fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend);
|
||||
indexstart = indexend;
|
||||
rgbstart = rgbend;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// font c64esque is public domain, source: https://nimblebeastscollective.itch.io/nb-pixel-font-bundle-2 - "^ + - *" glyphs modified by @dedeheai
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 9
|
||||
* [2] Fixed/max glyph width: 7
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
// Font: c64esque_9px
|
||||
// Height: 9, Max Width: 7, Spacing: 1
|
||||
// Characters: 32-126 (95 glyphs)
|
||||
// Unicode Offset: 0x00000000
|
||||
// Variable Width: Yes
|
||||
|
||||
static const unsigned char c64esque_9px[] PROGMEM = {
|
||||
0x57, 0x09, 0x07, 0x01, 0x01, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: 'W', H, W, S, Flags, First, Last, Reserved, UnicodeOffset (32bit)
|
||||
|
||||
// Width table
|
||||
0x02, 0x02, 0x05, 0x06, 0x06, 0x06, 0x07, 0x02, 0x04, 0x04, 0x05, 0x06, 0x03, 0x04, 0x02, 0x05,
|
||||
0x06, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x02, 0x03, 0x05, 0x06, 0x05, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x02, 0x04, 0x06, 0x06, 0x07, 0x06, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x04, 0x05, 0x04, 0x06, 0x06,
|
||||
0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x02, 0x04, 0x06, 0x03, 0x07, 0x06, 0x06,
|
||||
0x06, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x04, 0x02, 0x04, 0x06,
|
||||
|
||||
0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" ", w=2 */
|
||||
0x3F, 0xF3, 0x00, /* code=33, hex=0x21, ascii="!", w=2 */
|
||||
0x06, 0xF6, 0x00, 0x00, 0x00, 0x00, /* code=34, hex=0x22, ascii=""", w=5 */
|
||||
0x00, 0x04, 0xBF, 0x49, 0x2F, 0xD2, 0x00, /* code=35, hex=0x23, ascii="#", w=6 */
|
||||
0x00, 0xC7, 0xF0, 0x78, 0x3F, 0x8C, 0x00, /* code=36, hex=0x24, ascii="$", w=6 */
|
||||
0x03, 0x3C, 0xC6, 0x31, 0x8C, 0xF3, 0x00, /* code=37, hex=0x25, ascii="%", w=6 */
|
||||
0x00, 0xF3, 0x06, 0x07, 0xF9, 0xB3, 0x3C, 0x00, /* code=38, hex=0x26, ascii="&", w=7 */
|
||||
0x3C, 0x00, 0x00, /* code=39, hex=0x27, ascii="'", w=2 */
|
||||
0x36, 0xCC, 0xCC, 0xC6, 0x30, /* code=40, hex=0x28, ascii="(", w=4 */
|
||||
0xC6, 0x33, 0x33, 0x36, 0xC0, /* code=41, hex=0x29, ascii=")", w=4 */
|
||||
0x01, 0x2A, 0xEA, 0x90, 0x00, 0x00, /* code=42, hex=0x2A, ascii="*", w=5 */
|
||||
0x00, 0x03, 0x0C, 0xFF, 0xF3, 0x0C, 0x00, /* code=43, hex=0x2B, ascii="+", w=6 */
|
||||
0x00, 0x00, 0x1B, 0xC0, /* code=44, hex=0x2C, ascii=",", w=3 */
|
||||
0x00, 0x00, 0xFF, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-", w=4 */
|
||||
0x00, 0x0F, 0x00, /* code=46, hex=0x2E, ascii=".", w=2 */
|
||||
0x18, 0xCC, 0x66, 0x33, 0x18, 0x00, /* code=47, hex=0x2F, ascii="/", w=5 */
|
||||
0x01, 0xEC, 0xF7, 0xEF, 0x3C, 0xDE, 0x00, /* code=48, hex=0x30, ascii="0", w=6 */
|
||||
0x0F, 0xB6, 0xDB, 0x00, /* code=49, hex=0x31, ascii="1", w=3 */
|
||||
0x01, 0xEC, 0xC3, 0x18, 0xC6, 0x3F, 0x00, /* code=50, hex=0x32, ascii="2", w=6 */
|
||||
0x01, 0xEC, 0xC3, 0x38, 0x3C, 0xDE, 0x00, /* code=51, hex=0x33, ascii="3", w=6 */
|
||||
0x00, 0x63, 0x96, 0xDB, 0xF1, 0x86, 0x00, /* code=52, hex=0x34, ascii="4", w=6 */
|
||||
0x03, 0xFC, 0x30, 0xF8, 0x3C, 0xDE, 0x00, /* code=53, hex=0x35, ascii="5", w=6 */
|
||||
0x01, 0xEC, 0xF0, 0xFB, 0x3C, 0xDE, 0x00, /* code=54, hex=0x36, ascii="6", w=6 */
|
||||
0x03, 0xF0, 0xC6, 0x18, 0xC3, 0x0C, 0x00, /* code=55, hex=0x37, ascii="7", w=6 */
|
||||
0x01, 0xEC, 0xF3, 0x7B, 0x3C, 0xDE, 0x00, /* code=56, hex=0x38, ascii="8", w=6 */
|
||||
0x01, 0xEC, 0xF3, 0x7C, 0x3C, 0xDE, 0x00, /* code=57, hex=0x39, ascii="9", w=6 */
|
||||
0x03, 0xCF, 0x00, /* code=58, hex=0x3A, ascii=":", w=2 */
|
||||
0x00, 0x36, 0x1B, 0xC0, /* code=59, hex=0x3B, ascii=";", w=3 */
|
||||
0x00, 0xCC, 0xCC, 0x30, 0xC3, 0x00, /* code=60, hex=0x3C, ascii="<", w=5 */
|
||||
0x00, 0x00, 0x3F, 0x03, 0xF0, 0x00, 0x00, /* code=61, hex=0x3D, ascii="=", w=6 */
|
||||
0x06, 0x18, 0x61, 0x99, 0x98, 0x00, /* code=62, hex=0x3E, ascii=">", w=5 */
|
||||
0x01, 0xEC, 0xC3, 0x18, 0xC0, 0x0C, 0x00, /* code=63, hex=0x3F, ascii="?", w=6 */
|
||||
0x01, 0xEC, 0xF7, 0xDF, 0x7C, 0x1E, 0x00, /* code=64, hex=0x40, ascii="@", w=6 */
|
||||
0x00, 0xC7, 0xB3, 0xCF, 0xFC, 0xF3, 0x00, /* code=65, hex=0x41, ascii="A", w=6 */
|
||||
0x03, 0xEC, 0xF3, 0xFB, 0x3C, 0xFE, 0x00, /* code=66, hex=0x42, ascii="B", w=6 */
|
||||
0x01, 0xEC, 0xF0, 0xC3, 0x0C, 0xDE, 0x00, /* code=67, hex=0x43, ascii="C", w=6 */
|
||||
0x03, 0xEC, 0xF3, 0xCF, 0x3C, 0xFE, 0x00, /* code=68, hex=0x44, ascii="D", w=6 */
|
||||
0x03, 0xFC, 0x30, 0xFB, 0x0C, 0x3F, 0x00, /* code=69, hex=0x45, ascii="E", w=6 */
|
||||
0x03, 0xFC, 0x30, 0xFB, 0x0C, 0x30, 0x00, /* code=70, hex=0x46, ascii="F", w=6 */
|
||||
0x01, 0xEC, 0xF0, 0xDF, 0x3C, 0xDF, 0x00, /* code=71, hex=0x47, ascii="G", w=6 */
|
||||
0x03, 0x3C, 0xF3, 0xFF, 0x3C, 0xF3, 0x00, /* code=72, hex=0x48, ascii="H", w=6 */
|
||||
0x3F, 0xFF, 0x00, /* code=73, hex=0x49, ascii="I", w=2 */
|
||||
0x03, 0x33, 0x33, 0x33, 0xE0, /* code=74, hex=0x4A, ascii="J", w=4 */
|
||||
0x03, 0x3C, 0xF6, 0xF3, 0x6C, 0xF3, 0x00, /* code=75, hex=0x4B, ascii="K", w=6 */
|
||||
0x03, 0x0C, 0x30, 0xC3, 0x0C, 0x3F, 0x00, /* code=76, hex=0x4C, ascii="L", w=6 */
|
||||
0x01, 0x8F, 0xBF, 0xFD, 0x78, 0xF1, 0xE3, 0x00, /* code=77, hex=0x4D, ascii="M", w=7 */
|
||||
0x03, 0x3E, 0xFF, 0xDF, 0x3C, 0xF3, 0x00, /* code=78, hex=0x4E, ascii="N", w=6 */
|
||||
0x01, 0xEC, 0xF3, 0xCF, 0x3C, 0xDE, 0x00, /* code=79, hex=0x4F, ascii="O", w=6 */
|
||||
0x03, 0xEC, 0xF3, 0xFB, 0x0C, 0x30, 0x00, /* code=80, hex=0x50, ascii="P", w=6 */
|
||||
0x01, 0xEC, 0xF3, 0xCF, 0x3C, 0xDE, 0x0C, /* code=81, hex=0x51, ascii="Q", w=6 */
|
||||
0x03, 0xEC, 0xF3, 0xFB, 0x6C, 0xF3, 0x00, /* code=82, hex=0x52, ascii="R", w=6 */
|
||||
0x01, 0xEC, 0xF0, 0x78, 0x3C, 0xDE, 0x00, /* code=83, hex=0x53, ascii="S", w=6 */
|
||||
0x03, 0xF3, 0x0C, 0x30, 0xC3, 0x0C, 0x00, /* code=84, hex=0x54, ascii="T", w=6 */
|
||||
0x03, 0x3C, 0xF3, 0xCF, 0x3C, 0xDE, 0x00, /* code=85, hex=0x55, ascii="U", w=6 */
|
||||
0x03, 0x3C, 0xF3, 0xCF, 0x37, 0x8C, 0x00, /* code=86, hex=0x56, ascii="V", w=6 */
|
||||
0x01, 0x8F, 0x1E, 0x3D, 0x7F, 0xFB, 0xE3, 0x00, /* code=87, hex=0x57, ascii="W", w=7 */
|
||||
0x03, 0x3C, 0xDE, 0x31, 0xEC, 0xF3, 0x00, /* code=88, hex=0x58, ascii="X", w=6 */
|
||||
0x03, 0x3C, 0xF3, 0x78, 0xC3, 0x0C, 0x00, /* code=89, hex=0x59, ascii="Y", w=6 */
|
||||
0x03, 0xF0, 0xC6, 0x31, 0x8C, 0x3F, 0x00, /* code=90, hex=0x5A, ascii="Z", w=6 */
|
||||
0xFC, 0xCC, 0xCC, 0xCC, 0xF0, /* code=91, hex=0x5B, ascii="[", w=4 */
|
||||
0xC6, 0x18, 0xC3, 0x18, 0x63, 0x00, /* code=92, hex=0x5C, ascii="\", w=5 */
|
||||
0xF3, 0x33, 0x33, 0x33, 0xF0, /* code=93, hex=0x5D, ascii="]", w=4 */
|
||||
0x31, 0xEC, 0xC0, 0x00, 0x00, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^", w=6 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, /* code=95, hex=0x5F, ascii="_", w=6 */
|
||||
0xCC, 0x00, 0x00, 0x00, /* code=96, hex=0x60, ascii="`", w=3 */
|
||||
0x00, 0x00, 0x1E, 0x0D, 0xFC, 0xDF, 0x00, /* code=97, hex=0x61, ascii="a", w=6 */
|
||||
0x03, 0x0C, 0x3E, 0xCF, 0x3C, 0xFE, 0x00, /* code=98, hex=0x62, ascii="b", w=6 */
|
||||
0x00, 0x00, 0x1F, 0xC3, 0x0C, 0x1F, 0x00, /* code=99, hex=0x63, ascii="c", w=6 */
|
||||
0x00, 0x30, 0xDF, 0xCF, 0x3C, 0xDF, 0x00, /* code=100, hex=0x64, ascii="d", w=6 */
|
||||
0x00, 0x00, 0x1E, 0xCF, 0xFC, 0x1E, 0x00, /* code=101, hex=0x65, ascii="e", w=6 */
|
||||
0x01, 0xD8, 0xCF, 0xB1, 0x8C, 0x00, /* code=102, hex=0x66, ascii="f", w=5 */
|
||||
0x00, 0x00, 0x1F, 0xCF, 0x37, 0xC3, 0x78, /* code=103, hex=0x67, ascii="g", w=6 */
|
||||
0x03, 0x0C, 0x3E, 0xCF, 0x3C, 0xF3, 0x00, /* code=104, hex=0x68, ascii="h", w=6 */
|
||||
0x33, 0xFF, 0x00, /* code=105, hex=0x69, ascii="i", w=2 */
|
||||
0x03, 0x03, 0x33, 0x33, 0xE0, /* code=106, hex=0x6A, ascii="j", w=4 */
|
||||
0x03, 0x0C, 0x33, 0xDB, 0xCD, 0xB3, 0x00, /* code=107, hex=0x6B, ascii="k", w=6 */
|
||||
0x1D, 0xB6, 0xDB, 0x00, /* code=108, hex=0x6C, ascii="l", w=3 */
|
||||
0x00, 0x00, 0x07, 0xED, 0x7A, 0xF1, 0xE3, 0x00, /* code=109, hex=0x6D, ascii="m", w=7 */
|
||||
0x00, 0x00, 0x3E, 0xCF, 0x3C, 0xF3, 0x00, /* code=110, hex=0x6E, ascii="n", w=6 */
|
||||
0x00, 0x00, 0x1E, 0xCF, 0x3C, 0xDE, 0x00, /* code=111, hex=0x6F, ascii="o", w=6 */
|
||||
0x00, 0x00, 0x3E, 0xCF, 0x3F, 0xB0, 0xC0, /* code=112, hex=0x70, ascii="p", w=6 */
|
||||
0x00, 0x00, 0x1F, 0xCF, 0x37, 0xC3, 0x0C, /* code=113, hex=0x71, ascii="q", w=6 */
|
||||
0x00, 0x00, 0x3E, 0xCF, 0x0C, 0x30, 0x00, /* code=114, hex=0x72, ascii="r", w=6 */
|
||||
0x00, 0x00, 0x1F, 0xC1, 0xE0, 0xFE, 0x00, /* code=115, hex=0x73, ascii="s", w=6 */
|
||||
0x03, 0x19, 0xF6, 0x31, 0x87, 0x00, /* code=116, hex=0x74, ascii="t", w=5 */
|
||||
0x00, 0x00, 0x33, 0xCF, 0x3C, 0xDF, 0x00, /* code=117, hex=0x75, ascii="u", w=6 */
|
||||
0x00, 0x00, 0x33, 0xCF, 0x37, 0x8C, 0x00, /* code=118, hex=0x76, ascii="v", w=6 */
|
||||
0x00, 0x00, 0x06, 0x3C, 0x7A, 0xF5, 0xBE, 0x00, /* code=119, hex=0x77, ascii="w", w=7 */
|
||||
0x00, 0x00, 0x33, 0x78, 0xC7, 0xB3, 0x00, /* code=120, hex=0x78, ascii="x", w=6 */
|
||||
0x00, 0x00, 0x33, 0xCF, 0x37, 0xC3, 0x78, /* code=121, hex=0x79, ascii="y", w=6 */
|
||||
0x00, 0x00, 0x3F, 0x18, 0xC6, 0x3F, 0x00, /* code=122, hex=0x7A, ascii="z", w=6 */
|
||||
0x36, 0x66, 0xC6, 0x66, 0x30, /* code=123, hex=0x7B, ascii="{", w=4 */
|
||||
0xFF, 0xFF, 0xC0, /* code=124, hex=0x7C, ascii="|", w=2 */
|
||||
0xC6, 0x66, 0x36, 0x66, 0xC0, /* code=125, hex=0x7D, ascii="}", w=4 */
|
||||
0x00, 0x00, 0x19, 0xFE, 0x60, 0x00, 0x00 /* code=126, hex=0x7E, ascii="~", w=6 */
|
||||
};
|
||||
Binary file not shown.
2566
wled00/src/font/console_font_4x6.h
Normal file
2566
wled00/src/font/console_font_4x6.h
Normal file
File diff suppressed because it is too large
Load Diff
4102
wled00/src/font/console_font_5x12.h
Normal file
4102
wled00/src/font/console_font_5x12.h
Normal file
File diff suppressed because it is too large
Load Diff
3078
wled00/src/font/console_font_5x8.h
Normal file
3078
wled00/src/font/console_font_5x8.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
3334
wled00/src/font/console_font_7x9.h
Normal file
3334
wled00/src/font/console_font_7x9.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,153 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// source font "6x13.bdf" is public domain, source: https://github.com/IT-Studio-Rech/bdf-fonts modified by @dedehai (cleaner dots)
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 12
|
||||
* [2] Fixed/max glyph width: 5
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
// Font: 5x12
|
||||
// Height: 12, Max Width: 5, Spacing: 1
|
||||
// Characters: 32-126 (95 glyphs)
|
||||
// Unicode Offset: 0x00000000
|
||||
// Variable Width: Yes
|
||||
|
||||
static const unsigned char font_5x12[] PROGMEM = {
|
||||
0x57, 0x0C, 0x05, 0x01, 0x01, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: 'W', H, W, S, Flags, First, Last, Reserved, UnicodeOffset (32bit)
|
||||
|
||||
// Width table
|
||||
0x02, 0x01, 0x03, 0x05, 0x05, 0x05, 0x05, 0x01, 0x03, 0x03, 0x05, 0x05, 0x03, 0x05, 0x02, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x02, 0x03, 0x05, 0x05, 0x05, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x03, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x03, 0x05, 0x03, 0x05, 0x05,
|
||||
0x02, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x03, 0x04, 0x05, 0x03, 0x05, 0x05, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x01, 0x05, 0x05,
|
||||
|
||||
0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" ", w=2 */
|
||||
0x7F, 0x40, /* code=33, hex=0x21, ascii="!", w=1 */
|
||||
0x16, 0xD0, 0x00, 0x00, 0x00, /* code=34, hex=0x22, ascii=""", w=3 */
|
||||
0x00, 0x14, 0xAF, 0xAB, 0xEA, 0x50, 0x00, 0x00, /* code=35, hex=0x23, ascii="#", w=5 */
|
||||
0x01, 0x1F, 0x4A, 0x38, 0xA5, 0xF1, 0x00, 0x00, /* code=36, hex=0x24, ascii="$", w=5 */
|
||||
0x02, 0x6A, 0xA1, 0x11, 0x0A, 0xAC, 0x80, 0x00, /* code=37, hex=0x25, ascii="%", w=5 */
|
||||
0x00, 0x11, 0x4A, 0x22, 0x93, 0x93, 0x40, 0x00, /* code=38, hex=0x26, ascii="&", w=5 */
|
||||
0x70, 0x00, /* code=39, hex=0x27, ascii="'", w=1 */
|
||||
0x29, 0x49, 0x24, 0x48, 0x80, /* code=40, hex=0x28, ascii="(", w=3 */
|
||||
0x89, 0x12, 0x49, 0x4A, 0x00, /* code=41, hex=0x29, ascii=")", w=3 */
|
||||
0x01, 0x2A, 0xEA, 0x90, 0x00, 0x00, 0x00, 0x00, /* code=42, hex=0x2A, ascii="*", w=5 */
|
||||
0x00, 0x00, 0x42, 0x7C, 0x84, 0x00, 0x00, 0x00, /* code=43, hex=0x2B, ascii="+", w=5 */
|
||||
0x00, 0x00, 0x00, 0x6A, 0x00, /* code=44, hex=0x2C, ascii=",", w=3 */
|
||||
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-", w=5 */
|
||||
0x00, 0x00, 0xF0, /* code=46, hex=0x2E, ascii=".", w=2 */
|
||||
0x00, 0x42, 0x21, 0x11, 0x08, 0x84, 0x00, 0x00, /* code=47, hex=0x2F, ascii="/", w=5 */
|
||||
0x01, 0x15, 0x18, 0xC6, 0x31, 0x51, 0x00, 0x00, /* code=48, hex=0x30, ascii="0", w=5 */
|
||||
0x01, 0x19, 0x42, 0x10, 0x84, 0x27, 0xC0, 0x00, /* code=49, hex=0x31, ascii="1", w=5 */
|
||||
0x03, 0xA3, 0x10, 0x88, 0x88, 0x87, 0xC0, 0x00, /* code=50, hex=0x32, ascii="2", w=5 */
|
||||
0x07, 0xC2, 0x22, 0x38, 0x21, 0x8B, 0x80, 0x00, /* code=51, hex=0x33, ascii="3", w=5 */
|
||||
0x00, 0x84, 0x65, 0x2A, 0x5F, 0x10, 0x80, 0x00, /* code=52, hex=0x34, ascii="4", w=5 */
|
||||
0x07, 0xE1, 0x0B, 0x64, 0x21, 0x8B, 0x80, 0x00, /* code=53, hex=0x35, ascii="5", w=5 */
|
||||
0x03, 0xA3, 0x08, 0x7A, 0x31, 0x8B, 0x80, 0x00, /* code=54, hex=0x36, ascii="6", w=5 */
|
||||
0x07, 0xC2, 0x21, 0x10, 0x88, 0x42, 0x00, 0x00, /* code=55, hex=0x37, ascii="7", w=5 */
|
||||
0x03, 0xA3, 0x18, 0xBA, 0x31, 0x8B, 0x80, 0x00, /* code=56, hex=0x38, ascii="8", w=5 */
|
||||
0x03, 0xA3, 0x18, 0xBC, 0x21, 0x8B, 0x80, 0x00, /* code=57, hex=0x39, ascii="9", w=5 */
|
||||
0x03, 0xC0, 0xF0, /* code=58, hex=0x3A, ascii=":", w=2 */
|
||||
0x00, 0x36, 0x00, 0x6A, 0x00, /* code=59, hex=0x3B, ascii=";", w=3 */
|
||||
0x00, 0x44, 0x44, 0x41, 0x04, 0x10, 0x40, 0x00, /* code=60, hex=0x3C, ascii="<", w=5 */
|
||||
0x00, 0x00, 0x0F, 0x80, 0x1F, 0x00, 0x00, 0x00, /* code=61, hex=0x3D, ascii="=", w=5 */
|
||||
0x04, 0x10, 0x41, 0x04, 0x44, 0x44, 0x00, 0x00, /* code=62, hex=0x3E, ascii=">", w=5 */
|
||||
0x03, 0xA3, 0x10, 0x88, 0x84, 0x01, 0x00, 0x00, /* code=63, hex=0x3F, ascii="?", w=5 */
|
||||
0x03, 0xA3, 0x19, 0xD6, 0xB6, 0x83, 0xC0, 0x00, /* code=64, hex=0x40, ascii="@", w=5 */
|
||||
0x01, 0x15, 0x18, 0xC7, 0xF1, 0x8C, 0x40, 0x00, /* code=65, hex=0x41, ascii="A", w=5 */
|
||||
0x07, 0x92, 0x94, 0xB9, 0x29, 0x4F, 0x80, 0x00, /* code=66, hex=0x42, ascii="B", w=5 */
|
||||
0x03, 0xA3, 0x08, 0x42, 0x10, 0x8B, 0x80, 0x00, /* code=67, hex=0x43, ascii="C", w=5 */
|
||||
0x07, 0x92, 0x94, 0xA5, 0x29, 0x4F, 0x80, 0x00, /* code=68, hex=0x44, ascii="D", w=5 */
|
||||
0x07, 0xE1, 0x08, 0x7A, 0x10, 0x87, 0xC0, 0x00, /* code=69, hex=0x45, ascii="E", w=5 */
|
||||
0x07, 0xE1, 0x08, 0x7A, 0x10, 0x84, 0x00, 0x00, /* code=70, hex=0x46, ascii="F", w=5 */
|
||||
0x03, 0xA3, 0x08, 0x42, 0x71, 0x8B, 0x80, 0x00, /* code=71, hex=0x47, ascii="G", w=5 */
|
||||
0x04, 0x63, 0x18, 0xFE, 0x31, 0x8C, 0x40, 0x00, /* code=72, hex=0x48, ascii="H", w=5 */
|
||||
0x1D, 0x24, 0x92, 0x5C, 0x00, /* code=73, hex=0x49, ascii="I", w=3 */
|
||||
0x01, 0xC4, 0x21, 0x08, 0x42, 0x93, 0x00, 0x00, /* code=74, hex=0x4A, ascii="J", w=5 */
|
||||
0x04, 0x63, 0x2A, 0x62, 0x92, 0x8C, 0x40, 0x00, /* code=75, hex=0x4B, ascii="K", w=5 */
|
||||
0x04, 0x21, 0x08, 0x42, 0x10, 0x87, 0xC0, 0x00, /* code=76, hex=0x4C, ascii="L", w=5 */
|
||||
0x04, 0x63, 0xBA, 0xD6, 0x31, 0x8C, 0x40, 0x00, /* code=77, hex=0x4D, ascii="M", w=5 */
|
||||
0x04, 0x73, 0x9A, 0xD6, 0x73, 0x8C, 0x40, 0x00, /* code=78, hex=0x4E, ascii="N", w=5 */
|
||||
0x03, 0xA3, 0x18, 0xC6, 0x31, 0x8B, 0x80, 0x00, /* code=79, hex=0x4F, ascii="O", w=5 */
|
||||
0x07, 0xA3, 0x18, 0xFA, 0x10, 0x84, 0x00, 0x00, /* code=80, hex=0x50, ascii="P", w=5 */
|
||||
0x03, 0xA3, 0x18, 0xC6, 0x31, 0xAB, 0x82, 0x00, /* code=81, hex=0x51, ascii="Q", w=5 */
|
||||
0x07, 0xA3, 0x18, 0xFA, 0x92, 0x8C, 0x40, 0x00, /* code=82, hex=0x52, ascii="R", w=5 */
|
||||
0x03, 0xA3, 0x08, 0x38, 0x21, 0x8B, 0x80, 0x00, /* code=83, hex=0x53, ascii="S", w=5 */
|
||||
0x07, 0xC8, 0x42, 0x10, 0x84, 0x21, 0x00, 0x00, /* code=84, hex=0x54, ascii="T", w=5 */
|
||||
0x04, 0x63, 0x18, 0xC6, 0x31, 0x8B, 0x80, 0x00, /* code=85, hex=0x55, ascii="U", w=5 */
|
||||
0x04, 0x63, 0x18, 0xA9, 0x4A, 0x21, 0x00, 0x00, /* code=86, hex=0x56, ascii="V", w=5 */
|
||||
0x04, 0x63, 0x18, 0xD6, 0xB5, 0xAA, 0x80, 0x00, /* code=87, hex=0x57, ascii="W", w=5 */
|
||||
0x04, 0x62, 0xA5, 0x11, 0x4A, 0x8C, 0x40, 0x00, /* code=88, hex=0x58, ascii="X", w=5 */
|
||||
0x04, 0x62, 0xA5, 0x10, 0x84, 0x21, 0x00, 0x00, /* code=89, hex=0x59, ascii="Y", w=5 */
|
||||
0x07, 0xC2, 0x21, 0x11, 0x08, 0x87, 0xC0, 0x00, /* code=90, hex=0x5A, ascii="Z", w=5 */
|
||||
0xF2, 0x49, 0x24, 0x93, 0x80, /* code=91, hex=0x5B, ascii="[", w=3 */
|
||||
0x04, 0x20, 0x84, 0x10, 0x42, 0x08, 0x40, 0x00, /* code=92, hex=0x5C, ascii="\", w=5 */
|
||||
0xE4, 0x92, 0x49, 0x27, 0x80, /* code=93, hex=0x5D, ascii="]", w=3 */
|
||||
0x01, 0x15, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^", w=5 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, /* code=95, hex=0x5F, ascii="_", w=5 */
|
||||
0x90, 0x00, 0x00, /* code=96, hex=0x60, ascii="`", w=2 */
|
||||
0x00, 0x00, 0x07, 0x05, 0xF1, 0x9B, 0x40, 0x00, /* code=97, hex=0x61, ascii="a", w=5 */
|
||||
0x04, 0x21, 0x0F, 0x46, 0x31, 0x8F, 0x80, 0x00, /* code=98, hex=0x62, ascii="b", w=5 */
|
||||
0x00, 0x00, 0x07, 0x46, 0x10, 0x8B, 0x80, 0x00, /* code=99, hex=0x63, ascii="c", w=5 */
|
||||
0x00, 0x42, 0x17, 0xC6, 0x31, 0x8B, 0xC0, 0x00, /* code=100, hex=0x64, ascii="d", w=5 */
|
||||
0x00, 0x00, 0x07, 0x47, 0xF0, 0x8B, 0x80, 0x00, /* code=101, hex=0x65, ascii="e", w=5 */
|
||||
0x01, 0x92, 0x84, 0x79, 0x08, 0x42, 0x00, 0x00, /* code=102, hex=0x66, ascii="f", w=5 */
|
||||
0x00, 0x00, 0x07, 0x46, 0x31, 0x78, 0x62, 0xE0, /* code=103, hex=0x67, ascii="g", w=5 */
|
||||
0x04, 0x21, 0x0B, 0x66, 0x31, 0x8C, 0x40, 0x00, /* code=104, hex=0x68, ascii="h", w=5 */
|
||||
0x01, 0x0C, 0x92, 0x5C, 0x00, /* code=105, hex=0x69, ascii="i", w=3 */
|
||||
0x00, 0x10, 0x31, 0x11, 0x19, 0x96, /* code=106, hex=0x6A, ascii="j", w=4 */
|
||||
0x04, 0x21, 0x09, 0x53, 0x14, 0x94, 0x40, 0x00, /* code=107, hex=0x6B, ascii="k", w=5 */
|
||||
0x19, 0x24, 0x92, 0x5C, 0x00, /* code=108, hex=0x6C, ascii="l", w=3 */
|
||||
0x00, 0x00, 0x0D, 0x56, 0xB5, 0xAC, 0x40, 0x00, /* code=109, hex=0x6D, ascii="m", w=5 */
|
||||
0x00, 0x00, 0x0B, 0x66, 0x31, 0x8C, 0x40, 0x00, /* code=110, hex=0x6E, ascii="n", w=5 */
|
||||
0x00, 0x00, 0x07, 0x46, 0x31, 0x8B, 0x80, 0x00, /* code=111, hex=0x6F, ascii="o", w=5 */
|
||||
0x00, 0x00, 0x0F, 0x46, 0x31, 0xF4, 0x21, 0x00, /* code=112, hex=0x70, ascii="p", w=5 */
|
||||
0x00, 0x00, 0x07, 0xC6, 0x31, 0x78, 0x42, 0x10, /* code=113, hex=0x71, ascii="q", w=5 */
|
||||
0x00, 0x00, 0x0B, 0x66, 0x10, 0x84, 0x00, 0x00, /* code=114, hex=0x72, ascii="r", w=5 */
|
||||
0x00, 0x00, 0x07, 0x45, 0x82, 0x8B, 0x80, 0x00, /* code=115, hex=0x73, ascii="s", w=5 */
|
||||
0x00, 0x10, 0x8F, 0x21, 0x08, 0x49, 0x80, 0x00, /* code=116, hex=0x74, ascii="t", w=5 */
|
||||
0x00, 0x00, 0x08, 0xC6, 0x31, 0x9B, 0x40, 0x00, /* code=117, hex=0x75, ascii="u", w=5 */
|
||||
0x00, 0x00, 0x08, 0xC6, 0x2A, 0x51, 0x00, 0x00, /* code=118, hex=0x76, ascii="v", w=5 */
|
||||
0x00, 0x00, 0x08, 0xC6, 0xB5, 0xAA, 0x80, 0x00, /* code=119, hex=0x77, ascii="w", w=5 */
|
||||
0x00, 0x00, 0x08, 0xA8, 0x84, 0x54, 0x40, 0x00, /* code=120, hex=0x78, ascii="x", w=5 */
|
||||
0x00, 0x00, 0x08, 0xC6, 0x33, 0x68, 0x62, 0xE0, /* code=121, hex=0x79, ascii="y", w=5 */
|
||||
0x00, 0x00, 0x0F, 0x88, 0x88, 0x87, 0xC0, 0x00, /* code=122, hex=0x7A, ascii="z", w=5 */
|
||||
0x19, 0x08, 0x42, 0x60, 0x84, 0x21, 0x06, 0x00, /* code=123, hex=0x7B, ascii="{", w=5 */
|
||||
0x7F, 0xC0, /* code=124, hex=0x7C, ascii="|", w=1 */
|
||||
0xC1, 0x08, 0x42, 0x0C, 0x84, 0x21, 0x30, 0x00, /* code=125, hex=0x7D, ascii="}", w=5 */
|
||||
0x02, 0x6B, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 /* code=126, hex=0x7E, ascii="~", w=5 */
|
||||
};
|
||||
Binary file not shown.
@@ -1,152 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory
|
||||
// font TinyUnicode is public domain, source: https://github.com/IT-Studio-Rech/bdf-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 8
|
||||
* [2] Fixed/max glyph width: 5
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x01 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
// Font: font_TinyUnicode_8px
|
||||
// Height: 8, Max Width: 7, Spacing: 1
|
||||
// Characters: 32-126 (95 glyphs)
|
||||
// Unicode Offset: 0x00000000
|
||||
// Variable Width: Yes
|
||||
|
||||
static const unsigned char font_TinyUnicode_8px[] PROGMEM = {
|
||||
0x57, 0x08, 0x07, 0x01, 0x01, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: 'W', H, W, S, Flags, First, Last, Reserved, UnicodeOffset (32bit)
|
||||
|
||||
// Width table
|
||||
0x07, 0x01, 0x03, 0x05, 0x04, 0x03, 0x05, 0x01, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x01, 0x03,
|
||||
0x04, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x01, 0x02, 0x02, 0x04, 0x02, 0x04,
|
||||
0x07, 0x04, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x05, 0x04, 0x04,
|
||||
0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x05, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x03, 0x05,
|
||||
0x02, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, 0x01, 0x02, 0x04, 0x01, 0x05, 0x04, 0x04,
|
||||
0x04, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x03, 0x04, 0x04, 0x03, 0x01, 0x03, 0x05,
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" ", w=7 */
|
||||
0x74, /* code=33, hex=0x21, ascii="!", w=1 */
|
||||
0xB4, 0x00, 0x00, /* code=34, hex=0x22, ascii=""", w=3 */
|
||||
0x01, 0x55, 0xF5, 0x50, 0x00, /* code=35, hex=0x23, ascii="#", w=5 */
|
||||
0x04, 0x7C, 0x3E, 0x20, /* code=36, hex=0x24, ascii="$", w=4 */
|
||||
0x14, 0xA9, 0x40, /* code=37, hex=0x25, ascii="%", w=3 */
|
||||
0x03, 0x20, 0xD9, 0x34, 0x00, /* code=38, hex=0x26, ascii="&", w=5 */
|
||||
0xC0, /* code=39, hex=0x27, ascii="'", w=1 */
|
||||
0x6A, 0xA4, /* code=40, hex=0x28, ascii="(", w=2 */
|
||||
0x95, 0x58, /* code=41, hex=0x29, ascii=")", w=2 */
|
||||
0xAA, 0x80, 0x00, /* code=42, hex=0x2A, ascii="*", w=3 */
|
||||
0x01, 0x74, 0x00, /* code=43, hex=0x2B, ascii="+", w=3 */
|
||||
0x00, 0x18, /* code=44, hex=0x2C, ascii=",", w=2 */
|
||||
0x00, 0x70, 0x00, /* code=45, hex=0x2D, ascii="-", w=3 */
|
||||
0x04, /* code=46, hex=0x2E, ascii=".", w=1 */
|
||||
0x04, 0xA9, 0x00, /* code=47, hex=0x2F, ascii="/", w=3 */
|
||||
0x06, 0x99, 0x96, 0x00, /* code=48, hex=0x30, ascii="0", w=4 */
|
||||
0x1D, 0x50, /* code=49, hex=0x31, ascii="1", w=2 */
|
||||
0x06, 0x92, 0x4F, 0x00, /* code=50, hex=0x32, ascii="2", w=4 */
|
||||
0x0E, 0x17, 0x1E, 0x00, /* code=51, hex=0x33, ascii="3", w=4 */
|
||||
0x02, 0x6A, 0xF2, 0x00, /* code=52, hex=0x34, ascii="4", w=4 */
|
||||
0x0F, 0x8E, 0x1E, 0x00, /* code=53, hex=0x35, ascii="5", w=4 */
|
||||
0x06, 0x8E, 0x96, 0x00, /* code=54, hex=0x36, ascii="6", w=4 */
|
||||
0x0F, 0x12, 0x44, 0x00, /* code=55, hex=0x37, ascii="7", w=4 */
|
||||
0x06, 0x96, 0x96, 0x00, /* code=56, hex=0x38, ascii="8", w=4 */
|
||||
0x06, 0x97, 0x16, 0x00, /* code=57, hex=0x39, ascii="9", w=4 */
|
||||
0x14, /* code=58, hex=0x3A, ascii=":", w=1 */
|
||||
0x01, 0x18, /* code=59, hex=0x3B, ascii=";", w=2 */
|
||||
0x06, 0x40, /* code=60, hex=0x3C, ascii="<", w=2 */
|
||||
0x00, 0xF0, 0xF0, 0x00, /* code=61, hex=0x3D, ascii="=", w=4 */
|
||||
0x09, 0x80, /* code=62, hex=0x3E, ascii=">", w=2 */
|
||||
0x0E, 0x16, 0x04, 0x00, /* code=63, hex=0x3F, ascii="?", w=4 */
|
||||
0x3C, 0x86, 0x6D, 0x59, 0xC8, 0x0F, 0x00, /* code=64, hex=0x40, ascii="@", w=7 */
|
||||
0x06, 0x9F, 0x99, 0x00, /* code=65, hex=0x41, ascii="A", w=4 */
|
||||
0x0E, 0x9E, 0x9E, 0x00, /* code=66, hex=0x42, ascii="B", w=4 */
|
||||
0x0E, 0x48, 0xC0, /* code=67, hex=0x43, ascii="C", w=3 */
|
||||
0x0E, 0x99, 0x9E, 0x00, /* code=68, hex=0x44, ascii="D", w=4 */
|
||||
0x1E, 0x69, 0xC0, /* code=69, hex=0x45, ascii="E", w=3 */
|
||||
0x1E, 0x79, 0x00, /* code=70, hex=0x46, ascii="F", w=3 */
|
||||
0x07, 0x8B, 0x97, 0x00, /* code=71, hex=0x47, ascii="G", w=4 */
|
||||
0x09, 0x9F, 0x99, 0x00, /* code=72, hex=0x48, ascii="H", w=4 */
|
||||
0x1D, 0x25, 0xC0, /* code=73, hex=0x49, ascii="I", w=3 */
|
||||
0x07, 0x11, 0x96, 0x00, /* code=74, hex=0x4A, ascii="J", w=4 */
|
||||
0x09, 0xAC, 0xA9, 0x00, /* code=75, hex=0x4B, ascii="K", w=4 */
|
||||
0x12, 0x49, 0xC0, /* code=76, hex=0x4C, ascii="L", w=3 */
|
||||
0x04, 0x77, 0x58, 0xC4, 0x00, /* code=77, hex=0x4D, ascii="M", w=5 */
|
||||
0x09, 0xDB, 0x99, 0x00, /* code=78, hex=0x4E, ascii="N", w=4 */
|
||||
0x06, 0x99, 0x96, 0x00, /* code=79, hex=0x4F, ascii="O", w=4 */
|
||||
0x0E, 0x9E, 0x88, 0x00, /* code=80, hex=0x50, ascii="P", w=4 */
|
||||
0x06, 0x99, 0x96, 0x10, /* code=81, hex=0x51, ascii="Q", w=4 */
|
||||
0x0E, 0x99, 0xE9, 0x00, /* code=82, hex=0x52, ascii="R", w=4 */
|
||||
0x07, 0x86, 0x1E, 0x00, /* code=83, hex=0x53, ascii="S", w=4 */
|
||||
0x1D, 0x24, 0x80, /* code=84, hex=0x54, ascii="T", w=3 */
|
||||
0x09, 0x99, 0x96, 0x00, /* code=85, hex=0x55, ascii="U", w=4 */
|
||||
0x09, 0x9A, 0xA4, 0x00, /* code=86, hex=0x56, ascii="V", w=4 */
|
||||
0x04, 0x63, 0x5A, 0xA8, 0x00, /* code=87, hex=0x57, ascii="W", w=5 */
|
||||
0x09, 0x96, 0x99, 0x00, /* code=88, hex=0x58, ascii="X", w=4 */
|
||||
0x09, 0x97, 0x16, 0x00, /* code=89, hex=0x59, ascii="Y", w=4 */
|
||||
0x1C, 0xA9, 0xC0, /* code=90, hex=0x5A, ascii="Z", w=3 */
|
||||
0x3A, 0xAC, /* code=91, hex=0x5B, ascii="[", w=2 */
|
||||
0x12, 0x22, 0x40, /* code=92, hex=0x5C, ascii="\", w=3 */
|
||||
0x35, 0x5C, /* code=93, hex=0x5D, ascii="]", w=2 */
|
||||
0x0A, 0x80, 0x00, /* code=94, hex=0x5E, ascii="^", w=3 */
|
||||
0x00, 0x00, 0x00, 0x03, 0xE0, /* code=95, hex=0x5F, ascii="_", w=5 */
|
||||
0x24, 0x00, /* code=96, hex=0x60, ascii="`", w=2 */
|
||||
0x00, 0x79, 0x97, 0x00, /* code=97, hex=0x61, ascii="a", w=4 */
|
||||
0x08, 0xE9, 0x9E, 0x00, /* code=98, hex=0x62, ascii="b", w=4 */
|
||||
0x01, 0xC8, 0xC0, /* code=99, hex=0x63, ascii="c", w=3 */
|
||||
0x01, 0x79, 0x97, 0x00, /* code=100, hex=0x64, ascii="d", w=4 */
|
||||
0x00, 0x6B, 0xC6, 0x00, /* code=101, hex=0x65, ascii="e", w=4 */
|
||||
0x0D, 0x74, 0x80, /* code=102, hex=0x66, ascii="f", w=3 */
|
||||
0x00, 0x79, 0x97, 0x16, /* code=103, hex=0x67, ascii="g", w=4 */
|
||||
0x08, 0xE9, 0x99, 0x00, /* code=104, hex=0x68, ascii="h", w=4 */
|
||||
0x5C, /* code=105, hex=0x69, ascii="i", w=1 */
|
||||
0x11, 0x56, /* code=106, hex=0x6A, ascii="j", w=2 */
|
||||
0x08, 0x9A, 0xE9, 0x00, /* code=107, hex=0x6B, ascii="k", w=4 */
|
||||
0x7C, /* code=108, hex=0x6C, ascii="l", w=1 */
|
||||
0x00, 0x3D, 0x5A, 0xD4, 0x00, /* code=109, hex=0x6D, ascii="m", w=5 */
|
||||
0x00, 0xE9, 0x99, 0x00, /* code=110, hex=0x6E, ascii="n", w=4 */
|
||||
0x00, 0x69, 0x96, 0x00, /* code=111, hex=0x6F, ascii="o", w=4 */
|
||||
0x00, 0xE9, 0x9E, 0x88, /* code=112, hex=0x70, ascii="p", w=4 */
|
||||
0x00, 0x79, 0x97, 0x11, /* code=113, hex=0x71, ascii="q", w=4 */
|
||||
0x02, 0xE9, 0x00, /* code=114, hex=0x72, ascii="r", w=3 */
|
||||
0x00, 0x7C, 0x3E, 0x00, /* code=115, hex=0x73, ascii="s", w=4 */
|
||||
0x0B, 0xA4, 0xC0, /* code=116, hex=0x74, ascii="t", w=3 */
|
||||
0x00, 0x99, 0x97, 0x00, /* code=117, hex=0x75, ascii="u", w=4 */
|
||||
0x00, 0x99, 0xA4, 0x00, /* code=118, hex=0x76, ascii="v", w=4 */
|
||||
0x00, 0x23, 0x5A, 0xA8, 0x00, /* code=119, hex=0x77, ascii="w", w=5 */
|
||||
0x02, 0xA5, 0x40, /* code=120, hex=0x78, ascii="x", w=3 */
|
||||
0x00, 0x99, 0x97, 0x1E, /* code=121, hex=0x79, ascii="y", w=4 */
|
||||
0x00, 0xF2, 0x4F, 0x00, /* code=122, hex=0x7A, ascii="z", w=4 */
|
||||
0x69, 0x44, 0x98, /* code=123, hex=0x7B, ascii="{", w=3 */
|
||||
0x7C, /* code=124, hex=0x7C, ascii="|", w=1 */
|
||||
0xC9, 0x14, 0xB0, /* code=125, hex=0x7D, ascii="}", w=3 */
|
||||
0x00, 0x13, 0x59, 0x00, 0x00 /* code=126, hex=0x7E, ascii="~", w=5 */
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,145 +0,0 @@
|
||||
// tom-thumb, public domain, source: https://robey.lag.net/2010/01/23/tiny-monospace-font.html
|
||||
// ASCII chars 0-31 and 127-255 are not included to save memory
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 6
|
||||
* [2] Fixed/max glyph width: 4
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
|
||||
// Font: font_tom-thumb_6px
|
||||
// Height: 6, Max Width: 3, Spacing: 1
|
||||
// Characters: 32-126 (95 glyphs)
|
||||
// Unicode Offset: 0x00000000
|
||||
// Variable Width: No
|
||||
|
||||
static const unsigned char font_tom_thumb_6px[] PROGMEM = {
|
||||
0x57, 0x06, 0x03, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: 'W', H, W, S, Flags, First, Last, Reserved, UnicodeOffset (32bit)
|
||||
|
||||
0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x49, 0x04, 0x00, /* code=33, hex=0x21, ascii="!" */
|
||||
0xB4, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0xBE, 0xFA, 0x00, /* code=35, hex=0x23, ascii="#" */
|
||||
0x79, 0xE4, 0x00, /* code=36, hex=0x24, ascii="$" */
|
||||
0x85, 0x42, 0x00, /* code=37, hex=0x25, ascii="%" */
|
||||
0xDB, 0xD6, 0x00, /* code=38, hex=0x26, ascii="&" */
|
||||
0x48, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x52, 0x44, 0x00, /* code=40, hex=0x28, ascii="(" */
|
||||
0x89, 0x28, 0x00, /* code=41, hex=0x29, ascii=")" */
|
||||
0xAA, 0x80, 0x00, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x0B, 0xA0, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x28, 0x00, /* code=44, hex=0x2C, ascii="," */
|
||||
0x03, 0x80, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x04, 0x00, /* code=46, hex=0x2E, ascii="." */
|
||||
0x25, 0x48, 0x00, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x76, 0xDC, 0x00, /* code=48, hex=0x30, ascii="0" */
|
||||
0x59, 0x24, 0x00, /* code=49, hex=0x31, ascii="1" */
|
||||
0xC5, 0x4E, 0x00, /* code=50, hex=0x32, ascii="2" */
|
||||
0xC5, 0x1C, 0x00, /* code=51, hex=0x33, ascii="3" */
|
||||
0xB7, 0x92, 0x00, /* code=52, hex=0x34, ascii="4" */
|
||||
0xF3, 0x1C, 0x00, /* code=53, hex=0x35, ascii="5" */
|
||||
0x73, 0xDE, 0x00, /* code=54, hex=0x36, ascii="6" */
|
||||
0xE5, 0x48, 0x00, /* code=55, hex=0x37, ascii="7" */
|
||||
0xF7, 0xDE, 0x00, /* code=56, hex=0x38, ascii="8" */
|
||||
0xF7, 0x9C, 0x00, /* code=57, hex=0x39, ascii="9" */
|
||||
0x08, 0x20, 0x00, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x08, 0x28, 0x00, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x2A, 0x22, 0x00, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x1C, 0x70, 0x00, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x88, 0xA8, 0x00, /* code=62, hex=0x3E, ascii=">" */
|
||||
0xE5, 0x04, 0x00, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x57, 0xC6, 0x00, /* code=64, hex=0x40, ascii="@" */
|
||||
0x57, 0xDA, 0x00, /* code=65, hex=0x41, ascii="A" */
|
||||
0xD7, 0x5C, 0x00, /* code=66, hex=0x42, ascii="B" */
|
||||
0x72, 0x46, 0x00, /* code=67, hex=0x43, ascii="C" */
|
||||
0xD6, 0xDC, 0x00, /* code=68, hex=0x44, ascii="D" */
|
||||
0xF3, 0xCE, 0x00, /* code=69, hex=0x45, ascii="E" */
|
||||
0xF3, 0xC8, 0x00, /* code=70, hex=0x46, ascii="F" */
|
||||
0x73, 0xD6, 0x00, /* code=71, hex=0x47, ascii="G" */
|
||||
0xB7, 0xDA, 0x00, /* code=72, hex=0x48, ascii="H" */
|
||||
0xE9, 0x2E, 0x00, /* code=73, hex=0x49, ascii="I" */
|
||||
0x24, 0xD4, 0x00, /* code=74, hex=0x4A, ascii="J" */
|
||||
0xB7, 0x5A, 0x00, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x92, 0x4E, 0x00, /* code=76, hex=0x4C, ascii="L" */
|
||||
0xBF, 0xDA, 0x00, /* code=77, hex=0x4D, ascii="M" */
|
||||
0xBF, 0xFA, 0x00, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x56, 0xD4, 0x00, /* code=79, hex=0x4F, ascii="O" */
|
||||
0xD7, 0x48, 0x00, /* code=80, hex=0x50, ascii="P" */
|
||||
0x56, 0xF6, 0x00, /* code=81, hex=0x51, ascii="Q" */
|
||||
0xD7, 0xEA, 0x00, /* code=82, hex=0x52, ascii="R" */
|
||||
0x71, 0x1C, 0x00, /* code=83, hex=0x53, ascii="S" */
|
||||
0xE9, 0x24, 0x00, /* code=84, hex=0x54, ascii="T" */
|
||||
0xB6, 0xD6, 0x00, /* code=85, hex=0x55, ascii="U" */
|
||||
0xB6, 0xA4, 0x00, /* code=86, hex=0x56, ascii="V" */
|
||||
0xB7, 0xFA, 0x00, /* code=87, hex=0x57, ascii="W" */
|
||||
0xB5, 0x5A, 0x00, /* code=88, hex=0x58, ascii="X" */
|
||||
0xB5, 0x24, 0x00, /* code=89, hex=0x59, ascii="Y" */
|
||||
0xE5, 0x4E, 0x00, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0xF2, 0x4E, 0x00, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x11, 0x10, 0x00, /* code=92, hex=0x5C, ascii="\" */
|
||||
0xE4, 0x9E, 0x00, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x54, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x0E, 0x00, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x88, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x19, 0xDE, 0x00, /* code=97, hex=0x61, ascii="a" */
|
||||
0x9A, 0xDC, 0x00, /* code=98, hex=0x62, ascii="b" */
|
||||
0x0E, 0x46, 0x00, /* code=99, hex=0x63, ascii="c" */
|
||||
0x2E, 0xD6, 0x00, /* code=100, hex=0x64, ascii="d" */
|
||||
0x0E, 0xE6, 0x00, /* code=101, hex=0x65, ascii="e" */
|
||||
0x2B, 0xA4, 0x00, /* code=102, hex=0x66, ascii="f" */
|
||||
0x0E, 0xF2, 0x80, /* code=103, hex=0x67, ascii="g" */
|
||||
0x9A, 0xDA, 0x00, /* code=104, hex=0x68, ascii="h" */
|
||||
0x41, 0x24, 0x00, /* code=105, hex=0x69, ascii="i" */
|
||||
0x20, 0x9A, 0x80, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x97, 0x6A, 0x00, /* code=107, hex=0x6B, ascii="k" */
|
||||
0xC9, 0x2E, 0x00, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x1F, 0xFA, 0x00, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x1A, 0xDA, 0x00, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x0A, 0xD4, 0x00, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x1A, 0xDD, 0x00, /* code=112, hex=0x70, ascii="p" */
|
||||
0x0E, 0xD6, 0x40, /* code=113, hex=0x71, ascii="q" */
|
||||
0x0E, 0x48, 0x00, /* code=114, hex=0x72, ascii="r" */
|
||||
0x0F, 0x3C, 0x00, /* code=115, hex=0x73, ascii="s" */
|
||||
0x5D, 0x26, 0x00, /* code=116, hex=0x74, ascii="t" */
|
||||
0x16, 0xD6, 0x00, /* code=117, hex=0x75, ascii="u" */
|
||||
0x16, 0xF4, 0x00, /* code=118, hex=0x76, ascii="v" */
|
||||
0x17, 0xFE, 0x00, /* code=119, hex=0x77, ascii="w" */
|
||||
0x15, 0x2A, 0x00, /* code=120, hex=0x78, ascii="x" */
|
||||
0x16, 0xB2, 0x80, /* code=121, hex=0x79, ascii="y" */
|
||||
0x1D, 0xEE, 0x00, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x6A, 0x26, 0x00, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x48, 0x24, 0x00, /* code=124, hex=0x7C, ascii="|" */
|
||||
0xC8, 0xAC, 0x00, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x78, 0x00, 0x00 /* code=126, hex=0x7E, ascii="~" */
|
||||
};
|
||||
@@ -1,301 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// font courtesy of https://github.com/idispatch/raster-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 6
|
||||
* [2] Fixed/max glyph width: 4
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
|
||||
static const unsigned char console_font_4x6[] PROGMEM = {
|
||||
0x57, 0x06, 0x04, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
|
||||
|
||||
// 0x00, 0x00, 0x00, // /* code=0, hex=0x00, ascii="^@" */
|
||||
// 0x25, 0x75, 0x20, // /* code=1, hex=0x01, ascii="^A" */
|
||||
// 0x27, 0x57, 0x20, // /* code=2, hex=0x02, ascii="^B" */
|
||||
// 0x05, 0x77, 0x20, // /* code=3, hex=0x03, ascii="^C" */
|
||||
// 0x02, 0x77, 0x20, // /* code=4, hex=0x04, ascii="^D" */
|
||||
// 0x27, 0x72, 0x70, // /* code=5, hex=0x05, ascii="^E" */
|
||||
// 0x22, 0x72, 0x70, // /* code=6, hex=0x06, ascii="^F" */
|
||||
// 0x00, 0x20, 0x00, // /* code=7, hex=0x07, ascii="^G" */
|
||||
// 0xFF, 0xDF, 0xFF, // /* code=8, hex=0x08, ascii="^H" */
|
||||
// 0x07, 0x57, 0x00, // /* code=9, hex=0x09, ascii="^I" */
|
||||
// 0xF8, 0xA8, 0xFF, // /* code=10, hex=0x0A, ascii="^J" */
|
||||
// 0x03, 0x16, 0x60, // /* code=11, hex=0x0B, ascii="^K" */
|
||||
// 0x25, 0x27, 0x20, // /* code=12, hex=0x0C, ascii="^L" */
|
||||
// 0x23, 0x22, 0x60, // /* code=13, hex=0x0D, ascii="^M" */
|
||||
// 0x23, 0x51, 0x20, // /* code=14, hex=0x0E, ascii="^N" */
|
||||
// 0x27, 0x57, 0x20, // /* code=15, hex=0x0F, ascii="^O" */
|
||||
// 0x46, 0x76, 0x40, // /* code=16, hex=0x10, ascii="^P" */
|
||||
// 0x13, 0x73, 0x10, // /* code=17, hex=0x11, ascii="^Q" */
|
||||
// 0x27, 0x27, 0x20, // /* code=18, hex=0x12, ascii="^R" */
|
||||
// 0x55, 0x50, 0x50, // /* code=19, hex=0x13, ascii="^S" */
|
||||
// 0x7D, 0xD5, 0x50, // /* code=20, hex=0x14, ascii="^T" */
|
||||
// 0x36, 0x53, 0x60, // /* code=21, hex=0x15, ascii="^U" */
|
||||
// 0x00, 0x00, 0x70, // /* code=22, hex=0x16, ascii="^V" */
|
||||
// 0x27, 0x27, 0x27, // /* code=23, hex=0x17, ascii="^W" */
|
||||
// 0x27, 0x22, 0x20, // /* code=24, hex=0x18, ascii="^X" */
|
||||
// 0x22, 0x27, 0x20, // /* code=25, hex=0x19, ascii="^Y" */
|
||||
// 0x02, 0xF2, 0x00, // /* code=26, hex=0x1A, ascii="^Z" */
|
||||
// 0x04, 0xF4, 0x00, // /* code=27, hex=0x1B, ascii="^[" */
|
||||
// 0x00, 0x47, 0x00, // /* code=28, hex=0x1C, ascii="^\" */
|
||||
// 0x05, 0x75, 0x00, // /* code=29, hex=0x1D, ascii="^]" */
|
||||
// 0x02, 0x77, 0x00, // /* code=30, hex=0x1E, ascii="^^" */
|
||||
// 0x07, 0x72, 0x00, // /* code=31, hex=0x1F, ascii="^_" */
|
||||
0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x22, 0x20, 0x20, /* code=33, hex=0x21, ascii="!" */
|
||||
0x55, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0x57, 0x57, 0x50, /* code=35, hex=0x23, ascii="#" */
|
||||
0x23, 0x63, 0x62, /* code=36, hex=0x24, ascii="$" */
|
||||
0x41, 0x24, 0x10, /* code=37, hex=0x25, ascii="%" */
|
||||
0x25, 0x35, 0x70, /* code=38, hex=0x26, ascii="&" */
|
||||
0x64, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x24, 0x44, 0x20, /* code=40, hex=0x28, ascii="(" */
|
||||
0x42, 0x22, 0x40, /* code=41, hex=0x29, ascii=")" */
|
||||
0x52, 0x72, 0x50, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x02, 0x72, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x00, 0x64, /* code=44, hex=0x2C, ascii="," */
|
||||
0x00, 0x70, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x00, 0x20, /* code=46, hex=0x2E, ascii="." */
|
||||
0x11, 0x24, 0x40, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x35, 0x55, 0x60, /* code=48, hex=0x30, ascii="0" */
|
||||
0x26, 0x22, 0x70, /* code=49, hex=0x31, ascii="1" */
|
||||
0x61, 0x24, 0x70, /* code=50, hex=0x32, ascii="2" */
|
||||
0x61, 0x21, 0x60, /* code=51, hex=0x33, ascii="3" */
|
||||
0x15, 0x71, 0x10, /* code=52, hex=0x34, ascii="4" */
|
||||
0x74, 0x61, 0x60, /* code=53, hex=0x35, ascii="5" */
|
||||
0x24, 0x65, 0x20, /* code=54, hex=0x36, ascii="6" */
|
||||
0x71, 0x32, 0x20, /* code=55, hex=0x37, ascii="7" */
|
||||
0x25, 0x25, 0x20, /* code=56, hex=0x38, ascii="8" */
|
||||
0x25, 0x31, 0x20, /* code=57, hex=0x39, ascii="9" */
|
||||
0x00, 0x20, 0x20, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x00, 0x20, 0x64, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x12, 0x42, 0x10, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x00, 0x70, 0x70, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x42, 0x12, 0x40, /* code=62, hex=0x3E, ascii=">" */
|
||||
0x61, 0x20, 0x20, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x75, 0x54, 0x70, /* code=64, hex=0x40, ascii="@" */
|
||||
0x25, 0x75, 0x50, /* code=65, hex=0x41, ascii="A" */
|
||||
0x65, 0x65, 0x60, /* code=66, hex=0x42, ascii="B" */
|
||||
0x34, 0x44, 0x30, /* code=67, hex=0x43, ascii="C" */
|
||||
0x65, 0x55, 0x60, /* code=68, hex=0x44, ascii="D" */
|
||||
0x74, 0x64, 0x70, /* code=69, hex=0x45, ascii="E" */
|
||||
0x74, 0x64, 0x40, /* code=70, hex=0x46, ascii="F" */
|
||||
0x34, 0x55, 0x30, /* code=71, hex=0x47, ascii="G" */
|
||||
0x55, 0x75, 0x50, /* code=72, hex=0x48, ascii="H" */
|
||||
0x72, 0x22, 0x70, /* code=73, hex=0x49, ascii="I" */
|
||||
0x11, 0x15, 0x20, /* code=74, hex=0x4A, ascii="J" */
|
||||
0x55, 0x65, 0x50, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x44, 0x44, 0x70, /* code=76, hex=0x4C, ascii="L" */
|
||||
0x57, 0x75, 0x50, /* code=77, hex=0x4D, ascii="M" */
|
||||
0x57, 0x55, 0x50, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x25, 0x55, 0x20, /* code=79, hex=0x4F, ascii="O" */
|
||||
0x65, 0x64, 0x40, /* code=80, hex=0x50, ascii="P" */
|
||||
0x25, 0x57, 0x30, /* code=81, hex=0x51, ascii="Q" */
|
||||
0x65, 0x65, 0x50, /* code=82, hex=0x52, ascii="R" */
|
||||
0x34, 0x71, 0x60, /* code=83, hex=0x53, ascii="S" */
|
||||
0x72, 0x22, 0x20, /* code=84, hex=0x54, ascii="T" */
|
||||
0x55, 0x55, 0x70, /* code=85, hex=0x55, ascii="U" */
|
||||
0x55, 0x55, 0x20, /* code=86, hex=0x56, ascii="V" */
|
||||
0x55, 0x77, 0x50, /* code=87, hex=0x57, ascii="W" */
|
||||
0x55, 0x25, 0x50, /* code=88, hex=0x58, ascii="X" */
|
||||
0x55, 0x22, 0x20, /* code=89, hex=0x59, ascii="Y" */
|
||||
0x71, 0x24, 0x70, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0x64, 0x44, 0x60, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x44, 0x21, 0x10, /* code=92, hex=0x5C, ascii="\" */
|
||||
0x62, 0x22, 0x60, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x25, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x00, 0x0F, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x62, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x00, 0x35, 0x70, /* code=97, hex=0x61, ascii="a" */
|
||||
0x44, 0x65, 0x60, /* code=98, hex=0x62, ascii="b" */
|
||||
0x00, 0x34, 0x30, /* code=99, hex=0x63, ascii="c" */
|
||||
0x11, 0x35, 0x30, /* code=100, hex=0x64, ascii="d" */
|
||||
0x00, 0x76, 0x30, /* code=101, hex=0x65, ascii="e" */
|
||||
0x12, 0x72, 0x20, /* code=102, hex=0x66, ascii="f" */
|
||||
0x00, 0x75, 0x17, /* code=103, hex=0x67, ascii="g" */
|
||||
0x44, 0x65, 0x50, /* code=104, hex=0x68, ascii="h" */
|
||||
0x20, 0x22, 0x20, /* code=105, hex=0x69, ascii="i" */
|
||||
0x20, 0x22, 0x26, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x44, 0x56, 0x50, /* code=107, hex=0x6B, ascii="k" */
|
||||
0x22, 0x22, 0x20, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x00, 0x77, 0x50, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x00, 0x65, 0x50, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x00, 0x25, 0x20, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x00, 0x65, 0x64, /* code=112, hex=0x70, ascii="p" */
|
||||
0x00, 0x35, 0x31, /* code=113, hex=0x71, ascii="q" */
|
||||
0x00, 0x64, 0x40, /* code=114, hex=0x72, ascii="r" */
|
||||
0x00, 0x32, 0x60, /* code=115, hex=0x73, ascii="s" */
|
||||
0x02, 0x72, 0x30, /* code=116, hex=0x74, ascii="t" */
|
||||
0x00, 0x55, 0x70, /* code=117, hex=0x75, ascii="u" */
|
||||
0x00, 0x55, 0x20, /* code=118, hex=0x76, ascii="v" */
|
||||
0x00, 0x57, 0x70, /* code=119, hex=0x77, ascii="w" */
|
||||
0x00, 0x52, 0x50, /* code=120, hex=0x78, ascii="x" */
|
||||
0x00, 0x55, 0x24, /* code=121, hex=0x79, ascii="y" */
|
||||
0x00, 0x62, 0x30, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x32, 0x62, 0x30, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x22, 0x22, 0x20, /* code=124, hex=0x7C, ascii="|" */
|
||||
0x62, 0x32, 0x60, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x5A, 0x00, 0x00, /* code=126, hex=0x7E, ascii="~" */
|
||||
// 0x02, 0x57, 0x00, // /* code=127, hex=0x7F, ascii="^?" */
|
||||
// 0x34, 0x47, 0x24, // /* code=128, hex=0x80, ascii="!^@" */
|
||||
// 0x50, 0x55, 0x30, // /* code=129, hex=0x81, ascii="!^A" */
|
||||
// 0x12, 0x76, 0x30, // /* code=130, hex=0x82, ascii="!^B" */
|
||||
// 0x25, 0x35, 0x70, // /* code=131, hex=0x83, ascii="!^C" */
|
||||
// 0x50, 0x35, 0x70, // /* code=132, hex=0x84, ascii="!^D" */
|
||||
// 0x42, 0x35, 0x70, // /* code=133, hex=0x85, ascii="!^E" */
|
||||
// 0x20, 0x35, 0x70, // /* code=134, hex=0x86, ascii="!^F" */
|
||||
// 0x07, 0x47, 0x26, // /* code=135, hex=0x87, ascii="!^G" */
|
||||
// 0x25, 0x76, 0x30, // /* code=136, hex=0x88, ascii="!^H" */
|
||||
// 0x50, 0x76, 0x30, // /* code=137, hex=0x89, ascii="!^I" */
|
||||
// 0x42, 0x76, 0x30, // /* code=138, hex=0x8A, ascii="!^J" */
|
||||
// 0x50, 0x22, 0x20, // /* code=139, hex=0x8B, ascii="!^K" */
|
||||
// 0x25, 0x02, 0x20, // /* code=140, hex=0x8C, ascii="!^L" */
|
||||
// 0x42, 0x02, 0x20, // /* code=141, hex=0x8D, ascii="!^M" */
|
||||
// 0x52, 0x57, 0x50, // /* code=142, hex=0x8E, ascii="!^N" */
|
||||
// 0x22, 0x57, 0x50, // /* code=143, hex=0x8F, ascii="!^O" */
|
||||
// 0x12, 0x76, 0x70, // /* code=144, hex=0x90, ascii="!^P" */
|
||||
// 0x00, 0x37, 0x60, // /* code=145, hex=0x91, ascii="!^Q" */
|
||||
// 0x36, 0x76, 0x70, // /* code=146, hex=0x92, ascii="!^R" */
|
||||
// 0x25, 0x25, 0x20, // /* code=147, hex=0x93, ascii="!^S" */
|
||||
// 0x50, 0x25, 0x20, // /* code=148, hex=0x94, ascii="!^T" */
|
||||
// 0x42, 0x25, 0x20, // /* code=149, hex=0x95, ascii="!^U" */
|
||||
// 0x25, 0x05, 0x70, // /* code=150, hex=0x96, ascii="!^V" */
|
||||
// 0x42, 0x55, 0x70, // /* code=151, hex=0x97, ascii="!^W" */
|
||||
// 0x50, 0x55, 0x24, // /* code=152, hex=0x98, ascii="!^X" */
|
||||
// 0x52, 0x55, 0x20, // /* code=153, hex=0x99, ascii="!^Y" */
|
||||
// 0x50, 0x55, 0x70, // /* code=154, hex=0x9A, ascii="!^Z" */
|
||||
// 0x27, 0x47, 0x20, // /* code=155, hex=0x9B, ascii="!^[" */
|
||||
// 0x12, 0x72, 0x70, // /* code=156, hex=0x9C, ascii="!^\" */
|
||||
// 0x57, 0x27, 0x20, // /* code=157, hex=0x9D, ascii="!^]" */
|
||||
// 0x06, 0x65, 0x50, // /* code=158, hex=0x9E, ascii="!^^" */
|
||||
// 0x32, 0x32, 0x60, // /* code=159, hex=0x9F, ascii="!^_" */
|
||||
// 0x12, 0x35, 0x70, // /* code=160, hex=0xA0, ascii="! " */
|
||||
// 0x12, 0x02, 0x20, // /* code=161, hex=0xA1, ascii="!!" */
|
||||
// 0x12, 0x75, 0x70, // /* code=162, hex=0xA2, ascii="!"" */
|
||||
// 0x12, 0x05, 0x70, // /* code=163, hex=0xA3, ascii="!#" */
|
||||
// 0x70, 0x75, 0x50, // /* code=164, hex=0xA4, ascii="!$" */
|
||||
// 0x70, 0x57, 0x50, // /* code=165, hex=0xA5, ascii="!%" */
|
||||
// 0x35, 0x70, 0x70, // /* code=166, hex=0xA6, ascii="!&" */
|
||||
// 0x25, 0x20, 0x70, // /* code=167, hex=0xA7, ascii="!'" */
|
||||
// 0x20, 0x24, 0x30, // /* code=168, hex=0xA8, ascii="!(" */
|
||||
// 0x07, 0x44, 0x00, // /* code=169, hex=0xA9, ascii="!)" */
|
||||
// 0x0E, 0x22, 0x00, // /* code=170, hex=0xAA, ascii="!*" */
|
||||
// 0x45, 0x25, 0x30, // /* code=171, hex=0xAB, ascii="!+" */
|
||||
// 0x45, 0x27, 0x10, // /* code=172, hex=0xAC, ascii="!," */
|
||||
// 0x20, 0x22, 0x20, // /* code=173, hex=0xAD, ascii="!-" */
|
||||
// 0x05, 0xA5, 0x00, // /* code=174, hex=0xAE, ascii="!." */
|
||||
// 0x0A, 0x5A, 0x00, // /* code=175, hex=0xAF, ascii="!/" */
|
||||
// 0x41, 0x41, 0x41, // /* code=176, hex=0xB0, ascii="!0" */
|
||||
// 0x5A, 0x5A, 0x5A, // /* code=177, hex=0xB1, ascii="!1" */
|
||||
// 0xBE, 0xBE, 0xBE, // /* code=178, hex=0xB2, ascii="!2" */
|
||||
// 0x22, 0x22, 0x22, // /* code=179, hex=0xB3, ascii="!3" */
|
||||
// 0x22, 0xE2, 0x22, // /* code=180, hex=0xB4, ascii="!4" */
|
||||
// 0x2E, 0x2E, 0x22, // /* code=181, hex=0xB5, ascii="!5" */
|
||||
// 0x55, 0xD5, 0x55, // /* code=182, hex=0xB6, ascii="!6" */
|
||||
// 0x00, 0xF5, 0x55, // /* code=183, hex=0xB7, ascii="!7" */
|
||||
// 0x0E, 0x2E, 0x22, // /* code=184, hex=0xB8, ascii="!8" */
|
||||
// 0x5D, 0x1D, 0x55, // /* code=185, hex=0xB9, ascii="!9" */
|
||||
// 0x55, 0x55, 0x55, // /* code=186, hex=0xBA, ascii="!:" */
|
||||
// 0x0F, 0x1D, 0x55, // /* code=187, hex=0xBB, ascii="!;" */
|
||||
// 0x5D, 0x1F, 0x00, // /* code=188, hex=0xBC, ascii="!<" */
|
||||
// 0x55, 0xF0, 0x00, // /* code=189, hex=0xBD, ascii="!=" */
|
||||
// 0x2E, 0x2E, 0x00, // /* code=190, hex=0xBE, ascii="!>" */
|
||||
// 0x00, 0xE2, 0x22, // /* code=191, hex=0xBF, ascii="!?" */
|
||||
// 0x22, 0x30, 0x00, // /* code=192, hex=0xC0, ascii="!@" */
|
||||
// 0x22, 0xF0, 0x00, // /* code=193, hex=0xC1, ascii="!A" */
|
||||
// 0x00, 0xF2, 0x22, // /* code=194, hex=0xC2, ascii="!B" */
|
||||
// 0x22, 0x32, 0x22, // /* code=195, hex=0xC3, ascii="!C" */
|
||||
// 0x00, 0xF0, 0x00, // /* code=196, hex=0xC4, ascii="!D" */
|
||||
// 0x22, 0xF2, 0x22, // /* code=197, hex=0xC5, ascii="!E" */
|
||||
// 0x23, 0x23, 0x22, // /* code=198, hex=0xC6, ascii="!F" */
|
||||
// 0x55, 0x55, 0x55, // /* code=199, hex=0xC7, ascii="!G" */
|
||||
// 0x55, 0x47, 0x00, // /* code=200, hex=0xC8, ascii="!H" */
|
||||
// 0x07, 0x45, 0x55, // /* code=201, hex=0xC9, ascii="!I" */
|
||||
// 0x5D, 0x0F, 0x00, // /* code=202, hex=0xCA, ascii="!J" */
|
||||
// 0x0F, 0x0D, 0x55, // /* code=203, hex=0xCB, ascii="!K" */
|
||||
// 0x55, 0x45, 0x55, // /* code=204, hex=0xCC, ascii="!L" */
|
||||
// 0x0F, 0x0F, 0x00, // /* code=205, hex=0xCD, ascii="!M" */
|
||||
// 0x5D, 0x0D, 0x55, // /* code=206, hex=0xCE, ascii="!N" */
|
||||
// 0x2F, 0x0F, 0x00, // /* code=207, hex=0xCF, ascii="!O" */
|
||||
// 0x55, 0xF0, 0x00, // /* code=208, hex=0xD0, ascii="!P" */
|
||||
// 0x0F, 0x0F, 0x22, // /* code=209, hex=0xD1, ascii="!Q" */
|
||||
// 0x00, 0xF5, 0x55, // /* code=210, hex=0xD2, ascii="!R" */
|
||||
// 0x55, 0x70, 0x00, // /* code=211, hex=0xD3, ascii="!S" */
|
||||
// 0x23, 0x23, 0x00, // /* code=212, hex=0xD4, ascii="!T" */
|
||||
// 0x03, 0x23, 0x22, // /* code=213, hex=0xD5, ascii="!U" */
|
||||
// 0x00, 0x75, 0x55, // /* code=214, hex=0xD6, ascii="!V" */
|
||||
// 0x55, 0xD5, 0x55, // /* code=215, hex=0xD7, ascii="!W" */
|
||||
// 0x2F, 0x0F, 0x22, // /* code=216, hex=0xD8, ascii="!X" */
|
||||
// 0x22, 0xE0, 0x00, // /* code=217, hex=0xD9, ascii="!Y" */
|
||||
// 0x00, 0x32, 0x22, // /* code=218, hex=0xDA, ascii="!Z" */
|
||||
// 0xFF, 0xFF, 0xFF, // /* code=219, hex=0xDB, ascii="![" */
|
||||
// 0x00, 0x0F, 0xFF, // /* code=220, hex=0xDC, ascii="!\" */
|
||||
// 0xCC, 0xCC, 0xCC, // /* code=221, hex=0xDD, ascii="!]" */
|
||||
// 0x33, 0x33, 0x33, // /* code=222, hex=0xDE, ascii="!^" */
|
||||
// 0xFF, 0xF0, 0x00, // /* code=223, hex=0xDF, ascii="!_" */
|
||||
// 0x00, 0x76, 0x70, // /* code=224, hex=0xE0, ascii="!`" */
|
||||
// 0x25, 0x65, 0x64, // /* code=225, hex=0xE1, ascii="!a" */
|
||||
// 0x75, 0x44, 0x40, // /* code=226, hex=0xE2, ascii="!b" */
|
||||
// 0x75, 0x55, 0x50, // /* code=227, hex=0xE3, ascii="!c" */
|
||||
// 0x74, 0x24, 0x70, // /* code=228, hex=0xE4, ascii="!d" */
|
||||
// 0x00, 0x35, 0x20, // /* code=229, hex=0xE5, ascii="!e" */
|
||||
// 0x00, 0x55, 0x74, // /* code=230, hex=0xE6, ascii="!f" */
|
||||
// 0x01, 0x62, 0x20, // /* code=231, hex=0xE7, ascii="!g" */
|
||||
// 0x72, 0x52, 0x70, // /* code=232, hex=0xE8, ascii="!h" */
|
||||
// 0x25, 0x75, 0x20, // /* code=233, hex=0xE9, ascii="!i" */
|
||||
// 0x02, 0x55, 0x50, // /* code=234, hex=0xEA, ascii="!j" */
|
||||
// 0x34, 0x25, 0x20, // /* code=235, hex=0xEB, ascii="!k" */
|
||||
// 0x00, 0x75, 0x70, // /* code=236, hex=0xEC, ascii="!l" */
|
||||
// 0x27, 0x57, 0x20, // /* code=237, hex=0xED, ascii="!m" */
|
||||
// 0x34, 0x74, 0x30, // /* code=238, hex=0xEE, ascii="!n" */
|
||||
// 0x25, 0x55, 0x50, // /* code=239, hex=0xEF, ascii="!o" */
|
||||
// 0x70, 0x70, 0x70, // /* code=240, hex=0xF0, ascii="!p" */
|
||||
// 0x27, 0x20, 0x70, // /* code=241, hex=0xF1, ascii="!q" */
|
||||
// 0x61, 0x60, 0x70, // /* code=242, hex=0xF2, ascii="!r" */
|
||||
// 0x34, 0x30, 0x70, // /* code=243, hex=0xF3, ascii="!s" */
|
||||
// 0x01, 0x22, 0x22, // /* code=244, hex=0xF4, ascii="!t" */
|
||||
// 0x22, 0x22, 0x40, // /* code=245, hex=0xF5, ascii="!u" */
|
||||
// 0x20, 0x70, 0x20, // /* code=246, hex=0xF6, ascii="!v" */
|
||||
// 0x05, 0xA5, 0xA0, // /* code=247, hex=0xF7, ascii="!w" */
|
||||
// 0x25, 0x20, 0x00, // /* code=248, hex=0xF8, ascii="!x" */
|
||||
// 0x02, 0x72, 0x00, // /* code=249, hex=0xF9, ascii="!y" */
|
||||
// 0x00, 0x20, 0x00, // /* code=250, hex=0xFA, ascii="!z" */
|
||||
// 0x32, 0x26, 0x20, // /* code=251, hex=0xFB, ascii="!{" */
|
||||
// 0x75, 0x50, 0x00, // /* code=252, hex=0xFC, ascii="!|" */
|
||||
// 0x62, 0x46, 0x00, // /* code=253, hex=0xFD, ascii="!}" */
|
||||
// 0x00, 0x66, 0x00, // /* code=254, hex=0xFE, ascii="!~" */
|
||||
// 0x00, 0x00, 0x00, // /* code=255, hex=0xFF, ascii="!^" */
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
@@ -1,300 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// font courtesy of https://github.com/idispatch/raster-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 12
|
||||
* [2] Fixed/max glyph width: 5
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
static const unsigned char console_font_5x12[] PROGMEM = {
|
||||
0x57, 0x0C, 0x05, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
|
||||
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=0, hex=0x00, ascii="^@" */
|
||||
// 0x74, 0x63, 0xB8, 0xC7, 0x75, 0x8B, 0x80, 0x00, // /* code=1, hex=0x01, ascii="^A" */
|
||||
// 0x77, 0xFF, 0x5F, 0xFE, 0xBB, 0xFB, 0x80, 0x00, // /* code=2, hex=0x02, ascii="^B" */
|
||||
// 0x02, 0xB7, 0xFF, 0xFF, 0xEE, 0x71, 0x08, 0x00, // /* code=3, hex=0x03, ascii="^C" */
|
||||
// 0x21, 0x1C, 0xEF, 0xFF, 0xEE, 0x71, 0x08, 0x00, // /* code=4, hex=0x04, ascii="^D" */
|
||||
// 0x23, 0x9C, 0xE2, 0x7F, 0xEE, 0x23, 0x80, 0x00, // /* code=5, hex=0x05, ascii="^E" */
|
||||
// 0x21, 0x1C, 0xEF, 0xFF, 0xEE, 0x23, 0x80, 0x00, // /* code=6, hex=0x06, ascii="^F" */
|
||||
// 0x00, 0x00, 0x00, 0x31, 0x80, 0x00, 0x00, 0x00, // /* code=7, hex=0x07, ascii="^G" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xCE, 0x7F, 0xFF, 0xFF, 0xF0, // /* code=8, hex=0x08, ascii="^H" */
|
||||
// 0x00, 0x00, 0x07, 0x29, 0x4E, 0x00, 0x00, 0x00, // /* code=9, hex=0x09, ascii="^I" */
|
||||
// 0xFF, 0xFF, 0xF8, 0xD6, 0xB1, 0xFF, 0xFF, 0xF0, // /* code=10, hex=0x0A, ascii="^J" */
|
||||
// 0x00, 0x0E, 0x32, 0xB2, 0x52, 0x93, 0x00, 0x00, // /* code=11, hex=0x0B, ascii="^K" */
|
||||
// 0x03, 0x25, 0x29, 0x31, 0x08, 0xE2, 0x00, 0x00, // /* code=12, hex=0x0C, ascii="^L" */
|
||||
// 0x00, 0x08, 0x63, 0x10, 0x84, 0xE6, 0x00, 0x00, // /* code=13, hex=0x0D, ascii="^M" */
|
||||
// 0x32, 0x9C, 0xA5, 0x29, 0xCE, 0xC6, 0x00, 0x00, // /* code=14, hex=0x0E, ascii="^N" */
|
||||
// 0x00, 0x00, 0x0A, 0xBA, 0x2E, 0xA8, 0x00, 0x00, // /* code=15, hex=0x0F, ascii="^O" */
|
||||
// 0x00, 0x21, 0x8E, 0x7B, 0x98, 0x80, 0x00, 0x00, // /* code=16, hex=0x10, ascii="^P" */
|
||||
// 0x00, 0x04, 0x67, 0x79, 0xC6, 0x10, 0x00, 0x00, // /* code=17, hex=0x11, ascii="^Q" */
|
||||
// 0x01, 0x1D, 0x52, 0x10, 0x95, 0x71, 0x00, 0x00, // /* code=18, hex=0x12, ascii="^R" */
|
||||
// 0x00, 0x14, 0xA5, 0x29, 0x40, 0x52, 0x80, 0x00, // /* code=19, hex=0x13, ascii="^S" */
|
||||
// 0x03, 0xB5, 0xAD, 0x69, 0x4A, 0x52, 0x80, 0x00, // /* code=20, hex=0x14, ascii="^T" */
|
||||
// 0x03, 0x25, 0x06, 0x49, 0x82, 0x93, 0x00, 0x00, // /* code=21, hex=0x15, ascii="^U" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x80, 0x00, // /* code=22, hex=0x16, ascii="^V" */
|
||||
// 0x01, 0x1D, 0x52, 0x10, 0x95, 0x71, 0x3E, 0x00, // /* code=23, hex=0x17, ascii="^W" */
|
||||
// 0x01, 0x1D, 0x52, 0x10, 0x84, 0x21, 0x00, 0x00, // /* code=24, hex=0x18, ascii="^X" */
|
||||
// 0x00, 0x08, 0x42, 0x10, 0x95, 0x71, 0x00, 0x00, // /* code=25, hex=0x19, ascii="^Y" */
|
||||
// 0x00, 0x00, 0x82, 0x78, 0x88, 0x00, 0x00, 0x00, // /* code=26, hex=0x1A, ascii="^Z" */
|
||||
// 0x00, 0x00, 0x44, 0x79, 0x04, 0x00, 0x00, 0x00, // /* code=27, hex=0x1B, ascii="^[" */
|
||||
// 0x00, 0x00, 0x00, 0x42, 0x1E, 0x00, 0x00, 0x00, // /* code=28, hex=0x1C, ascii="^\" */
|
||||
// 0x00, 0x00, 0xA5, 0x7D, 0x4A, 0x00, 0x00, 0x00, // /* code=29, hex=0x1D, ascii="^]" */
|
||||
// 0x00, 0x00, 0x42, 0x39, 0xDF, 0x00, 0x00, 0x00, // /* code=30, hex=0x1E, ascii="^^" */
|
||||
// 0x00, 0x01, 0xF7, 0x38, 0x84, 0x00, 0x00, 0x00, // /* code=31, hex=0x1F, ascii="^_" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x01, 0x08, 0x42, 0x10, 0x80, 0x21, 0x00, 0x00, /* code=33, hex=0x21, ascii="!" */
|
||||
0x52, 0x94, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0x02, 0x95, 0xF5, 0x2B, 0xEA, 0x50, 0x00, 0x00, /* code=35, hex=0x23, ascii="#" */
|
||||
0x02, 0x19, 0x28, 0x20, 0x82, 0x93, 0x08, 0x00, /* code=36, hex=0x24, ascii="$" */
|
||||
0x06, 0xB4, 0x42, 0x21, 0x08, 0xB5, 0x80, 0x00, /* code=37, hex=0x25, ascii="%" */
|
||||
0x02, 0x29, 0x4A, 0x22, 0xB2, 0x93, 0x40, 0x00, /* code=38, hex=0x26, ascii="&" */
|
||||
0x31, 0x84, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x01, 0x10, 0x84, 0x21, 0x08, 0x41, 0x00, 0x00, /* code=40, hex=0x28, ascii="(" */
|
||||
0x02, 0x08, 0x42, 0x10, 0x84, 0x22, 0x00, 0x00, /* code=41, hex=0x29, ascii=")" */
|
||||
0x00, 0x09, 0x57, 0x3A, 0xA4, 0x00, 0x00, 0x00, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x00, 0x00, 0x42, 0x7C, 0x84, 0x00, 0x00, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x08, 0x80, /* code=44, hex=0x2C, ascii="," */
|
||||
0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, /* code=46, hex=0x2E, ascii="." */
|
||||
0x00, 0x84, 0x42, 0x21, 0x08, 0x84, 0x00, 0x00, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x03, 0xA5, 0x29, 0x4A, 0x52, 0x97, 0x00, 0x00, /* code=48, hex=0x30, ascii="0" */
|
||||
0x01, 0x19, 0x42, 0x10, 0x84, 0x27, 0x80, 0x00, /* code=49, hex=0x31, ascii="1" */
|
||||
0x03, 0x25, 0x21, 0x11, 0x10, 0x87, 0x80, 0x00, /* code=50, hex=0x32, ascii="2" */
|
||||
0x03, 0x24, 0x21, 0x30, 0x42, 0x93, 0x00, 0x00, /* code=51, hex=0x33, ascii="3" */
|
||||
0x00, 0x8C, 0xA5, 0x4B, 0xC2, 0x10, 0x80, 0x00, /* code=52, hex=0x34, ascii="4" */
|
||||
0x07, 0xA1, 0x08, 0x70, 0x42, 0x93, 0x00, 0x00, /* code=53, hex=0x35, ascii="5" */
|
||||
0x01, 0x91, 0x0E, 0x4A, 0x52, 0x93, 0x00, 0x00, /* code=54, hex=0x36, ascii="6" */
|
||||
0x07, 0x84, 0x21, 0x10, 0x88, 0x42, 0x00, 0x00, /* code=55, hex=0x37, ascii="7" */
|
||||
0x03, 0x25, 0x29, 0x32, 0x52, 0x93, 0x00, 0x00, /* code=56, hex=0x38, ascii="8" */
|
||||
0x03, 0x25, 0x29, 0x49, 0xC2, 0x26, 0x00, 0x00, /* code=57, hex=0x39, ascii="9" */
|
||||
0x00, 0x00, 0x06, 0x30, 0x00, 0x63, 0x00, 0x00, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x00, 0x00, 0x06, 0x30, 0x00, 0x63, 0x08, 0x80, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x00, 0x04, 0x44, 0x42, 0x08, 0x20, 0x80, 0x00, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x00, 0x00, 0x00, 0x78, 0x1E, 0x00, 0x00, 0x00, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x00, 0x20, 0x82, 0x08, 0x44, 0x44, 0x00, 0x00, /* code=62, hex=0x3E, ascii=">" */
|
||||
0x03, 0x25, 0x21, 0x10, 0x80, 0x21, 0x00, 0x00, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x03, 0x25, 0x29, 0x5A, 0xD0, 0x83, 0x80, 0x00, /* code=64, hex=0x40, ascii="@" */
|
||||
0x03, 0x25, 0x29, 0x7A, 0x52, 0x94, 0x80, 0x00, /* code=65, hex=0x41, ascii="A" */
|
||||
0x07, 0x25, 0x29, 0x72, 0x52, 0x97, 0x00, 0x00, /* code=66, hex=0x42, ascii="B" */
|
||||
0x03, 0x25, 0x28, 0x42, 0x10, 0x93, 0x00, 0x00, /* code=67, hex=0x43, ascii="C" */
|
||||
0x07, 0x25, 0x29, 0x4A, 0x52, 0x97, 0x00, 0x00, /* code=68, hex=0x44, ascii="D" */
|
||||
0x07, 0xA1, 0x08, 0x7A, 0x10, 0x87, 0x80, 0x00, /* code=69, hex=0x45, ascii="E" */
|
||||
0x07, 0xA1, 0x08, 0x7A, 0x10, 0x84, 0x00, 0x00, /* code=70, hex=0x46, ascii="F" */
|
||||
0x03, 0x25, 0x28, 0x42, 0xD2, 0x93, 0x00, 0x00, /* code=71, hex=0x47, ascii="G" */
|
||||
0x04, 0xA5, 0x29, 0x7A, 0x52, 0x94, 0x80, 0x00, /* code=72, hex=0x48, ascii="H" */
|
||||
0x03, 0x88, 0x42, 0x10, 0x84, 0x23, 0x80, 0x00, /* code=73, hex=0x49, ascii="I" */
|
||||
0x03, 0x88, 0x42, 0x10, 0x94, 0xA2, 0x00, 0x00, /* code=74, hex=0x4A, ascii="J" */
|
||||
0x04, 0xA5, 0x4A, 0x62, 0x94, 0x94, 0x80, 0x00, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x04, 0x21, 0x08, 0x42, 0x10, 0x87, 0x80, 0x00, /* code=76, hex=0x4C, ascii="L" */
|
||||
0x04, 0xA5, 0xEF, 0x4A, 0x52, 0x94, 0x80, 0x00, /* code=77, hex=0x4D, ascii="M" */
|
||||
0x04, 0xA5, 0xAD, 0x5A, 0xD2, 0x94, 0x80, 0x00, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x03, 0x25, 0x29, 0x4A, 0x52, 0x93, 0x00, 0x00, /* code=79, hex=0x4F, ascii="O" */
|
||||
0x07, 0x25, 0x29, 0x72, 0x10, 0x84, 0x00, 0x00, /* code=80, hex=0x50, ascii="P" */
|
||||
0x03, 0x25, 0x29, 0x4A, 0x52, 0x93, 0x08, 0x20, /* code=81, hex=0x51, ascii="Q" */
|
||||
0x07, 0x25, 0x29, 0x73, 0x14, 0x94, 0x80, 0x00, /* code=82, hex=0x52, ascii="R" */
|
||||
0x03, 0x25, 0x28, 0x30, 0x52, 0x93, 0x00, 0x00, /* code=83, hex=0x53, ascii="S" */
|
||||
0x07, 0xC8, 0x42, 0x10, 0x84, 0x21, 0x00, 0x00, /* code=84, hex=0x54, ascii="T" */
|
||||
0x04, 0xA5, 0x29, 0x4A, 0x52, 0x93, 0x00, 0x00, /* code=85, hex=0x55, ascii="U" */
|
||||
0x04, 0xA5, 0x29, 0x49, 0x4A, 0x21, 0x00, 0x00, /* code=86, hex=0x56, ascii="V" */
|
||||
0x04, 0xA5, 0x29, 0x4A, 0x5E, 0x94, 0x80, 0x00, /* code=87, hex=0x57, ascii="W" */
|
||||
0x04, 0xA5, 0x26, 0x31, 0x92, 0x94, 0x80, 0x00, /* code=88, hex=0x58, ascii="X" */
|
||||
0x04, 0x63, 0x15, 0x28, 0x84, 0x21, 0x00, 0x00, /* code=89, hex=0x59, ascii="Y" */
|
||||
0x07, 0x84, 0x42, 0x21, 0x10, 0x87, 0x80, 0x00, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0x03, 0x10, 0x84, 0x21, 0x08, 0x43, 0x00, 0x00, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x04, 0x20, 0x84, 0x10, 0x84, 0x10, 0x80, 0x00, /* code=92, hex=0x5C, ascii="\" */
|
||||
0x03, 0x08, 0x42, 0x10, 0x84, 0x23, 0x00, 0x00, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x01, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x63, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x00, 0x00, 0x06, 0x09, 0xD2, 0x93, 0x80, 0x00, /* code=97, hex=0x61, ascii="a" */
|
||||
0x04, 0x21, 0x0E, 0x4A, 0x52, 0x97, 0x00, 0x00, /* code=98, hex=0x62, ascii="b" */
|
||||
0x00, 0x00, 0x07, 0x42, 0x10, 0x83, 0x80, 0x00, /* code=99, hex=0x63, ascii="c" */
|
||||
0x00, 0x84, 0x27, 0x4A, 0x52, 0x93, 0x80, 0x00, /* code=100, hex=0x64, ascii="d" */
|
||||
0x00, 0x00, 0x06, 0x4A, 0x5E, 0x83, 0x80, 0x00, /* code=101, hex=0x65, ascii="e" */
|
||||
0x01, 0x10, 0x8E, 0x21, 0x08, 0x42, 0x00, 0x00, /* code=102, hex=0x66, ascii="f" */
|
||||
0x00, 0x00, 0x07, 0x4A, 0x52, 0x93, 0x84, 0xC0, /* code=103, hex=0x67, ascii="g" */
|
||||
0x04, 0x21, 0x0E, 0x4A, 0x52, 0x94, 0x80, 0x00, /* code=104, hex=0x68, ascii="h" */
|
||||
0x00, 0x08, 0x02, 0x10, 0x84, 0x23, 0x80, 0x00, /* code=105, hex=0x69, ascii="i" */
|
||||
0x00, 0x08, 0x02, 0x10, 0x84, 0x21, 0x28, 0x80, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x04, 0x21, 0x09, 0x53, 0x18, 0xA4, 0x80, 0x00, /* code=107, hex=0x6B, ascii="k" */
|
||||
0x03, 0x08, 0x42, 0x10, 0x84, 0x23, 0x80, 0x00, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x00, 0x00, 0x09, 0x7A, 0x52, 0x94, 0x80, 0x00, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x00, 0x00, 0x0A, 0x6A, 0x52, 0x94, 0x80, 0x00, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x00, 0x00, 0x06, 0x4A, 0x52, 0x93, 0x00, 0x00, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x00, 0x00, 0x0E, 0x4A, 0x52, 0x97, 0x21, 0x00, /* code=112, hex=0x70, ascii="p" */
|
||||
0x00, 0x00, 0x07, 0x4A, 0x52, 0x93, 0x84, 0x20, /* code=113, hex=0x71, ascii="q" */
|
||||
0x00, 0x00, 0x0B, 0x29, 0x08, 0x42, 0x00, 0x00, /* code=114, hex=0x72, ascii="r" */
|
||||
0x00, 0x00, 0x06, 0x49, 0x04, 0x93, 0x00, 0x00, /* code=115, hex=0x73, ascii="s" */
|
||||
0x00, 0x10, 0x8E, 0x21, 0x08, 0x43, 0x00, 0x00, /* code=116, hex=0x74, ascii="t" */
|
||||
0x00, 0x00, 0x09, 0x4A, 0x52, 0x93, 0x80, 0x00, /* code=117, hex=0x75, ascii="u" */
|
||||
0x00, 0x00, 0x09, 0x49, 0x4A, 0x21, 0x00, 0x00, /* code=118, hex=0x76, ascii="v" */
|
||||
0x00, 0x00, 0x09, 0x4A, 0x52, 0xF4, 0x80, 0x00, /* code=119, hex=0x77, ascii="w" */
|
||||
0x00, 0x00, 0x09, 0x49, 0x8C, 0x94, 0x80, 0x00, /* code=120, hex=0x78, ascii="x" */
|
||||
0x00, 0x00, 0x09, 0x4A, 0x52, 0x93, 0x85, 0xC0, /* code=121, hex=0x79, ascii="y" */
|
||||
0x00, 0x00, 0x0F, 0x08, 0x88, 0x87, 0x80, 0x00, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x01, 0x10, 0x84, 0x41, 0x08, 0x41, 0x00, 0x00, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x21, 0x08, 0x40, 0x10, 0x84, 0x21, 0x00, 0x00, /* code=124, hex=0x7C, ascii="|" */
|
||||
0x02, 0x08, 0x42, 0x08, 0x84, 0x22, 0x00, 0x00, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x02, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=126, hex=0x7E, ascii="~" */
|
||||
// 0x01, 0x08, 0xA5, 0x46, 0x31, 0x8F, 0xC0, 0x00, // /* code=127, hex=0x7F, ascii="^?" */
|
||||
// 0x03, 0x25, 0x28, 0x42, 0x10, 0x93, 0x08, 0x80, // /* code=128, hex=0x80, ascii="!^@" */
|
||||
// 0x00, 0x14, 0x09, 0x4A, 0x52, 0x93, 0x80, 0x00, // /* code=129, hex=0x81, ascii="!^A" */
|
||||
// 0x01, 0x90, 0x06, 0x4A, 0x5E, 0x83, 0x80, 0x00, // /* code=130, hex=0x82, ascii="!^B" */
|
||||
// 0x03, 0x24, 0x06, 0x48, 0x4E, 0x93, 0x80, 0x00, // /* code=131, hex=0x83, ascii="!^C" */
|
||||
// 0x00, 0x14, 0x06, 0x48, 0x4E, 0x93, 0x80, 0x00, // /* code=132, hex=0x84, ascii="!^D" */
|
||||
// 0x06, 0x08, 0x06, 0x48, 0x4E, 0x93, 0x80, 0x00, // /* code=133, hex=0x85, ascii="!^E" */
|
||||
// 0x01, 0x14, 0x46, 0x48, 0x4E, 0x93, 0x80, 0x00, // /* code=134, hex=0x86, ascii="!^F" */
|
||||
// 0x00, 0x00, 0x07, 0x42, 0x10, 0x83, 0x89, 0x80, // /* code=135, hex=0x87, ascii="!^G" */
|
||||
// 0x03, 0x24, 0x06, 0x4A, 0x5E, 0x83, 0x80, 0x00, // /* code=136, hex=0x88, ascii="!^H" */
|
||||
// 0x00, 0x14, 0x06, 0x4A, 0x5E, 0x83, 0x80, 0x00, // /* code=137, hex=0x89, ascii="!^I" */
|
||||
// 0x06, 0x08, 0x06, 0x4A, 0x5E, 0x83, 0x80, 0x00, // /* code=138, hex=0x8A, ascii="!^J" */
|
||||
// 0x00, 0x14, 0x06, 0x10, 0x84, 0x23, 0x80, 0x00, // /* code=139, hex=0x8B, ascii="!^K" */
|
||||
// 0x03, 0x24, 0x06, 0x10, 0x84, 0x23, 0x80, 0x00, // /* code=140, hex=0x8C, ascii="!^L" */
|
||||
// 0x03, 0x04, 0x06, 0x10, 0x84, 0x23, 0x80, 0x00, // /* code=141, hex=0x8D, ascii="!^M" */
|
||||
// 0x50, 0x19, 0x29, 0x4B, 0xD2, 0x94, 0x80, 0x00, // /* code=142, hex=0x8E, ascii="!^N" */
|
||||
// 0x22, 0x88, 0xC9, 0x4B, 0xD2, 0x94, 0x80, 0x00, // /* code=143, hex=0x8F, ascii="!^O" */
|
||||
// 0x32, 0x01, 0xE8, 0x43, 0xD0, 0x87, 0x80, 0x00, // /* code=144, hex=0x90, ascii="!^P" */
|
||||
// 0x00, 0x00, 0x07, 0x14, 0xAE, 0xA2, 0x80, 0x00, // /* code=145, hex=0x91, ascii="!^Q" */
|
||||
// 0x00, 0x1D, 0xCA, 0x53, 0xD4, 0xA5, 0x80, 0x00, // /* code=146, hex=0x92, ascii="!^R" */
|
||||
// 0x03, 0x24, 0x06, 0x4A, 0x52, 0x93, 0x00, 0x00, // /* code=147, hex=0x93, ascii="!^S" */
|
||||
// 0x00, 0x14, 0x06, 0x4A, 0x52, 0x93, 0x00, 0x00, // /* code=148, hex=0x94, ascii="!^T" */
|
||||
// 0x03, 0x04, 0x06, 0x4A, 0x52, 0x93, 0x00, 0x00, // /* code=149, hex=0x95, ascii="!^U" */
|
||||
// 0x03, 0x24, 0x09, 0x4A, 0x52, 0x93, 0x80, 0x00, // /* code=150, hex=0x96, ascii="!^V" */
|
||||
// 0x06, 0x08, 0x09, 0x4A, 0x52, 0x93, 0x80, 0x00, // /* code=151, hex=0x97, ascii="!^W" */
|
||||
// 0x00, 0x14, 0x09, 0x4A, 0x52, 0x93, 0x85, 0xC0, // /* code=152, hex=0x98, ascii="!^X" */
|
||||
// 0x50, 0x19, 0x29, 0x4A, 0x52, 0x93, 0x00, 0x00, // /* code=153, hex=0x99, ascii="!^Y" */
|
||||
// 0x50, 0x25, 0x29, 0x4A, 0x52, 0x93, 0x80, 0x00, // /* code=154, hex=0x9A, ascii="!^Z" */
|
||||
// 0x00, 0x00, 0x47, 0x46, 0x11, 0x71, 0x00, 0x00, // /* code=155, hex=0x9B, ascii="!^[" */
|
||||
// 0x01, 0x14, 0x84, 0x71, 0x08, 0xD7, 0x80, 0x00, // /* code=156, hex=0x9C, ascii="!^\" */
|
||||
// 0x04, 0x54, 0xAF, 0x93, 0xE4, 0x21, 0x00, 0x00, // /* code=157, hex=0x9D, ascii="!^]" */
|
||||
// 0x07, 0x25, 0x2E, 0x4A, 0xF2, 0x94, 0x80, 0x00, // /* code=158, hex=0x9E, ascii="!^^" */
|
||||
// 0x11, 0x08, 0x42, 0x38, 0x84, 0x21, 0x28, 0x80, // /* code=159, hex=0x9F, ascii="!^_" */
|
||||
// 0x01, 0x90, 0x06, 0x48, 0x4E, 0x93, 0x80, 0x00, // /* code=160, hex=0xA0, ascii="! " */
|
||||
// 0x01, 0x90, 0x06, 0x10, 0x84, 0x23, 0x80, 0x00, // /* code=161, hex=0xA1, ascii="!!" */
|
||||
// 0x01, 0x90, 0x06, 0x4A, 0x52, 0x93, 0x00, 0x00, // /* code=162, hex=0xA2, ascii="!"" */
|
||||
// 0x01, 0x90, 0x09, 0x4A, 0x52, 0x93, 0x80, 0x00, // /* code=163, hex=0xA3, ascii="!#" */
|
||||
// 0x02, 0xA8, 0x0A, 0x6A, 0x52, 0x94, 0x80, 0x00, // /* code=164, hex=0xA4, ascii="!$" */
|
||||
// 0x55, 0x01, 0x2D, 0x7A, 0xD2, 0x94, 0x80, 0x00, // /* code=165, hex=0xA5, ascii="!%" */
|
||||
// 0x03, 0x04, 0xE9, 0x38, 0x1E, 0x00, 0x00, 0x00, // /* code=166, hex=0xA6, ascii="!&" */
|
||||
// 0x03, 0x25, 0x29, 0x30, 0x1E, 0x00, 0x00, 0x00, // /* code=167, hex=0xA7, ascii="!'" */
|
||||
// 0x02, 0x10, 0x04, 0x22, 0x10, 0x93, 0x00, 0x00, // /* code=168, hex=0xA8, ascii="!(" */
|
||||
// 0x00, 0x00, 0x00, 0x03, 0xD0, 0x80, 0x00, 0x00, // /* code=169, hex=0xA9, ascii="!)" */
|
||||
// 0x00, 0x00, 0x00, 0x03, 0xC2, 0x10, 0x00, 0x00, // /* code=170, hex=0xAA, ascii="!*" */
|
||||
// 0x02, 0x14, 0xA2, 0x21, 0xD2, 0xA1, 0x80, 0x00, // /* code=171, hex=0xAB, ascii="!+" */
|
||||
// 0x02, 0x14, 0xA2, 0x21, 0x56, 0xB0, 0x80, 0x00, // /* code=172, hex=0xAC, ascii="!," */
|
||||
// 0x00, 0x08, 0x02, 0x10, 0x84, 0x21, 0x00, 0x00, // /* code=173, hex=0xAD, ascii="!-" */
|
||||
// 0x00, 0x00, 0xA5, 0x52, 0x8A, 0x50, 0x00, 0x00, // /* code=174, hex=0xAE, ascii="!." */
|
||||
// 0x00, 0x01, 0x4A, 0x29, 0x54, 0xA0, 0x00, 0x00, // /* code=175, hex=0xAF, ascii="!/" */
|
||||
// 0x00, 0x2A, 0x00, 0x28, 0x00, 0xA8, 0x00, 0xA0, // /* code=176, hex=0xB0, ascii="!0" */
|
||||
// 0x02, 0x81, 0x50, 0x28, 0x15, 0x02, 0x81, 0x50, // /* code=177, hex=0xB1, ascii="!1" */
|
||||
// 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, // /* code=178, hex=0xB2, ascii="!2" */
|
||||
// 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0x08, 0x40, // /* code=179, hex=0xB3, ascii="!3" */
|
||||
// 0x21, 0x08, 0x42, 0x70, 0x84, 0x21, 0x08, 0x40, // /* code=180, hex=0xB4, ascii="!4" */
|
||||
// 0x21, 0x08, 0x4E, 0x13, 0x84, 0x21, 0x08, 0x40, // /* code=181, hex=0xB5, ascii="!5" */
|
||||
// 0x52, 0x94, 0xA5, 0x69, 0x4A, 0x52, 0x94, 0xA0, // /* code=182, hex=0xB6, ascii="!6" */
|
||||
// 0x00, 0x00, 0x00, 0x79, 0x4A, 0x52, 0x94, 0xA0, // /* code=183, hex=0xB7, ascii="!7" */
|
||||
// 0x00, 0x00, 0x0E, 0x13, 0x84, 0x21, 0x08, 0x40, // /* code=184, hex=0xB8, ascii="!8" */
|
||||
// 0x52, 0x94, 0xAD, 0x0B, 0x4A, 0x52, 0x94, 0xA0, // /* code=185, hex=0xB9, ascii="!9" */
|
||||
// 0x52, 0x94, 0xA5, 0x29, 0x4A, 0x52, 0x94, 0xA0, // /* code=186, hex=0xBA, ascii="!:" */
|
||||
// 0x00, 0x00, 0x0F, 0x0B, 0x4A, 0x52, 0x94, 0xA0, // /* code=187, hex=0xBB, ascii="!;" */
|
||||
// 0x52, 0x94, 0xAD, 0x0B, 0xC0, 0x00, 0x00, 0x00, // /* code=188, hex=0xBC, ascii="!<" */
|
||||
// 0x52, 0x94, 0xA5, 0x78, 0x00, 0x00, 0x00, 0x00, // /* code=189, hex=0xBD, ascii="!=" */
|
||||
// 0x21, 0x08, 0x4E, 0x13, 0x80, 0x00, 0x00, 0x00, // /* code=190, hex=0xBE, ascii="!>" */
|
||||
// 0x00, 0x00, 0x00, 0x70, 0x84, 0x21, 0x08, 0x40, // /* code=191, hex=0xBF, ascii="!?" */
|
||||
// 0x21, 0x08, 0x42, 0x1C, 0x00, 0x00, 0x00, 0x00, // /* code=192, hex=0xC0, ascii="!@" */
|
||||
// 0x21, 0x08, 0x42, 0x7C, 0x00, 0x00, 0x00, 0x00, // /* code=193, hex=0xC1, ascii="!A" */
|
||||
// 0x00, 0x00, 0x00, 0x7C, 0x84, 0x21, 0x08, 0x40, // /* code=194, hex=0xC2, ascii="!B" */
|
||||
// 0x21, 0x08, 0x42, 0x1C, 0x84, 0x21, 0x08, 0x40, // /* code=195, hex=0xC3, ascii="!C" */
|
||||
// 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, // /* code=196, hex=0xC4, ascii="!D" */
|
||||
// 0x21, 0x08, 0x42, 0x7C, 0x84, 0x21, 0x08, 0x40, // /* code=197, hex=0xC5, ascii="!E" */
|
||||
// 0x21, 0x08, 0x43, 0x90, 0xE4, 0x21, 0x08, 0x40, // /* code=198, hex=0xC6, ascii="!F" */
|
||||
// 0x52, 0x94, 0xA5, 0x2D, 0x4A, 0x52, 0x94, 0xA0, // /* code=199, hex=0xC7, ascii="!G" */
|
||||
// 0x52, 0x94, 0xA5, 0xA1, 0xE0, 0x00, 0x00, 0x00, // /* code=200, hex=0xC8, ascii="!H" */
|
||||
// 0x00, 0x00, 0x07, 0xA1, 0x6A, 0x52, 0x94, 0xA0, // /* code=201, hex=0xC9, ascii="!I" */
|
||||
// 0x52, 0x94, 0xAD, 0x83, 0xE0, 0x00, 0x00, 0x00, // /* code=202, hex=0xCA, ascii="!J" */
|
||||
// 0x00, 0x00, 0x0F, 0x83, 0x6A, 0x52, 0x94, 0xA0, // /* code=203, hex=0xCB, ascii="!K" */
|
||||
// 0x52, 0x94, 0xA5, 0xA1, 0x6A, 0x52, 0x94, 0xA0, // /* code=204, hex=0xCC, ascii="!L" */
|
||||
// 0x00, 0x00, 0x0F, 0x83, 0xE0, 0x00, 0x00, 0x00, // /* code=205, hex=0xCD, ascii="!M" */
|
||||
// 0x52, 0x94, 0xAD, 0x83, 0x6A, 0x52, 0x94, 0xA0, // /* code=206, hex=0xCE, ascii="!N" */
|
||||
// 0x21, 0x08, 0x4F, 0x83, 0xE0, 0x00, 0x00, 0x00, // /* code=207, hex=0xCF, ascii="!O" */
|
||||
// 0x52, 0x94, 0xA5, 0x7C, 0x00, 0x00, 0x00, 0x00, // /* code=208, hex=0xD0, ascii="!P" */
|
||||
// 0x00, 0x00, 0x0F, 0x83, 0xE4, 0x21, 0x08, 0x40, // /* code=209, hex=0xD1, ascii="!Q" */
|
||||
// 0x00, 0x00, 0x00, 0x7D, 0x4A, 0x52, 0x94, 0xA0, // /* code=210, hex=0xD2, ascii="!R" */
|
||||
// 0x52, 0x94, 0xA5, 0x3C, 0x00, 0x00, 0x00, 0x00, // /* code=211, hex=0xD3, ascii="!S" */
|
||||
// 0x21, 0x08, 0x43, 0x90, 0xE0, 0x00, 0x00, 0x00, // /* code=212, hex=0xD4, ascii="!T" */
|
||||
// 0x00, 0x00, 0x03, 0x90, 0xE4, 0x21, 0x08, 0x40, // /* code=213, hex=0xD5, ascii="!U" */
|
||||
// 0x00, 0x00, 0x00, 0x3D, 0x4A, 0x52, 0x94, 0xA0, // /* code=214, hex=0xD6, ascii="!V" */
|
||||
// 0x52, 0x94, 0xA5, 0x6D, 0x4A, 0x52, 0x94, 0xA0, // /* code=215, hex=0xD7, ascii="!W" */
|
||||
// 0x21, 0x08, 0x4F, 0x83, 0xE4, 0x21, 0x08, 0x40, // /* code=216, hex=0xD8, ascii="!X" */
|
||||
// 0x21, 0x08, 0x42, 0x70, 0x00, 0x00, 0x00, 0x00, // /* code=217, hex=0xD9, ascii="!Y" */
|
||||
// 0x00, 0x00, 0x00, 0x1C, 0x84, 0x21, 0x08, 0x40, // /* code=218, hex=0xDA, ascii="!Z" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, // /* code=219, hex=0xDB, ascii="![" */
|
||||
// 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, // /* code=220, hex=0xDC, ascii="!\" */
|
||||
// 0xE7, 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x39, 0xC0, // /* code=221, hex=0xDD, ascii="!]" */
|
||||
// 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x39, 0xCE, 0x70, // /* code=222, hex=0xDE, ascii="!^" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, // /* code=223, hex=0xDF, ascii="!_" */
|
||||
// 0x00, 0x00, 0x9B, 0x52, 0x96, 0x48, 0x00, 0x00, // /* code=224, hex=0xE0, ascii="!`" */
|
||||
// 0x03, 0x25, 0x2A, 0x72, 0x52, 0x97, 0x21, 0x00, // /* code=225, hex=0xE1, ascii="!a" */
|
||||
// 0x00, 0x1E, 0x94, 0x21, 0x08, 0x42, 0x00, 0x00, // /* code=226, hex=0xE2, ascii="!b" */
|
||||
// 0x00, 0x00, 0x00, 0x7D, 0x4A, 0x52, 0x80, 0x00, // /* code=227, hex=0xE3, ascii="!c" */
|
||||
// 0x00, 0x01, 0xF4, 0x90, 0x44, 0x4F, 0xC0, 0x00, // /* code=228, hex=0xE4, ascii="!d" */
|
||||
// 0x00, 0x00, 0x0F, 0xD2, 0x94, 0x40, 0x00, 0x00, // /* code=229, hex=0xE5, ascii="!e" */
|
||||
// 0x00, 0x00, 0x09, 0x4A, 0x52, 0x97, 0x21, 0x00, // /* code=230, hex=0xE6, ascii="!f" */
|
||||
// 0x00, 0x00, 0x00, 0x2A, 0x84, 0x21, 0x00, 0x00, // /* code=231, hex=0xE7, ascii="!g" */
|
||||
// 0x00, 0x00, 0x07, 0x11, 0x4A, 0x23, 0x80, 0x00, // /* code=232, hex=0xE8, ascii="!h" */
|
||||
// 0x00, 0x00, 0x45, 0x47, 0xF1, 0x51, 0x00, 0x00, // /* code=233, hex=0xE9, ascii="!i" */
|
||||
// 0x00, 0x00, 0x45, 0x46, 0x2A, 0x56, 0xC0, 0x00, // /* code=234, hex=0xEA, ascii="!j" */
|
||||
// 0x00, 0x0C, 0x84, 0x11, 0xD2, 0x93, 0x00, 0x00, // /* code=235, hex=0xEB, ascii="!k" */
|
||||
// 0x00, 0x00, 0x05, 0x56, 0xAA, 0x00, 0x00, 0x00, // /* code=236, hex=0xEC, ascii="!l" */
|
||||
// 0x00, 0x08, 0x47, 0x56, 0xAE, 0x21, 0x00, 0x00, // /* code=237, hex=0xED, ascii="!m" */
|
||||
// 0x00, 0x00, 0xE8, 0x43, 0x90, 0x83, 0x80, 0x00, // /* code=238, hex=0xEE, ascii="!n" */
|
||||
// 0x00, 0x00, 0xC9, 0x4A, 0x52, 0x90, 0x00, 0x00, // /* code=239, hex=0xEF, ascii="!o" */
|
||||
// 0x00, 0x01, 0xE0, 0x78, 0x1E, 0x00, 0x00, 0x00, // /* code=240, hex=0xF0, ascii="!p" */
|
||||
// 0x00, 0x00, 0x42, 0x7C, 0x84, 0x07, 0xC0, 0x00, // /* code=241, hex=0xF1, ascii="!q" */
|
||||
// 0x04, 0x10, 0x41, 0x11, 0x10, 0x07, 0x80, 0x00, // /* code=242, hex=0xF2, ascii="!r" */
|
||||
// 0x00, 0x88, 0x88, 0x20, 0x82, 0x07, 0x80, 0x00, // /* code=243, hex=0xF3, ascii="!s" */
|
||||
// 0x00, 0x04, 0x52, 0x10, 0x84, 0x21, 0x08, 0x40, // /* code=244, hex=0xF4, ascii="!t" */
|
||||
// 0x21, 0x08, 0x42, 0x10, 0x84, 0xA2, 0x00, 0x00, // /* code=245, hex=0xF5, ascii="!u" */
|
||||
// 0x00, 0x08, 0x40, 0x7C, 0x04, 0x20, 0x00, 0x00, // /* code=246, hex=0xF6, ascii="!v" */
|
||||
// 0x00, 0x00, 0x05, 0x50, 0x0A, 0xA0, 0x00, 0x00, // /* code=247, hex=0xF7, ascii="!w" */
|
||||
// 0x64, 0xA4, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=248, hex=0xF8, ascii="!x" */
|
||||
// 0x00, 0x00, 0x02, 0x38, 0x80, 0x00, 0x00, 0x00, // /* code=249, hex=0xF9, ascii="!y" */
|
||||
// 0x00, 0x00, 0x00, 0x10, 0x80, 0x00, 0x00, 0x00, // /* code=250, hex=0xFA, ascii="!z" */
|
||||
// 0x01, 0xC8, 0x42, 0x12, 0x94, 0x61, 0x00, 0x00, // /* code=251, hex=0xFB, ascii="!{" */
|
||||
// 0xA6, 0xA5, 0x29, 0x48, 0x00, 0x00, 0x00, 0x00, // /* code=252, hex=0xFC, ascii="!|" */
|
||||
// 0x64, 0x84, 0xC8, 0x78, 0x00, 0x00, 0x00, 0x00, // /* code=253, hex=0xFD, ascii="!}" */
|
||||
// 0x00, 0x00, 0xE7, 0x39, 0xCE, 0x00, 0x00, 0x00, // /* code=254, hex=0xFE, ascii="!~" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=255, hex=0xFF, ascii="!^" */
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
@@ -1,300 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// font courtesy of https://github.com/idispatch/raster-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 8
|
||||
* [2] Fixed/max glyph width: 5
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
static const unsigned char console_font_5x8[] PROGMEM = {
|
||||
0x57, 0x08, 0x05, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
|
||||
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=0, hex=0x00, ascii="^@" */
|
||||
// 0x00, 0x1D, 0x5F, 0xED, 0xC0, // /* code=1, hex=0x01, ascii="^A" */
|
||||
// 0x00, 0x1D, 0x5F, 0xFD, 0xC0, // /* code=2, hex=0x02, ascii="^B" */
|
||||
// 0x00, 0x15, 0xFF, 0xB8, 0x80, // /* code=3, hex=0x03, ascii="^C" */
|
||||
// 0x00, 0x08, 0xEF, 0xB8, 0x80, // /* code=4, hex=0x04, ascii="^D" */
|
||||
// 0x00, 0x1D, 0x5F, 0x90, 0x80, // /* code=5, hex=0x05, ascii="^E" */
|
||||
// 0x00, 0x08, 0xEF, 0xD4, 0x80, // /* code=6, hex=0x06, ascii="^F" */
|
||||
// 0x00, 0x00, 0x47, 0x10, 0x00, // /* code=7, hex=0x07, ascii="^G" */
|
||||
// 0x07, 0xFF, 0xB8, 0xEF, 0xFF, // /* code=8, hex=0x08, ascii="^H" */
|
||||
// 0x00, 0x00, 0x45, 0x10, 0x00, // /* code=9, hex=0x09, ascii="^I" */
|
||||
// 0x07, 0xFF, 0xB8, 0xEF, 0xFF, // /* code=10, hex=0x0A, ascii="^J" */
|
||||
// 0x00, 0x0E, 0x36, 0xD1, 0x00, // /* code=11, hex=0x0B, ascii="^K" */
|
||||
// 0x00, 0x08, 0xA2, 0x38, 0x80, // /* code=12, hex=0x0C, ascii="^L" */
|
||||
// 0x00, 0x08, 0xA4, 0x62, 0x00, // /* code=13, hex=0x0D, ascii="^M" */
|
||||
// 0x00, 0x0E, 0x95, 0xEA, 0x00, // /* code=14, hex=0x0E, ascii="^N" */
|
||||
// 0x00, 0x00, 0x45, 0x10, 0x00, // /* code=15, hex=0x0F, ascii="^O" */
|
||||
// 0x00, 0x10, 0xC7, 0x31, 0x00, // /* code=16, hex=0x10, ascii="^P" */
|
||||
// 0x00, 0x04, 0x67, 0x18, 0x40, // /* code=17, hex=0x11, ascii="^Q" */
|
||||
// 0x00, 0x08, 0xE2, 0x38, 0x80, // /* code=18, hex=0x12, ascii="^R" */
|
||||
// 0x00, 0x14, 0xA5, 0x01, 0x40, // /* code=19, hex=0x13, ascii="^S" */
|
||||
// 0x00, 0x1F, 0xAD, 0x29, 0x4A, // /* code=20, hex=0x14, ascii="^T" */
|
||||
// 0x00, 0x06, 0xC9, 0x24, 0xD8, // /* code=21, hex=0x15, ascii="^U" */
|
||||
// 0x00, 0x00, 0x00, 0x7F, 0xE0, // /* code=22, hex=0x16, ascii="^V" */
|
||||
// 0x00, 0x08, 0xE2, 0x38, 0x8E, // /* code=23, hex=0x17, ascii="^W" */
|
||||
// 0x00, 0x08, 0xE2, 0x10, 0x80, // /* code=24, hex=0x18, ascii="^X" */
|
||||
// 0x00, 0x08, 0x42, 0x38, 0x80, // /* code=25, hex=0x19, ascii="^Y" */
|
||||
// 0x00, 0x00, 0x2F, 0x88, 0x00, // /* code=26, hex=0x1A, ascii="^Z" */
|
||||
// 0x00, 0x00, 0x8F, 0xA0, 0x00, // /* code=27, hex=0x1B, ascii="^[" */
|
||||
// 0x00, 0x00, 0x08, 0x7C, 0x00, // /* code=28, hex=0x1C, ascii="^\" */
|
||||
// 0x00, 0x00, 0xAF, 0xA8, 0x00, // /* code=29, hex=0x1D, ascii="^]" */
|
||||
// 0x00, 0x00, 0x02, 0x3B, 0xE0, // /* code=30, hex=0x1E, ascii="^^" */
|
||||
// 0x00, 0x00, 0x0F, 0xB8, 0x80, // /* code=31, hex=0x1F, ascii="^_" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x00, 0x08, 0x42, 0x00, 0x80, /* code=33, hex=0x21, ascii="!" */
|
||||
0x00, 0x14, 0xA0, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0x00, 0x15, 0xF5, 0x7D, 0x40, /* code=35, hex=0x23, ascii="#" */
|
||||
0x00, 0x08, 0x64, 0x19, 0x84, /* code=36, hex=0x24, ascii="$" */
|
||||
0x02, 0x2A, 0xA3, 0x36, 0x40, /* code=37, hex=0x25, ascii="%" */
|
||||
0x00, 0x0C, 0x86, 0xC9, 0xA0, /* code=38, hex=0x26, ascii="&" */
|
||||
0x01, 0x08, 0x00, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x00, 0x08, 0x84, 0x20, 0x80, /* code=40, hex=0x28, ascii="(" */
|
||||
0x00, 0x10, 0x42, 0x11, 0x00, /* code=41, hex=0x29, ascii=")" */
|
||||
0x00, 0x14, 0x47, 0x11, 0x40, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x00, 0x00, 0x47, 0x10, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x88, /* code=44, hex=0x2C, ascii="," */
|
||||
0x00, 0x01, 0xE0, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, /* code=46, hex=0x2E, ascii="." */
|
||||
0x00, 0x04, 0x42, 0x21, 0x00, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x00, 0x19, 0x29, 0x49, 0x80, /* code=48, hex=0x30, ascii="0" */
|
||||
0x00, 0x08, 0xC2, 0x10, 0x80, /* code=49, hex=0x31, ascii="1" */
|
||||
0x00, 0x19, 0x22, 0x23, 0xC0, /* code=50, hex=0x32, ascii="2" */
|
||||
0x00, 0x38, 0x26, 0x0B, 0x80, /* code=51, hex=0x33, ascii="3" */
|
||||
0x00, 0x04, 0x65, 0x78, 0x40, /* code=52, hex=0x34, ascii="4" */
|
||||
0x00, 0x3D, 0x0E, 0x0B, 0x80, /* code=53, hex=0x35, ascii="5" */
|
||||
0x00, 0x19, 0x0E, 0x49, 0x80, /* code=54, hex=0x36, ascii="6" */
|
||||
0x00, 0x3C, 0x22, 0x21, 0x00, /* code=55, hex=0x37, ascii="7" */
|
||||
0x00, 0x19, 0x26, 0x49, 0x80, /* code=56, hex=0x38, ascii="8" */
|
||||
0x00, 0x19, 0x27, 0x09, 0x80, /* code=57, hex=0x39, ascii="9" */
|
||||
0x00, 0x00, 0x02, 0x00, 0x80, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x00, 0x00, 0x02, 0x00, 0x88, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x00, 0x04, 0x44, 0x10, 0x40, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x00, 0x00, 0xE0, 0x38, 0x00, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x00, 0x10, 0x41, 0x11, 0x00, /* code=62, hex=0x3E, ascii=">" */
|
||||
0x00, 0x18, 0x26, 0x01, 0x00, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x00, 0x1D, 0x1B, 0x41, 0xC0, /* code=64, hex=0x40, ascii="@" */
|
||||
0x00, 0x19, 0x2F, 0x4A, 0x40, /* code=65, hex=0x41, ascii="A" */
|
||||
0x00, 0x39, 0x2E, 0x4B, 0x80, /* code=66, hex=0x42, ascii="B" */
|
||||
0x00, 0x1D, 0x08, 0x41, 0xC0, /* code=67, hex=0x43, ascii="C" */
|
||||
0x00, 0x39, 0x29, 0x4B, 0x80, /* code=68, hex=0x44, ascii="D" */
|
||||
0x00, 0x3D, 0x0E, 0x43, 0xC0, /* code=69, hex=0x45, ascii="E" */
|
||||
0x00, 0x3D, 0x0E, 0x42, 0x00, /* code=70, hex=0x46, ascii="F" */
|
||||
0x00, 0x19, 0x28, 0x49, 0xC0, /* code=71, hex=0x47, ascii="G" */
|
||||
0x00, 0x25, 0x2F, 0x4A, 0x40, /* code=72, hex=0x48, ascii="H" */
|
||||
0x00, 0x1C, 0x42, 0x11, 0xC0, /* code=73, hex=0x49, ascii="I" */
|
||||
0x00, 0x04, 0x29, 0x49, 0x80, /* code=74, hex=0x4A, ascii="J" */
|
||||
0x00, 0x25, 0x4C, 0x52, 0x40, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x00, 0x21, 0x08, 0x43, 0xC0, /* code=76, hex=0x4C, ascii="L" */
|
||||
0x00, 0x25, 0xE9, 0x4A, 0x40, /* code=77, hex=0x4D, ascii="M" */
|
||||
0x00, 0x25, 0xAB, 0x4A, 0x40, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x00, 0x19, 0x29, 0x49, 0x80, /* code=79, hex=0x4F, ascii="O" */
|
||||
0x00, 0x39, 0x2E, 0x42, 0x00, /* code=80, hex=0x50, ascii="P" */
|
||||
0x00, 0x19, 0x29, 0x49, 0x82, /* code=81, hex=0x51, ascii="Q" */
|
||||
0x00, 0x39, 0x2E, 0x4A, 0x40, /* code=82, hex=0x52, ascii="R" */
|
||||
0x00, 0x1D, 0x06, 0x0B, 0x80, /* code=83, hex=0x53, ascii="S" */
|
||||
0x00, 0x3E, 0x42, 0x10, 0x80, /* code=84, hex=0x54, ascii="T" */
|
||||
0x00, 0x25, 0x29, 0x49, 0x80, /* code=85, hex=0x55, ascii="U" */
|
||||
0x00, 0x25, 0x29, 0x31, 0x80, /* code=86, hex=0x56, ascii="V" */
|
||||
0x00, 0x23, 0x5A, 0xA9, 0x40, /* code=87, hex=0x57, ascii="W" */
|
||||
0x00, 0x25, 0x26, 0x2A, 0x40, /* code=88, hex=0x58, ascii="X" */
|
||||
0x00, 0x14, 0xA5, 0x10, 0x80, /* code=89, hex=0x59, ascii="Y" */
|
||||
0x00, 0x3C, 0x44, 0x43, 0xC0, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0x00, 0x18, 0x84, 0x21, 0x80, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x00, 0x10, 0x82, 0x10, 0x40, /* code=92, hex=0x5C, ascii="\" */
|
||||
0x00, 0x18, 0x42, 0x11, 0x80, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x00, 0x08, 0xA0, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x00, 0x10, 0x40, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x00, 0x00, 0xC1, 0x39, 0x40, /* code=97, hex=0x61, ascii="a" */
|
||||
0x04, 0x21, 0xC9, 0x4B, 0x80, /* code=98, hex=0x62, ascii="b" */
|
||||
0x00, 0x00, 0x64, 0x20, 0xC0, /* code=99, hex=0x63, ascii="c" */
|
||||
0x00, 0x04, 0xE9, 0x49, 0xC0, /* code=100, hex=0x64, ascii="d" */
|
||||
0x00, 0x00, 0xCF, 0x41, 0xC0, /* code=101, hex=0x65, ascii="e" */
|
||||
0x00, 0x0C, 0x8E, 0x21, 0x00, /* code=102, hex=0x66, ascii="f" */
|
||||
0x00, 0x00, 0xE9, 0x38, 0x4C, /* code=103, hex=0x67, ascii="g" */
|
||||
0x00, 0x21, 0xC9, 0x4A, 0x40, /* code=104, hex=0x68, ascii="h" */
|
||||
0x01, 0x00, 0xC2, 0x11, 0xC0, /* code=105, hex=0x69, ascii="i" */
|
||||
0x00, 0x80, 0x21, 0x08, 0x4C, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x00, 0x21, 0x4C, 0x52, 0x40, /* code=107, hex=0x6B, ascii="k" */
|
||||
0x03, 0x08, 0x42, 0x11, 0xC0, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x00, 0x01, 0x2F, 0x4A, 0x40, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x00, 0x01, 0xC9, 0x4A, 0x40, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x00, 0x00, 0xC9, 0x49, 0x80, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x00, 0x01, 0xC9, 0x4B, 0x90, /* code=112, hex=0x70, ascii="p" */
|
||||
0x00, 0x00, 0xE9, 0x49, 0xC2, /* code=113, hex=0x71, ascii="q" */
|
||||
0x00, 0x00, 0xA6, 0x21, 0x00, /* code=114, hex=0x72, ascii="r" */
|
||||
0x00, 0x00, 0xEC, 0x1B, 0x80, /* code=115, hex=0x73, ascii="s" */
|
||||
0x00, 0x11, 0xE4, 0x20, 0xC0, /* code=116, hex=0x74, ascii="t" */
|
||||
0x00, 0x01, 0x29, 0x49, 0xC0, /* code=117, hex=0x75, ascii="u" */
|
||||
0x00, 0x01, 0x29, 0x31, 0x80, /* code=118, hex=0x76, ascii="v" */
|
||||
0x00, 0x01, 0x29, 0x7A, 0x40, /* code=119, hex=0x77, ascii="w" */
|
||||
0x00, 0x01, 0x26, 0x32, 0x40, /* code=120, hex=0x78, ascii="x" */
|
||||
0x00, 0x01, 0x29, 0x38, 0x4C, /* code=121, hex=0x79, ascii="y" */
|
||||
0x00, 0x01, 0xE2, 0x23, 0xC0, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x00, 0x88, 0x84, 0x10, 0x40, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x01, 0x08, 0x42, 0x10, 0x80, /* code=124, hex=0x7C, ascii="|" */
|
||||
0x02, 0x08, 0x21, 0x11, 0x00, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x00, 0x00, 0xAA, 0x00, 0x00, /* code=126, hex=0x7E, ascii="~" */
|
||||
// 0x00, 0x00, 0x45, 0x47, 0xE0, // /* code=127, hex=0x7F, ascii="^?" */
|
||||
// 0x00, 0x1D, 0x08, 0x41, 0xC4, // /* code=128, hex=0x80, ascii="!^@" */
|
||||
// 0x02, 0x81, 0x29, 0x49, 0xC0, // /* code=129, hex=0x81, ascii="!^A" */
|
||||
// 0x11, 0x00, 0xCF, 0x41, 0xC0, // /* code=130, hex=0x82, ascii="!^B" */
|
||||
// 0x22, 0x81, 0x82, 0x51, 0x40, // /* code=131, hex=0x83, ascii="!^C" */
|
||||
// 0x02, 0x81, 0x82, 0x32, 0xC0, // /* code=132, hex=0x84, ascii="!^D" */
|
||||
// 0x41, 0x01, 0x82, 0x32, 0xC0, // /* code=133, hex=0x85, ascii="!^E" */
|
||||
// 0x01, 0x01, 0x82, 0x32, 0xC0, // /* code=134, hex=0x86, ascii="!^F" */
|
||||
// 0x00, 0x00, 0x64, 0x20, 0xC4, // /* code=135, hex=0x87, ascii="!^G" */
|
||||
// 0x22, 0x80, 0xCF, 0x41, 0xC0, // /* code=136, hex=0x88, ascii="!^H" */
|
||||
// 0x02, 0x80, 0xCF, 0x41, 0xC0, // /* code=137, hex=0x89, ascii="!^I" */
|
||||
// 0x41, 0x00, 0xCF, 0x41, 0xC0, // /* code=138, hex=0x8A, ascii="!^J" */
|
||||
// 0x02, 0x80, 0xC2, 0x11, 0xC0, // /* code=139, hex=0x8B, ascii="!^K" */
|
||||
// 0x22, 0x80, 0xC2, 0x11, 0xC0, // /* code=140, hex=0x8C, ascii="!^L" */
|
||||
// 0x41, 0x00, 0xC2, 0x11, 0xC0, // /* code=141, hex=0x8D, ascii="!^M" */
|
||||
// 0xA0, 0x19, 0x2F, 0x4A, 0x40, // /* code=142, hex=0x8E, ascii="!^N" */
|
||||
// 0x20, 0x19, 0x2F, 0x4A, 0x40, // /* code=143, hex=0x8F, ascii="!^O" */
|
||||
// 0x11, 0x3D, 0x0E, 0x43, 0xC0, // /* code=144, hex=0x90, ascii="!^P" */
|
||||
// 0x00, 0x01, 0xB7, 0xF2, 0xE0, // /* code=145, hex=0x91, ascii="!^Q" */
|
||||
// 0x00, 0x1D, 0x4F, 0x52, 0xC0, // /* code=146, hex=0x92, ascii="!^R" */
|
||||
// 0x22, 0x80, 0xC9, 0x49, 0x80, // /* code=147, hex=0x93, ascii="!^S" */
|
||||
// 0x02, 0x80, 0xC9, 0x49, 0x80, // /* code=148, hex=0x94, ascii="!^T" */
|
||||
// 0x41, 0x00, 0xC9, 0x49, 0x80, // /* code=149, hex=0x95, ascii="!^U" */
|
||||
// 0x22, 0x81, 0x29, 0x49, 0xC0, // /* code=150, hex=0x96, ascii="!^V" */
|
||||
// 0x41, 0x01, 0x29, 0x49, 0xC0, // /* code=151, hex=0x97, ascii="!^W" */
|
||||
// 0x02, 0x81, 0x29, 0x38, 0x4C, // /* code=152, hex=0x98, ascii="!^X" */
|
||||
// 0x02, 0x80, 0xC9, 0x49, 0x80, // /* code=153, hex=0x99, ascii="!^Y" */
|
||||
// 0x50, 0x25, 0x29, 0x49, 0x80, // /* code=154, hex=0x9A, ascii="!^Z" */
|
||||
// 0x00, 0x08, 0xE8, 0x41, 0xC4, // /* code=155, hex=0x9B, ascii="!^[" */
|
||||
// 0x01, 0x94, 0x8E, 0x23, 0xC0, // /* code=156, hex=0x9C, ascii="!^\" */
|
||||
// 0x06, 0xD4, 0xA2, 0x38, 0x80, // /* code=157, hex=0x9D, ascii="!^]" */
|
||||
// 0x06, 0x29, 0x6F, 0xCA, 0x20, // /* code=158, hex=0x9E, ascii="!^^" */
|
||||
// 0x01, 0x90, 0x8F, 0x21, 0x10, // /* code=159, hex=0x9F, ascii="!^_" */
|
||||
// 0x22, 0x01, 0x82, 0x32, 0xC0, // /* code=160, hex=0xA0, ascii="! " */
|
||||
// 0x11, 0x00, 0xC2, 0x11, 0xC0, // /* code=161, hex=0xA1, ascii="!!" */
|
||||
// 0x11, 0x00, 0xC9, 0x49, 0x80, // /* code=162, hex=0xA2, ascii="!"" */
|
||||
// 0x11, 0x01, 0x29, 0x49, 0xC0, // /* code=163, hex=0xA3, ascii="!#" */
|
||||
// 0x55, 0x01, 0xC9, 0x4A, 0x40, // /* code=164, hex=0xA4, ascii="!$" */
|
||||
// 0x55, 0x25, 0xAD, 0x5A, 0x40, // /* code=165, hex=0xA5, ascii="!%" */
|
||||
// 0x01, 0x14, 0x60, 0x38, 0x00, // /* code=166, hex=0xA6, ascii="!&" */
|
||||
// 0x01, 0x14, 0x40, 0x38, 0x00, // /* code=167, hex=0xA7, ascii="!'" */
|
||||
// 0x01, 0x00, 0x44, 0x49, 0x80, // /* code=168, hex=0xA8, ascii="!(" */
|
||||
// 0x00, 0x00, 0x00, 0x7E, 0x00, // /* code=169, hex=0xA9, ascii="!)" */
|
||||
// 0x00, 0x00, 0x00, 0x7C, 0x20, // /* code=170, hex=0xAA, ascii="!*" */
|
||||
// 0x04, 0x25, 0x45, 0xC4, 0xE0, // /* code=171, hex=0xAB, ascii="!+" */
|
||||
// 0x04, 0x65, 0x44, 0xCC, 0xE1, // /* code=172, hex=0xAC, ascii="!," */
|
||||
// 0x01, 0x00, 0x42, 0x38, 0x80, // /* code=173, hex=0xAD, ascii="!-" */
|
||||
// 0x00, 0x00, 0x05, 0x51, 0x40, // /* code=174, hex=0xAE, ascii="!." */
|
||||
// 0x00, 0x00, 0x0A, 0x2A, 0x80, // /* code=175, hex=0xAF, ascii="!/" */
|
||||
// 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // /* code=176, hex=0xB0, ascii="!0" */
|
||||
// 0xEA, 0xAE, 0xAE, 0xAA, 0xEA, // /* code=177, hex=0xB1, ascii="!1" */
|
||||
// 0xDB, 0xB6, 0xED, 0xBB, 0x6E, // /* code=178, hex=0xB2, ascii="!2" */
|
||||
// 0x21, 0x08, 0x42, 0x10, 0x84, // /* code=179, hex=0xB3, ascii="!3" */
|
||||
// 0x21, 0x09, 0xC2, 0x10, 0x84, // /* code=180, hex=0xB4, ascii="!4" */
|
||||
// 0x21, 0x38, 0x4E, 0x10, 0x84, // /* code=181, hex=0xB5, ascii="!5" */
|
||||
// 0x52, 0x95, 0xA5, 0x29, 0x4A, // /* code=182, hex=0xB6, ascii="!6" */
|
||||
// 0x00, 0x01, 0xE5, 0x29, 0x4A, // /* code=183, hex=0xB7, ascii="!7" */
|
||||
// 0x00, 0x38, 0x4E, 0x10, 0x84, // /* code=184, hex=0xB8, ascii="!8" */
|
||||
// 0x52, 0xB4, 0x2D, 0x29, 0x4A, // /* code=185, hex=0xB9, ascii="!9" */
|
||||
// 0x52, 0x94, 0xA5, 0x29, 0x4A, // /* code=186, hex=0xBA, ascii="!:" */
|
||||
// 0x00, 0x3C, 0x2D, 0x29, 0x4A, // /* code=187, hex=0xBB, ascii="!;" */
|
||||
// 0x52, 0xB4, 0x2F, 0x00, 0x00, // /* code=188, hex=0xBC, ascii="!<" */
|
||||
// 0x52, 0x95, 0xE0, 0x00, 0x00, // /* code=189, hex=0xBD, ascii="!=" */
|
||||
// 0x21, 0x38, 0x4E, 0x00, 0x00, // /* code=190, hex=0xBE, ascii="!>" */
|
||||
// 0x00, 0x01, 0xC2, 0x10, 0x84, // /* code=191, hex=0xBF, ascii="!?" */
|
||||
// 0x21, 0x08, 0x70, 0x00, 0x00, // /* code=192, hex=0xC0, ascii="!@" */
|
||||
// 0x21, 0x09, 0xF0, 0x00, 0x00, // /* code=193, hex=0xC1, ascii="!A" */
|
||||
// 0x00, 0x01, 0xF2, 0x10, 0x84, // /* code=194, hex=0xC2, ascii="!B" */
|
||||
// 0x21, 0x08, 0x72, 0x10, 0x84, // /* code=195, hex=0xC3, ascii="!C" */
|
||||
// 0x00, 0x01, 0xF0, 0x00, 0x00, // /* code=196, hex=0xC4, ascii="!D" */
|
||||
// 0x21, 0x09, 0xF2, 0x10, 0x84, // /* code=197, hex=0xC5, ascii="!E" */
|
||||
// 0x21, 0x0E, 0x43, 0x90, 0x84, // /* code=198, hex=0xC6, ascii="!F" */
|
||||
// 0x52, 0x94, 0xB5, 0x29, 0x4A, // /* code=199, hex=0xC7, ascii="!G" */
|
||||
// 0x52, 0x96, 0x87, 0x80, 0x00, // /* code=200, hex=0xC8, ascii="!H" */
|
||||
// 0x00, 0x1E, 0x85, 0xA9, 0x4A, // /* code=201, hex=0xC9, ascii="!I" */
|
||||
// 0x52, 0xB6, 0x0F, 0x80, 0x00, // /* code=202, hex=0xCA, ascii="!J" */
|
||||
// 0x00, 0x3E, 0x0D, 0xA9, 0x4A, // /* code=203, hex=0xCB, ascii="!K" */
|
||||
// 0x52, 0x96, 0x85, 0xA9, 0x4A, // /* code=204, hex=0xCC, ascii="!L" */
|
||||
// 0x00, 0x3E, 0x0F, 0x80, 0x00, // /* code=205, hex=0xCD, ascii="!M" */
|
||||
// 0x52, 0xB6, 0x0D, 0xA9, 0x4A, // /* code=206, hex=0xCE, ascii="!N" */
|
||||
// 0x21, 0x3E, 0x0F, 0x80, 0x00, // /* code=207, hex=0xCF, ascii="!O" */
|
||||
// 0x52, 0x95, 0xF0, 0x00, 0x00, // /* code=208, hex=0xD0, ascii="!P" */
|
||||
// 0x00, 0x3E, 0x0F, 0x90, 0x84, // /* code=209, hex=0xD1, ascii="!Q" */
|
||||
// 0x00, 0x01, 0xF5, 0x29, 0x4A, // /* code=210, hex=0xD2, ascii="!R" */
|
||||
// 0x52, 0x94, 0xF0, 0x00, 0x00, // /* code=211, hex=0xD3, ascii="!S" */
|
||||
// 0x21, 0x0E, 0x43, 0x80, 0x00, // /* code=212, hex=0xD4, ascii="!T" */
|
||||
// 0x00, 0x0E, 0x43, 0x90, 0x84, // /* code=213, hex=0xD5, ascii="!U" */
|
||||
// 0x00, 0x00, 0xF5, 0x29, 0x4A, // /* code=214, hex=0xD6, ascii="!V" */
|
||||
// 0x52, 0x95, 0xF5, 0x29, 0x4A, // /* code=215, hex=0xD7, ascii="!W" */
|
||||
// 0x21, 0x3E, 0x4F, 0x90, 0x84, // /* code=216, hex=0xD8, ascii="!X" */
|
||||
// 0x21, 0x09, 0xC0, 0x00, 0x00, // /* code=217, hex=0xD9, ascii="!Y" */
|
||||
// 0x00, 0x00, 0x72, 0x10, 0x84, // /* code=218, hex=0xDA, ascii="!Z" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // /* code=219, hex=0xDB, ascii="![" */
|
||||
// 0x00, 0x00, 0x0F, 0xFF, 0xFF, // /* code=220, hex=0xDC, ascii="!\" */
|
||||
// 0xE7, 0x39, 0xCE, 0x73, 0x9C, // /* code=221, hex=0xDD, ascii="!]" */
|
||||
// 0x18, 0xC6, 0x31, 0x8C, 0x63, // /* code=222, hex=0xDE, ascii="!^" */
|
||||
// 0xFF, 0xFF, 0xF0, 0x00, 0x00, // /* code=223, hex=0xDF, ascii="!_" */
|
||||
// 0x00, 0x00, 0xD9, 0x49, 0xA0, // /* code=224, hex=0xE0, ascii="!`" */
|
||||
// 0x03, 0x25, 0xE9, 0x4B, 0x90, // /* code=225, hex=0xE1, ascii="!a" */
|
||||
// 0x03, 0x90, 0x84, 0x21, 0x00, // /* code=226, hex=0xE2, ascii="!b" */
|
||||
// 0x00, 0x1C, 0xA5, 0x29, 0x40, // /* code=227, hex=0xE3, ascii="!c" */
|
||||
// 0x07, 0xD2, 0x44, 0x47, 0xE0, // /* code=228, hex=0xE4, ascii="!d" */
|
||||
// 0x00, 0x1F, 0x29, 0x49, 0x80, // /* code=229, hex=0xE5, ascii="!e" */
|
||||
// 0x00, 0x01, 0x29, 0x4B, 0xB0, // /* code=230, hex=0xE6, ascii="!f" */
|
||||
// 0x04, 0xD4, 0x42, 0x10, 0x80, // /* code=231, hex=0xE7, ascii="!g" */
|
||||
// 0x01, 0x08, 0xE8, 0xB8, 0x84, // /* code=232, hex=0xE8, ascii="!h" */
|
||||
// 0x00, 0x1D, 0x1F, 0xC5, 0xC0, // /* code=233, hex=0xE9, ascii="!i" */
|
||||
// 0x00, 0x1D, 0x18, 0xAB, 0x60, // /* code=234, hex=0xEA, ascii="!j" */
|
||||
// 0x64, 0x10, 0xC9, 0x49, 0x80, // /* code=235, hex=0xEB, ascii="!k" */
|
||||
// 0x00, 0x00, 0xEA, 0xD5, 0xC0, // /* code=236, hex=0xEC, ascii="!l" */
|
||||
// 0x00, 0x02, 0xEA, 0xA6, 0xC0, // /* code=237, hex=0xED, ascii="!m" */
|
||||
// 0x01, 0x90, 0xE4, 0x20, 0xC0, // /* code=238, hex=0xEE, ascii="!n" */
|
||||
// 0x03, 0x25, 0x29, 0x4A, 0x40, // /* code=239, hex=0xEF, ascii="!o" */
|
||||
// 0x00, 0x3C, 0x0F, 0x03, 0xC0, // /* code=240, hex=0xF0, ascii="!p" */
|
||||
// 0x00, 0x09, 0xF2, 0x03, 0xE0, // /* code=241, hex=0xF1, ascii="!q" */
|
||||
// 0x02, 0x08, 0x22, 0x23, 0xC0, // /* code=242, hex=0xF2, ascii="!r" */
|
||||
// 0x00, 0x88, 0x82, 0x09, 0xC0, // /* code=243, hex=0xF3, ascii="!s" */
|
||||
// 0x00, 0x06, 0x52, 0x10, 0x84, // /* code=244, hex=0xF4, ascii="!t" */
|
||||
// 0x21, 0x08, 0x4A, 0x60, 0x00, // /* code=245, hex=0xF5, ascii="!u" */
|
||||
// 0x00, 0x18, 0x0F, 0x01, 0x80, // /* code=246, hex=0xF6, ascii="!v" */
|
||||
// 0x00, 0x15, 0x40, 0x2A, 0x80, // /* code=247, hex=0xF7, ascii="!w" */
|
||||
// 0x00, 0x08, 0xA2, 0x00, 0x00, // /* code=248, hex=0xF8, ascii="!x" */
|
||||
// 0x00, 0x00, 0xC6, 0x00, 0x00, // /* code=249, hex=0xF9, ascii="!y" */
|
||||
// 0x00, 0x00, 0x40, 0x00, 0x00, // /* code=250, hex=0xFA, ascii="!z" */
|
||||
// 0x00, 0x06, 0x22, 0x51, 0x00, // /* code=251, hex=0xFB, ascii="!{" */
|
||||
// 0x03, 0x14, 0xA0, 0x00, 0x00, // /* code=252, hex=0xFC, ascii="!|" */
|
||||
// 0x03, 0x04, 0x47, 0x00, 0x00, // /* code=253, hex=0xFD, ascii="!}" */
|
||||
// 0x00, 0x00, 0xE7, 0x38, 0x00, // /* code=254, hex=0xFE, ascii="!~" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=255, hex=0xFF, ascii="!^" */
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,300 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// font courtesy of https://github.com/idispatch/raster-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 8
|
||||
* [2] Fixed/max glyph width: 6
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
static const unsigned char console_font_6x8[] PROGMEM = {
|
||||
0x57, 0x08, 0x06, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
|
||||
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=0, hex=0x00, ascii="^@" */
|
||||
// 0x39, 0x16, 0xD1, 0x55, 0x13, 0x80, // /* code=1, hex=0x01, ascii="^A" */
|
||||
// 0x39, 0xF5, 0x5F, 0x45, 0xF3, 0x80, // /* code=2, hex=0x02, ascii="^B" */
|
||||
// 0x00, 0xA7, 0xDF, 0x7C, 0xE1, 0x00, // /* code=3, hex=0x03, ascii="^C" */
|
||||
// 0x00, 0x43, 0x9F, 0x7C, 0xE1, 0x00, // /* code=4, hex=0x04, ascii="^D" */
|
||||
// 0x10, 0xE3, 0x84, 0x7D, 0xF1, 0x00, // /* code=5, hex=0x05, ascii="^E" */
|
||||
// 0x00, 0x43, 0x9F, 0x7C, 0x43, 0x80, // /* code=6, hex=0x06, ascii="^F" */
|
||||
// 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, // /* code=7, hex=0x07, ascii="^G" */
|
||||
// 0xFF, 0xFF, 0xF3, 0xCF, 0xFF, 0xFF, // /* code=8, hex=0x08, ascii="^H" */
|
||||
// 0x00, 0x07, 0x92, 0x49, 0xE0, 0x00, // /* code=9, hex=0x09, ascii="^I" */
|
||||
// 0xFF, 0xF8, 0x6D, 0xB6, 0x1F, 0xFF, // /* code=10, hex=0x0A, ascii="^J" */
|
||||
// 0x00, 0x70, 0xCD, 0x49, 0x23, 0x00, // /* code=11, hex=0x0B, ascii="^K" */
|
||||
// 0x39, 0x14, 0x4E, 0x10, 0xE1, 0x00, // /* code=12, hex=0x0C, ascii="^L" */
|
||||
// 0x10, 0x61, 0x44, 0x31, 0xC6, 0x00, // /* code=13, hex=0x0D, ascii="^M" */
|
||||
// 0x0C, 0xD2, 0xCD, 0x2D, 0xB6, 0x00, // /* code=14, hex=0x0E, ascii="^N" */
|
||||
// 0x01, 0x53, 0x9B, 0x39, 0x50, 0x00, // /* code=15, hex=0x0F, ascii="^O" */
|
||||
// 0x20, 0xC3, 0x8F, 0x38, 0xC2, 0x00, // /* code=16, hex=0x10, ascii="^P" */
|
||||
// 0x08, 0x63, 0x9E, 0x38, 0x60, 0x80, // /* code=17, hex=0x11, ascii="^Q" */
|
||||
// 0x10, 0xE7, 0xC4, 0x7C, 0xE1, 0x00, // /* code=18, hex=0x12, ascii="^R" */
|
||||
// 0x28, 0xA2, 0x8A, 0x28, 0x02, 0x80, // /* code=19, hex=0x13, ascii="^S" */
|
||||
// 0x3D, 0x55, 0x4D, 0x14, 0x51, 0x40, // /* code=20, hex=0x14, ascii="^T" */
|
||||
// 0x39, 0x13, 0x0A, 0x19, 0x13, 0x80, // /* code=21, hex=0x15, ascii="^U" */
|
||||
// 0x00, 0x00, 0x00, 0x01, 0xE7, 0x80, // /* code=22, hex=0x16, ascii="^V" */
|
||||
// 0x10, 0xE7, 0xC4, 0x7C, 0xE1, 0x0E, // /* code=23, hex=0x17, ascii="^W" */
|
||||
// 0x10, 0xE7, 0xC4, 0x10, 0x41, 0x00, // /* code=24, hex=0x18, ascii="^X" */
|
||||
// 0x10, 0x41, 0x04, 0x7C, 0xE1, 0x00, // /* code=25, hex=0x19, ascii="^Y" */
|
||||
// 0x00, 0x41, 0x9F, 0x18, 0x40, 0x00, // /* code=26, hex=0x1A, ascii="^Z" */
|
||||
// 0x00, 0x43, 0x1F, 0x30, 0x40, 0x00, // /* code=27, hex=0x1B, ascii="^[" */
|
||||
// 0x00, 0x00, 0x10, 0x41, 0x07, 0xC0, // /* code=28, hex=0x1C, ascii="^\" */
|
||||
// 0x00, 0xA2, 0x9F, 0x28, 0xA0, 0x00, // /* code=29, hex=0x1D, ascii="^]" */
|
||||
// 0x10, 0x43, 0x8E, 0x7D, 0xF0, 0x00, // /* code=30, hex=0x1E, ascii="^^" */
|
||||
// 0x7D, 0xF3, 0x8E, 0x10, 0x40, 0x00, // /* code=31, hex=0x1F, ascii="^_" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x10, 0xE3, 0x84, 0x10, 0x01, 0x00, /* code=33, hex=0x21, ascii="!" */
|
||||
0x6D, 0xB4, 0x80, 0x00, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0x00, 0xA7, 0xCA, 0x29, 0xF2, 0x80, /* code=35, hex=0x23, ascii="#" */
|
||||
0x20, 0xE4, 0x0C, 0x09, 0xC1, 0x00, /* code=36, hex=0x24, ascii="$" */
|
||||
0x65, 0x90, 0x84, 0x21, 0x34, 0xC0, /* code=37, hex=0x25, ascii="%" */
|
||||
0x21, 0x45, 0x08, 0x55, 0x23, 0x40, /* code=38, hex=0x26, ascii="&" */
|
||||
0x30, 0xC2, 0x00, 0x00, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x10, 0x82, 0x08, 0x20, 0x81, 0x00, /* code=40, hex=0x28, ascii="(" */
|
||||
0x20, 0x41, 0x04, 0x10, 0x42, 0x00, /* code=41, hex=0x29, ascii=")" */
|
||||
0x00, 0xA3, 0x9F, 0x38, 0xA0, 0x00, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x00, 0x41, 0x1F, 0x10, 0x40, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x00, 0x00, 0x00, 0xC3, 0x08, /* code=44, hex=0x2C, ascii="," */
|
||||
0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, /* code=46, hex=0x2E, ascii="." */
|
||||
0x00, 0x10, 0x84, 0x21, 0x00, 0x00, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x39, 0x14, 0xD5, 0x65, 0x13, 0x80, /* code=48, hex=0x30, ascii="0" */
|
||||
0x10, 0xC1, 0x04, 0x10, 0x43, 0x80, /* code=49, hex=0x31, ascii="1" */
|
||||
0x39, 0x10, 0x46, 0x21, 0x07, 0xC0, /* code=50, hex=0x32, ascii="2" */
|
||||
0x39, 0x10, 0x4E, 0x05, 0x13, 0x80, /* code=51, hex=0x33, ascii="3" */
|
||||
0x08, 0x62, 0x92, 0x7C, 0x20, 0x80, /* code=52, hex=0x34, ascii="4" */
|
||||
0x7D, 0x04, 0x1E, 0x05, 0x13, 0x80, /* code=53, hex=0x35, ascii="5" */
|
||||
0x18, 0x84, 0x1E, 0x45, 0x13, 0x80, /* code=54, hex=0x36, ascii="6" */
|
||||
0x7C, 0x10, 0x84, 0x20, 0x82, 0x00, /* code=55, hex=0x37, ascii="7" */
|
||||
0x39, 0x14, 0x4E, 0x45, 0x13, 0x80, /* code=56, hex=0x38, ascii="8" */
|
||||
0x39, 0x14, 0x4F, 0x04, 0x23, 0x00, /* code=57, hex=0x39, ascii="9" */
|
||||
0x00, 0x03, 0x0C, 0x00, 0xC3, 0x00, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x00, 0x03, 0x0C, 0x00, 0xC3, 0x08, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x08, 0x42, 0x10, 0x20, 0x40, 0x80, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x20, 0x40, 0x81, 0x08, 0x42, 0x00, /* code=62, hex=0x3E, ascii=">" */
|
||||
0x39, 0x10, 0x46, 0x10, 0x01, 0x00, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x39, 0x15, 0xD5, 0x5D, 0x03, 0x80, /* code=64, hex=0x40, ascii="@" */
|
||||
0x39, 0x14, 0x51, 0x7D, 0x14, 0x40, /* code=65, hex=0x41, ascii="A" */
|
||||
0x79, 0x14, 0x5E, 0x45, 0x17, 0x80, /* code=66, hex=0x42, ascii="B" */
|
||||
0x39, 0x14, 0x10, 0x41, 0x13, 0x80, /* code=67, hex=0x43, ascii="C" */
|
||||
0x79, 0x14, 0x51, 0x45, 0x17, 0x80, /* code=68, hex=0x44, ascii="D" */
|
||||
0x7D, 0x04, 0x1E, 0x41, 0x07, 0xC0, /* code=69, hex=0x45, ascii="E" */
|
||||
0x7D, 0x04, 0x1E, 0x41, 0x04, 0x00, /* code=70, hex=0x46, ascii="F" */
|
||||
0x39, 0x14, 0x17, 0x45, 0x13, 0xC0, /* code=71, hex=0x47, ascii="G" */
|
||||
0x45, 0x14, 0x5F, 0x45, 0x14, 0x40, /* code=72, hex=0x48, ascii="H" */
|
||||
0x38, 0x41, 0x04, 0x10, 0x43, 0x80, /* code=73, hex=0x49, ascii="I" */
|
||||
0x04, 0x10, 0x41, 0x45, 0x13, 0x80, /* code=74, hex=0x4A, ascii="J" */
|
||||
0x45, 0x25, 0x18, 0x51, 0x24, 0x40, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x41, 0x04, 0x10, 0x41, 0x07, 0xC0, /* code=76, hex=0x4C, ascii="L" */
|
||||
0x45, 0xB5, 0x51, 0x45, 0x14, 0x40, /* code=77, hex=0x4D, ascii="M" */
|
||||
0x45, 0x95, 0x53, 0x45, 0x14, 0x40, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x39, 0x14, 0x51, 0x45, 0x13, 0x80, /* code=79, hex=0x4F, ascii="O" */
|
||||
0x79, 0x14, 0x5E, 0x41, 0x04, 0x00, /* code=80, hex=0x50, ascii="P" */
|
||||
0x39, 0x14, 0x51, 0x55, 0x23, 0x40, /* code=81, hex=0x51, ascii="Q" */
|
||||
0x79, 0x14, 0x5E, 0x49, 0x14, 0x40, /* code=82, hex=0x52, ascii="R" */
|
||||
0x39, 0x14, 0x0E, 0x05, 0x13, 0x80, /* code=83, hex=0x53, ascii="S" */
|
||||
0x7C, 0x41, 0x04, 0x10, 0x41, 0x00, /* code=84, hex=0x54, ascii="T" */
|
||||
0x45, 0x14, 0x51, 0x45, 0x13, 0x80, /* code=85, hex=0x55, ascii="U" */
|
||||
0x45, 0x14, 0x51, 0x44, 0xA1, 0x00, /* code=86, hex=0x56, ascii="V" */
|
||||
0x45, 0x15, 0x55, 0x55, 0x52, 0x80, /* code=87, hex=0x57, ascii="W" */
|
||||
0x45, 0x12, 0x84, 0x29, 0x14, 0x40, /* code=88, hex=0x58, ascii="X" */
|
||||
0x45, 0x14, 0x4A, 0x10, 0x41, 0x00, /* code=89, hex=0x59, ascii="Y" */
|
||||
0x78, 0x21, 0x08, 0x41, 0x07, 0x80, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0x38, 0x82, 0x08, 0x20, 0x83, 0x80, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x00, /* code=92, hex=0x5C, ascii="\" */
|
||||
0x38, 0x20, 0x82, 0x08, 0x23, 0x80, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x10, 0xA4, 0x40, 0x00, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x30, 0xC1, 0x00, 0x00, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x00, 0x03, 0x81, 0x3D, 0x13, 0xC0, /* code=97, hex=0x61, ascii="a" */
|
||||
0x41, 0x07, 0x91, 0x45, 0x17, 0x80, /* code=98, hex=0x62, ascii="b" */
|
||||
0x00, 0x03, 0x91, 0x41, 0x13, 0x80, /* code=99, hex=0x63, ascii="c" */
|
||||
0x04, 0x13, 0xD1, 0x45, 0x13, 0xC0, /* code=100, hex=0x64, ascii="d" */
|
||||
0x00, 0x03, 0x91, 0x79, 0x03, 0x80, /* code=101, hex=0x65, ascii="e" */
|
||||
0x18, 0x82, 0x1E, 0x20, 0x82, 0x00, /* code=102, hex=0x66, ascii="f" */
|
||||
0x00, 0x03, 0xD1, 0x44, 0xF0, 0x4E, /* code=103, hex=0x67, ascii="g" */
|
||||
0x41, 0x07, 0x12, 0x49, 0x24, 0x80, /* code=104, hex=0x68, ascii="h" */
|
||||
0x10, 0x01, 0x04, 0x10, 0x41, 0x80, /* code=105, hex=0x69, ascii="i" */
|
||||
0x08, 0x01, 0x82, 0x08, 0x24, 0x8C, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x41, 0x04, 0x94, 0x61, 0x44, 0x80, /* code=107, hex=0x6B, ascii="k" */
|
||||
0x10, 0x41, 0x04, 0x10, 0x41, 0x80, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x00, 0x06, 0x95, 0x55, 0x14, 0x40, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x00, 0x07, 0x12, 0x49, 0x24, 0x80, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x00, 0x03, 0x91, 0x45, 0x13, 0x80, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x00, 0x07, 0x91, 0x45, 0x17, 0x90, /* code=112, hex=0x70, ascii="p" */
|
||||
0x00, 0x03, 0xD1, 0x45, 0x13, 0xC1, /* code=113, hex=0x71, ascii="q" */
|
||||
0x00, 0x05, 0x89, 0x20, 0x87, 0x00, /* code=114, hex=0x72, ascii="r" */
|
||||
0x00, 0x03, 0x90, 0x38, 0x13, 0x80, /* code=115, hex=0x73, ascii="s" */
|
||||
0x00, 0x87, 0x88, 0x20, 0xA1, 0x00, /* code=116, hex=0x74, ascii="t" */
|
||||
0x00, 0x04, 0x92, 0x49, 0x62, 0x80, /* code=117, hex=0x75, ascii="u" */
|
||||
0x00, 0x04, 0x51, 0x44, 0xA1, 0x00, /* code=118, hex=0x76, ascii="v" */
|
||||
0x00, 0x04, 0x51, 0x55, 0xF2, 0x80, /* code=119, hex=0x77, ascii="w" */
|
||||
0x00, 0x04, 0x92, 0x31, 0x24, 0x80, /* code=120, hex=0x78, ascii="x" */
|
||||
0x00, 0x04, 0x92, 0x48, 0xE1, 0x18, /* code=121, hex=0x79, ascii="y" */
|
||||
0x00, 0x07, 0x82, 0x31, 0x07, 0x80, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x18, 0x82, 0x18, 0x20, 0x81, 0x80, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x10, 0x41, 0x00, 0x10, 0x41, 0x00, /* code=124, hex=0x7C, ascii="|" */
|
||||
0x30, 0x20, 0x83, 0x08, 0x23, 0x00, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x29, 0x40, 0x00, 0x00, 0x00, 0x00, /* code=126, hex=0x7E, ascii="~" */
|
||||
// 0x10, 0xE6, 0xD1, 0x45, 0xF0, 0x00, // /* code=127, hex=0x7F, ascii="^?" */
|
||||
// 0x39, 0x14, 0x10, 0x44, 0xE1, 0x0C, // /* code=128, hex=0x80, ascii="!^@" */
|
||||
// 0x48, 0x04, 0x92, 0x49, 0x62, 0x80, // /* code=129, hex=0x81, ascii="!^A" */
|
||||
// 0x0C, 0x03, 0x91, 0x79, 0x03, 0x80, // /* code=130, hex=0x82, ascii="!^B" */
|
||||
// 0x38, 0x03, 0x81, 0x3D, 0x13, 0xC0, // /* code=131, hex=0x83, ascii="!^C" */
|
||||
// 0x28, 0x03, 0x81, 0x3D, 0x13, 0xC0, // /* code=132, hex=0x84, ascii="!^D" */
|
||||
// 0x30, 0x03, 0x81, 0x3D, 0x13, 0xC0, // /* code=133, hex=0x85, ascii="!^E" */
|
||||
// 0x38, 0xA3, 0x81, 0x3D, 0x13, 0xC0, // /* code=134, hex=0x86, ascii="!^F" */
|
||||
// 0x00, 0xE4, 0x50, 0x44, 0xE1, 0x0C, // /* code=135, hex=0x87, ascii="!^G" */
|
||||
// 0x38, 0x03, 0x91, 0x79, 0x03, 0x80, // /* code=136, hex=0x88, ascii="!^H" */
|
||||
// 0x28, 0x03, 0x91, 0x79, 0x03, 0x80, // /* code=137, hex=0x89, ascii="!^I" */
|
||||
// 0x30, 0x03, 0x91, 0x79, 0x03, 0x80, // /* code=138, hex=0x8A, ascii="!^J" */
|
||||
// 0x28, 0x01, 0x04, 0x10, 0x41, 0x80, // /* code=139, hex=0x8B, ascii="!^K" */
|
||||
// 0x10, 0xA0, 0x04, 0x10, 0x41, 0x80, // /* code=140, hex=0x8C, ascii="!^L" */
|
||||
// 0x20, 0x01, 0x04, 0x10, 0x41, 0x80, // /* code=141, hex=0x8D, ascii="!^M" */
|
||||
// 0x28, 0x01, 0x0A, 0x45, 0xF4, 0x40, // /* code=142, hex=0x8E, ascii="!^N" */
|
||||
// 0x38, 0xA3, 0x9B, 0x45, 0xF4, 0x40, // /* code=143, hex=0x8F, ascii="!^O" */
|
||||
// 0x0C, 0x07, 0xD0, 0x79, 0x07, 0xC0, // /* code=144, hex=0x90, ascii="!^P" */
|
||||
// 0x00, 0x07, 0x85, 0x7D, 0x43, 0xC0, // /* code=145, hex=0x91, ascii="!^Q" */
|
||||
// 0x3D, 0x45, 0x1F, 0x51, 0x45, 0xC0, // /* code=146, hex=0x92, ascii="!^R" */
|
||||
// 0x38, 0x03, 0x12, 0x49, 0x23, 0x00, // /* code=147, hex=0x93, ascii="!^S" */
|
||||
// 0x28, 0x03, 0x12, 0x49, 0x23, 0x00, // /* code=148, hex=0x94, ascii="!^T" */
|
||||
// 0x60, 0x03, 0x12, 0x49, 0x23, 0x00, // /* code=149, hex=0x95, ascii="!^U" */
|
||||
// 0x38, 0x04, 0x92, 0x49, 0x62, 0x80, // /* code=150, hex=0x96, ascii="!^V" */
|
||||
// 0x60, 0x04, 0x92, 0x49, 0x62, 0x80, // /* code=151, hex=0x97, ascii="!^W" */
|
||||
// 0x28, 0x04, 0x92, 0x48, 0xE1, 0x18, // /* code=152, hex=0x98, ascii="!^X" */
|
||||
// 0x48, 0xC4, 0x92, 0x49, 0x23, 0x00, // /* code=153, hex=0x99, ascii="!^Y" */
|
||||
// 0x28, 0x04, 0x92, 0x49, 0x23, 0x00, // /* code=154, hex=0x9A, ascii="!^Z" */
|
||||
// 0x00, 0x43, 0x90, 0x40, 0xE1, 0x00, // /* code=155, hex=0x9B, ascii="!^[" */
|
||||
// 0x18, 0x92, 0x1E, 0x20, 0x95, 0xC0, // /* code=156, hex=0x9C, ascii="!^\" */
|
||||
// 0x44, 0xA1, 0x1F, 0x11, 0xF1, 0x00, // /* code=157, hex=0x9D, ascii="!^]" */
|
||||
// 0x61, 0x45, 0x1A, 0x5D, 0x24, 0x80, // /* code=158, hex=0x9E, ascii="!^^" */
|
||||
// 0x08, 0x51, 0x0E, 0x10, 0x45, 0x08, // /* code=159, hex=0x9F, ascii="!^_" */
|
||||
// 0x18, 0x03, 0x81, 0x3D, 0x13, 0xC0, // /* code=160, hex=0xA0, ascii="! " */
|
||||
// 0x18, 0x01, 0x04, 0x10, 0x41, 0x80, // /* code=161, hex=0xA1, ascii="!!" */
|
||||
// 0x18, 0x03, 0x12, 0x49, 0x23, 0x00, // /* code=162, hex=0xA2, ascii="!"" */
|
||||
// 0x18, 0x04, 0x92, 0x49, 0x62, 0x80, // /* code=163, hex=0xA3, ascii="!#" */
|
||||
// 0x29, 0x40, 0x1C, 0x49, 0x24, 0x80, // /* code=164, hex=0xA4, ascii="!$" */
|
||||
// 0x29, 0x40, 0x12, 0x69, 0x64, 0x80, // /* code=165, hex=0xA5, ascii="!%" */
|
||||
// 0x38, 0x13, 0xD1, 0x3C, 0x03, 0xC0, // /* code=166, hex=0xA6, ascii="!&" */
|
||||
// 0x31, 0x24, 0x92, 0x30, 0x07, 0x80, // /* code=167, hex=0xA7, ascii="!'" */
|
||||
// 0x10, 0x01, 0x0C, 0x41, 0x13, 0x80, // /* code=168, hex=0xA8, ascii="!(" */
|
||||
// 0x00, 0x07, 0xD0, 0x41, 0x00, 0x00, // /* code=169, hex=0xA9, ascii="!)" */
|
||||
// 0x00, 0x0F, 0xC1, 0x04, 0x00, 0x00, // /* code=170, hex=0xAA, ascii="!*" */
|
||||
// 0x41, 0x25, 0x0E, 0x44, 0x21, 0xC0, // /* code=171, hex=0xAB, ascii="!+" */
|
||||
// 0x41, 0x25, 0x0B, 0x54, 0x70, 0x40, // /* code=172, hex=0xAC, ascii="!," */
|
||||
// 0x10, 0x01, 0x04, 0x38, 0xE1, 0x00, // /* code=173, hex=0xAD, ascii="!-" */
|
||||
// 0x00, 0x02, 0x52, 0x24, 0x00, 0x00, // /* code=174, hex=0xAE, ascii="!." */
|
||||
// 0x00, 0x04, 0x89, 0x48, 0x00, 0x00, // /* code=175, hex=0xAF, ascii="!/" */
|
||||
// 0x54, 0x0A, 0x80, 0x54, 0x0A, 0x80, // /* code=176, hex=0xB0, ascii="!0" */
|
||||
// 0x56, 0xA5, 0x6A, 0x56, 0xA5, 0x6A, // /* code=177, hex=0xB1, ascii="!1" */
|
||||
// 0xAB, 0xF5, 0x7F, 0xAB, 0xF5, 0x7F, // /* code=178, hex=0xB2, ascii="!2" */
|
||||
// 0x10, 0x41, 0x04, 0x10, 0x41, 0x04, // /* code=179, hex=0xB3, ascii="!3" */
|
||||
// 0x10, 0x41, 0x3C, 0x10, 0x41, 0x04, // /* code=180, hex=0xB4, ascii="!4" */
|
||||
// 0x13, 0xC1, 0x3C, 0x10, 0x41, 0x04, // /* code=181, hex=0xB5, ascii="!5" */
|
||||
// 0x51, 0x45, 0x34, 0x51, 0x45, 0x14, // /* code=182, hex=0xB6, ascii="!6" */
|
||||
// 0x00, 0x00, 0x3C, 0x51, 0x45, 0x14, // /* code=183, hex=0xB7, ascii="!7" */
|
||||
// 0x03, 0xC1, 0x3C, 0x10, 0x41, 0x04, // /* code=184, hex=0xB8, ascii="!8" */
|
||||
// 0x53, 0x41, 0x34, 0x51, 0x45, 0x14, // /* code=185, hex=0xB9, ascii="!9" */
|
||||
// 0x51, 0x45, 0x14, 0x51, 0x45, 0x14, // /* code=186, hex=0xBA, ascii="!:" */
|
||||
// 0x03, 0xC1, 0x34, 0x51, 0x45, 0x14, // /* code=187, hex=0xBB, ascii="!;" */
|
||||
// 0x53, 0x41, 0x3C, 0x00, 0x00, 0x00, // /* code=188, hex=0xBC, ascii="!<" */
|
||||
// 0x51, 0x45, 0x3C, 0x00, 0x00, 0x00, // /* code=189, hex=0xBD, ascii="!=" */
|
||||
// 0x13, 0xC1, 0x3C, 0x00, 0x00, 0x00, // /* code=190, hex=0xBE, ascii="!>" */
|
||||
// 0x00, 0x00, 0x3C, 0x10, 0x41, 0x04, // /* code=191, hex=0xBF, ascii="!?" */
|
||||
// 0x10, 0x41, 0x07, 0x00, 0x00, 0x00, // /* code=192, hex=0xC0, ascii="!@" */
|
||||
// 0x10, 0x41, 0x3F, 0x00, 0x00, 0x00, // /* code=193, hex=0xC1, ascii="!A" */
|
||||
// 0x00, 0x00, 0x3F, 0x10, 0x41, 0x04, // /* code=194, hex=0xC2, ascii="!B" */
|
||||
// 0x10, 0x41, 0x07, 0x10, 0x41, 0x04, // /* code=195, hex=0xC3, ascii="!C" */
|
||||
// 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, // /* code=196, hex=0xC4, ascii="!D" */
|
||||
// 0x10, 0x41, 0x3F, 0x10, 0x41, 0x04, // /* code=197, hex=0xC5, ascii="!E" */
|
||||
// 0x10, 0x71, 0x07, 0x10, 0x41, 0x04, // /* code=198, hex=0xC6, ascii="!F" */
|
||||
// 0x51, 0x45, 0x17, 0x51, 0x45, 0x14, // /* code=199, hex=0xC7, ascii="!G" */
|
||||
// 0x51, 0x74, 0x1F, 0x00, 0x00, 0x00, // /* code=200, hex=0xC8, ascii="!H" */
|
||||
// 0x01, 0xF4, 0x17, 0x51, 0x45, 0x14, // /* code=201, hex=0xC9, ascii="!I" */
|
||||
// 0x53, 0x70, 0x3F, 0x00, 0x00, 0x00, // /* code=202, hex=0xCA, ascii="!J" */
|
||||
// 0x03, 0xF0, 0x37, 0x51, 0x45, 0x14, // /* code=203, hex=0xCB, ascii="!K" */
|
||||
// 0x51, 0x74, 0x17, 0x51, 0x45, 0x14, // /* code=204, hex=0xCC, ascii="!L" */
|
||||
// 0x03, 0xF0, 0x3F, 0x00, 0x00, 0x00, // /* code=205, hex=0xCD, ascii="!M" */
|
||||
// 0x53, 0x70, 0x37, 0x51, 0x45, 0x14, // /* code=206, hex=0xCE, ascii="!N" */
|
||||
// 0x13, 0xF0, 0x3F, 0x00, 0x00, 0x00, // /* code=207, hex=0xCF, ascii="!O" */
|
||||
// 0x51, 0x45, 0x3F, 0x00, 0x00, 0x00, // /* code=208, hex=0xD0, ascii="!P" */
|
||||
// 0x03, 0xF0, 0x3F, 0x10, 0x41, 0x04, // /* code=209, hex=0xD1, ascii="!Q" */
|
||||
// 0x00, 0x00, 0x3F, 0x51, 0x45, 0x14, // /* code=210, hex=0xD2, ascii="!R" */
|
||||
// 0x51, 0x45, 0x1F, 0x00, 0x00, 0x00, // /* code=211, hex=0xD3, ascii="!S" */
|
||||
// 0x10, 0x71, 0x07, 0x00, 0x00, 0x00, // /* code=212, hex=0xD4, ascii="!T" */
|
||||
// 0x00, 0x71, 0x07, 0x10, 0x41, 0x04, // /* code=213, hex=0xD5, ascii="!U" */
|
||||
// 0x00, 0x00, 0x1F, 0x51, 0x45, 0x14, // /* code=214, hex=0xD6, ascii="!V" */
|
||||
// 0x51, 0x45, 0x37, 0x51, 0x45, 0x14, // /* code=215, hex=0xD7, ascii="!W" */
|
||||
// 0x13, 0xF0, 0x3F, 0x10, 0x41, 0x04, // /* code=216, hex=0xD8, ascii="!X" */
|
||||
// 0x10, 0x41, 0x3C, 0x00, 0x00, 0x00, // /* code=217, hex=0xD9, ascii="!Y" */
|
||||
// 0x00, 0x00, 0x07, 0x10, 0x41, 0x04, // /* code=218, hex=0xDA, ascii="!Z" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // /* code=219, hex=0xDB, ascii="![" */
|
||||
// 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // /* code=220, hex=0xDC, ascii="!\" */
|
||||
// 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, // /* code=221, hex=0xDD, ascii="!]" */
|
||||
// 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, // /* code=222, hex=0xDE, ascii="!^" */
|
||||
// 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, // /* code=223, hex=0xDF, ascii="!_" */
|
||||
// 0x00, 0x03, 0x52, 0x48, 0xD0, 0x00, // /* code=224, hex=0xE0, ascii="!`" */
|
||||
// 0x01, 0xC4, 0x9C, 0x49, 0x27, 0x10, // /* code=225, hex=0xE1, ascii="!a" */
|
||||
// 0x79, 0x24, 0x10, 0x41, 0x04, 0x00, // /* code=226, hex=0xE2, ascii="!b" */
|
||||
// 0x01, 0xF2, 0x8A, 0x28, 0xA2, 0x80, // /* code=227, hex=0xE3, ascii="!c" */
|
||||
// 0x79, 0x22, 0x04, 0x21, 0x27, 0x80, // /* code=228, hex=0xE4, ascii="!d" */
|
||||
// 0x00, 0x03, 0xD2, 0x48, 0xC0, 0x00, // /* code=229, hex=0xE5, ascii="!e" */
|
||||
// 0x00, 0x04, 0x92, 0x49, 0xC4, 0x10, // /* code=230, hex=0xE6, ascii="!f" */
|
||||
// 0x00, 0x02, 0x94, 0x10, 0x41, 0x00, // /* code=231, hex=0xE7, ascii="!g" */
|
||||
// 0x38, 0x43, 0x91, 0x38, 0x43, 0x80, // /* code=232, hex=0xE8, ascii="!h" */
|
||||
// 0x31, 0x24, 0x9E, 0x49, 0x23, 0x00, // /* code=233, hex=0xE9, ascii="!i" */
|
||||
// 0x00, 0xE4, 0x51, 0x28, 0xA6, 0xC0, // /* code=234, hex=0xEA, ascii="!j" */
|
||||
// 0x31, 0x02, 0x04, 0x39, 0x23, 0x00, // /* code=235, hex=0xEB, ascii="!k" */
|
||||
// 0x00, 0x02, 0x95, 0x54, 0xA0, 0x00, // /* code=236, hex=0xEC, ascii="!l" */
|
||||
// 0x00, 0x43, 0x95, 0x54, 0xE1, 0x00, // /* code=237, hex=0xED, ascii="!m" */
|
||||
// 0x00, 0xE4, 0x1E, 0x40, 0xE0, 0x00, // /* code=238, hex=0xEE, ascii="!n" */
|
||||
// 0x00, 0xC4, 0x92, 0x49, 0x20, 0x00, // /* code=239, hex=0xEF, ascii="!o" */
|
||||
// 0x01, 0xE0, 0x1E, 0x01, 0xE0, 0x00, // /* code=240, hex=0xF0, ascii="!p" */
|
||||
// 0x00, 0x43, 0x84, 0x00, 0xE0, 0x00, // /* code=241, hex=0xF1, ascii="!q" */
|
||||
// 0x40, 0xC0, 0x8C, 0x40, 0x07, 0x80, // /* code=242, hex=0xF2, ascii="!r" */
|
||||
// 0x08, 0xC4, 0x0C, 0x08, 0x07, 0x80, // /* code=243, hex=0xF3, ascii="!s" */
|
||||
// 0x00, 0x21, 0x44, 0x10, 0x41, 0x04, // /* code=244, hex=0xF4, ascii="!t" */
|
||||
// 0x10, 0x41, 0x04, 0x11, 0x42, 0x00, // /* code=245, hex=0xF5, ascii="!u" */
|
||||
// 0x00, 0x40, 0x1F, 0x00, 0x40, 0x00, // /* code=246, hex=0xF6, ascii="!v" */
|
||||
// 0x00, 0xA5, 0x00, 0x29, 0x40, 0x00, // /* code=247, hex=0xF7, ascii="!w" */
|
||||
// 0x31, 0x24, 0x8C, 0x00, 0x00, 0x00, // /* code=248, hex=0xF8, ascii="!x" */
|
||||
// 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, // /* code=249, hex=0xF9, ascii="!y" */
|
||||
// 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // /* code=250, hex=0xFA, ascii="!z" */
|
||||
// 0x00, 0x71, 0x04, 0x51, 0x42, 0x00, // /* code=251, hex=0xFB, ascii="!{" */
|
||||
// 0x50, 0xA2, 0x8A, 0x00, 0x00, 0x00, // /* code=252, hex=0xFC, ascii="!|" */
|
||||
// 0x60, 0x42, 0x1C, 0x00, 0x00, 0x00, // /* code=253, hex=0xFD, ascii="!}" */
|
||||
// 0x00, 0x07, 0x9E, 0x79, 0xE0, 0x00, // /* code=254, hex=0xFE, ascii="!~" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=255, hex=0xFF, ascii="!^ź" */
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,300 +0,0 @@
|
||||
// code points 0-31 and 127-255 are commented out to save memory, they contain extra characters (CP437),
|
||||
// which could be used with an UTF-8 to CP437 conversion
|
||||
// font courtesy of https://github.com/idispatch/raster-fonts
|
||||
|
||||
/*
|
||||
* WBF (WLED Bitmap Font) Packed Fixed-Width Format
|
||||
* Header Layout (12 Bytes):
|
||||
* [0] Magic 'W' (0x57)
|
||||
* [1] Glyph height: 9
|
||||
* [2] Fixed/max glyph width: 7
|
||||
* [3] Spacing between chars: 1
|
||||
* [4] Flags: 0x00 (0x01 = variable width)
|
||||
* [5] First Char: 32
|
||||
* [6] Last Char: 126
|
||||
* [7] reserved: 0x00
|
||||
* [8-11] Unicode Offset (32-bit little-endian): 0x00000000
|
||||
* If variable width flag is set, header is followed by a width table of (Last Char - First Char + 1) bytes.
|
||||
* Packing: row-by-row, top first bitstream, MSB-first.
|
||||
*/
|
||||
|
||||
/*
|
||||
* PACKING EXAMPLE: 4x6 Character '!'
|
||||
* -------------------------------------
|
||||
* VISUAL GRID (4x6): BINARY ROWS
|
||||
* [Row 1] . # . . (2) 0010
|
||||
* [Row 2] . # . . (2) 0010
|
||||
* [Row 3] . # . . (2) 0010
|
||||
* [Row 4] . . . . (0) 0000
|
||||
* [Row 5] . # . . (2) 0010
|
||||
* [Row 6] . . . . (0) 0000
|
||||
* -------------------------------------
|
||||
* CONCATENATED STREAM:
|
||||
* Rows: 1 & 2 | 3 & 4 | 5 & 6
|
||||
* Bits: 00100010 00100000 00100000
|
||||
* [Byte 1] [Byte 2] [Byte 3]
|
||||
* Final HEX for '!' = 0x22, 0x20, 0x20
|
||||
*
|
||||
* at the end of each glyph bitmap, padding bits are added if necessary to fill the last byte
|
||||
*/
|
||||
|
||||
static const unsigned char console_font_7x9[] PROGMEM = {
|
||||
0x57, 0x09, 0x07, 0x01, 0x00, 0x20, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, // Header: Magic, H, W, Spacing, Flags, First, Last, Reserved, UnicodeOffset
|
||||
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=0, hex=0x00, ascii="^@" */
|
||||
// 0x38, 0x8A, 0xAD, 0x58, 0x35, 0x65, 0x3C, 0x00, // /* code=1, hex=0x01, ascii="^A" */
|
||||
// 0x38, 0xFB, 0x5E, 0xBF, 0xF7, 0x71, 0xBE, 0x00, // /* code=2, hex=0x02, ascii="^B" */
|
||||
// 0x00, 0xDB, 0xFF, 0xFF, 0xEF, 0x8E, 0x08, 0x00, // /* code=3, hex=0x03, ascii="^C" */
|
||||
// 0x00, 0x20, 0xE3, 0xEF, 0xEF, 0x8E, 0x08, 0x00, // /* code=4, hex=0x04, ascii="^D" */
|
||||
// 0x38, 0x70, 0x46, 0xBF, 0xFA, 0xC4, 0x3E, 0x00, // /* code=5, hex=0x05, ascii="^E" */
|
||||
// 0x10, 0x71, 0xF7, 0xFF, 0xEA, 0x84, 0x1C, 0x00, // /* code=6, hex=0x06, ascii="^F" */
|
||||
// 0x00, 0x00, 0x61, 0xE3, 0xC3, 0x00, 0x00, 0x00, // /* code=7, hex=0x07, ascii="^G" */
|
||||
// 0xFF, 0xFF, 0x9E, 0x1C, 0x3C, 0xFF, 0xFF, 0xFE, // /* code=8, hex=0x08, ascii="^H" */
|
||||
// 0x00, 0x30, 0xF3, 0x36, 0x67, 0x86, 0x00, 0x00, // /* code=9, hex=0x09, ascii="^I" */
|
||||
// 0xFF, 0xCF, 0x0C, 0xC9, 0x98, 0x79, 0xFF, 0xFE, // /* code=10, hex=0x0A, ascii="^J" */
|
||||
// 0x0E, 0x0C, 0x28, 0xC3, 0xCC, 0xD9, 0x9E, 0x00, // /* code=11, hex=0x0B, ascii="^K" */
|
||||
// 0x3C, 0xCD, 0x99, 0xE1, 0x87, 0x86, 0x0C, 0x00, // /* code=12, hex=0x0C, ascii="^L" */
|
||||
// 0x00, 0x70, 0xB1, 0x02, 0x04, 0x18, 0x30, 0x00, // /* code=13, hex=0x0D, ascii="^M" */
|
||||
// 0x00, 0x78, 0x91, 0xE2, 0x44, 0x9B, 0x36, 0x00, // /* code=14, hex=0x0E, ascii="^N" */
|
||||
// 0x92, 0xA8, 0xE1, 0x4E, 0xE7, 0x15, 0x49, 0x00, // /* code=15, hex=0x0F, ascii="^O" */
|
||||
// 0x00, 0x40, 0xC1, 0xC3, 0xC7, 0x0C, 0x10, 0x00, // /* code=16, hex=0x10, ascii="^P" */
|
||||
// 0x00, 0x08, 0x30, 0xE3, 0xC3, 0x83, 0x02, 0x00, // /* code=17, hex=0x11, ascii="^Q" */
|
||||
// 0x10, 0x71, 0xF0, 0x81, 0x0F, 0x8E, 0x08, 0x00, // /* code=18, hex=0x12, ascii="^R" */
|
||||
// 0x6C, 0xD9, 0xB3, 0x66, 0xC0, 0x1B, 0x36, 0x00, // /* code=19, hex=0x13, ascii="^S" */
|
||||
// 0x00, 0x79, 0x52, 0xA3, 0xC2, 0x85, 0x0A, 0x00, // /* code=20, hex=0x14, ascii="^T" */
|
||||
// 0x3C, 0xCD, 0x81, 0xE6, 0x6C, 0xCF, 0x03, 0x66, // /* code=21, hex=0x15, ascii="^U" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x0F, 0x9F, 0x00, 0x00, // /* code=22, hex=0x16, ascii="^V" */
|
||||
// 0x10, 0x71, 0xF0, 0x81, 0x0F, 0x8E, 0x08, 0x7C, // /* code=23, hex=0x17, ascii="^W" */
|
||||
// 0x00, 0x30, 0xF2, 0xD1, 0x83, 0x06, 0x0C, 0x00, // /* code=24, hex=0x18, ascii="^X" */
|
||||
// 0x00, 0x30, 0x60, 0xC1, 0x8B, 0x4F, 0x0C, 0x00, // /* code=25, hex=0x19, ascii="^Y" */
|
||||
// 0x00, 0x00, 0x60, 0x67, 0xE1, 0x86, 0x00, 0x00, // /* code=26, hex=0x1A, ascii="^Z" */
|
||||
// 0x00, 0x00, 0x61, 0x87, 0xE6, 0x06, 0x00, 0x00, // /* code=27, hex=0x1B, ascii="^[" */
|
||||
// 0x00, 0x00, 0x00, 0x06, 0x0C, 0x1F, 0x00, 0x00, // /* code=28, hex=0x1C, ascii="^\" */
|
||||
// 0x00, 0x00, 0x93, 0x3F, 0xEC, 0xC9, 0x00, 0x00, // /* code=29, hex=0x1D, ascii="^]" */
|
||||
// 0x00, 0x00, 0x00, 0x83, 0x8F, 0xBF, 0x80, 0x00, // /* code=30, hex=0x1E, ascii="^^" */
|
||||
// 0x00, 0x00, 0x07, 0xF7, 0xC7, 0x04, 0x00, 0x00, // /* code=31, hex=0x1F, ascii="^_" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* code=32, hex=0x20, ascii=" " */
|
||||
0x00, 0x30, 0x60, 0xC1, 0x80, 0x06, 0x0C, 0x00, /* code=33, hex=0x21, ascii="!" */
|
||||
0x00, 0xD9, 0xB2, 0x20, 0x00, 0x00, 0x00, 0x00, /* code=34, hex=0x22, ascii=""" */
|
||||
0x00, 0xD9, 0xB7, 0xF6, 0xDF, 0xDB, 0x36, 0x00, /* code=35, hex=0x23, ascii="#" */
|
||||
0x08, 0x30, 0xF3, 0x03, 0xC0, 0xDF, 0x0C, 0x10, /* code=36, hex=0x24, ascii="$" */
|
||||
0x70, 0xA5, 0xD8, 0x61, 0x87, 0xDA, 0x87, 0x00, /* code=37, hex=0x25, ascii="%" */
|
||||
0x38, 0xD9, 0xB1, 0xC6, 0xED, 0x9B, 0x1F, 0x00, /* code=38, hex=0x26, ascii="&" */
|
||||
0x00, 0x30, 0x61, 0x83, 0x00, 0x00, 0x00, 0x00, /* code=39, hex=0x27, ascii="'" */
|
||||
0x00, 0x18, 0x61, 0x83, 0x06, 0x06, 0x06, 0x00, /* code=40, hex=0x28, ascii="(" */
|
||||
0x00, 0x60, 0x60, 0x60, 0xC1, 0x86, 0x18, 0x00, /* code=41, hex=0x29, ascii=")" */
|
||||
0x00, 0x89, 0xB1, 0xCF, 0xE7, 0x1B, 0x22, 0x00, /* code=42, hex=0x2A, ascii="*" */
|
||||
0x00, 0x00, 0x60, 0xC7, 0xEF, 0xC6, 0x0C, 0x00, /* code=43, hex=0x2B, ascii="+" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x60, /* code=44, hex=0x2C, ascii="," */
|
||||
0x00, 0x00, 0x00, 0x07, 0xCF, 0x80, 0x00, 0x00, /* code=45, hex=0x2D, ascii="-" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x00, /* code=46, hex=0x2E, ascii="." */
|
||||
0x00, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, 0x00, /* code=47, hex=0x2F, ascii="/" */
|
||||
0x00, 0x79, 0x9B, 0x77, 0x6C, 0xD9, 0x9E, 0x00, /* code=48, hex=0x30, ascii="0" */
|
||||
0x00, 0x31, 0xE0, 0xC1, 0x83, 0x06, 0x3F, 0x00, /* code=49, hex=0x31, ascii="1" */
|
||||
0x00, 0x79, 0x9A, 0x31, 0xC6, 0x19, 0xBF, 0x00, /* code=50, hex=0x32, ascii="2" */
|
||||
0x00, 0x79, 0x98, 0x31, 0xC0, 0xD9, 0x9E, 0x00, /* code=51, hex=0x33, ascii="3" */
|
||||
0x00, 0x18, 0x71, 0xE6, 0xCF, 0xC3, 0x0F, 0x00, /* code=52, hex=0x34, ascii="4" */
|
||||
0x00, 0xFD, 0x9B, 0x07, 0xC0, 0xD9, 0x9E, 0x00, /* code=53, hex=0x35, ascii="5" */
|
||||
0x00, 0x38, 0xC3, 0x07, 0xCC, 0xD9, 0x9E, 0x00, /* code=54, hex=0x36, ascii="6" */
|
||||
0x00, 0xFD, 0x98, 0x30, 0xC3, 0x06, 0x0C, 0x00, /* code=55, hex=0x37, ascii="7" */
|
||||
0x00, 0x79, 0x9B, 0x33, 0xCC, 0xD9, 0x9E, 0x00, /* code=56, hex=0x38, ascii="8" */
|
||||
0x00, 0x79, 0x9B, 0x33, 0xE0, 0xC3, 0x1C, 0x00, /* code=57, hex=0x39, ascii="9" */
|
||||
0x00, 0x00, 0xC1, 0x80, 0x00, 0x0C, 0x18, 0x00, /* code=58, hex=0x3A, ascii=":" */
|
||||
0x00, 0x00, 0xC1, 0x80, 0x00, 0x0C, 0x18, 0x60, /* code=59, hex=0x3B, ascii=";" */
|
||||
0x00, 0x00, 0x61, 0x86, 0x06, 0x06, 0x00, 0x00, /* code=60, hex=0x3C, ascii="<" */
|
||||
0x00, 0x00, 0x03, 0xE0, 0x0F, 0x80, 0x00, 0x00, /* code=61, hex=0x3D, ascii="=" */
|
||||
0x00, 0x00, 0xC0, 0xC0, 0xC3, 0x0C, 0x00, 0x00, /* code=62, hex=0x3E, ascii=">" */
|
||||
0x00, 0x79, 0x98, 0x30, 0xC3, 0x00, 0x0C, 0x00, /* code=63, hex=0x3F, ascii="?" */
|
||||
0x00, 0x79, 0x8B, 0x76, 0xAD, 0x98, 0x9E, 0x00, /* code=64, hex=0x40, ascii="@" */
|
||||
0x00, 0x20, 0xE1, 0x46, 0xCF, 0xB1, 0xE3, 0x00, /* code=65, hex=0x41, ascii="A" */
|
||||
0x00, 0xF9, 0x9B, 0x37, 0xCC, 0xD9, 0xBE, 0x00, /* code=66, hex=0x42, ascii="B" */
|
||||
0x00, 0x79, 0x9B, 0x06, 0x0C, 0x19, 0x9E, 0x00, /* code=67, hex=0x43, ascii="C" */
|
||||
0x00, 0xF9, 0x9B, 0x36, 0x6C, 0xD9, 0xBE, 0x00, /* code=68, hex=0x44, ascii="D" */
|
||||
0x00, 0xFD, 0x9B, 0x07, 0x8C, 0x19, 0xBF, 0x00, /* code=69, hex=0x45, ascii="E" */
|
||||
0x00, 0xFD, 0x9B, 0x07, 0xCC, 0x18, 0x30, 0x00, /* code=70, hex=0x46, ascii="F" */
|
||||
0x00, 0x79, 0x9B, 0x06, 0xEC, 0xD9, 0x9F, 0x00, /* code=71, hex=0x47, ascii="G" */
|
||||
0x00, 0xCD, 0x9B, 0x37, 0xEC, 0xD9, 0xB3, 0x00, /* code=72, hex=0x48, ascii="H" */
|
||||
0x00, 0x78, 0x60, 0xC1, 0x83, 0x06, 0x1E, 0x00, /* code=73, hex=0x49, ascii="I" */
|
||||
0x00, 0x3C, 0x30, 0x60, 0xCD, 0x9B, 0x1C, 0x00, /* code=74, hex=0x4A, ascii="J" */
|
||||
0x00, 0xCD, 0xB3, 0x67, 0x8D, 0x9B, 0x33, 0x00, /* code=75, hex=0x4B, ascii="K" */
|
||||
0x00, 0xC1, 0x83, 0x06, 0x0C, 0x19, 0xBF, 0x00, /* code=76, hex=0x4C, ascii="L" */
|
||||
0x01, 0x8F, 0x1F, 0x7F, 0xFA, 0xF5, 0xEB, 0x00, /* code=77, hex=0x4D, ascii="M" */
|
||||
0x00, 0xCD, 0xDB, 0xB7, 0xED, 0xD9, 0xB3, 0x00, /* code=78, hex=0x4E, ascii="N" */
|
||||
0x00, 0x79, 0x9B, 0x36, 0x6C, 0xD9, 0x9E, 0x00, /* code=79, hex=0x4F, ascii="O" */
|
||||
0x00, 0xF9, 0x9B, 0x36, 0x6F, 0x98, 0x30, 0x00, /* code=80, hex=0x50, ascii="P" */
|
||||
0x00, 0x79, 0x9B, 0x36, 0x6E, 0xDB, 0x9E, 0x06, /* code=81, hex=0x51, ascii="Q" */
|
||||
0x00, 0xF9, 0x9B, 0x36, 0xCF, 0x1B, 0x33, 0x00, /* code=82, hex=0x52, ascii="R" */
|
||||
0x00, 0x79, 0x9B, 0x03, 0xC0, 0xD9, 0x9E, 0x00, /* code=83, hex=0x53, ascii="S" */
|
||||
0x00, 0xFD, 0x68, 0xC1, 0x83, 0x06, 0x1E, 0x00, /* code=84, hex=0x54, ascii="T" */
|
||||
0x00, 0xCD, 0x9B, 0x36, 0x6C, 0xD9, 0x9E, 0x00, /* code=85, hex=0x55, ascii="U" */
|
||||
0x01, 0x8F, 0x1B, 0x66, 0xC7, 0x0E, 0x08, 0x00, /* code=86, hex=0x56, ascii="V" */
|
||||
0x01, 0x8F, 0x5E, 0xBD, 0x6F, 0x9B, 0x22, 0x00, /* code=87, hex=0x57, ascii="W" */
|
||||
0x00, 0xCD, 0x99, 0xE1, 0x87, 0x99, 0xB3, 0x00, /* code=88, hex=0x58, ascii="X" */
|
||||
0x00, 0xCD, 0x9B, 0x33, 0xC3, 0x06, 0x1E, 0x00, /* code=89, hex=0x59, ascii="Y" */
|
||||
0x00, 0xFD, 0x98, 0x61, 0x86, 0x19, 0xBF, 0x00, /* code=90, hex=0x5A, ascii="Z" */
|
||||
0x00, 0x78, 0xC1, 0x83, 0x06, 0x0C, 0x1E, 0x00, /* code=91, hex=0x5B, ascii="[" */
|
||||
0x00, 0xC1, 0x81, 0x83, 0x03, 0x06, 0x06, 0x00, /* code=92, hex=0x5C, ascii="\" */
|
||||
0x00, 0x78, 0x30, 0x60, 0xC1, 0x83, 0x1E, 0x00, /* code=93, hex=0x5D, ascii="]" */
|
||||
0x00, 0x20, 0xE3, 0x60, 0x00, 0x00, 0x00, 0x00, /* code=94, hex=0x5E, ascii="^" */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x7C, /* code=95, hex=0x5F, ascii="_" */
|
||||
0x00, 0x60, 0xC0, 0xC1, 0x80, 0x00, 0x00, 0x00, /* code=96, hex=0x60, ascii="`" */
|
||||
0x00, 0x00, 0x01, 0xE0, 0x67, 0xD9, 0x9D, 0x00, /* code=97, hex=0x61, ascii="a" */
|
||||
0x00, 0xC1, 0x83, 0xE6, 0x6C, 0xD9, 0xAE, 0x00, /* code=98, hex=0x62, ascii="b" */
|
||||
0x00, 0x00, 0x01, 0xE6, 0x6C, 0x19, 0x9E, 0x00, /* code=99, hex=0x63, ascii="c" */
|
||||
0x00, 0x0C, 0x19, 0xF6, 0x6C, 0xD9, 0x9D, 0x00, /* code=100, hex=0x64, ascii="d" */
|
||||
0x00, 0x00, 0x01, 0xE6, 0x6F, 0xD8, 0x1E, 0x00, /* code=101, hex=0x65, ascii="e" */
|
||||
0x00, 0x38, 0xD9, 0x87, 0xC6, 0x0C, 0x3C, 0x00, /* code=102, hex=0x66, ascii="f" */
|
||||
0x00, 0x00, 0x01, 0xD6, 0x6C, 0xCF, 0x83, 0x3C, /* code=103, hex=0x67, ascii="g" */
|
||||
0x00, 0xC1, 0x83, 0x67, 0x6C, 0xD9, 0xB3, 0x00, /* code=104, hex=0x68, ascii="h" */
|
||||
0x18, 0x30, 0x00, 0xC3, 0x83, 0x06, 0x1E, 0x00, /* code=105, hex=0x69, ascii="i" */
|
||||
0x0C, 0x18, 0x00, 0x63, 0xC1, 0x93, 0x36, 0x38, /* code=106, hex=0x6A, ascii="j" */
|
||||
0x00, 0xC1, 0x83, 0x36, 0xCF, 0x1B, 0x33, 0x00, /* code=107, hex=0x6B, ascii="k" */
|
||||
0x00, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x1E, 0x00, /* code=108, hex=0x6C, ascii="l" */
|
||||
0x00, 0x00, 0x03, 0x6F, 0xFA, 0xF5, 0xEB, 0x00, /* code=109, hex=0x6D, ascii="m" */
|
||||
0x00, 0x00, 0x03, 0x67, 0x6C, 0xD9, 0xB3, 0x00, /* code=110, hex=0x6E, ascii="n" */
|
||||
0x00, 0x00, 0x01, 0xE6, 0x6C, 0xD9, 0x9E, 0x00, /* code=111, hex=0x6F, ascii="o" */
|
||||
0x00, 0x00, 0x03, 0xE6, 0x6C, 0xDD, 0xB6, 0x60, /* code=112, hex=0x70, ascii="p" */
|
||||
0x00, 0x00, 0x01, 0xD6, 0x6C, 0xDB, 0x9B, 0x06, /* code=113, hex=0x71, ascii="q" */
|
||||
0x00, 0x00, 0x01, 0x37, 0xE6, 0x0C, 0x3C, 0x00, /* code=114, hex=0x72, ascii="r" */
|
||||
0x00, 0x00, 0x01, 0xE6, 0x07, 0x81, 0x9E, 0x00, /* code=115, hex=0x73, ascii="s" */
|
||||
0x00, 0x20, 0xC7, 0xE3, 0x06, 0x0D, 0x8E, 0x00, /* code=116, hex=0x74, ascii="t" */
|
||||
0x00, 0x00, 0x03, 0x36, 0x6C, 0xDB, 0x9B, 0x00, /* code=117, hex=0x75, ascii="u" */
|
||||
0x00, 0x00, 0x06, 0x3C, 0x6D, 0x8E, 0x08, 0x00, /* code=118, hex=0x76, ascii="v" */
|
||||
0x00, 0x00, 0x06, 0xBD, 0x6F, 0x9B, 0x22, 0x00, /* code=119, hex=0x77, ascii="w" */
|
||||
0x00, 0x00, 0x06, 0x36, 0xC7, 0x1B, 0x63, 0x00, /* code=120, hex=0x78, ascii="x" */
|
||||
0x00, 0x00, 0x03, 0x36, 0x66, 0xC7, 0x36, 0x38, /* code=121, hex=0x79, ascii="y" */
|
||||
0x00, 0x00, 0x03, 0xF0, 0x63, 0x0C, 0x3F, 0x00, /* code=122, hex=0x7A, ascii="z" */
|
||||
0x00, 0x38, 0xC1, 0x86, 0x06, 0x0C, 0x0E, 0x00, /* code=123, hex=0x7B, ascii="{" */
|
||||
0x18, 0x30, 0x60, 0xC0, 0x03, 0x06, 0x0C, 0x00, /* code=124, hex=0x7C, ascii="|" */
|
||||
0x00, 0xE0, 0x60, 0xC0, 0xC3, 0x06, 0x38, 0x00, /* code=125, hex=0x7D, ascii="}" */
|
||||
0x00, 0x20, 0xEB, 0x70, 0x40, 0x00, 0x00, 0x00, /* code=126, hex=0x7E, ascii="~" */
|
||||
// 0x00, 0x00, 0x20, 0xE3, 0x6C, 0x5F, 0x80, 0x00, // /* code=127, hex=0x7F, ascii="^?" */
|
||||
// 0x00, 0x79, 0x9B, 0x06, 0x0C, 0x19, 0x9E, 0x78, // /* code=128, hex=0x80, ascii="!^@" */
|
||||
// 0x66, 0xCC, 0x03, 0x36, 0x6C, 0xD9, 0x9D, 0x00, // /* code=129, hex=0x81, ascii="!^A" */
|
||||
// 0x0C, 0x30, 0x01, 0xF6, 0x2F, 0xD8, 0x1F, 0x00, // /* code=130, hex=0x82, ascii="!^B" */
|
||||
// 0x1C, 0x6C, 0x01, 0xE0, 0x67, 0xD9, 0x9F, 0x00, // /* code=131, hex=0x83, ascii="!^C" */
|
||||
// 0x36, 0x6C, 0x01, 0xE0, 0x67, 0xD9, 0x9F, 0x00, // /* code=132, hex=0x84, ascii="!^D" */
|
||||
// 0x18, 0x18, 0x01, 0xE0, 0x67, 0xD9, 0x9D, 0x00, // /* code=133, hex=0x85, ascii="!^E" */
|
||||
// 0x1C, 0x28, 0x01, 0xE0, 0x67, 0xD9, 0x9D, 0x00, // /* code=134, hex=0x86, ascii="!^F" */
|
||||
// 0x00, 0x00, 0x00, 0xE3, 0x6C, 0x0D, 0x8E, 0x78, // /* code=135, hex=0x87, ascii="!^G" */
|
||||
// 0x08, 0x38, 0x01, 0xE6, 0x6F, 0xD8, 0x1F, 0x00, // /* code=136, hex=0x88, ascii="!^H" */
|
||||
// 0x66, 0xCC, 0x01, 0xE6, 0x6F, 0xD8, 0x1F, 0x00, // /* code=137, hex=0x89, ascii="!^I" */
|
||||
// 0x18, 0x18, 0x01, 0xE6, 0x6F, 0xD8, 0x1F, 0x00, // /* code=138, hex=0x8A, ascii="!^J" */
|
||||
// 0x66, 0xCC, 0x01, 0xC1, 0x83, 0x06, 0x1E, 0x00, // /* code=139, hex=0x8B, ascii="!^K" */
|
||||
// 0x10, 0x70, 0x01, 0xC1, 0x83, 0x06, 0x1E, 0x00, // /* code=140, hex=0x8C, ascii="!^L" */
|
||||
// 0x30, 0x30, 0x01, 0xC1, 0x83, 0x06, 0x1E, 0x00, // /* code=141, hex=0x8D, ascii="!^M" */
|
||||
// 0xC6, 0x20, 0xE1, 0x46, 0xCF, 0xB1, 0xE3, 0x00, // /* code=142, hex=0x8E, ascii="!^N" */
|
||||
// 0x38, 0x50, 0xE1, 0x46, 0xCF, 0xB1, 0xE3, 0x00, // /* code=143, hex=0x8F, ascii="!^O" */
|
||||
// 0x1C, 0x61, 0xFB, 0x07, 0xCC, 0x18, 0x3F, 0x00, // /* code=144, hex=0x90, ascii="!^P" */
|
||||
// 0x00, 0x00, 0x03, 0xE1, 0xAF, 0xF6, 0x3F, 0x00, // /* code=145, hex=0x91, ascii="!^Q" */
|
||||
// 0x00, 0x3C, 0xE2, 0xC5, 0xFF, 0x36, 0x6F, 0x00, // /* code=146, hex=0x92, ascii="!^R" */
|
||||
// 0x10, 0x70, 0x01, 0xE6, 0x6C, 0xD9, 0x9E, 0x00, // /* code=147, hex=0x93, ascii="!^S" */
|
||||
// 0x66, 0xCC, 0x01, 0xE6, 0x6C, 0xD9, 0x9E, 0x00, // /* code=148, hex=0x94, ascii="!^T" */
|
||||
// 0x18, 0x18, 0x01, 0xE6, 0x6C, 0xD9, 0x9E, 0x00, // /* code=149, hex=0x95, ascii="!^U" */
|
||||
// 0x08, 0x38, 0x03, 0x36, 0x6C, 0xD9, 0x9D, 0x00, // /* code=150, hex=0x96, ascii="!^V" */
|
||||
// 0x18, 0x18, 0x03, 0x36, 0x6C, 0xD9, 0x9D, 0x00, // /* code=151, hex=0x97, ascii="!^W" */
|
||||
// 0x66, 0xCC, 0x03, 0x36, 0x66, 0xC7, 0x36, 0x38, // /* code=152, hex=0x98, ascii="!^X" */
|
||||
// 0x66, 0x00, 0xF3, 0x36, 0x6C, 0xD9, 0x9E, 0x00, // /* code=153, hex=0x99, ascii="!^Y" */
|
||||
// 0x66, 0x01, 0x9B, 0x36, 0x6C, 0xD9, 0x9E, 0x00, // /* code=154, hex=0x9A, ascii="!^Z" */
|
||||
// 0x08, 0x10, 0xF3, 0x06, 0x07, 0x84, 0x08, 0x00, // /* code=155, hex=0x9B, ascii="!^[" */
|
||||
// 0x1C, 0x6C, 0xC1, 0x87, 0xC6, 0x0F, 0x33, 0x00, // /* code=156, hex=0x9C, ascii="!^\" */
|
||||
// 0x66, 0xCC, 0xF0, 0xC7, 0xE3, 0x1F, 0x8C, 0x00, // /* code=157, hex=0x9D, ascii="!^]" */
|
||||
// 0xE1, 0xA3, 0x47, 0xAC, 0xDB, 0xF3, 0x03, 0x00, // /* code=158, hex=0x9E, ascii="!^^" */
|
||||
// 0x0E, 0x30, 0x60, 0xC7, 0xE3, 0x06, 0x0C, 0x70, // /* code=159, hex=0x9F, ascii="!^_" */
|
||||
// 0x06, 0x18, 0x01, 0xE0, 0x67, 0xD9, 0x9D, 0x00, // /* code=160, hex=0xA0, ascii="! " */
|
||||
// 0x0C, 0x30, 0x01, 0xC1, 0x83, 0x06, 0x1E, 0x00, // /* code=161, hex=0xA1, ascii="!!" */
|
||||
// 0x0C, 0x30, 0x01, 0xE6, 0x6C, 0xD9, 0x9E, 0x00, // /* code=162, hex=0xA2, ascii="!"" */
|
||||
// 0x0C, 0x30, 0x03, 0x36, 0x6C, 0xD9, 0x9D, 0x00, // /* code=163, hex=0xA3, ascii="!#" */
|
||||
// 0x77, 0xB8, 0x03, 0x67, 0x6C, 0xD9, 0xB3, 0x00, // /* code=164, hex=0xA4, ascii="!$" */
|
||||
// 0x77, 0xB8, 0x03, 0x37, 0x6F, 0xDB, 0xB3, 0x00, // /* code=165, hex=0xA5, ascii="!%" */
|
||||
// 0x38, 0x18, 0xF3, 0x63, 0x40, 0x1F, 0x00, 0x00, // /* code=166, hex=0xA6, ascii="!&" */
|
||||
// 0x3C, 0xCD, 0x99, 0xE0, 0x0F, 0xC0, 0x00, 0x00, // /* code=167, hex=0xA7, ascii="!'" */
|
||||
// 0x00, 0x30, 0x00, 0xC3, 0x0C, 0x19, 0x9E, 0x00, // /* code=168, hex=0xA8, ascii="!(" */
|
||||
// 0x00, 0x00, 0x01, 0xE3, 0xC6, 0x0C, 0x00, 0x00, // /* code=169, hex=0xA9, ascii="!)" */
|
||||
// 0x00, 0x00, 0x03, 0xE7, 0xC1, 0x83, 0x00, 0x00, // /* code=170, hex=0xAA, ascii="!*" */
|
||||
// 0x60, 0xC1, 0x83, 0x71, 0xA0, 0x86, 0x0F, 0x00, // /* code=171, hex=0xAB, ascii="!+" */
|
||||
// 0x60, 0xC1, 0x83, 0x67, 0xC5, 0x9F, 0x06, 0x00, // /* code=172, hex=0xAC, ascii="!," */
|
||||
// 0x00, 0x30, 0x00, 0xC1, 0x87, 0x8F, 0x0C, 0x00, // /* code=173, hex=0xAD, ascii="!-" */
|
||||
// 0x00, 0x00, 0xCB, 0x3C, 0xCC, 0xCC, 0x80, 0x00, // /* code=174, hex=0xAE, ascii="!." */
|
||||
// 0x00, 0x03, 0x33, 0x33, 0x2C, 0xF3, 0x00, 0x00, // /* code=175, hex=0xAF, ascii="!/" */
|
||||
// 0x54, 0x02, 0xA8, 0x05, 0x40, 0x2A, 0x80, 0x54, // /* code=176, hex=0xB0, ascii="!0" */
|
||||
// 0x92, 0x90, 0x94, 0x94, 0x84, 0xA4, 0xA4, 0x24, // /* code=177, hex=0xB1, ascii="!1" */
|
||||
// 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // /* code=178, hex=0xB2, ascii="!2" */
|
||||
// 0x10, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10, // /* code=179, hex=0xB3, ascii="!3" */
|
||||
// 0x10, 0x20, 0x40, 0x8F, 0x02, 0x04, 0x08, 0x10, // /* code=180, hex=0xB4, ascii="!4" */
|
||||
// 0x10, 0x20, 0x47, 0x81, 0x1E, 0x04, 0x08, 0x10, // /* code=181, hex=0xB5, ascii="!5" */
|
||||
// 0x28, 0x50, 0xA1, 0x4E, 0x85, 0x0A, 0x14, 0x28, // /* code=182, hex=0xB6, ascii="!6" */
|
||||
// 0x00, 0x00, 0x00, 0x0F, 0x85, 0x0A, 0x14, 0x28, // /* code=183, hex=0xB7, ascii="!7" */
|
||||
// 0x00, 0x00, 0x07, 0x81, 0x1E, 0x04, 0x08, 0x10, // /* code=184, hex=0xB8, ascii="!8" */
|
||||
// 0x28, 0x50, 0xA7, 0x40, 0x9D, 0x0A, 0x14, 0x28, // /* code=185, hex=0xB9, ascii="!9" */
|
||||
// 0x28, 0x50, 0xA1, 0x42, 0x85, 0x0A, 0x14, 0x28, // /* code=186, hex=0xBA, ascii="!:" */
|
||||
// 0x00, 0x00, 0x07, 0xC0, 0x9D, 0x0A, 0x14, 0x28, // /* code=187, hex=0xBB, ascii="!;" */
|
||||
// 0x28, 0x50, 0xA7, 0x40, 0x9F, 0x00, 0x00, 0x00, // /* code=188, hex=0xBC, ascii="!<" */
|
||||
// 0x28, 0x50, 0xA1, 0x4F, 0x80, 0x00, 0x00, 0x00, // /* code=189, hex=0xBD, ascii="!=" */
|
||||
// 0x10, 0x20, 0x47, 0x81, 0x1E, 0x00, 0x00, 0x00, // /* code=190, hex=0xBE, ascii="!>" */
|
||||
// 0x00, 0x00, 0x00, 0x0F, 0x02, 0x04, 0x08, 0x10, // /* code=191, hex=0xBF, ascii="!?" */
|
||||
// 0x10, 0x20, 0x40, 0x81, 0xE0, 0x00, 0x00, 0x00, // /* code=192, hex=0xC0, ascii="!@" */
|
||||
// 0x10, 0x20, 0x40, 0x8F, 0xE0, 0x00, 0x00, 0x00, // /* code=193, hex=0xC1, ascii="!A" */
|
||||
// 0x00, 0x00, 0x00, 0x0F, 0xE2, 0x04, 0x08, 0x10, // /* code=194, hex=0xC2, ascii="!B" */
|
||||
// 0x10, 0x20, 0x40, 0x81, 0xE2, 0x04, 0x08, 0x10, // /* code=195, hex=0xC3, ascii="!C" */
|
||||
// 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00, // /* code=196, hex=0xC4, ascii="!D" */
|
||||
// 0x10, 0x20, 0x40, 0x8F, 0xE2, 0x04, 0x08, 0x10, // /* code=197, hex=0xC5, ascii="!E" */
|
||||
// 0x10, 0x20, 0x40, 0xF1, 0x03, 0xC4, 0x08, 0x10, // /* code=198, hex=0xC6, ascii="!F" */
|
||||
// 0x28, 0x50, 0xA1, 0x42, 0xE5, 0x0A, 0x14, 0x28, // /* code=199, hex=0xC7, ascii="!G" */
|
||||
// 0x28, 0x50, 0xA1, 0x72, 0x07, 0xC0, 0x00, 0x00, // /* code=200, hex=0xC8, ascii="!H" */
|
||||
// 0x00, 0x00, 0x01, 0xF2, 0x05, 0xCA, 0x14, 0x28, // /* code=201, hex=0xC9, ascii="!I" */
|
||||
// 0x28, 0x50, 0xA7, 0x70, 0x1F, 0xC0, 0x00, 0x00, // /* code=202, hex=0xCA, ascii="!J" */
|
||||
// 0x00, 0x00, 0x07, 0xF0, 0x1D, 0xCA, 0x14, 0x28, // /* code=203, hex=0xCB, ascii="!K" */
|
||||
// 0x28, 0x50, 0xA1, 0x72, 0x05, 0xCA, 0x14, 0x28, // /* code=204, hex=0xCC, ascii="!L" */
|
||||
// 0x00, 0x00, 0x07, 0xF0, 0x1F, 0xC0, 0x00, 0x00, // /* code=205, hex=0xCD, ascii="!M" */
|
||||
// 0x28, 0x50, 0xA7, 0x70, 0x1D, 0xCA, 0x14, 0x28, // /* code=206, hex=0xCE, ascii="!N" */
|
||||
// 0x10, 0x20, 0x47, 0xF0, 0x1F, 0xC0, 0x00, 0x00, // /* code=207, hex=0xCF, ascii="!O" */
|
||||
// 0x28, 0x50, 0xA1, 0x4F, 0x80, 0x00, 0x00, 0x00, // /* code=208, hex=0xD0, ascii="!P" */
|
||||
// 0x00, 0x00, 0x07, 0xF0, 0x1F, 0xC4, 0x08, 0x10, // /* code=209, hex=0xD1, ascii="!Q" */
|
||||
// 0x00, 0x00, 0x00, 0x0F, 0x85, 0x0A, 0x14, 0x28, // /* code=210, hex=0xD2, ascii="!R" */
|
||||
// 0x28, 0x50, 0xA1, 0x43, 0xE0, 0x00, 0x00, 0x00, // /* code=211, hex=0xD3, ascii="!S" */
|
||||
// 0x10, 0x20, 0x40, 0xF1, 0x03, 0xC0, 0x00, 0x00, // /* code=212, hex=0xD4, ascii="!T" */
|
||||
// 0x00, 0x00, 0x00, 0xF1, 0x03, 0xC4, 0x08, 0x10, // /* code=213, hex=0xD5, ascii="!U" */
|
||||
// 0x00, 0x00, 0x00, 0x03, 0xE5, 0x0A, 0x14, 0x28, // /* code=214, hex=0xD6, ascii="!V" */
|
||||
// 0x28, 0x50, 0xA1, 0x4E, 0x85, 0x0A, 0x14, 0x28, // /* code=215, hex=0xD7, ascii="!W" */
|
||||
// 0x10, 0x20, 0x47, 0xF1, 0x1F, 0xC4, 0x08, 0x10, // /* code=216, hex=0xD8, ascii="!X" */
|
||||
// 0x10, 0x20, 0x40, 0x8F, 0x00, 0x00, 0x00, 0x00, // /* code=217, hex=0xD9, ascii="!Y" */
|
||||
// 0x00, 0x00, 0x00, 0x01, 0xE2, 0x04, 0x08, 0x10, // /* code=218, hex=0xDA, ascii="!Z" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, // /* code=219, hex=0xDB, ascii="![" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, // /* code=220, hex=0xDC, ascii="!\" */
|
||||
// 0xF1, 0xE3, 0xC7, 0x8F, 0x1E, 0x3C, 0x78, 0xF0, // /* code=221, hex=0xDD, ascii="!]" */
|
||||
// 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, // /* code=222, hex=0xDE, ascii="!^" */
|
||||
// 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, // /* code=223, hex=0xDF, ascii="!_" */
|
||||
// 0x00, 0x69, 0xA3, 0x46, 0x86, 0x80, 0x00, 0x00, // /* code=224, hex=0xE0, ascii="!`" */
|
||||
// 0x7C, 0xCD, 0x9B, 0x66, 0x6C, 0x59, 0xB6, 0x08, // /* code=225, hex=0xE1, ascii="!a" */
|
||||
// 0x00, 0xFD, 0x8B, 0x06, 0x0C, 0x18, 0x30, 0x00, // /* code=226, hex=0xE2, ascii="!b" */
|
||||
// 0x00, 0x01, 0xB7, 0xFF, 0x6C, 0xDB, 0x36, 0x00, // /* code=227, hex=0xE3, ascii="!c" */
|
||||
// 0x01, 0xFF, 0x1B, 0x03, 0x86, 0x19, 0xFF, 0x00, // /* code=228, hex=0xE4, ascii="!d" */
|
||||
// 0x00, 0x00, 0x01, 0xF6, 0xCD, 0x9B, 0x1C, 0x00, // /* code=229, hex=0xE5, ascii="!e" */
|
||||
// 0x00, 0x00, 0x01, 0xB3, 0x66, 0xCF, 0xB1, 0x40, // /* code=230, hex=0xE6, ascii="!f" */
|
||||
// 0x00, 0x01, 0xEB, 0x50, 0xE1, 0x86, 0x0C, 0x00, // /* code=231, hex=0xE7, ascii="!g" */
|
||||
// 0x3C, 0x30, 0xF3, 0x36, 0x67, 0x86, 0x1E, 0x00, // /* code=232, hex=0xE8, ascii="!h" */
|
||||
// 0x00, 0x79, 0x9B, 0x37, 0xEC, 0xD9, 0x9E, 0x00, // /* code=233, hex=0xE9, ascii="!i" */
|
||||
// 0x00, 0x79, 0x9B, 0x36, 0x6C, 0xC9, 0x33, 0x00, // /* code=234, hex=0xEA, ascii="!j" */
|
||||
// 0x00, 0x79, 0x81, 0x81, 0x87, 0x99, 0x9E, 0x00, // /* code=235, hex=0xEB, ascii="!k" */
|
||||
// 0x00, 0x00, 0x01, 0xA4, 0xA9, 0x52, 0x9A, 0x00, // /* code=236, hex=0xEC, ascii="!l" */
|
||||
// 0x04, 0x79, 0x9B, 0x77, 0x6C, 0xCF, 0x08, 0x20, // /* code=237, hex=0xED, ascii="!m" */
|
||||
// 0x1E, 0x61, 0x83, 0x07, 0xEC, 0x0C, 0x0F, 0x00, // /* code=238, hex=0xEE, ascii="!n" */
|
||||
// 0x00, 0x00, 0xF3, 0x36, 0x6C, 0xD9, 0xB3, 0x00, // /* code=239, hex=0xEF, ascii="!o" */
|
||||
// 0x00, 0xF8, 0x00, 0x07, 0xC0, 0x00, 0x3E, 0x00, // /* code=240, hex=0xF0, ascii="!p" */
|
||||
// 0x00, 0x30, 0x63, 0xF1, 0x83, 0x00, 0x3F, 0x00, // /* code=241, hex=0xF1, ascii="!q" */
|
||||
// 0x00, 0x60, 0x60, 0x61, 0x86, 0x00, 0x1E, 0x00, // /* code=242, hex=0xF2, ascii="!r" */
|
||||
// 0x00, 0x18, 0x61, 0x81, 0x81, 0x80, 0x1E, 0x00, // /* code=243, hex=0xF3, ascii="!s" */
|
||||
// 0x0C, 0x34, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, // /* code=244, hex=0xF4, ascii="!t" */
|
||||
// 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x2C, 0x30, // /* code=245, hex=0xF5, ascii="!u" */
|
||||
// 0x00, 0x30, 0x60, 0x07, 0xE0, 0x06, 0x0C, 0x00, // /* code=246, hex=0xF6, ascii="!v" */
|
||||
// 0x00, 0x00, 0x6B, 0xB0, 0x03, 0x5D, 0x80, 0x00, // /* code=247, hex=0xF7, ascii="!w" */
|
||||
// 0x00, 0x79, 0x9B, 0x33, 0xC0, 0x00, 0x00, 0x00, // /* code=248, hex=0xF8, ascii="!x" */
|
||||
// 0x00, 0x00, 0x61, 0xE3, 0xC3, 0x00, 0x00, 0x00, // /* code=249, hex=0xF9, ascii="!y" */
|
||||
// 0x00, 0x00, 0x00, 0xC1, 0x80, 0x00, 0x00, 0x00, // /* code=250, hex=0xFA, ascii="!z" */
|
||||
// 0x0E, 0x18, 0x30, 0x60, 0xCD, 0x8F, 0x06, 0x00, // /* code=251, hex=0xFB, ascii="!{" */
|
||||
// 0x00, 0xF1, 0xB3, 0x66, 0xCD, 0x80, 0x00, 0x00, // /* code=252, hex=0xFC, ascii="!|" */
|
||||
// 0x00, 0x71, 0x30, 0xC3, 0x0F, 0x80, 0x00, 0x00, // /* code=253, hex=0xFD, ascii="!}" */
|
||||
// 0x00, 0xF9, 0xF3, 0xE7, 0xC0, 0x00, 0x00, 0x00, // /* code=254, hex=0xFE, ascii="!~" */
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // /* code=255, hex=0xFF, ascii="!^" */
|
||||
};
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user