Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
37e4e48f2b Revert DEFAULT_LED_PIN change per reviewer feedback
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-03-20 08:17:34 +00:00
copilot-swe-agent[bot]
7166473d3b Only change DEFAULT_LED_PIN to 4 when a specific ethernet board is pre-selected via WLED_ETH_DEFAULT
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-03-19 19:06:32 +00:00
copilot-swe-agent[bot]
bc68a5e2db Initial plan 2026-03-19 18:50:02 +00:00
108 changed files with 17579 additions and 9473 deletions

View File

@@ -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

View File

@@ -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>` | 1520 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`

View File

@@ -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 | 1520 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
View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 (0255)
* @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 28 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 (24).
### 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)
```

View File

@@ -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 3961). 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 28 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 3337 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 3337 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.44.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.44.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.15.3 | **13.2** | C++20 (gnu++2b) | Significant warning changes |
| 5.45.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) | 200320 KB | Hot-path buffers, task stacks, small allocations |
| IRAM | `MALLOC_CAP_EXEC` | Fastest | No | 32128 KB | Code (automatic via `IRAM_ATTR`) |
| PSRAM (SPIRAM) | `MALLOC_CAP_SPIRAM \| MALLOC_CAP_8BIT` | Slower | Chip-dependent | 216 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, 310× 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 1314 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 15003000 µ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 -->

View File

@@ -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
View File

@@ -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",

View File

@@ -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": {

View File

@@ -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])

View File

@@ -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'))

View 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

View File

@@ -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
;

View 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;

View File

@@ -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>

View File

@@ -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),

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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];
}
}
}
}

View File

@@ -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);

View File

@@ -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 //
///////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -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;

View File

@@ -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"
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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, &current->_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;
}
};

View File

@@ -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();

View File

@@ -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;

View File

@@ -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");

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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;

View File

@@ -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 } });
}

View File

@@ -147,7 +147,7 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 32px;
font-size: 48px;
}
.fs2 {
font-size: 28px;

View File

@@ -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:&nbsp;26)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs:&nbsp;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="&#xe0a1;" 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>

View File

@@ -11,7 +11,6 @@
<glyph unicode="&#xe04c;" 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="&#xe066;" 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="&#xe08f;" 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="&#xe0a1;" 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="&#xe0a2;" 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="&#xe0e8;" 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="&#xe116;" 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

File diff suppressed because one or more lines are too long

View File

@@ -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";
}

View File

@@ -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 {

View File

@@ -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">&#xe037;</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,

View File

@@ -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>

View File

@@ -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">&#128197;</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">&#128197;</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">&#128197;</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">&#128197;</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-&gt;off</td>
<td>long<br>off-&gt;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>

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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++;
}
}
}

View File

@@ -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);
};

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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
};
};

View File

@@ -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);
}
}

View File

@@ -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; }
};

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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.

View File

@@ -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));
}

View File

@@ -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;
}
};

View File

@@ -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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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 */
};

View File

@@ -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="~" */
};

View File

@@ -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.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -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.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -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.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -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.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -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="!^Ÿ" */
};

Some files were not shown because too many files have changed in this diff Show More