Compare commits
44 Commits
new-settin
...
v0.13.0-b5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4af1f62aab | ||
|
|
bc403440ba | ||
|
|
38d8dfe5ab | ||
|
|
6df64d0d31 | ||
|
|
83753a5f81 | ||
|
|
3161f5fa47 | ||
|
|
5784092c1b | ||
|
|
d6ad089c60 | ||
|
|
446b4b084c | ||
|
|
adeb9bccb1 | ||
|
|
b44ffffed8 | ||
|
|
2bdaf53ecf | ||
|
|
46e7db6d94 | ||
|
|
7e1920dc4b | ||
|
|
a93f05c047 | ||
|
|
00238247cd | ||
|
|
b33e28835d | ||
|
|
0acca2e313 | ||
|
|
00f1b483eb | ||
|
|
c3d48acb4c | ||
|
|
392bda7d8c | ||
|
|
10cfcdab8c | ||
|
|
3f71d3b250 | ||
|
|
1b50fbab22 | ||
|
|
303fc65a6a | ||
|
|
445b6ee13f | ||
|
|
8afaac1e30 | ||
|
|
0327f9428e | ||
|
|
a5de66bbd5 | ||
|
|
d47157eec3 | ||
|
|
f4b47ed399 | ||
|
|
8b2145bd88 | ||
|
|
de454e8b78 | ||
|
|
6cd770b4c7 | ||
|
|
355525c248 | ||
|
|
47d4e7381f | ||
|
|
5dac6690d7 | ||
|
|
b89f7180db | ||
|
|
2ebb837a15 | ||
|
|
849aa64678 | ||
|
|
d00b4335b5 | ||
|
|
3ac772badc | ||
|
|
22fc58d93b | ||
|
|
ccd3152b24 |
27
.github/ISSUE_TEMPLATE/bug.md
vendored
27
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Noticed an issue with your lights?
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. Please quickly search existing issues first!
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior, if consistently possible
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**WLED version**
|
||||
- Board: [e.g. Wemos D1, ESP32 dev]
|
||||
- Version [e.g. 0.10.0, dev200603]
|
||||
- Format [e.g. Binary, self-compiled]
|
||||
|
||||
**Additional context**
|
||||
Anything else you'd like to say about the problem?
|
||||
|
||||
Thank you for your help!
|
||||
83
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
83
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please quickly search existing issues first before submitting a bug.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: Tell us what the problem is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
attributes:
|
||||
label: To Reproduce Bug
|
||||
description: Steps to reproduce the behavior, if consistently possible.
|
||||
placeholder: Tell us how to make the bug appear.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: Tell us what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: install_format
|
||||
attributes:
|
||||
label: Install Method
|
||||
description: How did you install WLED?
|
||||
options:
|
||||
- Binary from WLED.me
|
||||
- Self-Compiled
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What version of WLED?
|
||||
description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
|
||||
placeholder: "e.g. WLED 0.13.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
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -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
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.0-b3",
|
||||
"version": "0.13.0-b5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -24,7 +24,7 @@ void RGBNET_readValues() {
|
||||
int channel = UDP.read();
|
||||
|
||||
//channel data is not used we only supports one channel
|
||||
int len = UDP.read(RGBNET_packet, ledCount*3);
|
||||
int len = UDP.read(RGBNET_packet, strip.getLengthTotal()*3);
|
||||
if(len==0){
|
||||
return;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ void handleConfig(AsyncWebServerRequest *request)
|
||||
\"channels\": [\
|
||||
{\
|
||||
\"channel\": 1,\
|
||||
\"leds\": " + ledCount + "\
|
||||
\"leds\": " + strip.getLengthTotal() + "\
|
||||
},\
|
||||
{\
|
||||
\"channel\": 2,\
|
||||
|
||||
16
usermods/BH1750_v2/platformio_override.ini
Normal file
16
usermods/BH1750_v2/platformio_override.ini
Normal file
@@ -0,0 +1,16 @@
|
||||
; Options
|
||||
; -------
|
||||
; USERMOD_BH1750 - define this to have this user mod included wled00\usermods_list.cpp
|
||||
; USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
; USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL - the min number of milliseconds between measurements, defaults to 500ms
|
||||
; USERMOD_BH1750_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
; USERMOD_BH1750_OFFSET_VALUE - the offset value to report on, defaults to 1
|
||||
;
|
||||
[env:usermod_BH1750_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BH1750
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
claws/BH1750 @ ^1.2.0
|
||||
24
usermods/BH1750_v2/readme.md
Normal file
24
usermods/BH1750_v2/readme.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# BH1750 usermod
|
||||
|
||||
This usermod will read from an ambient light sensor like the BH1750 sensor.
|
||||
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
|
||||
|
||||
## Installation
|
||||
|
||||
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms
|
||||
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:usermod_BH1750_d1_mini`.
|
||||
|
||||
## Change Log
|
||||
177
usermods/BH1750_v2/usermod_bh1750.h
Normal file
177
usermods/BH1750_v2/usermod_bh1750.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include <Wire.h>
|
||||
#include <BH1750.h>
|
||||
|
||||
// the max frequency to check photoresistor, 10 seconds
|
||||
#ifndef USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL 10000
|
||||
#endif
|
||||
|
||||
// the min frequency to check photoresistor, 500 ms
|
||||
#ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500
|
||||
#endif
|
||||
|
||||
// how many seconds after boot to take first measurement, 10 seconds
|
||||
#ifndef USERMOD_BH1750_FIRST_MEASUREMENT_AT
|
||||
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
|
||||
#endif
|
||||
|
||||
// only report if differance grater than offset value
|
||||
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
||||
#define USERMOD_BH1750_OFFSET_VALUE 1
|
||||
#endif
|
||||
|
||||
class Usermod_BH1750 : public Usermod
|
||||
{
|
||||
private:
|
||||
int8_t offset = USERMOD_BH1750_OFFSET_VALUE;
|
||||
|
||||
unsigned long maxReadingInterval = USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL;
|
||||
unsigned long minReadingInterval = USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL;
|
||||
unsigned long lastMeasurement = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
|
||||
unsigned long lastSend = UINT32_MAX - (USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - USERMOD_BH1750_FIRST_MEASUREMENT_AT);
|
||||
// flag to indicate we have finished the first readLightLevel call
|
||||
// allows this library to report to the user how long until the first
|
||||
// measurement
|
||||
bool getLuminanceComplete = false;
|
||||
|
||||
// flag set at startup
|
||||
bool disabled = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _maxReadInterval[];
|
||||
static const char _minReadInterval[];
|
||||
static const char _offset[];
|
||||
|
||||
BH1750 lightMeter;
|
||||
float lastLux = -1000;
|
||||
|
||||
bool checkBoundSensor(float newValue, float prevValue, float maxDiff)
|
||||
{
|
||||
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
Wire.begin();
|
||||
lightMeter.begin();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (disabled || strip.isUpdating())
|
||||
return;
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// check to see if we are due for taking a measurement
|
||||
// lastMeasurement will not be updated until the conversion
|
||||
// is complete the the reading is finished
|
||||
if (now - lastMeasurement < minReadingInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldUpdate = now - lastSend > maxReadingInterval;
|
||||
|
||||
float lux = lightMeter.readLightLevel();
|
||||
lastMeasurement = millis();
|
||||
getLuminanceComplete = true;
|
||||
|
||||
if (shouldUpdate || checkBoundSensor(lux, lastLux, offset))
|
||||
{
|
||||
lastLux = lux;
|
||||
lastSend = millis();
|
||||
if (WLED_MQTT_CONNECTED)
|
||||
{
|
||||
char subuf[45];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/luminance"));
|
||||
mqtt->publish(subuf, 0, true, String(lux).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull())
|
||||
user = root.createNestedObject(F("u"));
|
||||
|
||||
JsonArray lux_json = user.createNestedArray(F("Luminance"));
|
||||
|
||||
if (!getLuminanceComplete)
|
||||
{
|
||||
// if we haven't read the sensor yet, let the user know
|
||||
// that we are still waiting for the first measurement
|
||||
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||
lux_json.add(F(" sec until read"));
|
||||
return;
|
||||
}
|
||||
|
||||
lux_json.add(lastLux);
|
||||
lux_json.add(F(" lx"));
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_BH1750;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
// we add JSON object.
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = !disabled;
|
||||
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||
top[FPSTR(_offset)] = offset;
|
||||
|
||||
DEBUG_PRINTLN(F("Photoresistor config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
// we look for JSON object.
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull())
|
||||
{
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
disabled = !(top[FPSTR(_enabled)] | !disabled);
|
||||
maxReadingInterval = (top[FPSTR(_maxReadInterval)] | maxReadingInterval); // ms
|
||||
minReadingInterval = (top[FPSTR(_minReadInterval)] | minReadingInterval); // ms
|
||||
offset = top[FPSTR(_offset)] | offset;
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char Usermod_BH1750::_name[] PROGMEM = "BH1750";
|
||||
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
|
||||
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
||||
const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms";
|
||||
const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx";
|
||||
14
usermods/BH1750_v2/usermods_list.cpp
Normal file
14
usermods/BH1750_v2/usermods_list.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "wled.h"
|
||||
/*
|
||||
* Register your v2 usermods here!
|
||||
*/
|
||||
#ifdef USERMOD_BH1750
|
||||
#include "../usermods/BH1750_v2/usermod_BH1750.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
#ifdef USERMOD_BH1750
|
||||
usermods.add(new Usermod_BH1750());
|
||||
#endif
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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\"></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";
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* I've had good results with settings around 5 (20 fps).
|
||||
*
|
||||
*/
|
||||
#include "wled.h"
|
||||
|
||||
const uint8_t PCARS_dimcolor = 20;
|
||||
WiFiUDP UDP;
|
||||
const unsigned int PCARS_localUdpPort = 5606; // local port to listen on
|
||||
@@ -49,11 +51,12 @@ void PCARS_readValues() {
|
||||
void PCARS_buildcolorbars() {
|
||||
boolean activated = false;
|
||||
float ledratio = 0;
|
||||
uint16_t totalLen = strip.getLengthTotal();
|
||||
|
||||
for (uint16_t i = 0; i < ledCount; i++) {
|
||||
for (uint16_t i = 0; i < totalLen; i++) {
|
||||
if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) {
|
||||
|
||||
ledratio = (float)i / (float)ledCount;
|
||||
ledratio = (float)i / (float)totalLen;
|
||||
if (ledratio < PCARS_rpmRatio) {
|
||||
activated = true;
|
||||
} else {
|
||||
|
||||
755
usermods/quinled-an-penta/quinled-an-penta.h
Normal file
755
usermods/quinled-an-penta/quinled-an-penta.h
Normal file
@@ -0,0 +1,755 @@
|
||||
#pragma once
|
||||
|
||||
#include "U8g2lib.h"
|
||||
#include "SHT85.h"
|
||||
#include "Wire.h"
|
||||
#include "wled.h"
|
||||
|
||||
class QuinLEDAnPentaUsermod : public Usermod
|
||||
{
|
||||
private:
|
||||
bool enabled = false;
|
||||
bool firstRunDone = false;
|
||||
bool initDone = false;
|
||||
U8G2 *oledDisplay = nullptr;
|
||||
SHT *sht30TempHumidSensor;
|
||||
|
||||
// Network info vars
|
||||
bool networkHasChanged = false;
|
||||
bool lastKnownNetworkConnected;
|
||||
IPAddress lastKnownIp;
|
||||
bool lastKnownWiFiConnected;
|
||||
String lastKnownSsid;
|
||||
bool lastKnownApActive;
|
||||
char *lastKnownApSsid;
|
||||
char *lastKnownApPass;
|
||||
byte lastKnownApChannel;
|
||||
int lastKnownEthType;
|
||||
bool lastKnownEthLinkUp;
|
||||
|
||||
// Brightness / LEDC vars
|
||||
byte lastKnownBri = 0;
|
||||
int8_t currentBussesNumPins[5] = {0, 0, 0, 0, 0};
|
||||
int8_t currentLedPins[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t currentLedcReads[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t lastKnownLedcReads[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
// OLED vars
|
||||
bool oledEnabled = false;
|
||||
bool oledInitDone = false;
|
||||
bool oledUseProgressBars = false;
|
||||
bool oledFlipScreen = false;
|
||||
bool oledFixBuggedScreen = false;
|
||||
byte oledMaxPage = 3;
|
||||
byte oledCurrentPage = 3; // Start with the network page to help identifying the IP
|
||||
byte oledSecondsPerPage = 10;
|
||||
unsigned long oledLogoDrawn = 0;
|
||||
unsigned long oledLastTimeUpdated = 0;
|
||||
unsigned long oledLastTimePageChange = 0;
|
||||
unsigned long oledLastTimeFixBuggedScreen = 0;
|
||||
|
||||
// SHT30 vars
|
||||
bool shtEnabled = false;
|
||||
bool shtInitDone = false;
|
||||
bool shtReadDataSuccess = false;
|
||||
byte shtI2cAddress = 0x44;
|
||||
unsigned long shtLastTimeUpdated = 0;
|
||||
bool shtDataRequested = false;
|
||||
float shtCurrentTemp = 0;
|
||||
float shtLastKnownTemp = 0;
|
||||
float shtCurrentHumidity = 0;
|
||||
float shtLastKnownHumidity = 0;
|
||||
|
||||
// Pin/IO vars
|
||||
const int8_t 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,
|
||||
};
|
||||
69
usermods/quinled-an-penta/readme.md
Normal file
69
usermods/quinled-an-penta/readme.md
Normal 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.
|
||||
@@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
void initRotaryEncoder()
|
||||
{
|
||||
PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, UM_RGBRotaryEncoder)) {
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) {
|
||||
eaIo = -1;
|
||||
ebIo = -1;
|
||||
cleanup();
|
||||
@@ -208,7 +208,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
lastKnownBri = bri;
|
||||
|
||||
updateLeds();
|
||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
}
|
||||
|
||||
// If the brightness is changed not with the rotary, update the rotary
|
||||
@@ -323,7 +323,7 @@ class RgbRotaryEncoderUsermod : public Usermod
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return 0x4711;
|
||||
return USERMOD_RGB_ROTARY_ENCODER;
|
||||
}
|
||||
|
||||
//More methods can be added in the future, this example will then be extended.
|
||||
|
||||
55
usermods/seven_segment_display/readme.md
Normal file
55
usermods/seven_segment_display/readme.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Seven Segment Display
|
||||
|
||||
Usermod that uses the overlay feature to create a configurable seven segment display.
|
||||
This has only been tested on a single configuration. Colon support is entirely untested.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_SEVEN_SEGMENT` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SEVEN_SEGMENT` in `my_config.h`.
|
||||
|
||||
## Settings
|
||||
Settings can be controlled through both the usermod setting page and through MQTT with a raw payload.
|
||||
##### Example
|
||||
Topic ```<mqttDeviceTopic||mqttGroupTopic>/sevenSeg/perSegment/set```
|
||||
Payload ```3```
|
||||
#### perSegment -- ssLEDPerSegment
|
||||
The number of individual LEDs per segment. There are 7 segments per digit.
|
||||
#### perPeriod -- ssLEDPerPeriod
|
||||
The number of individual LEDs per period. A ':' has 2x periods.
|
||||
#### startIdx -- ssStartLED
|
||||
Index of the LED that the display starts at. Allows a seven segment display to be in the middle of a string.
|
||||
#### timeEnable -- ssTimeEnabled
|
||||
When true, when displayMask is configured for a time output and no message is set the time will be displayed.
|
||||
#### scrollSpd -- ssScrollSpeed
|
||||
Time, in milliseconds, between message shifts when the length of displayMsg exceeds the length of the displayMask.
|
||||
#### displayMask -- ssDisplayMask
|
||||
This should represent the configuration of the physical display.
|
||||
<pre>
|
||||
HH - 0-23. hh - 1-12, kk - 1-24 hours
|
||||
MM or mm - 0-59 minutes
|
||||
SS or ss = 0-59 seconds
|
||||
: for a colon
|
||||
All others for alpha numeric, (will be blank when displaying time)
|
||||
</pre>
|
||||
##### Example
|
||||
```HHMMSS ```
|
||||
```hh:MM:SS ```
|
||||
#### displayMsg -- ssDisplayMessage
|
||||
Message to be displayed across the display. If the length exceeds the length of the displayMask the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'.
|
||||
#### displayCfg -- ssDisplayConfig
|
||||
The order that your LEDs are configured. All seven segments in the display need to be wired the same way.
|
||||
<pre>
|
||||
-------
|
||||
/ A / 0 - EDCGFAB
|
||||
/ F / B 1 - EDCBAFG
|
||||
/ / 2 - GCDEFAB
|
||||
------- 3 - GBAFEDC
|
||||
/ G / 4 - FABGEDC
|
||||
/ E / C 5 - FABCDEG
|
||||
/ /
|
||||
-------
|
||||
D
|
||||
</pre>
|
||||
|
||||
## Version
|
||||
20211009 - Initial release
|
||||
@@ -0,0 +1,497 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class SevenSegmentDisplay : public Usermod
|
||||
{
|
||||
|
||||
#define WLED_SS_BUFFLEN 6
|
||||
#define REFRESHTIME 497
|
||||
private:
|
||||
//Runtime variables.
|
||||
unsigned long lastRefresh = 0;
|
||||
unsigned long lastCharacterStep = 0;
|
||||
String ssDisplayBuffer = "";
|
||||
char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B};
|
||||
int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed.
|
||||
bool ssDoDisplayTime = true;
|
||||
int ssVirtualDisplayMessageIdxStart = 0;
|
||||
int ssVirtualDisplayMessageIdxEnd = 0;
|
||||
unsigned long resfreshTime = 497;
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment)
|
||||
int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x
|
||||
int ssStartLED = 0; //The pixel that the display starts at.
|
||||
/* HH - 0-23. hh - 1-12, kk - 1-24 hours
|
||||
// MM or mm - 0-59 minutes
|
||||
// SS or ss = 0-59 seconds
|
||||
// : for a colon
|
||||
// All others for alpha numeric, (will be blank when displaying time)
|
||||
*/
|
||||
String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment.
|
||||
/* ssDisplayConfig
|
||||
// -------
|
||||
// / A / 0 - EDCGFAB
|
||||
// / F / B 1 - EDCBAFG
|
||||
// / / 2 - GCDEFAB
|
||||
// ------- 3 - GBAFEDC
|
||||
// / G / 4 - FABGEDC
|
||||
// / E / C 5 - FABCDEG
|
||||
// / /
|
||||
// -------
|
||||
// D
|
||||
*/
|
||||
int ssDisplayConfig = 5; //Physical configuration of the Seven segment display
|
||||
String ssDisplayMessage = "~";
|
||||
bool ssTimeEnabled = true; //If not, display message.
|
||||
unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds.
|
||||
|
||||
//String to reduce flash memory usage
|
||||
static const char _str_perSegment[];
|
||||
static const char _str_perPeriod[];
|
||||
static const char _str_startIdx[];
|
||||
static const char _str_displayCfg[];
|
||||
static const char _str_timeEnabled[];
|
||||
static const char _str_scrollSpd[];
|
||||
static const char _str_displayMask[];
|
||||
static const char _str_displayMsg[];
|
||||
static const char _str_sevenSeg[];
|
||||
static const char _str_subFormat[];
|
||||
static const char _str_topicFormat[];
|
||||
|
||||
unsigned long _overlaySevenSegmentProcess()
|
||||
{
|
||||
//Do time for now.
|
||||
if (ssDoDisplayTime)
|
||||
{
|
||||
//Format the ssDisplayBuffer based on ssDisplayMask
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int index = 0; index < displayMaskLen; index++)
|
||||
{
|
||||
//Only look for time formatting if there are at least 2 characters left in the buffer.
|
||||
if ((index < displayMaskLen - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1]))
|
||||
{
|
||||
int timeVar = 0;
|
||||
switch (ssDisplayMask[index])
|
||||
{
|
||||
case 'h':
|
||||
timeVar = hourFormat12(localTime);
|
||||
break;
|
||||
case 'H':
|
||||
timeVar = hour(localTime);
|
||||
break;
|
||||
case 'k':
|
||||
timeVar = hour(localTime) + 1;
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
timeVar = minute(localTime);
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
timeVar = second(localTime);
|
||||
break;
|
||||
}
|
||||
|
||||
//Only want to leave a blank in the hour formatting.
|
||||
if ((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10)
|
||||
ssDisplayBuffer[index] = ' ';
|
||||
else
|
||||
ssDisplayBuffer[index] = 0x30 + (timeVar / 10);
|
||||
ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10);
|
||||
|
||||
//Need to increment the index because of the second digit.
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' ');
|
||||
}
|
||||
}
|
||||
return REFRESHTIME;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */
|
||||
|
||||
//Check to see if the message has scrolled completely
|
||||
int len = static_cast<int>(ssDisplayMessage.length());
|
||||
if (ssDisplayMessageIdx > len)
|
||||
{
|
||||
//If it has scrolled the whole message, reset it.
|
||||
setSevenSegmentMessage(ssDisplayMessage);
|
||||
return REFRESHTIME;
|
||||
}
|
||||
//Display message
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int index = 0; index < displayMaskLen; index++)
|
||||
{
|
||||
if (ssDisplayMessageIdx + index < len && ssDisplayMessageIdx + index >= 0)
|
||||
ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx + index];
|
||||
else
|
||||
ssDisplayBuffer[index] = ' ';
|
||||
}
|
||||
|
||||
//Increase the displayed message index to progress it one character if the length exceeds the display length.
|
||||
if (len > displayMaskLen)
|
||||
ssDisplayMessageIdx++;
|
||||
|
||||
return ssScrollSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
void _overlaySevenSegmentDraw()
|
||||
{
|
||||
|
||||
//Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer
|
||||
int indexLED = ssStartLED;
|
||||
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
|
||||
for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++)
|
||||
{
|
||||
if (ssDisplayBuffer[indexBuffer] == 0)
|
||||
break;
|
||||
else if (ssDisplayBuffer[indexBuffer] == '.')
|
||||
{
|
||||
//Won't ever turn off LED lights for a period. (or will we?)
|
||||
indexLED += ssLEDPerPeriod;
|
||||
continue;
|
||||
}
|
||||
else if (ssDisplayBuffer[indexBuffer] == ':')
|
||||
{
|
||||
//Turn off colon if odd second?
|
||||
indexLED += ssLEDPerPeriod * 2;
|
||||
}
|
||||
else if (ssDisplayBuffer[indexBuffer] == ' ')
|
||||
{
|
||||
//Turn off all 7 segments.
|
||||
_overlaySevenSegmentLEDOutput(0, indexLED);
|
||||
indexLED += ssLEDPerSegment * 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Turn off correct segments.
|
||||
_overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED);
|
||||
indexLED += ssLEDPerSegment * 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _overlaySevenSegmentLEDOutput(char mask, int indexLED)
|
||||
{
|
||||
for (char index = 0; index < 7; index++)
|
||||
{
|
||||
if ((mask & (0x40 >> index)) != (0x40 >> index))
|
||||
{
|
||||
for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++)
|
||||
{
|
||||
strip.setPixelColor(indexLED + numPerSeg, 0x000000);
|
||||
}
|
||||
}
|
||||
indexLED += ssLEDPerSegment;
|
||||
}
|
||||
}
|
||||
|
||||
char _overlaySevenSegmentGetCharMask(char var)
|
||||
{
|
||||
if (var >= 0x30 && var <= 0x39)
|
||||
{ /*If its a number, shift to index 0.*/
|
||||
var -= 0x30;
|
||||
}
|
||||
else if (var >= 0x41 && var <= 0x5a)
|
||||
{ /*If its an Upper case, shift to index 0xA.*/
|
||||
var -= 0x37;
|
||||
}
|
||||
else if (var >= 0x61 && var <= 0x7A)
|
||||
{ /*If its a lower case, shift to index 0xA.*/
|
||||
var -= 0x57;
|
||||
}
|
||||
else
|
||||
{ /* Else unsupported, return 0; */
|
||||
return 0;
|
||||
}
|
||||
char mask = ssCharacterMask[static_cast<int>(var)];
|
||||
/*
|
||||
0 - EDCGFAB
|
||||
1 - EDCBAFG
|
||||
2 - GCDEFAB
|
||||
3 - GBAFEDC
|
||||
4 - FABGEDC
|
||||
5 - FABCDEG
|
||||
*/
|
||||
switch (ssDisplayConfig)
|
||||
{
|
||||
case 1:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
|
||||
break;
|
||||
case 2:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
|
||||
break;
|
||||
case 3:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
|
||||
break;
|
||||
case 4:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
break;
|
||||
case 5:
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
|
||||
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
|
||||
break;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n)
|
||||
{
|
||||
/* Move all bits of first set to rightmost side */
|
||||
char set1 = (x >> p1) & ((1U << n) - 1);
|
||||
|
||||
/* Move all bits of second set to rightmost side */
|
||||
char set2 = (x >> p2) & ((1U << n) - 1);
|
||||
|
||||
/* Xor the two sets */
|
||||
char Xor = (set1 ^ set2);
|
||||
|
||||
/* Put the Xor bits back to their original positions */
|
||||
Xor = (Xor << p1) | (Xor << p2);
|
||||
|
||||
/* Xor the 'Xor' with the original number so that the
|
||||
two sets are swapped */
|
||||
char result = x ^ Xor;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _publishMQTTint_P(const char *subTopic, int value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
|
||||
char buffer[64];
|
||||
char valBuffer[12];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
|
||||
sprintf_P(valBuffer, PSTR("%d"), value);
|
||||
mqtt->publish(buffer, 2, true, valBuffer);
|
||||
}
|
||||
|
||||
void _publishMQTTstr_P(const char *subTopic, String Value)
|
||||
{
|
||||
if(mqtt == NULL) return;
|
||||
char buffer[64];
|
||||
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic);
|
||||
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
|
||||
}
|
||||
|
||||
void _updateMQTT()
|
||||
{
|
||||
_publishMQTTint_P(_str_perSegment, ssLEDPerSegment);
|
||||
_publishMQTTint_P(_str_perPeriod, ssLEDPerPeriod);
|
||||
_publishMQTTint_P(_str_startIdx, ssStartLED);
|
||||
_publishMQTTint_P(_str_displayCfg, ssDisplayConfig);
|
||||
_publishMQTTint_P(_str_timeEnabled, ssTimeEnabled);
|
||||
_publishMQTTint_P(_str_scrollSpd, ssScrollSpeed);
|
||||
|
||||
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
|
||||
_publishMQTTstr_P(_str_displayMsg, ssDisplayMessage);
|
||||
}
|
||||
|
||||
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
|
||||
{
|
||||
if (strcmp_P(topic, setting) == 0)
|
||||
{
|
||||
*((int *)value) = strtol(payload, NULL, 10);
|
||||
_publishMQTTint_P(setting, *((int *)value));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _handleSetting(char *topic, char *payload)
|
||||
{
|
||||
if (_cmpIntSetting_P(topic, payload, _str_perSegment, &ssLEDPerSegment))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_perPeriod, &ssLEDPerPeriod))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_startIdx, &ssStartLED))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_displayCfg, &ssDisplayConfig))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &ssTimeEnabled))
|
||||
return true;
|
||||
if (_cmpIntSetting_P(topic, payload, _str_scrollSpd, &ssScrollSpeed))
|
||||
return true;
|
||||
if (strcmp_P(topic, _str_displayMask) == 0)
|
||||
{
|
||||
ssDisplayMask = String(payload);
|
||||
ssDisplayBuffer = ssDisplayMask;
|
||||
_publishMQTTstr_P(_str_displayMask, ssDisplayMask);
|
||||
return true;
|
||||
}
|
||||
if (strcmp_P(topic, _str_displayMsg) == 0)
|
||||
{
|
||||
setSevenSegmentMessage(String(payload));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
void setSevenSegmentMessage(String message)
|
||||
{
|
||||
//If the message isn't blank display it otherwise show time, if enabled.
|
||||
if (message.length() < 1 || message == "~")
|
||||
ssDoDisplayTime = ssTimeEnabled;
|
||||
else
|
||||
ssDoDisplayTime = false;
|
||||
|
||||
//Determine is the message is longer than the display, if it is configure it to scroll the message.
|
||||
if (message.length() > ssDisplayMask.length())
|
||||
ssDisplayMessageIdx = -ssDisplayMask.length();
|
||||
else
|
||||
ssDisplayMessageIdx = 0;
|
||||
|
||||
//If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll)
|
||||
if (!ssDisplayMessage.equals(message))
|
||||
{
|
||||
_publishMQTTstr_P(_str_displayMsg, message);
|
||||
ssDisplayMessage = message;
|
||||
}
|
||||
}
|
||||
//Functions called by WLED
|
||||
|
||||
/*
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
ssDisplayBuffer = ssDisplayMask;
|
||||
}
|
||||
|
||||
/*
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
if (millis() - lastRefresh > resfreshTime)
|
||||
{
|
||||
//In theory overlaySevenSegmentProcess should return the amount of time until it changes next.
|
||||
//So we should be okay to trigger the stripi on every process loop.
|
||||
resfreshTime = _overlaySevenSegmentProcess();
|
||||
lastRefresh = millis();
|
||||
strip.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
_overlaySevenSegmentDraw();
|
||||
}
|
||||
|
||||
void onMqttConnect(bool sessionPresent)
|
||||
{
|
||||
char subBuffer[48];
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
{
|
||||
_updateMQTT();
|
||||
//subscribe for sevenseg messages on the device topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_sevenSeg);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
//subcribe for sevenseg messages on the group topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool onMqttMessage(char *topic, char *payload)
|
||||
{
|
||||
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
|
||||
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
|
||||
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
|
||||
topic += topicPrefixLen;
|
||||
else
|
||||
return false;
|
||||
//We only care if the topic ends with /set
|
||||
size_t topicLen = strlen(topic);
|
||||
if (topicLen > 4 &&
|
||||
topic[topicLen - 4] == '/' &&
|
||||
topic[topicLen - 3] == 's' &&
|
||||
topic[topicLen - 2] == 'e' &&
|
||||
topic[topicLen - 1] == 't')
|
||||
{
|
||||
//Trim /set and handle it
|
||||
topic[topicLen - 4] = '\0';
|
||||
_handleSetting(topic, payload);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_str_sevenSeg)];
|
||||
if (top.isNull())
|
||||
{
|
||||
top = root.createNestedObject(FPSTR(_str_sevenSeg));
|
||||
}
|
||||
top[FPSTR(_str_perSegment)] = ssLEDPerSegment;
|
||||
top[FPSTR(_str_perPeriod)] = ssLEDPerPeriod;
|
||||
top[FPSTR(_str_startIdx)] = ssStartLED;
|
||||
top[FPSTR(_str_displayMask)] = ssDisplayMask;
|
||||
top[FPSTR(_str_displayCfg)] = ssDisplayConfig;
|
||||
top[FPSTR(_str_displayMsg)] = ssDisplayMessage;
|
||||
top[FPSTR(_str_timeEnabled)] = ssTimeEnabled;
|
||||
top[FPSTR(_str_scrollSpd)] = ssScrollSpeed;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_str_sevenSeg)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
//if sevenseg section doesn't exist return
|
||||
if (!configComplete)
|
||||
return configComplete;
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_perSegment)], ssLEDPerSegment);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_perPeriod)], ssLEDPerPeriod);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_startIdx)], ssStartLED);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayMask)], ssDisplayMask);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayCfg)], ssDisplayConfig);
|
||||
|
||||
String newDisplayMessage;
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_displayMsg)], newDisplayMessage);
|
||||
setSevenSegmentMessage(newDisplayMessage);
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_timeEnabled)], ssTimeEnabled);
|
||||
configComplete &= getJsonValue(top[FPSTR(_str_scrollSpd)], ssScrollSpeed);
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_SEVEN_SEGMENT_DISPLAY;
|
||||
}
|
||||
};
|
||||
|
||||
const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment";
|
||||
const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod";
|
||||
const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx";
|
||||
const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg";
|
||||
const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled";
|
||||
const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd";
|
||||
const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask";
|
||||
const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg";
|
||||
const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg";
|
||||
16
wled00/FX.h
16
wled00/FX.h
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"></i></div><div class="l e-label" id="h-cfg"></div>
|
||||
<div class="btn save"><div class="b-icon"><i class="icons"></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"></i></div><div class="l e-label" id="e-nw"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-hw"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-ui"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-if"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-tm"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-dx"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-sr"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></i></div><div class="l e-label" id="e-um"></div></div>
|
||||
<div class="entry"><div class="e-icon"><i class="icons"></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>
|
||||
@@ -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();
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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}"> </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}"> </div>
|
||||
</div>`;
|
||||
f.insertAdjacentHTML("beforeend", cn);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
1551
wled00/html_ui.h
1551
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
244
wled00/improv.cpp
Normal file
244
wled00/improv.cpp
Normal 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();
|
||||
}
|
||||
@@ -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\":["));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
118
wled00/set.cpp
118
wled00/set.cpp
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]) + ",";
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user