Compare commits
345 Commits
v0.13.0-b3
...
0_13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd0471386d | ||
|
|
1daa97545b | ||
|
|
8bd8975e0a | ||
|
|
2847921e5a | ||
|
|
102a28aef4 | ||
|
|
cade1800f4 | ||
|
|
8176f1141e | ||
|
|
515827c745 | ||
|
|
420f858d9b | ||
|
|
902c11d074 | ||
|
|
38330b735c | ||
|
|
bda3c4ab7a | ||
|
|
d8d01ac353 | ||
|
|
51d935f419 | ||
|
|
c96f83b076 | ||
|
|
7308f5993c | ||
|
|
22ac12dc36 | ||
|
|
866296fefd | ||
|
|
9d574397bc | ||
|
|
bee48dae7e | ||
|
|
e12f7b67e5 | ||
|
|
a8908238d5 | ||
|
|
fd4c0e795a | ||
|
|
c79eb43347 | ||
|
|
860e74bffa | ||
|
|
ed374684a6 | ||
|
|
169a46c38c | ||
|
|
1dbea434a3 | ||
|
|
0dd12cf0a6 | ||
|
|
19c8b4fe2d | ||
|
|
26fa38d052 | ||
|
|
db8e1dec3e | ||
|
|
213e3e998a | ||
|
|
bef9c68f81 | ||
|
|
099d2fd03d | ||
|
|
23d39e5366 | ||
|
|
1a513c7bbf | ||
|
|
d1f76042e1 | ||
|
|
9cd8acab43 | ||
|
|
8b79a9708b | ||
|
|
e362b3b6aa | ||
|
|
d2ced93e58 | ||
|
|
958cd35e21 | ||
|
|
46eae410c3 | ||
|
|
73a9e1c316 | ||
|
|
03862d4b6c | ||
|
|
ae90aa4ccc | ||
|
|
b583def913 | ||
|
|
dd85da053f | ||
|
|
6079effae3 | ||
|
|
8d2fe315db | ||
|
|
22c3ac5be3 | ||
|
|
a517f0df1d | ||
|
|
9c9854b6bf | ||
|
|
d280e16723 | ||
|
|
b93a9cb8bc | ||
|
|
8601052179 | ||
|
|
eaa20ff4bf | ||
|
|
e4c6e4bc48 | ||
|
|
c52597205e | ||
|
|
c73033c0b4 | ||
|
|
522e752582 | ||
|
|
854ed8cfa9 | ||
|
|
4642205768 | ||
|
|
40dbfbe092 | ||
|
|
6c315e5a9c | ||
|
|
ef0f91d8d0 | ||
|
|
9552784e72 | ||
|
|
f068327307 | ||
|
|
1bc698ae78 | ||
|
|
1b2134d7a8 | ||
|
|
f922268af7 | ||
|
|
4865ddb377 | ||
|
|
a556732e4f | ||
|
|
0ea31cb088 | ||
|
|
b626c7620e | ||
|
|
5d90d8930e | ||
|
|
b01309c3bf | ||
|
|
961d5591bd | ||
|
|
eca3f12fed | ||
|
|
a2c8796e04 | ||
|
|
ad301fd087 | ||
|
|
02b08939cd | ||
|
|
9b0d583f1b | ||
|
|
4a0a07f158 | ||
|
|
9c864c9759 | ||
|
|
85b1c309d1 | ||
|
|
6fe43b7b5c | ||
|
|
25427ee60d | ||
|
|
be90bf0188 | ||
|
|
adcdaba199 | ||
|
|
17907589cc | ||
|
|
f333df181f | ||
|
|
4ce557a829 | ||
|
|
fc845dc936 | ||
|
|
7beae93441 | ||
|
|
4d4a20e05e | ||
|
|
c03d4f115f | ||
|
|
ed90b638a9 | ||
|
|
94a0199955 | ||
|
|
44739c5198 | ||
|
|
5f871bc01f | ||
|
|
1f5971f15a | ||
|
|
694466a196 | ||
|
|
03311d3776 | ||
|
|
ae0eba866a | ||
|
|
906737bedb | ||
|
|
7138e891be | ||
|
|
53abe36b83 | ||
|
|
efbb7a034c | ||
|
|
05bc81bf4e | ||
|
|
f8bc0bd2b5 | ||
|
|
cf94cb1092 | ||
|
|
02d92e32c7 | ||
|
|
7f92607b85 | ||
|
|
3be4b69b44 | ||
|
|
bb9afcb304 | ||
|
|
e9a05890a5 | ||
|
|
613809c2af | ||
|
|
7b969bb8c2 | ||
|
|
7aef551292 | ||
|
|
447b811fa0 | ||
|
|
435040814d | ||
|
|
9987416a4a | ||
|
|
31e33e0a8b | ||
|
|
b211d8b085 | ||
|
|
83416ee2e0 | ||
|
|
fa981a389f | ||
|
|
55817f31f9 | ||
|
|
6d2ef4e0bf | ||
|
|
4d714cf9a4 | ||
|
|
930ded6767 | ||
|
|
4cdb18907f | ||
|
|
38bc618ee5 | ||
|
|
00b0193a43 | ||
|
|
f9bce54104 | ||
|
|
7ee14724fc | ||
|
|
4eb0dbb5a4 | ||
|
|
8c5b3fe23e | ||
|
|
97f8eea302 | ||
|
|
04d5932252 | ||
|
|
b33c5798ee | ||
|
|
6180c2f948 | ||
|
|
795c515999 | ||
|
|
00dbdc2267 | ||
|
|
32286888e5 | ||
|
|
565d8d8f04 | ||
|
|
0a5a0bef48 | ||
|
|
6e0e5c102e | ||
|
|
be8a9ae73b | ||
|
|
afaa001738 | ||
|
|
22fbb0e35b | ||
|
|
e17203ca1b | ||
|
|
3170fa2208 | ||
|
|
07216db864 | ||
|
|
fec870f264 | ||
|
|
2c5eba335f | ||
|
|
fb19f1ecbc | ||
|
|
e879fe5843 | ||
|
|
0ca7699fe5 | ||
|
|
7f6adfa331 | ||
|
|
5f0b102671 | ||
|
|
d28eb6ae21 | ||
|
|
eca980dfca | ||
|
|
742c792ec7 | ||
|
|
9b062f33c5 | ||
|
|
ea15c2245e | ||
|
|
26ae6d3691 | ||
|
|
f97bc9dba8 | ||
|
|
fe6b1c13c4 | ||
|
|
5608425a12 | ||
|
|
f784b01d20 | ||
|
|
2648eba5bf | ||
|
|
255347ab77 | ||
|
|
52c36ef6a4 | ||
|
|
e54819e7e5 | ||
|
|
7eb029dcb6 | ||
|
|
f8c80283e4 | ||
|
|
04f5bdb843 | ||
|
|
aba4dc7c50 | ||
|
|
7fb46cf982 | ||
|
|
ae8281f835 | ||
|
|
fa35293618 | ||
|
|
20ccca0aec | ||
|
|
10e216da6b | ||
|
|
6491353a57 | ||
|
|
9f44f989e5 | ||
|
|
33f72e40da | ||
|
|
18868a5bd6 | ||
|
|
754682577c | ||
|
|
71520f6709 | ||
|
|
3f5a09229d | ||
|
|
5609771993 | ||
|
|
79b01cdc3c | ||
|
|
aef0243b73 | ||
|
|
736053e24e | ||
|
|
2c14181051 | ||
|
|
296fe4b62e | ||
|
|
118f02fd11 | ||
|
|
990d0f6e3e | ||
|
|
84624666ce | ||
|
|
8bd716c056 | ||
|
|
cd95abb2a1 | ||
|
|
1270f2d577 | ||
|
|
c27117e99e | ||
|
|
28556790d6 | ||
|
|
41c9bb63a0 | ||
|
|
7d5e2466f0 | ||
|
|
d3f35955d6 | ||
|
|
fb338c0728 | ||
|
|
2ce8f1ee5d | ||
|
|
3f0258e215 | ||
|
|
e72a8d999f | ||
|
|
fed16fd14e | ||
|
|
5dbc45ecb9 | ||
|
|
094bdb29b6 | ||
|
|
9e6866c160 | ||
|
|
7101ad81c4 | ||
|
|
8643263227 | ||
|
|
eab132ed32 | ||
|
|
66bad2b6f8 | ||
|
|
46ec504743 | ||
|
|
cadda12371 | ||
|
|
a643b56555 | ||
|
|
f7404085de | ||
|
|
33036e7599 | ||
|
|
f6e5b67f0d | ||
|
|
9547ac353d | ||
|
|
48339b19d4 | ||
|
|
11c7ffad4e | ||
|
|
1973424e05 | ||
|
|
16d97d3c63 | ||
|
|
3e6728fedb | ||
|
|
3e9aea072d | ||
|
|
9f3e66fff0 | ||
|
|
624993fc89 | ||
|
|
ba8a00764a | ||
|
|
3dec4a6651 | ||
|
|
02fb2550d0 | ||
|
|
37bd525638 | ||
|
|
ea0f37f5b9 | ||
|
|
97b3c3db7b | ||
|
|
b97b6dc144 | ||
|
|
c8d5218c65 | ||
|
|
80a657965e | ||
|
|
b3324d22f5 | ||
|
|
31b7cdff9b | ||
|
|
0465298507 | ||
|
|
d31e4c7815 | ||
|
|
4af1f62aab | ||
|
|
bc403440ba | ||
|
|
38d8dfe5ab | ||
|
|
eb92c0bbf5 | ||
|
|
6df64d0d31 | ||
|
|
83753a5f81 | ||
|
|
3161f5fa47 | ||
|
|
5784092c1b | ||
|
|
d6ad089c60 | ||
|
|
446b4b084c | ||
|
|
d590e01a58 | ||
|
|
adeb9bccb1 | ||
|
|
b44ffffed8 | ||
|
|
2bdaf53ecf | ||
|
|
46e7db6d94 | ||
|
|
7e1920dc4b | ||
|
|
a93f05c047 | ||
|
|
00238247cd | ||
|
|
b33e28835d | ||
|
|
f55f803531 | ||
|
|
8ca298b299 | ||
|
|
090e29effd | ||
|
|
0acca2e313 | ||
|
|
0d77027f60 | ||
|
|
39b7b3ad53 | ||
|
|
00f1b483eb | ||
|
|
c3d48acb4c | ||
|
|
392bda7d8c | ||
|
|
10cfcdab8c | ||
|
|
3f71d3b250 | ||
|
|
1b50fbab22 | ||
|
|
303fc65a6a | ||
|
|
445b6ee13f | ||
|
|
8afaac1e30 | ||
|
|
0327f9428e | ||
|
|
a5de66bbd5 | ||
|
|
d47157eec3 | ||
|
|
f4b47ed399 | ||
|
|
8b2145bd88 | ||
|
|
de454e8b78 | ||
|
|
6cd770b4c7 | ||
|
|
355525c248 | ||
|
|
47d4e7381f | ||
|
|
5dac6690d7 | ||
|
|
b89f7180db | ||
|
|
2ebb837a15 | ||
|
|
849aa64678 | ||
|
|
cbb12e1b7c | ||
|
|
cc87ba4962 | ||
|
|
fb2e556726 | ||
|
|
3f0eb0a046 | ||
|
|
7d6d9eddc4 | ||
|
|
cf87da0ef3 | ||
|
|
0775acedc0 | ||
|
|
8f1cee2e61 | ||
|
|
caa9cc32d7 | ||
|
|
b750f827c5 | ||
|
|
5d147163e5 | ||
|
|
75fe1a19eb | ||
|
|
5c9405fffc | ||
|
|
6457314794 | ||
|
|
84f4e3eedc | ||
|
|
b003ed3f03 | ||
|
|
330da137db | ||
|
|
9e5d45d0de | ||
|
|
b5c15d97fa | ||
|
|
6ddcba8917 | ||
|
|
91598cbbbf | ||
|
|
772c80aa85 | ||
|
|
a28345d858 | ||
|
|
05b532b9eb | ||
|
|
0b0d18f182 | ||
|
|
c1b0877956 | ||
|
|
46b66c76ef | ||
|
|
d00b4335b5 | ||
|
|
7a129e6de1 | ||
|
|
17c20276a9 | ||
|
|
dc9dedf220 | ||
|
|
3ac772badc | ||
|
|
22fc58d93b | ||
|
|
ccd3152b24 | ||
|
|
7d929dcde6 | ||
|
|
3a874bc8c7 | ||
|
|
8453cd82e9 | ||
|
|
f62e56b7ec | ||
|
|
2ac90bbb96 | ||
|
|
f85f2d5d22 | ||
|
|
a94269ceb9 | ||
|
|
476ac263fb | ||
|
|
51a4f61a8f | ||
|
|
267f5159a3 | ||
|
|
a899666e68 | ||
|
|
10a52f8cf9 | ||
|
|
72d04a0120 | ||
|
|
6dbed30008 | ||
|
|
f368bbec32 |
27
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Noticed an issue with your lights?
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. Please quickly search existing issues first!
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior, if consistently possible
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**WLED version**
|
||||
- Board: [e.g. Wemos D1, ESP32 dev]
|
||||
- Version [e.g. 0.10.0, dev200603]
|
||||
- Format [e.g. Binary, self-compiled]
|
||||
|
||||
**Additional context**
|
||||
Anything else you'd like to say about the problem?
|
||||
|
||||
Thank you for your help!
|
||||
83
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please quickly search existing issues first before submitting a bug.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: Tell us what the problem is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
attributes:
|
||||
label: To Reproduce Bug
|
||||
description: Steps to reproduce the behavior, if consistently possible.
|
||||
placeholder: Tell us how to make the bug appear.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: Tell us what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install_format
|
||||
attributes:
|
||||
label: Install Method
|
||||
description: How did you install WLED?
|
||||
options:
|
||||
- Binary from WLED.me
|
||||
- Self-Compiled
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What version of WLED?
|
||||
description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
|
||||
placeholder: "e.g. WLED 0.13.1 (build 2203150)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: Board
|
||||
attributes:
|
||||
label: Which microcontroller/board are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- ESP8266
|
||||
- ESP32
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log/trace output
|
||||
description: Please copy and paste any relevant log output if you have it. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
17
.vscode/extensions.json
vendored
@@ -1,7 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
]
|
||||
}
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
|
||||
183
CHANGELOG.md
@@ -1,6 +1,186 @@
|
||||
## WLED changelog
|
||||
|
||||
### Builds after release 0.12.0
|
||||
### WLED release 0.13.3
|
||||
|
||||
- Version bump to v0.13.3 "Toki"
|
||||
- Disable ESP watchdog by default (fixes flickering and boot issues on a fresh install)
|
||||
- Added support for LPD6803
|
||||
|
||||
### WLED release 0.13.2
|
||||
|
||||
#### Build 2208140
|
||||
|
||||
- Version bump to v0.13.2 "Toki"
|
||||
- Added option to receive live data on the main segment only (PR #2601)
|
||||
- Enable ESP watchdog by default (PR #2657)
|
||||
- Fixed race condition when saving bus config
|
||||
- Better potentiometer filtering (PR #2693)
|
||||
- More suitable DMX libraries (PR #2652)
|
||||
- Fixed outgoing serial TPM2 message length (PR #2628)
|
||||
- Fixed next universe overflow and Art-Net DMX start address (PR #2607)
|
||||
- Fixed relative segment brightness (PR #2665)
|
||||
|
||||
### Builds between releases 0.13.1 and 0.13.2
|
||||
|
||||
#### Build 2203191
|
||||
|
||||
- Fixed sunrise/set calculation (once again)
|
||||
|
||||
#### Build 2203190
|
||||
|
||||
- Fixed `/json/cfg` unable to set busses (#2589)
|
||||
- Fixed Peek with odd LED counts > 255 (#2586)
|
||||
|
||||
#### Build 2203160
|
||||
|
||||
- Version bump to v0.13.2-a0 "Toki"
|
||||
- Add ability to skip up to 255 LEDs
|
||||
- Dependency version bumps
|
||||
|
||||
### WLED release 0.13.1
|
||||
|
||||
#### Build 2203150
|
||||
|
||||
- Version bump to v0.13.1 "Toki"
|
||||
- Fix persistent preset bug, preventing save of new presets
|
||||
|
||||
### WLED release 0.13.0
|
||||
|
||||
#### Build 2203142
|
||||
|
||||
- Release of WLED v0.13.0 "Toki"
|
||||
- Reduce APA102 hardware SPI frequency to 5Mhz
|
||||
- Remove `persistent` parameter in `savePreset()`
|
||||
|
||||
### Builds between releases 0.12.0 and 0.13.0
|
||||
|
||||
#### Build 2203140
|
||||
|
||||
- Added factory reset by pressing button 0 for >10 seconds
|
||||
- Added ability to set presets from DMX Effect mode
|
||||
- Simplified label hiding JS in user interface
|
||||
- Fixed JSON `{"live":true}` indefinite realtime mode
|
||||
|
||||
#### Build 2203080
|
||||
|
||||
- Disabled auto white mode in segments with no RGB bus
|
||||
- Fixed hostname string not 0-terminated
|
||||
- Fixed Popcorn mode not lighting first LED on pop
|
||||
|
||||
#### Build 2203060
|
||||
|
||||
- Dynamic hiding of unused color controls in UI (PR #2567)
|
||||
- Removed native Cronixie support and added Cronixie usermod
|
||||
- Fixed disabled timed preset expanding calendar
|
||||
- Fixed Color Order setting shown for analog busses
|
||||
- Fixed incorrect operator (#2566)
|
||||
|
||||
#### Build 2203011
|
||||
|
||||
- IR rewrite (PR #2561), supports CCT
|
||||
- Added locate button to Time settings
|
||||
- CSS fixes and adjustments
|
||||
- Consistent Tab indentation in index JS and CSS
|
||||
- Added initial contribution style guideline
|
||||
|
||||
#### Build 2202222
|
||||
|
||||
- Version bump to 0.13.0-b7 "Toki"
|
||||
- Fixed HTTP API commands not applying to all selected segments in some conditions
|
||||
- Blynk support is not compiled in by default on ESP32 builds
|
||||
|
||||
#### Build 2202210
|
||||
|
||||
- Fixed HTTP API commands not applying to all selected segments if called from JSON
|
||||
- Improved Stream effects, no longer rely on LED state and won't fade out at low brightness
|
||||
|
||||
#### Build 2202200
|
||||
|
||||
- Added `info.leds.seglc` per-segment light capability info (PR #2552)
|
||||
- Fixed `info.leds.rgbw` behavior
|
||||
- Segment bounds sync (PR #2547)
|
||||
- WebSockets auto reconnection and error handling
|
||||
- Disable relay pin by default (PR #2531)
|
||||
- Various fixes (ESP32 touch pin 33, floats, PR #2530, #2534, #2538)
|
||||
- Deprecated `info.leds.cct`, `info.leds.wv` and `info.leds.rgbw`
|
||||
- Deprecated `/url` endpoint
|
||||
|
||||
#### Build 2202030
|
||||
|
||||
- Switched to binary format for WebSockets peek (PR #2516)
|
||||
- Playlist bugfix
|
||||
- Added `extractModeName()` utility function
|
||||
- Added serial out (PR #2517)
|
||||
- Added configurable baud rate
|
||||
|
||||
#### Build 2201260
|
||||
|
||||
- Initial ESP32-C3 and ESP32-S2 support (PRs #2452, #2454, #2502)
|
||||
- Full segment sync (PR #2427)
|
||||
- Allow overriding of color order by ranges (PR #2463)
|
||||
- Added white channel to Peek
|
||||
|
||||
#### Build 2112080
|
||||
|
||||
- Version bump to 0.13.0-b6 "Toki"
|
||||
- Added "ESP02" (ESP8266 with 2M of flash) to PIO/release binaries
|
||||
|
||||
#### Build 2112070
|
||||
|
||||
- Added new effect "Fairy", replacing "Police All"
|
||||
- Added new effect "Fairytwinkle", replacing "Two Areas"
|
||||
- Static single JSON buffer (performance and stability improvement) (PR #2336)
|
||||
|
||||
#### Build 2112030
|
||||
|
||||
- Fixed ESP32 crash on Colortwinkles brightness change
|
||||
- Fixed setting picker to black resetting hue and saturation
|
||||
- Fixed auto white mode not saved to config
|
||||
|
||||
#### Build 2111300
|
||||
|
||||
- Added CCT and white balance correction support (PR #2285)
|
||||
- Unified UI slider style
|
||||
- Added LED settings config template upload
|
||||
|
||||
#### Build 2111220
|
||||
|
||||
- Fixed preset cycle not working from preset called by UI
|
||||
- Reintroduced permanent min. and max. cycle bounds
|
||||
|
||||
#### Build 2111190
|
||||
|
||||
- Changed default ESP32 LED pin from 16 to 2
|
||||
- Renamed "Running 2" to "Chase 2"
|
||||
- Renamed "Tri Chase" to "Chase 3"
|
||||
|
||||
#### Build 2111170
|
||||
|
||||
- Version bump to 0.13.0-b5 "Toki"
|
||||
- Improv Serial support (PR #2334)
|
||||
- Button improvements (PR #2284)
|
||||
- Added two time zones (PR #2264, 2311)
|
||||
- JSON in/decrementing support for brightness and presets
|
||||
- Fixed no gamma correction for JSON individual LED control
|
||||
- Preset cycle bugfix
|
||||
- Removed ledCount
|
||||
- LED settings buffer bugfix
|
||||
- Network pin conflict bugfix
|
||||
- Changed default ESP32 partition layout to 4M, 1M FS
|
||||
|
||||
#### Build 2110110
|
||||
|
||||
- Version bump to 0.13.0-b4 "Toki"
|
||||
- Added option for bus refresh if off (PR #2259)
|
||||
- New auto segment logic
|
||||
- Fixed current calculations for virtual or non-linear configs (PR #2262)
|
||||
|
||||
#### Build 2110060
|
||||
|
||||
- Added virtual network DDP busses (PR #2245)
|
||||
- Allow playlist as end preset in playlist
|
||||
- Improved bus start field UX
|
||||
- Pin reservations improvements (PR #2214)
|
||||
|
||||
#### Build 2109220
|
||||
|
||||
@@ -360,6 +540,7 @@
|
||||
- Added support for WESP32 ethernet board (PR #1764)
|
||||
- Added Caching for main UI (PR #1704)
|
||||
- Added Tetrix mode (PR #1729)
|
||||
- Removed Merry Christmas mode (use "Chase 2" - called Running 2 before 0.13.0)
|
||||
- Added memory check on Bus creation
|
||||
|
||||
#### Build 2102050
|
||||
|
||||
78
CONTRIBUTING.md
Normal file
@@ -0,0 +1,78 @@
|
||||
## Thank you for making WLED better!
|
||||
|
||||
Here are a few suggestions to make it easier for you to contribute!
|
||||
|
||||
### Code style
|
||||
|
||||
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
|
||||
Below are the guidelines we use in the WLED repository.
|
||||
|
||||
#### Indentation
|
||||
|
||||
We use tabs for Indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files.
|
||||
You are all set if you have enabled `Editor: Detect Indentation` in VS Code.
|
||||
|
||||
#### Blocks
|
||||
|
||||
Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable.
|
||||
|
||||
Good:
|
||||
```cpp
|
||||
if (a == b) {
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
if (a == b)
|
||||
{
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
if (a == b) doStuff(a);
|
||||
```
|
||||
|
||||
There should always be a space between a keyword and its condition and between the condition and brace.
|
||||
Within the condition, no space should be between the paranthesis and variables.
|
||||
Spaces between variables and operators are up to the authors discretion.
|
||||
There should be no space between function names and their argument parenthesis.
|
||||
|
||||
Good:
|
||||
```cpp
|
||||
if (a == b) {
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
Not good:
|
||||
```cpp
|
||||
if( a==b ){
|
||||
doStuff ( a);
|
||||
}
|
||||
```
|
||||
|
||||
#### Comments
|
||||
|
||||
Comments should have a space between the delimiting characters (e.g. `//`) and the comment text.
|
||||
Note: This is a recent change, the majority of the codebase still has comments without spaces.
|
||||
|
||||
Good:
|
||||
```
|
||||
// This is a comment.
|
||||
|
||||
/* This is a CSS inline comment */
|
||||
|
||||
/*
|
||||
* This is a comment
|
||||
* wrapping over multiple lines,
|
||||
* used in WLED for file headers and function explanations
|
||||
*/
|
||||
|
||||
<!-- This is an HTML comment -->
|
||||
```
|
||||
|
||||
There is no set character limit for a comment within a line,
|
||||
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
|
||||
Inline comments are OK if they describe that line only and are not exceedingly wide.
|
||||
20
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.0-b3",
|
||||
"version": "0.13.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -28,9 +28,9 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -1339,9 +1339,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
@@ -2067,9 +2067,9 @@
|
||||
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
|
||||
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.0-b3",
|
||||
"version": "0.13.3",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
154
platformio.ini
@@ -12,14 +12,15 @@
|
||||
; default_envs = travis_esp8266, travis_esp32
|
||||
|
||||
# Release binaries
|
||||
default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3
|
||||
|
||||
# Build everything
|
||||
; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
|
||||
; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
|
||||
|
||||
# Single binaries (uncomment your board)
|
||||
; default_envs = elekstube_ips
|
||||
; default_envs = nodemcuv2
|
||||
; default_envs = esp8266_2m
|
||||
; default_envs = esp01_1m_full
|
||||
; default_envs = esp07
|
||||
; default_envs = d1_mini
|
||||
@@ -29,12 +30,14 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth
|
||||
; default_envs = d1_mini_ota
|
||||
; default_envs = esp32dev
|
||||
; default_envs = esp8285_4CH_MagicHome
|
||||
; default_envs = esp8285_4CH_H801
|
||||
; default_envs = esp8285_5CH_H801
|
||||
; default_envs = esp8285_H801
|
||||
; default_envs = d1_mini_5CH_Shojo_PCB
|
||||
; default_envs = wemos_shield_esp32
|
||||
; default_envs = m5atom
|
||||
; default_envs = esp32_eth
|
||||
; default_envs = esp32dev_qio80
|
||||
; default_envs = esp32_eth_ota1mapp
|
||||
; default_envs = esp32s2_saola
|
||||
|
||||
src_dir = ./wled00
|
||||
data_dir = ./wled00/data
|
||||
@@ -53,6 +56,7 @@ extra_configs =
|
||||
arduino_core_2_6_3 = espressif8266@2.3.3
|
||||
arduino_core_2_7_4 = espressif8266@2.6.2
|
||||
arduino_core_3_0_0 = espressif8266@3.0.0
|
||||
arduino_core_3_0_2 = espressif8266@3.2.0
|
||||
|
||||
# Development platforms
|
||||
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
|
||||
@@ -76,10 +80,8 @@ debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld)
|
||||
# ldscript_1m0m (1024 KB) = 999 KB sketch, 4 KB eeprom, no spiffs, 16 KB reserved
|
||||
# ldscript_2m1m (2048 KB) = 1019 KB sketch, 4 KB eeprom, 1004 KB spiffs, 16 KB reserved
|
||||
# ldscript_4m1m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 1002 KB spiffs, 16 KB reserved, 2048 KB empty/ota?
|
||||
# ldscript_4m3m (4096 KB) = 1019 KB sketch, 4 KB eeprom, 3040 KB spiffs, 16 KB reserved
|
||||
#
|
||||
# Available lwIP variants (macros):
|
||||
# -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH = v1.4 Higher Bandwidth (default)
|
||||
@@ -159,10 +161,9 @@ upload_speed = 115200
|
||||
# ------------------------------------------------------------------------------
|
||||
lib_compat_mode = strict
|
||||
lib_deps =
|
||||
fastled/FastLED @ 3.4.0
|
||||
IRremoteESP8266 @ 2.7.18
|
||||
https://github.com/lorol/LITTLEFS.git
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.2
|
||||
fastled/FastLED @ 3.5.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4
|
||||
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
|
||||
#TFT_eSPI
|
||||
#For use SSD1306 OLED display uncomment following
|
||||
@@ -176,14 +177,15 @@ lib_deps =
|
||||
; adafruit/Adafruit CCS811 Library @ 1.0.4
|
||||
; adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||
|
||||
extra_scripts = ${scripts_defaults.extra_scripts}
|
||||
extra_scripts = ${scripts_defaults.extra_scripts}
|
||||
|
||||
[esp8266]
|
||||
build_flags =
|
||||
-DESP8266
|
||||
-DFP_IN_IROM
|
||||
;-Wno-deprecated-declarations
|
||||
;-Wno-register
|
||||
-Wno-register
|
||||
-Wno-misleading-indentation
|
||||
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
|
||||
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
|
||||
; lwIP 2 - Higher Bandwidth no Features
|
||||
@@ -197,25 +199,35 @@ build_flags =
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
# ESPAsyncTCP @ 1.2.0
|
||||
#https://github.com/lorol/LITTLEFS.git
|
||||
ESPAsyncTCP @ 1.2.2
|
||||
ESPAsyncUDP
|
||||
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
|
||||
[esp32]
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
|
||||
platform = espressif32@3.5.0
|
||||
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
|
||||
|
||||
build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
||||
-D LOROL_LITTLEFS
|
||||
|
||||
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
makuna/NeoPixelBus @ 2.6.7
|
||||
https://github.com/lorol/LITTLEFS.git
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
[esp32s2]
|
||||
build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
-DARDUINO_ARCH_ESP32S2
|
||||
-DCONFIG_IDF_TARGET_ESP32S2
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
@@ -223,7 +235,20 @@ build_flags = -g
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
makuna/NeoPixelBus @ 2.6.7
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
[esp32c3]
|
||||
build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_ARCH_ESP32C3
|
||||
-DCONFIG_IDF_TARGET_ESP32C3
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-DCO
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -239,6 +264,15 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp8266_2m]
|
||||
board = esp_wroom_02
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp01_1m_full]
|
||||
board = esp01_1m
|
||||
platform = ${common.platform_wled_default}
|
||||
@@ -288,31 +322,56 @@ lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp32dev]
|
||||
board = esp32dev
|
||||
platform = espressif32@2.0
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:esp32dev_qio80]
|
||||
board = esp32dev
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 #-D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
|
||||
[env:esp32_eth]
|
||||
board = esp32-poe
|
||||
platform = espressif32@2.0
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
upload_speed = 921600
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:esp32s2_saola]
|
||||
board = esp32dev
|
||||
board_build.mcu = esp32s2
|
||||
platform = espressif32
|
||||
board = esp32-s2-saola-1
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
platform_packages =
|
||||
framework = arduino
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
board_build.flash_mode = qio
|
||||
upload_speed = 460800
|
||||
build_unflags = ${common.build_unflags}
|
||||
lib_deps = ${esp32s2.lib_deps}
|
||||
|
||||
[env:esp32c3]
|
||||
board = esp32-c3-devkitm-1
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
platform_packages =
|
||||
toolchain-xtensa32s2
|
||||
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.0-alpha1
|
||||
framework = arduino
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
upload_speed = 460800
|
||||
build_unflags = ${common.build_unflags}
|
||||
lib_deps = ${esp32s2.lib_deps}
|
||||
lib_deps = ${esp32c3.lib_deps}
|
||||
|
||||
[env:esp8285_4CH_MagicHome]
|
||||
board = esp8285
|
||||
@@ -323,16 +382,7 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp8285_4CH_H801]
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp8285_5CH_H801]
|
||||
[env:esp8285_H801]
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
@@ -406,6 +456,7 @@ build_flags = ${common.build_flags_esp32}
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
OneWire@~2.3.5
|
||||
olikraus/U8g2 @ ^2.28.8
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:m5atom]
|
||||
board = esp32dev
|
||||
@@ -413,6 +464,7 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
platform = espressif32@3.2
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:sp501e]
|
||||
board = esp_wroom_02
|
||||
@@ -428,6 +480,29 @@ board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:athom7w]
|
||||
board = esp_wroom_02
|
||||
platform = ${common.platform_wled_default}
|
||||
board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:athom15w]
|
||||
board = esp_wroom_02
|
||||
platform = ${common.platform_wled_default}
|
||||
board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_USE_IC_CCT -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:MY9291]
|
||||
board = esp01_1m
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# travis test board configurations
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -480,7 +555,7 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_D
|
||||
-D LEDPIN=12
|
||||
-D RLYPIN=27
|
||||
-D BTNPIN=34
|
||||
-D WLED_DISABLE_INFRARED
|
||||
-D WLED_DISABLE_BLYNK
|
||||
-D DEFAULT_LED_COUNT=6
|
||||
# Display config
|
||||
-D ST7789_DRIVER
|
||||
@@ -498,3 +573,4 @@ monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
${esp32.lib_deps}
|
||||
TFT_eSPI @ ^2.3.70
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
@@ -19,10 +19,14 @@ build_flags = ${common.build_flags_esp8266}
|
||||
; *** Use custom settings from file my_config.h
|
||||
-DWLED_USE_MY_CONFIG
|
||||
; *********************************************************************
|
||||
;
|
||||
;
|
||||
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
|
||||
;
|
||||
; disable specific features
|
||||
; -D WLED_DISABLE_OTA
|
||||
; -D WLED_DISABLE_ALEXA
|
||||
; -D WLED_DISABLE_BLYNK
|
||||
; -D WLED_DISABLE_CRONIXIE
|
||||
; -D WLED_DISABLE_HUESYNC
|
||||
; -D WLED_DISABLE_INFRARED
|
||||
; -D WLED_DISABLE_WEBSOCKETS
|
||||
@@ -45,3 +49,18 @@ build_flags = ${common.build_flags_esp8266}
|
||||
; for the Magic Home LED Controller use PWM pins 5,12,13,15
|
||||
; for the H801 controller use PINs 15,13,12,14 (W2 = 04)
|
||||
; for the BW-LT11 controller use PINs 12,4,14,5
|
||||
;
|
||||
; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name
|
||||
; -D SERVERNAME="\"WLED\""
|
||||
;
|
||||
; set the number of LEDs
|
||||
; -D DEFAULT_LED_COUNT=30
|
||||
;
|
||||
; set milliampere limit when using ESP pin to power leds
|
||||
; -D ABL_MILLIAMPS_DEFAULT=850
|
||||
;
|
||||
; enable IR by setting remote type
|
||||
; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
|
||||
;
|
||||
; set default color order of your led strip
|
||||
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
|
||||
|
||||
35
readme.md
@@ -27,7 +27,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
|
||||
- Presets can be used to automatically execute API calls
|
||||
- Nightlight function (gradually dims down)
|
||||
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
|
||||
- Configurable analog clock + support for the Cronixie kit by Diamex
|
||||
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
|
||||
- Configurable Auto Brightness limit for safer operation
|
||||
- Filesystem-based config for easier backup of presets and settings
|
||||
|
||||
@@ -49,42 +49,21 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
|
||||
|
||||
## 📲 Quick start guide and documentation
|
||||
|
||||
See the [wiki](https://github.com/Aircoookie/WLED/wiki)!
|
||||
See the [documentation on our official site](https://kno.wled.ge)!
|
||||
|
||||
[On this page](https://github.com/Aircoookie/WLED/wiki/Learning-the-ropes) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
|
||||
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
|
||||
|
||||
## 🖼️ Images
|
||||
## 🖼️ User interface
|
||||
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
|
||||
|
||||
## 💾 Compatible LED Strips
|
||||
Type | Voltage | Comments
|
||||
|---|---|---|
|
||||
WS2812B | 5v |
|
||||
WS2813 | 5v |
|
||||
SK6812 | 5v | RGBW
|
||||
APA102 | 5v | C/D
|
||||
WS2801 | 5v | C/D
|
||||
LPD8806 | 5v | C/D
|
||||
TM1814 | 12v | RGBW
|
||||
WS2811 | 12v | 3-LED segments
|
||||
WS2815 | 12v |
|
||||
GS8208 | 12v |
|
||||
Analog/non-addressable | any | Requires additional circuitry
|
||||
|
||||
## 🧊 Compatible PC RGB Fans and ARGB accessories
|
||||
Brand | Model | Comments
|
||||
|---|---|---|
|
||||
Corsair | HD120 Fan | Uses WS2812B, data-in only
|
||||
PCCOOLER | Moonlight 5-pack Fans | Uses WS2812B, includes Data-out connector to keep each fan uniquely addressable if wired in series like traditional LED strips
|
||||
Any | 5v 3-pin ARGB for PC | Any PC RGB device that supports the 5v 3-pin ARGB motherboard header should work fine with WLED. All the major motherboard vendors support the Corsair HD120 and PCCOOLER fans listed, so we can safely assume any device that supports motherboard ARGB 5V 3-Pin standard will work with WLED.
|
||||
## 💾 Compatible hardware
|
||||
|
||||
See [here](https://kno.wled.ge/basics/compatible-hardware)!
|
||||
|
||||
## ✌️ Other
|
||||
|
||||
Licensed under the MIT license
|
||||
Credits [here](https://github.com/Aircoookie/WLED/wiki/Contributors-&-About)!
|
||||
|
||||
Uses Linearicons by Perxis!
|
||||
Credits [here](https://kno.wled.ge/about/contributors/)!
|
||||
|
||||
Join the Discord server to discuss everything about WLED!
|
||||
|
||||
|
||||
@@ -1,54 +1,70 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# This file is autogenerated by pip-compile with python 3.8
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile
|
||||
#
|
||||
aiofiles==0.6.0
|
||||
aiofiles==0.8.0
|
||||
# via platformio
|
||||
ajsonrpc==1.1.0
|
||||
ajsonrpc==1.2.0
|
||||
# via platformio
|
||||
bottle==0.12.19
|
||||
anyio==3.6.1
|
||||
# via starlette
|
||||
async-timeout==4.0.2
|
||||
# via zeroconf
|
||||
bottle==0.12.23
|
||||
# via platformio
|
||||
certifi==2020.12.5
|
||||
certifi==2022.6.15
|
||||
# via requests
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.1.1
|
||||
# via requests
|
||||
click==7.1.2
|
||||
click==8.1.3
|
||||
# via
|
||||
# platformio
|
||||
# uvicorn
|
||||
colorama==0.4.4
|
||||
# via platformio
|
||||
h11==0.12.0
|
||||
colorama==0.4.5
|
||||
# via
|
||||
# click
|
||||
# platformio
|
||||
h11==0.13.0
|
||||
# via
|
||||
# uvicorn
|
||||
# wsproto
|
||||
idna==2.10
|
||||
# via requests
|
||||
ifaddr==0.1.7
|
||||
idna==3.3
|
||||
# via
|
||||
# anyio
|
||||
# requests
|
||||
ifaddr==0.2.0
|
||||
# via zeroconf
|
||||
marshmallow==3.11.1
|
||||
marshmallow==3.17.0
|
||||
# via platformio
|
||||
platformio==5.1.1
|
||||
packaging==21.3
|
||||
# via marshmallow
|
||||
platformio==6.1.4
|
||||
# via -r requirements.in
|
||||
pyelftools==0.27
|
||||
pyelftools==0.29
|
||||
# via platformio
|
||||
pyparsing==3.0.9
|
||||
# via packaging
|
||||
pyserial==3.5
|
||||
# via platformio
|
||||
requests==2.25.1
|
||||
requests==2.28.1
|
||||
# via platformio
|
||||
semantic-version==2.8.5
|
||||
semantic-version==2.10.0
|
||||
# via platformio
|
||||
starlette==0.14.2
|
||||
sniffio==1.2.0
|
||||
# via anyio
|
||||
starlette==0.20.4
|
||||
# via platformio
|
||||
tabulate==0.8.9
|
||||
tabulate==0.8.10
|
||||
# via platformio
|
||||
urllib3==1.26.5
|
||||
typing-extensions==4.3.0
|
||||
# via starlette
|
||||
urllib3==1.26.11
|
||||
# via requests
|
||||
uvicorn==0.13.4
|
||||
uvicorn==0.18.2
|
||||
# via platformio
|
||||
wsproto==1.0.0
|
||||
wsproto==1.1.0
|
||||
# via platformio
|
||||
zeroconf==0.28.8
|
||||
zeroconf==0.39.0
|
||||
# via platformio
|
||||
|
||||
@@ -90,7 +90,7 @@ function writeHtmlGzipped(sourceFile, resultFile) {
|
||||
* Binary array for the Web UI.
|
||||
* gzip is used for smaller size and improved speeds.
|
||||
*
|
||||
* Please see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui
|
||||
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
||||
* to find out how to easily modify the web UI source!
|
||||
*/
|
||||
|
||||
@@ -175,7 +175,7 @@ function writeChunks(srcDir, specs, resultFile) {
|
||||
let src = `/*
|
||||
* More web UI HTML source arrays.
|
||||
* This file is auto generated, please don't make any changes manually.
|
||||
* Instead, see https://github.com/Aircoookie/WLED/wiki/Add-own-functionality#web-ui
|
||||
* Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
||||
* to find out how to easily modify the web UI source!
|
||||
*/
|
||||
`;
|
||||
@@ -217,7 +217,7 @@ writeChunks(
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace("%", "%%")
|
||||
.replace(/User Interface\<\/button\>\<\/form\>/gms, "User Interface\<\/button\>\<\/form\>%DMXMENU%"),
|
||||
.replace(/Usermods\<\/button\>\<\/form\>/gms, "Usermods\<\/button\>\<\/form\>%DMXMENU%"),
|
||||
},
|
||||
{
|
||||
file: "settings_wifi.htm",
|
||||
|
||||
@@ -111,17 +111,17 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
|
||||
if (i >= onIndex && i < offIndex) {
|
||||
segments->setOption(SEG_OPTION_ON, 1, 1);
|
||||
segments->setOption(SEG_OPTION_ON, 1, i);
|
||||
|
||||
// We may need to copy mode and colors from segment 0 to make sure
|
||||
// changes are propagated even when the config is changed during a wipe
|
||||
// segments->mode = mainsegment.mode;
|
||||
// segments->colors[0] = mainsegment.colors[0];
|
||||
} else {
|
||||
segments->setOption(SEG_OPTION_ON, 0, 1);
|
||||
segments->setOption(SEG_OPTION_ON, 0, i);
|
||||
}
|
||||
// Always mark segments as "transitional", we are animating the staircase
|
||||
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, 1);
|
||||
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, i);
|
||||
}
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
}
|
||||
@@ -296,7 +296,7 @@ class Animated_Staircase : public Usermod {
|
||||
maxSegmentId = i - 1;
|
||||
break;
|
||||
}
|
||||
segments->setOption(SEG_OPTION_ON, 1, 1);
|
||||
segments->setOption(SEG_OPTION_ON, 1, i);
|
||||
}
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||
@@ -508,10 +508,10 @@ class Animated_Staircase : public Usermod {
|
||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase")); // name
|
||||
String btn = F("<button class=\"btn infobtn\" onclick=\"requestJson({staircase:{enabled:");
|
||||
if (enabled) {
|
||||
btn += F("false}},false,false);loadInfo();\">");
|
||||
btn += F("false}});\">");
|
||||
btn += F("enabled");
|
||||
} else {
|
||||
btn += F("true}},false,false);loadInfo();\">");
|
||||
btn += F("true}});\">");
|
||||
btn += F("disabled");
|
||||
}
|
||||
btn += F("</button>");
|
||||
|
||||
@@ -24,7 +24,7 @@ void RGBNET_readValues() {
|
||||
int channel = UDP.read();
|
||||
|
||||
//channel data is not used we only supports one channel
|
||||
int len = UDP.read(RGBNET_packet, ledCount*3);
|
||||
int len = UDP.read(RGBNET_packet, strip.getLengthTotal()*3);
|
||||
if(len==0){
|
||||
return;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ void handleConfig(AsyncWebServerRequest *request)
|
||||
\"channels\": [\
|
||||
{\
|
||||
\"channel\": 1,\
|
||||
\"leds\": " + ledCount + "\
|
||||
\"leds\": " + strip.getLengthTotal() + "\
|
||||
},\
|
||||
{\
|
||||
\"channel\": 2,\
|
||||
|
||||
16
usermods/BH1750_v2/platformio_override.ini
Normal file
@@ -0,0 +1,16 @@
|
||||
; Options
|
||||
; -------
|
||||
; USERMOD_BH1750 - define this to have this user mod included wled00\usermods_list.cpp
|
||||
; USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
; USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL - the min number of milliseconds between measurements, defaults to 500ms
|
||||
; USERMOD_BH1750_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
; USERMOD_BH1750_OFFSET_VALUE - the offset value to report on, defaults to 1
|
||||
;
|
||||
[env:usermod_BH1750_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BH1750
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
claws/BH1750 @ ^1.2.0
|
||||
24
usermods/BH1750_v2/readme.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# BH1750 usermod
|
||||
|
||||
This usermod will read from an ambient light sensor like the BH1750 sensor.
|
||||
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
|
||||
|
||||
## Installation
|
||||
|
||||
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms
|
||||
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:usermod_BH1750_d1_mini`.
|
||||
|
||||
## Change Log
|
||||
177
usermods/BH1750_v2/usermod_bh1750.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include <Wire.h>
|
||||
#include <BH1750.h>
|
||||
|
||||
// the max frequency to check photoresistor, 10 seconds
|
||||
#ifndef USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL 10000
|
||||
#endif
|
||||
|
||||
// the min frequency to check photoresistor, 500 ms
|
||||
#ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500
|
||||
#endif
|
||||
|
||||
// how many seconds after boot to take first measurement, 10 seconds
|
||||
#ifndef USERMOD_BH1750_FIRST_MEASUREMENT_AT
|
||||
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
|
||||
#endif
|
||||
|
||||
// only report if differance grater than offset value
|
||||
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
||||
#define USERMOD_BH1750_OFFSET_VALUE 1
|
||||
#endif
|
||||
|
||||
class Usermod_BH1750 : public Usermod
|
||||
{
|
||||
private:
|
||||
int8_t offset = USERMOD_BH1750_OFFSET_VALUE;
|
||||
|
||||
unsigned long maxReadingInterval = USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL;
|
||||
unsigned long minReadingInterval = USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL;
|
||||
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
|
||||
unsigned long lastSend = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
|
||||
// flag to indicate we have finished the first readLightLevel call
|
||||
// allows this library to report to the user how long until the first
|
||||
// measurement
|
||||
bool getLuminanceComplete = false;
|
||||
|
||||
// flag set at startup
|
||||
bool disabled = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _maxReadInterval[];
|
||||
static const char _minReadInterval[];
|
||||
static const char _offset[];
|
||||
|
||||
BH1750 lightMeter;
|
||||
float lastLux = -1000;
|
||||
|
||||
bool checkBoundSensor(float newValue, float prevValue, float maxDiff)
|
||||
{
|
||||
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
Wire.begin();
|
||||
lightMeter.begin();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (disabled || strip.isUpdating())
|
||||
return;
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// check to see if we are due for taking a measurement
|
||||
// lastMeasurement will not be updated until the conversion
|
||||
// is complete the the reading is finished
|
||||
if (now - lastMeasurement < minReadingInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldUpdate = now - lastSend > maxReadingInterval;
|
||||
|
||||
float lux = lightMeter.readLightLevel();
|
||||
lastMeasurement = millis();
|
||||
getLuminanceComplete = true;
|
||||
|
||||
if (shouldUpdate || checkBoundSensor(lux, lastLux, offset))
|
||||
{
|
||||
lastLux = lux;
|
||||
lastSend = millis();
|
||||
if (WLED_MQTT_CONNECTED)
|
||||
{
|
||||
char subuf[45];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/luminance"));
|
||||
mqtt->publish(subuf, 0, true, String(lux).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull())
|
||||
user = root.createNestedObject(F("u"));
|
||||
|
||||
JsonArray lux_json = user.createNestedArray(F("Luminance"));
|
||||
|
||||
if (!getLuminanceComplete)
|
||||
{
|
||||
// if we haven't read the sensor yet, let the user know
|
||||
// that we are still waiting for the first measurement
|
||||
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||
lux_json.add(F(" sec until read"));
|
||||
return;
|
||||
}
|
||||
|
||||
lux_json.add(lastLux);
|
||||
lux_json.add(F(" lx"));
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_BH1750;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
// we add JSON object.
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = !disabled;
|
||||
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||
top[FPSTR(_offset)] = offset;
|
||||
|
||||
DEBUG_PRINTLN(F("Photoresistor config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
// we look for JSON object.
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull())
|
||||
{
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
disabled = !(top[FPSTR(_enabled)] | !disabled);
|
||||
maxReadingInterval = (top[FPSTR(_maxReadInterval)] | maxReadingInterval); // ms
|
||||
minReadingInterval = (top[FPSTR(_minReadInterval)] | minReadingInterval); // ms
|
||||
offset = top[FPSTR(_offset)] | offset;
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char Usermod_BH1750::_name[] PROGMEM = "BH1750";
|
||||
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
|
||||
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
||||
const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms";
|
||||
const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx";
|
||||
14
usermods/BH1750_v2/usermods_list.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "wled.h"
|
||||
/*
|
||||
* Register your v2 usermods here!
|
||||
*/
|
||||
#ifdef USERMOD_BH1750
|
||||
#include "../usermods/BH1750_v2/usermod_BH1750.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
#ifdef USERMOD_BH1750
|
||||
usermods.add(new Usermod_BH1750());
|
||||
#endif
|
||||
}
|
||||
@@ -1,40 +1,90 @@
|
||||
Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.
|
||||
# Usermod BME280
|
||||
This Usermod is designed to read a `BME280` or `BMP280` sensor and output the following:
|
||||
- Temperature
|
||||
- Humidity (`BME280` only)
|
||||
- Pressure
|
||||
- Heat Index (`BME280` only)
|
||||
- Dew Point (`BME280` only)
|
||||
|
||||
- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||
- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
|
||||
Configuration is all completed via the Usermod menu. There are no settings to set in code! The following settings can be configured in the Usermod Menu:
|
||||
- Temperature Decimals (number of decimal places to output)
|
||||
- Humidity Decimals
|
||||
- Pressure Decimals
|
||||
- Temperature Interval (how many seconds between reads of temperature and humidity)
|
||||
- Pressure Interval
|
||||
- Publish Always (turn off to only publish changes, on to publish whether or not value changed)
|
||||
- Use Celsius (turn off to use Farenheit)
|
||||
- Home Assistant Discovery (turn on to sent MQTT Discovery entries for Home Assistant)
|
||||
- SCL/SDA GPIO Pins
|
||||
|
||||
Dependencies
|
||||
- Libraries
|
||||
- `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280))
|
||||
- `Wire`
|
||||
- These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||
- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages!
|
||||
|
||||
To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`)
|
||||
In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface.
|
||||
|
||||
Methods also exist to read the read/calculated values from other WLED modules through code.
|
||||
- `getTemperatureC()`
|
||||
- `getTemperatureF()`
|
||||
- `getHumidity()`
|
||||
- `getPressure()`
|
||||
- `getDewPointC()`
|
||||
- `getDewPointF()`
|
||||
- `getHeatIndexC()`
|
||||
- `getHeatIndexF()`
|
||||
|
||||
# Complilation
|
||||
|
||||
To enable, compile with `USERMOD_BME280` defined (e.g. in `platformio_override.ini`)
|
||||
```ini
|
||||
[env:usermod_bme280_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BME280
|
||||
```
|
||||
or define `USERMOD_BME280` in `my_config.h`
|
||||
```c++
|
||||
#define USERMOD_BME280
|
||||
lib_deps =
|
||||
${esp8266.lib_deps}
|
||||
BME280@~3.0.0
|
||||
Wire
|
||||
```
|
||||
|
||||
Changes include:
|
||||
- Adjustable measure intervals
|
||||
- Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude
|
||||
- Adjustment of number of decimal places in published sensor values
|
||||
- Separate adjustment for temperature, humidity and pressure values
|
||||
- Values are rounded to the specified number of decimal places
|
||||
- Pressure measured in units of hPa instead of Pa
|
||||
- Calculation of heat index (apparent temperature) and dew point
|
||||
- These, along with humidity measurements, are disabled if the sensor is a BMP280
|
||||
- 16x oversampling of sensor during measurement
|
||||
- Values are only published if they are different from the previous value
|
||||
- Values are published on startup (continually until the MQTT broker acknowledges a successful publication)
|
||||
|
||||
Adjustments are made through preprocessor definitions at the start of the class definition.
|
||||
|
||||
MQTT topics are as follows:
|
||||
# MQTT
|
||||
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
|
||||
Measurement type | MQTT topic
|
||||
--- | ---
|
||||
Temperature | `<deviceTopic>/temperature`
|
||||
Humidity | `<deviceTopic>/humidity`
|
||||
Pressure | `<deviceTopic>/pressure`
|
||||
Heat index | `<deviceTopic>/heat_index`
|
||||
Dew point | `<deviceTopic>/dew_point`
|
||||
Dew point | `<deviceTopic>/dew_point`
|
||||
|
||||
If you are using Home Assistant, and `Home Assistant Discovery` is turned on, Home Assistant should automatically detect a new device, provided you have the MQTT integration installed. The device is seperate from the main WLED device and will contain sensors for Pressure, Humidity, Temperature, Dew Point and Heat Index.
|
||||
|
||||
# Revision History
|
||||
Jul 2022
|
||||
- Added Home Assistant Discovery
|
||||
- Added API interface to output data
|
||||
- Removed compile-time variables
|
||||
- Added usermod menu interface
|
||||
- Added value outputs to info screen
|
||||
- Updated `readme.md`
|
||||
- Registered usermod
|
||||
- Implemented PinManager for usermod
|
||||
- Implemented reallocation of pins without reboot
|
||||
|
||||
Apr 2021
|
||||
- Added `Publish Always` option
|
||||
|
||||
Dec 2020
|
||||
- Ported to V2 Usermod format
|
||||
- Customisable `measure intervals`
|
||||
- Customisable number of `decimal places` in published sensor values
|
||||
- Pressure measured in units of hPa instead of Pa
|
||||
- Calculation of heat index (apparent temperature) and dew point
|
||||
- `16x oversampling` of sensor during measurement
|
||||
- Values only published if they are different from the previous value
|
||||
@@ -1,3 +1,6 @@
|
||||
// force the compiler to show a warning to confirm that this file is included
|
||||
#warning **** Included USERMOD_BME280 version 2.0 ****
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
@@ -9,43 +12,30 @@
|
||||
class UsermodBME280 : public Usermod
|
||||
{
|
||||
private:
|
||||
// User-defined configuration
|
||||
#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit
|
||||
#define TemperatureDecimals 1 // Number of decimal places in published temperaure values
|
||||
#define HumidityDecimals 2 // Number of decimal places in published humidity values
|
||||
#define PressureDecimals 2 // Number of decimal places in published pressure values
|
||||
#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds
|
||||
#define PressureInterval 300 // Interval to measure pressure in seconds
|
||||
#define PublishAlways 0 // Publish values even when they have not changed
|
||||
|
||||
// NOTE: Do not implement any compile-time variables, anything the user needs to configure
|
||||
// should be configurable from the Usermod menu using the methods below
|
||||
// key settings set via usermod menu
|
||||
unsigned long TemperatureDecimals = 0; // Number of decimal places in published temperaure values
|
||||
unsigned long HumidityDecimals = 0; // Number of decimal places in published humidity values
|
||||
unsigned long PressureDecimals = 0; // Number of decimal places in published pressure values
|
||||
unsigned long TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
|
||||
unsigned long PressureInterval = 300; // Interval to measure pressure in seconds
|
||||
bool PublishAlways = false; // Publish values even when they have not changed
|
||||
bool UseCelsius = true; // Use Celsius for Reporting
|
||||
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
|
||||
|
||||
// Sanity checks
|
||||
#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
|
||||
#define TemperatureDecimals 0
|
||||
#endif
|
||||
#if !defined(HumidityDecimals) || HumidityDecimals < 0
|
||||
#define HumidityDecimals 0
|
||||
#endif
|
||||
#if !defined(PressureDecimals) || PressureDecimals < 0
|
||||
#define PressureDecimals 0
|
||||
#endif
|
||||
#if !defined(TemperatureInterval) || TemperatureInterval < 0
|
||||
#define TemperatureInterval 1
|
||||
#endif
|
||||
#if !defined(PressureInterval) || PressureInterval < 0
|
||||
#define PressureInterval TemperatureInterval
|
||||
#endif
|
||||
#if !defined(PublishAlways)
|
||||
#define PublishAlways 0
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
|
||||
uint8_t SCL_PIN = 22;
|
||||
uint8_t SDA_PIN = 21;
|
||||
#else // ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
// set the default pins based on the architecture, these get overridden by Usermod menu settings
|
||||
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else // ESP8266 boards
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
|
||||
bool initDone = false;
|
||||
|
||||
// BME280 sensor settings
|
||||
BME280I2C::Settings settings{
|
||||
@@ -75,6 +65,7 @@ private:
|
||||
float sensorHeatIndex;
|
||||
float sensorDewPoint;
|
||||
float sensorPressure;
|
||||
String tempScale;
|
||||
// Track previous sensor values
|
||||
float lastTemperature;
|
||||
float lastHumidity;
|
||||
@@ -82,43 +73,122 @@ private:
|
||||
float lastDewPoint;
|
||||
float lastPressure;
|
||||
|
||||
// MQTT topic strings for publishing Home Assistant discovery topics
|
||||
bool mqttInitialized = false;
|
||||
String mqttTemperatureTopic = "";
|
||||
String mqttHumidityTopic = "";
|
||||
String mqttPressureTopic = "";
|
||||
String mqttHeatIndexTopic = "";
|
||||
String mqttDewPointTopic = "";
|
||||
|
||||
// Store packet IDs of MQTT publications
|
||||
uint16_t mqttTemperaturePub = 0;
|
||||
uint16_t mqttPressurePub = 0;
|
||||
|
||||
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
|
||||
void UpdateBME280Data(int SensorType)
|
||||
{
|
||||
float _temperature, _humidity, _pressure;
|
||||
#ifdef Celsius
|
||||
|
||||
if (UseCelsius) {
|
||||
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
||||
#else
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
tempScale = "°C";
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
}
|
||||
} else {
|
||||
BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
|
||||
#endif
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
tempScale = "°F";
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procedure to define all MQTT discovery Topics
|
||||
void _mqttInitialize()
|
||||
{
|
||||
mqttTemperatureTopic = String(mqttDeviceTopic) + F("/temperature");
|
||||
mqttPressureTopic = String(mqttDeviceTopic) + F("/pressure");
|
||||
mqttHumidityTopic = String(mqttDeviceTopic) + F("/humidity");
|
||||
mqttHeatIndexTopic = String(mqttDeviceTopic) + F("/heat_index");
|
||||
mqttDewPointTopic = String(mqttDeviceTopic) + F("/dew_point");
|
||||
|
||||
if (HomeAssistantDiscovery) {
|
||||
_createMqttSensor(F("Temperature"), mqttTemperatureTopic, F("temperature"), tempScale);
|
||||
_createMqttSensor(F("Pressure"), mqttPressureTopic, F("pressure"), F("hPa"));
|
||||
_createMqttSensor(F("Humidity"), mqttHumidityTopic, F("humidity"), F("%"));
|
||||
_createMqttSensor(F("HeatIndex"), mqttHeatIndexTopic, F("temperature"), tempScale);
|
||||
_createMqttSensor(F("DewPoint"), mqttDewPointTopic, F("temperature"), tempScale);
|
||||
}
|
||||
}
|
||||
|
||||
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||
{
|
||||
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||
|
||||
StaticJsonDocument<600> doc;
|
||||
|
||||
doc[F("name")] = String(serverDescription) + " " + name;
|
||||
doc[F("state_topic")] = topic;
|
||||
doc[F("unique_id")] = String(mqttClientID) + name;
|
||||
if (unitOfMeasurement != "")
|
||||
doc[F("unit_of_measurement")] = unitOfMeasurement;
|
||||
if (deviceClass != "")
|
||||
doc[F("device_class")] = deviceClass;
|
||||
doc[F("expire_after")] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||
device[F("manufacturer")] = F("WLED");
|
||||
device[F("model")] = F("FOSS");
|
||||
device[F("sw_version")] = versionString;
|
||||
|
||||
String temp;
|
||||
serializeJson(doc, temp);
|
||||
DEBUG_PRINTLN(t);
|
||||
DEBUG_PRINTLN(temp);
|
||||
|
||||
mqtt->publish(t.c_str(), 0, true, temp.c_str());
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
Wire.begin(SDA_PIN, SCL_PIN);
|
||||
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used
|
||||
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins
|
||||
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
|
||||
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
|
||||
|
||||
Wire.begin(ioPin[1], ioPin[0]);
|
||||
|
||||
if (!bme.begin())
|
||||
{
|
||||
sensorType = 0;
|
||||
Serial.println("Could not find BME280I2C sensor!");
|
||||
DEBUG_PRINTLN(F("Could not find BME280I2C sensor!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -126,24 +196,25 @@ public:
|
||||
{
|
||||
case BME280::ChipModel_BME280:
|
||||
sensorType = 1;
|
||||
Serial.println("Found BME280 sensor! Success.");
|
||||
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
|
||||
break;
|
||||
case BME280::ChipModel_BMP280:
|
||||
sensorType = 2;
|
||||
Serial.println("Found BMP280 sensor! No Humidity available.");
|
||||
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
|
||||
break;
|
||||
default:
|
||||
sensorType = 0;
|
||||
Serial.println("Found UNKNOWN sensor! Error!");
|
||||
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
|
||||
}
|
||||
}
|
||||
initDone=true;
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// BME280 sensor MQTT publishing
|
||||
// Check if sensor present and MQTT Connected, otherwise it will crash the MCU
|
||||
if (sensorType != 0 && mqtt != nullptr)
|
||||
if (sensorType != 0 && WLED_MQTT_CONNECTED)
|
||||
{
|
||||
// Timer to fetch new temperature, humidity and pressure data at intervals
|
||||
timer = millis();
|
||||
@@ -154,9 +225,15 @@ public:
|
||||
|
||||
UpdateBME280Data(sensorType);
|
||||
|
||||
float temperature = roundf(sensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
float temperature = roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
float humidity, heatIndex, dewPoint;
|
||||
|
||||
if (WLED_MQTT_CONNECTED && !mqttInitialized)
|
||||
{
|
||||
_mqttInitialize();
|
||||
mqttInitialized = true;
|
||||
}
|
||||
|
||||
// If temperature has changed since last measure, create string populated with device topic
|
||||
// from the UI and values read from sensor, then publish to broker
|
||||
if (temperature != lastTemperature || PublishAlways)
|
||||
@@ -169,25 +246,25 @@ public:
|
||||
|
||||
if (sensorType == 1) // Only if sensor is a BME280
|
||||
{
|
||||
humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
|
||||
heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
humidity = roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals);
|
||||
heatIndex = roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
dewPoint = roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
|
||||
if (humidity != lastHumidity || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/humidity";
|
||||
String topic = String(mqttDeviceTopic) + F("/humidity");
|
||||
mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str());
|
||||
}
|
||||
|
||||
if (heatIndex != lastHeatIndex || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/heat_index";
|
||||
String topic = String(mqttDeviceTopic) + F("/heat_index");
|
||||
mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str());
|
||||
}
|
||||
|
||||
if (dewPoint != lastDewPoint || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/dew_point";
|
||||
String topic = String(mqttDeviceTopic) + F("/dew_point");
|
||||
mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str());
|
||||
}
|
||||
|
||||
@@ -201,11 +278,11 @@ public:
|
||||
{
|
||||
lastPressureMeasure = timer;
|
||||
|
||||
float pressure = roundf(sensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals);
|
||||
float pressure = roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals);
|
||||
|
||||
if (pressure != lastPressure || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/pressure";
|
||||
String topic = String(mqttDeviceTopic) + F("/pressure");
|
||||
mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str());
|
||||
}
|
||||
|
||||
@@ -213,4 +290,173 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* API calls te enable data exchange between WLED modules
|
||||
*/
|
||||
inline float getTemperatureC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
|
||||
}
|
||||
inline float getTemperatureF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
inline float getHumidity() {
|
||||
return (float)roundf(sensorHumidity * powf(10, HumidityDecimals));
|
||||
}
|
||||
inline float getPressure() {
|
||||
return (float)roundf(sensorPressure * powf(10, PressureDecimals));
|
||||
}
|
||||
inline float getDewPointC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
}
|
||||
inline float getDewPointF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
inline float getHeatIndexC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
}inline float getHeatIndexF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
// Publish Sensor Information to Info Page
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) user = root.createNestedObject(F("u"));
|
||||
|
||||
if (sensorType==0) //No Sensor
|
||||
{
|
||||
// if we sensor not detected, let the user know
|
||||
JsonArray temperature_json = user.createNestedArray(F("BME/BMP280 Sensor"));
|
||||
temperature_json.add(F("Not Found"));
|
||||
}
|
||||
else if (sensorType==2) //BMP280
|
||||
{
|
||||
|
||||
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
|
||||
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
|
||||
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)));
|
||||
temperature_json.add(tempScale);
|
||||
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
|
||||
pressure_json.add(F("hPa"));
|
||||
}
|
||||
else if (sensorType==1) //BME280
|
||||
{
|
||||
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
|
||||
JsonArray humidity_json = user.createNestedArray(F("Humidity"));
|
||||
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
|
||||
JsonArray heatindex_json = user.createNestedArray(F("Heat Index"));
|
||||
JsonArray dewpoint_json = user.createNestedArray(F("Dew Point"));
|
||||
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
temperature_json.add(tempScale);
|
||||
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)));
|
||||
humidity_json.add(F("%"));
|
||||
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
|
||||
pressure_json.add(F("hPa"));
|
||||
heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
heatindex_json.add(tempScale);
|
||||
dewpoint_json.add(roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
dewpoint_json.add(tempScale);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Save Usermod Config Settings
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(F("BME280/BMP280"));
|
||||
top[F("TemperatureDecimals")] = TemperatureDecimals;
|
||||
top[F("HumidityDecimals")] = HumidityDecimals;
|
||||
top[F("PressureDecimals")] = PressureDecimals;
|
||||
top[F("TemperatureInterval")] = TemperatureInterval;
|
||||
top[F("PressureInterval")] = PressureInterval;
|
||||
top[F("PublishAlways")] = PublishAlways;
|
||||
top[F("UseCelsius")] = UseCelsius;
|
||||
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
|
||||
JsonArray io_pin = top.createNestedArray(F("pin"));
|
||||
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
|
||||
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
|
||||
DEBUG_PRINTLN(F("BME280 config saved."));
|
||||
}
|
||||
|
||||
// Read Usermod Config Settings
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
|
||||
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
|
||||
|
||||
JsonObject top = root[F("BME280/BMP280")];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(F("BME280/BMP280"));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
|
||||
configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1);
|
||||
configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0);
|
||||
configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0);
|
||||
configComplete &= getJsonValue(top[F("TemperatureInterval")], TemperatureInterval, 30);
|
||||
configComplete &= getJsonValue(top[F("PressureInterval")], PressureInterval, 30);
|
||||
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
|
||||
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
|
||||
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
|
||||
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
|
||||
|
||||
DEBUG_PRINT(FPSTR(F("BME280/BMP280")));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing parameters from settings page
|
||||
bool pinsChanged = false;
|
||||
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
|
||||
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
|
||||
PinOwner po = PinOwner::UM_BME280;
|
||||
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
setup();
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[F("pin")].isNull();
|
||||
}
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_BME280;
|
||||
}
|
||||
};
|
||||
8
usermods/Cronixie/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Cronixie clock usermod
|
||||
|
||||
This usermod supports driving the Cronixie M and L clock kits by Diamex.
|
||||
|
||||
## Installation
|
||||
|
||||
Compile and upload after adding `-D USERMOD_CRONIXIE` to `build_flags` of your PlatformIO environment.
|
||||
Make sure the Auto Brightness Limiter is enabled at 420mA (!) and configure 60 WS281x LEDs.
|
||||
301
usermods/Cronixie/usermod_cronixie.h
Normal file
@@ -0,0 +1,301 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class UsermodCronixie : public Usermod {
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
char cronixieDisplay[7] = "HHMMSS";
|
||||
byte _digitOut[6] = {10,10,10,10,10,10};
|
||||
byte dP[6] = {255, 255, 255, 255, 255, 255};
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
bool backlight = true;
|
||||
|
||||
public:
|
||||
void initCronixie()
|
||||
{
|
||||
if (dP[0] == 255) // if dP[0] is 255, cronixie is not yet init'ed
|
||||
{
|
||||
setCronixie();
|
||||
strip.getSegment(0).grouping = 10; // 10 LEDs per digit
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!toki.isTick()) return;
|
||||
initCronixie();
|
||||
_overlayCronixie();
|
||||
strip.trigger();
|
||||
}
|
||||
|
||||
byte getSameCodeLength(char code, int index, char const cronixieDisplay[])
|
||||
{
|
||||
byte counter = 0;
|
||||
|
||||
for (int i = index+1; i < 6; i++)
|
||||
{
|
||||
if (cronixieDisplay[i] == code)
|
||||
{
|
||||
counter++;
|
||||
} else {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void setCronixie()
|
||||
{
|
||||
/*
|
||||
* digit purpose index
|
||||
* 0-9 | 0-9 (incl. random)
|
||||
* 10 | blank
|
||||
* 11 | blank, bg off
|
||||
* 12 | test upw.
|
||||
* 13 | test dnw.
|
||||
* 14 | binary AM/PM
|
||||
* 15 | BB upper +50 for no trailing 0
|
||||
* 16 | BBB
|
||||
* 17 | BBBB
|
||||
* 18 | BBBBB
|
||||
* 19 | BBBBBB
|
||||
* 20 | H
|
||||
* 21 | HH
|
||||
* 22 | HHH
|
||||
* 23 | HHHH
|
||||
* 24 | M
|
||||
* 25 | MM
|
||||
* 26 | MMM
|
||||
* 27 | MMMM
|
||||
* 28 | MMMMM
|
||||
* 29 | MMMMMM
|
||||
* 30 | S
|
||||
* 31 | SS
|
||||
* 32 | SSS
|
||||
* 33 | SSSS
|
||||
* 34 | SSSSS
|
||||
* 35 | SSSSSS
|
||||
* 36 | Y
|
||||
* 37 | YY
|
||||
* 38 | YYYY
|
||||
* 39 | I
|
||||
* 40 | II
|
||||
* 41 | W
|
||||
* 42 | WW
|
||||
* 43 | D
|
||||
* 44 | DD
|
||||
* 45 | DDD
|
||||
* 46 | V
|
||||
* 47 | VV
|
||||
* 48 | VVV
|
||||
* 49 | VVVV
|
||||
* 50 | VVVVV
|
||||
* 51 | VVVVVV
|
||||
* 52 | v
|
||||
* 53 | vv
|
||||
* 54 | vvv
|
||||
* 55 | vvvv
|
||||
* 56 | vvvvv
|
||||
* 57 | vvvvvv
|
||||
*/
|
||||
|
||||
//H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year
|
||||
//M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year
|
||||
//S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week
|
||||
//B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5
|
||||
|
||||
//Y YearLower | YY - Year LU | YYYY - Std.
|
||||
//I MonthLower | II - Month of Year
|
||||
//W Week of Month | WW Week of Year
|
||||
//D Day of Week | DD Day Of Month | DDD Day Of Year
|
||||
|
||||
DEBUG_PRINT("cset ");
|
||||
DEBUG_PRINTLN(cronixieDisplay);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
dP[i] = 10;
|
||||
switch (cronixieDisplay[i])
|
||||
{
|
||||
case '_': dP[i] = 10; break;
|
||||
case '-': dP[i] = 11; break;
|
||||
case 'r': dP[i] = random(1,7); break; //random btw. 1-6
|
||||
case 'R': dP[i] = random(0,10); break; //random btw. 0-9
|
||||
//case 't': break; //Test upw.
|
||||
//case 'T': break; //Test dnw.
|
||||
case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break;
|
||||
case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break;
|
||||
case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break;
|
||||
case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break;
|
||||
case 'A': dP[i] = 108; i++; break;
|
||||
case 'a': dP[i] = 58; i++; break;
|
||||
case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break;
|
||||
case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break;
|
||||
case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; break; //refresh more often bc. of secs
|
||||
case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; break;
|
||||
case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break;
|
||||
case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break;
|
||||
case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M.
|
||||
case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break;
|
||||
//case 'W': break;
|
||||
//case 'w': break;
|
||||
case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break;
|
||||
case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break;
|
||||
case '0': dP[i] = 0; break;
|
||||
case '1': dP[i] = 1; break;
|
||||
case '2': dP[i] = 2; break;
|
||||
case '3': dP[i] = 3; break;
|
||||
case '4': dP[i] = 4; break;
|
||||
case '5': dP[i] = 5; break;
|
||||
case '6': dP[i] = 6; break;
|
||||
case '7': dP[i] = 7; break;
|
||||
case '8': dP[i] = 8; break;
|
||||
case '9': dP[i] = 9; break;
|
||||
//case 'V': break; //user var0
|
||||
//case 'v': break; //user var1
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("result ");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
DEBUG_PRINT((int)dP[i]);
|
||||
DEBUG_PRINT(" ");
|
||||
}
|
||||
DEBUG_PRINTLN((int)dP[5]);
|
||||
|
||||
_overlayCronixie(); // refresh
|
||||
}
|
||||
|
||||
void _overlayCronixie()
|
||||
{
|
||||
byte h = hour(localTime);
|
||||
byte h0 = h;
|
||||
byte m = minute(localTime);
|
||||
byte s = second(localTime);
|
||||
byte d = day(localTime);
|
||||
byte mi = month(localTime);
|
||||
int y = year(localTime);
|
||||
//this has to be changed in time for 22nd century
|
||||
y -= 2000; if (y<0) y += 30; //makes countdown work
|
||||
|
||||
if (useAMPM && !countdownMode)
|
||||
{
|
||||
if (h>12) h-=12;
|
||||
else if (h==0) h+=12;
|
||||
}
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (dP[i] < 12) _digitOut[i] = dP[i];
|
||||
else {
|
||||
if (dP[i] < 65)
|
||||
{
|
||||
switch(dP[i])
|
||||
{
|
||||
case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH
|
||||
case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM
|
||||
case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS
|
||||
|
||||
case 20: _digitOut[i] = h- (h/10)*10; break; //H
|
||||
case 24: _digitOut[i] = m/10; break; //M
|
||||
case 30: _digitOut[i] = s/10; break; //S
|
||||
|
||||
case 43: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D
|
||||
case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD
|
||||
case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II
|
||||
case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY
|
||||
case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY
|
||||
|
||||
//case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI)
|
||||
//case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB
|
||||
case 14: _digitOut[i] = (h0>11)?1:0; break; //B
|
||||
}
|
||||
} else
|
||||
{
|
||||
switch(dP[i])
|
||||
{
|
||||
case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh
|
||||
case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm
|
||||
case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss
|
||||
//case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI)
|
||||
//case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb
|
||||
case 64: _digitOut[i] = (h0>11)?1:10; break; //b
|
||||
|
||||
case 93: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d
|
||||
case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd
|
||||
case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii
|
||||
case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy
|
||||
case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4};
|
||||
|
||||
for (uint16_t i = 0; i < 6; i++)
|
||||
{
|
||||
byte o = 10*i;
|
||||
byte excl = 10;
|
||||
if(_digitOut[i] < 10) excl = offsets[_digitOut[i]];
|
||||
excl += o;
|
||||
|
||||
if (backlight && _digitOut[i] <11)
|
||||
{
|
||||
uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]);
|
||||
for (uint16_t j=o; j< o+10; j++) {
|
||||
if (j != excl) strip.setPixelColor(j, col);
|
||||
}
|
||||
} else
|
||||
{
|
||||
for (uint16_t j=o; j< o+10; j++) {
|
||||
if (j != excl) strip.setPixelColor(j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
root["nx"] = cronixieDisplay;
|
||||
}
|
||||
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
if (root["nx"].is<const char*>()) {
|
||||
strncpy(cronixieDisplay, root["nx"], 6);
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(F("Cronixie"));
|
||||
top["backlight"] = backlight;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
JsonObject top = root[F("Cronixie")];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top["backlight"], backlight);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_CRONIXIE;
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
# ESP32 Touch Brightness Control
|
||||
|
||||
Toggle On/Off with a long press (800ms)
|
||||
Switch through 5 brightness levels (defined in usermod_touchbrightness.h, values 0-255) with a short (100ms) touch
|
||||
|
||||
## Installation
|
||||
|
||||
Copy 'usermod_touchbrightness.h' to the wled00 directory.
|
||||
in 'usermod_list.cpp' add this:
|
||||
|
||||
> #include "usermod_touchbrightness.h"
|
||||
above "void registerUsermods()"
|
||||
|
||||
and
|
||||
|
||||
> usermods.add(new TouchBrightnessControl());
|
||||
inside the "registerUsermods()" function
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// usermod_touchbrightness.h
|
||||
// github.com/aircoookie/WLED
|
||||
//
|
||||
// Created by Justin Kühner on 14.09.2020.
|
||||
// Copyright © 2020 NeariX. All rights reserved.
|
||||
// https://github.com/NeariX67/
|
||||
// Discord: @NeariX#4799
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#define threshold 40 //Increase value if touches falsely accur. Decrease value if actual touches are not recognized
|
||||
#define touchPin T0 //T0 = D4 / GPIO4
|
||||
|
||||
//Define the 5 brightness levels
|
||||
//Long press to turn off / on
|
||||
#define brightness1 51
|
||||
#define brightness2 102
|
||||
#define brightness3 153
|
||||
#define brightness4 204
|
||||
#define brightness5 255
|
||||
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
|
||||
class TouchBrightnessControl : public Usermod {
|
||||
private:
|
||||
unsigned long lastTime = 0; //Interval
|
||||
unsigned long lastTouch = 0; //Timestamp of last Touch
|
||||
unsigned long lastRelease = 0; //Timestamp of last Touch release
|
||||
boolean released = true; //current Touch state (touched/released)
|
||||
uint16_t touchReading = 0; //sensor reading, maybe use uint8_t???
|
||||
uint16_t touchDuration = 0; //duration of last touch
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
lastTouch = millis();
|
||||
lastRelease = millis();
|
||||
lastTime = millis();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (millis() - lastTime >= 50) { //Check every 50ms if a touch occurs
|
||||
lastTime = millis();
|
||||
touchReading = touchRead(touchPin); //Read touch sensor on pin T0 (GPIO4 / D4)
|
||||
|
||||
if(touchReading < threshold && released) { //Touch started
|
||||
released = false;
|
||||
lastTouch = millis();
|
||||
}
|
||||
else if(touchReading >= threshold && !released) { //Touch released
|
||||
released = true;
|
||||
lastRelease = millis();
|
||||
touchDuration = lastRelease - lastTouch; //Calculate duration
|
||||
}
|
||||
|
||||
//Serial.println(touchDuration);
|
||||
|
||||
if(touchDuration >= 800 && released) { //Toggle power if button press is longer than 800ms
|
||||
touchDuration = 0; //Reset touch duration to avoid multiple actions on same touch
|
||||
toggleOnOff();
|
||||
colorUpdated(2); //Refresh values
|
||||
}
|
||||
else if(touchDuration >= 100 && released) { //Switch to next brightness if touch is between 100 and 800ms
|
||||
touchDuration = 0; //Reset touch duration to avoid multiple actions on same touch
|
||||
if(bri < brightness1) {
|
||||
bri = brightness1;
|
||||
} else if(bri >= brightness1 && bri < brightness2) {
|
||||
bri = brightness2;
|
||||
} else if(bri >= brightness2 && bri < brightness3) {
|
||||
bri = brightness3;
|
||||
} else if(bri >= brightness3 && bri < brightness4) {
|
||||
bri = brightness4;
|
||||
} else if(bri >= brightness4 && bri < brightness5) {
|
||||
bri = brightness5;
|
||||
} else if(bri >= brightness5) {
|
||||
bri = brightness1;
|
||||
}
|
||||
colorUpdated(2); //Refresh values
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -207,6 +207,17 @@ class MyExampleUsermod : public Usermod {
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
|
||||
@@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI {
|
||||
private:
|
||||
uint8_t digits[NUM_DIGITS];
|
||||
|
||||
|
||||
// These read 16- and 32-bit types from the SD card file.
|
||||
// BMP data is stored little-endian, Arduino is little-endian too.
|
||||
// May need to reverse subscript order if porting elsewhere.
|
||||
@@ -33,7 +34,16 @@ private:
|
||||
}
|
||||
|
||||
uint16_t output_buffer[TFT_HEIGHT][TFT_WIDTH];
|
||||
|
||||
int16_t w = 135, h = 240, x = 0, y = 0, bufferedDigit = 255;
|
||||
uint16_t digitR, digitG, digitB, dimming = 255;
|
||||
uint32_t digitColor = 0;
|
||||
|
||||
void drawBuffer() {
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
pushImage(x, y, w, h, (uint16_t *)output_buffer);
|
||||
setSwapBytes(oldSwapBytes);
|
||||
}
|
||||
|
||||
// These BMP functions are stolen directly from the TFT_SPIFFS_BMP example in the TFT_eSPI library.
|
||||
// Unfortunately, they aren't part of the library itself, so I had to copy them.
|
||||
@@ -41,44 +51,69 @@ private:
|
||||
|
||||
//// BEGIN STOLEN CODE
|
||||
|
||||
// Draw directly from file stored in RGB565 format
|
||||
// Draw directly from file stored in RGB565 format. Fastest
|
||||
bool drawBin(const char *filename) {
|
||||
fs::File bmpFS;
|
||||
|
||||
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print(F("File not found: "));
|
||||
Serial.println(filename);
|
||||
return(false);
|
||||
}
|
||||
|
||||
size_t sz = bmpFS.size();
|
||||
if (sz <= 64800)
|
||||
{
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
|
||||
int16_t h = sz / (135 * 2);
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
int16_t y = (height() - h) /2;
|
||||
|
||||
bmpFS.read((uint8_t *) output_buffer,sz);
|
||||
|
||||
if (!realtimeMode || realtimeOverride) strip.service();
|
||||
|
||||
pushImage(0, y, 135, h, (uint16_t *)output_buffer);
|
||||
|
||||
setSwapBytes(oldSwapBytes);
|
||||
if (sz > 64800) {
|
||||
bmpFS.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t r, g, b, dimming = 255;
|
||||
int16_t row, col;
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
w = 135;
|
||||
h = sz / (w * 2);
|
||||
x = 0;
|
||||
y = (height() - h) /2;
|
||||
|
||||
uint8_t lineBuffer[w * 2];
|
||||
|
||||
if (!realtimeMode || realtimeOverride) strip.service();
|
||||
|
||||
// 0,0 coordinates are top left
|
||||
for (row = 0; row < h; row++) {
|
||||
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t PixM, PixL;
|
||||
|
||||
// Colors are already in 16-bit R5, G6, B5 format
|
||||
for (col = 0; col < w; col++)
|
||||
{
|
||||
if (dimming == 255 && !digitColor) { // not needed, copy directly
|
||||
output_buffer[row][col] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
|
||||
} else {
|
||||
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
|
||||
PixM = lineBuffer[col*2+1];
|
||||
PixL = lineBuffer[col*2];
|
||||
// align to 8-bit value (MSB left aligned)
|
||||
r = (PixM) & 0xF8;
|
||||
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
|
||||
b = (PixL << 3) & 0xF8;
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
|
||||
return(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool drawBmp(const char *filename) {
|
||||
@@ -87,53 +122,52 @@ private:
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print(F("File not found: "));
|
||||
Serial.println(filename);
|
||||
return(false);
|
||||
}
|
||||
|
||||
uint32_t seekOffset;
|
||||
int16_t w, h, row;
|
||||
uint8_t r, g, b;
|
||||
uint32_t seekOffset, headerSize, paletteSize = 0;
|
||||
int16_t row;
|
||||
uint16_t r, g, b, dimming = 255, bitDepth;
|
||||
|
||||
uint16_t magic = read16(bmpFS);
|
||||
if (magic == 0xFFFF) {
|
||||
if (magic != ('B' | ('M' << 8))) { // File not found or not a BMP
|
||||
Serial.println(F("BMP not found!"));
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
}
|
||||
|
||||
if (magic != 0x4D42) {
|
||||
Serial.print(F("File not a BMP. Magic: "));
|
||||
Serial.println(magic);
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
seekOffset = read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
w = read32(bmpFS);
|
||||
h = read32(bmpFS);
|
||||
read32(bmpFS); // filesize in bytes
|
||||
read32(bmpFS); // reserved
|
||||
seekOffset = read32(bmpFS); // start of bitmap
|
||||
headerSize = read32(bmpFS); // header size
|
||||
w = read32(bmpFS); // width
|
||||
h = read32(bmpFS); // height
|
||||
read16(bmpFS); // color planes (must be 1)
|
||||
bitDepth = read16(bmpFS);
|
||||
|
||||
if ((read16(bmpFS) != 1) || (read16(bmpFS) != 24) || (read32(bmpFS) != 0)) {
|
||||
if (read32(bmpFS) != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) {
|
||||
Serial.println(F("BMP format not recognized."));
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
int16_t y = (height() - h) /2;
|
||||
uint32_t palette[256];
|
||||
if (bitDepth <= 8) // 1,4,8 bit bitmap: read color palette
|
||||
{
|
||||
read32(bmpFS); read32(bmpFS); read32(bmpFS); // size, w resolution, h resolution
|
||||
paletteSize = read32(bmpFS);
|
||||
if (paletteSize == 0) paletteSize = bitDepth * bitDepth; //if 0, size is 2^bitDepth
|
||||
bmpFS.seek(14 + headerSize); // start of color palette
|
||||
for (uint16_t i = 0; i < paletteSize; i++) {
|
||||
palette[i] = read32(bmpFS);
|
||||
}
|
||||
}
|
||||
|
||||
// draw img that is shorter than 240pix into the center
|
||||
x = (width() - w) /2;
|
||||
y = (height() - h) /2;
|
||||
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
bmpFS.seek(seekOffset);
|
||||
|
||||
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
||||
uint8_t lineBuffer[w * 3 + padding];
|
||||
uint32_t lineSize = ((bitDepth * w +31) >> 5) * 4;
|
||||
uint8_t lineBuffer[lineSize];
|
||||
|
||||
uint8_t serviceStrip = (!realtimeMode || realtimeOverride) ? 7 : 0;
|
||||
// row is decremented as the BMP image is drawn bottom up
|
||||
@@ -142,23 +176,121 @@ private:
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t* bptr = lineBuffer;
|
||||
|
||||
// Convert 24 to 16 bit colours while copying to output buffer.
|
||||
// Convert 24 to 16 bit colors while copying to output buffer.
|
||||
for (uint16_t col = 0; col < w; col++)
|
||||
{
|
||||
b = *bptr++;
|
||||
g = *bptr++;
|
||||
r = *bptr++;
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
if (bitDepth == 24) {
|
||||
b = *bptr++;
|
||||
g = *bptr++;
|
||||
r = *bptr++;
|
||||
} else {
|
||||
uint32_t c = 0;
|
||||
if (bitDepth == 8) {
|
||||
c = palette[*bptr++];
|
||||
}
|
||||
else if (bitDepth == 4) {
|
||||
c = palette[(*bptr >> ((col & 0x01)?0:4)) & 0x0F];
|
||||
if (col & 0x01) bptr++;
|
||||
}
|
||||
else { // bitDepth == 1
|
||||
c = palette[(*bptr >> (7 - (col & 0x07))) & 0x01];
|
||||
if ((col & 0x07) == 0x07) bptr++;
|
||||
}
|
||||
b = c; g = c >> 8; r = c >> 16;
|
||||
}
|
||||
if (dimming != 255) { // only dimm when needed
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xFF) >> 3);
|
||||
}
|
||||
}
|
||||
|
||||
pushImage(0, y, w, h, (uint16_t *)output_buffer);
|
||||
setSwapBytes(oldSwapBytes);
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
return(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool drawClk(const char *filename) {
|
||||
fs::File bmpFS;
|
||||
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print("File not found: ");
|
||||
Serial.println(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t r, g, b, dimming = 255, magic;
|
||||
int16_t row, col;
|
||||
|
||||
magic = read16(bmpFS);
|
||||
if (magic != 0x4B43) { // look for "CK" header
|
||||
Serial.print(F("File not a CLK. Magic: "));
|
||||
Serial.println(magic);
|
||||
bmpFS.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
w = read16(bmpFS);
|
||||
h = read16(bmpFS);
|
||||
x = (width() - w) / 2;
|
||||
y = (height() - h) / 2;
|
||||
|
||||
uint8_t lineBuffer[w * 2];
|
||||
|
||||
if (!realtimeMode || realtimeOverride) strip.service();
|
||||
|
||||
// 0,0 coordinates are top left
|
||||
for (row = 0; row < h; row++) {
|
||||
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t PixM, PixL;
|
||||
|
||||
// Colors are already in 16-bit R5, G6, B5 format
|
||||
for (col = 0; col < w; col++)
|
||||
{
|
||||
if (dimming == 255 && !digitColor) { // not needed, copy directly
|
||||
output_buffer[row][col+x] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
|
||||
} else {
|
||||
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
|
||||
PixM = lineBuffer[col*2+1];
|
||||
PixL = lineBuffer[col*2];
|
||||
// align to 8-bit value (MSB left aligned)
|
||||
r = (PixM) & 0xF8;
|
||||
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
|
||||
b = (PixL << 3) & 0xF8;
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col+x] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
TFTs() : TFT_eSPI(), chip_select()
|
||||
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
||||
@@ -167,6 +299,9 @@ public:
|
||||
enum show_t { no, yes, force };
|
||||
// A digit of 0xFF means blank the screen.
|
||||
const static uint8_t blanked = 255;
|
||||
|
||||
uint8_t tubeSegment = 1;
|
||||
uint8_t digitOffset = 0;
|
||||
|
||||
void begin() {
|
||||
pinMode(TFT_ENABLE_PIN, OUTPUT);
|
||||
@@ -182,34 +317,60 @@ public:
|
||||
|
||||
void showDigit(uint8_t digit) {
|
||||
chip_select.setDigit(digit);
|
||||
uint8_t digitToDraw = digits[digit];
|
||||
if (digitToDraw < 10) digitToDraw += digitOffset;
|
||||
|
||||
if (digits[digit] == blanked) {
|
||||
fillScreen(TFT_BLACK);
|
||||
if (digitToDraw == blanked) {
|
||||
fillScreen(TFT_BLACK); return;
|
||||
}
|
||||
else {
|
||||
// Filenames are no bigger than "255.bmp\0"
|
||||
char file_name[10];
|
||||
sprintf(file_name, "/%d.bmp", digits[digit]);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
drawBmp(file_name);
|
||||
} else {
|
||||
sprintf(file_name, "/%d.bin", digits[digit]);
|
||||
drawBin(file_name);
|
||||
}
|
||||
|
||||
// if last digit was the same, skip loading from FS to buffer
|
||||
if (!digitColor && digitToDraw == bufferedDigit) drawBuffer();
|
||||
digitR = R(digitColor); digitG = G(digitColor); digitB = B(digitColor);
|
||||
|
||||
// Filenames are no bigger than "254.bmp\0"
|
||||
char file_name[10];
|
||||
// Fastest, raw RGB565
|
||||
sprintf(file_name, "/%d.bin", digitToDraw);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
if (drawBin(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fast, raw RGB565, see https://github.com/aly-fly/EleksTubeHAX on how to create this clk format
|
||||
sprintf(file_name, "/%d.clk", digitToDraw);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
if (drawClk(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
// Slow, regular RGB888 or 1,4,8 bit palette BMP
|
||||
sprintf(file_name, "/%d.bmp", digitToDraw);
|
||||
if (drawBmp(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
|
||||
void setDigit(uint8_t digit, uint8_t value, show_t show=yes) {
|
||||
uint8_t old_value = digits[digit];
|
||||
digits[digit] = value;
|
||||
|
||||
|
||||
// Color in grayscale bitmaps if Segment 1 exists
|
||||
// TODO If secondary and tertiary are black, color all in primary,
|
||||
// else color first three from Seg 1 color slots and last three from Seg 2 color slots
|
||||
WS2812FX::Segment& seg1 = strip.getSegment(tubeSegment);
|
||||
if (seg1.isActive()) {
|
||||
digitColor = strip.getPixelColor(seg1.start + digit);
|
||||
dimming = seg1.opacity;
|
||||
} else {
|
||||
digitColor = 0;
|
||||
dimming = 255;
|
||||
}
|
||||
|
||||
if (show != no && (old_value != value || show == force)) {
|
||||
showDigit(digit);
|
||||
}
|
||||
}
|
||||
uint8_t getDigit(uint8_t digit) { return digits[digit]; }
|
||||
uint8_t getDigit(uint8_t digit) {return digits[digit];}
|
||||
|
||||
void showAllDigits() { for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit); }
|
||||
void showAllDigits() {for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit);}
|
||||
|
||||
// Making chip_select public so we don't have to proxy all methods, and the caller can just use it directly.
|
||||
ChipSelect chip_select;
|
||||
|
||||
@@ -5,16 +5,17 @@ It enables running all WLED effects on the background SK6812 lighting, while dis
|
||||
Code is largely based on https://github.com/SmittyHalibut/EleksTubeHAX by Mark Smith!
|
||||
|
||||
Supported:
|
||||
- Display with custom bitmaps or raw RGB565 images (.bin) from filesystem
|
||||
- Display with custom bitmaps (.bmp) or raw RGB565 images (.bin) from filesystem
|
||||
- Background lighting
|
||||
- Power button
|
||||
- All 4 hardware buttons
|
||||
- RTC (with RTC usermod)
|
||||
- Standard WLED time features (NTP, DST, timezones)
|
||||
|
||||
Not supported:
|
||||
- 3 navigation buttons, on-device setup
|
||||
- On-device setup with buttons (WiFi setup only)
|
||||
|
||||
Your images must be exactly 135 pixels wide and 1-240 pixels high.
|
||||
Your images must be 1-135 pixels wide and 1-240 pixels high.
|
||||
For BMP, 1, 4, 8, and 24 bits per pixel formats are supported.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -25,7 +26,20 @@ Use LED pin 12, relay pin 27 and button pin 34.
|
||||
|
||||
## Use of RGB565 images
|
||||
|
||||
Binary 16-bit per pixel RGB565 format `.bin` images are now supported. This has the benefit of only using 2/3rds of the file size a `.bmp` has.
|
||||
Binary 16-bit per pixel RGB565 format `.bin` and `.clk` images are now supported. This has the benefit of only using 2/3rds of the file size a 24 BPP `.bmp` has.
|
||||
The drawback is that this format cannot be handled by common image programs and that an extra conversion step is needed.
|
||||
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`)
|
||||
Thank you to @RedNax67 for adding .bin support.
|
||||
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`).
|
||||
Thank you to @RedNax67 for adding .bin and .clk support.
|
||||
For most clockface designs, using 4 or 8 BPP BMP formats will save even more file size:
|
||||
|
||||
| Bits per pixel | File size in kB (for 135x240 img) | % of 24 BPP BMP | Max unique colors
|
||||
| --- | --- | --- | --- |
|
||||
24 | 98 | 100% | 16M (66K)
|
||||
16 (.clk) | 64.8 | 66% | 66K
|
||||
8 | 33.7 | 34% | 256
|
||||
4 | 16.4 | 17% | 16
|
||||
1 | 4.9 | 5% | 2
|
||||
|
||||
Comparison 1 vs. 4 vs. 8 vs. 24 BPP. With this clockface on the actual clock, 4 bit looks good, and 8 bit is almost indistinguishable from 24 bit.
|
||||
|
||||

|
||||
@@ -6,6 +6,13 @@
|
||||
|
||||
class ElekstubeIPSUsermod : public Usermod {
|
||||
private:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _tubeSeg[];
|
||||
static const char _digitOffset[];
|
||||
|
||||
char cronixieDisplay[7] = "HHMMSS";
|
||||
|
||||
TFTs tfts;
|
||||
void updateClockDisplay(TFTs::show_t show=TFTs::yes) {
|
||||
bool set[6] = {false};
|
||||
@@ -21,6 +28,8 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
set[i] = false; //display HHMMSS time
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t hrTens = hr/10;
|
||||
uint8_t mi = minute(localTime);
|
||||
@@ -37,6 +46,10 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
unsigned long lastTime = 0;
|
||||
public:
|
||||
|
||||
uint8_t lastBri;
|
||||
uint32_t lastCols[6];
|
||||
TFTs::show_t fshow=TFTs::yes;
|
||||
|
||||
void setup() {
|
||||
tfts.begin();
|
||||
tfts.fillScreen(TFT_BLACK);
|
||||
@@ -47,14 +60,99 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (toki.isTick()) {
|
||||
updateLocalTime();
|
||||
updateClockDisplay();
|
||||
if (!toki.isTick()) return;
|
||||
updateLocalTime();
|
||||
|
||||
WS2812FX::Segment& seg1 = strip.getSegment(tfts.tubeSegment);
|
||||
if (seg1.isActive()) {
|
||||
bool update = false;
|
||||
if (seg1.opacity != lastBri) update = true;
|
||||
lastBri = seg1.opacity;
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
uint32_t c = strip.getPixelColor(seg1.start + i);
|
||||
if (c != lastCols[i]) update = true;
|
||||
lastCols[i] = c;
|
||||
}
|
||||
if (update) fshow=TFTs::force;
|
||||
} else if (lastCols[0] != 0) { // Segment 1 deleted
|
||||
fshow=TFTs::force;
|
||||
lastCols[0] = 0;
|
||||
}
|
||||
|
||||
updateClockDisplay(fshow);
|
||||
fshow=TFTs::yes;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root) {
|
||||
// we add JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_tubeSeg)] = tfts.tubeSegment;
|
||||
top[FPSTR(_digitOffset)] = tfts.digitOffset;
|
||||
DEBUG_PRINTLN(F("EleksTube config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
// we look for JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
tfts.tubeSegment = top[FPSTR(_tubeSeg)] | tfts.tubeSegment;
|
||||
uint8_t digitOffsetPrev = tfts.digitOffset;
|
||||
tfts.digitOffset = top[FPSTR(_digitOffset)] | tfts.digitOffset;
|
||||
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
|
||||
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_digitOffset)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
root["nx"] = cronixieDisplay;
|
||||
root[FPSTR(_digitOffset)] = tfts.digitOffset;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
if (root["nx"].is<const char*>()) {
|
||||
strncpy(cronixieDisplay, root["nx"], 6);
|
||||
}
|
||||
|
||||
uint8_t digitOffsetPrev = tfts.digitOffset;
|
||||
tfts.digitOffset = root[FPSTR(_digitOffset)] | tfts.digitOffset;
|
||||
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
|
||||
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_ELEKSTUBE_IPS;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char ElekstubeIPSUsermod::_name[] PROGMEM = "EleksTubeIPS";
|
||||
const char ElekstubeIPSUsermod::_tubeSeg[] PROGMEM = "tubeSegment";
|
||||
const char ElekstubeIPSUsermod::_digitOffset[] PROGMEM = "digitOffset";
|
||||
|
||||
@@ -100,9 +100,9 @@ void userLoop() {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
} else if (knownMode != strip.getMainSegment().mode) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
} else if (knownPalette != strip.getMainSegment().palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ void userLoop() {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
|
||||
@@ -143,9 +143,9 @@ void userLoop() {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
} else if (knownMode != strip.getMainSegment().mode) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
} else if (knownPalette != strip.getMainSegment().palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
@@ -169,8 +169,8 @@ void userLoop() {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
# JSON IR remote
|
||||
|
||||
## Purpose
|
||||
|
||||
The JSON IR remote allows users to customize IR remote behavior without writing custom code and compiling.
|
||||
It also enables using any remote that is compatible with your IR receiver. Using the JSON IR remote, you can
|
||||
map buttons from any remote to any HTTP request API or JSON API command.
|
||||
|
||||
## Usage
|
||||
|
||||
* Upload the IR config file, named _ir.json_ to your board using the [ip address]/edit url. Pick from one of the included files or create your own.
|
||||
* On the config > LED settings page, set the correct IR pin.
|
||||
* On the config > Sync Interfaces page, select "JSON Remote" as the Infrared remote.
|
||||
|
||||
## Modification
|
||||
|
||||
* See if there is a json file with the same number of buttons as your remote. Many remotes will have the same internals and emit the same codes but have different labels.
|
||||
* In the ir.json file, each key will be the hex encoded IR code.
|
||||
* The "cmd" property will be the HTTP Request API or JSON API to execute when that button is pressed.
|
||||
* A limited number of c functions are supported (!incBrightness, !decBrightness, !presetFallback)
|
||||
* When using !presetFallback, include properties PL (preset to load), FX (effect to fall back to) and FP (palette to fall back to)
|
||||
* If the command is _repeatable_ and does not contain the "~" character, add a "rpt": true property.
|
||||
* Other properties are ignored, but having a label property may help when editing.
|
||||
|
||||
|
||||
Sample:
|
||||
{
|
||||
"0xFF629D": {"cmd": "T=2", "rpt": true, "label": "Toggle on/off"}, // HTTP command
|
||||
"0xFF9867": {"cmd": "A=~16", "label": "Inc brightness"}, // HTTP command with incrementing
|
||||
"0xFF38C7": {"cmd": {"bri": 10}, "label": "Dim to 10"}, // JSON command
|
||||
"0xFF22DD": {"cmd": "!presetFallback", "PL": 1, "FX": 16, "FP": 6,
|
||||
"label": "Preset 1 or fallback to Saw - Party"}, // c function
|
||||
}
|
||||
# JSON IR remote
|
||||
|
||||
## Purpose
|
||||
|
||||
The JSON IR remote allows users to customize IR remote behavior without writing custom code and compiling.
|
||||
It also enables using any remote that is compatible with your IR receiver. Using the JSON IR remote, you can
|
||||
map buttons from any remote to any HTTP request API or JSON API command.
|
||||
|
||||
## Usage
|
||||
|
||||
* Upload the IR config file, named _ir.json_ to your board using the [ip address]/edit url. Pick from one of the included files or create your own.
|
||||
* On the config > LED settings page, set the correct IR pin.
|
||||
* On the config > Sync Interfaces page, select "JSON Remote" as the Infrared remote.
|
||||
|
||||
## Modification
|
||||
|
||||
* See if there is a json file with the same number of buttons as your remote. Many remotes will have the same internals and emit the same codes but have different labels.
|
||||
* In the ir.json file, each key will be the hex encoded IR code.
|
||||
* The "cmd" property will be the HTTP Request API or JSON API to execute when that button is pressed.
|
||||
* A limited number of c functions are supported (!incBrightness, !decBrightness, !presetFallback)
|
||||
* When using !presetFallback, include properties PL (preset to load), FX (effect to fall back to) and FP (palette to fall back to)
|
||||
* If the command is _repeatable_ and does not contain the "~" character, add a "rpt": true property.
|
||||
* Other properties are ignored, but having a label property may help when editing.
|
||||
|
||||
|
||||
Sample:
|
||||
{
|
||||
"0xFF629D": {"cmd": "T=2", "rpt": true, "label": "Toggle on/off"}, // HTTP command
|
||||
"0xFF9867": {"cmd": "A=~16", "label": "Inc brightness"}, // HTTP command with incrementing
|
||||
"0xFF38C7": {"cmd": {"bri": 10}, "label": "Dim to 10"}, // JSON command
|
||||
"0xFF22DD": {"cmd": "!presetFallback", "PL": 1, "FX": 16, "FP": 6,
|
||||
"label": "Preset 1 or fallback to Saw - Party"}, // c function
|
||||
}
|
||||
|
||||
321
usermods/MY9291/MY92xx.h
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
|
||||
MY92XX LED Driver for Arduino
|
||||
Based on the C driver by MaiKe Labs
|
||||
|
||||
Copyright (c) 2016 - 2026 MaiKe Labs
|
||||
Copyright (C) 2017 - 2018 Xose Pérez for the Arduino compatible library
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _my92xx_h
|
||||
#define _my92xx_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef DEBUG_MY92XX
|
||||
#if ARDUINO_ARCH_ESP8266
|
||||
#define DEBUG_MSG_MY92XX(...) DEBUG_MY92XX.printf( __VA_ARGS__ )
|
||||
#elif ARDUINO_ARCH_AVR
|
||||
#define DEBUG_MSG_MY92XX(...) { char buffer[80]; snprintf(buffer, sizeof(buffer), __VA_ARGS__ ); DEBUG_MY92XX.print(buffer); }
|
||||
#endif
|
||||
#else
|
||||
#define DEBUG_MSG_MY92XX(...)
|
||||
#endif
|
||||
|
||||
typedef enum my92xx_model_t {
|
||||
MY92XX_MODEL_MY9291 = 0X00,
|
||||
MY92XX_MODEL_MY9231 = 0X01,
|
||||
} my92xx_model_t;
|
||||
|
||||
typedef enum my92xx_cmd_one_shot_t {
|
||||
MY92XX_CMD_ONE_SHOT_DISABLE = 0X00,
|
||||
MY92XX_CMD_ONE_SHOT_ENFORCE = 0X01,
|
||||
} my92xx_cmd_one_shot_t;
|
||||
|
||||
typedef enum my92xx_cmd_reaction_t {
|
||||
MY92XX_CMD_REACTION_FAST = 0X00,
|
||||
MY92XX_CMD_REACTION_SLOW = 0X01,
|
||||
} my92xx_cmd_reaction_t;
|
||||
|
||||
typedef enum my92xx_cmd_bit_width_t {
|
||||
MY92XX_CMD_BIT_WIDTH_16 = 0X00,
|
||||
MY92XX_CMD_BIT_WIDTH_14 = 0X01,
|
||||
MY92XX_CMD_BIT_WIDTH_12 = 0X02,
|
||||
MY92XX_CMD_BIT_WIDTH_8 = 0X03,
|
||||
} my92xx_cmd_bit_width_t;
|
||||
|
||||
typedef enum my92xx_cmd_frequency_t {
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_1 = 0X00,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_4 = 0X01,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_16 = 0X02,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_64 = 0X03,
|
||||
} my92xx_cmd_frequency_t;
|
||||
|
||||
typedef enum my92xx_cmd_scatter_t {
|
||||
MY92XX_CMD_SCATTER_APDM = 0X00,
|
||||
MY92XX_CMD_SCATTER_PWM = 0X01,
|
||||
} my92xx_cmd_scatter_t;
|
||||
|
||||
typedef struct {
|
||||
my92xx_cmd_scatter_t scatter : 1;
|
||||
my92xx_cmd_frequency_t frequency : 2;
|
||||
my92xx_cmd_bit_width_t bit_width : 2;
|
||||
my92xx_cmd_reaction_t reaction : 1;
|
||||
my92xx_cmd_one_shot_t one_shot : 1;
|
||||
unsigned char resv : 1;
|
||||
} __attribute__((aligned(1), packed)) my92xx_cmd_t;
|
||||
|
||||
#define MY92XX_COMMAND_DEFAULT { \
|
||||
.scatter = MY92XX_CMD_SCATTER_APDM, \
|
||||
.frequency = MY92XX_CMD_FREQUENCY_DIVIDE_1, \
|
||||
.bit_width = MY92XX_CMD_BIT_WIDTH_8, \
|
||||
.reaction = MY92XX_CMD_REACTION_FAST, \
|
||||
.one_shot = MY92XX_CMD_ONE_SHOT_DISABLE, \
|
||||
.resv = 0 \
|
||||
}
|
||||
|
||||
class my92xx {
|
||||
|
||||
public:
|
||||
|
||||
my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command);
|
||||
unsigned char getChannels();
|
||||
void setChannel(unsigned char channel, unsigned int value);
|
||||
unsigned int getChannel(unsigned char channel);
|
||||
void setState(bool state);
|
||||
bool getState();
|
||||
void update();
|
||||
|
||||
private:
|
||||
|
||||
void _di_pulse(unsigned int times);
|
||||
void _dcki_pulse(unsigned int times);
|
||||
void _set_cmd(my92xx_cmd_t command);
|
||||
void _send();
|
||||
void _write(unsigned int data, unsigned char bit_length);
|
||||
|
||||
my92xx_cmd_t _command;
|
||||
my92xx_model_t _model = MY92XX_MODEL_MY9291;
|
||||
unsigned char _chips = 1;
|
||||
unsigned char _channels;
|
||||
uint16_t* _value;
|
||||
bool _state = false;
|
||||
unsigned char _pin_di;
|
||||
unsigned char _pin_dcki;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#if ARDUINO_ARCH_ESP8266
|
||||
|
||||
extern "C" {
|
||||
void os_delay_us(unsigned int);
|
||||
}
|
||||
|
||||
#elif ARDUINO_ARCH_AVR
|
||||
|
||||
#define os_delay_us delayMicroseconds
|
||||
|
||||
#endif
|
||||
|
||||
void my92xx::_di_pulse(unsigned int times) {
|
||||
for (unsigned int i = 0; i < times; i++) {
|
||||
digitalWrite(_pin_di, HIGH);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void my92xx::_dcki_pulse(unsigned int times) {
|
||||
for (unsigned int i = 0; i < times; i++) {
|
||||
digitalWrite(_pin_dcki, HIGH);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void my92xx::_write(unsigned int data, unsigned char bit_length) {
|
||||
|
||||
unsigned int mask = (0x01 << (bit_length - 1));
|
||||
|
||||
for (unsigned int i = 0; i < bit_length / 2; i++) {
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
|
||||
digitalWrite(_pin_dcki, HIGH);
|
||||
data = data << 1;
|
||||
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
data = data << 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void my92xx::_set_cmd(my92xx_cmd_t command) {
|
||||
|
||||
// ets_intr_lock();
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
|
||||
// pulse's rising edge convert to command mode.
|
||||
_di_pulse(12);
|
||||
|
||||
// Delay >12us, begin send CMD data
|
||||
os_delay_us(12);
|
||||
|
||||
// Send CMD data
|
||||
unsigned char command_data = *(unsigned char*)(&command);
|
||||
for (unsigned char i = 0; i < _chips; i++) {
|
||||
_write(command_data, 8);
|
||||
}
|
||||
|
||||
// TStart > 12us. Delay 12 us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 16 DI pulse,at 14 pulse's falling edge store CMD data, and
|
||||
// at 16 pulse's falling edge convert to duty mode.
|
||||
_di_pulse(16);
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// ets_intr_unlock();
|
||||
|
||||
}
|
||||
|
||||
void my92xx::_send() {
|
||||
|
||||
#ifdef DEBUG_MY92XX
|
||||
DEBUG_MSG_MY92XX("[MY92XX] Refresh: %s (", _state ? "ON" : "OFF");
|
||||
for (unsigned char channel = 0; channel < _channels; channel++) {
|
||||
DEBUG_MSG_MY92XX(" %d", _value[channel]);
|
||||
}
|
||||
DEBUG_MSG_MY92XX(" )\n");
|
||||
#endif
|
||||
|
||||
unsigned char bit_length = 8;
|
||||
switch (_command.bit_width) {
|
||||
case MY92XX_CMD_BIT_WIDTH_16:
|
||||
bit_length = 16;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_14:
|
||||
bit_length = 14;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_12:
|
||||
bit_length = 12;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_8:
|
||||
bit_length = 8;
|
||||
break;
|
||||
default:
|
||||
bit_length = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
// ets_intr_lock();
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send color data
|
||||
for (unsigned char channel = 0; channel < _channels; channel++) {
|
||||
_write(_state ? _value[channel] : 0, bit_length);
|
||||
}
|
||||
|
||||
// TStart > 12us. Ready for send DI pulse.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 8 DI pulse. After 8 pulse falling edge, store old data.
|
||||
_di_pulse(8);
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// ets_intr_unlock();
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
unsigned char my92xx::getChannels() {
|
||||
return _channels;
|
||||
}
|
||||
|
||||
void my92xx::setChannel(unsigned char channel, unsigned int value) {
|
||||
if (channel < _channels) {
|
||||
_value[channel] = value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int my92xx::getChannel(unsigned char channel) {
|
||||
if (channel < _channels) {
|
||||
return _value[channel];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool my92xx::getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
void my92xx::setState(bool state) {
|
||||
_state = state;
|
||||
}
|
||||
|
||||
void my92xx::update() {
|
||||
_send();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
my92xx::my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command) : _command(command) {
|
||||
|
||||
_model = model;
|
||||
_chips = chips;
|
||||
_pin_di = di;
|
||||
_pin_dcki = dcki;
|
||||
|
||||
// Init channels
|
||||
if (_model == MY92XX_MODEL_MY9291) {
|
||||
_channels = 4 * _chips;
|
||||
}
|
||||
else if (_model == MY92XX_MODEL_MY9231) {
|
||||
_channels = 3 * _chips;
|
||||
}
|
||||
_value = new uint16_t[_channels];
|
||||
for (unsigned char i = 0; i < _channels; i++) {
|
||||
_value[i] = 0;
|
||||
}
|
||||
|
||||
// Init GPIO
|
||||
pinMode(_pin_di, OUTPUT);
|
||||
pinMode(_pin_dcki, OUTPUT);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
|
||||
// Clear all duty register
|
||||
_dcki_pulse(32 * _chips);
|
||||
|
||||
// Send init command
|
||||
_set_cmd(command);
|
||||
|
||||
DEBUG_MSG_MY92XX("[MY92XX] Initialized\n");
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
45
usermods/MY9291/usermode_MY9291.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include "MY92xx.h"
|
||||
|
||||
#define MY92XX_MODEL MY92XX_MODEL_MY9291
|
||||
#define MY92XX_CHIPS 1
|
||||
#define MY92XX_DI_PIN 13
|
||||
#define MY92XX_DCKI_PIN 15
|
||||
|
||||
#define MY92XX_RED 0
|
||||
#define MY92XX_GREEN 1
|
||||
#define MY92XX_BLUE 2
|
||||
#define MY92XX_WHITE 3
|
||||
|
||||
class MY9291Usermod : public Usermod {
|
||||
private:
|
||||
my92xx _my92xx = my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND_DEFAULT);
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
_my92xx.setState(true);
|
||||
}
|
||||
|
||||
void connected() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t c = strip.getPixelColor(0);
|
||||
int w = ((c >> 24) & 0xff) * bri / 255.0;
|
||||
int r = ((c >> 16) & 0xff) * bri / 255.0;
|
||||
int g = ((c >> 8) & 0xff) * bri / 255.0;
|
||||
int b = (c & 0xff) * bri / 255.0;
|
||||
_my92xx.setChannel(MY92XX_RED, r);
|
||||
_my92xx.setChannel(MY92XX_GREEN, g);
|
||||
_my92xx.setChannel(MY92XX_BLUE, b);
|
||||
_my92xx.setChannel(MY92XX_WHITE, w);
|
||||
_my92xx.update();
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_MY9291;
|
||||
}
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
# PIR sensor with MQTT
|
||||
|
||||
This simple usermod allows attaching a PIR sensor like the AM312 and publish the readings over MQTT. A message is sent when motion is detected as well as when motion has stopped.
|
||||
|
||||
This usermod has only been tested with the AM312 sensor though should work for any other PIR sensor. Note that this does not control the LED strip directly, it only publishes MQTT readings for use with other integrations like Home Assistant.
|
||||
|
||||
## Installation
|
||||
|
||||
Copy and replace the file `usermod.cpp` in wled00 directory.
|
||||
@@ -1,55 +0,0 @@
|
||||
#include "wled.h"
|
||||
/*
|
||||
* This v1 usermod file allows you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
|
||||
* If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE)
|
||||
*
|
||||
* Consider the v2 usermod API if you need a more advanced feature set!
|
||||
*/
|
||||
|
||||
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
|
||||
|
||||
// PIR sensor pin
|
||||
const int MOTION_PIN = 16;
|
||||
// MQTT topic for sensor values
|
||||
const char MQTT_TOPIC[] = "/motion";
|
||||
|
||||
int prevState = LOW;
|
||||
|
||||
//gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
void userSetup()
|
||||
{
|
||||
pinMode(MOTION_PIN, INPUT);
|
||||
}
|
||||
|
||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
||||
void userConnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void publishMqtt(String state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (mqtt != nullptr){
|
||||
char subuf[38];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, MQTT_TOPIC);
|
||||
mqtt->publish(subuf, 0, true, state.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
|
||||
void userLoop()
|
||||
{
|
||||
if (digitalRead(MOTION_PIN) == HIGH && prevState == LOW) { // Motion detected
|
||||
publishMqtt("ON");
|
||||
prevState = HIGH;
|
||||
}
|
||||
if (digitalRead(MOTION_PIN) == LOW && prevState == HIGH) { // Motion stopped
|
||||
publishMqtt("OFF");
|
||||
prevState = LOW;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class PIRsensorSwitch : public Usermod {
|
||||
private:
|
||||
// PIR sensor pin
|
||||
const uint8_t PIRsensorPin = 13; // D7 on D1 mini
|
||||
// notification mode for colorUpdated()
|
||||
// notification mode for stateUpdated()
|
||||
const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE
|
||||
// 1 min delay before switch off after the sensor state goes LOW
|
||||
uint32_t m_switchOffDelay = 60000;
|
||||
@@ -127,7 +127,7 @@ class PIRsensorSwitch : public Usermod {
|
||||
if (bri != briHighlight) {
|
||||
bri = briHighlight; // set current highlight brightness to last set highlight brightness
|
||||
}
|
||||
colorUpdated(NotifyUpdateMode);
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
highlightActive = true; // flag highlight is on
|
||||
}
|
||||
else { // **pir timer has elapsed**
|
||||
@@ -157,7 +157,7 @@ class PIRsensorSwitch : public Usermod {
|
||||
}
|
||||
applyMacro(macroLongPress); // apply standby lighting without brightness
|
||||
}
|
||||
colorUpdated(NotifyUpdateMode);
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
highlightActive = false; // flag highlight is off
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,17 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik
|
||||
|
||||
## Webinterface
|
||||
|
||||
The info page in the web interface shows the remaining time of the off timer.
|
||||
The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disbled/enabled from the info page by clicking PIR button.
|
||||
|
||||
## Sensor connection
|
||||
|
||||
My setup uses an HC-SR501 sensor, a HC-SR505 should also work.
|
||||
My setup uses an HC-SR501 or HC-SR602 sensor, a HC-SR505 should also work.
|
||||
|
||||
The usermod uses GPIO13 (D1 mini pin D7) by default for the sensor signal but can be changed in the Usermod settings page.
|
||||
[This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor.
|
||||
|
||||
Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above.
|
||||
You can also use usermod's off timer instead of sensor's. In such case rotate the potentiometer to its shortest time possible (or use SR602 which lacks such potentiometer).
|
||||
|
||||
## Usermod installation
|
||||
|
||||
@@ -59,6 +60,8 @@ void registerUsermods()
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin.
|
||||
|
||||
## API to enable/disable the PIR sensor from outside. For example from another usermod.
|
||||
|
||||
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
||||
@@ -95,8 +98,27 @@ class MyUsermod : public Usermod {
|
||||
};
|
||||
```
|
||||
|
||||
Have fun - @gegu
|
||||
### Configuration options
|
||||
|
||||
Usermod can be configured in Usermods settings page.
|
||||
|
||||
* `PIRenabled` - enable/disable usermod
|
||||
* `pin` - dynamically change GPIO pin where PIR sensor is attached to ESP
|
||||
* `PIRoffSec` - number of seconds after PIR sensor deactivates when usermod triggers Off preset (or turns WLED off)
|
||||
* `on-preset` - preset triggered when PIR activates (if this is 0 it will just turn WLED on)
|
||||
* `off-preset` - preset triggered when PIR deactivates (if this is 0 it will just turn WLED off)
|
||||
* `nighttime-only` - enable triggering only between sunset and sunrise (you will need to set up _NTP_, _Lat_ & _Lon_ in Time & Macro settings)
|
||||
* `mqtt-only` - only send MQTT messages, do not interact with WLED
|
||||
* `off-only` - only trigger presets or turn WLED on/off in WLED is not already on (displaying effect)
|
||||
* `notifications` - enable or disable sending notifications to other WLED instances using Sync button
|
||||
|
||||
|
||||
Have fun - @gegu & @blazoncek
|
||||
|
||||
## Change log
|
||||
2021-04
|
||||
* Adaptation for runtime configuration.
|
||||
* Adaptation for runtime configuration.
|
||||
|
||||
2021-11
|
||||
* Added information about dynamic configuration options
|
||||
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
|
||||
@@ -1,409 +1,452 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef PIR_SENSOR_PIN
|
||||
// compatible with QuinLED-Dig-Uno
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define PIR_SENSOR_PIN 23 // Q4
|
||||
#else //ESP8266 boards
|
||||
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This usermod handles PIR sensor states.
|
||||
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
|
||||
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
|
||||
*
|
||||
*
|
||||
* Usermods allow you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
|
||||
* Multiple v2 usermods can be added to one compilation easily.
|
||||
*
|
||||
* Creating a usermod:
|
||||
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
|
||||
* Please remember to rename the class and file to a descriptive name.
|
||||
* You may also use multiple .h and .cpp files.
|
||||
*
|
||||
* Using a usermod:
|
||||
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
|
||||
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
|
||||
*/
|
||||
|
||||
class PIRsensorSwitch : public Usermod
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
PIRsensorSwitch() {}
|
||||
/**
|
||||
* desctructor
|
||||
*/
|
||||
~PIRsensorSwitch() {}
|
||||
|
||||
/**
|
||||
* Enable/Disable the PIR sensor
|
||||
*/
|
||||
void EnablePIRsensor(bool en) { enabled = en; }
|
||||
/**
|
||||
* Get PIR sensor enabled/disabled state
|
||||
*/
|
||||
bool PIRsensorEnabled() { return enabled; }
|
||||
|
||||
private:
|
||||
// PIR sensor pin
|
||||
int8_t PIRsensorPin = PIR_SENSOR_PIN;
|
||||
// notification mode for colorUpdated()
|
||||
const byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // CALL_MODE_DIRECT_CHANGE
|
||||
// delay before switch off after the sensor state goes LOW
|
||||
uint32_t m_switchOffDelay = 600000; // 10min
|
||||
// off timer start time
|
||||
uint32_t m_offTimerStart = 0;
|
||||
// current PIR sensor pin state
|
||||
byte sensorPinState = LOW;
|
||||
// PIR sensor enabled
|
||||
bool enabled = true;
|
||||
// status of initialisation
|
||||
bool initDone = false;
|
||||
// on and off presets
|
||||
uint8_t m_onPreset = 0;
|
||||
uint8_t m_offPreset = 0;
|
||||
// flag to indicate that PIR sensor should activate WLED during nighttime only
|
||||
bool m_nightTimeOnly = false;
|
||||
// flag to send MQTT message only (assuming it is enabled)
|
||||
bool m_mqttOnly = false;
|
||||
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
|
||||
bool m_offOnly = false;
|
||||
bool PIRtriggered = false;
|
||||
|
||||
unsigned long lastLoop = 0;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _switchOffDelay[];
|
||||
static const char _enabled[];
|
||||
static const char _onPreset[];
|
||||
static const char _offPreset[];
|
||||
static const char _nightTime[];
|
||||
static const char _mqttOnly[];
|
||||
static const char _offOnly[];
|
||||
|
||||
/**
|
||||
* check if it is daytime
|
||||
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||
*/
|
||||
bool isDayTime() {
|
||||
bool isDayTime = false;
|
||||
updateLocalTime();
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t mi = minute(localTime);
|
||||
|
||||
if (sunrise && sunset) {
|
||||
if (hour(sunrise)<hr && hour(sunset)>hr) {
|
||||
isDayTime = true;
|
||||
} else {
|
||||
if (hour(sunrise)==hr && minute(sunrise)<mi) {
|
||||
isDayTime = true;
|
||||
}
|
||||
if (hour(sunset)==hr && minute(sunset)>mi) {
|
||||
isDayTime = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isDayTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch strip on/off
|
||||
*/
|
||||
void switchStrip(bool switchOn)
|
||||
{
|
||||
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
|
||||
PIRtriggered = switchOn;
|
||||
if (switchOn && m_onPreset) {
|
||||
applyPreset(m_onPreset);
|
||||
} else if (!switchOn && m_offPreset) {
|
||||
applyPreset(m_offPreset);
|
||||
} else if (switchOn && bri == 0) {
|
||||
bri = briLast;
|
||||
colorUpdated(NotifyUpdateMode);
|
||||
} else if (!switchOn && bri != 0) {
|
||||
briLast = bri;
|
||||
bri = 0;
|
||||
colorUpdated(NotifyUpdateMode);
|
||||
}
|
||||
}
|
||||
|
||||
void publishMqtt(const char* state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/motion"));
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and update PIR sensor state.
|
||||
* Initilize/reset switch off timer
|
||||
*/
|
||||
bool updatePIRsensorState()
|
||||
{
|
||||
bool pinState = digitalRead(PIRsensorPin);
|
||||
if (pinState != sensorPinState) {
|
||||
sensorPinState = pinState; // change previous state
|
||||
|
||||
if (sensorPinState == HIGH) {
|
||||
m_offTimerStart = 0;
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||
publishMqtt("on");
|
||||
} else /*if (bri != 0)*/ {
|
||||
// start switch off timer
|
||||
m_offTimerStart = millis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch off the strip if the delay has elapsed
|
||||
*/
|
||||
bool handleOffTimer()
|
||||
{
|
||||
if (m_offTimerStart > 0 && millis() - m_offTimerStart > m_switchOffDelay)
|
||||
{
|
||||
if (enabled == true)
|
||||
{
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
|
||||
publishMqtt("off");
|
||||
}
|
||||
m_offTimerStart = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/**
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
// PIR Sensor mode INPUT_PULLUP
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
} else {
|
||||
if (PIRsensorPin >= 0) {
|
||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||
}
|
||||
PIRsensorPin = -1; // allocation failed
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
// only check sensors 4x/s
|
||||
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
|
||||
lastLoop = millis();
|
||||
|
||||
if (!updatePIRsensorState()) {
|
||||
handleOffTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
*
|
||||
* Add PIR sensor state and switch off timer duration to jsoninfo
|
||||
*/
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
// off timer
|
||||
String uiDomString = F("PIR <i class=\"icons\"></i>");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
if (m_offTimerStart > 0)
|
||||
{
|
||||
uiDomString = "";
|
||||
unsigned int offSeconds = (m_switchOffDelay - (millis() - m_offTimerStart)) / 1000;
|
||||
if (offSeconds >= 3600)
|
||||
{
|
||||
uiDomString += (offSeconds / 3600);
|
||||
uiDomString += F("h ");
|
||||
offSeconds %= 3600;
|
||||
}
|
||||
if (offSeconds >= 60)
|
||||
{
|
||||
uiDomString += (offSeconds / 60);
|
||||
offSeconds %= 60;
|
||||
}
|
||||
else if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += 0;
|
||||
}
|
||||
if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += F("min ");
|
||||
}
|
||||
uiDomString += (offSeconds);
|
||||
infoArr.add(uiDomString + F("s"));
|
||||
} else {
|
||||
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
||||
}
|
||||
} else {
|
||||
String uiDomString = F("PIR sensor");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString);
|
||||
infoArr.add(F("disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* provide the changeable values
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||
top["pin"] = PIRsensorPin;
|
||||
top[FPSTR(_onPreset)] = m_onPreset;
|
||||
top[FPSTR(_offPreset)] = m_offPreset;
|
||||
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||
top[FPSTR(_offOnly)] = m_offOnly;
|
||||
DEBUG_PRINTLN(F("PIR config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the changeable values
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
bool oldEnabled = enabled;
|
||||
int8_t oldPin = PIRsensorPin;
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
PIRsensorPin = top["pin"] | PIRsensorPin;
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
|
||||
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
|
||||
|
||||
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
|
||||
m_onPreset = max(0,min(250,(int)m_onPreset));
|
||||
|
||||
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
|
||||
m_offPreset = max(0,min(250,(int)m_offPreset));
|
||||
|
||||
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
||||
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
||||
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// reading config prior to setup()
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
||||
// check if pin is OK
|
||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||
// if we are changing pin in settings page
|
||||
// deallocate old pin
|
||||
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
|
||||
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
} else {
|
||||
// allocation failed
|
||||
PIRsensorPin = -1;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_offOnly)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_PIRSWITCH;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
|
||||
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
|
||||
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
|
||||
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
|
||||
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef PIR_SENSOR_PIN
|
||||
// compatible with QuinLED-Dig-Uno
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define PIR_SENSOR_PIN 23 // Q4
|
||||
#else //ESP8266 boards
|
||||
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This usermod handles PIR sensor states.
|
||||
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
|
||||
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
|
||||
*
|
||||
*
|
||||
* Usermods allow you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
|
||||
* Multiple v2 usermods can be added to one compilation easily.
|
||||
*
|
||||
* Creating a usermod:
|
||||
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
|
||||
* Please remember to rename the class and file to a descriptive name.
|
||||
* You may also use multiple .h and .cpp files.
|
||||
*
|
||||
* Using a usermod:
|
||||
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
|
||||
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
|
||||
*/
|
||||
|
||||
class PIRsensorSwitch : public Usermod
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
PIRsensorSwitch() {}
|
||||
/**
|
||||
* desctructor
|
||||
*/
|
||||
~PIRsensorSwitch() {}
|
||||
|
||||
/**
|
||||
* Enable/Disable the PIR sensor
|
||||
*/
|
||||
void EnablePIRsensor(bool en) { enabled = en; }
|
||||
/**
|
||||
* Get PIR sensor enabled/disabled state
|
||||
*/
|
||||
bool PIRsensorEnabled() { return enabled; }
|
||||
|
||||
private:
|
||||
|
||||
byte prevPreset = 0;
|
||||
byte prevPlaylist = 0;
|
||||
bool savedState = false;
|
||||
|
||||
uint32_t offTimerStart = 0; // off timer start time
|
||||
byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
|
||||
byte sensorPinState = LOW; // current PIR sensor pin state
|
||||
bool initDone = false; // status of initialization
|
||||
bool PIRtriggered = false;
|
||||
unsigned long lastLoop = 0;
|
||||
|
||||
// configurable parameters
|
||||
bool enabled = true; // PIR sensor enabled
|
||||
int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
|
||||
uint32_t m_switchOffDelay = 600000; // delay before switch off after the sensor state goes LOW (10min)
|
||||
uint8_t m_onPreset = 0; // on preset
|
||||
uint8_t m_offPreset = 0; // off preset
|
||||
bool m_nightTimeOnly = false; // flag to indicate that PIR sensor should activate WLED during nighttime only
|
||||
bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled)
|
||||
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
|
||||
bool m_offOnly = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _switchOffDelay[];
|
||||
static const char _enabled[];
|
||||
static const char _onPreset[];
|
||||
static const char _offPreset[];
|
||||
static const char _nightTime[];
|
||||
static const char _mqttOnly[];
|
||||
static const char _offOnly[];
|
||||
static const char _notify[];
|
||||
|
||||
/**
|
||||
* check if it is daytime
|
||||
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||
*/
|
||||
bool isDayTime() {
|
||||
updateLocalTime();
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t mi = minute(localTime);
|
||||
|
||||
if (sunrise && sunset) {
|
||||
if (hour(sunrise)<hr && hour(sunset)>hr) {
|
||||
return true;
|
||||
} else {
|
||||
if (hour(sunrise)==hr && minute(sunrise)<mi) {
|
||||
return true;
|
||||
}
|
||||
if (hour(sunset)==hr && minute(sunset)>mi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch strip on/off
|
||||
*/
|
||||
void switchStrip(bool switchOn)
|
||||
{
|
||||
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
|
||||
PIRtriggered = switchOn;
|
||||
if (switchOn) {
|
||||
if (m_onPreset) {
|
||||
if (currentPlaylist>0) prevPlaylist = currentPlaylist;
|
||||
else if (currentPreset>0) prevPreset = currentPreset;
|
||||
else {
|
||||
saveTemporaryPreset();
|
||||
savedState = true;
|
||||
prevPlaylist = 0;
|
||||
prevPreset = 0;
|
||||
}
|
||||
applyPreset(m_onPreset, NotifyUpdateMode);
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri == 0) {
|
||||
bri = briLast;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
} else {
|
||||
if (m_offPreset) {
|
||||
applyPreset(m_offPreset, NotifyUpdateMode);
|
||||
return;
|
||||
} else if (prevPlaylist) {
|
||||
applyPreset(prevPlaylist, NotifyUpdateMode);
|
||||
prevPlaylist = 0;
|
||||
return;
|
||||
} else if (prevPreset) {
|
||||
applyPreset(prevPreset, NotifyUpdateMode);
|
||||
prevPreset = 0;
|
||||
return;
|
||||
} else if (savedState) {
|
||||
applyTemporaryPreset();
|
||||
savedState = false;
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri != 0) {
|
||||
briLast = bri;
|
||||
bri = 0;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void publishMqtt(const char* state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/motion"));
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and update PIR sensor state.
|
||||
* Initilize/reset switch off timer
|
||||
*/
|
||||
bool updatePIRsensorState()
|
||||
{
|
||||
bool pinState = digitalRead(PIRsensorPin);
|
||||
if (pinState != sensorPinState) {
|
||||
sensorPinState = pinState; // change previous state
|
||||
|
||||
if (sensorPinState == HIGH) {
|
||||
offTimerStart = 0;
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||
publishMqtt("on");
|
||||
} else /*if (bri != 0)*/ {
|
||||
// start switch off timer
|
||||
offTimerStart = millis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch off the strip if the delay has elapsed
|
||||
*/
|
||||
bool handleOffTimer()
|
||||
{
|
||||
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay)
|
||||
{
|
||||
if (enabled == true)
|
||||
{
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
|
||||
publishMqtt("off");
|
||||
}
|
||||
offTimerStart = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/**
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
// PIR Sensor mode INPUT_PULLUP
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
} else {
|
||||
if (PIRsensorPin >= 0) {
|
||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||
}
|
||||
PIRsensorPin = -1; // allocation failed
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
// only check sensors 4x/s
|
||||
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
|
||||
lastLoop = millis();
|
||||
|
||||
if (!updatePIRsensorState()) {
|
||||
handleOffTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
*
|
||||
* Add PIR sensor state and switch off timer duration to jsoninfo
|
||||
*/
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
String uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
if (enabled) {
|
||||
uiDomString += F(":false}});\">");
|
||||
uiDomString += F("PIR <i class=\"icons\"></i>");
|
||||
} else {
|
||||
uiDomString += F(":true}});\">");
|
||||
uiDomString += F("PIR <i class=\"icons\"></i>");
|
||||
}
|
||||
uiDomString += F("</button>");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
|
||||
if (enabled) {
|
||||
if (offTimerStart > 0)
|
||||
{
|
||||
uiDomString = "";
|
||||
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
|
||||
if (offSeconds >= 3600)
|
||||
{
|
||||
uiDomString += (offSeconds / 3600);
|
||||
uiDomString += F("h ");
|
||||
offSeconds %= 3600;
|
||||
}
|
||||
if (offSeconds >= 60)
|
||||
{
|
||||
uiDomString += (offSeconds / 60);
|
||||
offSeconds %= 60;
|
||||
}
|
||||
else if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += 0;
|
||||
}
|
||||
if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += F("min ");
|
||||
}
|
||||
uiDomString += (offSeconds);
|
||||
infoArr.add(uiDomString + F("s"));
|
||||
} else {
|
||||
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
||||
}
|
||||
} else {
|
||||
infoArr.add(F("disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod[FPSTR(_enabled)].is<bool>()) {
|
||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* provide the changeable values
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||
top["pin"] = PIRsensorPin;
|
||||
top[FPSTR(_onPreset)] = m_onPreset;
|
||||
top[FPSTR(_offPreset)] = m_offPreset;
|
||||
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||
top[FPSTR(_offOnly)] = m_offOnly;
|
||||
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
|
||||
DEBUG_PRINTLN(F("PIR config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the changeable values
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
bool oldEnabled = enabled;
|
||||
int8_t oldPin = PIRsensorPin;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
PIRsensorPin = top["pin"] | PIRsensorPin;
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
|
||||
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
|
||||
|
||||
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
|
||||
m_onPreset = max(0,min(250,(int)m_onPreset));
|
||||
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
|
||||
m_offPreset = max(0,min(250,(int)m_offPreset));
|
||||
|
||||
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
||||
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
||||
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
||||
|
||||
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
|
||||
|
||||
if (!initDone) {
|
||||
// reading config prior to setup()
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
||||
// check if pin is OK
|
||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||
// if we are changing pin in settings page
|
||||
// deallocate old pin
|
||||
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
|
||||
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
} else {
|
||||
// allocation failed
|
||||
PIRsensorPin = -1;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_notify)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_PIRSWITCH;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
|
||||
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
|
||||
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
|
||||
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
|
||||
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
|
||||
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
|
||||
|
||||
36
usermods/PWM_fan/readme.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# PWM fan
|
||||
|
||||
v2 Usermod to to control PWM fan with RPM feedback and temperature control
|
||||
|
||||
This usermod requires Dallas Temperature usermod to obtain temperature information. If this is not available the fan will always run at 100% speed.
|
||||
If the fan does not have _tacho_ (RPM) output you can set the _tacho-pin_ to -1 to not use that feature.
|
||||
|
||||
You can also set the thershold temperature at which fan runs at lowest speed. If the actual temperature measured will be 3°C greater than threshold temperature the fan will run at 100%.
|
||||
|
||||
If the _tacho_ is supported the current speed (in RPM) will be repored in WLED Info page.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_PWM_FAN` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_PWM_FAN` in `myconfig.h`.
|
||||
You will also need `-D USERMOD_DALLASTEMPERATURE`.
|
||||
|
||||
### Define Your Options
|
||||
|
||||
All of the parameters are configured during run-time using Usermods settings page.
|
||||
This includes:
|
||||
|
||||
* PWM output pin (can be configured at compile time `-D PWM_PIN=xx`)
|
||||
* tacho input pin (can be configured at compile time `-D TACHO_PIN=xx`)
|
||||
* sampling frequency in seconds
|
||||
* threshold temperature in degees C
|
||||
|
||||
_NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency.
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
No special requirements.
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-10
|
||||
* First public release
|
||||
339
usermods/PWM_fan/usermod_PWM_fan.h
Normal file
@@ -0,0 +1,339 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef USERMOD_DALLASTEMPERATURE
|
||||
#error The "PWM fan" usermod requires "Dallas Temeprature" usermod to function properly.
|
||||
#endif
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
// PWM & tacho code curtesy of @KlausMu
|
||||
// https://github.com/KlausMu/esp32-fan-controller/tree/main/src
|
||||
// adapted for WLED usermod by @blazoncek
|
||||
|
||||
#ifndef TACHO_PIN
|
||||
#define TACHO_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef PWM_PIN
|
||||
#define PWM_PIN -1
|
||||
#endif
|
||||
|
||||
// tacho counter
|
||||
static volatile unsigned long counter_rpm = 0;
|
||||
// Interrupt counting every rotation of the fan
|
||||
// https://desire.giesecke.tk/index.php/2018/01/30/change-global-variables-from-isr/
|
||||
static void IRAM_ATTR rpm_fan() {
|
||||
counter_rpm++;
|
||||
}
|
||||
|
||||
|
||||
class PWMFanUsermod : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
bool initDone = false;
|
||||
bool enabled = true;
|
||||
unsigned long msLastTachoMeasurement = 0;
|
||||
uint16_t last_rpm = 0;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t pwmChannel = 255;
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
UsermodTemperature* tempUM;
|
||||
#endif
|
||||
|
||||
// configurable parameters
|
||||
int8_t tachoPin = TACHO_PIN;
|
||||
int8_t pwmPin = PWM_PIN;
|
||||
uint8_t tachoUpdateSec = 30;
|
||||
float targetTemperature = 25.0;
|
||||
uint8_t minPWMValuePct = 50;
|
||||
uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups.
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _tachoPin[];
|
||||
static const char _pwmPin[];
|
||||
static const char _temperature[];
|
||||
static const char _tachoUpdateSec[];
|
||||
static const char _minPWMValuePct[];
|
||||
static const char _IRQperRotation[];
|
||||
|
||||
void initTacho(void) {
|
||||
if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){
|
||||
tachoPin = -1;
|
||||
return;
|
||||
}
|
||||
pinMode(tachoPin, INPUT);
|
||||
digitalWrite(tachoPin, HIGH);
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
DEBUG_PRINTLN(F("Tacho sucessfully initialized."));
|
||||
}
|
||||
|
||||
void deinitTacho(void) {
|
||||
if (tachoPin < 0) return;
|
||||
detachInterrupt(digitalPinToInterrupt(tachoPin));
|
||||
pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified);
|
||||
tachoPin = -1;
|
||||
}
|
||||
|
||||
void updateTacho(void) {
|
||||
if (tachoPin < 0) return;
|
||||
|
||||
// start of tacho measurement
|
||||
// detach interrupt while calculating rpm
|
||||
detachInterrupt(digitalPinToInterrupt(tachoPin));
|
||||
// calculate rpm
|
||||
last_rpm = (counter_rpm * 60) / numberOfInterrupsInOneSingleRotation;
|
||||
last_rpm /= tachoUpdateSec;
|
||||
// reset counter
|
||||
counter_rpm = 0;
|
||||
// store milliseconds when tacho was measured the last time
|
||||
msLastTachoMeasurement = millis();
|
||||
// attach interrupt again
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
}
|
||||
|
||||
// https://randomnerdtutorials.com/esp32-pwm-arduino-ide/
|
||||
void initPWMfan(void) {
|
||||
if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
|
||||
pwmPin = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
analogWriteRange(255);
|
||||
analogWriteFreq(WLED_PWM_FREQ);
|
||||
#else
|
||||
pwmChannel = pinManager.allocateLedc(1);
|
||||
if (pwmChannel == 255) { //no more free LEDC channels
|
||||
deinitPWMfan(); return;
|
||||
}
|
||||
// configure LED PWM functionalitites
|
||||
ledcSetup(pwmChannel, 25000, 8);
|
||||
// attach the channel to the GPIO to be controlled
|
||||
ledcAttachPin(pwmPin, pwmChannel);
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
|
||||
}
|
||||
|
||||
void deinitPWMfan(void) {
|
||||
if (pwmPin < 0) return;
|
||||
|
||||
pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
pinManager.deallocateLedc(pwmChannel, 1);
|
||||
#endif
|
||||
pwmPin = -1;
|
||||
}
|
||||
|
||||
void updateFanSpeed(uint8_t pwmValue){
|
||||
if (pwmPin < 0) return;
|
||||
|
||||
#ifdef ESP8266
|
||||
analogWrite(pwmPin, pwmValue);
|
||||
#else
|
||||
ledcWrite(pwmChannel, pwmValue);
|
||||
#endif
|
||||
}
|
||||
|
||||
float getActualTemperature(void) {
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
if (tempUM != nullptr)
|
||||
return tempUM->getTemperatureC();
|
||||
#endif
|
||||
return -127.0f;
|
||||
}
|
||||
|
||||
void setFanPWMbasedOnTemperature(void) {
|
||||
float temp = getActualTemperature();
|
||||
float difftemp = temp - targetTemperature;
|
||||
// Default to run fan at full speed.
|
||||
int newPWMvalue = 255;
|
||||
int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100);
|
||||
int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100;
|
||||
|
||||
if ((temp == NAN) || (temp <= 0.0)) {
|
||||
DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255."));
|
||||
} else if (difftemp <= 0.0) {
|
||||
// Temperature is below target temperature. Run fan at minimum speed.
|
||||
newPWMvalue = pwmMinimumValue;
|
||||
} else if (difftemp <= 0.5) {
|
||||
newPWMvalue = pwmMinimumValue + pwmStep;
|
||||
} else if (difftemp <= 1.0) {
|
||||
newPWMvalue = pwmMinimumValue + 2*pwmStep;
|
||||
} else if (difftemp <= 1.5) {
|
||||
newPWMvalue = pwmMinimumValue + 3*pwmStep;
|
||||
} else if (difftemp <= 2.0) {
|
||||
newPWMvalue = pwmMinimumValue + 4*pwmStep;
|
||||
} else if (difftemp <= 2.5) {
|
||||
newPWMvalue = pwmMinimumValue + 5*pwmStep;
|
||||
} else if (difftemp <= 3.0) {
|
||||
newPWMvalue = pwmMinimumValue + 6*pwmStep;
|
||||
}
|
||||
updateFanSpeed(newPWMvalue);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void setup() {
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
// This Usermod requires Temperature usermod
|
||||
tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE);
|
||||
#endif
|
||||
initTacho();
|
||||
initPWMfan();
|
||||
updateFanSpeed((minPWMValuePct * 255) / 100); // inital fan speed
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||
// interfaces here
|
||||
void connected() {}
|
||||
|
||||
/*
|
||||
* Da loop.
|
||||
*/
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
if ((now - msLastTachoMeasurement) < (tachoUpdateSec * 1000)) return;
|
||||
|
||||
updateTacho();
|
||||
setFanPWMbasedOnTemperature();
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
if (tachoPin < 0) return;
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
JsonArray data = user.createNestedArray(FPSTR(_name));
|
||||
data.add(last_rpm);
|
||||
data.add(F("rpm"));
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
//void addToJsonState(JsonObject& root) {
|
||||
//}
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
//void readFromJsonState(JsonObject& root) {
|
||||
// if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
//}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* If you want to force saving the current state, use serializeConfig() in your loop().
|
||||
*
|
||||
* CAUTION: serializeConfig() will initiate a filesystem write operation.
|
||||
* It might cause the LEDs to stutter and will cause flash wear if called too often.
|
||||
* Use it sparingly and always in the loop, never in network callbacks!
|
||||
*
|
||||
* addToConfig() will also not yet add your setting to one of the settings pages automatically.
|
||||
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
||||
*
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_pwmPin)] = pwmPin;
|
||||
top[FPSTR(_tachoPin)] = tachoPin;
|
||||
top[FPSTR(_tachoUpdateSec)] = tachoUpdateSec;
|
||||
top[FPSTR(_temperature)] = targetTemperature;
|
||||
top[FPSTR(_minPWMValuePct)] = minPWMValuePct;
|
||||
top[FPSTR(_IRQperRotation)] = numberOfInterrupsInOneSingleRotation;
|
||||
DEBUG_PRINTLN(F("Autosave config saved."));
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
|
||||
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
|
||||
*
|
||||
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
|
||||
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
|
||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject& root) {
|
||||
int8_t newTachoPin = tachoPin;
|
||||
int8_t newPwmPin = pwmPin;
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
newTachoPin = top[FPSTR(_tachoPin)] | newTachoPin;
|
||||
newPwmPin = top[FPSTR(_pwmPin)] | newPwmPin;
|
||||
tachoUpdateSec = top[FPSTR(_tachoUpdateSec)] | tachoUpdateSec;
|
||||
tachoUpdateSec = (uint8_t) max(1,(int)tachoUpdateSec); // bounds checking
|
||||
targetTemperature = top[FPSTR(_temperature)] | targetTemperature;
|
||||
minPWMValuePct = top[FPSTR(_minPWMValuePct)] | minPWMValuePct;
|
||||
minPWMValuePct = (uint8_t) min(100,max(0,(int)minPWMValuePct)); // bounds checking
|
||||
numberOfInterrupsInOneSingleRotation = top[FPSTR(_IRQperRotation)] | numberOfInterrupsInOneSingleRotation;
|
||||
numberOfInterrupsInOneSingleRotation = (uint8_t) max(1,(int)numberOfInterrupsInOneSingleRotation); // bounds checking
|
||||
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
tachoPin = newTachoPin;
|
||||
pwmPin = newPwmPin;
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing paramters from settings page
|
||||
if (tachoPin != newTachoPin || pwmPin != newPwmPin) {
|
||||
DEBUG_PRINTLN(F("Re-init pins."));
|
||||
// deallocate pin and release interrupts
|
||||
deinitTacho();
|
||||
deinitPWMfan();
|
||||
tachoPin = newTachoPin;
|
||||
pwmPin = newPwmPin;
|
||||
// initialise
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_IRQperRotation)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_PWM_FAN;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PWMFanUsermod::_name[] PROGMEM = "PWM-fan";
|
||||
const char PWMFanUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char PWMFanUsermod::_tachoPin[] PROGMEM = "tacho-pin";
|
||||
const char PWMFanUsermod::_pwmPin[] PROGMEM = "PWM-pin";
|
||||
const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C";
|
||||
const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s";
|
||||
const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent";
|
||||
const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation";
|
||||
@@ -3,6 +3,14 @@
|
||||
#include "src/dependencies/time/DS1307RTC.h"
|
||||
#include "wled.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
//Connect DS1307 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL))
|
||||
|
||||
class RTCUsermod : public Usermod {
|
||||
@@ -12,6 +20,8 @@ class RTCUsermod : public Usermod {
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { disabled = true; return; }
|
||||
time_t rtcTime = RTC.get();
|
||||
if (rtcTime) {
|
||||
toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC);
|
||||
@@ -22,12 +32,26 @@ class RTCUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (strip.isUpdating()) return;
|
||||
if (!disabled && toki.isTick()) {
|
||||
time_t t = toki.second();
|
||||
if (t != RTC.get()) RTC.set(t); //set RTC to NTP/UI-provided value
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("RTC");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_RTC;
|
||||
|
||||
76
usermods/RelayBlinds/index.htm
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Blinds</title>
|
||||
<script>
|
||||
strA = "";
|
||||
function send()
|
||||
{
|
||||
nocache = "&nocache=" + Math.random() * 1000000;
|
||||
var request = new XMLHttpRequest();
|
||||
// send HTTP request
|
||||
request.open("GET", "win/" + strA +nocache, true);
|
||||
request.send(null);
|
||||
strA = "";
|
||||
}
|
||||
function up()
|
||||
{
|
||||
strA = "&U0=2";
|
||||
send();
|
||||
}
|
||||
function down()
|
||||
{
|
||||
strA = "&U0=1";
|
||||
send();
|
||||
}
|
||||
function OpenSettings()
|
||||
{
|
||||
window.open("/settings", "_self");
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
background: linear-gradient(45deg,#0ca,#0ac);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
svg {
|
||||
width: 30vw;
|
||||
padding: 2vh;
|
||||
}
|
||||
.tool_box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
<style id="holderjs-style" type="text/css"></style></head>
|
||||
<body class=" __plain_text_READY__">
|
||||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<symbol id="icon-box-add" viewBox="0 0 32 32">
|
||||
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM16 26l-10-8h6v-6h8v6h6l-10 8zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-box-remove" viewBox="0 0 32 32">
|
||||
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM20 20v6h-8v-6h-6l10-8 10 8h-6zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-cog" viewBox="0 0 32 32">
|
||||
<path d="M29.181 19.070c-1.679-2.908-0.669-6.634 2.255-8.328l-3.145-5.447c-0.898 0.527-1.943 0.829-3.058 0.829-3.361 0-6.085-2.742-6.085-6.125h-6.289c0.008 1.044-0.252 2.103-0.811 3.070-1.679 2.908-5.411 3.897-8.339 2.211l-3.144 5.447c0.905 0.515 1.689 1.268 2.246 2.234 1.676 2.903 0.672 6.623-2.241 8.319l3.145 5.447c0.895-0.522 1.935-0.82 3.044-0.82 3.35 0 6.067 2.725 6.084 6.092h6.289c-0.003-1.034 0.259-2.080 0.811-3.038 1.676-2.903 5.399-3.894 8.325-2.219l3.145-5.447c-0.899-0.515-1.678-1.266-2.232-2.226zM16 22.479c-3.578 0-6.479-2.901-6.479-6.479s2.901-6.479 6.479-6.479c3.578 0 6.479 2.901 6.479 6.479s-2.901 6.479-6.479 6.479z"></path>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
<div id="tbB" class="tool_box">
|
||||
<svg id="upb" onclick="up()"><use xlink:href="#icon-box-remove"></use></svg>
|
||||
<svg id="dnb" onclick="down()"><use xlink:href="#icon-box-add"></use></svg>
|
||||
<svg id="stb" onclick="OpenSettings()"><use xlink:href="#icon-cog"></use></svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
usermods/RelayBlinds/presets.json
Normal file
@@ -0,0 +1 @@
|
||||
{"0":{},"2":{"n":"▲","win":"U0=2"},"1":{"n":"▼","win":"U0=1"}}
|
||||
8
usermods/RelayBlinds/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# RelayBlinds usermod
|
||||
|
||||
This simple usermod toggles two relay pins momentarily (default for 500ms) when `userVar0` is set.
|
||||
This can be used to e.g. "push" the buttons of a window blinds motor controller.
|
||||
|
||||
v1 usermod. Please replace usermod.cpp in the `wled00` directory with the one in this file.
|
||||
You may upload `index.htm` to `[WLED-IP]/edit` to replace the default lighting UI with a simple Up/Down button one.
|
||||
Also, a simple `presets.json` file is available, this makes the relay actions controllable via two presets to facilitate control e.g. via the default UI or Alexa.
|
||||
83
usermods/RelayBlinds/usermod.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "wled.h"
|
||||
|
||||
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
|
||||
|
||||
//gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
void userSetup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
||||
void userConnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Physical IO
|
||||
*/
|
||||
#define PIN_UP_RELAY 4
|
||||
#define PIN_DN_RELAY 5
|
||||
#define PIN_ON_TIME 500
|
||||
bool upActive = false, upActiveBefore = false, downActive = false, downActiveBefore = false;
|
||||
unsigned long upStartTime = 0, downStartTime = 0;
|
||||
|
||||
void handleRelay()
|
||||
{
|
||||
//up and down relays
|
||||
if (userVar0) {
|
||||
upActive = true;
|
||||
if (userVar0 == 1) {
|
||||
upActive = false;
|
||||
downActive = true;
|
||||
}
|
||||
userVar0 = 0;
|
||||
}
|
||||
|
||||
if (upActive)
|
||||
{
|
||||
if(!upActiveBefore)
|
||||
{
|
||||
pinMode(PIN_UP_RELAY, OUTPUT);
|
||||
digitalWrite(PIN_UP_RELAY, LOW);
|
||||
upActiveBefore = true;
|
||||
upStartTime = millis();
|
||||
DEBUG_PRINTLN("UPA");
|
||||
}
|
||||
if (millis()- upStartTime > PIN_ON_TIME)
|
||||
{
|
||||
upActive = false;
|
||||
DEBUG_PRINTLN("UPN");
|
||||
}
|
||||
} else if (upActiveBefore)
|
||||
{
|
||||
pinMode(PIN_UP_RELAY, INPUT);
|
||||
upActiveBefore = false;
|
||||
}
|
||||
|
||||
if (downActive)
|
||||
{
|
||||
if(!downActiveBefore)
|
||||
{
|
||||
pinMode(PIN_DN_RELAY, OUTPUT);
|
||||
digitalWrite(PIN_DN_RELAY, LOW);
|
||||
downActiveBefore = true;
|
||||
downStartTime = millis();
|
||||
}
|
||||
if (millis()- downStartTime > PIN_ON_TIME)
|
||||
{
|
||||
downActive = false;
|
||||
}
|
||||
} else if (downActiveBefore)
|
||||
{
|
||||
pinMode(PIN_DN_RELAY, INPUT);
|
||||
downActiveBefore = false;
|
||||
}
|
||||
}
|
||||
|
||||
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
|
||||
void userLoop()
|
||||
{
|
||||
handleRelay();
|
||||
}
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
// 10 bits
|
||||
#ifndef USERMOD_SN_PHOTORESISTOR_ADC_PRECISION
|
||||
#define USERMOD_SN_PHOTORESISTOR_ADC_PRECISION 1024.0
|
||||
#define USERMOD_SN_PHOTORESISTOR_ADC_PRECISION 1024.0f
|
||||
#endif
|
||||
|
||||
// resistor size 10K hms
|
||||
#ifndef USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE
|
||||
#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0
|
||||
#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0f
|
||||
#endif
|
||||
|
||||
// only report if differance grater than offset value
|
||||
@@ -123,6 +123,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t getLastLDRValue()
|
||||
{
|
||||
return lastLDRValue;
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
|
||||
@@ -12,7 +12,7 @@ This usermod allow to use 240x240 display to display following:
|
||||
## Hardware
|
||||
|
||||
***
|
||||

|
||||

|
||||
|
||||
## Library used
|
||||
|
||||
|
||||
@@ -118,11 +118,11 @@ class St7789DisplayUsermod : public Usermod {
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownMode != strip.getMode())
|
||||
else if (knownMode != strip.getMainSegment().mode)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownPalette != strip.getSegment(0).palette)
|
||||
else if (knownPalette != strip.getMainSegment().palette)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
@@ -148,8 +148,8 @@ class St7789DisplayUsermod : public Usermod {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextSize(2);
|
||||
|
||||
69
usermods/Si7021_MQTT_HA/readme.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Si7021 to MQTT (with Home Assistant Auto Discovery) usermod
|
||||
|
||||
This usermod implements support for [Si7021 I²C temperature and humidity sensors](https://www.silabs.com/documents/public/data-sheets/Si7021-A20.pdf).
|
||||
|
||||
The sensor data will *not* be shown on the WLED UI (so far) but published via MQTT to WLED's "build in" MQTT device topic.
|
||||
|
||||
```
|
||||
temperature: $mqttDeviceTopic/si7021_temperature
|
||||
humidity: $mqttDeviceTopic/si7021_humidity
|
||||
```
|
||||
|
||||
Additionally the following sensors can be published:
|
||||
|
||||
```
|
||||
heat_index: $mqttDeviceTopic/si7021_heat_index
|
||||
dew_point: $mqttDeviceTopic/si7021_dew_point
|
||||
absolute_humidity: $mqttDeviceTopic/si7021_absolute_humidity
|
||||
```
|
||||
|
||||
Sensor data will be updated/send every 60 seconds.
|
||||
|
||||
This usermod also supports Home Assistant Auto Discovery.
|
||||
|
||||
## Settings via Usermod Setup
|
||||
|
||||
- `enabled`: Enables this usermod
|
||||
- `Send Dew Point, Abs. Humidity and Heat Index`: Enables additional sensors
|
||||
- `Home Assistant MQTT Auto-Discovery`: Enables Home Assistant Auto Discovery
|
||||
|
||||
# Installation
|
||||
|
||||
## Hardware
|
||||
|
||||
Attach the Si7021 sensor to the I²C interface.
|
||||
|
||||
Default PINs ESP32:
|
||||
|
||||
```
|
||||
SCL_PIN = 22;
|
||||
SDA_PIN = 21;
|
||||
```
|
||||
|
||||
Default PINs ESP8266:
|
||||
|
||||
```
|
||||
SCL_PIN = 5;
|
||||
SDA_PIN = 4;
|
||||
```
|
||||
|
||||
## Software
|
||||
|
||||
Add to `build_flags` in platformio.ini:
|
||||
|
||||
```
|
||||
-D USERMOD_SI7021_MQTT_HA
|
||||
```
|
||||
|
||||
Add to `lib_deps` in platformio.ini:
|
||||
|
||||
```
|
||||
adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||
BME280@~3.0.0
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
- Aircoookie for making WLED
|
||||
- Other usermod creators for example code (`sensors_to_mqtt` and `multi_relay` especially)
|
||||
- You, for reading this
|
||||
236
usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h
Normal file
@@ -0,0 +1,236 @@
|
||||
#pragma once
|
||||
|
||||
// this is remixed from usermod_v2_SensorsToMqtt.h (sensors_to_mqtt usermod)
|
||||
// and usermod_multi_relay.h (multi_relay usermod)
|
||||
|
||||
#include "wled.h"
|
||||
#include <Adafruit_Si7021.h>
|
||||
#include <EnvironmentCalculations.h> // EnvironmentCalculations::HeatIndex(), ::DewPoint(), ::AbsoluteHumidity()
|
||||
|
||||
Adafruit_Si7021 si7021;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
|
||||
uint8_t SCL_PIN = 22;
|
||||
uint8_t SDA_PIN = 21;
|
||||
#else //ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
#endif
|
||||
|
||||
class Si7021_MQTT_HA : public Usermod
|
||||
{
|
||||
private:
|
||||
bool sensorInitialized = false;
|
||||
bool mqttInitialized = false;
|
||||
float sensorTemperature = 0;
|
||||
float sensorHumidity = 0;
|
||||
float sensorHeatIndex = 0;
|
||||
float sensorDewPoint = 0;
|
||||
float sensorAbsoluteHumidity= 0;
|
||||
String mqttTemperatureTopic = "";
|
||||
String mqttHumidityTopic = "";
|
||||
String mqttHeatIndexTopic = "";
|
||||
String mqttDewPointTopic = "";
|
||||
String mqttAbsoluteHumidityTopic = "";
|
||||
unsigned long nextMeasure = 0;
|
||||
bool enabled = false;
|
||||
bool haAutoDiscovery = true;
|
||||
bool sendAdditionalSensors = true;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _sendAdditionalSensors[];
|
||||
static const char _haAutoDiscovery[];
|
||||
|
||||
void _initializeSensor()
|
||||
{
|
||||
sensorInitialized = si7021.begin();
|
||||
Serial.printf("Si7021_MQTT_HA: sensorInitialized = %d\n", sensorInitialized);
|
||||
}
|
||||
|
||||
void _initializeMqtt()
|
||||
{
|
||||
mqttTemperatureTopic = String(mqttDeviceTopic) + "/si7021_temperature";
|
||||
mqttHumidityTopic = String(mqttDeviceTopic) + "/si7021_humidity";
|
||||
mqttHeatIndexTopic = String(mqttDeviceTopic) + "/si7021_heat_index";
|
||||
mqttDewPointTopic = String(mqttDeviceTopic) + "/si7021_dew_point";
|
||||
mqttAbsoluteHumidityTopic = String(mqttDeviceTopic) + "/si7021_absolute_humidity";
|
||||
|
||||
// Update and publish sensor data
|
||||
_updateSensorData();
|
||||
_publishSensorData();
|
||||
|
||||
if (haAutoDiscovery) {
|
||||
_publishHAMqttSensor("temperature", "Temperature", mqttTemperatureTopic, "temperature", "°C");
|
||||
_publishHAMqttSensor("humidity", "Humidity", mqttHumidityTopic, "humidity", "%");
|
||||
if (sendAdditionalSensors) {
|
||||
_publishHAMqttSensor("heat_index", "Heat Index", mqttHeatIndexTopic, "temperature", "°C");
|
||||
_publishHAMqttSensor("dew_point", "Dew Point", mqttDewPointTopic, "", "°C");
|
||||
_publishHAMqttSensor("absolute_humidity", "Absolute Humidity", mqttAbsoluteHumidityTopic, "", "g/m³");
|
||||
}
|
||||
}
|
||||
|
||||
mqttInitialized = true;
|
||||
}
|
||||
|
||||
void _publishHAMqttSensor(
|
||||
const String &name,
|
||||
const String &friendly_name,
|
||||
const String &state_topic,
|
||||
const String &deviceClass,
|
||||
const String &unitOfMeasurement)
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
String topic = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config";
|
||||
|
||||
StaticJsonDocument<300> doc;
|
||||
|
||||
doc["name"] = String(serverDescription) + " " + friendly_name;
|
||||
doc["state_topic"] = state_topic;
|
||||
doc["unique_id"] = String(mqttClientID) + name;
|
||||
if (unitOfMeasurement != "")
|
||||
doc["unit_of_measurement"] = unitOfMeasurement;
|
||||
if (deviceClass != "")
|
||||
doc["device_class"] = deviceClass;
|
||||
doc["expire_after"] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
|
||||
device["name"] = String(serverDescription);
|
||||
device["model"] = "WLED";
|
||||
device["manufacturer"] = "Aircoookie";
|
||||
device["identifiers"] = String("wled-") + String(serverDescription);
|
||||
device["sw_version"] = VERSION;
|
||||
|
||||
String payload;
|
||||
serializeJson(doc, payload);
|
||||
|
||||
mqtt->publish(topic.c_str(), 0, true, payload.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSensorData()
|
||||
{
|
||||
sensorTemperature = si7021.readTemperature();
|
||||
sensorHumidity = si7021.readHumidity();
|
||||
|
||||
// Serial.print("Si7021_MQTT_HA: Temperature: ");
|
||||
// Serial.print(sensorTemperature, 2);
|
||||
// Serial.print("\tHumidity: ");
|
||||
// Serial.print(sensorHumidity, 2);
|
||||
|
||||
if (sendAdditionalSensors) {
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
sensorAbsoluteHumidity = EnvironmentCalculations::AbsoluteHumidity(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
|
||||
// Serial.print("\tHeat Index: ");
|
||||
// Serial.print(sensorHeatIndex, 2);
|
||||
// Serial.print("\tDew Point: ");
|
||||
// Serial.print(sensorDewPoint, 2);
|
||||
// Serial.print("\tAbsolute Humidity: ");
|
||||
// Serial.println(sensorAbsoluteHumidity, 2);
|
||||
}
|
||||
// else
|
||||
// Serial.println("");
|
||||
}
|
||||
|
||||
void _publishSensorData()
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
mqtt->publish(mqttTemperatureTopic.c_str(), 0, false, String(sensorTemperature).c_str());
|
||||
mqtt->publish(mqttHumidityTopic.c_str(), 0, false, String(sensorHumidity).c_str());
|
||||
if (sendAdditionalSensors) {
|
||||
mqtt->publish(mqttHeatIndexTopic.c_str(), 0, false, String(sensorHeatIndex).c_str());
|
||||
mqtt->publish(mqttDewPointTopic.c_str(), 0, false, String(sensorDewPoint).c_str());
|
||||
mqtt->publish(mqttAbsoluteHumidityTopic.c_str(), 0, false, String(sensorAbsoluteHumidity).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_sendAdditionalSensors)] = sendAdditionalSensors;
|
||||
top[FPSTR(_haAutoDiscovery)] = haAutoDiscovery;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||
configComplete &= getJsonValue(top[FPSTR(_sendAdditionalSensors)], sendAdditionalSensors);
|
||||
configComplete &= getJsonValue(top[FPSTR(_haAutoDiscovery)], haAutoDiscovery);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
_initializeMqtt();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
Serial.println("Si7021_MQTT_HA: Starting!");
|
||||
Wire.begin(SDA_PIN, SCL_PIN);
|
||||
Serial.println("Si7021_MQTT_HA: Initializing sensors.. ");
|
||||
_initializeSensor();
|
||||
}
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected.
|
||||
void connected()
|
||||
{
|
||||
nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
yield();
|
||||
if (!enabled || strip.isUpdating()) return; // !sensorFound ||
|
||||
|
||||
unsigned long tempTimer = millis();
|
||||
|
||||
if (tempTimer > nextMeasure) {
|
||||
nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds
|
||||
|
||||
if (!sensorInitialized) {
|
||||
Serial.println("Si7021_MQTT_HA: Error! Sensors not initialized in loop()!");
|
||||
_initializeSensor();
|
||||
return; // lets try again next loop
|
||||
}
|
||||
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
if (!mqttInitialized)
|
||||
_initializeMqtt();
|
||||
|
||||
// Update and publish sensor data
|
||||
_updateSensorData();
|
||||
_publishSensorData();
|
||||
}
|
||||
else {
|
||||
Serial.println("Si7021_MQTT_HA: Missing MQTT connection. Not publishing data");
|
||||
mqttInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_SI7021_MQTT_HA;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char Si7021_MQTT_HA::_name[] PROGMEM = "Si7021 MQTT (Home Assistant)";
|
||||
const char Si7021_MQTT_HA::_enabled[] PROGMEM = "enabled";
|
||||
const char Si7021_MQTT_HA::_sendAdditionalSensors[] PROGMEM = "Send Dew Point, Abs. Humidity and Heat Index";
|
||||
const char Si7021_MQTT_HA::_haAutoDiscovery[] PROGMEM = "Home Assistant MQTT Auto-Discovery";
|
||||
@@ -110,9 +110,9 @@ void userLoop() {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
} else if (knownMode != strip.getMainSegment().mode) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
} else if (knownPalette != strip.getMainSegment().palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
@@ -136,8 +136,8 @@ void userLoop() {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextSize(2);
|
||||
|
||||
@@ -37,12 +37,12 @@ class UsermodTemperature : public Usermod {
|
||||
// used to determine when we can read the sensors temperature
|
||||
// we have to wait at least 93.75 ms after requestTemperatures() is called
|
||||
unsigned long lastTemperaturesRequest;
|
||||
float temperature = -100; // default to -100, DS18B20 only goes down to -50C
|
||||
float temperature;
|
||||
// indicates requestTemperatures has been called but the sensor measurement is not complete
|
||||
bool waitingForConversion = false;
|
||||
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
|
||||
// temperature if flashed to a board without a sensor attached
|
||||
bool sensorFound = false;
|
||||
byte sensorFound;
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
@@ -54,34 +54,56 @@ class UsermodTemperature : public Usermod {
|
||||
|
||||
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
|
||||
float readDallas() {
|
||||
byte i;
|
||||
byte data[2];
|
||||
byte data[9];
|
||||
int16_t result; // raw data from sensor
|
||||
if (!oneWire->reset()) return -127.0f; // send reset command and fail fast
|
||||
oneWire->skip(); // skip ROM
|
||||
oneWire->write(0xBE); // read (temperature) from EEPROM
|
||||
for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature
|
||||
for (i=2; i < 8; i++) oneWire->read(); // read unused bytes
|
||||
result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
|
||||
if (data[1]&0x80) result |= 0xFF00; // fix negative value
|
||||
oneWire->reset();
|
||||
oneWire->skip(); // skip ROM
|
||||
oneWire->write(0x44,parasite); // request new temperature reading (without parasite power)
|
||||
return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f);
|
||||
float retVal = -127.0f;
|
||||
if (oneWire->reset()) { // if reset() fails there are no OneWire devices
|
||||
oneWire->skip(); // skip ROM
|
||||
oneWire->write(0xBE); // read (temperature) from EEPROM
|
||||
oneWire->read_bytes(data, 9); // first 2 bytes contain temperature
|
||||
#ifdef WLED_DEBUG
|
||||
if (OneWire::crc8(data,8) != data[8]) {
|
||||
DEBUG_PRINTLN(F("CRC error reading temperature."));
|
||||
for (byte i=0; i < 9; i++) DEBUG_PRINTF("0x%02X ", data[i]);
|
||||
DEBUG_PRINT(F(" => "));
|
||||
DEBUG_PRINTF("0x%02X\n", OneWire::crc8(data,8));
|
||||
}
|
||||
#endif
|
||||
switch(sensorFound) {
|
||||
case 0x10: // DS18S20 has 9-bit precision
|
||||
result = (data[1] << 8) | data[0];
|
||||
retVal = float(result) * 0.5f;
|
||||
break;
|
||||
case 0x22: // DS18B20
|
||||
case 0x28: // DS1822
|
||||
case 0x3B: // DS1825
|
||||
case 0x42: // DS28EA00
|
||||
result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning
|
||||
if (data[1] & 0x80) result |= 0xF000; // fix negative value
|
||||
retVal = float(result) + ((data[0] & 0x08) ? 0.5f : 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (byte i=1; i<9; i++) data[0] &= data[i];
|
||||
return data[0]==0xFF ? -127.0f : retVal;
|
||||
}
|
||||
|
||||
void requestTemperatures() {
|
||||
readDallas();
|
||||
DEBUG_PRINTLN(F("Requesting temperature."));
|
||||
oneWire->reset();
|
||||
oneWire->skip(); // skip ROM
|
||||
oneWire->write(0x44,parasite); // request new temperature reading (TODO: parasite would need special handling)
|
||||
lastTemperaturesRequest = millis();
|
||||
waitingForConversion = true;
|
||||
DEBUG_PRINTLN(F("Requested temperature."));
|
||||
}
|
||||
|
||||
void readTemperature() {
|
||||
temperature = readDallas();
|
||||
lastMeasurement = millis();
|
||||
waitingForConversion = false;
|
||||
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
|
||||
//DEBUG_PRINTF("Read temperature %2.1f.\n", temperature); // does not work properly on 8266
|
||||
DEBUG_PRINT(F("Read temperature "));
|
||||
DEBUG_PRINTLN(temperature);
|
||||
}
|
||||
|
||||
bool findSensor() {
|
||||
@@ -100,10 +122,13 @@ class UsermodTemperature : public Usermod {
|
||||
case 0x3B: // DS1825
|
||||
case 0x42: // DS28EA00
|
||||
DEBUG_PRINTLN(F("Sensor found."));
|
||||
sensorFound = deviceAddress[0];
|
||||
DEBUG_PRINTF("0x%02X\n", sensorFound);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F("Sensor NOT found."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,16 +136,16 @@ class UsermodTemperature : public Usermod {
|
||||
|
||||
void setup() {
|
||||
int retries = 10;
|
||||
sensorFound = 0;
|
||||
temperature = -127.0f; // default to -127, DS18B20 only goes down to -50C
|
||||
if (enabled) {
|
||||
// config says we are enabled
|
||||
DEBUG_PRINTLN(F("Allocating temperature pin..."));
|
||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) {
|
||||
oneWire = new OneWire(temperaturePin);
|
||||
if (!oneWire->reset()) {
|
||||
sensorFound = false; // resetting 1-Wire bus yielded an error
|
||||
} else {
|
||||
while ((sensorFound=findSensor()) && retries--) {
|
||||
if (oneWire->reset()) {
|
||||
while (!findSensor() && retries--) {
|
||||
delay(25); // try to find sensor
|
||||
}
|
||||
}
|
||||
@@ -129,7 +154,6 @@ class UsermodTemperature : public Usermod {
|
||||
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
|
||||
}
|
||||
temperaturePin = -1; // allocation failed
|
||||
sensorFound = false;
|
||||
}
|
||||
}
|
||||
lastMeasurement = millis() - readingInterval + 10000;
|
||||
@@ -137,8 +161,9 @@ class UsermodTemperature : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
if (!enabled || !sensorFound || strip.isUpdating()) return;
|
||||
|
||||
static uint8_t errorCount = 0;
|
||||
unsigned long now = millis();
|
||||
|
||||
// check to see if we are due for taking a measurement
|
||||
@@ -154,20 +179,26 @@ class UsermodTemperature : public Usermod {
|
||||
}
|
||||
|
||||
// we were waiting for a conversion to complete, have we waited log enough?
|
||||
if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) {
|
||||
if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) {
|
||||
readTemperature();
|
||||
if (getTemperatureC() < -100.0f) {
|
||||
if (++errorCount > 10) sensorFound = 0;
|
||||
lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms
|
||||
return;
|
||||
}
|
||||
errorCount = 0;
|
||||
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
if (-100 <= temperature) {
|
||||
if (temperature > -100.0f) {
|
||||
// dont publish super low temperature as the graph will get messed up
|
||||
// the DallasTemperature library returns -127C or -196.6F when problem
|
||||
// reading the sensor
|
||||
strcat_P(subuf, PSTR("/temperature"));
|
||||
mqtt->publish(subuf, 0, false, String(temperature).c_str());
|
||||
mqtt->publish(subuf, 0, false, String(getTemperatureC()).c_str());
|
||||
strcat_P(subuf, PSTR("_f"));
|
||||
mqtt->publish(subuf, 0, false, String((float)temperature * 1.8f + 32).c_str());
|
||||
mqtt->publish(subuf, 0, false, String(getTemperatureF()).c_str());
|
||||
} else {
|
||||
// publish something else to indicate status?
|
||||
}
|
||||
@@ -200,13 +231,13 @@ class UsermodTemperature : public Usermod {
|
||||
JsonArray temp = user.createNestedArray(FPSTR(_name));
|
||||
//temp.add(F("Loaded."));
|
||||
|
||||
if (temperature <= -100.0 || (!sensorFound && temperature == -1.0)) {
|
||||
if (temperature <= -100.0f) {
|
||||
temp.add(0);
|
||||
temp.add(F(" Sensor Error!"));
|
||||
return;
|
||||
}
|
||||
|
||||
temp.add(degC ? temperature : (float)temperature * 1.8f + 32);
|
||||
temp.add(degC ? getTemperatureC() : getTemperatureF());
|
||||
if (degC) temp.add(F("°C"));
|
||||
else temp.add(F("°F"));
|
||||
}
|
||||
@@ -250,23 +281,21 @@ class UsermodTemperature : public Usermod {
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
||||
int8_t newTemperaturePin = temperaturePin;
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
newTemperaturePin = top["pin"] | newTemperaturePin;
|
||||
// newTemperaturePin = min(33,max(-1,(int)newTemperaturePin)); // bounds check
|
||||
degC = top["degC"] | degC;
|
||||
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
|
||||
readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
|
||||
parasite = top[FPSTR(_parasite)] | parasite;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
temperaturePin = newTemperaturePin;
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
#include <Wire.h>
|
||||
#include <VL53L0X.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
#ifndef VL53L0X_MAX_RANGE_MM
|
||||
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions
|
||||
#endif
|
||||
@@ -42,6 +50,7 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
//Private class members. You can declare variables and functions only accessible to your usermod here
|
||||
unsigned long lastTime = 0;
|
||||
VL53L0X sensor;
|
||||
bool enabled = true;
|
||||
|
||||
bool wasMotionBefore = false;
|
||||
bool isLongMotion = false;
|
||||
@@ -50,6 +59,8 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
|
||||
Wire.begin();
|
||||
|
||||
sensor.setTimeout(150);
|
||||
@@ -63,6 +74,7 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
|
||||
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
if (millis() - lastTime > VL53L0X_DELAY_MS)
|
||||
{
|
||||
lastTime = millis();
|
||||
@@ -94,7 +106,7 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
// set brightness according to range
|
||||
bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET);
|
||||
DEBUG_PRINTF(F("new brightness: %d"), bri);
|
||||
colorUpdated(1);
|
||||
stateUpdated(1);
|
||||
}
|
||||
} else if (wasMotionBefore) { //released
|
||||
long dur = millis() - motionStartTime;
|
||||
@@ -110,6 +122,19 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("VL53L0x");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
|
||||
@@ -137,9 +137,9 @@ void userLoop() {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
} else if (knownMode != strip.getMainSegment().mode) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
} else if (knownPalette != strip.getMainSegment().palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
@@ -163,8 +163,8 @@ void userLoop() {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
|
||||
@@ -143,9 +143,9 @@ void userLoop() {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
} else if (knownMode != strip.getMainSegment().mode) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
} else if (knownPalette != strip.getMainSegment().palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
@@ -169,8 +169,8 @@ void userLoop() {
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
|
||||
@@ -54,35 +54,27 @@ void userLoop()
|
||||
switch (myKey) {
|
||||
case '1':
|
||||
applyPreset(1);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case '2':
|
||||
applyPreset(2);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case '3':
|
||||
applyPreset(3);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case '4':
|
||||
applyPreset(4);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case '5':
|
||||
applyPreset(5);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case '6':
|
||||
applyPreset(6);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case 'A':
|
||||
applyPreset(7);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
case 'B':
|
||||
applyPreset(8);
|
||||
colorUpdated(CALL_MODE_FX_CHANGED);
|
||||
break;
|
||||
|
||||
case '7':
|
||||
|
||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 46 KiB |
BIN
usermods/battery_status_basic/assets/battery_info_screen.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
@@ -2,16 +2,25 @@
|
||||
|
||||
This Usermod allows you to monitor the battery level of your battery powered project.
|
||||
|
||||
You can see the battery level in the `info modal` right under the `estimated current`.
|
||||
You can see the battery level and voltage in the `info modal`.
|
||||
|
||||
For this to work the positive side of the (18650) battery must be connected to pin `A0` of the d1mini/esp8266 with a 100k ohm resistor (see [Useful Links](#useful-links)).
|
||||
|
||||
If you have a esp32 board it is best to connect the positive side of the battery to ADC1 (GPIO32 - GPIO39)
|
||||
|
||||
<p align="center">
|
||||
<img width="300" src="assets/battery_info_screen.png">
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
|
||||
define `USERMOD_BATTERY_STATUS_BASIC` in `my_config.h`
|
||||
|
||||
### Basic wiring diagram
|
||||
<p align="center">
|
||||
<img width="300" src="assets/battery_connection_schematic_01.png">
|
||||
</p>
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this user mod included wled00\usermods_list.cpp
|
||||
@@ -45,6 +54,11 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.
|
||||
* https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/
|
||||
|
||||
## Change Log
|
||||
2021-09-02
|
||||
* added "Battery voltage" to info
|
||||
* added circuit diagram to readme
|
||||
* added MQTT support, sending battery voltage
|
||||
* minor fixes
|
||||
|
||||
2021-08-15
|
||||
* changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
#ifndef USERMOD_BATTERY_ADC_PRECISION
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// 12 bits
|
||||
#define USERMOD_BATTERY_ADC_PRECISION 4095.0
|
||||
#define USERMOD_BATTERY_ADC_PRECISION 4095.0f
|
||||
#else
|
||||
// 10 bits
|
||||
#define USERMOD_BATTERY_ADC_PRECISION 1024.0
|
||||
#define USERMOD_BATTERY_ADC_PRECISION 1024.0f
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// the frequency to check the battery, 1 minute
|
||||
// the frequency to check the battery, 30 sec
|
||||
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
|
||||
#endif
|
||||
@@ -39,11 +39,11 @@
|
||||
// https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop
|
||||
// Discharge voltage: 2.5 volt + .1 for personal safety
|
||||
#ifndef USERMOD_BATTERY_MIN_VOLTAGE
|
||||
#define USERMOD_BATTERY_MIN_VOLTAGE 2.6
|
||||
#define USERMOD_BATTERY_MIN_VOLTAGE 2.6f
|
||||
#endif
|
||||
|
||||
#ifndef USERMOD_BATTERY_MAX_VOLTAGE
|
||||
#define USERMOD_BATTERY_MAX_VOLTAGE 4.2
|
||||
#define USERMOD_BATTERY_MAX_VOLTAGE 4.2f
|
||||
#endif
|
||||
|
||||
class UsermodBatteryBasic : public Usermod
|
||||
@@ -53,7 +53,8 @@ class UsermodBatteryBasic : public Usermod
|
||||
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
|
||||
// how often to read the battery voltage
|
||||
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
|
||||
unsigned long lastTime = 0;
|
||||
unsigned long nextReadTime = 0;
|
||||
unsigned long lastReadTime = 0;
|
||||
// battery min. voltage
|
||||
float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE;
|
||||
// battery max. voltage
|
||||
@@ -68,6 +69,7 @@ class UsermodBatteryBasic : public Usermod
|
||||
// mapped battery level based on voltage
|
||||
long batteryLevel = 0;
|
||||
bool initDone = false;
|
||||
bool initializing = true;
|
||||
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
@@ -82,6 +84,19 @@ class UsermodBatteryBasic : public Usermod
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
float truncate(float val, byte dec)
|
||||
{
|
||||
float x = val * pow(10, dec);
|
||||
float y = round(x);
|
||||
float z = x - y;
|
||||
if ((int)z == 5)
|
||||
{
|
||||
y++;
|
||||
}
|
||||
x = y / pow(10, dec);
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public:
|
||||
@@ -107,6 +122,9 @@ class UsermodBatteryBasic : public Usermod
|
||||
pinMode(batteryPin, INPUT);
|
||||
#endif
|
||||
|
||||
nextReadTime = millis() + readingInterval;
|
||||
lastReadTime = millis();
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -129,26 +147,38 @@ class UsermodBatteryBasic : public Usermod
|
||||
{
|
||||
if(strip.isUpdating()) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
|
||||
if (now - lastTime >= readingInterval) {
|
||||
if (millis() < nextReadTime) return;
|
||||
|
||||
// read battery raw input
|
||||
rawValue = analogRead(batteryPin);
|
||||
|
||||
// calculate the voltage
|
||||
voltage = (rawValue / adcPrecision) * maxBatteryVoltage ;
|
||||
nextReadTime = millis() + readingInterval;
|
||||
lastReadTime = millis();
|
||||
initializing = false;
|
||||
|
||||
// translate battery voltage into percentage
|
||||
/*
|
||||
the standard "map" function doesn't work
|
||||
https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom
|
||||
*/
|
||||
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100);
|
||||
// read battery raw input
|
||||
rawValue = analogRead(batteryPin);
|
||||
|
||||
lastTime = now;
|
||||
// calculate the voltage
|
||||
voltage = (rawValue / adcPrecision) * maxBatteryVoltage ;
|
||||
// check if voltage is within specified voltage range
|
||||
voltage = voltage<minBatteryVoltage||voltage>maxBatteryVoltage?-1.0f:voltage;
|
||||
|
||||
// translate battery voltage into percentage
|
||||
/*
|
||||
the standard "map" function doesn't work
|
||||
https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom
|
||||
*/
|
||||
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100);
|
||||
|
||||
|
||||
// SmartHome stuff
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/voltage"));
|
||||
mqtt->publish(subuf, 0, false, String(voltage).c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -163,9 +193,31 @@ class UsermodBatteryBasic : public Usermod
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray battery = user.createNestedArray("Battery level");
|
||||
battery.add(batteryLevel);
|
||||
battery.add(F(" %"));
|
||||
// info modal display names
|
||||
JsonArray batteryPercentage = user.createNestedArray("Battery level");
|
||||
JsonArray batteryVoltage = user.createNestedArray("Battery voltage");
|
||||
|
||||
if (initializing) {
|
||||
batteryPercentage.add((nextReadTime - millis()) / 1000);
|
||||
batteryPercentage.add(" sec");
|
||||
batteryVoltage.add((nextReadTime - millis()) / 1000);
|
||||
batteryVoltage.add(" sec");
|
||||
return;
|
||||
}
|
||||
|
||||
if(batteryLevel < 0) {
|
||||
batteryPercentage.add(F("invalid"));
|
||||
} else {
|
||||
batteryPercentage.add(batteryLevel);
|
||||
}
|
||||
batteryPercentage.add(F(" %"));
|
||||
|
||||
if(voltage < 0) {
|
||||
batteryVoltage.add(F("invalid"));
|
||||
} else {
|
||||
batteryVoltage.add(truncate(voltage, 2));
|
||||
}
|
||||
batteryVoltage.add(F(" V"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,515 +0,0 @@
|
||||
//this code is a modified version of https://github.com/Makuna/NeoPixelBus/issues/103
|
||||
#ifndef NpbWrapper_h
|
||||
#define NpbWrapper_h
|
||||
|
||||
// make sure we're using esp32 platform
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#error This version of NbpWrapper.h only works with ESP32 hardware.
|
||||
#endif
|
||||
|
||||
#ifndef NUM_STRIPS
|
||||
#error Need to define number of LED strips using build flag -D NUM_STRIPS=4 for 4 LED strips
|
||||
#endif
|
||||
|
||||
#ifndef PIXEL_COUNTS
|
||||
#error Need to define pixel counts using build flag -D PIXEL_COUNTS="25, 25, 25, 25" for 4 LED strips with 25 LEDs each
|
||||
#endif
|
||||
|
||||
#ifndef DATA_PINS
|
||||
#error Need to define data pins using build flag -D DATA_PINS="1, 2, 3, 4" if LED strips are on data pins 1, 2, 3, and 4
|
||||
#endif
|
||||
|
||||
// //PIN CONFIGURATION
|
||||
#ifndef LEDPIN
|
||||
#define LEDPIN 1 // Legacy pin def required by some other portions of code. This pin is not used do drive LEDs.
|
||||
#endif
|
||||
|
||||
#ifndef IRPIN
|
||||
#define IRPIN -1 //infrared pin (-1 to disable) MagicHome: 4, H801 Wifi: 0
|
||||
#endif
|
||||
|
||||
#ifndef RLYPIN
|
||||
#define RLYPIN -1 //pin for relay, will be set HIGH if LEDs are on (-1 to disable). Also usable for standby leds, triggers,...
|
||||
#endif
|
||||
|
||||
#ifndef AUXPIN
|
||||
#define AUXPIN -1 //debug auxiliary output pin (-1 to disable)
|
||||
#endif
|
||||
|
||||
#ifndef RLYMDE
|
||||
#define RLYMDE 1 //mode for relay, 0: LOW if LEDs are on 1: HIGH if LEDs are on
|
||||
#endif
|
||||
|
||||
#include <NeoPixelBrightnessBus.h>
|
||||
#include "const.h"
|
||||
|
||||
const uint8_t numStrips = NUM_STRIPS; // max 8 strips allowed on esp32
|
||||
const uint16_t pixelCounts[numStrips] = {PIXEL_COUNTS}; // number of pixels on each strip
|
||||
const uint8_t dataPins[numStrips] = {DATA_PINS}; // change these pins based on your board
|
||||
|
||||
#define PIXELFEATURE3 NeoGrbFeature
|
||||
#define PIXELFEATURE4 NeoGrbwFeature
|
||||
|
||||
// ESP32 has 8 RMT interfaces available, each of which can drive a strip of pixels
|
||||
// Convenience #defines for creating NeoPixelBrightnessBus on each RMT interface for both GRB and GRBW LED strips
|
||||
#define NeoPixelBrightnessBusGrbRmt0 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt0Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt1 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt1Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt2 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt2Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt3 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt3Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt4 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt4Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt5 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt5Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt6 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt6Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbRmt7 NeoPixelBrightnessBus<PIXELFEATURE3, NeoEsp32Rmt7Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt0 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt0Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt1 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt1Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt2 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt2Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt3 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt3Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt4 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt4Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt5 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt5Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt6 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt6Ws2812xMethod>
|
||||
#define NeoPixelBrightnessBusGrbwRmt7 NeoPixelBrightnessBus<PIXELFEATURE4, NeoEsp32Rmt7Ws2812xMethod>
|
||||
|
||||
enum NeoPixelType
|
||||
{
|
||||
NeoPixelType_None = 0,
|
||||
NeoPixelType_Grb = 1,
|
||||
NeoPixelType_Grbw = 2,
|
||||
NeoPixelType_End = 3
|
||||
};
|
||||
|
||||
class NeoPixelWrapper
|
||||
{
|
||||
public:
|
||||
NeoPixelWrapper() :
|
||||
_type(NeoPixelType_None)
|
||||
{
|
||||
// On initialization fill in the pixelStripStartIdx array with the beginning index of each strip
|
||||
// relative to th entire array.
|
||||
uint16_t totalPixels = 0;
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
pixelStripStartIdx[idx] = totalPixels;
|
||||
totalPixels += pixelCounts[idx];
|
||||
}
|
||||
}
|
||||
|
||||
~NeoPixelWrapper()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void Begin(NeoPixelType type, uint16_t pixelCount)
|
||||
{
|
||||
|
||||
cleanup();
|
||||
|
||||
_type = type;
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrb0 = new NeoPixelBrightnessBusGrbRmt0(pixelCounts[idx], dataPins[idx]); pGrb0->Begin(); break;
|
||||
case 1: pGrb1 = new NeoPixelBrightnessBusGrbRmt1(pixelCounts[idx], dataPins[idx]); pGrb1->Begin(); break;
|
||||
case 2: pGrb2 = new NeoPixelBrightnessBusGrbRmt2(pixelCounts[idx], dataPins[idx]); pGrb2->Begin(); break;
|
||||
case 3: pGrb3 = new NeoPixelBrightnessBusGrbRmt3(pixelCounts[idx], dataPins[idx]); pGrb3->Begin(); break;
|
||||
case 4: pGrb4 = new NeoPixelBrightnessBusGrbRmt4(pixelCounts[idx], dataPins[idx]); pGrb4->Begin(); break;
|
||||
case 5: pGrb5 = new NeoPixelBrightnessBusGrbRmt5(pixelCounts[idx], dataPins[idx]); pGrb5->Begin(); break;
|
||||
case 6: pGrb6 = new NeoPixelBrightnessBusGrbRmt6(pixelCounts[idx], dataPins[idx]); pGrb6->Begin(); break;
|
||||
case 7: pGrb7 = new NeoPixelBrightnessBusGrbRmt7(pixelCounts[idx], dataPins[idx]); pGrb7->Begin(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrbw0 = new NeoPixelBrightnessBusGrbwRmt0(pixelCounts[idx], dataPins[idx]); pGrbw0->Begin(); break;
|
||||
case 1: pGrbw1 = new NeoPixelBrightnessBusGrbwRmt1(pixelCounts[idx], dataPins[idx]); pGrbw1->Begin(); break;
|
||||
case 2: pGrbw2 = new NeoPixelBrightnessBusGrbwRmt2(pixelCounts[idx], dataPins[idx]); pGrbw2->Begin(); break;
|
||||
case 3: pGrbw3 = new NeoPixelBrightnessBusGrbwRmt3(pixelCounts[idx], dataPins[idx]); pGrbw3->Begin(); break;
|
||||
case 4: pGrbw4 = new NeoPixelBrightnessBusGrbwRmt4(pixelCounts[idx], dataPins[idx]); pGrbw4->Begin(); break;
|
||||
case 5: pGrbw5 = new NeoPixelBrightnessBusGrbwRmt5(pixelCounts[idx], dataPins[idx]); pGrbw5->Begin(); break;
|
||||
case 6: pGrbw6 = new NeoPixelBrightnessBusGrbwRmt6(pixelCounts[idx], dataPins[idx]); pGrbw6->Begin(); break;
|
||||
case 7: pGrbw7 = new NeoPixelBrightnessBusGrbwRmt7(pixelCounts[idx], dataPins[idx]); pGrbw7->Begin(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Show()
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrb0->Show(); break;
|
||||
case 1: pGrb1->Show(); break;
|
||||
case 2: pGrb2->Show(); break;
|
||||
case 3: pGrb3->Show(); break;
|
||||
case 4: pGrb4->Show(); break;
|
||||
case 5: pGrb5->Show(); break;
|
||||
case 6: pGrb6->Show(); break;
|
||||
case 7: pGrb7->Show(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrbw0->Show(); break;
|
||||
case 1: pGrbw1->Show(); break;
|
||||
case 2: pGrbw2->Show(); break;
|
||||
case 3: pGrbw3->Show(); break;
|
||||
case 4: pGrbw4->Show(); break;
|
||||
case 5: pGrbw5->Show(); break;
|
||||
case 6: pGrbw6->Show(); break;
|
||||
case 7: pGrbw7->Show(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CanShow()
|
||||
{
|
||||
bool canShow = true;
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: canShow &= pGrb0->CanShow(); break;
|
||||
case 1: canShow &= pGrb1->CanShow(); break;
|
||||
case 2: canShow &= pGrb2->CanShow(); break;
|
||||
case 3: canShow &= pGrb3->CanShow(); break;
|
||||
case 4: canShow &= pGrb4->CanShow(); break;
|
||||
case 5: canShow &= pGrb5->CanShow(); break;
|
||||
case 6: canShow &= pGrb6->CanShow(); break;
|
||||
case 7: canShow &= pGrb7->CanShow(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: canShow &= pGrbw0->CanShow(); break;
|
||||
case 1: canShow &= pGrbw1->CanShow(); break;
|
||||
case 2: canShow &= pGrbw2->CanShow(); break;
|
||||
case 3: canShow &= pGrbw3->CanShow(); break;
|
||||
case 4: canShow &= pGrbw4->CanShow(); break;
|
||||
case 5: canShow &= pGrbw5->CanShow(); break;
|
||||
case 6: canShow &= pGrbw6->CanShow(); break;
|
||||
case 7: canShow &= pGrbw7->CanShow(); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canShow;
|
||||
}
|
||||
|
||||
void SetPixelColorRaw(uint16_t indexPixel, RgbwColor c)
|
||||
{
|
||||
// figure out which strip this pixel index is on
|
||||
uint8_t stripIdx = 0;
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
if (indexPixel >= pixelStripStartIdx[idx])
|
||||
{
|
||||
stripIdx = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// subtract strip start index so we're addressing just this strip instead of all pixels on all strips
|
||||
indexPixel -= pixelStripStartIdx[stripIdx];
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
RgbColor rgb = RgbColor(c.R, c.G, c.B);
|
||||
switch (stripIdx)
|
||||
{
|
||||
case 0: pGrb0->SetPixelColor(indexPixel, rgb); break;
|
||||
case 1: pGrb1->SetPixelColor(indexPixel, rgb); break;
|
||||
case 2: pGrb2->SetPixelColor(indexPixel, rgb); break;
|
||||
case 3: pGrb3->SetPixelColor(indexPixel, rgb); break;
|
||||
case 4: pGrb4->SetPixelColor(indexPixel, rgb); break;
|
||||
case 5: pGrb5->SetPixelColor(indexPixel, rgb); break;
|
||||
case 6: pGrb6->SetPixelColor(indexPixel, rgb); break;
|
||||
case 7: pGrb7->SetPixelColor(indexPixel, rgb); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
switch (stripIdx)
|
||||
{
|
||||
case 0: pGrbw0->SetPixelColor(indexPixel, c); break;
|
||||
case 1: pGrbw1->SetPixelColor(indexPixel, c); break;
|
||||
case 2: pGrbw2->SetPixelColor(indexPixel, c); break;
|
||||
case 3: pGrbw3->SetPixelColor(indexPixel, c); break;
|
||||
case 4: pGrbw4->SetPixelColor(indexPixel, c); break;
|
||||
case 5: pGrbw5->SetPixelColor(indexPixel, c); break;
|
||||
case 6: pGrbw6->SetPixelColor(indexPixel, c); break;
|
||||
case 7: pGrbw7->SetPixelColor(indexPixel, c); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPixelColor(uint16_t indexPixel, RgbwColor c)
|
||||
{
|
||||
/*
|
||||
Set pixel color with necessary color order conversion.
|
||||
*/
|
||||
|
||||
RgbwColor col;
|
||||
|
||||
uint8_t co = _colorOrder;
|
||||
#ifdef COLOR_ORDER_OVERRIDE
|
||||
if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
|
||||
#endif
|
||||
|
||||
//reorder channels to selected order
|
||||
switch (co)
|
||||
{
|
||||
case 0: col.G = c.G; col.R = c.R; col.B = c.B; break; //0 = GRB, default
|
||||
case 1: col.G = c.R; col.R = c.G; col.B = c.B; break; //1 = RGB, common for WS2811
|
||||
case 2: col.G = c.B; col.R = c.R; col.B = c.G; break; //2 = BRG
|
||||
case 3: col.G = c.R; col.R = c.B; col.B = c.G; break; //3 = RBG
|
||||
case 4: col.G = c.B; col.R = c.G; col.B = c.R; break; //4 = BGR
|
||||
default: col.G = c.G; col.R = c.B; col.B = c.R; break; //5 = GBR
|
||||
}
|
||||
col.W = c.W;
|
||||
|
||||
SetPixelColorRaw(indexPixel, col);
|
||||
}
|
||||
|
||||
void SetBrightness(byte b)
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrb0->SetBrightness(b); break;
|
||||
case 1: pGrb1->SetBrightness(b); break;
|
||||
case 2: pGrb2->SetBrightness(b); break;
|
||||
case 3: pGrb3->SetBrightness(b); break;
|
||||
case 4: pGrb4->SetBrightness(b); break;
|
||||
case 5: pGrb5->SetBrightness(b); break;
|
||||
case 6: pGrb6->SetBrightness(b); break;
|
||||
case 7: pGrb7->SetBrightness(b); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: pGrbw0->SetBrightness(b); break;
|
||||
case 1: pGrbw1->SetBrightness(b); break;
|
||||
case 2: pGrbw2->SetBrightness(b); break;
|
||||
case 3: pGrbw3->SetBrightness(b); break;
|
||||
case 4: pGrbw4->SetBrightness(b); break;
|
||||
case 5: pGrbw5->SetBrightness(b); break;
|
||||
case 6: pGrbw6->SetBrightness(b); break;
|
||||
case 7: pGrbw7->SetBrightness(b); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetColorOrder(byte colorOrder)
|
||||
{
|
||||
_colorOrder = colorOrder;
|
||||
}
|
||||
|
||||
uint8_t GetColorOrder()
|
||||
{
|
||||
return _colorOrder;
|
||||
}
|
||||
|
||||
RgbwColor GetPixelColorRaw(uint16_t indexPixel) const
|
||||
{
|
||||
// figure out which strip this pixel index is on
|
||||
uint8_t stripIdx = 0;
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
if (indexPixel >= pixelStripStartIdx[idx])
|
||||
{
|
||||
stripIdx = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// subtract strip start index so we're addressing just this strip instead of all pixels on all strips
|
||||
indexPixel -= pixelStripStartIdx[stripIdx];
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
switch (stripIdx)
|
||||
{
|
||||
case 0: return pGrb0->GetPixelColor(indexPixel);
|
||||
case 1: return pGrb1->GetPixelColor(indexPixel);
|
||||
case 2: return pGrb2->GetPixelColor(indexPixel);
|
||||
case 3: return pGrb3->GetPixelColor(indexPixel);
|
||||
case 4: return pGrb4->GetPixelColor(indexPixel);
|
||||
case 5: return pGrb5->GetPixelColor(indexPixel);
|
||||
case 6: return pGrb6->GetPixelColor(indexPixel);
|
||||
case 7: return pGrb7->GetPixelColor(indexPixel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
switch (stripIdx)
|
||||
{
|
||||
case 0: return pGrbw0->GetPixelColor(indexPixel);
|
||||
case 1: return pGrbw1->GetPixelColor(indexPixel);
|
||||
case 2: return pGrbw2->GetPixelColor(indexPixel);
|
||||
case 3: return pGrbw3->GetPixelColor(indexPixel);
|
||||
case 4: return pGrbw4->GetPixelColor(indexPixel);
|
||||
case 5: return pGrbw5->GetPixelColor(indexPixel);
|
||||
case 6: return pGrbw6->GetPixelColor(indexPixel);
|
||||
case 7: return pGrbw7->GetPixelColor(indexPixel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Due to feature differences, some support RGBW but the method name
|
||||
// here needs to be unique, thus GetPixeColorRgbw
|
||||
uint32_t GetPixelColorRgbw(uint16_t indexPixel) const
|
||||
{
|
||||
RgbwColor col = GetPixelColorRaw(indexPixel);
|
||||
uint8_t co = _colorOrder;
|
||||
#ifdef COLOR_ORDER_OVERRIDE
|
||||
if (indexPixel >= COO_MIN && indexPixel < COO_MAX) co = COO_ORDER;
|
||||
#endif
|
||||
|
||||
switch (co)
|
||||
{
|
||||
// W G R B
|
||||
case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default
|
||||
case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811
|
||||
case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG
|
||||
case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG
|
||||
case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR
|
||||
case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
NeoPixelType _type;
|
||||
byte _colorOrder = 0;
|
||||
|
||||
uint16_t pixelStripStartIdx[numStrips];
|
||||
|
||||
// pointers for every possible type for up to 8 strips
|
||||
NeoPixelBrightnessBusGrbRmt0 *pGrb0;
|
||||
NeoPixelBrightnessBusGrbRmt1 *pGrb1;
|
||||
NeoPixelBrightnessBusGrbRmt2 *pGrb2;
|
||||
NeoPixelBrightnessBusGrbRmt3 *pGrb3;
|
||||
NeoPixelBrightnessBusGrbRmt4 *pGrb4;
|
||||
NeoPixelBrightnessBusGrbRmt5 *pGrb5;
|
||||
NeoPixelBrightnessBusGrbRmt6 *pGrb6;
|
||||
NeoPixelBrightnessBusGrbRmt7 *pGrb7;
|
||||
NeoPixelBrightnessBusGrbwRmt0 *pGrbw0;
|
||||
NeoPixelBrightnessBusGrbwRmt1 *pGrbw1;
|
||||
NeoPixelBrightnessBusGrbwRmt2 *pGrbw2;
|
||||
NeoPixelBrightnessBusGrbwRmt3 *pGrbw3;
|
||||
NeoPixelBrightnessBusGrbwRmt4 *pGrbw4;
|
||||
NeoPixelBrightnessBusGrbwRmt5 *pGrbw5;
|
||||
NeoPixelBrightnessBusGrbwRmt6 *pGrbw6;
|
||||
NeoPixelBrightnessBusGrbwRmt7 *pGrbw7;
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case NeoPixelType_Grb:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: delete pGrb0; pGrb0 = NULL; break;
|
||||
case 1: delete pGrb1; pGrb1 = NULL; break;
|
||||
case 2: delete pGrb2; pGrb2 = NULL; break;
|
||||
case 3: delete pGrb3; pGrb3 = NULL; break;
|
||||
case 4: delete pGrb4; pGrb4 = NULL; break;
|
||||
case 5: delete pGrb5; pGrb5 = NULL; break;
|
||||
case 6: delete pGrb6; pGrb6 = NULL; break;
|
||||
case 7: delete pGrb7; pGrb7 = NULL; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NeoPixelType_Grbw:
|
||||
{
|
||||
for (uint8_t idx = 0; idx < numStrips; idx++)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case 0: delete pGrbw0; pGrbw0 = NULL; break;
|
||||
case 1: delete pGrbw1; pGrbw1 = NULL; break;
|
||||
case 2: delete pGrbw2; pGrbw2 = NULL; break;
|
||||
case 3: delete pGrbw3; pGrbw3 = NULL; break;
|
||||
case 4: delete pGrbw4; pGrbw4 = NULL; break;
|
||||
case 5: delete pGrbw5; pGrbw5 = NULL; break;
|
||||
case 6: delete pGrbw6; pGrbw6 = NULL; break;
|
||||
case 7: delete pGrbw7; pGrbw7 = NULL; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
# esp32_multistrip
|
||||
|
||||
This usermod enables up to 8 data pins to be used from an esp32 module to drive separate LED strands. This only works with one-wire LEDs like the WS2812.
|
||||
|
||||
The esp32 RMT hardware is used for data output. See here for hardware driver implementation details: https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||||
|
||||
Pass the following variables to the compiler as build flags:
|
||||
|
||||
- `ESP32_MULTISTRIP`
|
||||
- Define this to use usermod NpbWrapper.h instead of default one in WLED.
|
||||
- `NUM_STRIPS`
|
||||
- Number of strips in use
|
||||
- `PIXEL_COUNTS`
|
||||
- List of pixel counts in each strip
|
||||
- `DATA_PINS`
|
||||
- List of data pins each strip is attached to. There may be board-specific restrictions on which pins can be used for RTM.
|
||||
|
||||
From the perspective of WLED software, the LEDs are addressed as one long strand. The modified NbpWrapper.h file addresses the appropriate strand from the overall LED index based on the number of LEDs defined in each strand.
|
||||
|
||||
See `platformio_override.ini` for example configuration.
|
||||
|
||||
Tested on low cost ESP-WROOM-32 dev boards from Amazon, such as those sold by KeeYees.
|
||||
@@ -1,16 +0,0 @@
|
||||
; Example platformio_override.ini that shows how to configure your environment to use the multistrip usermod.
|
||||
; Copy this file to the base wled directory that contains platformio.ini.
|
||||
; Multistrip requires ESP32 because it has many more pins that can be used as LED outputs.
|
||||
; Need to define NUM_STRIPS, PIXEL_COUNTS, and DATA_PINS as shown below.
|
||||
|
||||
[platformio]
|
||||
default_envs = esp32_multistrip
|
||||
|
||||
[env:esp32_multistrip]
|
||||
extends=env:esp32dev
|
||||
build_flags = ${env:esp32dev.build_flags}
|
||||
-D ESP32_MULTISTRIP ; define this variable to use ESP32_MULTISTRIP usermod
|
||||
-D NUM_STRIPS=4 ; number of pixel strips in use
|
||||
-D PIXEL_COUNTS="50, 50, 50, 50" ; number of pixels in each strip
|
||||
-D DATA_PINS="25, 26, 32, 33" ; esp32 pins used for each pixel strip. available pins depends on esp32 module.
|
||||
|
||||
@@ -36,7 +36,7 @@ lib_deps =
|
||||
AsyncTCP@1.0.3
|
||||
Esp Async WebServer@1.2.0
|
||||
IRremoteESP8266@2.7.3
|
||||
I2Cdevlib-MPU6050@fbde122cc5
|
||||
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
||||
```
|
||||
|
||||
## Wiring
|
||||
@@ -78,7 +78,7 @@ to the info object
|
||||
## Usermod installation
|
||||
|
||||
1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory.
|
||||
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
|
||||
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
|
||||
|
||||
Example **usermods_list.cpp**:
|
||||
|
||||
|
||||
@@ -42,6 +42,14 @@
|
||||
#include "Wire.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
// ================================================================
|
||||
// === INTERRUPT DETECTION ROUTINE ===
|
||||
// ================================================================
|
||||
@@ -55,7 +63,8 @@ void IRAM_ATTR dmpDataReady() {
|
||||
class MPU6050Driver : public Usermod {
|
||||
private:
|
||||
MPU6050 mpu;
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
// MPU control/status vars
|
||||
bool dmpReady = false; // set true if DMP init was successful
|
||||
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
|
||||
@@ -84,6 +93,8 @@ class MPU6050Driver : public Usermod {
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
*/
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
|
||||
// join I2C bus (I2Cdev library doesn't do this automatically)
|
||||
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
|
||||
Wire.begin();
|
||||
@@ -93,16 +104,16 @@ class MPU6050Driver : public Usermod {
|
||||
#endif
|
||||
|
||||
// initialize device
|
||||
Serial.println(F("Initializing I2C devices..."));
|
||||
DEBUG_PRINTLN(F("Initializing I2C devices..."));
|
||||
mpu.initialize();
|
||||
pinMode(INTERRUPT_PIN, INPUT);
|
||||
|
||||
// verify connection
|
||||
Serial.println(F("Testing device connections..."));
|
||||
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
|
||||
DEBUG_PRINTLN(F("Testing device connections..."));
|
||||
DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
|
||||
|
||||
// load and configure the DMP
|
||||
Serial.println(F("Initializing DMP..."));
|
||||
DEBUG_PRINTLN(F("Initializing DMP..."));
|
||||
devStatus = mpu.dmpInitialize();
|
||||
|
||||
// supply your own gyro offsets here, scaled for min sensitivity
|
||||
@@ -114,16 +125,16 @@ class MPU6050Driver : public Usermod {
|
||||
// make sure it worked (returns 0 if so)
|
||||
if (devStatus == 0) {
|
||||
// turn on the DMP, now that it's ready
|
||||
Serial.println(F("Enabling DMP..."));
|
||||
DEBUG_PRINTLN(F("Enabling DMP..."));
|
||||
mpu.setDMPEnabled(true);
|
||||
|
||||
// enable Arduino interrupt detection
|
||||
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
|
||||
DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
|
||||
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
|
||||
mpuIntStatus = mpu.getIntStatus();
|
||||
|
||||
// set our DMP Ready flag so the main loop() function knows it's okay to use it
|
||||
Serial.println(F("DMP ready! Waiting for first interrupt..."));
|
||||
DEBUG_PRINTLN(F("DMP ready! Waiting for first interrupt..."));
|
||||
dmpReady = true;
|
||||
|
||||
// get expected DMP packet size for later comparison
|
||||
@@ -133,9 +144,9 @@ class MPU6050Driver : public Usermod {
|
||||
// 1 = initial memory load failed
|
||||
// 2 = DMP configuration updates failed
|
||||
// (if it's going to break, usually the code will be 1)
|
||||
Serial.print(F("DMP Initialization failed (code "));
|
||||
Serial.print(devStatus);
|
||||
Serial.println(F(")"));
|
||||
DEBUG_PRINT(F("DMP Initialization failed (code "));
|
||||
DEBUG_PRINT(devStatus);
|
||||
DEBUG_PRINTLN(F(")"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +155,7 @@ class MPU6050Driver : public Usermod {
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected() {
|
||||
//Serial.println("Connected to WiFi!");
|
||||
//DEBUG_PRINTLN("Connected to WiFi!");
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +164,7 @@ class MPU6050Driver : public Usermod {
|
||||
*/
|
||||
void loop() {
|
||||
// if programming failed, don't try to do anything
|
||||
if (!dmpReady) return;
|
||||
if (!enabled || !dmpReady || strip.isUpdating()) return;
|
||||
|
||||
// wait for MPU interrupt or extra packet(s) available
|
||||
if (!mpuInterrupt && fifoCount < packetSize) return;
|
||||
@@ -169,7 +180,7 @@ class MPU6050Driver : public Usermod {
|
||||
if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
|
||||
// reset so we can continue cleanly
|
||||
mpu.resetFIFO();
|
||||
Serial.println(F("FIFO overflow!"));
|
||||
DEBUG_PRINTLN(F("FIFO overflow!"));
|
||||
|
||||
// otherwise, check for DMP data ready interrupt (this should happen frequently)
|
||||
} else if (mpuIntStatus & 0x02) {
|
||||
@@ -259,10 +270,23 @@ class MPU6050Driver : public Usermod {
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||
//if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("MPU6050_IMU");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# DEPRECATION NOTICE
|
||||
This usermod is deprecated and no longer maintained. It will be removed in a future WLED release. Please use usermod multi_relay which has more features.
|
||||
|
||||
|
||||
# MQTT controllable switches
|
||||
This usermod allows controlling switches (e.g. relays) via MQTT.
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#warning "This usermod is deprecated and no longer maintained. It will be removed in a future WLED release. Please use usermod multi_relay which has more features."
|
||||
|
||||
#include "wled.h"
|
||||
#ifndef WLED_ENABLE_MQTT
|
||||
#error "This user mod requires MQTT to be enabled."
|
||||
|
||||
@@ -5,32 +5,42 @@ This usermod-v2 modification allows the connection of multiple relays each with
|
||||
## HTTP API
|
||||
All responses are returned as JSON.
|
||||
|
||||
Status Request: `http://[device-ip]/relays`
|
||||
Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
|
||||
* Status Request: `http://[device-ip]/relays`
|
||||
* Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
|
||||
|
||||
The number of numbers behind the switch parameter must correspond to the number of relays. The number 1 switches the relay on. The number 0 switches the relay off.
|
||||
|
||||
Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
|
||||
* Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
|
||||
|
||||
The number of numbers behind the parameter switch must correspond to the number of relays. The number 1 causes a toggling of the relay. The number 0 leaves the state of the device.
|
||||
|
||||
Examples
|
||||
1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
|
||||
2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
|
||||
1. total of 4 relays, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
|
||||
2. total of 3 relays, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
|
||||
|
||||
## JSON API
|
||||
You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json`
|
||||
|
||||
|
||||
Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}`
|
||||
|
||||
Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
|
||||
|
||||
## MQTT API
|
||||
|
||||
wled/deviceMAC/relay/0/command on|off|toggle
|
||||
wled/deviceMAC/relay/1/command on|off|toggle
|
||||
* `wled`/_deviceMAC_/`relay`/`0`/`command` `on`|`off`|`toggle`
|
||||
* `wled`/_deviceMAC_/`relay`/`1`/`command` `on`|`off`|`toggle`
|
||||
|
||||
When relay is switched it will publish a message:
|
||||
|
||||
wled/deviceMAC/relay/0 on|off
|
||||
* `wled`/_deviceMAC_/`relay`/`0` `on`|`off`
|
||||
|
||||
|
||||
## Usermod installation
|
||||
|
||||
1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`.
|
||||
or
|
||||
2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY`in your platformio.ini
|
||||
2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini
|
||||
|
||||
You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS.
|
||||
|
||||
@@ -70,10 +80,23 @@ void registerUsermods()
|
||||
|
||||
Usermod can be configured in Usermods settings page.
|
||||
|
||||
* `enabled` - enable/disable usermod
|
||||
* `pin` - GPIO pin where relay is attached to ESP (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`)
|
||||
* `delay-s` - delay in seconds after on/off command is received
|
||||
* `active-high` - toggle high/low activation of relay (can be used to reverse relay states)
|
||||
* `external` - if enabled WLED does not control relay, it can only be triggered by external command (MQTT, HTTP, JSON or button)
|
||||
* `button` - button (from LED Settings) that controls this relay
|
||||
* `broadcast`- time in seconds between state broadcasts using MQTT
|
||||
* `HA-discovery`- enable Home Assistant auto discovery
|
||||
|
||||
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
|
||||
|
||||
Have fun - @blazoncek
|
||||
|
||||
## Change log
|
||||
2021-04
|
||||
* First implementation.
|
||||
* First implementation.
|
||||
|
||||
2021-11
|
||||
* Added information about dynamic configuration options
|
||||
* Added button support.
|
||||
@@ -6,6 +6,12 @@
|
||||
#define MULTI_RELAY_MAX_RELAYS 4
|
||||
#endif
|
||||
|
||||
#ifndef MULTI_RELAY_PINS
|
||||
#define MULTI_RELAY_PINS -1
|
||||
#endif
|
||||
|
||||
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
|
||||
|
||||
#define ON true
|
||||
#define OFF false
|
||||
|
||||
@@ -23,6 +29,7 @@ typedef struct relay_t {
|
||||
bool state;
|
||||
bool external;
|
||||
uint16_t delay;
|
||||
int8_t button;
|
||||
} Relay;
|
||||
|
||||
|
||||
@@ -35,13 +42,18 @@ class MultiRelay : public Usermod {
|
||||
// switch timer start time
|
||||
uint32_t _switchTimerStart = 0;
|
||||
// old brightness
|
||||
bool _oldBrightness = 0;
|
||||
bool _oldMode;
|
||||
|
||||
// usermod enabled
|
||||
bool enabled = false; // needs to be configured (no default config)
|
||||
// status of initialisation
|
||||
bool initDone = false;
|
||||
|
||||
bool HAautodiscovery = false;
|
||||
|
||||
uint16_t periodicBroadcastSec = 60;
|
||||
unsigned long lastBroadcast = 0;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
@@ -49,14 +61,16 @@ class MultiRelay : public Usermod {
|
||||
static const char _delay_str[];
|
||||
static const char _activeHigh[];
|
||||
static const char _external[];
|
||||
static const char _button[];
|
||||
static const char _broadcast[];
|
||||
static const char _HAautodiscovery[];
|
||||
|
||||
|
||||
void publishMqtt(const char* state, int relay) {
|
||||
void publishMqtt(int relay) {
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
sprintf_P(subuf, PSTR("%s/relay/%d"), mqttDeviceTopic, relay);
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
mqtt->publish(subuf, 0, false, _relay[relay].state ? "on" : "off");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,15 +78,19 @@ class MultiRelay : public Usermod {
|
||||
* switch off the strip if the delay has elapsed
|
||||
*/
|
||||
void handleOffTimer() {
|
||||
unsigned long now = millis();
|
||||
bool activeRelays = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].active && _switchTimerStart > 0 && millis() - _switchTimerStart > (_relay[i].delay*1000)) {
|
||||
if (_relay[i].active && _switchTimerStart > 0 && now - _switchTimerStart > (_relay[i].delay*1000)) {
|
||||
if (!_relay[i].external) toggleRelay(i);
|
||||
_relay[i].active = false;
|
||||
} else if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) {
|
||||
if (_relay[i].pin>=0) publishMqtt(i);
|
||||
}
|
||||
activeRelays = activeRelays || _relay[i].active;
|
||||
}
|
||||
if (!activeRelays) _switchTimerStart = 0;
|
||||
if (periodicBroadcastSec && now - lastBroadcast > (periodicBroadcastSec*1000)) lastBroadcast = now;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +119,7 @@ class MultiRelay : public Usermod {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
int value = getValue(p->value(), ',', i);
|
||||
if (value==-1) {
|
||||
error = F("There must be as much arugments as relays");
|
||||
error = F("There must be as many arguments as relays");
|
||||
} else {
|
||||
// Switch
|
||||
if (_relay[i].external) switchRelay(i, (bool)value);
|
||||
@@ -114,7 +132,7 @@ class MultiRelay : public Usermod {
|
||||
for (int i=0;i<MULTI_RELAY_MAX_RELAYS;i++) {
|
||||
int value = getValue(p->value(), ',', i);
|
||||
if (value==-1) {
|
||||
error = F("There must be as mutch arugments as relays");
|
||||
error = F("There must be as many arguments as relays");
|
||||
} else {
|
||||
// Toggle
|
||||
if (value && _relay[i].external) toggleRelay(i);
|
||||
@@ -163,13 +181,15 @@ class MultiRelay : public Usermod {
|
||||
* constructor
|
||||
*/
|
||||
MultiRelay() {
|
||||
const int8_t defPins[] = {MULTI_RELAY_PINS};
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
_relay[i].pin = -1;
|
||||
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;
|
||||
_relay[i].delay = 0;
|
||||
_relay[i].mode = false;
|
||||
_relay[i].active = false;
|
||||
_relay[i].state = false;
|
||||
_relay[i].external = false;
|
||||
_relay[i].button = -1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -194,7 +214,7 @@ class MultiRelay : public Usermod {
|
||||
_relay[relay].state = mode;
|
||||
pinMode(_relay[relay].pin, OUTPUT);
|
||||
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
|
||||
publishMqtt(mode ? "on" : "off", relay);
|
||||
publishMqtt(relay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,6 +267,50 @@ class MultiRelay : public Usermod {
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/relay/#"));
|
||||
mqtt->subscribe(subuf, 0);
|
||||
if (HAautodiscovery) publishHomeAssistantAutodiscovery();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0) continue;
|
||||
publishMqtt(i); //publish current state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void publishHomeAssistantAutodiscovery() {
|
||||
for (uint8_t i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
char uid[24], json_str[1024], buf[128];
|
||||
size_t payload_size;
|
||||
sprintf_P(uid, PSTR("%s_sw%d"), escapedMac.c_str(), i);
|
||||
|
||||
if (_relay[i].pin >= 0 && _relay[i].external) {
|
||||
StaticJsonDocument<1024> json;
|
||||
sprintf_P(buf, PSTR("%s Switch %d"), serverDescription, i); //max length: 33 + 8 + 3 = 44
|
||||
json[F("name")] = buf;
|
||||
|
||||
sprintf_P(buf, PSTR("%s/relay/%d"), mqttDeviceTopic, i); //max length: 33 + 7 + 3 = 43
|
||||
json["~"] = buf;
|
||||
strcat_P(buf, PSTR("/command"));
|
||||
mqtt->subscribe(buf, 0);
|
||||
|
||||
json[F("stat_t")] = "~";
|
||||
json[F("cmd_t")] = F("~/command");
|
||||
json[F("pl_off")] = F("off");
|
||||
json[F("pl_on")] = F("on");
|
||||
json[F("uniq_id")] = uid;
|
||||
|
||||
strcpy(buf, mqttDeviceTopic); //max length: 33 + 7 = 40
|
||||
strcat_P(buf, PSTR("/status"));
|
||||
json[F("avty_t")] = buf;
|
||||
json[F("pl_avail")] = F("online");
|
||||
json[F("pl_not_avail")] = F("offline");
|
||||
//TODO: dev
|
||||
payload_size = serializeJson(json, json_str);
|
||||
} else {
|
||||
//Unpublish disabled or internal relays
|
||||
json_str[0] = 0;
|
||||
payload_size = 0;
|
||||
}
|
||||
sprintf_P(buf, PSTR("homeassistant/switch/%s/config"), uid);
|
||||
mqtt->publish(buf, 0, true, json_str, payload_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,11 +325,12 @@ class MultiRelay : public Usermod {
|
||||
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
|
||||
_relay[i].pin = -1; // allocation failed
|
||||
} else {
|
||||
switchRelay(i, _relay[i].state = (bool)bri);
|
||||
if (!_relay[i].external) _relay[i].state = !offMode;
|
||||
switchRelay(i, _relay[i].state);
|
||||
_relay[i].active = false;
|
||||
}
|
||||
}
|
||||
_oldBrightness = (bool)bri;
|
||||
_oldMode = offMode;
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -281,24 +346,119 @@ class MultiRelay : public Usermod {
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop() {
|
||||
yield();
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
static unsigned long lastUpdate = 0;
|
||||
if (millis() - lastUpdate < 200) return; // update only 5 times/s
|
||||
if (millis() - lastUpdate < 100) return; // update only 10 times/s
|
||||
lastUpdate = millis();
|
||||
|
||||
//set relay when LEDs turn on
|
||||
if (_oldBrightness != (bool)bri) {
|
||||
_oldBrightness = (bool)bri;
|
||||
if (_oldMode != offMode) {
|
||||
_oldMode = offMode;
|
||||
_switchTimerStart = millis();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0) _relay[i].active = true;
|
||||
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleOffTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* handleButton() can be used to override default button behaviour. Returning true
|
||||
* will prevent button working in a default way.
|
||||
* Replicating button.cpp
|
||||
*/
|
||||
bool handleButton(uint8_t b) {
|
||||
yield();
|
||||
if (!enabled
|
||||
|| buttonType[b] == BTN_TYPE_NONE
|
||||
|| buttonType[b] == BTN_TYPE_RESERVED
|
||||
|| buttonType[b] == BTN_TYPE_PIR_SENSOR
|
||||
|| buttonType[b] == BTN_TYPE_ANALOG
|
||||
|| buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b && _relay[i].external) {
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (!handled) return false;
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
|
||||
if (buttonType[b] == BTN_TYPE_SWITCH) {
|
||||
//handleSwitch(b);
|
||||
if (buttonPressedBefore[b] != isButtonPressed(b)) {
|
||||
buttonPressedTime[b] = now;
|
||||
buttonPressedBefore[b] = !buttonPressedBefore[b];
|
||||
}
|
||||
|
||||
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
|
||||
|
||||
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
switchRelay(i, buttonPressedBefore[b]);
|
||||
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
//momentary button logic
|
||||
if (isButtonPressed(b)) { //pressed
|
||||
|
||||
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
|
||||
buttonPressedBefore[b] = true;
|
||||
|
||||
if (now - buttonPressedTime[b] > 600) { //long press
|
||||
//longPressAction(b); //not exposed
|
||||
//handled = false; //use if you want to pass to default behaviour
|
||||
buttonLongPressed[b] = true;
|
||||
}
|
||||
|
||||
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
|
||||
|
||||
long dur = now - buttonPressedTime[b];
|
||||
if (dur < WLED_DEBOUNCE_THRESHOLD) {
|
||||
buttonPressedBefore[b] = false;
|
||||
return handled;
|
||||
} //too short "press", debounce
|
||||
bool doublePress = buttonWaitTime[b]; //did we have short press before?
|
||||
buttonWaitTime[b] = 0;
|
||||
|
||||
if (!buttonLongPressed[b]) { //short press
|
||||
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
|
||||
if (doublePress) {
|
||||
//doublePressAction(b); //not exposed
|
||||
//handled = false; //use if you want to pass to default behaviour
|
||||
} else {
|
||||
buttonWaitTime[b] = now;
|
||||
}
|
||||
}
|
||||
buttonPressedBefore[b] = false;
|
||||
buttonLongPressed[b] = false;
|
||||
}
|
||||
// if 350ms elapsed since last press/release it is a short press
|
||||
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
|
||||
buttonWaitTime[b] = 0;
|
||||
//shortPressAction(b); //not exposed
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
toggleRelay(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
*/
|
||||
@@ -310,6 +470,26 @@ class MultiRelay : public Usermod {
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
|
||||
infoArr.add(String(getActiveRelayCount()));
|
||||
|
||||
String uiDomString;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0 || !_relay[i].external) continue;
|
||||
uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_relay_str);
|
||||
uiDomString += F(":");
|
||||
uiDomString += i;
|
||||
uiDomString += F(",on:");
|
||||
uiDomString += _relay[i].state ? "false" : "true";
|
||||
uiDomString += F("}});\">");
|
||||
uiDomString += F("Relay ");
|
||||
uiDomString += i;
|
||||
uiDomString += F(" <i class=\"icons\"></i></button>");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
|
||||
infoArr.add(_relay[i].state ? "on" : "off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +498,23 @@ class MultiRelay : public Usermod {
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject &root) {
|
||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||
JsonObject multiRelay = root[FPSTR(_name)];
|
||||
if (multiRelay.isNull()) {
|
||||
multiRelay = root.createNestedObject(FPSTR(_name));
|
||||
}
|
||||
#if MULTI_RELAY_MAX_RELAYS > 1
|
||||
JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin < 0) continue;
|
||||
JsonObject relay = rel_arr.createNestedObject();
|
||||
relay[FPSTR(_relay_str)] = i;
|
||||
relay[F("state")] = _relay[i].state;
|
||||
}
|
||||
#else
|
||||
multiRelay[FPSTR(_relay_str)] = 0;
|
||||
multiRelay[F("state")] = _relay[0].state;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,6 +522,20 @@ class MultiRelay : public Usermod {
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject &root) {
|
||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod["on"].is<bool>() && usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(usermod[FPSTR(_relay_str)].as<int>(), usermod["on"].as<bool>());
|
||||
}
|
||||
} else if (root[FPSTR(_name)].is<JsonArray>()) {
|
||||
JsonArray relays = root[FPSTR(_name)].as<JsonArray>();
|
||||
for (JsonVariant r : relays) {
|
||||
if (r["on"].is<bool>() && r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(r[FPSTR(_relay_str)].as<int>(), r["on"].as<bool>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,13 +545,17 @@ class MultiRelay : public Usermod {
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_broadcast)] = periodicBroadcastSec;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
|
||||
top[parName+"pin"] = _relay[i].pin;
|
||||
top[parName+FPSTR(_activeHigh)] = _relay[i].mode;
|
||||
top[parName+FPSTR(_delay_str)] = _relay[i].delay;
|
||||
top[parName+FPSTR(_external)] = _relay[i].external;
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
JsonObject relay = top.createNestedObject(parName);
|
||||
relay["pin"] = _relay[i].pin;
|
||||
relay[FPSTR(_activeHigh)] = _relay[i].mode;
|
||||
relay[FPSTR(_delay_str)] = _relay[i].delay;
|
||||
relay[FPSTR(_external)] = _relay[i].external;
|
||||
relay[FPSTR(_button)] = _relay[i].button;
|
||||
}
|
||||
top[FPSTR(_HAautodiscovery)] = HAautodiscovery;
|
||||
DEBUG_PRINTLN(F("MultiRelay config saved."));
|
||||
}
|
||||
|
||||
@@ -361,14 +576,25 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
periodicBroadcastSec = top[FPSTR(_broadcast)] | periodicBroadcastSec;
|
||||
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
|
||||
HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery;
|
||||
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
oldPin[i] = _relay[i].pin;
|
||||
_relay[i].pin = top[parName]["pin"] | _relay[i].pin;
|
||||
_relay[i].mode = top[parName][FPSTR(_activeHigh)] | _relay[i].mode;
|
||||
_relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external;
|
||||
_relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay;
|
||||
_relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button;
|
||||
// begin backwards compatibility (beta) remove when 0.13 is released
|
||||
parName += '-';
|
||||
_relay[i].pin = top[parName+"pin"] | _relay[i].pin;
|
||||
_relay[i].mode = top[parName+FPSTR(_activeHigh)] | _relay[i].mode;
|
||||
_relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external;
|
||||
_relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay;
|
||||
// end compatibility
|
||||
_relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min
|
||||
}
|
||||
|
||||
@@ -386,7 +612,9 @@ class MultiRelay : public Usermod {
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
|
||||
if (!_relay[i].external) {
|
||||
switchRelay(i, _relay[i].state = (bool)bri);
|
||||
_relay[i].state = !offMode;
|
||||
switchRelay(i, _relay[i].state);
|
||||
_oldMode = offMode;
|
||||
}
|
||||
} else {
|
||||
_relay[i].pin = -1;
|
||||
@@ -396,7 +624,7 @@ class MultiRelay : public Usermod {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return true;
|
||||
return !top[FPSTR(_HAautodiscovery)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,3 +644,6 @@ const char MultiRelay::_relay_str[] PROGMEM = "relay";
|
||||
const char MultiRelay::_delay_str[] PROGMEM = "delay-s";
|
||||
const char MultiRelay::_activeHigh[] PROGMEM = "active-high";
|
||||
const char MultiRelay::_external[] PROGMEM = "external";
|
||||
const char MultiRelay::_button[] PROGMEM = "button";
|
||||
const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
|
||||
const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* I've had good results with settings around 5 (20 fps).
|
||||
*
|
||||
*/
|
||||
#include "wled.h"
|
||||
|
||||
const uint8_t PCARS_dimcolor = 20;
|
||||
WiFiUDP UDP;
|
||||
const unsigned int PCARS_localUdpPort = 5606; // local port to listen on
|
||||
@@ -49,11 +51,12 @@ void PCARS_readValues() {
|
||||
void PCARS_buildcolorbars() {
|
||||
boolean activated = false;
|
||||
float ledratio = 0;
|
||||
uint16_t totalLen = strip.getLengthTotal();
|
||||
|
||||
for (uint16_t i = 0; i < ledCount; i++) {
|
||||
for (uint16_t i = 0; i < totalLen; i++) {
|
||||
if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) {
|
||||
|
||||
ledratio = (float)i / (float)ledCount;
|
||||
ledratio = (float)i / (float)totalLen;
|
||||
if (ledratio < PCARS_rpmRatio) {
|
||||
activated = true;
|
||||
} else {
|
||||
|
||||
755
usermods/quinled-an-penta/quinled-an-penta.h
Normal file
@@ -0,0 +1,755 @@
|
||||
#pragma once
|
||||
|
||||
#include "U8g2lib.h"
|
||||
#include "SHT85.h"
|
||||
#include "Wire.h"
|
||||
#include "wled.h"
|
||||
|
||||
class QuinLEDAnPentaUsermod : public Usermod
|
||||
{
|
||||
private:
|
||||
bool enabled = false;
|
||||
bool firstRunDone = false;
|
||||
bool initDone = false;
|
||||
U8G2 *oledDisplay = nullptr;
|
||||
SHT *sht30TempHumidSensor;
|
||||
|
||||
// Network info vars
|
||||
bool networkHasChanged = false;
|
||||
bool lastKnownNetworkConnected;
|
||||
IPAddress lastKnownIp;
|
||||
bool lastKnownWiFiConnected;
|
||||
String lastKnownSsid;
|
||||
bool lastKnownApActive;
|
||||
char *lastKnownApSsid;
|
||||
char *lastKnownApPass;
|
||||
byte lastKnownApChannel;
|
||||
int lastKnownEthType;
|
||||
bool lastKnownEthLinkUp;
|
||||
|
||||
// Brightness / LEDC vars
|
||||
byte lastKnownBri = 0;
|
||||
int8_t currentBussesNumPins[5] = {0, 0, 0, 0, 0};
|
||||
int8_t currentLedPins[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t currentLedcReads[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t lastKnownLedcReads[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
// OLED vars
|
||||
bool oledEnabled = false;
|
||||
bool oledInitDone = false;
|
||||
bool oledUseProgressBars = false;
|
||||
bool oledFlipScreen = false;
|
||||
bool oledFixBuggedScreen = false;
|
||||
byte oledMaxPage = 3;
|
||||
byte oledCurrentPage = 3; // Start with the network page to help identifying the IP
|
||||
byte oledSecondsPerPage = 10;
|
||||
unsigned long oledLogoDrawn = 0;
|
||||
unsigned long oledLastTimeUpdated = 0;
|
||||
unsigned long oledLastTimePageChange = 0;
|
||||
unsigned long oledLastTimeFixBuggedScreen = 0;
|
||||
|
||||
// SHT30 vars
|
||||
bool shtEnabled = false;
|
||||
bool shtInitDone = false;
|
||||
bool shtReadDataSuccess = false;
|
||||
byte shtI2cAddress = 0x44;
|
||||
unsigned long shtLastTimeUpdated = 0;
|
||||
bool shtDataRequested = false;
|
||||
float shtCurrentTemp = 0;
|
||||
float shtLastKnownTemp = 0;
|
||||
float shtCurrentHumidity = 0;
|
||||
float shtLastKnownHumidity = 0;
|
||||
|
||||
// Pin/IO vars
|
||||
const int8_t anPentaLEDPins[5] = {14, 13, 12, 4, 2};
|
||||
int8_t oledSpiClk = 15;
|
||||
int8_t oledSpiData = 16;
|
||||
int8_t oledSpiCs = 27;
|
||||
int8_t oledSpiDc = 32;
|
||||
int8_t oledSpiRst = 33;
|
||||
int8_t shtSda = 1;
|
||||
int8_t shtScl = 3;
|
||||
|
||||
|
||||
bool isAnPentaLedPin(int8_t pin)
|
||||
{
|
||||
for(int8_t i = 0; i <= 4; i++)
|
||||
{
|
||||
if(anPentaLEDPins[i] == pin)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void getCurrentUsedLedPins()
|
||||
{
|
||||
for (int8_t lp = 0; lp <= 4; lp++) currentLedPins[lp] = 0;
|
||||
byte numBusses = busses.getNumBusses();
|
||||
byte numUsedPins = 0;
|
||||
|
||||
for (int8_t b = 0; b < numBusses; b++) {
|
||||
Bus* curBus = busses.getBus(b);
|
||||
if (curBus != nullptr) {
|
||||
uint8_t pins[5] = {0, 0, 0, 0, 0};
|
||||
currentBussesNumPins[b] = curBus->getPins(pins);
|
||||
for (int8_t p = 0; p < currentBussesNumPins[b]; p++) {
|
||||
if (isAnPentaLedPin(pins[p])) {
|
||||
currentLedPins[numUsedPins] = pins[p];
|
||||
numUsedPins++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getCurrentLedcValues()
|
||||
{
|
||||
byte numBusses = busses.getNumBusses();
|
||||
byte numLedc = 0;
|
||||
|
||||
for (int8_t b = 0; b < numBusses; b++) {
|
||||
Bus* curBus = busses.getBus(b);
|
||||
if (curBus != nullptr) {
|
||||
uint32_t curPixColor = curBus->getPixelColor(0);
|
||||
uint8_t _data[5] = {255, 255, 255, 255, 255};
|
||||
_data[3] = curPixColor >> 24;
|
||||
_data[0] = curPixColor >> 16;
|
||||
_data[1] = curPixColor >> 8;
|
||||
_data[2] = curPixColor;
|
||||
|
||||
for (uint8_t i = 0; i < currentBussesNumPins[b]; i++) {
|
||||
currentLedcReads[numLedc] = (_data[i] * bri) / 255;
|
||||
numLedc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void initOledDisplay()
|
||||
{
|
||||
PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) {
|
||||
DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name);
|
||||
oledEnabled = oledInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
oledDisplay = (U8G2 *) new U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(U8G2_R0, oledSpiClk, oledSpiData, oledSpiCs, oledSpiDc, oledSpiRst);
|
||||
if (oledDisplay == nullptr) {
|
||||
DEBUG_PRINTF("[%s] OLED init failed!\n", _name);
|
||||
oledEnabled = oledInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
oledDisplay->begin();
|
||||
oledDisplay->setBusClock(40 * 1000 * 1000);
|
||||
oledDisplay->setContrast(10);
|
||||
oledDisplay->setPowerSave(0);
|
||||
oledDisplay->setFont(u8g2_font_6x10_tf);
|
||||
oledDisplay->setFlipMode(oledFlipScreen);
|
||||
|
||||
oledDisplay->firstPage();
|
||||
do {
|
||||
oledDisplay->drawXBMP(0, 16, 128, 36, quinLedLogo);
|
||||
} while (oledDisplay->nextPage());
|
||||
oledLogoDrawn = millis();
|
||||
|
||||
oledInitDone = true;
|
||||
}
|
||||
|
||||
void cleanupOledDisplay()
|
||||
{
|
||||
if (oledInitDone) {
|
||||
oledDisplay->clear();
|
||||
}
|
||||
|
||||
pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta);
|
||||
pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta);
|
||||
pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta);
|
||||
pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta);
|
||||
pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta);
|
||||
|
||||
delete oledDisplay;
|
||||
|
||||
oledEnabled = false;
|
||||
oledInitDone = false;
|
||||
}
|
||||
|
||||
bool isOledReady()
|
||||
{
|
||||
return oledEnabled && oledInitDone;
|
||||
}
|
||||
|
||||
void initSht30TempHumiditySensor()
|
||||
{
|
||||
PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) {
|
||||
DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name);
|
||||
shtEnabled = shtInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
TwoWire *wire = new TwoWire(1);
|
||||
wire->setClock(400000);
|
||||
|
||||
sht30TempHumidSensor = (SHT *) new SHT30();
|
||||
sht30TempHumidSensor->begin(shtI2cAddress, wire);
|
||||
// The SHT lib calls wire.begin() again without the SDA and SCL pins... So call it again here...
|
||||
wire->begin(shtSda, shtScl);
|
||||
if (sht30TempHumidSensor->readStatus() == 0xFFFF) {
|
||||
DEBUG_PRINTF("[%s] SHT30 init failed!\n", _name);
|
||||
shtEnabled = shtInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
shtInitDone = true;
|
||||
}
|
||||
|
||||
void cleanupSht30TempHumiditySensor()
|
||||
{
|
||||
if (shtInitDone) {
|
||||
sht30TempHumidSensor->reset();
|
||||
}
|
||||
|
||||
pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta);
|
||||
pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta);
|
||||
|
||||
delete sht30TempHumidSensor;
|
||||
|
||||
shtEnabled = false;
|
||||
shtInitDone = false;
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
if (isOledReady()) {
|
||||
cleanupOledDisplay();
|
||||
}
|
||||
|
||||
if (isShtReady()) {
|
||||
cleanupSht30TempHumiditySensor();
|
||||
}
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool oledCheckForNetworkChanges()
|
||||
{
|
||||
if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP()
|
||||
|| lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID()
|
||||
|| lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) {
|
||||
lastKnownNetworkConnected = Network.isConnected();
|
||||
lastKnownIp = Network.localIP();
|
||||
lastKnownWiFiConnected = WiFi.isConnected();
|
||||
lastKnownSsid = WiFi.SSID();
|
||||
lastKnownApActive = apActive;
|
||||
lastKnownApSsid = apSSID;
|
||||
lastKnownApPass = apPass;
|
||||
lastKnownApChannel = apChannel;
|
||||
|
||||
return networkHasChanged = true;
|
||||
}
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
if (lastKnownEthType != ethernetType || lastKnownEthLinkUp != ETH.linkUp()) {
|
||||
lastKnownEthType = ethernetType;
|
||||
lastKnownEthLinkUp = ETH.linkUp();
|
||||
|
||||
return networkHasChanged = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return networkHasChanged = false;
|
||||
}
|
||||
|
||||
byte oledGetNextPage()
|
||||
{
|
||||
return oledCurrentPage + 1 <= oledMaxPage ? oledCurrentPage + 1 : 1;
|
||||
}
|
||||
|
||||
void oledShowPage(byte page, bool updateLastTimePageChange = false)
|
||||
{
|
||||
oledCurrentPage = page;
|
||||
updateOledDisplay();
|
||||
oledLastTimeUpdated = millis();
|
||||
if (updateLastTimePageChange) oledLastTimePageChange = oledLastTimeUpdated;
|
||||
}
|
||||
|
||||
/*
|
||||
* Page 1: Overall brightness and LED outputs
|
||||
* Page 2: General info like temp, humidity and others
|
||||
* Page 3: Network info
|
||||
*/
|
||||
void updateOledDisplay()
|
||||
{
|
||||
if (!isOledReady()) return;
|
||||
|
||||
oledDisplay->firstPage();
|
||||
do {
|
||||
oledDisplay->setFont(u8g2_font_chroma48medium8_8r);
|
||||
oledDisplay->drawStr(0, 8, serverDescription);
|
||||
oledDisplay->drawHLine(0, 13, 127);
|
||||
oledDisplay->setFont(u8g2_font_6x10_tf);
|
||||
|
||||
byte charPerRow = 21;
|
||||
byte oledRow = 23;
|
||||
switch (oledCurrentPage) {
|
||||
// LED Outputs
|
||||
case 1:
|
||||
{
|
||||
char charCurrentBrightness[charPerRow+1] = "Brightness:";
|
||||
if (oledUseProgressBars) {
|
||||
oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
|
||||
// There is no method to draw a filled box with rounded corners. So draw the rounded frame first, then fill that frame accordingly to LED percentage
|
||||
oledDisplay->drawRFrame(68, oledRow - 6, 60, 7, 2);
|
||||
oledDisplay->drawBox(69, oledRow - 5, int(round(58*getPercentageForBrightness(bri)) / 100), 5);
|
||||
}
|
||||
else {
|
||||
sprintf(charCurrentBrightness, "%s %d%%", charCurrentBrightness, getPercentageForBrightness(bri));
|
||||
oledDisplay->drawStr(0, oledRow, charCurrentBrightness);
|
||||
}
|
||||
oledRow += 8;
|
||||
|
||||
byte drawnLines = 0;
|
||||
for (int8_t app = 0; app <= 4; app++) {
|
||||
for (int8_t clp = 0; clp <= 4; clp++) {
|
||||
if (anPentaLEDPins[app] == currentLedPins[clp]) {
|
||||
char charCurrentLedcReads[17];
|
||||
sprintf(charCurrentLedcReads, "LED %d:", app+1);
|
||||
if (oledUseProgressBars) {
|
||||
oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
|
||||
oledDisplay->drawRFrame(38, oledRow - 6 + (drawnLines * 8), 90, 7, 2);
|
||||
oledDisplay->drawBox(39, oledRow - 5 + (drawnLines * 8), int(round(88*getPercentageForBrightness(currentLedcReads[clp])) / 100), 5);
|
||||
}
|
||||
else {
|
||||
sprintf(charCurrentLedcReads, "%s %d%%", charCurrentLedcReads, getPercentageForBrightness(currentLedcReads[clp]));
|
||||
oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads);
|
||||
}
|
||||
|
||||
drawnLines++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Various info
|
||||
case 2:
|
||||
{
|
||||
if (isShtReady() && shtReadDataSuccess) {
|
||||
char charShtCurrentTemp[charPerRow+4]; // Reserve 3 more bytes than usual as we gonna have one UTF8 char which can be up to 4 bytes.
|
||||
sprintf(charShtCurrentTemp, "Temperature: %.02f°C", shtCurrentTemp);
|
||||
char charShtCurrentHumidity[charPerRow+1];
|
||||
sprintf(charShtCurrentHumidity, "Humidity: %.02f RH", shtCurrentHumidity);
|
||||
|
||||
oledDisplay->drawUTF8(0, oledRow, charShtCurrentTemp);
|
||||
oledDisplay->drawStr(0, oledRow + 10, charShtCurrentHumidity);
|
||||
oledRow += 20;
|
||||
}
|
||||
|
||||
if (mqttEnabled && mqttServer[0] != 0) {
|
||||
char charMqttStatus[charPerRow+1];
|
||||
sprintf(charMqttStatus, "MQTT: %s", (WLED_MQTT_CONNECTED ? "Connected" : "Disconnected"));
|
||||
oledDisplay->drawStr(0, oledRow, charMqttStatus);
|
||||
oledRow += 10;
|
||||
}
|
||||
|
||||
// Always draw these two on the bottom
|
||||
char charUptime[charPerRow+1];
|
||||
sprintf(charUptime, "Uptime: %ds", int(millis()/1000 + rolloverMillis*4294967)); // From json.cpp
|
||||
oledDisplay->drawStr(0, 53, charUptime);
|
||||
|
||||
char charWledVersion[charPerRow+1];
|
||||
sprintf(charWledVersion, "WLED v%s", versionString);
|
||||
oledDisplay->drawStr(0, 63, charWledVersion);
|
||||
break;
|
||||
}
|
||||
|
||||
// Network Info
|
||||
case 3:
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
if (lastKnownEthType == WLED_ETH_NONE) {
|
||||
oledDisplay->drawStr(0, oledRow, "Ethernet: No board selected");
|
||||
oledRow += 10;
|
||||
}
|
||||
else if (!lastKnownEthLinkUp) {
|
||||
oledDisplay->drawStr(0, oledRow, "Ethernet: Link Down");
|
||||
oledRow += 10;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lastKnownNetworkConnected) {
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
if (lastKnownEthLinkUp) {
|
||||
oledDisplay->drawStr(0, oledRow, "Ethernet: Link Up");
|
||||
oledRow += 10;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
// Wi-Fi can be active with ETH being connected, but we don't mind...
|
||||
if (lastKnownWiFiConnected) {
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
if (!lastKnownEthLinkUp) {
|
||||
#endif
|
||||
|
||||
oledDisplay->drawStr(0, oledRow, "Wi-Fi: Connected");
|
||||
char currentSsidChar[lastKnownSsid.length() + 1];
|
||||
lastKnownSsid.toCharArray(currentSsidChar, lastKnownSsid.length() + 1);
|
||||
char charCurrentSsid[50];
|
||||
sprintf(charCurrentSsid, "SSID: %s", currentSsidChar);
|
||||
oledDisplay->drawStr(0, oledRow + 10, charCurrentSsid);
|
||||
oledRow += 20;
|
||||
|
||||
#ifdef WLED_USE_ETHERNET
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
String currentIpStr = lastKnownIp.toString();
|
||||
char currentIpChar[currentIpStr.length() + 1];
|
||||
currentIpStr.toCharArray(currentIpChar, currentIpStr.length() + 1);
|
||||
char charCurrentIp[30];
|
||||
sprintf(charCurrentIp, "IP: %s", currentIpChar);
|
||||
oledDisplay->drawStr(0, oledRow, charCurrentIp);
|
||||
}
|
||||
// If WLED AP is active. Theoretically, it can even be active with ETH being connected, but we don't mind...
|
||||
else if (lastKnownApActive) {
|
||||
char charCurrentApStatus[charPerRow+1];
|
||||
sprintf(charCurrentApStatus, "WLED AP: %s (Ch: %d)", (lastKnownApActive ? "On" : "Off"), lastKnownApChannel);
|
||||
oledDisplay->drawStr(0, oledRow, charCurrentApStatus);
|
||||
|
||||
char charCurrentApSsid[charPerRow+1];
|
||||
sprintf(charCurrentApSsid, "SSID: %s", lastKnownApSsid);
|
||||
oledDisplay->drawStr(0, oledRow + 10, charCurrentApSsid);
|
||||
|
||||
char charCurrentApPass[charPerRow+1];
|
||||
sprintf(charCurrentApPass, "PW: %s", lastKnownApPass);
|
||||
oledDisplay->drawStr(0, oledRow + 20, charCurrentApPass);
|
||||
|
||||
// IP is hardcoded / no var exists in WLED at the time this mod was coded, so also hardcode it here
|
||||
oledDisplay->drawStr(0, oledRow + 30, "IP: 4.3.2.1");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} while (oledDisplay->nextPage());
|
||||
}
|
||||
|
||||
bool isShtReady()
|
||||
{
|
||||
return shtEnabled && shtInitDone;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _oledEnabled[];
|
||||
static const char _oledUseProgressBars[];
|
||||
static const char _oledFlipScreen[];
|
||||
static const char _oledSecondsPerPage[];
|
||||
static const char _oledFixBuggedScreen[];
|
||||
static const char _shtEnabled[];
|
||||
static const unsigned char quinLedLogo[];
|
||||
|
||||
|
||||
static int8_t getPercentageForBrightness(byte brightness)
|
||||
{
|
||||
return int(((float)brightness / (float)255) * 100);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
lastKnownBri = bri;
|
||||
|
||||
if (oledEnabled) {
|
||||
initOledDisplay();
|
||||
}
|
||||
|
||||
if (shtEnabled) {
|
||||
initSht30TempHumiditySensor();
|
||||
}
|
||||
|
||||
getCurrentUsedLedPins();
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
firstRunDone = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*
|
||||
* Tips:
|
||||
* 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
|
||||
* Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
|
||||
*
|
||||
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
|
||||
* Instead, use a timer check as shown here.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
if (!enabled || !initDone || strip.isUpdating()) return;
|
||||
|
||||
if (isShtReady()) {
|
||||
if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) {
|
||||
sht30TempHumidSensor->requestData();
|
||||
shtDataRequested = true;
|
||||
|
||||
shtLastTimeUpdated = millis();
|
||||
}
|
||||
|
||||
if (shtDataRequested) {
|
||||
if (sht30TempHumidSensor->dataReady()) {
|
||||
if (sht30TempHumidSensor->readData()) {
|
||||
shtCurrentTemp = sht30TempHumidSensor->getTemperature();
|
||||
shtCurrentHumidity = sht30TempHumidSensor->getHumidity();
|
||||
shtReadDataSuccess = true;
|
||||
}
|
||||
else {
|
||||
shtReadDataSuccess = false;
|
||||
}
|
||||
|
||||
shtDataRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isOledReady() && millis() - oledLogoDrawn > 3000) {
|
||||
// Check for changes on the current page and update the OLED if a change is detected
|
||||
if (millis() - oledLastTimeUpdated > 150) {
|
||||
// If there was a network change, force page 3 (network page)
|
||||
if (oledCheckForNetworkChanges()) {
|
||||
oledCurrentPage = 3;
|
||||
}
|
||||
// Only redraw a page if there was a change for that page
|
||||
switch (oledCurrentPage) {
|
||||
case 1:
|
||||
lastKnownBri = bri;
|
||||
// Probably causes lag to always do ledcRead(), so rather re-do the math, 'cause we can't easily get it...
|
||||
getCurrentLedcValues();
|
||||
|
||||
if (bri != lastKnownBri || lastKnownLedcReads[0] != currentLedcReads[0] || lastKnownLedcReads[1] != currentLedcReads[1] || lastKnownLedcReads[2] != currentLedcReads[2]
|
||||
|| lastKnownLedcReads[3] != currentLedcReads[3] || lastKnownLedcReads[4] != currentLedcReads[4]) {
|
||||
lastKnownLedcReads[0] = currentLedcReads[0]; lastKnownLedcReads[1] = currentLedcReads[1]; lastKnownLedcReads[2] = currentLedcReads[2]; lastKnownLedcReads[3] = currentLedcReads[3]; lastKnownLedcReads[4] = currentLedcReads[4];
|
||||
|
||||
oledShowPage(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (shtLastKnownTemp != shtCurrentTemp || shtLastKnownHumidity != shtCurrentHumidity) {
|
||||
shtLastKnownTemp = shtCurrentTemp;
|
||||
shtLastKnownHumidity = shtCurrentHumidity;
|
||||
|
||||
oledShowPage(2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (networkHasChanged) {
|
||||
networkHasChanged = false;
|
||||
|
||||
oledShowPage(3, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Cycle through OLED pages
|
||||
if (millis() - oledLastTimePageChange > oledSecondsPerPage * 1000) {
|
||||
// Periodically fixing a "bugged out" OLED. More details in the ReadMe
|
||||
if (oledFixBuggedScreen && millis() - oledLastTimeFixBuggedScreen > 60000) {
|
||||
oledDisplay->begin();
|
||||
oledLastTimeFixBuggedScreen = millis();
|
||||
}
|
||||
oledShowPage(oledGetNextPage(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_oledEnabled)] = oledEnabled;
|
||||
top[FPSTR(_oledUseProgressBars)] = oledUseProgressBars;
|
||||
top[FPSTR(_oledFlipScreen)] = oledFlipScreen;
|
||||
top[FPSTR(_oledSecondsPerPage)] = oledSecondsPerPage;
|
||||
top[FPSTR(_oledFixBuggedScreen)] = oledFixBuggedScreen;
|
||||
top[FPSTR(_shtEnabled)] = shtEnabled;
|
||||
|
||||
// Update LED pins on config save
|
||||
getCurrentUsedLedPins();
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool oldEnabled = enabled;
|
||||
bool oldOledEnabled = oledEnabled;
|
||||
bool oldOledFlipScreen = oledFlipScreen;
|
||||
bool oldShtEnabled = shtEnabled;
|
||||
|
||||
getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||
getJsonValue(top[FPSTR(_oledEnabled)], oledEnabled);
|
||||
getJsonValue(top[FPSTR(_oledUseProgressBars)], oledUseProgressBars);
|
||||
getJsonValue(top[FPSTR(_oledFlipScreen)], oledFlipScreen);
|
||||
getJsonValue(top[FPSTR(_oledSecondsPerPage)], oledSecondsPerPage);
|
||||
getJsonValue(top[FPSTR(_oledFixBuggedScreen)], oledFixBuggedScreen);
|
||||
getJsonValue(top[FPSTR(_shtEnabled)], shtEnabled);
|
||||
|
||||
// First run: reading from cfg.json, nothing to do here, will be all done in setup()
|
||||
if (!firstRunDone) {
|
||||
DEBUG_PRINTF("[%s] First run, nothing to do\n", _name);
|
||||
}
|
||||
// Check if mod has been en-/disabled
|
||||
else if (enabled != oldEnabled) {
|
||||
enabled ? setup() : cleanup();
|
||||
DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name);
|
||||
}
|
||||
// Config has been changed, so adopt to changes
|
||||
else if (enabled) {
|
||||
if (oldOledEnabled != oledEnabled) {
|
||||
oledEnabled ? initOledDisplay() : cleanupOledDisplay();
|
||||
}
|
||||
else if (oledEnabled && oldOledFlipScreen != oledFlipScreen) {
|
||||
oledDisplay->clear();
|
||||
oledDisplay->setFlipMode(oledFlipScreen);
|
||||
oledShowPage(oledCurrentPage);
|
||||
}
|
||||
|
||||
if (oldShtEnabled != shtEnabled) {
|
||||
shtEnabled ? initSht30TempHumiditySensor() : cleanupSht30TempHumiditySensor();
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("[%s] Config (re)loaded\n", _name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
if (!enabled && !isShtReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray jsonTemp = user.createNestedArray("Temperature");
|
||||
JsonArray jsonHumidity = user.createNestedArray("Humidity");
|
||||
|
||||
if (shtLastTimeUpdated == 0 || !shtReadDataSuccess) {
|
||||
jsonTemp.add(0);
|
||||
jsonHumidity.add(0);
|
||||
if (shtLastTimeUpdated == 0) {
|
||||
jsonTemp.add(" Not read yet");
|
||||
jsonHumidity.add(" Not read yet");
|
||||
}
|
||||
else {
|
||||
jsonTemp.add(" Error");
|
||||
jsonHumidity.add(" Error");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
jsonHumidity.add(shtCurrentHumidity);
|
||||
jsonHumidity.add(" RH");
|
||||
|
||||
jsonTemp.add(shtCurrentTemp);
|
||||
jsonTemp.add(" °C");
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_QUINLED_AN_PENTA;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
// Config settings
|
||||
const char QuinLEDAnPentaUsermod::_name[] PROGMEM = "QuinLED-An-Penta";
|
||||
const char QuinLEDAnPentaUsermod::_enabled[] PROGMEM = "Enabled";
|
||||
const char QuinLEDAnPentaUsermod::_oledEnabled[] PROGMEM = "Enable-OLED";
|
||||
const char QuinLEDAnPentaUsermod::_oledUseProgressBars[] PROGMEM = "OLED-Use-Progress-Bars";
|
||||
const char QuinLEDAnPentaUsermod::_oledFlipScreen[] PROGMEM = "OLED-Flip-Screen-180";
|
||||
const char QuinLEDAnPentaUsermod::_oledSecondsPerPage[] PROGMEM = "OLED-Seconds-Per-Page";
|
||||
const char QuinLEDAnPentaUsermod::_oledFixBuggedScreen[] PROGMEM = "OLED-Fix-Bugged-Screen";
|
||||
const char QuinLEDAnPentaUsermod::_shtEnabled[] PROGMEM = "Enable-SHT30-Temp-Humidity-Sensor";
|
||||
// Other strings
|
||||
|
||||
const unsigned char QuinLEDAnPentaUsermod::quinLedLogo[] PROGMEM = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFD, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x80, 0xFF,
|
||||
0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0x07, 0xFE, 0xFF, 0xFF, 0x0F, 0xFC,
|
||||
0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0x0F, 0xFE,
|
||||
0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0xE3, 0xFF, 0xA5, 0xFF, 0xFF, 0xFF,
|
||||
0x0F, 0xFC, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xE1, 0xFF,
|
||||
0x00, 0xF0, 0xE3, 0xFF, 0x0F, 0xFE, 0x1F, 0xFE, 0xFF, 0xFF, 0x3F, 0xFF,
|
||||
0xFF, 0xFF, 0xE3, 0xFF, 0x00, 0xF0, 0x00, 0xFF, 0x07, 0xFE, 0x1F, 0xFC,
|
||||
0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0x00, 0xF0, 0x00, 0xFE,
|
||||
0x07, 0xFF, 0x1F, 0xFC, 0xF0, 0xC7, 0x3F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF,
|
||||
0xF1, 0xFF, 0x00, 0xFC, 0x07, 0xFF, 0x1F, 0xFE, 0xF0, 0xC3, 0x1F, 0xFE,
|
||||
0x00, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0x30, 0xF8, 0x07, 0xFF, 0x1F, 0xFE,
|
||||
0xF0, 0xC3, 0x1F, 0xFE, 0x00, 0xFC, 0xC3, 0xFF, 0xE1, 0xFF, 0xF0, 0xF0,
|
||||
0x03, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E, 0x00, 0xF8, 0xE3, 0xFF,
|
||||
0xE1, 0xFF, 0xF1, 0xF1, 0x83, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E,
|
||||
0x00, 0xF0, 0xC3, 0xFF, 0xE1, 0xFF, 0xF1, 0xE1, 0x83, 0xFF, 0x0F, 0xFE,
|
||||
0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0xA1, 0xFF, 0xF1, 0xE3,
|
||||
0x81, 0xFF, 0x0F, 0x7E, 0xF0, 0xC1, 0x1F, 0x7E, 0xF0, 0xF0, 0xC3, 0xFF,
|
||||
0x01, 0xF8, 0xE1, 0xC3, 0x83, 0xFF, 0x0F, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
|
||||
0xF8, 0xF0, 0xC3, 0xFF, 0x03, 0xF8, 0xE1, 0xC7, 0x81, 0xE4, 0x0F, 0x7F,
|
||||
0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0x01, 0xF8, 0xE3, 0xC7,
|
||||
0x01, 0xC0, 0x07, 0x7F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xE1, 0xC3, 0xFF,
|
||||
0xC3, 0xFD, 0xE1, 0x87, 0x01, 0x00, 0x07, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E,
|
||||
0xF8, 0xF0, 0xC3, 0xFF, 0xE3, 0xFF, 0xE3, 0x87, 0x01, 0x00, 0x82, 0x3F,
|
||||
0xF8, 0xE1, 0x1F, 0xFE, 0xF8, 0xE1, 0xC3, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
|
||||
0x01, 0x00, 0x80, 0x3F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xF1, 0xC3, 0xFF,
|
||||
0xC3, 0xFF, 0xC3, 0x87, 0x03, 0x0F, 0x80, 0x3F, 0xF8, 0xE1, 0x0F, 0x7E,
|
||||
0xF8, 0xE1, 0x87, 0xFF, 0xC3, 0xFF, 0xC7, 0x87, 0x03, 0x04, 0xC0, 0x7F,
|
||||
0xF0, 0xE1, 0x0F, 0xFF, 0xF8, 0xF1, 0x87, 0xFF, 0xC3, 0xFF, 0xC3, 0x87,
|
||||
0x07, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE0, 0xC3, 0xFF,
|
||||
0xC7, 0xFF, 0x87, 0x87, 0x0F, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x0F, 0x7F,
|
||||
0xF8, 0xE1, 0x07, 0x80, 0x07, 0xEA, 0x87, 0xC1, 0x0F, 0x00, 0x80, 0xFF,
|
||||
0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE1, 0x07, 0x00, 0x03, 0x80, 0x07, 0xC0,
|
||||
0x7F, 0x00, 0x00, 0xFF, 0x01, 0xE0, 0x1F, 0xFF, 0xF8, 0xE1, 0x07, 0x00,
|
||||
0x07, 0x00, 0x07, 0xE0, 0xFF, 0xF7, 0x01, 0xFF, 0x57, 0xF7, 0x9F, 0xFF,
|
||||
0xFC, 0xF1, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xE0, 0xFF, 0xFF, 0x03, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBF, 0xFE,
|
||||
0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
};
|
||||
79
usermods/quinled-an-penta/readme.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# QuinLED-An-Penta
|
||||
The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), like using the OLED and the SHT30 temperature/humidity sensor.
|
||||
|
||||
## Requirements
|
||||
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
|
||||
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
|
||||
|
||||
## Usermod installation
|
||||
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one, add the buildflag `-D QUINLED_AN_PENTA` and the below library dependencies.
|
||||
|
||||
ESP32 (**without** ethernet):
|
||||
```
|
||||
[env:custom_esp32dev_usermod_quinled_an_penta]
|
||||
extends = env:esp32dev
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D QUINLED_AN_PENTA
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
olikraus/U8g2@~2.28.8
|
||||
robtillaart/SHT85@~0.2.0
|
||||
```
|
||||
|
||||
ESP32 (**with** ethernet):
|
||||
```
|
||||
[env:custom_esp32dev_usermod_quinled_an_penta]
|
||||
extends = env:esp32dev
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D WLED_USE_ETHERNET -D QUINLED_AN_PENTA
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
olikraus/U8g2@~2.28.8
|
||||
robtillaart/SHT85@~0.2.0
|
||||
```
|
||||
|
||||
## Some words about the (optional) OLED
|
||||
This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller OLED or an OLED using a different driver will result in unexpected results.
|
||||
I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED.
|
||||
Also note, you need to have an **SPI** driven OLED, **not i2c**!
|
||||
|
||||
### Limitations combined with Ethernet
|
||||
The initial development of this mod had been done with a beta version of the QuinLED-An-Penta, which had a different IO layout for the OLED: The CS pin used to be IO_0, but has been changed to IO27 with the first v1 public release. Unfortunately, IO27 is used by the Ethernet boards, so WLED will not let you enable the OLED screen, if you're using it with Ethernet. This unfortunately makes the development I've done to support/show Ethernet information void, as it cannot be used.
|
||||
However (and I've not tried this, as I don't own a v1 board): You can try to modify this mod and try to use IO27 for the OLED and share it with the Ethernet board. It is "just" the chip select pin, so there is a chance that both can coexist and use the same IO. You need to skip WLEDs PinManager for the CS pin, so WLED will not block using it. If you don't know how this works: Leave it. If you know what I'm talking about: Try it and please let me know on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
|
||||
### My OLED flickers after some time, what should I do?
|
||||
That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose its settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display.
|
||||
If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display.
|
||||
|
||||
|
||||
## Configuration
|
||||
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there:
|
||||
* Enable-OLED:
|
||||
* What it does: Enables the optional SPI driven OLED that can be mounted to the 7-pin female header. Won't work with Ethernet, read above.
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* OLED-Use-Progress-Bars:
|
||||
* What it does: Toggle between showing percentage numbers or a progress-bar-like visualization for overall brightness and each LED channels brightness level
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* OLED-Flip-Screen-180:
|
||||
* What it does: Flips the screen 180° / upside-down
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* OLED-Seconds-Per-Page:
|
||||
* What it does: Defines how long the OLED should stay on one page in seconds before changing to the next
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: 10
|
||||
* OLED-Fix-Bugged-Screen:
|
||||
* What it does: Enable this if your OLED flickers after some time. For more info read above under ["My OLED flickers after some time, what should I do?"](#My-OLED-flickers-after-some-time-what-should-I-do)
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* Enable-SHT30-Temp-Humidity-Sensor:
|
||||
* What it does: Enables the onboard SHT30 temperature and humidity sensor
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
|
||||
## Change log
|
||||
2021-12
|
||||
* Adjusted IO layout to match An-Penta v1r1
|
||||
2021-10
|
||||
* First implementation.
|
||||
|
||||
## Credits
|
||||
ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
@@ -1,37 +0,0 @@
|
||||
# QuinLED-Dig-Quad Preassembled Unofficial Build
|
||||
|
||||
This usermod targets the [Preassembled QuinLED-Dig-Quad](https://quinled.info/pre-assembled-quinled-dig-quad/). Tested on board revision v1r6b,
|
||||
and includes the following features:
|
||||
|
||||
* **Multi-channel Support** - enabling use of LED1, LED2, LED3, LED4 pins to work using segments
|
||||
* **Temperature Sensor Support** - pulls readings from the built-in temperature sensor and adds the reading to the *Info* page in the UI
|
||||
|
||||
## Background
|
||||
|
||||
As a starting point, you should check out this awesome video from Quindor: [How to compile WLED yourself](https://quinled.info/2020/12/22/livestream-wled-compile/). The usermod you are reading now just provides some shortcuts for parts of what were covered in that video.
|
||||
|
||||
## Build Firmware with Multi-channel and Temp Support
|
||||
|
||||
1. Copy the `platformio_override.ini` file to the project's root directory
|
||||
1. If using VS Code with the PlatformIO plugin like in the video, you will now see this new project task listed in the PLATFORMIO panel at the bottom as `env:QL-DigQuad-Pre-v0.1` (you probably need to hit the refresh button)
|
||||
|
||||
<img src="images/pio-screenshot.png" width="400px"/>
|
||||
|
||||
1. Edit this file from the root directory as needed:
|
||||
|
||||
<img src="images/params.png" width="400px"/>
|
||||
|
||||
* `PIXEL_COUNTS` may need to be adjusted for your set-up. E.g. I have lots of LEDs in Channel 1, but that's probably unusual for most
|
||||
* `DATA_PINS` may need to be changed to "16,3,1,26" instead of "16,1,3,26" apparently depending on the board revision or some such
|
||||
|
||||
1. Build the mod (e.g. click `Build` from the project task circled above) and update your firmware using the `QL-DigQuad-Pre-v0.1` file, e.g. using _Manual OTA_ from the Config menu. Based on the video and my own experience, you might need to build twice 🤷♂️.
|
||||
|
||||
## Observing Temperature
|
||||
|
||||
Hopefully you can now see the Temperature listed in the Info page. If not, use Chrome Developer Tools to find the current temperature
|
||||
|
||||
1. Open the Developer Tools Console
|
||||
2. Enter `lastinfo.u.Temperature` to view the Temperature array
|
||||
|
||||
<img src="images/json-temp.png" width="300px"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 321 KiB |
@@ -1,16 +0,0 @@
|
||||
; QuinLED-Dig-Quad Preassembled Unofficial
|
||||
|
||||
[env:QL-DigQuad-Pre-v0.1]
|
||||
extends = env:esp32dev
|
||||
build_flags = ${common.build_flags_esp32}
|
||||
-D ESP32_MULTISTRIP
|
||||
-D NUM_STRIPS=4
|
||||
-D PIXEL_COUNTS="600, 300, 300, 300"
|
||||
-D DATA_PINS="16,1,3,26"
|
||||
-D RLYPIN=19
|
||||
-D BTNPIN=17
|
||||
-D USERMOD_DALLASTEMPERATURE
|
||||
-D USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL=10000
|
||||
lib_deps = ${env.lib_deps}
|
||||
milesburton/DallasTemperature@^3.9.0
|
||||
OneWire@~2.3.5
|
||||
@@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
void initRotaryEncoder()
|
||||
{
|
||||
PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, UM_RGBRotaryEncoder)) {
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) {
|
||||
eaIo = -1;
|
||||
ebIo = -1;
|
||||
cleanup();
|
||||
@@ -208,7 +208,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
lastKnownBri = bri;
|
||||
|
||||
updateLeds();
|
||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
}
|
||||
|
||||
// If the brightness is changed not with the rotary, update the rotary
|
||||
@@ -323,7 +323,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return 0x4711;
|
||||
return USERMOD_RGB_ROTARY_ENCODER;
|
||||
}
|
||||
|
||||
//More methods can be added in the future, this example will then be extended.
|
||||
|
||||
55
usermods/seven_segment_display/readme.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Seven Segment Display
|
||||
|
||||
Usermod that uses the overlay feature to create a configurable seven segment display.
|
||||
This has only been tested on a single configuration. Colon support is entirely untested.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_SEVEN_SEGMENT` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SEVEN_SEGMENT` in `my_config.h`.
|
||||
|
||||
## Settings
|
||||
Settings can be controlled through both the usermod setting page and through MQTT with a raw payload.
|
||||
##### Example
|
||||
Topic ```<mqttDeviceTopic||mqttGroupTopic>/sevenSeg/perSegment/set```
|
||||
Payload ```3```
|
||||
#### perSegment -- ssLEDPerSegment
|
||||
The number of individual LEDs per segment. There are 7 segments per digit.
|
||||
#### perPeriod -- ssLEDPerPeriod
|
||||
The number of individual LEDs per period. A ':' has 2x periods.
|
||||
#### startIdx -- ssStartLED
|
||||
Index of the LED that the display starts at. Allows a seven segment display to be in the middle of a string.
|
||||
#### timeEnable -- ssTimeEnabled
|
||||
When true, when displayMask is configured for a time output and no message is set the time will be displayed.
|
||||
#### scrollSpd -- ssScrollSpeed
|
||||
Time, in milliseconds, between message shifts when the length of displayMsg exceeds the length of the displayMask.
|
||||
#### displayMask -- ssDisplayMask
|
||||
This should represent the configuration of the physical display.
|
||||
<pre>
|
||||
HH - 0-23. hh - 1-12, kk - 1-24 hours
|
||||
MM or mm - 0-59 minutes
|
||||
SS or ss = 0-59 seconds
|
||||
: for a colon
|
||||
All others for alpha numeric, (will be blank when displaying time)
|
||||
</pre>
|
||||
##### Example
|
||||
```HHMMSS ```
|
||||
```hh:MM:SS ```
|
||||
#### displayMsg -- ssDisplayMessage
|
||||
Message to be displayed across the display. If the length exceeds the length of the displayMask the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'.
|
||||
#### displayCfg -- ssDisplayConfig
|
||||
The order that your LEDs are configured. All seven segments in the display need to be wired the same way.
|
||||
<pre>
|
||||
-------
|
||||
/ A / 0 - EDCGFAB
|
||||
/ F / B 1 - EDCBAFG
|
||||
/ / 2 - GCDEFAB
|
||||
------- 3 - GBAFEDC
|
||||
/ G / 4 - FABGEDC
|
||||
/ E / C 5 - FABCDEG
|
||||
/ /
|
||||
-------
|
||||
D
|
||||
</pre>
|
||||
|
||||
## Version
|
||||
20211009 - Initial release
|
||||
@@ -0,0 +1,497 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class SevenSegmentDisplay : public Usermod
|
||||
{
|
||||
|
||||
#define WLED_SS_BUFFLEN 6
|
||||
#define REFRESHTIME 497
|
||||
private:
|
||||
//Runtime variables.
|
||||
unsigned long lastRefresh = 0;
|
||||
unsigned long lastCharacterStep = 0;
|
||||
String ssDisplayBuffer = "";
|
||||
char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B};
|
||||
int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed.
|
||||
bool ssDoDisplayTime = true;
|
||||
int ssVirtualDisplayMessageIdxStart = 0;
|
||||
int ssVirtualDisplayMessageIdxEnd = 0;
|
||||
unsigned long resfreshTime = 497;
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment)
|
||||
int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x
|
||||
int ssStartLED = 0; //The pixel that the display starts at.
|
||||
/* HH - 0-23. hh - 1-12, kk - 1-24 hours
|
||||
// MM or mm - 0-59 minutes
|
||||
// SS or ss = 0-59 seconds
|
||||
// : for a colon
|
||||
// All others for alpha numeric, (will be blank when displaying time)
|
||||
*/
|
||||
String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment.
|
||||
/* ssDisplayConfig
|
||||
// -------
|
||||
// / A / 0 - EDCGFAB
|
||||
// / F / B 1 - EDCBAFG
|
||||
// / / 2 - GCDEFAB
|
||||
// ------- 3 - GBAFEDC
|
||||
// / G / 4 - FABGEDC
|
||||
// / E / C 5 - FABCDEG
|
||||
// / /
|
||||
// -------
|
||||
// D
|
||||
*/
|
||||
int ssDisplayConfig = 5; //Physical configuration of the Seven segment display
|
||||
String ssDisplayMessage = "~";
|
||||
bool ssTimeEnabled = true; //If not, display message.
|
||||
unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds.
|
||||
|
||||
//String to reduce flash memory usage
|
||||
static const char _str_perSegment[];
|
||||
static const char _str_perPeriod[];
|
||||
static const char _str_startIdx[];
|
||||
static const char _str_displayCfg[];
|
||||
static const char _str_timeEnabled[];
|
||||
static const char _str_scrollSpd[];
|
||||
static const char _str_displayMask[];
|
||||
static const char _str_displayMsg[];
|
||||
static const char _str_sevenSeg[];
|
||||
static const char _str_subFormat[];
|
||||
static const char _str_topicFormat[];
|
||||
|
||||
unsigned long _overlaySevenSegmentProcess()
|
||||
{
|
||||
//Do time for now.
|
||||
if (ssDoDisplayTime)
|
||||
{
|
||||
//Format the ssDisplayBuffer based on ssDisplayMask
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int index = 0; index < displayMaskLen; index++)
|
||||
{
|
||||
//Only look for time formatting if there are at least 2 characters left in the buffer.
|
||||
if ((index < displayMaskLen - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1]))
|
||||
{
|
||||
int timeVar = 0;
|
||||
switch (ssDisplayMask[index])
|
||||
{
|
||||
case 'h':
|
||||
timeVar = hourFormat12(localTime);
|
||||
break;
|
||||
case 'H':
|
||||
timeVar = hour(localTime);
|
||||
break;
|
||||
case 'k':
|
||||
timeVar = hour(localTime) + 1;
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
timeVar = minute(localTime);
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
timeVar = second(localTime);
|
||||
break;
|
||||
}
|
||||
|
||||
//Only want to leave a blank in the hour formatting.
|
||||
if ((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10)
|
||||
ssDisplayBuffer[index] = ' ';
|
||||
else
|
||||
ssDisplayBuffer[index] = 0x30 + (timeVar / 10);
|
||||
ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10);
|
||||
|
||||
//Need to increment the index because of the second digit.
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' ');
|
||||
}
|
||||
}
|
||||
return REFRESHTIME;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */
|
||||
|
||||
//Check to see if the message has scrolled completely
|
||||
int len = static_cast<int>(ssDisplayMessage.length());
|
||||
if (ssDisplayMessageIdx > len)
|
||||
{
|
||||
//If it has scrolled the whole message, reset it.
|
||||
setSevenSegmentMessage(ssDisplayMessage);
|
||||
return REFRESHTIME;
|
||||
}
|
||||
//Display message
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int index = 0; index < displayMaskLen; index++)
|
||||
{
|
||||
if (ssDisplayMessageIdx + index < len && ssDisplayMessageIdx + index >= 0)
|
||||
ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx + index];
|
||||
else
|
||||
ssDisplayBuffer[index] = ' ';
|
||||
}
|
||||
|
||||
//Increase the displayed message index to progress it one character if the length exceeds the display length.
|
||||
if (len > displayMaskLen)
|
||||
ssDisplayMessageIdx++;
|
||||
|
||||
return ssScrollSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
void _overlaySevenSegmentDraw()
|
||||
{
|
||||
|
||||
//Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer
|
||||
int indexLED = ssStartLED;
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++)
|
||||
{
|
||||
if (ssDisplayBuffer[indexBuffer] == 0)
|
||||
break;
|
||||
else if (ssDisplayBuffer[indexBuffer] == '.')
|
||||
{
|
||||
//Won't ever turn off LED lights for a period. (or will we?)
|
||||
indexLED += ssLEDPerPeriod;
|
||||
continue;
|
||||
}
|
||||
else if (ssDisplayBuffer[indexBuffer] == ':')
|
||||
{
|
||||
//Turn off colon if odd second?
|
||||
indexLED += ssLEDPerPeriod * 2;
|
||||
}
|
||||
else if (ssDisplayBuffer[indexBuffer] == ' ')
|
||||
{
|
||||
//Turn off all 7 segments.
|
||||
_overlaySevenSegmentLEDOutput(0, indexLED);
|
||||
indexLED += ssLEDPerSegment * 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Turn off correct segments.
|
||||
_overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED);
|
||||
indexLED += ssLEDPerSegment * 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _overlaySevenSegmentLEDOutput(char mask, int indexLED)
|
||||
{
|
||||
for (char index = 0; index < 7; index++)
|
||||
{
|
||||
if ((mask & (0x40 >> index)) != (0x40 >> index))
|
||||
{
|
||||
for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++)
|
||||
{
|
||||
strip.setPixelColor(indexLED + numPerSeg, 0x000000);
|
||||
}
|
||||
}
|
||||
indexLED += ssLEDPerSegment;
|
||||
}
|
||||
}
|
||||
|
||||
char _overlaySevenSegmentGetCharMask(char var)
|
||||
{
|
||||
if (var >= 0x30 && var <= 0x39)
|
||||
{ /*If its a number, shift to index 0.*/
|
||||
var -= 0x30;
|
||||
}
|
||||
else if (var >= 0x41 && var <= 0x5a)
|
||||
{ /*If its an Upper case, shift to index 0xA.*/
|
||||
var -= 0x37;
|
||||
}
|
||||
else if (var >= 0x61 && var <= 0x7A)
|
||||
{ /*If its a lower case, shift to index 0xA.*/
|
||||
var -= 0x57;
|
||||
}
|
||||
else
|
||||
{ /* Else unsupported, return 0; */
|
||||
return 0;
|
||||
}
|
||||
char mask = ssCharacterMask[static_cast<int>(var)];
|
||||
/*
|
||||
0 - EDCGFAB
|
||||
1 - EDCBAFG
|
||||
2 - GCDEFAB
|
||||
3 - GBAFEDC
|
||||
4 - FABGEDC
|
||||
5 - FABCDEG
|
||||
*/
|
||||
switch (ssDisplayConfig)
|
||||
{
|
||||
case 1:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
|
||||
break;
|
||||
case 2:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
|
||||
break;
|
||||
case 3:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
|
||||
break;
|
||||
case 4:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
break;
|
||||
case 5:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
|
||||
break;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n)
|
||||
{
|
||||
/* Move all bits of first set to rightmost side */
|
||||
char set1 = (x >> p1) & ((1U << n) - 1);
|
||||
|
||||
/* Move all bits of second set to rightmost side */
|
||||
char set2 = (x >> p2) & ((1U << n) - 1);
|
||||
|
||||
/* Xor the two sets */
|
||||
char Xor = (set1 ^ set2);
|
||||
|
||||
/* Put the Xor bits back to their original positions */
|
||||
Xor = (Xor << p1) | (Xor << p2);
|
||||
|
||||
/* Xor the 'Xor' with the original number so that the
|
||||
two sets are swapped */
|
||||
char result = x ^ Xor;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _publishMQTTint_P(const char *subTopic, int value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
|
||||
char buffer[64];
|
||||
char valBuffer[12];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
|
||||
sprintf_P(valBuffer, PSTR("%d"), value);
|
||||
mqtt->publish(buffer, 2, true, valBuffer);
|
||||
}
|
||||
|
||||
void _publishMQTTstr_P(const char *subTopic, String Value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
char buffer[64];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
|
||||
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
|
||||
}
|
||||
|
||||
void _updateMQTT()
|
||||
{
|
||||
_publishMQTTint_P(_str_perSegment, ssLEDPerSegment);
|
||||
_publishMQTTint_P(_str_perPeriod, ssLEDPerPeriod);
|
||||
_publishMQTTint_P(_str_startIdx, ssStartLED);
|
||||
_publishMQTTint_P(_str_displayCfg, ssDisplayConfig);
|
||||
_publishMQTTint_P(_str_timeEnabled, ssTimeEnabled);
|
||||
_publishMQTTint_P(_str_scrollSpd, ssScrollSpeed);
|
||||
|
||||
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
|
||||
_publishMQTTstr_P(_str_displayMsg, ssDisplayMessage);
|
||||
}
|
||||
|
||||
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
|
||||
{
|
||||
if (strcmp_P(topic, setting) == 0)
|
||||
{
|
||||
*((int *)value) = strtol(payload, NULL, 10);
|
||||
_publishMQTTint_P(setting, *((int *)value));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _handleSetting(char *topic, char *payload)
|
||||
{
|
||||
if (_cmpIntSetting_P(topic, payload, _str_perSegment, &ssLEDPerSegment))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_perPeriod, &ssLEDPerPeriod))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_startIdx, &ssStartLED))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_displayCfg, &ssDisplayConfig))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &ssTimeEnabled))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_scrollSpd, &ssScrollSpeed))
|
||||
return true;
|
||||
if (strcmp_P(topic, _str_displayMask) == 0)
|
||||
{
|
||||
ssDisplayMask = String(payload);
|
||||
ssDisplayBuffer = ssDisplayMask;
|
||||
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
|
||||
return true;
|
||||
}
|
||||
if (strcmp_P(topic, _str_displayMsg) == 0)
|
||||
{
|
||||
setSevenSegmentMessage(String(payload));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
void setSevenSegmentMessage(String message)
|
||||
{
|
||||
//If the message isn't blank display it otherwise show time, if enabled.
|
||||
if (message.length() < 1 || message == "~")
|
||||
ssDoDisplayTime = ssTimeEnabled;
|
||||
else
|
||||
ssDoDisplayTime = false;
|
||||
|
||||
//Determine is the message is longer than the display, if it is configure it to scroll the message.
|
||||
if (message.length() > ssDisplayMask.length())
|
||||
ssDisplayMessageIdx = -ssDisplayMask.length();
|
||||
else
|
||||
ssDisplayMessageIdx = 0;
|
||||
|
||||
//If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll)
|
||||
if (!ssDisplayMessage.equals(message))
|
||||
{
|
||||
_publishMQTTstr_P(_str_displayMsg, message);
|
||||
ssDisplayMessage = message;
|
||||
}
|
||||
}
|
||||
//Functions called by WLED
|
||||
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
ssDisplayBuffer = ssDisplayMask;
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
if (millis() - lastRefresh > resfreshTime)
|
||||
{
|
||||
//In theory overlaySevenSegmentProcess should return the amount of time until it changes next.
|
||||
//So we should be okay to trigger the stripi on every process loop.
|
||||
resfreshTime = _overlaySevenSegmentProcess();
|
||||
lastRefresh = millis();
|
||||
strip.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
_overlaySevenSegmentDraw();
|
||||
}
|
||||
|
||||
void onMqttConnect(bool sessionPresent)
|
||||
{
|
||||
char subBuffer[48];
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
{
|
||||
_updateMQTT();
|
||||
//subscribe for sevenseg messages on the device topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_sevenSeg);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
//subcribe for sevenseg messages on the group topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool onMqttMessage(char *topic, char *payload)
|
||||
{
|
||||
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
|
||||
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
|
||||
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
|
||||
topic += topicPrefixLen;
|
||||
else
|
||||
return false;
|
||||
//We only care if the topic ends with /set
|
||||
size_t topicLen = strlen(topic);
|
||||
if (topicLen > 4 &&
|
||||
topic[topicLen - 4] == '/' &&
|
||||
topic[topicLen - 3] == 's' &&
|
||||
topic[topicLen - 2] == 'e' &&
|
||||
topic[topicLen - 1] == 't')
|
||||
{
|
||||
//Trim /set and handle it
|
||||
topic[topicLen - 4] = '\0';
|
||||
_handleSetting(topic, payload);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_str_sevenSeg)];
|
||||
if (top.isNull())
|
||||
{
|
||||
top = root.createNestedObject(FPSTR(_str_sevenSeg));
|
||||
}
|
||||
top[FPSTR(_str_perSegment)] = ssLEDPerSegment;
|
||||
top[FPSTR(_str_perPeriod)] = ssLEDPerPeriod;
|
||||
top[FPSTR(_str_startIdx)] = ssStartLED;
|
||||
top[FPSTR(_str_displayMask)] = ssDisplayMask;
|
||||
top[FPSTR(_str_displayCfg)] = ssDisplayConfig;
|
||||
top[FPSTR(_str_displayMsg)] = ssDisplayMessage;
|
||||
top[FPSTR(_str_timeEnabled)] = ssTimeEnabled;
|
||||
top[FPSTR(_str_scrollSpd)] = ssScrollSpeed;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_str_sevenSeg)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
//if sevenseg section doesn't exist return
|
||||
if (!configComplete)
|
||||
return configComplete;
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_perSegment)], ssLEDPerSegment);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_perPeriod)], ssLEDPerPeriod);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_startIdx)], ssStartLED);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayMask)], ssDisplayMask);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayCfg)], ssDisplayConfig);
|
||||
|
||||
String newDisplayMessage;
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayMsg)], newDisplayMessage);
|
||||
setSevenSegmentMessage(newDisplayMessage);
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_timeEnabled)], ssTimeEnabled);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_scrollSpd)], ssScrollSpeed);
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_SEVEN_SEGMENT_DISPLAY;
|
||||
}
|
||||
};
|
||||
|
||||
const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment";
|
||||
const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod";
|
||||
const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx";
|
||||
const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg";
|
||||
const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled";
|
||||
const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd";
|
||||
const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask";
|
||||
const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg";
|
||||
const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg";
|
||||
129
usermods/seven_segment_display_reloaded/readme.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Seven Segment Display Reloaded
|
||||
|
||||
Usermod that uses the overlay feature to create a configurable seven segment display.
|
||||
Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/)
|
||||
Very loosely based on the existing usermod "seven segment display".
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`.
|
||||
|
||||
For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
|
||||
|
||||
## Settings
|
||||
All settings can be controlled the usermod setting page.
|
||||
Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state.
|
||||
|
||||
### enabled
|
||||
Enables/disables this overlay usermod
|
||||
|
||||
### inverted
|
||||
Enables the inverted mode in which the background should be enabled and the digits should be black (leds off)
|
||||
|
||||
### Colon-blinking
|
||||
Enables the blinking colon(s) if they are defined
|
||||
|
||||
### enable-auto-brightness
|
||||
Enables the auto brightness feature. Can be only used with the usermod SN_Photoresistor installed.
|
||||
|
||||
### auto-brightness-min / auto-brightness-max
|
||||
The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
|
||||
The mapping is 0 - 1000 lux will be mapped to auto-brightness-min - auto-brightness-max
|
||||
|
||||
The mA current protection of WLED will override the calculated value if it is too high.
|
||||
|
||||
### Display-Mask
|
||||
Defines the type of the time/date display.
|
||||
For example "H:m" (default)
|
||||
- H - 00-23 hours
|
||||
- h - 01-12 hours
|
||||
- k - 01-24 hours
|
||||
- m - 00-59 minutes
|
||||
- s - 00-59 seconds
|
||||
- d - 01-31 day of month
|
||||
- M - 01-12 month
|
||||
- y - 21 last two positions of year
|
||||
- Y - 2021 year
|
||||
- : for a colon
|
||||
|
||||
### LED-Numbers
|
||||
- LED-Numbers-Hours
|
||||
- LED-Numbers-Minutes
|
||||
- LED-Numbers-Seconds
|
||||
- LED-Numbers-Colons
|
||||
- LED-Numbers-Day
|
||||
- LED-Numbers-Month
|
||||
- LED-Numbers-Year
|
||||
|
||||
See following example for usage.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Example for Leds definition
|
||||
```
|
||||
< A >
|
||||
/\ /\
|
||||
F B
|
||||
\/ \/
|
||||
< G >
|
||||
/\ /\
|
||||
E C
|
||||
\/ \/
|
||||
< D >
|
||||
```
|
||||
|
||||
Leds or Range of Leds are seperated by a comma ","
|
||||
|
||||
Segments are seperated by a semicolon ";" and are read as A;B;C;D;E;F;G
|
||||
|
||||
Digits are seperated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
|
||||
|
||||
Ranges are defined as lower to higher (lower first)
|
||||
|
||||
For example, an clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
|
||||
|
||||
- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
|
||||
|
||||
- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25"
|
||||
|
||||
or
|
||||
|
||||
- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56"
|
||||
|
||||
- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41"
|
||||
|
||||
depending on the orientation.
|
||||
|
||||
# The example detailed:
|
||||
hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
|
||||
|
||||
there are two digits seperated by ":"
|
||||
|
||||
- 59,46;47-48;50-51;52-53;54-55;57-58;49,56
|
||||
- 0,13;1-2;4-5;6-7;8-9;11-12;3,10
|
||||
|
||||
In the first digit,
|
||||
the **segment A** consists of the leds number **59 and 46**., **segment B** consists of the leds number **47, 48** and so on
|
||||
|
||||
The second digit starts again with **segment A** and leds **0 and 13**, **segment B** consists of the leds number **1 and 2** and so on
|
||||
|
||||
### first digit of the hour
|
||||
- Segment A: 59, 46
|
||||
- Segment B: 47, 48
|
||||
- Segment C: 50, 51
|
||||
- Segment D: 52, 53
|
||||
- Segment E: 54, 55
|
||||
- Segment F: 57, 58
|
||||
- Segment G: 49, 56
|
||||
|
||||
### second digit of the hour
|
||||
|
||||
- Segment A: 0, 13
|
||||
- Segment B: 1, 2
|
||||
- Segment C: 4, 5
|
||||
- Segment D: 6, 7
|
||||
- Segment E: 8, 9
|
||||
- Segment F: 11, 12
|
||||
- Segment G: 3, 10
|
||||
@@ -0,0 +1,555 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class UsermodSSDR : public Usermod {
|
||||
|
||||
//#define REFRESHTIME 497
|
||||
|
||||
private:
|
||||
//Runtime variables.
|
||||
unsigned long umSSDRLastRefresh = 0;
|
||||
unsigned long umSSDRResfreshTime = 3000;
|
||||
bool umSSDRDisplayTime = false;
|
||||
bool umSSDRInverted = false;
|
||||
bool umSSDRColonblink = true;
|
||||
bool umSSDREnableLDR = false;
|
||||
String umSSDRHours = "";
|
||||
String umSSDRMinutes = "";
|
||||
String umSSDRSeconds = "";
|
||||
String umSSDRColons = "";
|
||||
String umSSDRDays = "";
|
||||
String umSSDRMonths = "";
|
||||
String umSSDRYears = "";
|
||||
uint16_t umSSDRLength = 0;
|
||||
uint16_t umSSDRBrightnessMin = 0;
|
||||
uint16_t umSSDRBrightnessMax = 255;
|
||||
|
||||
bool* umSSDRMask = 0;
|
||||
|
||||
/*// H - 00-23 hours
|
||||
// h - 01-12 hours
|
||||
// k - 01-24 hours
|
||||
// m - 00-59 minutes
|
||||
// s - 00-59 seconds
|
||||
// d - 01-31 day of month
|
||||
// M - 01-12 month
|
||||
// y - 21 last two positions of year
|
||||
// Y - 2021 year
|
||||
// : for a colon
|
||||
*/
|
||||
String umSSDRDisplayMask = "H:m"; //This should reflect physical equipment.
|
||||
|
||||
/* Segment order, seen from the front:
|
||||
|
||||
< A >
|
||||
/\ /\
|
||||
F B
|
||||
\/ \/
|
||||
< G >
|
||||
/\ /\
|
||||
E C
|
||||
\/ \/
|
||||
< D >
|
||||
|
||||
*/
|
||||
|
||||
uint8_t umSSDRNumbers[11][7] = {
|
||||
// A B C D E F G
|
||||
{ 1, 1, 1, 1, 1, 1, 0 }, // 0
|
||||
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
|
||||
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
|
||||
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
|
||||
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
|
||||
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
|
||||
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
|
||||
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
|
||||
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
|
||||
{ 1, 1, 1, 1, 0, 1, 1 }, // 9
|
||||
{ 0, 0, 0, 0, 0, 0, 0 } // blank
|
||||
};
|
||||
|
||||
//String to reduce flash memory usage
|
||||
static const char _str_name[];
|
||||
static const char _str_ldrEnabled[];
|
||||
static const char _str_timeEnabled[];
|
||||
static const char _str_inverted[];
|
||||
static const char _str_colonblink[];
|
||||
static const char _str_displayMask[];
|
||||
static const char _str_hours[];
|
||||
static const char _str_minutes[];
|
||||
static const char _str_seconds[];
|
||||
static const char _str_colons[];
|
||||
static const char _str_days[];
|
||||
static const char _str_months[];
|
||||
static const char _str_years[];
|
||||
static const char _str_minBrightness[];
|
||||
static const char _str_maxBrightness[];
|
||||
|
||||
#ifdef USERMOD_SN_PHOTORESISTOR
|
||||
Usermod_SN_Photoresistor *ptr;
|
||||
#else
|
||||
void* ptr = nullptr;
|
||||
#endif
|
||||
|
||||
void _overlaySevenSegmentDraw() {
|
||||
int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length());
|
||||
bool colonsDone = false;
|
||||
_setAllFalse();
|
||||
for (int index = 0; index < displayMaskLen; index++) {
|
||||
int timeVar = 0;
|
||||
switch (umSSDRDisplayMask[index]) {
|
||||
case 'h':
|
||||
timeVar = hourFormat12(localTime);
|
||||
_showElements(&umSSDRHours, timeVar, 0, 1);
|
||||
break;
|
||||
case 'H':
|
||||
timeVar = hour(localTime);
|
||||
_showElements(&umSSDRHours, timeVar, 0, 1);
|
||||
break;
|
||||
case 'k':
|
||||
timeVar = hour(localTime) + 1;
|
||||
_showElements(&umSSDRHours, timeVar, 0, 0);
|
||||
break;
|
||||
case 'm':
|
||||
timeVar = minute(localTime);
|
||||
_showElements(&umSSDRMinutes, timeVar, 0, 0);
|
||||
break;
|
||||
case 's':
|
||||
timeVar = second(localTime);
|
||||
_showElements(&umSSDRSeconds, timeVar, 0, 0);
|
||||
break;
|
||||
case 'd':
|
||||
timeVar = day(localTime);
|
||||
_showElements(&umSSDRDays, timeVar, 0, 0);
|
||||
break;
|
||||
case 'M':
|
||||
timeVar = month(localTime);
|
||||
_showElements(&umSSDRMonths, timeVar, 0, 0);
|
||||
break;
|
||||
case 'y':
|
||||
timeVar = second(localTime);
|
||||
_showElements(&umSSDRYears, timeVar, 0, 0);
|
||||
break;
|
||||
case 'Y':
|
||||
timeVar = year(localTime);
|
||||
_showElements(&umSSDRYears, timeVar, 0, 0);
|
||||
break;
|
||||
case ':':
|
||||
if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found
|
||||
_setColons();
|
||||
colonsDone = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
_setMaskToLeds();
|
||||
}
|
||||
|
||||
void _setColons() {
|
||||
if ( umSSDRColonblink ) {
|
||||
if ( second(localTime) % 2 == 0 ) {
|
||||
_showElements(&umSSDRColons, 0, 1, 0);
|
||||
}
|
||||
} else {
|
||||
_showElements(&umSSDRColons, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void _showElements(String *map, int timevar, bool isColon, bool removeZero
|
||||
|
||||
) {
|
||||
if (!(*map).equals("") && !(*map) == NULL) {
|
||||
int length = String(timevar).length();
|
||||
bool addZero = false;
|
||||
if (length == 1) {
|
||||
length = 2;
|
||||
addZero = true;
|
||||
}
|
||||
int timeArr[length];
|
||||
if(addZero) {
|
||||
if(removeZero)
|
||||
{
|
||||
timeArr[1] = 10;
|
||||
timeArr[0] = timevar;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeArr[1] = 0;
|
||||
timeArr[0] = timevar;
|
||||
}
|
||||
} else {
|
||||
int count = 0;
|
||||
while (timevar) {
|
||||
timeArr[count] = timevar%10;
|
||||
timevar /= 10;
|
||||
count++;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
int colonsLen = static_cast<int>((*map).length());
|
||||
int count = 0;
|
||||
int countSegments = 0;
|
||||
int countDigit = 0;
|
||||
bool range = false;
|
||||
int lastSeenLedNr = 0;
|
||||
|
||||
for (int index = 0; index < colonsLen; index++) {
|
||||
switch ((*map)[index]) {
|
||||
case '-':
|
||||
lastSeenLedNr = _checkForNumber(count, index, map);
|
||||
count = 0;
|
||||
range = true;
|
||||
break;
|
||||
case ':':
|
||||
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
|
||||
count = 0;
|
||||
range = false;
|
||||
countDigit++;
|
||||
countSegments = 0;
|
||||
break;
|
||||
case ';':
|
||||
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
|
||||
count = 0;
|
||||
range = false;
|
||||
countSegments++;
|
||||
break;
|
||||
case ',':
|
||||
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
|
||||
count = 0;
|
||||
range = false;
|
||||
break;
|
||||
default:
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
|
||||
}
|
||||
}
|
||||
|
||||
void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) {
|
||||
|
||||
if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) {
|
||||
|
||||
if (range) {
|
||||
for(int i = lastSeenLedNr; i <= lednr; i++) {
|
||||
umSSDRMask[i] = true;
|
||||
}
|
||||
} else {
|
||||
umSSDRMask[lednr] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _setMaskToLeds() {
|
||||
for(int i = 0; i <= umSSDRLength; i++) {
|
||||
if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) {
|
||||
strip.setPixelColor(i, 0x000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _setAllFalse() {
|
||||
for(int i = 0; i <= umSSDRLength; i++) {
|
||||
umSSDRMask[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
int _checkForNumber(int count, int index, String *map) {
|
||||
String number = (*map).substring(index - count, index);
|
||||
return number.toInt();
|
||||
}
|
||||
|
||||
void _publishMQTTint_P(const char *subTopic, int value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
|
||||
char buffer[64];
|
||||
char valBuffer[12];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
|
||||
sprintf_P(valBuffer, PSTR("%d"), value);
|
||||
mqtt->publish(buffer, 2, true, valBuffer);
|
||||
}
|
||||
|
||||
void _publishMQTTstr_P(const char *subTopic, String Value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
char buffer[64];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
|
||||
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
|
||||
}
|
||||
|
||||
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
|
||||
{
|
||||
if (strcmp_P(topic, setting) == 0)
|
||||
{
|
||||
*((int *)value) = strtol(payload, NULL, 10);
|
||||
_publishMQTTint_P(setting, *((int *)value));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _handleSetting(char *topic, char *payload) {
|
||||
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) {
|
||||
return true;
|
||||
}
|
||||
if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) {
|
||||
return true;
|
||||
}
|
||||
if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) {
|
||||
return true;
|
||||
}
|
||||
if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) {
|
||||
return true;
|
||||
}
|
||||
if (strcmp_P(topic, _str_displayMask) == 0) {
|
||||
umSSDRDisplayMask = String(payload);
|
||||
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _updateMQTT()
|
||||
{
|
||||
_publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime);
|
||||
_publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR);
|
||||
_publishMQTTint_P(_str_inverted, umSSDRInverted);
|
||||
_publishMQTTint_P(_str_colonblink, umSSDRColonblink);
|
||||
|
||||
_publishMQTTstr_P(_str_hours, umSSDRHours);
|
||||
_publishMQTTstr_P(_str_minutes, umSSDRMinutes);
|
||||
_publishMQTTstr_P(_str_seconds, umSSDRSeconds);
|
||||
_publishMQTTstr_P(_str_colons, umSSDRColons);
|
||||
_publishMQTTstr_P(_str_days, umSSDRDays);
|
||||
_publishMQTTstr_P(_str_months, umSSDRMonths);
|
||||
_publishMQTTstr_P(_str_years, umSSDRYears);
|
||||
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
|
||||
|
||||
_publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin);
|
||||
_publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax);
|
||||
}
|
||||
|
||||
void _addJSONObject(JsonObject& root) {
|
||||
JsonObject ssdrObj = root[FPSTR(_str_name)];
|
||||
if (ssdrObj.isNull()) {
|
||||
ssdrObj = root.createNestedObject(FPSTR(_str_name));
|
||||
}
|
||||
|
||||
ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime;
|
||||
ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR;
|
||||
ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted;
|
||||
ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink;
|
||||
ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask;
|
||||
ssdrObj[FPSTR(_str_hours)] = umSSDRHours;
|
||||
ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes;
|
||||
ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds;
|
||||
ssdrObj[FPSTR(_str_colons)] = umSSDRColons;
|
||||
ssdrObj[FPSTR(_str_days)] = umSSDRDays;
|
||||
ssdrObj[FPSTR(_str_months)] = umSSDRMonths;
|
||||
ssdrObj[FPSTR(_str_years)] = umSSDRYears;
|
||||
ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin;
|
||||
ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax;
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup() {
|
||||
umSSDRLength = strip.getLengthTotal();
|
||||
if (umSSDRMask != 0) {
|
||||
umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool));
|
||||
} else {
|
||||
umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool));
|
||||
}
|
||||
_setAllFalse();
|
||||
|
||||
#ifdef USERMOD_SN_PHOTORESISTOR
|
||||
ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR);
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Setup done"));
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop() {
|
||||
if (!umSSDRDisplayTime || strip.isUpdating()) {
|
||||
return;
|
||||
}
|
||||
#ifdef USERMOD_SN_PHOTORESISTOR
|
||||
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
|
||||
if (ptr != nullptr) {
|
||||
uint16_t lux = ptr->getLastLDRValue();
|
||||
uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax);
|
||||
if (bri != brightness) {
|
||||
bri = brightness;
|
||||
stateUpdated(1);
|
||||
}
|
||||
}
|
||||
umSSDRLastRefresh = millis();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void handleOverlayDraw() {
|
||||
if (umSSDRDisplayTime) {
|
||||
_overlaySevenSegmentDraw();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) {
|
||||
user = root.createNestedObject(F("u"));
|
||||
}
|
||||
JsonArray enabled = user.createNestedArray("Time enabled");
|
||||
enabled.add(umSSDRDisplayTime);
|
||||
JsonArray invert = user.createNestedArray("Time inverted");
|
||||
invert.add(umSSDRInverted);
|
||||
JsonArray blink = user.createNestedArray("Blinking colon");
|
||||
blink.add(umSSDRColonblink);
|
||||
JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled");
|
||||
ldrEnable.add(umSSDREnableLDR);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root) {
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) {
|
||||
user = root.createNestedObject(F("u"));
|
||||
}
|
||||
_addJSONObject(user);
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
JsonObject user = root[F("u")];
|
||||
if (!user.isNull()) {
|
||||
JsonObject ssdrObj = user[FPSTR(_str_name)];
|
||||
umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime;
|
||||
umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR;
|
||||
umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted;
|
||||
umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink;
|
||||
umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
|
||||
}
|
||||
}
|
||||
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
char subBuffer[48];
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
{
|
||||
_updateMQTT();
|
||||
//subscribe for sevenseg messages on the device topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_name);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
//subcribe for sevenseg messages on the group topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool onMqttMessage(char *topic, char *payload) {
|
||||
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
|
||||
size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
|
||||
if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
|
||||
topic += topicPrefixLen;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
//We only care if the topic ends with /set
|
||||
size_t topicLen = strlen(topic);
|
||||
if (topicLen > 4 &&
|
||||
topic[topicLen - 4] == '/' &&
|
||||
topic[topicLen - 3] == 's' &&
|
||||
topic[topicLen - 2] == 'e' &&
|
||||
topic[topicLen - 1] == 't')
|
||||
{
|
||||
//Trim /set and handle it
|
||||
topic[topicLen - 4] = '\0';
|
||||
_handleSetting(topic, payload);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject &root) {
|
||||
_addJSONObject(root);
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
JsonObject top = root[FPSTR(_str_name)];
|
||||
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(FPSTR(_str_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime);
|
||||
umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR);
|
||||
umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted);
|
||||
umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink);
|
||||
|
||||
umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
|
||||
umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours;
|
||||
umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes;
|
||||
umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds;
|
||||
umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons;
|
||||
umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays;
|
||||
umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths;
|
||||
umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears;
|
||||
umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin;
|
||||
umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_str_name));
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_SSDR;
|
||||
}
|
||||
};
|
||||
|
||||
const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR";
|
||||
const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled";
|
||||
const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted";
|
||||
const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking";
|
||||
const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask";
|
||||
const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours";
|
||||
const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes";
|
||||
const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds";
|
||||
const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons";
|
||||
const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day";
|
||||
const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month";
|
||||
const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year";
|
||||
const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness";
|
||||
const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min";
|
||||
const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max";
|
||||
@@ -1,35 +0,0 @@
|
||||
# SSD1306 128x32 OLED via I2C with u8g2
|
||||
This usermod allows to connect 128x32 Oled display to WLED controlled and show
|
||||
the next information:
|
||||
- Current SSID
|
||||
- IP address if obtained
|
||||
* in AP mode and turned off lightning AP password is shown
|
||||
- Current effect
|
||||
- Current palette
|
||||
- On/Off icon (sun/moon)
|
||||
|
||||
## Hardware
|
||||

|
||||
|
||||
## Requirements
|
||||
Functionality checked with:
|
||||
- commit 095429a7df4f9e2b34dd464f7bbfd068df6558eb
|
||||
- Wemos d1 mini
|
||||
- PlatformIO
|
||||
- Generic SSD1306 128x32 I2C OLED display from aliexpress
|
||||
|
||||
### Platformio
|
||||
Add `U8g2@~2.27.2` dependency to `lib_deps_external` under `[common]` section in `platformio.ini`:
|
||||
```ini
|
||||
# platformio.ini
|
||||
...
|
||||
[common]
|
||||
...
|
||||
lib_deps_external =
|
||||
...
|
||||
U8g2@~2.27.2
|
||||
...
|
||||
```
|
||||
|
||||
### Arduino IDE
|
||||
Install library `U8g2 by oliver` in `Tools | Include Library | Manage libraries` menu.
|
||||
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,175 +0,0 @@
|
||||
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
//Lolin32 boards use SCL=5 SDA=4
|
||||
#define U8X8_PIN_SCL 5
|
||||
#define U8X8_PIN_SDA 4
|
||||
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||
// or check the gallery:
|
||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL,
|
||||
U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void userSetup() {
|
||||
u8x8.begin();
|
||||
u8x8.setPowerSave(0);
|
||||
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8.drawString(0, 0, "Loading...");
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||
// interfaces here
|
||||
void userConnected() {}
|
||||
|
||||
// needRedraw marks if redraw is required to prevent often redrawing.
|
||||
bool needRedraw = true;
|
||||
|
||||
// Next variables hold the previous known values to determine if redraw is
|
||||
// required.
|
||||
String knownSsid = "";
|
||||
IPAddress knownIp;
|
||||
uint8_t knownBrightness = 0;
|
||||
uint8_t knownMode = 0;
|
||||
uint8_t knownPalette = 0;
|
||||
|
||||
long lastUpdate = 0;
|
||||
long lastRedraw = 0;
|
||||
bool displayTurnedOff = false;
|
||||
// How often we are redrawing screen
|
||||
#define USER_LOOP_REFRESH_RATE_MS 5000
|
||||
|
||||
void userLoop() {
|
||||
|
||||
// Check if we time interval for redrawing passes.
|
||||
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
|
||||
return;
|
||||
}
|
||||
lastUpdate = millis();
|
||||
|
||||
// Turn off display after 3 minutes with no change.
|
||||
if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) {
|
||||
u8x8.setPowerSave(1);
|
||||
displayTurnedOff = true;
|
||||
}
|
||||
|
||||
// Check if values which are shown on display changed from the last time.
|
||||
if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) {
|
||||
needRedraw = true;
|
||||
} else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) {
|
||||
needRedraw = true;
|
||||
} else if (knownBrightness != bri) {
|
||||
needRedraw = true;
|
||||
} else if (knownMode != strip.getMode()) {
|
||||
needRedraw = true;
|
||||
} else if (knownPalette != strip.getSegment(0).palette) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
if (!needRedraw) {
|
||||
return;
|
||||
}
|
||||
needRedraw = false;
|
||||
|
||||
if (displayTurnedOff)
|
||||
{
|
||||
u8x8.setPowerSave(0);
|
||||
displayTurnedOff = false;
|
||||
}
|
||||
lastRedraw = millis();
|
||||
|
||||
// Update last known values.
|
||||
#if defined(ESP8266)
|
||||
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
||||
#else
|
||||
knownSsid = WiFi.SSID();
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
// First row with Wifi name
|
||||
u8x8.setCursor(1, 0);
|
||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
if (knownSsid.length() > u8x8.getCols())
|
||||
u8x8.print("~");
|
||||
|
||||
// Second row with IP or Psssword
|
||||
u8x8.setCursor(1, 1);
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0)
|
||||
u8x8.print(apPass);
|
||||
else
|
||||
u8x8.print(knownIp);
|
||||
|
||||
// Third row with mode name
|
||||
u8x8.setCursor(2, 2);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
u8x8.setCursor(2, 3);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||
u8x8.drawGlyph(0, 1, 68); // home icon
|
||||
u8x8.setFont(u8x8_font_open_iconic_weather_2x2);
|
||||
u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class StairwayWipeUsermod : public Usermod {
|
||||
transitionDelayTemp = 4000; //fade out slowly
|
||||
#endif
|
||||
bri = 0;
|
||||
colorUpdated(CALL_MODE_NOTIFICATION);
|
||||
stateUpdated(CALL_MODE_NOTIFICATION);
|
||||
wipeState = 0;
|
||||
userVar0 = 0;
|
||||
previousUserVar0 = 0;
|
||||
|
||||
@@ -104,7 +104,7 @@ void turnOff()
|
||||
transitionDelayTemp = 4000; //fade out slowly
|
||||
#endif
|
||||
bri = 0;
|
||||
colorUpdated(CALL_MODE_NOTIFICATION);
|
||||
stateUpdated(CALL_MODE_NOTIFICATION);
|
||||
wipeState = 0;
|
||||
userVar0 = 0;
|
||||
previousUserVar0 = 0;
|
||||
|
||||
@@ -64,7 +64,7 @@ class AutoSaveUsermod : public Usermod {
|
||||
PSTR("~ %02d-%02d %02d:%02d:%02d ~"),
|
||||
month(localTime), day(localTime),
|
||||
hour(localTime), minute(localTime), second(localTime));
|
||||
savePreset(autoSavePreset, true, presetNameBuffer);
|
||||
savePreset(autoSavePreset, presetNameBuffer);
|
||||
}
|
||||
|
||||
void inline displayOverlay() {
|
||||
@@ -91,8 +91,8 @@ class AutoSaveUsermod : public Usermod {
|
||||
knownBrightness = bri;
|
||||
knownEffectSpeed = effectSpeed;
|
||||
knownEffectIntensity = effectIntensity;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected. Initialize own network
|
||||
@@ -106,8 +106,8 @@ class AutoSaveUsermod : public Usermod {
|
||||
if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave
|
||||
|
||||
unsigned long now = millis();
|
||||
uint8_t currentMode = strip.getMode();
|
||||
uint8_t currentPalette = strip.getSegment(0).palette;
|
||||
uint8_t currentMode = strip.getMainSegment().mode;
|
||||
uint8_t currentPalette = strip.getMainSegment().palette;
|
||||
|
||||
unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000;
|
||||
if (knownBrightness != bri) {
|
||||
|
||||
@@ -31,9 +31,33 @@ This usermod requires the `U8g2` and `Wire` libraries. See the
|
||||
`platformio_override.ini.sample` found in the Rotary Encoder
|
||||
UI usermod folder for how to include these using `platformio_override.ini`.
|
||||
|
||||
## Configuration
|
||||
|
||||
* `enabled` - enable/disable usermod
|
||||
* `pin` - GPIO pins used for display; I2C displays use Clk & Data; SPI displays can use SCK, MOSI, CS, DC & RST
|
||||
* `type` - display type in numeric format
|
||||
* 1 = I2C SSD1306 128x32
|
||||
* 2 = I2C SH1106 128x32
|
||||
* 3 = I2C SSD1306 128x64 (4 double-height lines)
|
||||
* 4 = I2C SSD1305 128x32
|
||||
* 5 = I2C SSD1305 128x64 (4 double-height lines)
|
||||
* 6 = SPI SSD1306 128x32
|
||||
* 7 = SPI SSD1306 128x64 (4 double-height lines)
|
||||
* `contrast` - set display contrast (higher contrast may reduce display lifetime)
|
||||
* `refreshRateSec` - time in seconds for display refresh
|
||||
* `screenTimeOutSec` - screen saver time-out in seconds
|
||||
* `flip` - flip/rotate display 180°
|
||||
* `sleepMode` - enable/disable screen saver
|
||||
* `clockMode` - enable/disable clock display in screen saver mode
|
||||
* `i2c-freq-kHz` - I2C clock frequency in kHz (may help reduce dropped frames, range: 400-3400)
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-02
|
||||
* First public release
|
||||
|
||||
2021-04
|
||||
* Adaptation for runtime configuration.
|
||||
* Adaptation for runtime configuration.
|
||||
|
||||
2021-11
|
||||
* Added configuration option description.
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#define HW_PIN_CLOCKSPI 18
|
||||
#define HW_PIN_DATASPI 23
|
||||
#ifndef FLD_PIN_SCL
|
||||
#define FLD_PIN_SCL 22
|
||||
#endif
|
||||
@@ -47,6 +51,10 @@
|
||||
#define FLD_PIN_RESET 26
|
||||
#endif
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#define HW_PIN_CLOCKSPI 14
|
||||
#define HW_PIN_DATASPI 13
|
||||
#ifndef FLD_PIN_SCL
|
||||
#define FLD_PIN_SCL 5
|
||||
#endif
|
||||
@@ -70,6 +78,14 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef FLD_TYPE
|
||||
#ifndef FLD_SPI_DEFAULT
|
||||
#define FLD_TYPE SSD1306
|
||||
#else
|
||||
#define FLD_TYPE SSD1306_SPI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// When to time out to the clock or blank the screen
|
||||
// if SLEEP_MODE_ENABLED.
|
||||
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
|
||||
@@ -114,11 +130,12 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
|
||||
#ifndef FLD_SPI_DEFAULT
|
||||
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
|
||||
DisplayType type = SSD1306; // display type
|
||||
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
|
||||
#else
|
||||
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST
|
||||
DisplayType type = SSD1306_SPI; // display type
|
||||
uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
|
||||
#endif
|
||||
DisplayType type = FLD_TYPE; // display type
|
||||
bool flip = false; // flip display 180°
|
||||
uint8_t contrast = 10; // screen contrast
|
||||
uint8_t lineHeight = 1; // 1 row or 2 rows
|
||||
@@ -126,6 +143,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
|
||||
bool sleepMode = true; // allow screen sleep?
|
||||
bool clockMode = false; // display clock
|
||||
bool enabled = true;
|
||||
|
||||
// Next variables hold the previous known values to determine if redraw is
|
||||
// required.
|
||||
@@ -149,12 +167,14 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _contrast[];
|
||||
static const char _refreshRate[];
|
||||
static const char _screenTimeOut[];
|
||||
static const char _flip[];
|
||||
static const char _sleepMode[];
|
||||
static const char _clockMode[];
|
||||
static const char _busClkFrequency[];
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
@@ -167,87 +187,72 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void setup() {
|
||||
if (type == NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
|
||||
bool isHW;
|
||||
PinOwner po = PinOwner::UM_FourLineDisplay;
|
||||
if (type == SSD1306_SPI || type == SSD1306_SPI64) {
|
||||
PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }};
|
||||
if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
|
||||
isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI);
|
||||
PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }};
|
||||
if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; }
|
||||
} else {
|
||||
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
|
||||
isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA);
|
||||
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } };
|
||||
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
|
||||
}
|
||||
|
||||
DEBUG_PRINTLN(F("Allocating display."));
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
#ifdef ESP8266
|
||||
if (!(ioPin[0]==5 && ioPin[1]==4))
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else
|
||||
#endif
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
lineHeight = 1;
|
||||
break;
|
||||
case SH1106:
|
||||
#ifdef ESP8266
|
||||
if (!(ioPin[0]==5 && ioPin[1]==4))
|
||||
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else
|
||||
#endif
|
||||
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
lineHeight = 2;
|
||||
break;
|
||||
case SSD1306_64:
|
||||
#ifdef ESP8266
|
||||
if (!(ioPin[0]==5 && ioPin[1]==4))
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else
|
||||
#endif
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
lineHeight = 2;
|
||||
break;
|
||||
case SSD1305:
|
||||
#ifdef ESP8266
|
||||
if (!(ioPin[0]==5 && ioPin[1]==4))
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else
|
||||
#endif
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
lineHeight = 1;
|
||||
break;
|
||||
case SSD1305_64:
|
||||
#ifdef ESP8266
|
||||
if (!(ioPin[0]==5 && ioPin[1]==4))
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else
|
||||
#endif
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
lineHeight = 2;
|
||||
break;
|
||||
case SSD1306_SPI:
|
||||
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
lineHeight = 1;
|
||||
break;
|
||||
case SSD1306_SPI64:
|
||||
if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else
|
||||
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
lineHeight = 2;
|
||||
break;
|
||||
default:
|
||||
u8x8 = nullptr;
|
||||
}
|
||||
|
||||
if (nullptr == u8x8) {
|
||||
DEBUG_PRINTLN(F("Display init failed."));
|
||||
for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
|
||||
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
|
||||
type = NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
DEBUG_PRINTLN(F("Starting display."));
|
||||
/*if (!(type == SSD1306_SPI || type == SSD1306_SPI64))*/ u8x8->setBusClock(ioFrequency); // can be used for SPI too
|
||||
u8x8->begin();
|
||||
setFlipMode(flip);
|
||||
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
@@ -263,7 +268,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Da loop.
|
||||
*/
|
||||
void loop() {
|
||||
if (millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return;
|
||||
if (!enabled || millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return;
|
||||
lastUpdate = millis();
|
||||
|
||||
redraw(false);
|
||||
@@ -273,40 +278,40 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Wrappers for screen drawing
|
||||
*/
|
||||
void setFlipMode(uint8_t mode) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setFlipMode(mode);
|
||||
}
|
||||
void setContrast(uint8_t contrast) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setContrast(contrast);
|
||||
}
|
||||
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r);
|
||||
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
|
||||
else u8x8->drawString(col, row, string);
|
||||
}
|
||||
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8->draw2x2String(col, row, string);
|
||||
}
|
||||
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setFont(font);
|
||||
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
|
||||
else u8x8->drawGlyph(col, row, glyph);
|
||||
}
|
||||
uint8_t getCols() {
|
||||
if (type==NONE) return 0;
|
||||
if (type==NONE || !enabled) return 0;
|
||||
return u8x8->getCols();
|
||||
}
|
||||
void clear() {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->clear();
|
||||
}
|
||||
void setPowerSave(uint8_t save) {
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
u8x8->setPowerSave(save);
|
||||
}
|
||||
|
||||
@@ -324,7 +329,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
static bool showName = false;
|
||||
unsigned long now = millis();
|
||||
|
||||
if (type==NONE) return;
|
||||
if (type == NONE || !enabled) return;
|
||||
if (overlayUntil > 0) {
|
||||
if (now >= overlayUntil) {
|
||||
// Time to display the overlay has elapsed.
|
||||
@@ -344,8 +349,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
(knownBrightness != bri) ||
|
||||
(knownEffectSpeed != effectSpeed) ||
|
||||
(knownEffectIntensity != effectIntensity) ||
|
||||
(knownMode != strip.getMode()) ||
|
||||
(knownPalette != strip.getSegment(0).palette)) {
|
||||
(knownMode != strip.getMainSegment().mode) ||
|
||||
(knownPalette != strip.getMainSegment().palette)) {
|
||||
knownHour = 99; // force time update
|
||||
lastRedraw = now; // update lastRedraw marker
|
||||
} else if (sleepMode && !displayTurnedOff && ((now - lastRedraw)/1000)%5 == 0) {
|
||||
@@ -393,8 +398,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMode();
|
||||
knownPalette = strip.getSegment(0).palette;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
knownEffectSpeed = effectSpeed;
|
||||
knownEffectIntensity = effectIntensity;
|
||||
|
||||
@@ -435,6 +440,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
|
||||
void drawLine(uint8_t line, Line4Type lineType) {
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
uint8_t printedChars;
|
||||
switch(lineType) {
|
||||
case FLD_LINE_BRIGHTNESS:
|
||||
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
|
||||
@@ -449,10 +455,16 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
drawString(2, line*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_MODE:
|
||||
showCurrentEffectOrPalette(knownMode, JSON_mode_names, line);
|
||||
printedChars = extractModeName(knownMode, JSON_mode_names, lineBuffer, LINE_BUFFER_SIZE-1);
|
||||
for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' ';
|
||||
lineBuffer[printedChars] = 0;
|
||||
drawString(2, line*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_PALETTE:
|
||||
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, line);
|
||||
printedChars = extractModeName(knownPalette, JSON_palette_names, lineBuffer, LINE_BUFFER_SIZE-1);
|
||||
for (;printedChars < getCols()-2 && printedChars < LINE_BUFFER_SIZE-3; printedChars++) lineBuffer[printedChars]=' ';
|
||||
lineBuffer[printedChars] = 0;
|
||||
drawString(2, line*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_TIME:
|
||||
default:
|
||||
@@ -461,41 +473,6 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the current effect or palette (desiredEntry)
|
||||
* on the appropriate line (row).
|
||||
*/
|
||||
void showCurrentEffectOrPalette(int knownMode, const char *qstring, uint8_t row) {
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(qstring); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(qstring + i);
|
||||
if (singleJsonSymbol == '\0') break;
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode)) break;
|
||||
lineBuffer[printedChars++] = singleJsonSymbol;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars >= getCols()-2) || printedChars >= sizeof(lineBuffer)-2) break;
|
||||
}
|
||||
for (;printedChars < getCols()-2 && printedChars < sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' ';
|
||||
lineBuffer[printedChars] = 0;
|
||||
drawString(2, row*lineHeight, lineBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there screen is off or in clock is displayed,
|
||||
* this will return true. This allows us to throw away
|
||||
@@ -503,6 +480,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* to wake up the screen.
|
||||
*/
|
||||
bool wakeDisplay() {
|
||||
if (type == NONE || !enabled) return false;
|
||||
knownHour = 99;
|
||||
if (displayTurnedOff) {
|
||||
// Turn the display back on
|
||||
@@ -519,6 +497,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Clears the screen and prints on the middle two lines.
|
||||
*/
|
||||
void overlay(const char* line1, const char *line2, long showHowLong) {
|
||||
if (type == NONE || !enabled) return;
|
||||
|
||||
if (displayTurnedOff) {
|
||||
// Turn the display back on (includes clear())
|
||||
sleepOrClock(false);
|
||||
@@ -580,6 +560,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* the useAMPM configuration.
|
||||
*/
|
||||
void showTime(bool fullScreen = true) {
|
||||
if (type == NONE || !enabled) return;
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
|
||||
updateLocalTime();
|
||||
@@ -673,16 +654,19 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
*/
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
JsonArray io_pin = top.createNestedArray("pin");
|
||||
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
|
||||
top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
|
||||
top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page
|
||||
top["type"] = type;
|
||||
top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page
|
||||
top[FPSTR(_flip)] = (bool) flip;
|
||||
top[FPSTR(_contrast)] = contrast;
|
||||
top[FPSTR(_refreshRate)] = refreshRate/1000;
|
||||
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
|
||||
top[FPSTR(_sleepMode)] = (bool) sleepMode;
|
||||
top[FPSTR(_clockMode)] = (bool) clockMode;
|
||||
top[FPSTR(_busClkFrequency)] = ioFrequency/1000;
|
||||
DEBUG_PRINTLN(F("4 Line Display config saved."));
|
||||
}
|
||||
|
||||
@@ -706,6 +690,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
newType = top["type"] | newType;
|
||||
for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i];
|
||||
flip = top[FPSTR(_flip)] | flip;
|
||||
@@ -714,6 +699,10 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
|
||||
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
|
||||
clockMode = top[FPSTR(_clockMode)] | clockMode;
|
||||
if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
|
||||
ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
|
||||
else
|
||||
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
@@ -728,10 +717,10 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; }
|
||||
if (pinsChanged || type!=newType) {
|
||||
if (type != NONE) delete u8x8;
|
||||
for (byte i=0; i<5; i++) {
|
||||
if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay);
|
||||
ioPin[i] = newPin[i];
|
||||
}
|
||||
PinOwner po = PinOwner::UM_FourLineDisplay;
|
||||
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po);
|
||||
for (byte i=0; i<5; i++) ioPin[i] = newPin[i];
|
||||
if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1
|
||||
type = NONE;
|
||||
return true;
|
||||
@@ -739,12 +728,13 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
setup();
|
||||
needsRedraw |= true;
|
||||
}
|
||||
if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too
|
||||
setContrast(contrast);
|
||||
setFlipMode(flip);
|
||||
if (needsRedraw && !wakeDisplay()) redraw(true);
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !(top["pin"][2]).isNull();
|
||||
return !top[FPSTR(_enabled)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -757,10 +747,12 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
|
||||
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
|
||||
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
|
||||
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
|
||||
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
|
||||
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
|
||||
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
|
||||
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
|
||||
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
|
||||
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
|
||||
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
|
||||
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
|
||||
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
|
||||
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
|
||||
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";
|
||||
|
||||
477
usermods/usermod_v2_four_line_display_ALT/4LD_wled_fonts.c
Normal file
@@ -0,0 +1,477 @@
|
||||
#pragma once
|
||||
|
||||
//WLED custom fonts, curtesy of @Benji (https://github.com/Proto-molecule)
|
||||
|
||||
|
||||
/*
|
||||
Fontname: wled_logo_akemi_4x4
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 3/3
|
||||
BBX Build Mode: 3
|
||||
* this logo ...WLED/images/wled_logo_akemi.png
|
||||
* encode map = 1, 2, 3
|
||||
*/
|
||||
const uint8_t u8x8_wled_logo_akemi_4x4[388] U8X8_FONT_SECTION("u8x8_wled_logo_akemi_4x4") =
|
||||
"\1\3\4\4\0\0\0\0\0\0\0\0\0\340\360\10\350\10\350\210\270\210\350\210\270\350\10\360\340\0\0\0"
|
||||
"\0\0\200\200\0\0@\340\300\340@\0\0\377\377\377\377\377\377\37\37\207\207\371\371\371\377\377\377\0\0\374"
|
||||
"\374\7\7\371\0\0\6\4\15\34x\340\200\177\177\377\351yy\376\356\357\217\177\177\177o\377\377\0\70\77"
|
||||
"\277\376~\71\0\0\0\0\0\0\0\1\3\3\3\1\0\0\37\77\353\365\77\37\0\0\0\0\5\7\2\3"
|
||||
"\7\4\0\0\300\300\300\300\200\200\200\0\0\0\0\0\0\0\200\200\300\300\300\300\200\200\0\0\0\0\0\0"
|
||||
"\0\200\200\300\371\37\37\371\371\7\7\377\374\0\0\0\374\377\377\37\37\341\341\377\377\377\377\374\0\0\0\374"
|
||||
"\377\7\7\231\371\376>\371\371>~\377\277\70\0\270\377\177\77\376\376\71\371\371\71\177\377\277\70\0\70\377"
|
||||
"\177>\376\371\377\377\0\77\77\0\0\4\7\2\7\5\0\0\0\377\377\0\77\77\0\0\0\5\7\2\7\5"
|
||||
"\0\0\377\377\300\300\300\200\200\0\0\0\0\0\0\0\200\200\300\300\300\300\300\200\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\231\231\231\371\377\377\374\0\0\0\374\377\347\347\371\1\1\371\371\7\7\377\374\0\0\0@\340"
|
||||
"\300\340@\0\71\371\371\71\177\377\277\70\0\70\277\377\177\71\371\370\70\371\371~\376\377\77\70\200\340x\34"
|
||||
"\15\4\6\0\0\77\77\0\0\0\5\7\2\7\5\0\0\0\377\377\0\77\77\0\0\1\3\3\1\1\0\0"
|
||||
"\0\0\0";
|
||||
|
||||
|
||||
/*
|
||||
Fontname: wled_logo_akemi_5x5
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 3/3
|
||||
BBX Build Mode: 3
|
||||
* this logo ...WLED/images/wled_logo_akemi.png
|
||||
* encoded = 1, 2, 3
|
||||
*/
|
||||
/*
|
||||
const uint8_t u8x8_wled_logo_akemi_5x5[604] U8X8_FONT_SECTION("u8x8_wled_logo_akemi_5x5") =
|
||||
"\1\3\5\5\0\0\0\0\0\0\0\0\0\0\0\0\340\340\374\14\354\14\354\14|\14\354\14||\14\354"
|
||||
"\14\374\340\340\0\0\0\0\0\0\0\200\0\0\0\200\200\0\200\200\0\0\0\0\377\377\377\376\377\376\377\377"
|
||||
"\377\377\77\77\307\307\307\307\306\377\377\377\0\0\0\360\374>\77\307\0\0\61cg\357\347\303\301\200\0\0"
|
||||
"\377\377\377\317\317\317\317\360\360\360\374\374\377\377\377\377\377\377\377\377\0\0\200\377\377\340\340\37\0\0\0\0"
|
||||
"\0\0\1\3\17\77\374\360\357\357\177\36\14\17\357\377\376\376>\376\360\357\17\17\14>\177o\340\300\343c"
|
||||
"{\77\17\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\37\37\362\375\37\37\17\0\0"
|
||||
"\0\0\1\1\1\0\1\1\1\0\0\0\200\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\200\200"
|
||||
"\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\200\200\307\307\377\377\307\307\307\77>\374\360\0"
|
||||
"\0\0\360\374\376\377\377\377\7\7\7\377\377\377\377\376\374\360\0\0\0\0\360\374\36\37\37\343\37\37\340\340"
|
||||
"\37\37\37\340\340\377\377\200\0\200\377\377\377\340\340\340\37\37\37\37\37\37\37\377\377\377\200\0\0\200\377\377"
|
||||
"\340\340\340\34\377\377\3\3\377\377\3\17\77{\343\303\300\303\343s\77\37\3\377\377\3\3\377\377\3\17\77"
|
||||
"{\343\303\300\300\343{\37\17\3\377\377\377\377\0\0\37\37\0\0\1\1\1\1\0\1\1\1\1\0\0\377"
|
||||
"\377\0\0\37\37\0\0\1\1\1\1\0\0\1\1\1\0\0\377\377\300\300\300\200\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\200\200\300\300\300\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\343\343\343\343"
|
||||
"\343\377\376\374\360\0\0\0\360\374\376\77\77\307\307\7\7\307\307\307\77>\374\360\0\0\0\0\0\200\200\0"
|
||||
"\200\200\0\0\34\34\34\37\37\377\377\377\377\200\0\200\377\377\377\377\37\37\37\0\0\37\37\37\340\340\377\377"
|
||||
"\200\0\0\0\1\303\347\357gc\61\0\3\3\377\377\3\7\37\177s\343\300\303s{\37\17\7\3\377\377"
|
||||
"\3\3\377\377\3\37\77scp<\36\17\3\1\0\0\0\0\0\0\0\37\37\0\0\0\1\1\1\0\1"
|
||||
"\1\1\0\0\0\0\377\377\0\0\37\37\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||
*/
|
||||
|
||||
/*
|
||||
Fontname: wled_logo_2x2
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 4/4
|
||||
BBX Build Mode: 3
|
||||
* this logo https://cdn.discordapp.com/attachments/706623245935444088/927361780613799956/wled_scaled.png
|
||||
* encode map = 1, 2, 3, 4
|
||||
*/
|
||||
const uint8_t u8x8_wled_logo_2x2[133] U8X8_FONT_SECTION("u8x8_wled_logo_2x2") =
|
||||
"\1\4\2\2\0\0\0\0\0\200\200\360\360\16\16\16\16\0\0\0\340\340\340\340\340\37\37\1\1\0\0\0"
|
||||
"\0\0\0\0\360\360\16\16\16\200\200\16\16\16\360\360\0\0\0\200\37\37\340\340\340\37\37\340\340\340\37\37"
|
||||
"\0\0\0\37\200~~\0\0\0\0\0\0\0\360\360\216\216\216\216\37\340\340\340\340\340\340\340\0\0\37\37"
|
||||
"\343\343\343\343\16\16\0\0ppp\16\16\376\376\16\16\16\360\360\340\340\0\0\0\0\0\340\340\377\377\340"
|
||||
"\340\340\37\37";
|
||||
|
||||
|
||||
/*
|
||||
Fontname: wled_logo_4x4
|
||||
Copyright: Created with Fony 1.4.7
|
||||
Glyphs: 4/4
|
||||
BBX Build Mode: 3
|
||||
* this logo https://cdn.discordapp.com/attachments/706623245935444088/927361780613799956/wled_scaled.png
|
||||
* encode map = 1, 2, 3, 4
|
||||
*/
|
||||
/*
|
||||
const uint8_t u8x8_wled_logo_4x4[517] U8X8_FONT_SECTION("u8x8_wled_logo_4x4") =
|
||||
"\1\4\4\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\374\374\374\374\374\374\374\374\374"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\300\300\300\300\300\377\377\377\377\377\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\17\17\17\17\17\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\370\370\370\370\370\370\370\370\370\7\7\7\7\7\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\374\374\374\374\374\0\0\0\0\0\374\374\374\374\374\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\377\377\377\377\377\0\0\0\0\0\300\300\300\300\300\0\0\0\0\0\377\377\377\377\377\0\0"
|
||||
"\0\0\300\300\0\377\377\377\377\377\0\0\0\0\0\377\377\377\377\377\0\0\0\0\0\377\377\377\377\377\0\0"
|
||||
"\0\0\377\377\0\7\7\7\7\7\370\370\370\370\370\7\7\7\7\7\370\370\370\370\370\7\7\7\7\7\0\0"
|
||||
"\0\0\7\7\0\0\0\374\374\374\374\374\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\374\374\374"
|
||||
"\374\374\374\374\300\300\300\77\77\77\77\77\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\300\300\300"
|
||||
"\300\300\300\300\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\37\37\37"
|
||||
"\37\37\37\37\7\7\7\370\370\370\370\370\370\370\370\370\370\370\370\370\0\0\0\0\7\7\7\7\7\370\370\370"
|
||||
"\370\370\370\370\374\374\374\374\374\374\0\0\0\0\0\0\0\0\374\374\374\374\374\374\374\374\374\374\374\374\374\374"
|
||||
"\0\0\0\0\300\300\0\0\0\0\0\0\0\77\77\77\77\77\0\0\0\0\377\377\377\377\377\0\0\0\0\377"
|
||||
"\377\377\377\377\37\37\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\0\0\0\0\377"
|
||||
"\377\377\377\377\370\370\370\370\370\370\0\0\0\0\0\0\0\0\370\370\370\370\377\377\377\377\377\370\370\370\370\377"
|
||||
"\7\7\7\7";
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_1x
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 13/13
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*-----------
|
||||
* 20 = wifi
|
||||
* 21 = media-play
|
||||
*/
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_1x1[172] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_1x1") =
|
||||
"\1\25\1\1\0B\30<<\30B\0~<\30\0~<\30\0p\374\77\216\340\370\360\0<n\372\377"
|
||||
"\275\277\26\34\374\374\77\77\374\374\60\60<~~\360\340``\0\200\340\360p\14\16\6\1<~\377\377"
|
||||
"\201\201B<\70D\200\217\200D\70\0\0\10x<<x\10\0\14\36>||>\36\14\64 \336\67"
|
||||
";\336 \64\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\1\11\311"
|
||||
"\311\1\2\0\0~<<\30\30\0";
|
||||
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_2x1
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 11/11
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*/
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_2x1[196] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_2x1") =
|
||||
"\1\14\2\1\20\20BB\30\30<\275\275<\30\30BB\20\20\377~<<\70\30\20\0\377~<<"
|
||||
"\70\30\20\0\60p\370\374\77>\236\214\300\340\370\360\360\340\0\0\34<v\326\336\375\375\377\277\275=>"
|
||||
"\66\66<\34\374\374\374\374~\77\77~\374\374\374\374 pp \30<~~\377\370\360\360\340\340\340\340"
|
||||
"@@ \0\200\300\340\360\360p`\10\34\34\16\6\6\3\0\0\70|~\376\376\377\377\377\201\201\203\202"
|
||||
"\302Fl\70\70xL\204\200\200\217\217\200\200\204Lx\70\0\0\10\10\30\330x|\77\77|x\330\30"
|
||||
"\10\10\0\0\14\36\37\77\77\177~\374\374~\177\77\77\37\36\14\24\64 \60>\26\367\33\375\36>\60"
|
||||
" \64\24";
|
||||
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_2x
|
||||
Copyright:
|
||||
Glyphs: 11/11
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*/
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_2x2[389] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_2x2") =
|
||||
"\1\14\2\2\200\200\14\14\300\340\360\363\363\360\340\300\14\14\200\200\1\1\60\60\3\7\17\317\317\17\7\3"
|
||||
"\60\60\1\1\374\370\360\340\340\300\200\0\374\370\360\340\340\300\200\0\77\37\17\7\7\3\1\0\77\37\17\7"
|
||||
"\7\3\1\0\0\200\340\360\377\376\374\360\0\0\300\200\0\0\0\0\17\77\177\377\17\7\301\340\370\374\377\377"
|
||||
"\377|\0\0\360\370\234\236\376\363\363\377\377\363\363\376><\370\360\3\17\77yy\377\377\377\377\317\17\17"
|
||||
"\17\17\7\3\360\360\360\360\366\377\377\366\360\360\360\360\0\0\0\0\377\377\377\377\237\17\17\237\377\377\377\377"
|
||||
"\6\17\17\6\340\370\374\376\377\340\200\0\0\0\0\0\0\0\0\0\3\17\37\77\177\177\177\377\376|||"
|
||||
"\70\30\14\0\0\0\0\0\0\0\0``\360\370|<\36\7\2\0\300\360\376\377\177\77\36\0\1\1\0"
|
||||
"\0\0\0\0\340\370\374\376\376\377\377\377\3\3\7\6\16<\370\340\7\37\77\177\177\377\377\377\300\300\340`"
|
||||
"p<\37\7\300\340p\30\0\0\377\377\0\0\30p\340\300\0\0\17\37\70`\340\300\300\300\300\340`\70"
|
||||
"\37\17\0\0\0@\300\300\300\300\340\374\374\340\300\300\300\300@\0\0\0\0\1s\77\37\17\17\37\77s"
|
||||
"\1\0\0\0\360\370\374\374\374\374\370\360\360\370\374\374\374\374\370\360\0\1\3\7\17\37\77\177\177\77\37\17"
|
||||
"\7\3\1\0\200\200\0\0\0\360\370\374<\334\330\360\0\0\200\200\2\2\14\30\24\37\6~\7\177\7\37"
|
||||
"\24\30\16\2";
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_3x
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 11/11
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*/
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_3x3[868] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_3x3") =
|
||||
"\1\14\3\3\0\0\34\34\34\0\200\300\300\340\347\347\347\340\300\300\200\0\34\34\34\0\0\0\34\34\34\0"
|
||||
"\0>\377\377\377\377\377\377\377\377\377\377\377>\0\0\34\34\34\0\0\0\16\16\16\0\0\1\1\3ss"
|
||||
"s\3\1\1\0\0\34\34\34\0\0\0\370\360\340\300\300\200\0\0\0\0\0\0\370\360\340\300\300\200\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\377\376~<\70\20\377\377\377\377\377\377\377\376~<\70\20\37\17\17\7"
|
||||
"\3\1\1\0\0\0\0\0\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\300\361\376\374\370\360\300"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376\377\377\377\377\377\177\77\17\6\0\200\342\374\370\360\340"
|
||||
"\200\0\0\0\1\17\37\77\177\377\7\3\0\200\360\370\374\376\377\377\377\377\377\377\77\0\0\0\0\200\340\360"
|
||||
"\370\370\374\316\206\206\317\377\377\377\317\206\206\316\374\374\370\360\340\200<\377\377\371\360py\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\363\341\341\363\377\177\0\1\7\17\34\70x|\377\377\377\377\367\363c\3\3\3\3\1"
|
||||
"\1\1\0\0\300\300\300\300\300\300\300\316\377\377\377\316\300\300\300\300\300\300\0\0\0\0\0\0\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\300\300\340\340\340\300\377\377\377\377\377\377\377\307\3\3\3\307"
|
||||
"\377\377\377\377\377\377\1\1\3\3\3\1\0\300\340\370\374\374\376\377\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0>\377\377\377\377\377\377\377\377\374\360\340\300\300\200\200\0\0\0\0\0\0\200\200\0\1\7\17"
|
||||
"\37\37\77\177\177\177\177\377\377\377\177\177\177\77\77\37\17\7\3\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\200\200\300\340\340\360\370\374|>\17\6\0\0\0\0\0\340\340\360\360\360\342\303\7\17\37\77\37\7\3\1"
|
||||
"\0\0\0\0\0\200\340\360\377\377\377\377\177\77\37\17\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\360"
|
||||
"\370\374\374\376\376\376\377\377\7\7\7\6\16\16\34\70\360\340\300\0|\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\0\0\0\0\0\0\0\0\0\377\377\377\0\3\7\17\37\77\177\177\377\377\377\377\340\340\340\340pp\70<"
|
||||
"\37\17\3\0\0\0\200\300\340\340\300\0\0\377\377\377\0\0\300\340\340\300\200\0\0\0\0\0\370\376\377\17"
|
||||
"\3\0\0\0\0\17\17\17\0\0\0\0\0\3\17\377\376\370\0\0\0\7\17\37<xp\340\340\340\340\340"
|
||||
"\340\340\340px<\37\17\3\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\376\370\200\0\0\0\0\0"
|
||||
"\0\0\0\0\0\2\6\16\36\36>~\376\376\377\377\377\377\377\376\376~>\36\16\6\6\2\0\0\0\0"
|
||||
"\0\300x<\37\17\17\7\3\7\17\17\37<x\300\0\0\0\0\200\300\340\360\360\370\370\370\360\360\340\300"
|
||||
"\200\300\340\360\360\370\370\370\360\360\340\200\17\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\177\77\37\17\0\0\0\0\0\1\3\7\17\37\77\177\177\77\37\17\7\3\1\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\200\300\340\360\370\370\370p`\300\200\0\0\0\0\0\0&,f\300\0\0\300\377\377\357\357\357"
|
||||
"\363\370\377\377\377\377\300\0\0\306l&\0\0\0\1\7\16\14\6\7\1\177\177\0\177\177\1\7\6\14\16"
|
||||
"\7\1\0";
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_4x
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 11/11
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*/
|
||||
/*
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_4x4[1540] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_4x4") =
|
||||
"\1\14\4\4\0\0\0\0`\360\360`\0\0\0\0\0\0\6\17\17\6\0\0\0\0\0\0`\360\360`"
|
||||
"\0\0\0\0\200\300\300\200\0\0\0\0\340\370\374\376\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0"
|
||||
"\200\300\300\200\1\3\3\1\0\0\0\0\7\37\77\177\177\377\377\377\377\377\377\177\177\77\37\7\0\0\0\0"
|
||||
"\1\3\3\1\0\0\0\0\6\17\17\6\0\0\0\0\0\0`\360\360`\0\0\0\0\0\0\6\17\17\6"
|
||||
"\0\0\0\0\360\340\300\200\200\0\0\0\0\0\0\0\0\0\0\0\360\340\300\200\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\376\374\374\370\360\340"
|
||||
"\340\300\200\0\377\377\377\377\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\177\77\77\37\17\7"
|
||||
"\7\3\1\0\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\17\7\3\1\1\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\200\341\376\374\370\360\340\300\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\200\300\360\370\374\376\377\377\377\377\377\377\377\377~\0\0\0\0\20\340\300\200\0\0\0\0"
|
||||
"\0\0\0\0~\377\377\377\377\377\377\377\377\177\77\37\17\3\1\0\200\300\340\370\376\377\377\377\377\376\374\340"
|
||||
"\0\0\0\0\0\3\7\17\37\77\177\207\1\0\0\0\340\370\374\377\377\377\377\377\377\377\377\377\377\177\77\17"
|
||||
"\0\0\0\0\0\0\0\200\300\360\370\370\374\34\16\16\16\36\377\377\377\377\37\16\16\16\36\374\374\370\370\360"
|
||||
"\340\300\0\0\340\374\377\377\217\7\7\7\217\377\376\376\376\377\377\377\377\377\377\376\376\376\377\377\217\7\7\7"
|
||||
"\217\377\377\374\17\177\377\377\377\37\17\17\17\37\377\377\377\377\377\377\377\377\377\377\377\377\177\177\177\177\77\77"
|
||||
"\77\37\17\7\0\0\0\3\7\17\36>>\177\177\377\377\377\377\377\377\371p\60\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0<\376\377\377\377\377\376<\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377~~\377\377"
|
||||
"\377\377~<\377\377\377\377\377\377\377\377\303\1\0\0\0\0\1\303\377\377\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\0\0\200\340\360\370\374\374\376\376\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\370\377\377\377\377\377\377\377\377\377\376\360\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\7\77\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\370\370\360\360\360\340\340\340\340\340\340"
|
||||
"\340\340\60\0\0\0\0\1\3\7\17\37\37\77\77\77\177\177\177\177\177\177\177\177\77\77\77\37\37\17\7\3"
|
||||
"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\200\300\340\340\360\370\374\374"
|
||||
"~\77\16\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\30\34>~\377\377\377\377\177\77\37\7\3\0"
|
||||
"\0\0\0\0\0\0\0\0\0\360\374\376\377\377\377\377\377\376\374\370\0\0\0\3\3\1\0\0\0\0\0\0"
|
||||
"\0\0\0\0@@\340\370\374\377\377\377\177\177\177\77\37\17\7\1\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\200\300\340\360\370\374\374\376\376\376\377\377\377\377\17\17\17\37\36\36>|\374\370\360\340"
|
||||
"\300\200\0\0\360\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\37"
|
||||
"\377\377\376\360\17\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\200\300\370"
|
||||
"\377\377\177\17\0\0\1\3\7\17\37\77\77\177\177\177\377\377\377\377\360\360\360\370xx|>\77\37\17\7"
|
||||
"\3\1\0\0\0\0\0\0\0\200\300\200\0\0\0\0\377\377\377\377\0\0\0\0\200\300\200\0\0\0\0\0"
|
||||
"\0\0\0\0\300\360\374\376\177\37\7\3\3\0\0\0\377\377\377\377\0\0\0\3\3\7\37\177\376\374\360\300"
|
||||
"\0\0\0\0\77\377\377\377\340\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\377\377\377\77"
|
||||
"\0\0\0\0\0\0\3\7\17\37><|x\370\360\360\360\360\360\360\370x|<>\37\17\7\3\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\340\374\374\340\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\20\60p\360\360\360\360\360\360\360\360\370\377\377\377\377\377\377\370\360\360\360\360\360\360\360\360"
|
||||
"p\60\20\0\0\0\0\0\0\0\1\3\7\317\377\377\377\377\377\377\377\377\377\377\377\377\317\7\3\1\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0p>\37\17\17\7\3\1\0\0\1\3\7\17\17\37>p\0\0\0"
|
||||
"\0\0\0\0\0\200\300\340\340\360\360\360\360\360\360\340\340\300\200\0\0\200\300\340\340\360\360\360\360\360\360\340"
|
||||
"\340\300\200\0~\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377~\0\1\3\7\17\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17"
|
||||
"\7\3\1\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\177\177\77\37\17\7\3\1\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\360\360\340\340\300\200\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0@\340\300\340@\0\0\0\376\377\377\177\177\177\237\207\347\371\371\371\377\376\0\0\0\0@"
|
||||
"\340\300\340@\2\4\4\35x\340\200\0\30\237\377\177\36\376\376\37\37\377\377\37\177\377\237\30\0\200\340x"
|
||||
"\34\5\4\2\0\0\0\0\0\1\3\3\3\1\0\0\0\17\17\0\0\17\17\0\0\0\1\3\3\3\1\0"
|
||||
"\0\0\0";
|
||||
*/
|
||||
|
||||
/*
|
||||
Fontname: 4LineDisplay_WLED_icons_6x
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 11/11
|
||||
BBX Build Mode: 3
|
||||
* 1 = sun
|
||||
* 2 = skip forward
|
||||
* 3 = fire
|
||||
* 4 = custom palette
|
||||
* 5 = puzzle piece
|
||||
* 6 = moon
|
||||
* 7 = brush
|
||||
* 8 = contrast
|
||||
* 9 = power-standby
|
||||
* 10 = star
|
||||
* 11 = heart
|
||||
* 12 = Akemi
|
||||
*/
|
||||
// you can replace this (wasteful) font by using 3x3 variant with draw2x2Glyph()
|
||||
const uint8_t u8x8_4LineDisplay_WLED_icons_6x6[3460] U8X8_FONT_SECTION("u8x8_4LineDisplay_WLED_icons_6x6") =
|
||||
"\1\14\6\6\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\36\77\77\77\77\36\0"
|
||||
"\0\0\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\7\17\17\17\17\7"
|
||||
"\0\0\0\0\200\300\340\340\340\360\360\360\360\360\360\340\340\340\300\200\0\0\0\0\7\17\17\17\17\7\0\0"
|
||||
"\0\0\0\0\300\340\340\340\340\300\0\0\0\0\0\0\340\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\376\374\340\0\0\0\0\0\0\300\340\340\340\340\300\3\7\7\7\7\3\0\0\0\0\0\0"
|
||||
"\7\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\7\0\0\0\0\0\0\3\7"
|
||||
"\7\7\7\3\0\0\0\0\0\0\340\360\360\360\360\340\0\0\0\0\1\3\7\7\7\17\17\17\17\17\17\7"
|
||||
"\7\7\3\1\0\0\0\0\340\360\360\360\360\340\0\0\0\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1"
|
||||
"\0\0\0\0\0\0\0\0\0x\374\374\374\374x\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1\0\0"
|
||||
"\0\0\0\0\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\200\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200"
|
||||
"\200\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\77\37\17\7"
|
||||
"\7\3\1\0\377\377\377\177\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\377\377\377\177"
|
||||
"\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\376\374\374\370\360\340\300\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\360\374"
|
||||
"\377\377\377\377\377\377\377\377\377\376\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\300\340\360\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\0\0"
|
||||
"\0\0\4\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\0\0\370\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\177\77\37\7\3\0\0\0\0\0\200\300\360\374\377\377\377\377\377\377\377\376\370\340\0\0\0"
|
||||
"\0\0\0\0\3\37\177\377\377\377\377\377\377\377\377\377\77\17\7\1\0\0\0\0\0\200\300\360\370\374\376\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\77\177\200"
|
||||
"\0\0\0\0\0\0\340\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\17\1\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\370|<>>>~\377\377\377\377\377\377\377\177"
|
||||
"\77\36\36\36\36<|\370\370\360\360\340\340\200\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377"
|
||||
"\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\370\360\340\340\340\340\360\370\377\377\377\377\377\377\377\377\377"
|
||||
"\374\360\340\200\360\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\17\377\377\377\377\377\377\377\376~>>"
|
||||
"\77\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\376\377\377\377"
|
||||
"\177\77\37\7\0\0\3\17\77\177\377\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\377\377\377\377\77\17"
|
||||
"\17\7\7\7\7\7\7\7\7\7\3\3\3\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37"
|
||||
"\37\77\77\177\177\177\377\377\377\377\377\377\377\377\377~\30\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\370\374\376\377\377\377\377\377\377\376\374\360\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\360\360\360\360\360\360\360\360\360\360\360\360"
|
||||
"\360\363\377\377\377\377\377\377\377\377\363\360\360\360\360\360\360\360\360\360\360\360\360\360\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\374\376\376\377\377\377\377"
|
||||
"\377\376\374\360\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\17\17\17\17\17\37\77\177\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\3\3\7\7\17\17\17\17\7\7\3\0\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\360\300\0\0\0\0\0\0\0\0\300\360\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\376\376\7\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377\377"
|
||||
"\377\377\377\340\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\360\300\200\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\177\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\376\374\370\360\360\340\340\300\300\300\200\200\200\200\0\0\0\0\0\0\200\200"
|
||||
"\200\200\0\0\0\0\1\7\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\7\1\0\0\0\0\0\0\0\0\0\0\1\3\3\7"
|
||||
"\17\17\37\37\37\77\77\77\77\177\177\177\177\177\177\77\77\77\77\37\37\37\17\17\7\3\3\1\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\200\200\300\340\360\360\370\374\374\376\377~\34\10\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\200\300\300\340\360\360\370\374\376\376\377\377\377\377\377\377\177\77\17\7\3"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\6\17\17\37\77\177\377"
|
||||
"\377\377\377\377\377\377\77\37\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376"
|
||||
"\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0\3\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\200\360\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\17\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`px\374\376\377\377\377\377\377\377"
|
||||
"\177\177\177\77\77\37\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\374\376\376\376\377\377\377\377\377\77\77\77\77"
|
||||
"\177~~\376\374\374\374\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\340\360\374\376\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\1\3\7\17\37\177\377\377\376\374"
|
||||
"\360\340\0\0\370\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\1\17\377\377\377\377\377\370\37\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\360\377\377"
|
||||
"\377\377\377\37\0\0\7\17\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\0\200\200\300\340\360\370\376\377\377\177\77\17\7\0\0\0\0\0\0\0\0\0\1\3\7\17\17"
|
||||
"\37\77\77\77\177\177\177\377\377\377\377\377\374\374\374\374\376~~\177\77\77\77\37\17\17\7\3\1\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\300\340\360\370\374\376\376|"
|
||||
"x \0\0\0\0\377\377\377\377\377\377\0\0\0\0 x|\376\376\374\370\360\340\300\200\0\0\0\0\0"
|
||||
"\0\0\0\0\300\370\376\377\377\377\177\17\7\1\0\0\0\0\0\0\0\0\377\377\377\377\377\377\0\0\0\0"
|
||||
"\0\0\0\0\1\7\37\177\377\377\377\376\370\200\0\0\0\0\0\0\177\377\377\377\377\377\200\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\377\377\377\377\377\177\0\0"
|
||||
"\0\0\0\0\0\7\37\177\377\377\377\374\370\340\300\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\200\200\300\340\370\374\377\377\377\177\37\7\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\37\77"
|
||||
"\77\177~~~\374\374\374\374\374\374\374\374~~~\177\77\77\37\37\17\7\3\1\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\340\374\374\340\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\300\370\377\377\377\377\377\377\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\4\14\34<<|\374\374\374\374\374\374\374\374\374\374\374\376\377\377\377\377\377\377\377\377\377"
|
||||
"\377\376\374\374\374\374\374\374\374\374\374\374\374|<<\34\14\4\0\0\0\0\0\0\0\0\0\1\3\3\7"
|
||||
"\17\37\77\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\77\37\17\7\3\3\1\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\377\377\377\377\377\377\177\77\37\17\17\37\77\177"
|
||||
"\377\377\377\377\377\377\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0p>"
|
||||
"\37\17\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\17\37>p\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\200\200\200\300\300\300\300\300\300\200\200\200\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\200\200\200\300\300\300\300\300\300\200\200\200\0\0\0\0\0\0\200\360\370\374\376\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\376\374\370\360\200\200\360\370\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376"
|
||||
"\374\370\360\200\37\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\1\3\7\17\37\77\177\377\377\377"
|
||||
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\7"
|
||||
"\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\177\377\377\377\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\177\77\37\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\1\3\7\17\37\77\77\37\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\300\300\300\300\300\300"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\340\370\370\376\376\377\377\377\377\377\377\377\377\77\77\77>\376\370\370\340\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0 p\360\340\360p \0\0\0\0\0\0\377\377\377\377\177\177\177\177\177\207\207\340\340\377"
|
||||
"\377\377\377\377\377\377\377\0\0\0\0\0 p\360\340\360p \0\6\4\14\14\15|x\360\200\200\0\0"
|
||||
"pp\177\177\377\377\374|\374\374\374\177\177\177\377\377\377\177\377\377\377\377\177pp\0\0\200\200\360x}"
|
||||
"\14\14\4\6\0\0\0\0\0\0\0\3\37\37|ppp\34\34\37\3\3\0\377\377\377\0\0\0\377\377"
|
||||
"\377\0\3\3\37\37\34ppp~\37\37\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\7\7\7\0\0\0\7\7\7\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0";
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Fontname: akemi_8x8
|
||||
Copyright: Benji (https://github.com/proto-molecule)
|
||||
Glyphs: 1/1
|
||||
BBX Build Mode: 3
|
||||
* 12 = Akemi
|
||||
*/
|
||||
/*
|
||||
const uint8_t u8x8_akemi_8x8[516] U8X8_FONT_SECTION("u8x8_akemi_8x8") =
|
||||
"\14\14\10\10\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\200\200\200\200\200\200\200\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\340\340\370\370\376\376\376\376"
|
||||
"\377\377\377\377\377\377\377\377\376\376\376\376\370\370\340\340\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\376\376\377\377\377\377\377\377\377\377"
|
||||
"\377\377\377\377\37\37\37\343\343\343\343\343\343\377\377\377\376\376\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\30\30~~\370\370~~\30\30\0\0\0\0\0\0\0\377\377\377\377\377\77\77\77\77\77"
|
||||
"\77\300\300\300\370\370\370\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\30\0f\0\200\0\0"
|
||||
"\0\0\0\0\6\6\30\30\30\31\371\370\370\340\340\0\0\0\0\0\340\340\377\377\377\377\377\376\376\376\376\376"
|
||||
"\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\371\346\346\6\6\6\6\6\0\340\340\340\341\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\1\1\37\37\377\376\376\340\340\200\201\201\341\341\177\177\37\37\1\1\377\377"
|
||||
"\377\377\1\1\1\1\377\377\377\377\1\1\37\37\177\177\341\341\201\201\200\200\370\370\376\376\37\37\1\1\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\7\7\7\7\7\7\1\1\0\0\0\0\0\0\377\377"
|
||||
"\377\377\0\0\0\0\377\377\377\377\0\0\0\0\0\0\1\1\7\7\7\7\7\7\1\1\0\0\0\0\0\0"
|
||||
"\0\0\0";
|
||||
*/
|
||||
45
usermods/usermod_v2_four_line_display_ALT/readme.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# I2C 4 Line Display Usermod ALT
|
||||
|
||||
Thank you to the authors of the original version of these usermods. It would not have been possible without them!
|
||||
"usermod_v2_four_line_display"
|
||||
"usermod_v2_rotary_encoder_ui"
|
||||
|
||||
The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod.
|
||||
The display usermod UI has been completely changed.
|
||||
|
||||
|
||||
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
|
||||
Without the display it functions identical to the original.
|
||||
The original "usermod_v2_auto_save" will not work with the display just yet.
|
||||
|
||||
Press the encoder to cycle through the options:
|
||||
*Brightness
|
||||
*Speed
|
||||
*Intensity
|
||||
*Palette
|
||||
*Effect
|
||||
*Main Color (only if display is used)
|
||||
*Saturation (only if display is used)
|
||||
|
||||
Press and hold the encoder to display Network Info
|
||||
if AP is active then it will display AP ssid and Password
|
||||
|
||||
Also shows if the timer is enabled
|
||||
|
||||
[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI)
|
||||
|
||||
## Installation
|
||||
|
||||
Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions
|
||||
Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file,
|
||||
or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file
|
||||
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-10
|
||||
* First public release
|
||||
@@ -290,12 +290,8 @@ public:
|
||||
}
|
||||
|
||||
void lampUdated() {
|
||||
bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette);
|
||||
|
||||
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
|
||||
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
updateInterfaces(CALL_MODE_DIRECT_CHANGE);
|
||||
colorUpdated(CALL_MODE_BUTTON);
|
||||
updateInterfaces(CALL_MODE_BUTTON);
|
||||
}
|
||||
|
||||
void changeBrightness(bool increase) {
|
||||
|
||||
45
usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Rotary Encoder UI Usermod ALT
|
||||
|
||||
Thank you to the authors of the original version of these usermods. It would not have been possible without them!
|
||||
"usermod_v2_four_line_display"
|
||||
"usermod_v2_rotary_encoder_ui"
|
||||
|
||||
The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod.
|
||||
The display usermod UI has been completely changed.
|
||||
|
||||
|
||||
The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod.
|
||||
Without the display it functions identical to the original.
|
||||
The original "usermod_v2_auto_save" will not work with the display just yet.
|
||||
|
||||
Press the encoder to cycle through the options:
|
||||
*Brightness
|
||||
*Speed
|
||||
*Intensity
|
||||
*Palette
|
||||
*Effect
|
||||
*Main Color (only if display is used)
|
||||
*Saturation (only if display is used)
|
||||
|
||||
Press and hold the encoder to display Network Info
|
||||
if AP is active then it will display AP ssid and Password
|
||||
|
||||
Also shows if the timer is enabled
|
||||
|
||||
[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI)
|
||||
|
||||
## Installation
|
||||
|
||||
Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions
|
||||
Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file,
|
||||
or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file
|
||||
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`.
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-10
|
||||
* First public release
|
||||
@@ -0,0 +1,828 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
//
|
||||
// Inspired by the original v2 usermods
|
||||
// * usermod_v2_rotaty_encoder_ui
|
||||
//
|
||||
// v2 usermod that provides a rotary encoder-based UI.
|
||||
//
|
||||
// This usermod allows you to control:
|
||||
//
|
||||
// * Brightness
|
||||
// * Selected Effect
|
||||
// * Effect Speed
|
||||
// * Effect Intensity
|
||||
// * Palette
|
||||
//
|
||||
// Change between modes by pressing a button.
|
||||
//
|
||||
// Dependencies
|
||||
// * This Usermod works best coupled with
|
||||
// FourLineDisplayUsermod.
|
||||
//
|
||||
// If FourLineDisplayUsermod is used the folowing options are also enabled
|
||||
//
|
||||
// * main color
|
||||
// * saturation of main color
|
||||
// * display network (long press buttion)
|
||||
//
|
||||
|
||||
#ifdef USERMOD_MODE_SORT
|
||||
#error "Usermod Mode Sort is no longer required. Remove -D USERMOD_MODE_SORT from platformio.ini"
|
||||
#endif
|
||||
|
||||
#ifndef ENCODER_DT_PIN
|
||||
#define ENCODER_DT_PIN 18
|
||||
#endif
|
||||
|
||||
#ifndef ENCODER_CLK_PIN
|
||||
#define ENCODER_CLK_PIN 5
|
||||
#endif
|
||||
|
||||
#ifndef ENCODER_SW_PIN
|
||||
#define ENCODER_SW_PIN 19
|
||||
#endif
|
||||
|
||||
// The last UI state, remove color and saturation option if diplay not active(too many options)
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
#define LAST_UI_STATE 8
|
||||
#else
|
||||
#define LAST_UI_STATE 4
|
||||
#endif
|
||||
|
||||
// Number of modes at the start of the list to not sort
|
||||
#define MODE_SORT_SKIP_COUNT 1
|
||||
|
||||
// Which list is being sorted
|
||||
static char **listBeingSorted;
|
||||
|
||||
/**
|
||||
* Modes and palettes are stored as strings that
|
||||
* end in a quote character. Compare two of them.
|
||||
* We are comparing directly within either
|
||||
* JSON_mode_names or JSON_palette_names.
|
||||
*/
|
||||
static int re_qstringCmp(const void *ap, const void *bp) {
|
||||
char *a = listBeingSorted[*((byte *)ap)];
|
||||
char *b = listBeingSorted[*((byte *)bp)];
|
||||
int i = 0;
|
||||
do {
|
||||
char aVal = pgm_read_byte_near(a + i);
|
||||
if (aVal >= 97 && aVal <= 122) {
|
||||
// Lowercase
|
||||
aVal -= 32;
|
||||
}
|
||||
char bVal = pgm_read_byte_near(b + i);
|
||||
if (bVal >= 97 && bVal <= 122) {
|
||||
// Lowercase
|
||||
bVal -= 32;
|
||||
}
|
||||
// Relly we shouldn't ever get to '\0'
|
||||
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
|
||||
// We're done. one is a substring of the other
|
||||
// or something happenend and the quote didn't stop us.
|
||||
if (aVal == bVal) {
|
||||
// Same value, probably shouldn't happen
|
||||
// with this dataset
|
||||
return 0;
|
||||
}
|
||||
else if (aVal == '"' || aVal == '\0') {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (aVal == bVal) {
|
||||
// Same characters. Move to the next.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// We're done
|
||||
if (aVal < bVal) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
} while (true);
|
||||
// We shouldn't get here.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
class RotaryEncoderUIUsermod : public Usermod {
|
||||
private:
|
||||
int8_t fadeAmount = 5; // Amount to change every step (brightness)
|
||||
unsigned long loopTime;
|
||||
|
||||
unsigned long buttonPressedTime = 0;
|
||||
unsigned long buttonWaitTime = 0;
|
||||
bool buttonPressedBefore = false;
|
||||
bool buttonLongPressed = false;
|
||||
|
||||
int8_t pinA = ENCODER_DT_PIN; // DT from encoder
|
||||
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder
|
||||
int8_t pinC = ENCODER_SW_PIN; // SW from encoder
|
||||
|
||||
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ...
|
||||
|
||||
uint16_t currentHue1 = 16; // default boot color
|
||||
byte currentSat1 = 255;
|
||||
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
FourLineDisplayUsermod *display;
|
||||
#else
|
||||
void* display = nullptr;
|
||||
#endif
|
||||
|
||||
// Pointers the start of the mode names within JSON_mode_names
|
||||
char **modes_qstrings = nullptr;
|
||||
|
||||
// Array of mode indexes in alphabetical order.
|
||||
byte *modes_alpha_indexes = nullptr;
|
||||
|
||||
// Pointers the start of the palette names within JSON_palette_names
|
||||
char **palettes_qstrings = nullptr;
|
||||
|
||||
// Array of palette indexes in alphabetical order.
|
||||
byte *palettes_alpha_indexes = nullptr;
|
||||
|
||||
unsigned char Enc_A;
|
||||
unsigned char Enc_B;
|
||||
unsigned char Enc_A_prev = 0;
|
||||
|
||||
bool currentEffectAndPaletteInitialized = false;
|
||||
uint8_t effectCurrentIndex = 0;
|
||||
uint8_t effectPaletteIndex = 0;
|
||||
uint8_t knownMode = 0;
|
||||
uint8_t knownPalette = 0;
|
||||
|
||||
uint8_t currentCCT = 128;
|
||||
bool isRgbw = false;
|
||||
|
||||
byte presetHigh = 0;
|
||||
byte presetLow = 0;
|
||||
|
||||
bool applyToAll = true;
|
||||
|
||||
bool initDone = false;
|
||||
bool enabled = true;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _DT_pin[];
|
||||
static const char _CLK_pin[];
|
||||
static const char _SW_pin[];
|
||||
static const char _presetHigh[];
|
||||
static const char _presetLow[];
|
||||
static const char _applyToAll[];
|
||||
|
||||
/**
|
||||
* Sort the modes and palettes to the index arrays
|
||||
* modes_alpha_indexes and palettes_alpha_indexes.
|
||||
*/
|
||||
void sortModesAndPalettes() {
|
||||
modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
|
||||
modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
|
||||
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
|
||||
|
||||
palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
|
||||
palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());
|
||||
|
||||
// How many palette names start with '*' and should not be sorted?
|
||||
// (Also skipping the first one, 'Default').
|
||||
int skipPaletteCount = 1;
|
||||
while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ;
|
||||
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
|
||||
}
|
||||
|
||||
byte *re_initIndexArray(int numModes) {
|
||||
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
|
||||
for (byte i = 0; i < numModes; i++) {
|
||||
indexes[i] = i;
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of mode or palette names from the JSON string.
|
||||
* They don't end in '\0', they end in '"'.
|
||||
*/
|
||||
char **re_findModeStrings(const char json[], int numModes) {
|
||||
char **modeStrings = (char **)malloc(sizeof(char *) * numModes);
|
||||
uint8_t modeIndex = 0;
|
||||
bool insideQuotes = false;
|
||||
// advance past the mark for markLineNum that may exist.
|
||||
char singleJsonSymbol;
|
||||
|
||||
// Find the mode name in JSON
|
||||
bool complete = false;
|
||||
for (size_t i = 0; i < strlen_P(json); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(json + i);
|
||||
if (singleJsonSymbol == '\0') break;
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
if (insideQuotes) {
|
||||
// We have a new mode or palette
|
||||
modeStrings[modeIndex] = (char *)(json + i + 1);
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
break;
|
||||
case ']':
|
||||
if (!insideQuotes) complete = true;
|
||||
break;
|
||||
case ',':
|
||||
if (!insideQuotes) modeIndex++;
|
||||
default:
|
||||
if (!insideQuotes) break;
|
||||
}
|
||||
if (complete) break;
|
||||
}
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort either the modes or the palettes using quicksort.
|
||||
*/
|
||||
void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) {
|
||||
listBeingSorted = modeNames;
|
||||
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
|
||||
listBeingSorted = nullptr;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
|
||||
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
|
||||
// BUG: configuring this usermod with conflicting pins
|
||||
// will cause it to de-allocate pins it does not own
|
||||
// (at second config)
|
||||
// This is the exact type of bug solved by pinManager
|
||||
// tracking the owner tags....
|
||||
pinA = pinB = pinC = -1;
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
pinMode(pinA, INPUT_PULLUP);
|
||||
pinMode(pinB, INPUT_PULLUP);
|
||||
pinMode(pinC, INPUT_PULLUP);
|
||||
loopTime = millis();
|
||||
|
||||
for (uint8_t s = 0; s < busses.getNumBusses(); s++) {
|
||||
Bus *bus = busses.getBus(s);
|
||||
if (!bus || bus->getLength()==0) break;
|
||||
isRgbw |= bus->isRgbw();
|
||||
}
|
||||
|
||||
currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5;
|
||||
|
||||
if (!initDone) sortModesAndPalettes();
|
||||
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
// This Usermod uses FourLineDisplayUsermod for the best experience.
|
||||
// But it's optional. But you want it.
|
||||
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
|
||||
if (display != nullptr) {
|
||||
display->setMarkLine(1, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
initDone = true;
|
||||
Enc_A = digitalRead(pinA); // Read encoder pins
|
||||
Enc_B = digitalRead(pinB);
|
||||
Enc_A_prev = Enc_A;
|
||||
}
|
||||
|
||||
/*
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
//Serial.println("Connected to WiFi!");
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*
|
||||
* Tips:
|
||||
* 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
|
||||
* Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
|
||||
*
|
||||
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
|
||||
* Instead, use a timer check as shown here.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
unsigned long currentTime = millis(); // get the current elapsed time
|
||||
|
||||
// Initialize effectCurrentIndex and effectPaletteIndex to
|
||||
// current state. We do it here as (at least) effectCurrent
|
||||
// is not yet initialized when setup is called.
|
||||
|
||||
if (!currentEffectAndPaletteInitialized) {
|
||||
findCurrentEffectAndPalette();
|
||||
}
|
||||
|
||||
if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
|
||||
currentEffectAndPaletteInitialized = false;
|
||||
}
|
||||
|
||||
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
|
||||
{
|
||||
loopTime = currentTime; // Updates loopTime
|
||||
|
||||
bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released
|
||||
if (buttonPressed) {
|
||||
if (!buttonPressedBefore) buttonPressedTime = currentTime;
|
||||
buttonPressedBefore = true;
|
||||
if (currentTime-buttonPressedTime > 3000) {
|
||||
if (!buttonLongPressed) displayNetworkInfo(); //long press for network info
|
||||
buttonLongPressed = true;
|
||||
}
|
||||
} else if (!buttonPressed && buttonPressedBefore) {
|
||||
bool doublePress = buttonWaitTime;
|
||||
buttonWaitTime = 0;
|
||||
if (!buttonLongPressed) {
|
||||
if (doublePress) {
|
||||
toggleOnOff();
|
||||
lampUdated();
|
||||
} else {
|
||||
buttonWaitTime = currentTime;
|
||||
}
|
||||
}
|
||||
buttonLongPressed = false;
|
||||
buttonPressedBefore = false;
|
||||
}
|
||||
if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp
|
||||
buttonWaitTime = 0;
|
||||
char newState = select_state + 1;
|
||||
bool changedState = true;
|
||||
if (newState > LAST_UI_STATE || (newState == 8 && presetHigh==0 && presetLow == 0)) newState = 0;
|
||||
if (display != nullptr) {
|
||||
switch (newState) {
|
||||
case 0: changedState = changeState(PSTR("Brightness"), 1, 0, 1); break; //1 = sun
|
||||
case 1: changedState = changeState(PSTR("Speed"), 1, 4, 2); break; //2 = skip forward
|
||||
case 2: changedState = changeState(PSTR("Intensity"), 1, 8, 3); break; //3 = fire
|
||||
case 3: changedState = changeState(PSTR("Color Palette"), 2, 0, 4); break; //4 = custom palette
|
||||
case 4: changedState = changeState(PSTR("Effect"), 3, 0, 5); break; //5 = puzzle piece
|
||||
case 5: changedState = changeState(PSTR("Main Color"), 255, 255, 7); break; //7 = brush
|
||||
case 6: changedState = changeState(PSTR("Saturation"), 255, 255, 8); break; //8 = contrast
|
||||
case 7: changedState = changeState(PSTR("CCT"), 255, 255, 10); break; //10 = star
|
||||
case 8: changedState = changeState(PSTR("Preset"), 255, 255, 11); break; //11 = heart
|
||||
}
|
||||
}
|
||||
if (changedState) select_state = newState;
|
||||
}
|
||||
|
||||
Enc_A = digitalRead(pinA); // Read encoder pins
|
||||
Enc_B = digitalRead(pinB);
|
||||
if ((Enc_A) && (!Enc_A_prev))
|
||||
{ // A has gone from high to low
|
||||
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
|
||||
{ // B is high so clockwise
|
||||
switch(select_state) {
|
||||
case 0: changeBrightness(true); break;
|
||||
case 1: changeEffectSpeed(true); break;
|
||||
case 2: changeEffectIntensity(true); break;
|
||||
case 3: changePalette(true); break;
|
||||
case 4: changeEffect(true); break;
|
||||
case 5: changeHue(true); break;
|
||||
case 6: changeSat(true); break;
|
||||
case 7: changeCCT(true); break;
|
||||
case 8: changePreset(true); break;
|
||||
}
|
||||
}
|
||||
else if (Enc_B == HIGH)
|
||||
{ // B is low so counter-clockwise
|
||||
switch(select_state) {
|
||||
case 0: changeBrightness(false); break;
|
||||
case 1: changeEffectSpeed(false); break;
|
||||
case 2: changeEffectIntensity(false); break;
|
||||
case 3: changePalette(false); break;
|
||||
case 4: changeEffect(false); break;
|
||||
case 5: changeHue(false); break;
|
||||
case 6: changeSat(false); break;
|
||||
case 7: changeCCT(false); break;
|
||||
case 8: changePreset(false); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Enc_A_prev = Enc_A; // Store value of A for next time
|
||||
}
|
||||
}
|
||||
|
||||
void displayNetworkInfo() {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->networkOverlay(PSTR("NETWORK INFO"), 10000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void findCurrentEffectAndPalette() {
|
||||
currentEffectAndPaletteInitialized = true;
|
||||
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
|
||||
if (modes_alpha_indexes[i] == effectCurrent) {
|
||||
effectCurrentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < strip.getPaletteCount(); i++) {
|
||||
if (palettes_alpha_indexes[i] == effectPalette) {
|
||||
effectPaletteIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display != nullptr) {
|
||||
if (display->wakeDisplay()) {
|
||||
// Throw away wake up input
|
||||
display->redraw(true);
|
||||
return false;
|
||||
}
|
||||
display->overlay(stateName, 750, glyph);
|
||||
display->setMarkLine(markedLine, markedCol);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void lampUdated() {
|
||||
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
|
||||
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
|
||||
//setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
|
||||
stateUpdated(CALL_MODE_BUTTON);
|
||||
updateInterfaces(CALL_MODE_BUTTON);
|
||||
}
|
||||
|
||||
void changeBrightness(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->updateBrightness();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void changeEffect(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0);
|
||||
effectCurrent = modes_alpha_indexes[effectCurrentIndex];
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
strip.setMode(i, effectCurrent);
|
||||
}
|
||||
} else {
|
||||
//WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
strip.setMode(strip.getMainSegmentId(), effectCurrent);
|
||||
}
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void changeEffectSpeed(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0);
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.speed = effectSpeed;
|
||||
}
|
||||
} else {
|
||||
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
seg.speed = effectSpeed;
|
||||
}
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->updateSpeed();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void changeEffectIntensity(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0);
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.intensity = effectIntensity;
|
||||
}
|
||||
} else {
|
||||
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
seg.intensity = effectIntensity;
|
||||
}
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->updateIntensity();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void changePalette(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0);
|
||||
effectPalette = palettes_alpha_indexes[effectPaletteIndex];
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.palette = effectPalette;
|
||||
}
|
||||
} else {
|
||||
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
seg.palette = effectPalette;
|
||||
}
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void changeHue(bool increase){
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0);
|
||||
colorHStoRGB(currentHue1*256, currentSat1, col);
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
|
||||
}
|
||||
} else {
|
||||
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
|
||||
}
|
||||
lampUdated();
|
||||
}
|
||||
|
||||
void changeSat(bool increase){
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
|
||||
colorHStoRGB(currentHue1*256, currentSat1, col);
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
|
||||
}
|
||||
} else {
|
||||
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
|
||||
}
|
||||
lampUdated();
|
||||
}
|
||||
|
||||
void changePreset(bool increase) {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
if (presetHigh && presetLow && presetHigh > presetLow) {
|
||||
String apireq = F("win&PL=~");
|
||||
if (!increase) apireq += '-';
|
||||
apireq += F("&P1=");
|
||||
apireq += presetLow;
|
||||
apireq += F("&P2=");
|
||||
apireq += presetHigh;
|
||||
handleSet(nullptr, apireq, false);
|
||||
lampUdated();
|
||||
}
|
||||
}
|
||||
|
||||
void changeCCT(bool increase){
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display && display->wakeDisplay()) {
|
||||
display->redraw(true);
|
||||
// Throw away wake up input
|
||||
return;
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0);
|
||||
// if (applyToAll) {
|
||||
for (byte i=0; i<strip.getMaxSegments(); i++) {
|
||||
WS2812FX::Segment& seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue;
|
||||
seg.setCCT(currentCCT, i);
|
||||
}
|
||||
// } else {
|
||||
// WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
|
||||
// seg.setCCT(currentCCT, strip.getMainSegmentId());
|
||||
// }
|
||||
lampUdated();
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
/*
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
int reading = 20;
|
||||
//this code adds "u":{"Light":[20," lux"]} to the info object
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
JsonArray lightArr = user.createNestedArray("Light"); //name
|
||||
lightArr.add(reading); //value
|
||||
lightArr.add(" lux"); //unit
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
//root["user0"] = userVar0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root) {
|
||||
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_DT_pin)] = pinA;
|
||||
top[FPSTR(_CLK_pin)] = pinB;
|
||||
top[FPSTR(_SW_pin)] = pinC;
|
||||
top[FPSTR(_presetLow)] = presetLow;
|
||||
top[FPSTR(_presetHigh)] = presetHigh;
|
||||
top[FPSTR(_applyToAll)] = applyToAll;
|
||||
DEBUG_PRINTLN(F("Rotary Encoder config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
// we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA;
|
||||
int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB;
|
||||
int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC;
|
||||
|
||||
presetHigh = top[FPSTR(_presetHigh)] | presetHigh;
|
||||
presetLow = top[FPSTR(_presetLow)] | presetLow;
|
||||
presetHigh = MIN(250,MAX(0,presetHigh));
|
||||
presetLow = MIN(250,MAX(0,presetLow));
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
applyToAll = top[FPSTR(_applyToAll)] | applyToAll;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
pinA = newDTpin;
|
||||
pinB = newCLKpin;
|
||||
pinC = newSWpin;
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing parameters from settings page
|
||||
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) {
|
||||
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
|
||||
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
|
||||
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
|
||||
pinA = newDTpin;
|
||||
pinB = newCLKpin;
|
||||
pinC = newSWpin;
|
||||
if (pinA<0 || pinB<0 || pinC<0) {
|
||||
enabled = false;
|
||||
return true;
|
||||
}
|
||||
setup();
|
||||
}
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_applyToAll)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_ROTARY_ENC_UI;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
|
||||
const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled";
|
||||
const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin";
|
||||
const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin";
|
||||
const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";
|
||||
const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high";
|
||||
const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low";
|
||||
const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg";
|
||||
38
usermods/usermod_v2_word_clock/readme.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Word Clock Usermod V2
|
||||
|
||||
This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes.
|
||||
The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
|
||||
There are 2 parameters to chnage the behaviour:
|
||||
|
||||
active: enable/disable usermod
|
||||
diplayItIs: enable/disable display of "Es ist" on the clock.
|
||||
|
||||
### Update for alternatative wiring pattern
|
||||
Based on this fantastic work I added an alternative wiring pattern.
|
||||
For original you have to use a long wire to connect DO - DI from first line to the next line.
|
||||
|
||||
I wired my clock in meander style. So the first LED in second line is in the right.
|
||||
With this problem every second line was inverted and showed the wrong letter.
|
||||
|
||||
I added a switch in usermod called "meander wiring?" to enable/disable alternativ wiring pattern.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Copy and update the example `platformio_override.ini.sample`
|
||||
from the Rotary Encoder UI usermode folder to the root directory of your particular build.
|
||||
This file should be placed in the same directory as `platformio.ini`.
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_WORDCLOCK` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
No special requirements.
|
||||
|
||||
## Change Log
|
||||
|
||||
2022/08/18 added meander wiring pattern.
|
||||
|
||||
2022/03/30 initial commit
|
||||
478
usermods/usermod_v2_word_clock/usermod_v2_word_clock.h
Normal file
@@ -0,0 +1,478 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
/*
|
||||
* Usermods allow you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes.
|
||||
* The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
|
||||
* There are 2 parameters to chnage the behaviour:
|
||||
*
|
||||
* active: enable/disable usermod
|
||||
* diplayItIs: enable/disable display of "Es ist" on the clock.
|
||||
*/
|
||||
|
||||
class WordClockUsermod : public Usermod
|
||||
{
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
int lastTimeMinutes = -1;
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
bool usermodActive = false;
|
||||
bool displayItIs = false;
|
||||
bool meander = false;
|
||||
|
||||
// defines for mask sizes
|
||||
#define maskSizeLeds 114
|
||||
#define maskSizeMinutes 12
|
||||
#define maskSizeMinutesMea 12
|
||||
#define maskSizeHours 6
|
||||
#define maskSizeHoursMea 6
|
||||
#define maskSizeItIs 5
|
||||
#define maskSizeMinuteDots 4
|
||||
|
||||
// "minute" masks
|
||||
// Normal wiring
|
||||
const int maskMinutes[12][maskSizeMinutes] =
|
||||
{
|
||||
{107, 108, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00
|
||||
{ 7, 8, 9, 10, 40, 41, 42, 43, -1, -1, -1, -1}, // :05 fünf nach
|
||||
{ 11, 12, 13, 14, 40, 41, 42, 43, -1, -1, -1, -1}, // :10 zehn nach
|
||||
{ 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel
|
||||
{ 15, 16, 17, 18, 19, 20, 21, 40, 41, 42, 43, -1}, // :20 zwanzig nach
|
||||
{ 7, 8, 9, 10, 33, 34, 35, 44, 45, 46, 47, -1}, // :25 fünf vor halb
|
||||
{ 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb
|
||||
{ 7, 8, 9, 10, 40, 41, 42, 43, 44, 45, 46, 47}, // :35 fünf nach halb
|
||||
{ 15, 16, 17, 18, 19, 20, 21, 33, 34, 35, -1, -1}, // :40 zwanzig vor
|
||||
{ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel
|
||||
{ 11, 12, 13, 14, 33, 34, 35, -1, -1, -1, -1, -1}, // :50 zehn vor
|
||||
{ 7, 8, 9, 10, 33, 34, 35, -1, -1, -1, -1, -1} // :55 fünf vor
|
||||
};
|
||||
|
||||
// Meander wiring
|
||||
const int maskMinutesMea[12][maskSizeMinutesMea] =
|
||||
{
|
||||
{ 99, 100, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00
|
||||
{ 7, 8, 9, 10, 33, 34, 35, 36, -1, -1, -1, -1}, // :05 fünf nach
|
||||
{ 18, 19, 20, 21, 33, 34, 35, 36, -1, -1, -1, -1}, // :10 zehn nach
|
||||
{ 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel
|
||||
{ 11, 12, 13, 14, 15, 16, 17, 33, 34, 35, 36, -1}, // :20 zwanzig nach
|
||||
{ 7, 8, 9, 10, 41, 42, 43, 44, 45, 46, 47, -1}, // :25 fünf vor halb
|
||||
{ 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb
|
||||
{ 7, 8, 9, 10, 33, 34, 35, 36, 44, 45, 46, 47}, // :35 fünf nach halb
|
||||
{ 11, 12, 13, 14, 15, 16, 17, 41, 42, 43, -1, -1}, // :40 zwanzig vor
|
||||
{ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel
|
||||
{ 18, 19, 20, 21, 41, 42, 43, -1, -1, -1, -1, -1}, // :50 zehn vor
|
||||
{ 7, 8, 9, 10, 41, 42, 43, -1, -1, -1, -1, -1} // :55 fünf vor
|
||||
};
|
||||
|
||||
// hour masks
|
||||
// Normal wiring
|
||||
const int maskHours[13][maskSizeHours] =
|
||||
{
|
||||
{ 55, 56, 57, -1, -1, -1}, // 01: ein
|
||||
{ 55, 56, 57, 58, -1, -1}, // 01: eins
|
||||
{ 62, 63, 64, 65, -1, -1}, // 02: zwei
|
||||
{ 66, 67, 68, 69, -1, -1}, // 03: drei
|
||||
{ 73, 74, 75, 76, -1, -1}, // 04: vier
|
||||
{ 51, 52, 53, 54, -1, -1}, // 05: fünf
|
||||
{ 77, 78, 79, 80, 81, -1}, // 06: sechs
|
||||
{ 88, 89, 90, 91, 92, 93}, // 07: sieben
|
||||
{ 84, 85, 86, 87, -1, -1}, // 08: acht
|
||||
{102, 103, 104, 105, -1, -1}, // 09: neun
|
||||
{ 99, 100, 101, 102, -1, -1}, // 10: zehn
|
||||
{ 49, 50, 51, -1, -1, -1}, // 11: elf
|
||||
{ 94, 95, 96, 97, 98, -1} // 12: zwölf and 00: null
|
||||
};
|
||||
// Meander wiring
|
||||
const int maskHoursMea[13][maskSizeHoursMea] =
|
||||
{
|
||||
{ 63, 64, 65, -1, -1, -1}, // 01: ein
|
||||
{ 62, 63, 64, 65, -1, -1}, // 01: eins
|
||||
{ 55, 56, 57, 58, -1, -1}, // 02: zwei
|
||||
{ 66, 67, 68, 69, -1, -1}, // 03: drei
|
||||
{ 73, 74, 75, 76, -1, -1}, // 04: vier
|
||||
{ 51, 52, 53, 54, -1, -1}, // 05: fünf
|
||||
{ 83, 84, 85, 86, 87, -1}, // 06: sechs
|
||||
{ 88, 89, 90, 91, 92, 93}, // 07: sieben
|
||||
{ 77, 78, 79, 80, -1, -1}, // 08: acht
|
||||
{103, 104, 105, 106, -1, -1}, // 09: neun
|
||||
{106, 107, 108, 109, -1, -1}, // 10: zehn
|
||||
{ 49, 50, 51, -1, -1, -1}, // 11: elf
|
||||
{ 94, 95, 96, 97, 98, -1} // 12: zwölf and 00: null
|
||||
};
|
||||
|
||||
// mask "it is"
|
||||
const int maskItIs[maskSizeItIs] = {0, 1, 3, 4, 5};
|
||||
|
||||
// mask minute dots
|
||||
const int maskMinuteDots[maskSizeMinuteDots] = {110, 111, 112, 113};
|
||||
|
||||
// overall mask to define which LEDs are on
|
||||
int maskLedsOn[maskSizeLeds] =
|
||||
{
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0
|
||||
};
|
||||
|
||||
// update led mask
|
||||
void updateLedMask(const int wordMask[], int arraySize)
|
||||
{
|
||||
// loop over array
|
||||
for (int x=0; x < arraySize; x++)
|
||||
{
|
||||
// check if mask has a valid LED number
|
||||
if (wordMask[x] >= 0 && wordMask[x] < maskSizeLeds)
|
||||
{
|
||||
// turn LED on
|
||||
maskLedsOn[wordMask[x]] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set hours
|
||||
void setHours(int hours, bool fullClock)
|
||||
{
|
||||
int index = hours;
|
||||
|
||||
// handle 00:xx as 12:xx
|
||||
if (hours == 0)
|
||||
{
|
||||
index = 12;
|
||||
}
|
||||
|
||||
// check if we get an overrun of 12 o´clock
|
||||
if (hours == 13)
|
||||
{
|
||||
index = 1;
|
||||
}
|
||||
|
||||
// special handling for "ein Uhr" instead of "eins Uhr"
|
||||
if (hours == 1 && fullClock == true)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// update led mask
|
||||
if (meander)
|
||||
{
|
||||
updateLedMask(maskHoursMea[index], maskSizeHoursMea);
|
||||
} else {
|
||||
updateLedMask(maskHours[index], maskSizeHours);
|
||||
}
|
||||
}
|
||||
|
||||
// set minutes
|
||||
void setMinutes(int index)
|
||||
{
|
||||
// update led mask
|
||||
if (meander)
|
||||
{
|
||||
updateLedMask(maskMinutesMea[index], maskSizeMinutesMea);
|
||||
} else {
|
||||
updateLedMask(maskMinutes[index], maskSizeMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
// set minutes dot
|
||||
void setSingleMinuteDots(int minutes)
|
||||
{
|
||||
// modulo to get minute dots
|
||||
int minutesDotCount = minutes % 5;
|
||||
|
||||
// check if minute dots are active
|
||||
if (minutesDotCount > 0)
|
||||
{
|
||||
// activate all minute dots until number is reached
|
||||
for (int i = 0; i < minutesDotCount; i++)
|
||||
{
|
||||
// activate LED
|
||||
maskLedsOn[maskMinuteDots[i]] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the display
|
||||
void updateDisplay(uint8_t hours, uint8_t minutes)
|
||||
{
|
||||
// disable complete matrix at the bigging
|
||||
for (int x = 0; x < maskSizeLeds; x++)
|
||||
{
|
||||
maskLedsOn[x] = 0;
|
||||
}
|
||||
|
||||
// display it is/es ist if activated
|
||||
if (displayItIs)
|
||||
{
|
||||
updateLedMask(maskItIs, maskSizeItIs);
|
||||
}
|
||||
|
||||
// set single minute dots
|
||||
setSingleMinuteDots(minutes);
|
||||
|
||||
// switch minutes
|
||||
switch (minutes / 5)
|
||||
{
|
||||
case 0:
|
||||
// full hour
|
||||
setMinutes(0);
|
||||
setHours(hours, true);
|
||||
break;
|
||||
case 1:
|
||||
// 5 nach
|
||||
setMinutes(1);
|
||||
setHours(hours, false);
|
||||
break;
|
||||
case 2:
|
||||
// 10 nach
|
||||
setMinutes(2);
|
||||
setHours(hours, false);
|
||||
break;
|
||||
case 3:
|
||||
// viertel
|
||||
setMinutes(3);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 4:
|
||||
// 20 nach
|
||||
setMinutes(4);
|
||||
setHours(hours, false);
|
||||
break;
|
||||
case 5:
|
||||
// 5 vor halb
|
||||
setMinutes(5);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 6:
|
||||
// halb
|
||||
setMinutes(6);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 7:
|
||||
// 5 nach halb
|
||||
setMinutes(7);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 8:
|
||||
// 20 vor
|
||||
setMinutes(8);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 9:
|
||||
// viertel vor
|
||||
setMinutes(9);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 10:
|
||||
// 10 vor
|
||||
setMinutes(10);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
case 11:
|
||||
// 5 vor
|
||||
setMinutes(11);
|
||||
setHours(hours + 1, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*
|
||||
* Tips:
|
||||
* 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection.
|
||||
* Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker.
|
||||
*
|
||||
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
|
||||
* Instead, use a timer check as shown here.
|
||||
*/
|
||||
void loop() {
|
||||
|
||||
// do it every 5 seconds
|
||||
if (millis() - lastTime > 5000)
|
||||
{
|
||||
// check the time
|
||||
int minutes = minute(localTime);
|
||||
|
||||
// check if we already updated this minute
|
||||
if (lastTimeMinutes != minutes)
|
||||
{
|
||||
// update the display with new time
|
||||
updateDisplay(hourFormat12(localTime), minute(localTime));
|
||||
|
||||
// remember last update time
|
||||
lastTimeMinutes = minutes;
|
||||
}
|
||||
|
||||
// remember last update
|
||||
lastTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
/*
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* If you want to force saving the current state, use serializeConfig() in your loop().
|
||||
*
|
||||
* CAUTION: serializeConfig() will initiate a filesystem write operation.
|
||||
* It might cause the LEDs to stutter and will cause flash wear if called too often.
|
||||
* Use it sparingly and always in the loop, never in network callbacks!
|
||||
*
|
||||
* addToConfig() will make your settings editable through the Usermod Settings page automatically.
|
||||
*
|
||||
* Usermod Settings Overview:
|
||||
* - Numeric values are treated as floats in the browser.
|
||||
* - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float
|
||||
* before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and
|
||||
* doubles are not supported, numbers will be rounded to the nearest float value when being parsed.
|
||||
* The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38.
|
||||
* - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a
|
||||
* C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod.
|
||||
* Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type
|
||||
* used in the Usermod when reading the value from ArduinoJson.
|
||||
* - Pin values can be treated differently from an integer value by using the key name "pin"
|
||||
* - "pin" can contain a single or array of integer values
|
||||
* - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins
|
||||
* - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin)
|
||||
* - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used
|
||||
*
|
||||
* See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings
|
||||
*
|
||||
* If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work.
|
||||
* You will have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
||||
* See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED
|
||||
*
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("WordClockUsermod");
|
||||
top["active"] = usermodActive;
|
||||
top["displayItIs"] = displayItIs;
|
||||
top["Meander wiring?"] = meander;
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
|
||||
* This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page)
|
||||
*
|
||||
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
|
||||
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
|
||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||
*
|
||||
* Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
|
||||
*
|
||||
* getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present
|
||||
* The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them
|
||||
*
|
||||
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
||||
*/
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
JsonObject top = root["WordClockUsermod"];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top["active"], usermodActive);
|
||||
configComplete &= getJsonValue(top["displayItIs"], displayItIs);
|
||||
configComplete &= getJsonValue(top["Meander wiring?"], meander);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
// check if usermod is active
|
||||
if (usermodActive == true)
|
||||
{
|
||||
// loop over all leds
|
||||
for (int x = 0; x < maskSizeLeds; x++)
|
||||
{
|
||||
// check mask
|
||||
if (maskLedsOn[x] == 0)
|
||||
{
|
||||
// set pixel off
|
||||
strip.setPixelColor(x, RGBW32(0,0,0,0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_WORDCLOCK;
|
||||
}
|
||||
|
||||
//More methods can be added in the future, this example will then be extended.
|
||||
//Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
|
||||
};
|
||||
35
usermods/wizlights/readme.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Controlling Wiz lights
|
||||
|
||||
This usermod allows the control of [WiZ](https://www.wizconnected.com/en/consumer/) lights that are in the same network as the WLED controller.
|
||||
|
||||
The mod takes the colors from the first few pixels and sends them to the lights.
|
||||
|
||||
## Configuration
|
||||
|
||||
- Interval (ms)
|
||||
- How frequently to update the WiZ lights, in milliseconds.
|
||||
- Setting too low may causse ESP to become unresponsive.
|
||||
- Send Delay (ms)
|
||||
- An optional millisecond delay after updating each WiZ light.
|
||||
- Can help smooth out effects when using a larger number of WiZ lights
|
||||
- Use Enhanced White
|
||||
- Enables using the WiZ lights onboard white LEDs instead of sending maximum RGB values.
|
||||
- Tunable with warm and cool LEDs as supported by WiZ bulbs
|
||||
- Note: Only sent when max RGB value is set, need to have automatic brightness limiter disabled
|
||||
- ToDo: Have better logic for white value mixing to better take advantage of the lights capabilities
|
||||
- Always Force Update
|
||||
- Can be enabled to always send update message to light, even when color matches what was previously sent.
|
||||
- Force update every x minutes
|
||||
- Configuration option to allow adjusting the default force update timeout of 5 minutes.
|
||||
- Setting to 0 has the same impact as enabling Always Force Update
|
||||
-
|
||||
Then enter the IPs for the lights to be controlled, in order. There is currently a limit of 15 devices that can be controled, but that number
|
||||
can be easily changed by updating _MAX_WIZ_LIGHTS_.
|
||||
|
||||
|
||||
|
||||
|
||||
## Related project
|
||||
|
||||
If you use these lights and python, make sure to check out the [pywizlight](https://github.com/sbidy/pywizlight) project. I learned how to
|
||||
format the messages to control the lights from that project.
|
||||