Compare commits

..

44 Commits

Author SHA1 Message Date
cschwinne
4af1f62aab Revert testing PIO changes 2021-11-17 01:19:04 +01:00
cschwinne
bc403440ba 0.13.0-b5 2021-11-17 01:18:19 +01:00
Christian Schwinne
38d8dfe5ab Improv support (#2334)
* Working Improv device identification

* Improv functional

* Cast fix

* Minor fix for two back-to-back Improv packets

* Improv checksum update and logic simplification

* Improved improv failed connection behavior
2021-11-16 23:20:26 +01:00
Jeff Cooper
6df64d0d31 Fix a bug which prevents DMX channel mappings from loading correctly. (#1525) (#2328) 2021-11-10 00:29:04 +01:00
cschwinne
83753a5f81 Fixed no gamma correction for JSON individual LED control 2021-11-09 17:50:29 +01:00
Blaz Kristan
3161f5fa47 Fix for network 'pin' conflicts. 2021-11-09 17:46:05 +01:00
Christian Schwinne
5784092c1b Fix settings JS buffer too small (#2323) 2021-11-09 09:56:02 +01:00
Blaž Kristan
d6ad089c60 Merge pull request #2324 from yoeywire/DMXdefine
Corrected WLED_USE_DMX to WLED_ENABLE_DMX
2021-11-09 07:57:41 +01:00
yoeywire
446b4b084c Changed WLED_USE_DMX to WLED_ENABLE_DMX 2021-11-08 17:05:47 +01:00
Shaun Eccles-Smith
adeb9bccb1 Use New Issue Forms for Bug Reports (#2312)
* Convert to Github Issue Forms

* Remove pre-filled title

* Change bug report from textarea to input
2021-11-04 12:23:53 +01:00
cschwinne
b44ffffed8 Fixed DDP override 2021-11-03 16:14:01 +01:00
Christian Schwinne
2bdaf53ecf Merge pull request #2311 from underritoSR/master
Add MX-CST/CDT Time Zone
2021-11-03 09:14:22 +01:00
Rod Minor
46e7db6d94 Add MX-CST/CDT Time Zone
Add the Mexico City CST/CDT timezone.
The start and finish date for DLS differs with the US-CST/CDT rules.
2021-11-02 21:26:11 -06:00
Christian Schwinne
7e1920dc4b Remove ledCount (#2300)
Bus initialization on reading from eeprom
2021-10-31 11:57:41 +01:00
Blaž Kristan
a93f05c047 Multirelay button support. (#2284)
* Multirelay button support.
Added button hook for usermods.

* Added MultiRelay relay states to JSON state object

* Move button timings to constants

No delay waiting for double press on button 0 if no macro set

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-10-31 11:57:03 +01:00
Christian Schwinne
00238247cd JSON in/decrementing (#2258)
* Initial JSON in/decrementing

* Segment brightness in/decrement

* Update json-increment (#2290)

* Add Basic Overlay support to Usermods.

* Add seven segment overlay usermod

* Add seven_seg debug build

* Add scrolling message to seven seg um

* Fixed red color on IP address

* bh1750

* Add msg scroll. Add MQTT and Config support

* Add readme

* Restore platformio.inii

* Edit comments

* Add strip off refresh option in LED settings. (#2259)

* Add strip off refresh option in LED settings.
New strip initialization logic.
Minor code clen-up.

* Dev code removal.

* Missing ethernet include

* Renamed mainseg to selseg

* Fix for preset cycling bounds.

* "Preset 0" bugfix.

* Auto segments only if segments were not modified

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>

* Exclude virtual busses from current calculation (#2262)

* Refactor string usage

* 0.13.0-b4

* Fix MQTT Null publish

* Additional Flash string concat

* Add AKST/AKDT

* UM RGB-Rotary-Encoder: Properly used PinOwner

* Cycling bugfix.

Co-authored-by: Gregory Schmidt <gregory.b.schmidt@hotmail.com>
Co-authored-by: Blaž Kristan <blaz@kristan-sp.si>
Co-authored-by: Caleb Mah <calebmah@gmail.com>
Co-authored-by: ezcGman <ich@andy-hofmann.com>

* Working JSON preset cycle

* Fix some Codacy style issues

Co-authored-by: Gregory Schmidt <gregory.b.schmidt@hotmail.com>
Co-authored-by: Blaž Kristan <blaz@kristan-sp.si>
Co-authored-by: Caleb Mah <calebmah@gmail.com>
Co-authored-by: ezcGman <ich@andy-hofmann.com>
2021-10-30 14:42:17 +02:00
Andy Hofmann
b33e28835d New Usermod: QuinLED-An-Penta (#2296)
* UM QuinLED-An-Penta: First version

* UM QuinLED-An-Penta: Made OLED seconds a setting; small improvements

* UM QuinLED-An-Penta: Fixed unique ID

* Merge branch 'master' of https://github.com/Aircoookie/WLED

* UM QuinLED-An-Penta: Fixed config loading

* UM QuinLED-An-Penta: Replaced ledcRead() with calculating the percentage

* UM QuinLED-An-Penta: Fixed temp sensor readings

* UM QuinLED-An-Penta: Removing OLED bus clk setting

* UM QuinLED-An-Penta: ETH support, lots of OLED improvements
2021-10-30 11:38:09 +02:00
Blaz Kristan
0acca2e313 Cycling bugfix. 2021-10-22 23:24:41 +02:00
Blaž Kristan
00f1b483eb Merge pull request #2279 from ezcGman/um-rgb-rotary-encoder
UM RGB Rotary Encoder: Now properly using the PinOwner class and unique ID
2021-10-17 14:16:18 +02:00
ezcGman
c3d48acb4c UM RGB-Rotary-Encoder: Properly used PinOwner 2021-10-17 01:24:23 +02:00
Blaž Kristan
392bda7d8c Merge pull request #2261 from AK5nowman/overlayum
Basic Usermod Overlay support & Seven Segment UM
2021-10-13 06:14:32 +02:00
Blaž Kristan
10cfcdab8c Merge pull request #2264 from AK5nowman/Alaskatime
Add AKST/AKDT Time Zone
2021-10-11 07:37:52 +02:00
Gregory Schmidt
3f71d3b250 Add AKST/AKDT 2021-10-10 19:09:48 -08:00
Gregory Schmidt
1b50fbab22 Additional Flash string concat 2021-10-10 17:24:36 -08:00
Gregory Schmidt
303fc65a6a Merge branch 'master' of https://github.com/Aircoookie/WLED into overlayum 2021-10-10 17:06:12 -08:00
Gregory Schmidt
445b6ee13f Fix MQTT Null publish 2021-10-10 17:05:55 -08:00
cschwinne
8afaac1e30 0.13.0-b4 2021-10-11 02:42:58 +02:00
Gregory Schmidt
0327f9428e Merge branch 'master' of https://github.com/Aircoookie/WLED into overlayum 2021-10-10 16:27:47 -08:00
Gregory Schmidt
a5de66bbd5 Merge branch 'overlayum' of https://github.com/AK5nowman/WLED into overlayum 2021-10-10 16:26:42 -08:00
Gregory Schmidt
d47157eec3 Refactor string usage 2021-10-10 16:26:14 -08:00
Christian Schwinne
f4b47ed399 Exclude virtual busses from current calculation (#2262) 2021-10-11 02:19:53 +02:00
Blaž Kristan
8b2145bd88 Add strip off refresh option in LED settings. (#2259)
* Add strip off refresh option in LED settings.
New strip initialization logic.
Minor code clen-up.

* Dev code removal.

* Missing ethernet include

* Renamed mainseg to selseg

* Fix for preset cycling bounds.

* "Preset 0" bugfix.

* Auto segments only if segments were not modified

Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
2021-10-11 02:19:33 +02:00
Christian Schwinne
de454e8b78 Edit comments 2021-10-11 01:29:13 +02:00
Gregory Schmidt
6cd770b4c7 Restore platformio.inii 2021-10-09 11:29:41 -08:00
Gregory Schmidt
355525c248 Add readme 2021-10-09 11:14:52 -08:00
Gregory Schmidt
47d4e7381f Merge 'master' of Aircoookie/WLED into overlayum 2021-10-08 00:00:09 -08:00
Gregory Schmidt
5dac6690d7 Add msg scroll. Add MQTT and Config support 2021-10-07 23:56:57 -08:00
Christian Schwinne
b89f7180db Merge pull request #2257 from calebmah/bh1750-usermod
Add BH1750 usermod
2021-10-07 23:52:48 +02:00
Caleb Mah
2ebb837a15 bh1750 2021-10-08 02:11:39 +08:00
Blaž Kristan
849aa64678 Fixed red color on IP address 2021-10-07 14:11:47 +02:00
Gregory Schmidt
d00b4335b5 Add scrolling message to seven seg um 2021-10-01 21:34:20 -08:00
Gregory Schmidt
3ac772badc Add seven_seg debug build 2021-09-29 20:59:02 -08:00
Gregory Schmidt
22fc58d93b Add seven segment overlay usermod 2021-09-29 20:01:26 -08:00
Gregory Schmidt
ccd3152b24 Add Basic Overlay support to Usermods. 2021-09-29 19:23:32 -08:00
53 changed files with 3488 additions and 1380 deletions

View File

@@ -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
View 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.0-b4 (build 2110110)"
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

View File

@@ -2,6 +2,27 @@
### Builds after release 0.12.0
#### Build 2111160
- 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)

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b3",
"version": "0.13.0-b5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.13.0-b3",
"version": "0.13.0-b5",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {

View File

@@ -207,6 +207,8 @@ build_flags = -g
-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
lib_deps =
${env.lib_deps}
makuna/NeoPixelBus @ 2.6.7
@@ -292,6 +294,7 @@ platform = espressif32@2.0
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32_eth]
board = esp32-poe
@@ -300,6 +303,7 @@ 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
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
[env:esp32s2_saola]
board = esp32dev
@@ -406,6 +410,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 +418,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
@@ -498,3 +504,4 @@ monitor_filters = esp32_exception_decoder
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ ^2.3.70
board_build.partitions = ${esp32.default_partitions}

View File

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

View 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

View 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

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

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

View File

@@ -16,6 +16,12 @@ 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`
## 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

View File

@@ -6,6 +6,8 @@
#define MULTI_RELAY_MAX_RELAYS 4
#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 +25,7 @@ typedef struct relay_t {
bool state;
bool external;
uint16_t delay;
int8_t button;
} Relay;
@@ -35,7 +38,7 @@ 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)
@@ -49,6 +52,7 @@ class MultiRelay : public Usermod {
static const char _delay_str[];
static const char _activeHigh[];
static const char _external[];
static const char _button[];
void publishMqtt(const char* state, int relay) {
@@ -170,6 +174,7 @@ class MultiRelay : public Usermod {
_relay[i].active = false;
_relay[i].state = false;
_relay[i].external = false;
_relay[i].button = -1;
}
}
/**
@@ -261,11 +266,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 +287,110 @@ 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 (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) {
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
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);
} else {
buttonWaitTime[b] = now;
}
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
// if 450ms elapsed since last press/release it is a short press
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
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 +402,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\">&#xe08f;</i></button>");
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
infoArr.add(_relay[i].state ? "on" : "off");
}
}
}
@@ -317,15 +429,46 @@ class MultiRelay : public Usermod {
* 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) {
//}
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
}
/**
* 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) {
//}
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>());
}
}
}
}
/**
* provide the changeable values
@@ -341,6 +484,7 @@ class MultiRelay : public Usermod {
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;
}
DEBUG_PRINTLN(F("MultiRelay config saved."));
}
@@ -370,6 +514,7 @@ class MultiRelay : public Usermod {
_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;
@@ -394,7 +539,7 @@ 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);
switchRelay(i, offMode);
}
} else {
_relay[i].pin = -1;
@@ -404,7 +549,7 @@ class MultiRelay : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded."));
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("relay-0")]["pin"].isNull();
return !top[F("relay-0")][FPSTR(_button)].isNull();
}
/**
@@ -424,3 +569,4 @@ 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";

View File

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

View 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 anPentaPins[5] = {14, 13, 12, 4, 2};
int8_t oledSpiClk = 15;
int8_t oledSpiData = 16;
int8_t oledSpiCs = 0;
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(anPentaPins[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 (anPentaPins[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,
};

View File

@@ -0,0 +1,69 @@
# QuinLED-An-Penta
The (un)official usermod to get the best out of the 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 and add the buildflag `-D QUINLED_AN_PENTA`.
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**!
### 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 it's 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: Enabled the optional SPI driven OLED that can be mounted to the 7-pin female header
* 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: Enabled the onboard SHT30 temperature and humidity sensor
* Possible values: Enabled/Disabled
* Default: Disabled
## Change log
2021-10
* First implementation.

View File

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

View 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

View File

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

View File

@@ -619,7 +619,7 @@ class WS2812FX {
}
void
finalizeInit(uint16_t countPixels),
finalizeInit(),
service(void),
blur(uint8_t),
fill(uint32_t),
@@ -636,7 +636,8 @@ class WS2812FX {
trigger(void),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0),
resetSegments(),
populateDefaultSegments(),
makeAutoSegments(),
fixInvalidSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void),
@@ -650,6 +651,7 @@ class WS2812FX {
gammaCorrectCol = true,
applyToAllSelected = true,
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p),
checkSegmentAlignment(void),
// return true if the strip is being sent pixel updates
isUpdating(void);
@@ -680,6 +682,8 @@ class WS2812FX {
ablMilliampsMax,
currentMilliamps,
triwave16(uint16_t),
getLengthTotal(void),
getLengthPhysical(void),
getFps();
uint32_t
@@ -839,9 +843,6 @@ class WS2812FX {
uint16_t _cumulativeFps = 2;
void load_gradient_palette(uint8_t);
void handle_palette(void);
bool
_triggered;
@@ -875,7 +876,10 @@ class WS2812FX {
void
blendPixelColor(uint16_t n, uint32_t color, uint8_t blend),
startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot);
startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot),
estimateCurrentAndLimitBri(void),
load_gradient_palette(uint8_t),
handle_palette(void);
uint16_t* customMappingTable = nullptr;
uint16_t customMappingSize = 0;

View File

@@ -65,25 +65,22 @@
#endif
//do not call this method from system context (network callback)
void WS2812FX::finalizeInit(uint16_t countPixels)
void WS2812FX::finalizeInit(void)
{
RESET_RUNTIME;
_length = countPixels;
isRgbw = isOffRefreshRequred = false;
//if busses failed to load, add default (FS issue...)
//if busses failed to load, add default (fresh install, FS issue, ...)
if (busses.getNumBusses() == 0) {
const uint8_t defDataPins[] = {DATA_PINS};
const uint16_t defCounts[] = {PIXEL_COUNTS};
const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0]));
const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
uint16_t prevLen = 0;
for (uint8_t i = 0; i < defNumBusses; i++) {
for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES; i++) {
uint8_t defPin[] = {defDataPins[i]};
uint16_t start = prevLen;
uint16_t count = _length;
if (defNumBusses > 1 && defNumCounts) {
count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
}
uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
prevLen += count;
BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, COL_ORDER_GRB);
busses.add(defCfg);
@@ -92,60 +89,29 @@ void WS2812FX::finalizeInit(uint16_t countPixels)
deserializeMap();
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
setBrightness(_brightness);
//TODO make sure segments are only refreshed when bus config actually changed (new settings page)
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
if (autoSegments) { //make one segment per bus
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
//check for overlap with previous segments
for (uint8_t j = 0; j < s; j++) {
if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
//segments overlap, merge
segStarts[j] = min(segStarts[s],segStarts[j]);
segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
s--;
}
}
s++;
}
_length = 0;
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
Bus *bus = busses.getBus(i);
if (bus == nullptr) continue;
if (bus->getStart() + bus->getLength() > MAX_LEDS) break;
//RGBW mode is enabled if at least one of the strips is RGBW
isRgbw |= bus->isRgbw();
//refresh is required to remain off if at least one of the strips requires the refresh.
isOffRefreshRequred |= bus->isOffRefreshRequired();
uint16_t busEnd = bus->getStart() + bus->getLength();
if (busEnd > _length) _length = busEnd;
#ifdef ESP8266
if ((!IS_DIGITAL(b->getType()) || IS_2PIN(b->getType()))) continue;
if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue;
uint8_t pins[5];
b->getPins(pins);
BusDigital* bd = static_cast<BusDigital*>(b);
if (!bus->getPins(pins)) continue;
BusDigital* bd = static_cast<BusDigital*>(bus);
if (pins[0] == 3) bd->reinit();
#endif
}
if (autoSegments) {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
setSegment(i, segStarts[i], segStops[i]);
}
} else {
//expand the main seg to the entire length, but only if there are no other segments
uint8_t mainSeg = getMainSegmentId();
if (getActiveSegmentsNum() < 2) {
setSegment(mainSeg, 0, _length);
} else {
//there are multiple segments, leave them, but prune length to total
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
}
}
}
//segments are created in makeAutoSegments();
setBrightness(_brightness);
}
void WS2812FX::service() {
@@ -292,12 +258,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA)
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
void WS2812FX::show(void) {
// avoid race condition, caputre _callback value
show_callback callback = _callback;
if (callback) callback();
void WS2812FX::estimateCurrentAndLimitBri() {
//power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
@@ -310,65 +271,72 @@ void WS2812FX::show(void) {
actualMilliampsPerLed = 12; // from testing an actual strip
}
if (ablMilliampsMax > 149 && actualMilliampsPerLed > 0) //0 mA per LED and too low numbers turn off calculation
{
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
if (powerBudget > puPerMilliamp * _length) //each LED uses about 1mA in standby, exclude that from power budget
{
powerBudget -= puPerMilliamp * _length;
} else
{
powerBudget = 0;
}
uint32_t powerSum = 0;
for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED
{
uint32_t c = busses.getPixelColor(i);
byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
if(useWackyWS2815PowerModel)
{
// ignore white component on WS2815 power calculation
powerSum += (MAX(MAX(r,g),b)) * 3;
}
else
{
powerSum += (r + g + b + w);
}
}
if (isRgbw) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
{
powerSum *= 3;
powerSum = powerSum >> 2; //same as /= 4
}
uint32_t powerSum0 = powerSum;
powerSum *= _brightness;
if (powerSum > powerBudget) //scale brightness down to stay in current limit
{
float scale = (float)powerBudget / (float)powerSum;
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
busses.setBrightness(newBri);
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else
{
currentMilliamps = powerSum / puPerMilliamp;
busses.setBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += _length; //add standby power back to estimate
} else {
if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
currentMilliamps = 0;
busses.setBrightness(_brightness);
return;
}
uint16_t pLen = getLengthPhysical();
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget -= puPerMilliamp * pLen;
} else {
powerBudget = 0;
}
uint32_t powerSum = 0;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
uint16_t len = bus->getLength();
uint32_t busPowerSum = 0;
for (uint16_t i = 0; i < len; i++) { //sum up the usage of each LED
uint32_t c = bus->getPixelColor(i);
byte r = c >> 16, g = c >> 8, b = c, w = c >> 24;
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (MAX(MAX(r,g),b)) * 3;
} else {
busPowerSum += (r + g + b + w);
}
}
if (bus->isRgbw()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum *= 3;
busPowerSum = busPowerSum >> 2; //same as /= 4
}
powerSum += busPowerSum;
}
uint32_t powerSum0 = powerSum;
powerSum *= _brightness;
if (powerSum > powerBudget) //scale brightness down to stay in current limit
{
float scale = (float)powerBudget / (float)powerSum;
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
busses.setBrightness(newBri); //to keep brightness uniform, sets virtual busses too
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else {
currentMilliamps = powerSum / puPerMilliamp;
busses.setBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += pLen; //add standby power back to estimate
}
void WS2812FX::show(void) {
// avoid race condition, caputre _callback value
show_callback callback = _callback;
if (callback) callback();
estimateCurrentAndLimitBri();
// some buses send asynchronously and this method will return before
// all of the data has been sent.
@@ -586,6 +554,20 @@ uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
uint16_t WS2812FX::getLengthTotal(void) {
return _length;
}
uint16_t WS2812FX::getLengthPhysical(void) {
uint16_t len = 0;
for (uint8_t b = 0; b < busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
len += bus->getLength();
}
return len;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
@@ -654,23 +636,66 @@ void WS2812FX::resetSegments() {
_segment_runtimes[0].reset();
}
void WS2812FX::populateDefaultSegments() {
uint16_t length = 0;
for (uint8_t i=0; i<busses.getNumBusses(); i++) {
Bus *bus = busses.getBus(i);
if (bus == nullptr) continue;
_segments[i].start = bus->getStart();
length += bus->getLength();
_segments[i].stop = _segments[i].start + bus->getLength();
_segments[i].mode = DEFAULT_MODE;
_segments[i].colors[0] = DEFAULT_COLOR;
_segments[i].speed = DEFAULT_SPEED;
_segments[i].intensity = DEFAULT_INTENSITY;
_segments[i].grouping = 1;
_segments[i].setOption(SEG_OPTION_SELECTED, 1);
_segments[i].setOption(SEG_OPTION_ON, 1);
_segments[i].opacity = 255;
void WS2812FX::makeAutoSegments() {
if (autoSegments) { //make one segment per bus
uint16_t segStarts[MAX_NUM_SEGMENTS] = {0};
uint16_t segStops [MAX_NUM_SEGMENTS] = {0};
uint8_t s = 0;
for (uint8_t i = 0; i < busses.getNumBusses(); i++) {
Bus* b = busses.getBus(i);
segStarts[s] = b->getStart();
segStops[s] = segStarts[s] + b->getLength();
//check for overlap with previous segments
for (uint8_t j = 0; j < s; j++) {
if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
//segments overlap, merge
segStarts[j] = min(segStarts[s],segStarts[j]);
segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
s--;
}
}
s++;
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) {
setSegment(i, segStarts[i], segStops[i]);
}
} else {
//expand the main seg to the entire length, but only if there are no other segments
uint8_t mainSeg = getMainSegmentId();
if (getActiveSegmentsNum() < 2) {
setSegment(mainSeg, 0, _length);
}
}
fixInvalidSegments();
}
void WS2812FX::fixInvalidSegments() {
//make sure no segment is longer than total (sanity check)
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].start >= _length) setSegment(i, 0, 0);
if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length);
}
}
//true if all segments align with a bus, or if a segment covers the total length
bool WS2812FX::checkSegmentAlignment() {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].start >= _segments[i].stop) continue; //inactive segment
bool aligned = false;
for (uint8_t b = 0; b<busses.getNumBusses(); b++) {
Bus *bus = busses.getBus(b);
if (_segments[i].start == bus->getStart() && _segments[i].stop == bus->getStart() + bus->getLength()) aligned = true;
}
if (_segments[i].start == 0 && _segments[i].stop == _length) aligned = true;
if (!aligned) return false;
}
return true;
}
//After this function is called, setPixelColor() will use that segment (offsets, grouping, ... will apply)

View File

@@ -24,6 +24,10 @@
#define DEBUG_PRINTF(x...)
#endif
#define GET_BIT(var,bit) (((var)>>(bit))&0x01)
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
#define UNSET_BIT(var,bit) ((var)&=(~(uint16_t)(0x0001<<(bit))))
//temporary struct for passing bus configuration to bus
struct BusConfig {
uint8_t type = TYPE_WS2812_RGB;
@@ -32,10 +36,12 @@ struct BusConfig {
uint8_t colorOrder = COL_ORDER_GRB;
bool reversed = false;
uint8_t skipAmount;
bool refreshReq;
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255};
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip=0) {
type = busType; count = len; start = pstart;
colorOrder = pcolorOrder; reversed = rev; skipAmount = skip;
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0) {
refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip;
uint8_t nPins = 1;
if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address
else if (type > 47) nPins = 2;
@@ -120,6 +126,10 @@ class Bus {
return false;
}
inline bool isOffRefreshRequired() {
return _needsRefresh;
}
bool reversed = false;
protected:
@@ -127,6 +137,7 @@ class Bus {
uint8_t _bri = 255;
uint16_t _start = 0;
bool _valid = false;
bool _needsRefresh = false;
};
@@ -143,6 +154,7 @@ class BusDigital : public Bus {
_pins[1] = bc.pins[1];
}
reversed = bc.reversed;
_needsRefresh = bc.refreshReq || bc.type == TYPE_TM1814;
_skip = bc.skipAmount; //sacrificial pixels
_len = bc.count + _skip;
_iType = PolyBus::getI(bc.type, _pins, nr);
@@ -204,7 +216,7 @@ class BusDigital : public Bus {
}
inline bool isRgbw() {
return (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814);
return Bus::isRgbw(_type);
}
inline uint8_t skippedLeds() {
@@ -216,7 +228,7 @@ class BusDigital : public Bus {
}
void cleanup() {
DEBUG_PRINTLN("Digital Cleanup");
DEBUG_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
_iType = I_NONE;
_valid = false;
@@ -326,7 +338,7 @@ class BusPwm : public Bus {
}
bool isRgbw() {
return (_type > TYPE_ONOFF && _type <= TYPE_ANALOG_5CH && _type != TYPE_ANALOG_3CH);
return Bus::isRgbw(_type);
}
inline void cleanup() {
@@ -481,7 +493,7 @@ class BusManager {
static uint32_t memUsage(BusConfig &bc) {
uint8_t type = bc.type;
uint16_t len = bc.count;
if (type < 32) {
if (type > 15 && type < 32) {
#ifdef ESP8266
if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem
if (type > 29) return len*20; //RGBW
@@ -496,7 +508,7 @@ class BusManager {
}
if (type > 31 && type < 48) return 5;
if (type == 44 || type == 45) return len*4; //RGBW
return len*3;
return len*3; //RGB
}
int add(BusConfig &bc) {
@@ -513,7 +525,7 @@ class BusManager {
//do not call this method from system context (network callback)
void removeAll() {
//Serial.println("Removing all.");
DEBUG_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
for (uint8_t i = 0; i < numBusses; i++) delete busses[i];
@@ -567,22 +579,13 @@ class BusManager {
return numBusses;
}
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
uint16_t getTotalLength() {
uint16_t len = 0;
for (uint8_t i=0; i<numBusses; i++ ) len += busses[i]->getLength();
for (uint8_t i=0; i<numBusses; i++) len += busses[i]->getLength();
return len;
}
// a workaround
static inline bool isRgbw(uint8_t type) {
return Bus::isRgbw(type);
}
//Return true if the strip requires a refresh to stay off.
static bool isOffRefreshRequred(uint8_t type) {
return type == TYPE_TM1814;
}
private:
uint8_t numBusses = 0;
Bus* busses[WLED_MAX_BUSSES];

View File

@@ -5,15 +5,20 @@
*/
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
#define WLED_LONG_PRESS 600 //long press if button is released after held for at least 600ms
#define WLED_DOUBLE_PRESS 350 //double press if another press within 350ms after a short press
#define WLED_LONG_REPEATED_ACTION 300 //how often a repeated action (e.g. dimming) is fired on long press on button IDs >0
#define WLED_LONG_AP 6000 //how long the button needs to be held to activate WLED-AP
static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage
void shortPressAction(uint8_t b)
{
if (!macroButton[b])
{
toggleOnOff();
colorUpdated(CALL_MODE_BUTTON);
if (!macroButton[b]) {
switch (b) {
case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break;
default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(macroButton[b], CALL_MODE_BUTTON);
}
@@ -26,6 +31,44 @@ void shortPressAction(uint8_t b)
}
}
void longPressAction(uint8_t b)
{
if (!macroLongPress[b]) {
switch (b) {
case 0: _setRandomColor(false,true); break;
default: bri += 8; colorUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
}
} else {
applyPreset(macroLongPress[b], CALL_MODE_BUTTON);
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long");
}
}
void doublePressAction(uint8_t b)
{
if (!macroDoublePress[b]) {
switch (b) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON);
}
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double");
}
}
bool isButtonPressed(uint8_t i)
{
if (btnPin[i]<0) return false;
@@ -175,6 +218,8 @@ void handleButton()
if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
#endif
if (usermods.handleButton(b)) continue; // did usermod handle buttons
if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > 250) { // button is not a button but a potentiometer
if (b+1 == WLED_MAX_BUTTONS) lastRead = millis();
handleAnalog(b); continue;
@@ -186,61 +231,46 @@ void handleButton()
}
//momentary button logic
if (isButtonPressed(b)) //pressed
{
if (isButtonPressed(b)) { //pressed
if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis();
buttonPressedBefore[b] = true;
if (millis() - buttonPressedTime[b] > 600) //long press
{
if (!buttonLongPressed[b])
{
if (macroLongPress[b]) {applyPreset(macroLongPress[b], CALL_MODE_BUTTON);}
else _setRandomColor(false,true);
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "long");
}
buttonLongPressed[b] = true;
if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
if (!buttonLongPressed[b]) longPressAction(b);
else if (b) { //repeatable action (~3 times per s) on button > 0
longPressAction(b);
buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms
}
buttonLongPressed[b] = true;
}
}
else if (!isButtonPressed(b) && buttonPressedBefore[b]) //released
{
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = millis() - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce
bool doublePress = buttonWaitTime[b];
bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttonWaitTime[b] = 0;
if (dur > 6000 && b==0) //long press on button 0
{
if (b == 0 && dur > WLED_LONG_AP) { //long press on button 0 (when released)
WLED::instance().initAP(true);
}
else if (!buttonLongPressed[b]) { //short press
if (macroDoublePress[b])
{
} else if (!buttonLongPressed[b]) { //short press
if (b == 0 && !macroDoublePress[b]) { //don't wait for double press on button 0 if no double press macro set
shortPressAction(b);
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
if (doublePress) {
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON);
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[64];
sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, "double");
}
} else buttonWaitTime[b] = millis();
} else shortPressAction(b);
doublePressAction(b);
} else {
buttonWaitTime[b] = millis();
}
}
}
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 450 && !buttonPressedBefore[b])
{
//if 350ms elapsed since last short press release it is a short press
if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
shortPressAction(b);
}

View File

@@ -1,4 +1,5 @@
#include "wled.h"
#include "wled_ethernet.h"
/*
* Serializes and parses the cfg.json and wsec.json settings files, stored in internal FS.
@@ -77,18 +78,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
// initialize LED pins and lengths prior to other HW (except for ethernet)
JsonObject hw_led = hw[F("led")];
CJSON(ledCount, hw_led[F("total")]);
if (ledCount > MAX_LEDS) ledCount = MAX_LEDS;
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]);
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
JsonArray ins = hw_led["ins"];
if (fromFS || !ins.isNull()) {
uint8_t s = 0; //bus iterator
strip.isRgbw = false;
strip.isOffRefreshRequred = false;
uint8_t s = 0; // bus iterator
busses.removeAll();
uint32_t mem = 0;
for (JsonObject elm : ins) {
@@ -107,21 +104,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t colorOrder = (int)elm[F("order")];
uint8_t skipFirst = elm[F("skip")];
uint16_t start = elm["start"] | 0;
if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop
uint8_t ledType = elm["type"] | TYPE_WS2812_RGB;
bool reversed = elm["rev"];
bool refresh = elm["ref"] | false;
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
s++;
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
if (bc.adjustBounds(ledCount)) {
//RGBW mode is enabled if at least one of the strips is RGBW
strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType));
//refresh is required to remain off if at least one of the strips requires the refresh.
strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(ledType);
s++;
mem += busses.memUsage(bc);
if (mem <= MAX_LED_MEMORY) busses.add(bc);
}
mem += BusManager::memUsage(bc);
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
}
strip.finalizeInit(ledCount);
// finalization done in beginStrip()
}
if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus
@@ -403,11 +396,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(DMXStartLED,dmx[F("start-led")]);
JsonArray dmx_fixmap = dmx[F("fixmap")];
it = 0;
for (int i : dmx_fixmap) {
if (it > 14) break;
for (int i = 0; i < dmx_fixmap.size(); i++) {
if (i > 14) break;
CJSON(DMXFixtureMap[i],dmx_fixmap[i]);
it++;
}
#endif
@@ -501,12 +492,31 @@ void serializeConfig() {
#ifdef WLED_USE_ETHERNET
JsonObject ethernet = doc.createNestedObject("eth");
ethernet["type"] = ethernetType;
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
JsonArray pins = ethernet.createNestedArray("pin");
for (uint8_t p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) pins.add(esp32_nonconfigurable_ethernet_pins[p].pin);
if (ethernetBoards[ethernetType].eth_power>=0) pins.add(ethernetBoards[ethernetType].eth_power);
if (ethernetBoards[ethernetType].eth_mdc>=0) pins.add(ethernetBoards[ethernetType].eth_mdc);
if (ethernetBoards[ethernetType].eth_mdio>=0) pins.add(ethernetBoards[ethernetType].eth_mdio);
switch (ethernetBoards[ethernetType].eth_clk_mode) {
case ETH_CLOCK_GPIO0_IN:
case ETH_CLOCK_GPIO0_OUT:
pins.add(0);
break;
case ETH_CLOCK_GPIO16_OUT:
pins.add(16);
break;
case ETH_CLOCK_GPIO17_OUT:
pins.add(17);
break;
}
}
#endif
JsonObject hw = doc.createNestedObject("hw");
JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = ledCount;
hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade
hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed;
hw_led[F("rgbwm")] = strip.rgbwMode;
@@ -526,7 +536,9 @@ void serializeConfig() {
ins[F("order")] = bus->getColorOrder();
ins["rev"] = bus->reversed;
ins[F("skip")] = bus->skippedLeds();
ins["type"] = bus->getType();
ins["type"] = bus->getType() & 0x7F;;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbw")] = bus->isRgbw();
}
// button(s)
@@ -551,7 +563,7 @@ void serializeConfig() {
JsonObject hw_ir = hw.createNestedObject("ir");
hw_ir["pin"] = irPin;
hw_ir[F("type")] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
hw_ir["type"] = irEnabled; // the byte 'irEnabled' does contain the IR-Remote Type ( 0=disabled )
JsonObject hw_relay = hw.createNestedObject(F("relay"));
hw_relay["pin"] = rlyPin;

View File

@@ -60,6 +60,10 @@
#define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.h"
#define USERMOD_ID_BATTERY_STATUS_BASIC 18 //Usermod "usermod_v2_battery_status_basic.h"
#define USERMOD_ID_PWM_FAN 19 //Usermod "usermod_PWM_fan.h"
#define USERMOD_ID_BH1750 20 //Usermod "usermod_bh1750.h"
#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h"
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -239,10 +243,10 @@
#define NTP_PACKET_SIZE 48
// maximum number of LEDs - more than 1500 LEDs (or 500 DMA "LEDPIN 3" driven ones) will cause a low memory condition on ESP8266
//maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 1664 // can't rely on memory limit to limit this to 1600 LEDs
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs
#else
#define MAX_LEDS 8192
#endif
@@ -261,12 +265,20 @@
#endif
// string temp buffer (now stored in stack locally)
#define OMAX 2048
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048
#else
#define SETTINGS_STACK_BUF_SIZE 3096
#endif
#ifdef WLED_USE_ETHERNET
#define E131_MAX_UNIVERSE_COUNT 20
#define E131_MAX_UNIVERSE_COUNT 20
#else
#define E131_MAX_UNIVERSE_COUNT 10
#ifdef ESP8266
#define E131_MAX_UNIVERSE_COUNT 9
#else
#define E131_MAX_UNIVERSE_COUNT 12
#endif
#endif
#define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit

View File

@@ -1,120 +0,0 @@
@font-face {
font-family: "CIcons";
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAApMAAsAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIGEWNtYXAAAAFoAAAAVAAAAFQXVtKTZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAABfwAAAX8iNRp/2hlYWQAAAfAAAAANgAAADYd+7tRaGhlYQAAB/gAAAAkAAAAJAeYA9JobXR4AAAIHAAAAEQAAABEOgAGTGxvY2EAAAhgAAAAJAAAACQItgqAbWF4cAAACIQAAAAgAAAAIAAWAExuYW1lAAAIpAAAAYYAAAGGmUoJ+3Bvc3QAAAosAAAAIAAAACAAAwAAAAMD2wGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QwDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkM//3//wAAAAAAIOkA//3//wAB/+MXBAADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQDWAIEDKgLVAAsAAAEhESMRITUhETMRIQMq/wBU/wABAFQBAAGB/wABAFQBAP8AAAAAAAIAgAArA7gDYwANABEAAAEXBzMRIREzJxUhESEVAREhEQLG8vK6/qqc8P6qAVb+qgFWA2Py8P6qAVbwnAFWuv26AVb+qgAAAAEAqgABA4ADVQAlAAABMxEhERQHBisBIicmNREhNSMVFAcGIyEiJyY9ATQ3NjMhMhcWFQMAgP6qDAwSVhIMDAGqKgwMEv4AEg0NDQ0SAgASDAwDAf6q/oASDAwMDBIB1qoqEg0NDQ0SqhIMDAwMEgABAQAAKwMqAysAEwAAASEVIxEjBgcGIyInJjU0NzYzMhcCAAEqqgIINjZKUDg4ODhQHCQDK4D+KkgxMTg4UFA4OAwAAAIAVgArA4ADKwALAB0AAAEWFRQHAScBNjMyFwEyFxYVFAcGIyInMjc2NTQ3NgN0DAz+gnYBfgwSEgz98DQmJjIyRmhCHhsbJiYC5QwSEgz+gnYBfgwM/jYmJjRGMjJWFxcmNCYmAAAAAQCSAIEDgAK9AAUAACUBFwEnNwGAAcQ8/gDuPPkBxDz+AO48AAAAAAIAqv/VA1YDgQAQACEAAAEWFRQHBiMVJzcVMjc2NTQnJyIHBhUUFwcmNTQ3NjM1FwcDIDZlZYyqqmpLSx7iaktLHj42ZWWMqqoCYVJkjGVlgKyqgEtLajw8iEtLakA4PlJkjGVlgKyqAAAAAAEAVgABA9YDgQA/AAABMhcWFRQHBisBFRQHBisBNTQnJiMiBwYdASMiJyY9ATMyNzY1NCcmKwE1NDc2OwE1NDc2MzIXFh0BMzIXFh0BA2osICAgICxAGRkioiIiMDAiIqIiGRlAMCEhISEwQBkZIqwfHywsHx+sIhkZAdUfHywsHx+sIhkZQDAhISEhMEAZGSKiIiIwMCIioiIZGUAsICAgICxAGRkirAAAAAACAFYAHQOqAysAIgA+AAAlNjc2NzY3NjU0JyYjIgcGByMmJyYjIgcGFRQXFhcWFxYfARMyFxYVFAcGBwYHBg8BJyYnJicmNTQ3NjMyFzYCBGAuLjY2FRUrK0AyKysQUBArKzJAKysVFTY2Li5gBMBkQ0MWFjs7MDBqPj6KPT00NENDZHRMTJNWLCw8PC4uLEAqKhwcLCwcHCoqQCwuLjw8LCxWBAKcRERiOjc3REQuLmA4Nnw+PlRUTmJERFpaAAAEAFYAAQOqA1UAAwATACMAJwAAATUzFQMyNzY1NCcmIyIHBhUUFxYTMhcWFRQHBiMiJyY1NDc2ExEzEQHWVCqMZWVlZYyMZWVlZYywfX19fbCwfX19fYZUAitWVv4qZWWMjGVlZWWMjGVlAwB9fbCwfX19fbCwfX39gAEA/wAAAAIAZAABA5wDVQAPAEkAAAEyNzY1NCcmIyIHBhUUFxYlFxYPAQYvAQYPAQYrASIvASYnBwYvASY/ASY1NDcnJj8BNh8BNj8BNjsBMh8BFhc3Nh8BFg8BFhUUAgA+LCwsLD4+LCwsLAF8Wg4KVggSaioeEAQQrBAEECYiahIIVgoOWgICWg4KVggSaioeEAQQrBAEECYiahIIVgoOWgIBFSwsPj4sLCwsPj4sLGxGChKUDgYqHgxwEhJwEBoqBg6UEgpGDhwcDkYKEpQOBioeDHASEnAQGioGDpQSCkYOHBwAAwAq/9UD1gOBAAcADwAXAAABPwEvAQ8BFwUnDwEfAT8BFw8BHwE/AScDKjZ2djY0dnb+9Gpq7OxqauxUNHZ2NDZ2dgIrdjQ2dnY2NIzs7Gpq7OxqgHY0NnZ2NjQAAAAAAwAqAFUD1gLtAA0AEwAdAAATNjMyFwcmJyYjIgcGBxc2MzIXBwE2ISAXByYjIgfWfK+velQkPz80ND8/JFY2Sko2gP4qxAETARPCVqDg4KABgXp6ViQaGhoaJFY2NoAB1sLCVp6eAAABAAAAAAAA3GG4ZV8PPPUACwQAAAAAAN2Du38AAAAA3YO7fwAA/9UD1gOBAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAPWAAEAAAAAAAAAAAAAAAAAAAARBAAAAAAAAAAAAAAAAgAAAAQAANYEAACABAAAqgQAAQAEAABWBAAAkgQAAKoEAABWBAAAVgQAAFYEAABkBAAAKgQAACoAAAAAAAoAFAAeADgAXACUALYA6gD+ATQBigHqAioCmgLKAv4AAQAAABEASgAEAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGljb21vb24AaQBjAG8AbQBvAG8AblZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGljb21vb24AaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AblJlZ3VsYXIAUgBlAGcAdQBsAGEAcmljb21vb24AaQBjAG8AbQBvAG8AbkZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('woff');
}
:root {
--c-1: #111;
--c-f: #fff;
--c-2: #222;
--c-3: #333;
--c-4: #444;
--c-5: #555;
--c-6: #666;
--c-8: #888;
--c-b: #bbb;
--c-c: #ccc;
--c-e: #eee;
--c-d: #ddd;
--c-r: #831;
}
html {
touch-action: manipulation;
}
body {
margin: 0;
background-color: var(--c-2);
font-family: Helvetica, Verdana, sans-serif;
font-size: 17px;
color: var(--c-f);
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
scrollbar-width: 6px;
scrollbar-color: var(--c-sb) transparent;
}
html,
body {
height: 100%;
width: 100%;
position: fixed;
overscroll-behavior: none;
}
.icons {
font-family: "CIcons";
font-style: normal;
font-size: 24px;
line-height: 1;
display: inline-block;
}
#header {
width: 100%;
background-color: var(--c-3);
height: 45px;
}
#menu {
height: calc(100% - 45px);
background-color: var(--c-3);
width: 180px;
}
.entry {
height: 45px;
color: var(--c-b);
}
.entry:hover {
color: var(--c-f);
background-color: var(--c-4);
}
.e-icon {
padding: 10px;
display: inline-block;
width: 45px;
height: 100%;
box-sizing: border-box;
}
.e-label {
display: inline-block;
height: 45px;
vertical-align: top;
padding: 14px 0;
box-sizing: border-box;
}
.btn {
padding: 9px;
color: var(--c-b);
box-sizing: border-box;
}
.btn:hover {
color: var(--c-f);
background-color: var(--c-4);
}
.save {
float: right;
height: 100%;
}
.b-icon {
display: inline-block;
}
.b-label {
display: inline-block;
vertical-align: top;
padding: 4px;
}

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta charset="utf-8">
<meta name="theme-color" content="#222222">
<link rel="stylesheet" href="cfg.css">
</head>
<body>
<div id="header">
<div class="e-icon"><i class="icons">&#xe90a;</i></div><div class="l e-label" id="h-cfg"></div>
<div class="btn save"><div class="b-icon"><i class="icons">&#xe905;</i></div><div class="l b-label" id="b-save"></div></div>
</div>
<div id="menu">
<div class="entry"><div class="e-icon"><i class="icons">&#xe90c;</i></div><div class="l e-label" id="e-nw"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90a;</i></div><div class="l e-label" id="e-hw"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe904;</i></div><div class="l e-label" id="e-ui"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe906;</i></div><div class="l e-label" id="e-if"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90b;</i></div><div class="l e-label" id="e-tm"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90a;</i></div><div class="l e-label" id="e-dx"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe903;</i></div><div class="l e-label" id="e-sr"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe907;</i></div><div class="l e-label" id="e-um"></div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe909;</i></div><div class="l e-label" id="e-ab"></div></div>
</div>
<script src="cfg_lang.js"></script>
<script src="cfg.js"></script>
</body>
</html>

View File

@@ -1,25 +0,0 @@
var d = document;
//startup, called on page load
function S() {
//populate labels
var l = d.getElementsByClassName("l");
for (var i=0;i<l.length;i++) {
if (lang.labels) {
var t = lang.labels[l[i].id];
if (t) l[i].textContent = t;
else l[i].textContent = l[i].id;
} else {
//invalid or missing language json
}
}
}
//toggle between hidden and 100% width (screen < ? px)
//toggle between icons-only and 100% width (screen < ?? px)
//toggle between icons-only and ? px (screen >= ?? px)
function menu() {
}
S();

View File

@@ -1,24 +0,0 @@
//This file contains just the JSON definition for translatable strings.
//A JSON file should be auto-embedded in cfg.js in the future, replacing this file
var lang =
{
"labels":{
"h-cfg":" ",
"e-bk":"Back",
"e-nw":"Network",
"e-hw":"Hardware",
"e-ui":"Customization",
"e-if":"Interfaces",
"e-tm":"Schedules",
"e-sr":"Sound Reactive",
"e-um":"Usermods",
"e-dx":"DMX Out",
"e-ab":"About",
"e-up":"Update",
"e-rb":"Reboot",
"b-save":"Save"
}
}

View File

@@ -1296,9 +1296,9 @@ var plJson = {"0":{
"end": 0
}};
//var plSelContent = "";
function makePlSel(incPl=false) {
var plSelContent = "";
delete pJson["0"]; // remove filler preset
var arr = Object.entries(pJson);
for (var i = 0; i < arr.length; i++) {
var n = arr[i][1].n ? arr[i][1].n : "Preset " + arr[i][0];

View File

@@ -55,7 +55,7 @@
if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
if (t2<16) continue;
if (t2>=80) continue;
}
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);LCs[j].value="";LCs[j].focus();return false;}
}
@@ -160,13 +160,16 @@
}
}
if (change) {
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state
if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED
}
gId("rf"+n).onclick = (t == 31) ? (function(){return false}) : (function(){}); // prevent change for TM1814
isRGBW |= (t == 30 || t == 31 || (t > 40 && t < 46 && t != 43)); // RGBW checkbox, TYPE_xxxx values from const.h
gId("co"+n).style.display = ((t>=80 && t<96) || t == 41 || t == 42) ? "none":"inline"; // hide color order for PWM W & WW/CW
gId("dig"+n+"c").style.display = (t > 40 && t < 48) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (t>=80 && t<96) ? "none":"inline"; // hide reversed for virtual
gId("dig"+n+"s").style.display = ((t>=80 && t<96) || (t > 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("dig"+n+"f").style.display = (t>=16 && t<32 || t>=50 && t<64) ? "inline":"none"; // hide refresh
gId("rev"+n).innerHTML = (t > 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
gId("psd"+n).innerHTML = (t > 40 && t < 48) ? "Index:":"Start:"; // change analog start description
}
@@ -208,6 +211,7 @@
if (t>=80) {
LCs[i].max = 255;
LCs[i].min = 0;
LCs[i].style.color="#fff";
continue; // do not check conflicts
} else {
LCs[i].max = 33;
@@ -327,9 +331,9 @@ ${i+1}:
<span id="p2d${i}"></span><input type="number" name="L2${i}" min="0" max="33" class="xs" onchange="UI()"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" min="0" max="33" class="xs" onchange="UI()"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" min="0" max="33" class="xs" onchange="UI()"/>
<br>
<div id="dig${i}r" style="display:inline"><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}">&nbsp;</div>
<div id="dig${i}s" style="display:inline">Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip 1<sup>st</sup> LED: <input id="sl${i}" type="checkbox" name="SL${i}"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}">&nbsp;</div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
}

View File

@@ -129,6 +129,8 @@
<option value="17">ACST/ACDT</option>
<option value="18">HST (Hawaii)</option>
<option value="19">NOVT (Novosibirsk)</option>
<option value="20">AKST/AKDT (Anchorage)</option>
<option value="21">MX-CST/CDT</option>
</select><br>
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
Current local time is <span class="times">unknown</span>.<br>

View File

@@ -18,7 +18,8 @@ void handleDMX()
uint8_t brightness = strip.getBrightness();
for (int i = DMXStartLED; i < ledCount; i++) { // uses the amount of LEDs as fixture count
uint16_t len = strip.getLengthTotal();
for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count
uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462
byte w = in >> 24 & 0xFF;

View File

@@ -34,9 +34,11 @@ void handleDDPPacket(e131_packet_t* p) {
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
for (uint16_t i = start; i < stop; i++) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], 0);
c+=3;
if (!realtimeOverride) {
for (uint16_t i = start; i < stop; i++) {
setRealtimePixel(i, data[c], data[c+1], data[c+2], 0);
c+=3;
}
}
bool push = p->flags & DDP_PUSH_FLAG;
@@ -102,6 +104,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// update status info
realtimeIP = clientIP;
byte wChannel = 0;
uint16_t totalLen = strip.getLengthTotal();
switch (DMXMode) {
case DMX_MODE_DISABLED:
@@ -114,7 +117,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
realtimeLock(realtimeTimeoutMs, mde);
if (realtimeOverride) return;
wChannel = (dmxChannels-DMXAddress+1 > 3) ? e131_data[DMXAddress+3] : 0;
for (uint16_t i = 0; i < ledCount; i++)
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+0], e131_data[DMXAddress+1], e131_data[DMXAddress+2], wChannel);
break;
@@ -129,7 +132,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
bri = e131_data[DMXAddress+0];
strip.setBrightness(bri);
}
for (uint16_t i = 0; i < ledCount; i++)
for (uint16_t i = 0; i < totalLen; i++)
setRealtimePixel(i, e131_data[DMXAddress+1], e131_data[DMXAddress+2], e131_data[DMXAddress+3], wChannel);
break;

View File

@@ -93,6 +93,12 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
//improv.cpp
void handleImprovPacket();
void sendImprovStateResponse(uint8_t state, bool error = false);
void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId);
//ir.cpp
bool decodeIRCustom(uint32_t code);
void applyRepeatActions();
@@ -191,6 +197,7 @@ bool isAsterisksOnly(const char* str, byte maxLen);
void handleSettingsSet(AsyncWebServerRequest *request, byte subPage);
bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true);
int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255);
//udp.cpp
@@ -206,6 +213,8 @@ void sendSysInfoUDP();
class Usermod {
public:
virtual void loop() {}
virtual void handleOverlayDraw() {}
virtual bool handleButton(uint8_t b) { return false; }
virtual void setup() {}
virtual void connected() {}
virtual void addToJsonState(JsonObject& obj) {}
@@ -225,6 +234,8 @@ class UsermodManager {
public:
void loop();
void handleOverlayDraw();
bool handleButton(uint8_t b);
void setup();
void connected();
void addToJsonState(JsonObject& obj);

View File

@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
</style></head><body><h2>WLED Software Update</h2><form method="POST"
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
Installed version: 0.13.0-b3<br>Download the latest binary: <a
Installed version: 0.13.0-b5<br>Download the latest binary: <a
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
</a><br><input type="file" class="bt" name="update" required><br><input

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

244
wled00/improv.cpp Normal file
View File

@@ -0,0 +1,244 @@
#include "wled.h"
#ifdef WLED_DEBUG_IMPROV
#define DIMPROV_PRINT(x) Serial.print(x)
#define DIMPROV_PRINTLN(x) Serial.println(x)
#define DIMPROV_PRINTF(x...) Serial.printf(x)
#else
#define DIMPROV_PRINT(x)
#define DIMPROV_PRINTLN(x)
#define DIMPROV_PRINTF(x...)
#endif
#define IMPROV_VERSION 1
void parseWiFiCommand(char *rpcData);
enum ImprovPacketType {
Current_State = 0x01,
Error_State = 0x02,
RPC_Command = 0x03,
RPC_Response = 0x04
};
enum ImprovPacketByte {
Version = 6,
PacketType = 7,
Length = 8,
RPC_CommandType = 9
};
enum ImprovRPCType {
Command_Wifi = 0x01,
Request_State = 0x02,
Request_Info = 0x03
};
//File dbgf;
//blocking function to parse an Improv Serial packet
void handleImprovPacket() {
uint8_t header[6] = {'I','M','P','R','O','V'};
//dbgf = WLED_FS.open("/improv.log","a");
bool timeout = false;
uint8_t waitTime = 25;
uint16_t packetByte = 0;
uint8_t packetLen = 9;
uint8_t checksum = 0;
uint8_t rpcCommandType = 0;
char rpcData[128];
rpcData[0] = 0;
while (!timeout) {
if (Serial.available() < 1) {
delay(1);
waitTime--;
if (!waitTime) timeout = true;
continue;
}
byte next = Serial.read();
DIMPROV_PRINT("Received improv byte: "); DIMPROV_PRINTF("%x\r\n",next);
//f.write(next);
switch (packetByte) {
case ImprovPacketByte::Version: {
if (next != IMPROV_VERSION) {
DIMPROV_PRINTLN(F("Invalid version"));
//dbgf.close();
return;
}
break;
}
case ImprovPacketByte::PacketType: {
if (next != ImprovPacketType::RPC_Command) {
DIMPROV_PRINTF("Non RPC-command improv packet type %i\n",next);
//dbgf.close();
return;
}
if (!improvActive) improvActive = 1;
break;
}
case ImprovPacketByte::Length: packetLen = 9 + next; break;
case ImprovPacketByte::RPC_CommandType: rpcCommandType = next; break;
default: {
if (packetByte >= packetLen) { //end of packet, check checksum match
if (checksum != next) {
DIMPROV_PRINTF("Got RPC checksum %i, expected %i",next,checksum);
sendImprovStateResponse(0x01, true);
//dbgf.close();
return;
}
switch (rpcCommandType) {
case ImprovRPCType::Command_Wifi: parseWiFiCommand(rpcData); break;
case ImprovRPCType::Request_State: {
uint8_t improvState = 0x02; //authorized
if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning
if (Network.isConnected()) improvState = 0x04; //provisioned
sendImprovStateResponse(improvState, false);
if (improvState == 0x04) sendImprovRPCResponse(ImprovRPCType::Request_State);
break;
}
case ImprovRPCType::Request_Info: sendImprovInfoResponse(); break;
default: {
DIMPROV_PRINTF("Unknown RPC command %i\n",next);
sendImprovStateResponse(0x02, true);
}
}
//dbgf.close();
return;
}
if (packetByte < 6) { //check header
if (next != header[packetByte]) {
DIMPROV_PRINTLN(F("Invalid improv header"));
//dbgf.close();
return;
}
} else if (packetByte > 9) { //RPC data
rpcData[packetByte - 10] = next;
if (packetByte > 137) return; //prevent buffer overflow
}
}
}
checksum += next;
packetByte++;
}
//dbgf.close();
}
void sendImprovStateResponse(uint8_t state, bool error) {
if (!error && improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
if (error) improvError = state;
char out[11] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = error? ImprovPacketType::Error_State : ImprovPacketType::Current_State;
out[8] = 1;
out[9] = state;
uint8_t checksum = 0;
for (uint8_t i = 0; i < 10; i++) checksum += out[i];
out[10] = checksum;
Serial.write((uint8_t*)out, 11);
Serial.write('\n');
}
void sendImprovRPCResponse(byte commandId) {
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
uint8_t packetLen = 12;
char out[64] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = ImprovPacketType::RPC_Response;
out[8] = 2; //Length (set below)
out[9] = commandId;
out[10] = 0; //Data len (set below)
out[11] = '\0'; //URL len (set below)
if (Network.isConnected())
{
IPAddress localIP = Network.localIP();
uint8_t len = sprintf(out+12, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
if (len > 24) return; //sprintf fail?
out[11] = len;
out[10] = 1 + len;
out[8] = 3 + len; //RPC command type + data len + url len + url
packetLen = 13 + len;
}
uint8_t checksum = 0;
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
out[packetLen -1] = checksum;
Serial.write((uint8_t*)out, packetLen);
Serial.write('\n');
improvActive = 1; //no longer provisioning
}
void sendImprovInfoResponse() {
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
uint8_t packetLen = 12;
char out[128] = {'I','M','P','R','O','V'};
out[6] = IMPROV_VERSION;
out[7] = ImprovPacketType::RPC_Response;
//out[8] = 2; //Length (set below)
out[9] = ImprovRPCType::Request_Info;
//out[10] = 0; //Data len (set below)
out[11] = 4; //Firmware len ("WLED")
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
uint8_t lengthSum = 17;
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b5/%i"),VERSION);
out[16] = vlen; lengthSum += vlen;
uint8_t hlen = 7;
#ifdef ESP8266
strcpy(out+lengthSum+1,"esp8266");
#else
hlen = 5;
strcpy(out+lengthSum+1,"esp32");
#endif
out[lengthSum] = hlen;
lengthSum += hlen + 1;
//Use serverDescription if it has been changed from the default "WLED", else mDNS name
bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0);
strcpy(out+lengthSum+1,useMdnsName ? cmDNS : serverDescription);
uint8_t nlen = strlen(useMdnsName ? cmDNS : serverDescription);
out[lengthSum] = nlen;
lengthSum += nlen + 1;
packetLen = lengthSum +1;
out[8] = lengthSum -9;
out[10] = lengthSum -11;
uint8_t checksum = 0;
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
out[packetLen -1] = checksum;
Serial.write((uint8_t*)out, packetLen);
Serial.write('\n');
DIMPROV_PRINT("Info checksum");
DIMPROV_PRINTLN(checksum);
}
void parseWiFiCommand(char* rpcData) {
uint8_t len = rpcData[0];
if (!len || len > 126) return;
uint8_t ssidLen = rpcData[1];
if (ssidLen > len -1 || ssidLen > 32) return;
memset(clientSSID, 0, 32);
memcpy(clientSSID, rpcData+2, ssidLen);
memset(clientPass, 0, 64);
if (len > ssidLen +1) {
uint8_t passLen = rpcData[2+ssidLen];
memset(clientPass, 0, 64);
memcpy(clientPass, rpcData+3+ssidLen, passLen);
}
sendImprovStateResponse(0x03); //provisioning
improvActive = 2;
forceReconnect = true;
serializeConfig();
}

View File

@@ -6,6 +6,20 @@
* JSON API (De)serialization
*/
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255) {
if (elem.is<int>()) {
*val = elem;
return true;
} else if (elem.is<const char*>()) {
const char* str = elem;
size_t len = strnlen(str, 12);
if (len == 0 || len > 10) return false;
parseNumber(str, val, vmin, vmax);
return true;
}
return false; //key does not exist
}
void deserializeSegment(JsonObject elem, byte it, byte presetId)
{
byte id = elem["id"] | it;
@@ -62,12 +76,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
}
if (stop > start && seg.offset > len -1) seg.offset = len -1;
int segbri = elem["bri"] | -1;
if (segbri == 0) {
seg.setOption(SEG_OPTION_ON, 0, id);
} else if (segbri > 0) {
seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, 1, id);
byte segbri = 0;
if (getVal(elem["bri"], &segbri)) {
if (segbri > 0) seg.setOpacity(segbri, id);
seg.setOption(SEG_OPTION_ON, segbri, id);
}
bool on = elem["on"] | seg.getOption(SEG_OPTION_ON);
@@ -191,7 +203,11 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId)
if (set < 2) stop = start + 1;
for (uint16_t i = start; i < stop; i++) {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
if (strip.gammaCorrectCol) {
strip.setPixelColor(i, strip.gamma8(rgbw[0]), strip.gamma8(rgbw[1]), strip.gamma8(rgbw[2]), strip.gamma8(rgbw[3]));
} else {
strip.setPixelColor(i, rgbw[0], rgbw[1], rgbw[2], rgbw[3]);
}
}
if (!set) start++;
set = 0;
@@ -210,7 +226,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
strip.applyToAllSelected = false;
bool stateResponse = root[F("v")] | false;
bri = root["bri"] | bri;
getVal(root["bri"], &bri);
bool on = root["on"] | (bri > 0);
if (!on != !bri) toggleOnOff();
@@ -314,18 +330,18 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
usermods.readFromJsonState(root);
int ps = root[F("psave")] | -1;
byte ps = root[F("psave")];
if (ps > 0) {
savePreset(ps, true, nullptr, root);
} else {
ps = root[F("pdel")] | -1; //deletion
ps = root[F("pdel")]; //deletion
if (ps > 0) {
deletePreset(ps);
}
ps = root["ps"] | -1; //load preset (clears state request!)
if (ps >= 0) {
if (getVal(root["ps"], &presetCycCurr, 1, 5)) { //load preset (clears state request!)
if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually
applyPreset(ps, callMode);
applyPreset(presetCycCurr, callMode);
return stateResponse;
}
@@ -478,14 +494,14 @@ void serializeInfo(JsonObject root)
//root[F("cn")] = WLED_CODENAME;
JsonObject leds = root.createNestedObject("leds");
leds[F("count")] = ledCount;
leds[F("count")] = strip.getLengthTotal();
leds[F("rgbw")] = strip.isRgbw;
leds[F("wv")] = strip.isRgbw && (strip.rgbwMode == RGBW_MODE_MANUAL_ONLY || strip.rgbwMode == RGBW_MODE_DUAL); //should a white channel slider be displayed?
leds[F("pwr")] = strip.currentMilliamps;
leds[F("fps")] = strip.getFps();
leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0;
leds[F("maxseg")] = strip.getMaxSegments();
leds[F("seglock")] = false; //will be used in the future to prevent modifications to segment config
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
root[F("str")] = syncToggleReceive;
@@ -547,7 +563,7 @@ void serializeInfo(JsonObject root)
root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
root[F("lwip")] = 0;
root[F("lwip")] = 0; //deprecated
#else
root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion();
@@ -841,7 +857,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
#endif
}
uint16_t used = ledCount;
uint16_t used = strip.getLengthTotal();
uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS
char buffer[2000];
strcpy_P(buffer, PSTR("{\"leds\":["));

View File

@@ -30,6 +30,8 @@ Timezone* tz;
#define TZ_AUSTRALIA_SOUTHERN 17
#define TZ_HAWAII 18
#define TZ_NOVOSIBIRSK 19
#define TZ_ANCHORAGE 20
#define TZ_MX_CENTRAL 21
#define TZ_INIT 255
byte tzCurrent = TZ_INIT; //uninitialized
@@ -135,6 +137,16 @@ void updateTimezone() {
tcrStandard = tcrDaylight;
break;
}
case TZ_ANCHORAGE : {
tcrDaylight = {Second, Sun, Mar, 2, -480}; //AKDT = UTC - 8 hours
tcrStandard = {First, Sun, Nov, 2, -540}; //AKST = UTC - 9 hours
break;
}
case TZ_MX_CENTRAL : {
tcrDaylight = {First, Sun, Apr, 2, -300}; //CDT = UTC - 5 hours
tcrStandard = {Last, Sun, Oct, 2, -360}; //CST = UTC - 6 hours
break;
}
}
tzCurrent = currentTimezone;

View File

@@ -18,6 +18,7 @@ void initCronixie()
}
//handleOverlays is essentially the equivalent of usermods.loop
void handleOverlays()
{
initCronixie();
@@ -111,8 +112,8 @@ void _overlayAnalogCountdown()
}
}
void handleOverlayDraw() {
usermods.handleOverlayDraw();
if (!overlayCurrent) return;
switch (overlayCurrent)
{
@@ -121,7 +122,6 @@ void handleOverlayDraw() {
}
}
/*
* Support for the Cronixie clock
*/

View File

@@ -36,7 +36,6 @@ enum struct PinOwner : uint8_t {
DMX = 0x8A, // 'DMX' == hard-coded to IO2
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_RGBRotaryEncoder = USERMOD_ID_UNSPECIFIED, // 0x01 // No define in const.h for this user module -- consider adding?
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
UM_Temperature = USERMOD_ID_TEMPERATURE, // 0x03 // Usermod "usermod_temperature.h"
// #define USERMOD_ID_FIXNETSERVICES // 0x04 // Usermod "usermod_Fix_unreachable_netservices.h" -- Does not allocate pins
@@ -53,6 +52,8 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_RTC // 0x0F // Usermod "usermod_rtc.h" -- Uses "standard" I2C pins ... TODO -- enable shared I2C bus use
// #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@@ -90,7 +90,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
}
strip.isRgbw = false;
uint8_t colorOrder, type, skip;
uint16_t length, start;
uint8_t pins[5] = {255, 255, 255, 255, 255};
@@ -105,6 +104,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //refresh required
if (!request->hasArg(lp)) {
DEBUG_PRINTLN(F("No data.")); break;
}
@@ -114,24 +114,21 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255;
}
type = request->arg(lt).toInt();
strip.isRgbw = strip.isRgbw || BusManager::isRgbw(type);
type |= request->hasArg(rf) << 7; // off refresh override
skip = request->hasArg(sl) ? LED_SKIP_AMOUNT : 0;
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
length = request->arg(lc).toInt();
t += length = request->arg(lc).toInt();
} else {
break; // no parameter
}
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : 0;
// actual finalization is done in WLED::loop() (removing old busses and adding new)
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder, request->hasArg(cv), skip);
if (!doInitBusses) ledCount = 1;
doInitBusses = true;
uint16_t totalNew = start + length;
if (totalNew > ledCount && totalNew <= MAX_LEDS) ledCount = totalNew; //total is end of last bus (where start + len is max.)
}
// upate other pins
@@ -181,7 +178,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
fadeTransition = request->hasArg(F("TF"));
t = request->arg(F("TD")).toInt();
if (t > 0) transitionDelay = t;
if (t >= 0) transitionDelay = t;
transitionDelayDefault = t;
strip.paletteFade = request->hasArg(F("PF"));
@@ -506,9 +503,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINTLN(value);
}
}
#ifdef WLED_DEBUG
serializeJson(um,Serial); DEBUG_PRINTLN();
#endif
usermods.readFromConfig(um); // force change of usermod parameters
}
@@ -525,21 +519,21 @@ int getNumVal(const String* req, uint16_t pos)
}
//helper to get int value at a position in string
bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv)
//helper to get int value with in/decrementing support via ~ syntax
void parseNumber(const char* str, byte* val, byte minv, byte maxv)
{
int pos = req->indexOf(key);
if (pos < 1) return false;
if (req->charAt(pos+3) == '~') {
int out = getNumVal(req, pos+1);
if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {*val = random8(minv,maxv); return;}
if (str[0] == '~') {
int out = atoi(str +1);
if (out == 0)
{
if (req->charAt(pos+4) == '-')
if (str[1] == '0') return;
if (str[1] == '-')
{
*val = (*val <= minv)? maxv : *val -1;
*val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around
} else {
*val = (*val >= maxv)? minv : *val +1;
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
}
} else {
out += *val;
@@ -549,8 +543,25 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv, byte ma
}
} else
{
*val = getNumVal(req, pos);
byte p1 = atoi(str);
const char* str2 = strchr(str,'~'); //min/max range (for preset cycle, e.g. "1~5~")
if (str2) {
byte p2 = atoi(str2+1);
while (isdigit((str2+1)[0])) str2++;
parseNumber(str2+1, val, p1, p2);
} else {
*val = p1;
}
}
}
bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv)
{
int pos = req->indexOf(key);
if (pos < 1) return false;
if (req->length() < (unsigned int)(pos + 4)) return false;
parseNumber(req->c_str() + pos +3, val, minv, maxv);
return true;
}
@@ -581,7 +592,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
if (t < strip.getMaxSegments()) selectedSeg = t;
}
WS2812FX::Segment& mainseg = strip.getSegment(selectedSeg);
WS2812FX::Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected
if (pos > 0) {
byte t = getNumVal(&req, pos);
@@ -591,13 +602,13 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
strip.getSegment(i).setOption(SEG_OPTION_SELECTED, 0);
}
}
mainseg.setOption(SEG_OPTION_SELECTED, t);
selseg.setOption(SEG_OPTION_SELECTED, t);
}
uint16_t startI = mainseg.start;
uint16_t stopI = mainseg.stop;
uint8_t grpI = mainseg.grouping;
uint16_t spcI = mainseg.spacing;
uint16_t startI = selseg.start;
uint16_t stopI = selseg.stop;
uint8_t grpI = selseg.grouping;
uint16_t spcI = selseg.spacing;
pos = req.indexOf(F("&S=")); //segment start
if (pos > 0) {
startI = getNumVal(&req, pos);
@@ -617,9 +628,36 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
strip.setSegment(selectedSeg, startI, stopI, grpI, spcI);
pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.setOption(SEG_OPTION_REVERSED, req.charAt(pos+3) != '0');
pos = req.indexOf(F("MI=")); //Segment mirror
if (pos > 0) selseg.setOption(SEG_OPTION_MIRROR, req.charAt(pos+3) != '0');
pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
selseg.setOption(SEG_OPTION_ON, segbri, selectedSeg);
if (segbri) {
selseg.setOpacity(segbri, selectedSeg);
}
}
pos = req.indexOf(F("SW=")); //segment power
if (pos > 0) {
switch (getNumVal(&req, pos)) {
case 0: selseg.setOption(SEG_OPTION_ON, false); break;
case 1: selseg.setOption(SEG_OPTION_ON, true); break;
default: selseg.setOption(SEG_OPTION_ON, !selseg.getOption(SEG_OPTION_ON)); break;
}
}
pos = req.indexOf(F("PS=")); //saves current in preset
if (pos > 0) savePreset(getNumVal(&req, pos));
byte presetCycleMin = 1;
byte presetCycleMax = 5;
pos = req.indexOf(F("P1=")); //sets first preset for cycle
if (pos > 0) presetCycleMin = getNumVal(&req, pos);
@@ -707,7 +745,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
strip.applyToAllSelected = true;
strip.setColor(2, t[0], t[1], t[2], t[3]);
} else {
strip.getSegment(selectedSeg).setColor(2,((t[0] << 16) + (t[1] << 8) + t[2] + (t[3] << 24)), selectedSeg);
selseg.setColor(2,((t[0] << 16) + (t[1] << 8) + t[2] + (t[3] << 24)), selectedSeg); // defined above (SS=)
}
}
@@ -811,24 +849,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos);
//Segment reverse
pos = req.indexOf(F("RV="));
if (pos > 0) strip.getSegment(selectedSeg).setOption(SEG_OPTION_REVERSED, req.charAt(pos+3) != '0');
//Segment reverse
pos = req.indexOf(F("MI="));
if (pos > 0) strip.getSegment(selectedSeg).setOption(SEG_OPTION_MIRROR, req.charAt(pos+3) != '0');
//Segment brightness/opacity
pos = req.indexOf(F("SB="));
if (pos > 0) {
byte segbri = getNumVal(&req, pos);
strip.getSegment(selectedSeg).setOption(SEG_OPTION_ON, segbri, selectedSeg);
if (segbri) {
strip.getSegment(selectedSeg).setOpacity(segbri, selectedSeg);
}
}
//set time (unix timestamp)
pos = req.indexOf(F("ST="));
if (pos > 0) {

View File

@@ -92,7 +92,8 @@ void notify(byte callMode, bool followUp)
void realtimeLock(uint32_t timeoutMs, byte md)
{
if (!realtimeMode && !realtimeOverride){
for (uint16_t i = 0; i < ledCount; i++)
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < totalLen; i++)
{
strip.setPixelColor(i,0,0,0,0);
}
@@ -168,10 +169,11 @@ void handleNotifications()
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION);
if (realtimeOverride) return;
uint16_t id = 0;
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 0; i < packetSize -2; i += 3)
{
setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
strip.show();
return;
@@ -339,9 +341,10 @@ void handleNotifications()
byte numPackets = udpIn[5];
uint16_t id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
uint16_t totalLen = strip.getLengthTotal();
for (uint16_t i = 6; i < tpmPayloadFrameSize + 4; i += 3)
{
if (id < ledCount)
if (id < totalLen)
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
@@ -372,6 +375,7 @@ void handleNotifications()
}
if (realtimeOverride) return;
uint16_t totalLen = strip.getLengthTotal();
if (udpIn[0] == 1) //warls
{
for (uint16_t i = 2; i < packetSize -3; i += 4)
@@ -385,7 +389,7 @@ void handleNotifications()
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 3) //drgbw
{
@@ -394,14 +398,14 @@ void handleNotifications()
{
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++; if (id >= ledCount) break;
id++; if (id >= totalLen) break;
}
} else if (udpIn[0] == 4) //dnrgb
{
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 3)
{
if (id >= ledCount) break;
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
id++;
}
@@ -410,7 +414,7 @@ void handleNotifications()
uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
for (uint16_t i = 4; i < packetSize -2; i += 4)
{
if (id >= ledCount) break;
if (id >= totalLen) break;
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
id++;
}
@@ -438,7 +442,7 @@ void handleNotifications()
void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w)
{
uint16_t pix = i + arlsOffset;
if (pix < ledCount)
if (pix < strip.getLengthTotal())
{
if (!arlsDisableGammaCorrection && strip.gammaCorrectCol)
{
@@ -479,6 +483,7 @@ void sendSysInfoUDP()
if (!udp2Connected) return;
IPAddress ip = Network.localIP();
if (!ip || ip == IPAddress(255,255,255,255)) ip = IPAddress(4,3,2,1);
// TODO: make a nice struct of it and clean up
// 0: 1 byte 'binary token 255'

View File

@@ -4,7 +4,15 @@
*/
//Usermod Manager internals
void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::handleOverlayDraw() { for (byte i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); }
bool UsermodManager::handleButton(uint8_t b) {
bool overrideIO = false;
for (byte i = 0; i < numMods; i++) {
if (ums[i]->handleButton(b)) overrideIO = true;
}
return overrideIO;
}
void UsermodManager::setup() { for (byte i = 0; i < numMods; i++) ums[i]->setup(); }
void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) ums[i]->connected(); }

View File

@@ -96,6 +96,14 @@
#include "../usermods/rgb-rotary-encoder/rgb-rotary-encoder.h"
#endif
#ifdef USERMOD_SEVEN_SEGMENT
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#endif
#ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
void registerUsermods()
{
/*
@@ -179,4 +187,12 @@ void registerUsermods()
#ifdef RGB_ROTARY_ENCODER
usermods.add(new RgbRotaryEncoderUsermod());
#endif
#ifdef USERMOD_SEVEN_SEGMENT
usermods.add(new SevenSegmentDisplay());
#endif
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif
}

View File

@@ -42,7 +42,7 @@ bool oappendi(int i)
bool oappend(const char* txt)
{
uint16_t len = strlen(txt);
if (olen + len >= OMAX)
if (olen + len >= SETTINGS_STACK_BUF_SIZE)
return false; // buffer full
strcpy(obuf + olen, txt);
olen += len;
@@ -209,25 +209,20 @@ void WLED::loop()
if (doInitBusses) {
doInitBusses = false;
DEBUG_PRINTLN(F("Re-init busses."));
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
busses.removeAll();
uint32_t mem = 0;
strip.isRgbw = false;
for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) {
if (busConfigs[i] == nullptr) break;
if (busConfigs[i]->adjustBounds(ledCount)) {
mem += busses.memUsage(*busConfigs[i]);
if (mem <= MAX_LED_MEMORY) {
busses.add(*busConfigs[i]);
//RGBW mode is enabled if at least one of the strips is RGBW
strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(busConfigs[i]->type));
//refresh is required to remain off if at least one of the strips requires the refresh.
strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(busConfigs[i]->type);
}
mem += BusManager::memUsage(*busConfigs[i]);
if (mem <= MAX_LED_MEMORY) {
busses.add(*busConfigs[i]);
}
delete busConfigs[i]; busConfigs[i] = nullptr;
}
strip.finalizeInit(ledCount);
strip.finalizeInit();
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
yield();
serializeConfig();
}
@@ -308,7 +303,7 @@ void WLED::setup()
#ifdef WLED_DEBUG
pinManager.allocatePin(1, true, PinOwner::DebugOut); // GPIO1 reserved for debug output
#endif
#ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin
#ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin
pinManager.allocatePin(2, true, PinOwner::DMX);
#endif
@@ -379,6 +374,8 @@ void WLED::setup()
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
}
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
strip.service();
#ifndef WLED_DISABLE_OTA
@@ -396,6 +393,8 @@ void WLED::setup()
#ifdef WLED_ENABLE_DMX
initDMX();
#endif
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
// HTTP server page init
initServer();
@@ -407,11 +406,8 @@ void WLED::setup()
void WLED::beginStrip()
{
// Initialize NeoPixel Strip and button
if (ledCount > MAX_LEDS || ledCount == 0)
ledCount = 30;
strip.finalizeInit(ledCount);
strip.finalizeInit(); // busses created during deserializeConfig()
strip.makeAutoSegments();
strip.setBrightness(0);
strip.setShowCallback(handleOverlayDraw);
@@ -728,14 +724,26 @@ void WLED::handleConnection()
interfacesInited = false;
initConnection();
}
if (now - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED)
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
sendImprovStateResponse(0x03, true);
improvActive = 2;
}
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) {
if (improvActive == 2) improvActive = 3;
initConnection();
}
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN))
initAP();
} else if (!interfacesInited) { // newly connected
} else if (!interfacesInited) { //newly connected
DEBUG_PRINTLN("");
DEBUG_PRINT(F("Connected! IP address: "));
DEBUG_PRINTLN(Network.localIP());
if (improvActive) {
if (improvError == 3) sendImprovStateResponse(0x00, true);
sendImprovStateResponse(0x04);
if (improvActive > 1) sendImprovRPCResponse(0x01);
}
initInterfaces();
userConnected();
usermods.connected();
@@ -784,4 +792,4 @@ void WLED::handleStatusLED()
}
#endif
}
}

View File

@@ -3,12 +3,12 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.13.0-b3
@version 0.13.0-b5
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2110060
#define VERSION 2111170
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -262,7 +262,6 @@ WLED_GLOBAL bool noWifiSleep _INIT(false);
#endif
// LED CONFIG
WLED_GLOBAL uint16_t ledCount _INIT(DEFAULT_LED_COUNT); // overcurrent prevented by ABL
WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up
WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up
@@ -366,7 +365,7 @@ WLED_GLOBAL byte currentTimezone _INIT(0); // Timezone ID. Refer to timez
WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC before timzone calculation
WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clock 3: cronixie
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(ledCount - 1); // boundaries of overlay mode
WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(DEFAULT_LED_COUNT - 1); // boundaries of overlay mode
WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be
WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel
@@ -507,12 +506,15 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
// blynk
WLED_GLOBAL bool blynkEnabled _INIT(false);
//improv
WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning
WLED_GLOBAL byte improvError _INIT(0);
//playlists
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);
//still used for "PL=~" HTTP API command
WLED_GLOBAL byte presetCycleMin _INIT(1), presetCycleMax _INIT(5);
WLED_GLOBAL byte presetCycCurr _INIT(presetCycleMin);
WLED_GLOBAL byte presetCycCurr _INIT(0);
// realtime
WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE);

View File

@@ -89,7 +89,16 @@ void loadSettingsFromEEPROM()
if (apChannel > 13 || apChannel < 1) apChannel = 1;
apHide = EEPROM.read(228);
if (apHide > 1) apHide = 1;
ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30;
uint16_t length = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); //was ledCount
if (length > MAX_LEDS || length == 0) length = 30;
uint8_t pins[5] = {2, 255, 255, 255, 255};
uint8_t colorOrder = COL_ORDER_GRB;
if (lastEEPROMversion > 9) colorOrder = EEPROM.read(383);
if (colorOrder > COL_ORDER_GBR) colorOrder = COL_ORDER_GRB;
bool skipFirst = EEPROM.read(2204);
bool reversed = EEPROM.read(252);
BusConfig bc = BusConfig(EEPROM.read(372) ? TYPE_SK6812_RGBW : TYPE_WS2812_RGB, pins, 0, length, colorOrder, reversed, skipFirst);
busses.add(bc);
notifyButton = EEPROM.read(230);
notifyTwice = EEPROM.read(231);
@@ -143,7 +152,7 @@ void loadSettingsFromEEPROM()
arlsOffset = EEPROM.read(368);
if (!EEPROM.read(367)) arlsOffset = -arlsOffset;
turnOnAtBoot = EEPROM.read(369);
strip.isRgbw = EEPROM.read(372);
//strip.isRgbw = EEPROM.read(372);
//374 - strip.paletteFade
apBehavior = EEPROM.read(376);

View File

@@ -16,7 +16,7 @@ enum class AdaState {
Data_Blue,
TPM2_Header_Type,
TPM2_Header_CountHi,
TPM2_Header_CountLo
TPM2_Header_CountLo,
};
void handleSerial()
@@ -41,7 +41,12 @@ void handleSerial()
else if (next == 0xC9) { //TPM2 start byte
state = AdaState::TPM2_Header_Type;
}
else if (next == '{') { //JSON API
else if (next == 'I') {
handleImprovPacket();
return;
} else if (next == 'v') {
Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION);
} else if (next == '{') { //JSON API
bool verboseResponse = false;
{
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
@@ -61,6 +66,7 @@ void handleSerial()
serializeInfo(info);
serializeJson(doc, Serial);
Serial.println();
}
}
break;

View File

@@ -362,9 +362,10 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
String settingsProcessor(const String& var)
{
if (var == "CSS") {
char buf[2048];
char buf[SETTINGS_STACK_BUF_SIZE];
buf[0] = 0;
getSettingsJS(optionType, buf);
//Serial.println(uxTaskGetStackHighWaterMark(NULL));
return String(buf);
}
@@ -387,7 +388,7 @@ String dmxProcessor(const String& var)
mapJS += "\nCN=" + String(DMXChannels) + ";\n";
mapJS += "CS=" + String(DMXStart) + ";\n";
mapJS += "CG=" + String(DMXGap) + ";\n";
mapJS += "LC=" + String(ledCount) + ";\n";
mapJS += "LC=" + String(strip.getLengthTotal()) + ";\n";
mapJS += "var CH=[";
for (int i=0;i<15;i++) {
mapJS += String(DMXFixtureMap[i]) + ",";

View File

@@ -380,6 +380,7 @@ void getSettingsJS(byte subPage, char* dest)
char ls[4] = "LS"; ls[2] = 48+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = 48+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = 48+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = 48+s; rf[3] = 0; //off refresh
oappend(SET_F("addLEDs(1);"));
uint8_t pins[5];
uint8_t nPins = bus->getPins(pins);
@@ -393,6 +394,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',ls,bus->getStart());
sappend('c',cv,bus->reversed);
sappend('c',sl,bus->skippedLeds());
sappend('c',rf,bus->isOffRefreshRequired());
}
sappend('v',SET_F("MA"),strip.ablMilliampsMax);
sappend('v',SET_F("LA"),strip.milliampsPerLed);