Merge branch 'main' of https://github.com/aircoookie/WLED into audioreactive-prototype

This commit is contained in:
Blaz Kristan
2022-10-22 10:36:04 +02:00
41 changed files with 1885 additions and 696 deletions

View File

@@ -1,5 +1,11 @@
## WLED changelog
### WLED release 0.13.3
- Version bump to v0.13.3 "Toki"
- Disable ESP watchdog by default (fixes flickering and boot issues on a fresh install)
- Added support for LPD6803
### WLED release 0.13.2
#### Build 2208140

View File

@@ -115,6 +115,8 @@ build_flags =
-D DECODE_LG=true
-DWLED_USE_MY_CONFIG
; -D USERMOD_SENSORSTOMQTT
#For ADS1115 sensor uncomment following
; -D USERMOD_ADS1115
build_unflags =
@@ -177,6 +179,9 @@ lib_deps =
; adafruit/Adafruit BMP280 Library @ 2.1.0
; adafruit/Adafruit CCS811 Library @ 1.0.4
; adafruit/Adafruit Si7021 Library @ 1.4.0
#For ADS1115 sensor uncomment following
; adafruit/Adafruit BusIO @ 1.13.2
; adafruit/Adafruit ADS1X15 @ 2.4.0
extra_scripts = ${scripts_defaults.extra_scripts}

View File

@@ -0,0 +1,15 @@
#include "wled.h"
namespace ADS1115
{
struct ChannelSettings {
const String settingName;
bool isEnabled;
String name;
String units;
const uint16_t mux;
float multiplier;
float offset;
uint8_t decimals;
};
}

View File

@@ -0,0 +1,10 @@
# ADS1115 16-Bit ADC with four inputs
This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI.
Configuration is all completed via the Usermod menu. There are no settings to set in code!
## Installation
Add the build flag `-D USERMOD_ADS1115` to your platformio environment.
Uncomment libraries with comment `#For ADS1115 sensor uncomment following`

View File

@@ -0,0 +1,255 @@
#pragma once
#include "wled.h"
#include <Adafruit_ADS1X15.h>
#include <math.h>
#include "ChannelSettings.h"
using namespace ADS1115;
class ADS1115Usermod : public Usermod {
public:
void setup() {
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V
if (!ads.begin()) {
Serial.println("Failed to initialize ADS");
return;
}
if (!initChannel()) {
isInitialized = true;
return;
}
startReading();
isEnabled = true;
isInitialized = true;
}
void loop() {
if (isEnabled && millis() - lastTime > loopInterval) {
lastTime = millis();
// If we don't have new data, skip this iteration.
if (!ads.conversionComplete()) {
return;
}
updateResult();
moveToNextChannel();
startReading();
}
}
void addToJsonInfo(JsonObject& root)
{
if (!isEnabled) {
return;
}
JsonObject user = root[F("u")];
if (user.isNull()) user = root.createNestedObject(F("u"));
for (uint8_t i = 0; i < channelsCount; i++) {
ChannelSettings* settingsPtr = &(channelSettings[i]);
if (!settingsPtr->isEnabled) {
continue;
}
JsonArray lightArr = user.createNestedArray(settingsPtr->name); //name
float value = round((readings[i] + settingsPtr->offset) * settingsPtr->multiplier, settingsPtr->decimals);
lightArr.add(value); //value
lightArr.add(" " + settingsPtr->units); //unit
}
}
void addToConfig(JsonObject& root)
{
JsonObject top = root.createNestedObject(F("ADC ADS1115"));
for (uint8_t i = 0; i < channelsCount; i++) {
ChannelSettings* settingsPtr = &(channelSettings[i]);
JsonObject channel = top.createNestedObject(settingsPtr->settingName);
channel[F("Enabled")] = settingsPtr->isEnabled;
channel[F("Name")] = settingsPtr->name;
channel[F("Units")] = settingsPtr->units;
channel[F("Multiplier")] = settingsPtr->multiplier;
channel[F("Offset")] = settingsPtr->offset;
channel[F("Decimals")] = settingsPtr->decimals;
}
top[F("Loop Interval")] = loopInterval;
}
bool readFromConfig(JsonObject& root)
{
JsonObject top = root[F("ADC ADS1115")];
bool configComplete = !top.isNull();
bool hasEnabledChannels = false;
for (uint8_t i = 0; i < channelsCount && configComplete; i++) {
ChannelSettings* settingsPtr = &(channelSettings[i]);
JsonObject channel = top[settingsPtr->settingName];
configComplete &= !channel.isNull();
configComplete &= getJsonValue(channel[F("Enabled")], settingsPtr->isEnabled);
configComplete &= getJsonValue(channel[F("Name")], settingsPtr->name);
configComplete &= getJsonValue(channel[F("Units")], settingsPtr->units);
configComplete &= getJsonValue(channel[F("Multiplier")], settingsPtr->multiplier);
configComplete &= getJsonValue(channel[F("Offset")], settingsPtr->offset);
configComplete &= getJsonValue(channel[F("Decimals")], settingsPtr->decimals);
hasEnabledChannels |= settingsPtr->isEnabled;
}
configComplete &= getJsonValue(top[F("Loop Interval")], loopInterval);
isEnabled = isInitialized && configComplete && hasEnabledChannels;
return configComplete;
}
uint16_t getId()
{
return USERMOD_ID_ADS1115;
}
private:
static const uint8_t channelsCount = 8;
ChannelSettings channelSettings[channelsCount] = {
{
"Differential reading from AIN0 (P) and AIN1 (N)",
false,
"Differential AIN0 AIN1",
"V",
ADS1X15_REG_CONFIG_MUX_DIFF_0_1,
1,
0,
3
},
{
"Differential reading from AIN0 (P) and AIN3 (N)",
false,
"Differential AIN0 AIN3",
"V",
ADS1X15_REG_CONFIG_MUX_DIFF_0_3,
1,
0,
3
},
{
"Differential reading from AIN1 (P) and AIN3 (N)",
false,
"Differential AIN1 AIN3",
"V",
ADS1X15_REG_CONFIG_MUX_DIFF_1_3,
1,
0,
3
},
{
"Differential reading from AIN2 (P) and AIN3 (N)",
false,
"Differential AIN2 AIN3",
"V",
ADS1X15_REG_CONFIG_MUX_DIFF_2_3,
1,
0,
3
},
{
"Single-ended reading from AIN0",
false,
"Single-ended AIN0",
"V",
ADS1X15_REG_CONFIG_MUX_SINGLE_0,
1,
0,
3
},
{
"Single-ended reading from AIN1",
false,
"Single-ended AIN1",
"V",
ADS1X15_REG_CONFIG_MUX_SINGLE_1,
1,
0,
3
},
{
"Single-ended reading from AIN2",
false,
"Single-ended AIN2",
"V",
ADS1X15_REG_CONFIG_MUX_SINGLE_2,
1,
0,
3
},
{
"Single-ended reading from AIN3",
false,
"Single-ended AIN3",
"V",
ADS1X15_REG_CONFIG_MUX_SINGLE_3,
1,
0,
3
},
};
float readings[channelsCount] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned long loopInterval = 1000;
unsigned long lastTime = 0;
Adafruit_ADS1115 ads;
uint8_t activeChannel;
bool isEnabled = false;
bool isInitialized = false;
static float round(float value, uint8_t decimals) {
return roundf(value * powf(10, decimals)) / powf(10, decimals);
}
bool initChannel() {
for (uint8_t i = 0; i < channelsCount; i++) {
if (channelSettings[i].isEnabled) {
activeChannel = i;
return true;
}
}
activeChannel = 0;
return false;
}
void moveToNextChannel() {
uint8_t oldActiveChannel = activeChannel;
do
{
if (++activeChannel >= channelsCount){
activeChannel = 0;
}
}
while (!channelSettings[activeChannel].isEnabled && oldActiveChannel != activeChannel);
}
void startReading() {
ads.startADCReading(channelSettings[activeChannel].mux, /*continuous=*/false);
}
void updateResult() {
int16_t results = ads.getLastConversionResults();
readings[activeChannel] = ads.computeVolts(results);
}
};

View File

@@ -0,0 +1,244 @@
#pragma once
#include "wled.h"
/*
* Usermod for analog clock
*/
extern Timezone* tz;
class AnalogClockUsermod : public Usermod {
private:
static constexpr uint32_t refreshRate = 50; // per second
static constexpr uint32_t refreshDelay = 1000 / refreshRate;
struct Segment {
// config
int16_t firstLed = 0;
int16_t lastLed = 59;
int16_t centerLed = 0;
// runtime
int16_t size;
Segment() {
update();
}
void validateAndUpdate() {
if (firstLed < 0 || firstLed >= strip.getLengthTotal() ||
lastLed < firstLed || lastLed >= strip.getLengthTotal()) {
*this = {};
return;
}
if (centerLed < firstLed || centerLed > lastLed) {
centerLed = firstLed;
}
update();
}
void update() {
size = lastLed - firstLed + 1;
}
};
// configuration (available in API and stored in flash)
bool enabled = false;
Segment mainSegment;
uint32_t hourColor = 0x0000FF;
uint32_t minuteColor = 0x00FF00;
bool secondsEnabled = true;
Segment secondsSegment;
uint32_t secondColor = 0xFF0000;
bool blendColors = true;
uint16_t secondsEffect = 0;
// runtime
bool initDone = false;
uint32_t lastOverlayDraw = 0;
void validateAndUpdate() {
mainSegment.validateAndUpdate();
secondsSegment.validateAndUpdate();
if (secondsEffect < 0 || secondsEffect > 1) {
secondsEffect = 0;
}
}
int16_t adjustToSegment(double progress, Segment const& segment) {
int16_t led = segment.centerLed + progress * segment.size;
return led > segment.lastLed
? segment.firstLed + led - segment.lastLed - 1
: led;
}
void setPixelColor(uint16_t n, uint32_t c) {
if (!blendColors) {
strip.setPixelColor(n, c);
} else {
uint32_t oldC = strip.getPixelColor(n);
strip.setPixelColor(n, qadd32(oldC, c));
}
}
String colorToHexString(uint32_t c) {
char buffer[9];
sprintf(buffer, "%06X", c);
return buffer;
}
bool hexStringToColor(String const& s, uint32_t& c, uint32_t def) {
errno = 0;
char* ep;
unsigned long long r = strtoull(s.c_str(), &ep, 16);
if (*ep == 0 && errno != ERANGE) {
c = r;
return true;
} else {
c = def;
return false;
}
}
void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) {
uint32_t ms = time.ms % 1000;
uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2;
setPixelColor(secondLed, gamma32(scale32(secondColor, b0)));
uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2;
setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1)));
}
static inline uint32_t qadd32(uint32_t c1, uint32_t c2) {
return RGBW32(
qadd8(R(c1), R(c2)),
qadd8(G(c1), G(c2)),
qadd8(B(c1), B(c2)),
qadd8(W(c1), W(c2))
);
}
static inline uint32_t scale32(uint32_t c, fract8 scale) {
return RGBW32(
scale8(R(c), scale),
scale8(G(c), scale),
scale8(B(c), scale),
scale8(W(c), scale)
);
}
static inline int16_t dec(int16_t n, int16_t i, Segment const& seg) {
return n - seg.firstLed >= i
? n - i
: seg.lastLed - seg.firstLed - i + n + 1;
}
static inline int16_t inc(int16_t n, int16_t i, Segment const& seg) {
int16_t r = n + i;
if (r > seg.lastLed) {
return seg.firstLed + n - seg.lastLed;
}
return r;
}
public:
AnalogClockUsermod() {
}
void setup() override {
initDone = true;
validateAndUpdate();
}
void loop() override {
if (millis() - lastOverlayDraw > refreshDelay) {
strip.trigger();
}
}
void handleOverlayDraw() override {
if (!enabled) {
return;
}
lastOverlayDraw = millis();
auto time = toki.getTime();
auto localSec = tz ? tz->toLocal(time.sec) : time.sec;
double secondP = second(localSec) / 60.0;
double minuteP = minute(localSec) / 60.0;
double hourP = (hour(localSec) % 12) / 12.0 + minuteP / 12.0;
if (secondsEnabled) {
int16_t secondLed = adjustToSegment(secondP, secondsSegment);
switch (secondsEffect) {
case 0: // no effect
setPixelColor(secondLed, secondColor);
break;
case 1: // fading seconds
secondsEffectSineFade(secondLed, time);
break;
}
// TODO: move to secondsTrailEffect
// for (uint16_t i = 1; i < secondsTrail + 1; ++i) {
// uint16_t trailLed = dec(secondLed, i, secondsSegment);
// uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1);
// setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright)));
// }
}
setPixelColor(adjustToSegment(minuteP, mainSegment), minuteColor);
setPixelColor(adjustToSegment(hourP, mainSegment), hourColor);
}
void addToConfig(JsonObject& root) override {
validateAndUpdate();
JsonObject top = root.createNestedObject("Analog Clock");
top["Overlay Enabled"] = enabled;
top["First LED (Main Ring)"] = mainSegment.firstLed;
top["Last LED (Main Ring)"] = mainSegment.lastLed;
top["Center/12h LED (Main Ring)"] = mainSegment.centerLed;
top["Hour Color (RRGGBB)"] = colorToHexString(hourColor);
top["Minute Color (RRGGBB)"] = colorToHexString(minuteColor);
top["Show Seconds"] = secondsEnabled;
top["First LED (Seconds Ring)"] = secondsSegment.firstLed;
top["Last LED (Seconds Ring)"] = secondsSegment.lastLed;
top["Center/12h LED (Seconds Ring)"] = secondsSegment.centerLed;
top["Second Color (RRGGBB)"] = colorToHexString(secondColor);
top["Seconds Effect (0-1)"] = secondsEffect;
top["Blend Colors"] = blendColors;
}
bool readFromConfig(JsonObject& root) override {
JsonObject top = root["Analog Clock"];
bool configComplete = !top.isNull();
String color;
configComplete &= getJsonValue(top["Overlay Enabled"], enabled, false);
configComplete &= getJsonValue(top["First LED (Main Ring)"], mainSegment.firstLed, 0);
configComplete &= getJsonValue(top["Last LED (Main Ring)"], mainSegment.lastLed, 59);
configComplete &= getJsonValue(top["Center/12h LED (Main Ring)"], mainSegment.centerLed, 0);
configComplete &= getJsonValue(top["Hour Color (RRGGBB)"], color, "0000FF") && hexStringToColor(color, hourColor, 0x0000FF);
configComplete &= getJsonValue(top["Minute Color (RRGGBB)"], color, "00FF00") && hexStringToColor(color, minuteColor, 0x00FF00);
configComplete &= getJsonValue(top["Show Seconds"], secondsEnabled, true);
configComplete &= getJsonValue(top["First LED (Seconds Ring)"], secondsSegment.firstLed, 0);
configComplete &= getJsonValue(top["Last LED (Seconds Ring)"], secondsSegment.lastLed, 59);
configComplete &= getJsonValue(top["Center/12h LED (Seconds Ring)"], secondsSegment.centerLed, 0);
configComplete &= getJsonValue(top["Second Color (RRGGBB)"], color, "FF0000") && hexStringToColor(color, secondColor, 0xFF0000);
configComplete &= getJsonValue(top["Seconds Effect (0-1)"], secondsEffect, 0);
configComplete &= getJsonValue(top["Blend Colors"], blendColors, true);
if (initDone) {
validateAndUpdate();
}
return configComplete;
}
uint16_t getId() override {
return USERMOD_ID_ANALOG_CLOCK;
}
};

View File

@@ -1,16 +0,0 @@
; Options
; -------
; USERMOD_BH1750 - define this to have this user mod included wled00\usermods_list.cpp
; USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - the max number of milliseconds between measurements, defaults to 10000ms
; USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL - the min number of milliseconds between measurements, defaults to 500ms
; USERMOD_BH1750_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
; USERMOD_BH1750_OFFSET_VALUE - the offset value to report on, defaults to 1
;
[env:usermod_BH1750_d1_mini]
extends = env:d1_mini
build_flags =
${common.build_flags_esp8266}
-D USERMOD_BH1750
lib_deps =
${env.lib_deps}
claws/BH1750 @ ^1.2.0

View File

@@ -3,22 +3,47 @@
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
## Dependencies
- Libraries
- `claws/BH1750 @^1.2.0`
- This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`).
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
## Compiliation
### Define Your Options
To enable, compile with `USERMOD_BH1750` defined (e.g. in `platformio_override.ini`)
```ini
[env:usermod_BH1750_d1_mini]
extends = env:d1_mini
build_flags =
${common.build_flags_esp8266}
-D USERMOD_BH1750
lib_deps =
${esp8266.lib_deps}
claws/BH1750 @ ^1.2.0
```
* `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
### Configuration Options
The following settings can be set at compile-time but are configurable on the usermod menu (except First Measurement time):
* `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_OFFSET_VALUE` - the offset value to report on, defaults to 1
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
All parameters can be configured at runtime using Usermods settings page.
In addition, on the Usermod screen allows you to:
- enable/disable the usermod
- Enable Home Assistant Discovery of usermod
- Configure the SCL/SDA pins
### 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`.
## API
The following method is available to interact with the usermod from other code modules:
- `getIlluminance` read the brightness from the sensor
## Change Log
Jul 2022
- Added Home Assistant Discovery
- Implemented PinManager to register pins
- Made pins configurable in usermod menu
- Added API call to read illuminance from other modules
- Enhanced info-screen outputs
- Updated `readme.md`

View File

@@ -1,3 +1,6 @@
// force the compiler to show a warning to confirm that this file is included
#warning **** Included USERMOD_BH1750 ****
#pragma once
#include "wled.h"
@@ -47,6 +50,24 @@ private:
static const char _maxReadInterval[];
static const char _minReadInterval[];
static const char _offset[];
static const char _HomeAssistantDiscovery[];
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
#endif
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false;
bool sensorFound = false;
// Home Assistant and MQTT
String mqttLuminanceTopic = F("");
bool mqttInitialized = false;
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages
BH1750 lightMeter;
float lastLux = -1000;
@@ -55,12 +76,59 @@ private:
{
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
}
// set up Home Assistant discovery entries
void _mqttInitialize()
{
mqttLuminanceTopic = String(mqttDeviceTopic) + F("/brightness");
if (HomeAssistantDiscovery) _createMqttSensor(F("Brightness"), mqttLuminanceTopic, F("Illuminance"), F(" lx"));
}
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
{
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
StaticJsonDocument<600> doc;
doc[F("name")] = String(serverDescription) + F(" ") + name;
doc[F("state_topic")] = topic;
doc[F("unique_id")] = String(mqttClientID) + name;
if (unitOfMeasurement != "")
doc[F("unit_of_measurement")] = unitOfMeasurement;
if (deviceClass != "")
doc[F("device_class")] = deviceClass;
doc[F("expire_after")] = 1800;
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
device[F("name")] = serverDescription;
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
device[F("manufacturer")] = F("WLED");
device[F("model")] = F("FOSS");
device[F("sw_version")] = versionString;
String temp;
serializeJson(doc, temp);
DEBUG_PRINTLN(t);
DEBUG_PRINTLN(temp);
mqtt->publish(t.c_str(), 0, true, temp.c_str());
}
public:
void setup()
{
Wire.begin();
lightMeter.begin();
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used
PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) return;
Wire.begin(ioPin[1], ioPin[0]);
sensorFound = lightMeter.begin();
initDone = true;
}
void loop()
@@ -90,18 +158,25 @@ public:
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());
if (!mqttInitialized)
{
_mqttInitialize();
mqttInitialized = true;
}
mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str());
DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx"));
}
else
{
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data"));
}
}
}
inline float getIlluminance() {
return (float)lastLux;
}
void addToJsonInfo(JsonObject &root)
{
JsonObject user = root[F("u")];
@@ -109,28 +184,23 @@ public:
user = root.createNestedObject(F("u"));
JsonArray lux_json = user.createNestedArray(F("Luminance"));
if (!getLuminanceComplete)
{
if (!sensorFound) {
// if no sensor
lux_json.add(F("BH1750 "));
lux_json.add(F("Not Found"));
} else 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;
// 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;
} else {
lux_json.add(lastLux);
lux_json.add(F(" lx"));
}
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
*/
// (called from set.cpp) stores persistent properties to cfg.json
void addToConfig(JsonObject &root)
{
// we add JSON object.
@@ -138,35 +208,68 @@ public:
top[FPSTR(_enabled)] = !disabled;
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
top[FPSTR(_minReadInterval)] = minReadingInterval;
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
top[FPSTR(_offset)] = offset;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("Photoresistor config saved."));
DEBUG_PRINTLN(F("BH1750 config saved."));
}
/**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*/
// called before setup() to populate properties from values stored in cfg.json
bool readFromConfig(JsonObject &root)
{
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
// we look for JSON object.
JsonObject top = root[FPSTR(_name)];
if (top.isNull())
{
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINT(F("BH1750"));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top[FPSTR(_enabled)], disabled, false);
configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, 10000); //ms
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false);
configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
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."));
if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BH1750;
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
return configComplete;
}
uint16_t getId()
{
return USERMOD_ID_BH1750;
}
};
// strings to reduce flash memory usage (used more than twice)
@@ -174,4 +277,5 @@ 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::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscoveryLux";
const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx";

View File

@@ -1,14 +0,0 @@
#include "wled.h"
/*
* Register your v2 usermods here!
*/
#ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h"
#endif
void registerUsermods()
{
#ifdef USERMOD_BH1750
usermods.add(new Usermod_BH1750());
#endif
}

View File

@@ -6,12 +6,13 @@
; USERMOD_DHT_CELSIUS - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
; USERMOD_DHT_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
; USERMOD_DHT_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
; USERMOD_DHT_MQTT - publish measurements to the MQTT broker
; USERMOD_DHT_STATS - For debug, report delay stats
[env:d1_mini_usermod_dht_C]
extends = env:d1_mini
build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS
lib_deps = ${env.lib_deps}
lib_deps = ${env:d1_mini.lib_deps}
https://github.com/alwynallan/DHT_nonblocking
[env:custom32_LEDPIN_16_usermod_dht_C]

View File

@@ -1,10 +1,14 @@
# DHT Temperature/Humidity sensor usermod
This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor.
The sensor readings are displayed in the Info section of the web UI.
The sensor readings are displayed in the Info section of the web UI (and optionally send to a MQTT broker).
If sensor is not detected after a while (10 update intervals), this usermod will be disabled.
If enabled measured temperature and humidity will be published to the following MQTT topics
* `{devceTopic}/dht/temperature`
* `{devceTopic}/dht/humidity`
## Installation
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
@@ -17,6 +21,7 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds
* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
* `USERMOD_DHT_MQTT` - publish measurements to the MQTT broker
* `USERMOD_DHT_STATS` - For debug, report delay stats
## Project link
@@ -29,13 +34,15 @@ If you are using `platformio_override.ini`, you should be able to refresh the ta
## Change Log
2022-10-15
* Add possibility to publish sensor readings to an MQTT broker
* fix compilation error for sample [env:d1_mini_usermod_dht_C] task
2020-02-04
* Change default QuinLed pin to Q2
* Instead of trying to keep updates at constant cadence, space readings out by measurement interval; hope this helps to avoid occasional bursts of readings with errors
* Add some more (optional) stats
2020-02-03
* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking
* The new library serializes/delays up to 5ms for the sensor readout
2020-02-02
* The new library serializes/delays up to 5ms for the sensor readout
2020-02-02
* Created

View File

@@ -62,6 +62,10 @@ class UsermodDHT : public Usermod {
float humidity, temperature = 0;
bool initializing = true;
bool disabled = false;
#ifdef USERMOD_DHT_MQTT
char dhtMqttTopic[64];
size_t dhtMqttTopicLen;
#endif
#ifdef USERMOD_DHT_STATS
unsigned long nextResetStatsTime = 0;
uint16_t updates = 0;
@@ -76,6 +80,10 @@ class UsermodDHT : public Usermod {
void setup() {
nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
lastReadTime = millis();
#ifdef USERMOD_DHT_MQTT
sprintf(dhtMqttTopic, "%s/dht", mqttDeviceTopic);
dhtMqttTopicLen = strlen(dhtMqttTopic);
#endif
#ifdef USERMOD_DHT_STATS
nextResetStatsTime = millis() + 60*60*1000;
#endif
@@ -110,10 +118,29 @@ class UsermodDHT : public Usermod {
temperature = tempC * 9 / 5 + 32;
#endif
#ifdef USERMOD_DHT_MQTT
// 10^n where n is number of decimal places to display in mqtt message. Please adjust buff size together with this constant
#define FLOAT_PREC 100
if (WLED_MQTT_CONNECTED) {
char buff[10];
strcpy(dhtMqttTopic + dhtMqttTopicLen, "/temperature");
sprintf(buff, "%d.%d", (int)temperature, ((int)(temperature * FLOAT_PREC)) % FLOAT_PREC);
mqtt->publish(dhtMqttTopic, 0, false, buff);
sprintf(buff, "%d.%d", (int)humidity, ((int)(humidity * FLOAT_PREC)) % FLOAT_PREC);
strcpy(dhtMqttTopic + dhtMqttTopicLen, "/humidity");
mqtt->publish(dhtMqttTopic, 0, false, buff);
dhtMqttTopic[dhtMqttTopicLen] = '\0';
}
#undef FLOAT_PREC
#endif
nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL;
lastReadTime = millis();
initializing = false;
#ifdef USERMOD_DHT_STATS
unsigned long icalc = millis() - currentIteration;
if (icalc > maxIteration) {
@@ -134,7 +161,7 @@ class UsermodDHT : public Usermod {
dcalc = millis() - dcalc;
if (dcalc > maxDelay) {
maxDelay = dcalc;
}
}
#endif
if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) {
@@ -207,7 +234,7 @@ class UsermodDHT : public Usermod {
temp.add("°F");
#endif
}
uint16_t getId()
{
return USERMOD_ID_DHT;

View File

@@ -5,13 +5,12 @@ It can be useful for kitchen strips to avoid any touches.
- on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros)
- brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode.
Configure brightness by changing distance to the sensor (see parameters below for customization).
"macroLongPress" is also called here.
## Installation
## Installation
1. Attach VL53L0X sensor to i2c pins according to default pins for your board.
2. Add `-D USERMOD_VL53L0X_GESTURES` to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
In my case, for example: `build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES`
In my case, for example: `build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES`
3. Add "pololu/VL53L0X" dependency below to `lib_deps` like this:
```ini
lib_deps = ${env.lib_deps}
@@ -21,15 +20,10 @@ lib_deps = ${env.lib_deps}
My entire `platformio_override.ini` for example (for nodemcu board):
```ini
[platformio]
default_envs = nodemcu
default_envs = nodemcuv2
[env:nodemcu]
board = nodemcu
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES
[env:nodemcuv2]
build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES
lib_deps = ${env.lib_deps}
pololu/VL53L0X @ ^1.3.0
pololu/VL53L0X @ ^1.3.0
```

View File

@@ -3,14 +3,13 @@
* It can be useful for kitchen strips to avoid any touches.
* - on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros)
* - brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode.
* Configure brightness by changing distance to the sensor (see parameters below for customization).
* "macroLongPress" is also called here.
Configure brightness by changing distance to the sensor (see parameters below for customization).
*
* Enabling this mod usermod:
* Enabling this usermod:
* 1. Attach VL53L0X sensor to i2c pins according to default pins for your board.
* 2. Add "-D USERMOD_VL53L0X_GESTURES" to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
* In my case, for example: build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES
* 3. Add "pololu/VL53L0X" dependency to lib_deps like this:
* 2. Add `-D USERMOD_VL53L0X_GESTURES` to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
* In my case, for example: `build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES`
* 3. Add "pololu/VL53L0X" dependency below to `lib_deps` like this:
* lib_deps = ${env.lib_deps}
* pololu/VL53L0X @ ^1.3.0
*/
@@ -22,19 +21,19 @@
#include <VL53L0X.h>
#ifndef VL53L0X_MAX_RANGE_MM
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimeters to react for motions
#endif
#ifndef VL53L0X_MIN_RANGE_OFFSET
#define VL53L0X_MIN_RANGE_OFFSET 60 // minimal range in millimiters that sensor can detect. Used in long motions to correct brightnes calculation.
#define VL53L0X_MIN_RANGE_OFFSET 60 // minimal range in millimeters that sensor can detect. Used in long motions to correct brightness calculation.
#endif
#ifndef VL53L0X_DELAY_MS
#define VL53L0X_DELAY_MS 100 // how often to get data from sensor
#define VL53L0X_DELAY_MS 100 // how often to get data from sensor
#endif
#ifndef VL53L0X_LONG_MOTION_DELAY_MS
#define VL53L0X_LONG_MOTION_DELAY_MS 1000 // how often to get data from sensor
#define VL53L0X_LONG_MOTION_DELAY_MS 1000 // switch onto "long motion" action after this delay
#endif
class UsermodVL53L0XGestures : public Usermod {
@@ -47,7 +46,7 @@ class UsermodVL53L0XGestures : public Usermod {
bool wasMotionBefore = false;
bool isLongMotion = false;
unsigned long motionStartTime = 0;
public:
void setup() {
@@ -72,40 +71,34 @@ class UsermodVL53L0XGestures : public Usermod {
lastTime = millis();
int range = sensor.readRangeSingleMillimeters();
DEBUG_PRINTF(F("range: %d, brightness: %d"), range, bri);
DEBUG_PRINTF("range: %d, brightness: %d\r\n", range, bri);
if (range < VL53L0X_MAX_RANGE_MM)
{
if (!wasMotionBefore)
{
motionStartTime = millis();
DEBUG_PRINTF(F("motionStartTime: %d"), motionStartTime);
DEBUG_PRINTF("motionStartTime: %d\r\n", motionStartTime);
}
wasMotionBefore = true;
if (millis() - motionStartTime > VL53L0X_LONG_MOTION_DELAY_MS) //long motion
{
DEBUG_PRINTF(F("long motion: %d"), motionStartTime);
DEBUG_PRINTF("long motion: %d\r\n", motionStartTime);
if (!isLongMotion)
{
if (macroLongPress)
{
applyMacro(macroLongPress);
}
isLongMotion = true;
}
// set brightness according to range
bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET);
DEBUG_PRINTF(F("new brightness: %d"), bri);
DEBUG_PRINTF("new brightness: %d", bri);
stateUpdated(1);
}
} else if (wasMotionBefore) { //released
long dur = millis() - motionStartTime;
if (!isLongMotion)
{ //short press
DEBUG_PRINTF(F("shortPressAction..."));
DEBUG_PRINTLN(F("shortPressAction..."));
shortPressAction();
}
wasMotionBefore = false;

View File

@@ -36,7 +36,7 @@ lib_deps =
AsyncTCP@1.0.3
Esp Async WebServer@1.2.0
IRremoteESP8266@2.7.3
I2Cdevlib-MPU6050@fbde122cc5
jrowberg/I2Cdevlib-MPU6050@^1.0.0
```
## Wiring
@@ -78,7 +78,7 @@ to the info object
## Usermod installation
1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory.
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
Example **usermods_list.cpp**:

View File

@@ -384,7 +384,7 @@ public:
if (!umSSDRDisplayTime || strip.isUpdating()) {
return;
}
#ifdef USERMOD_ID_SN_PHOTORESISTOR
#ifdef USERMOD_SN_PHOTORESISTOR
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
if (ptr != nullptr) {
uint16_t lux = ptr->getLastLDRValue();

View File

@@ -0,0 +1,61 @@
# Smartnest
This usermod-v2 modification allows integration with `smartnest.cz` service which provides MQTT integration with voice assistants.
In order to setup Smartnest follow the [documentation](https://www.docu.smartnest.cz/).
## MQTT API
The API is described in the Smartnest [Github repo](https://github.com/aososam/Smartnest/blob/master/Devices/lightRgb/lightRgb.ino).
## Usermod installation
1. Register the usermod by adding `#include "../usermods/smartnest/usermod_smartnest.h"` at the top and `usermods.add(new Smartnest());` at the bottom of `usermods_list.cpp`.
or
2. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini
Example **usermods_list.cpp**:
```cpp
#include "wled.h"
/*
* Register your v2 usermods here!
* (for v1 usermods using just usermod.cpp, you can ignore this file)
*/
/*
* Add/uncomment your usermod filename here (and once more below)
* || || ||
* \/ \/ \/
*/
//#include "usermod_v2_example.h"
//#include "usermod_temperature.h"
#include "../usermods/usermod_smartnest.h"
void registerUsermods()
{
/*
* Add your usermod class name here
* || || ||
* \/ \/ \/
*/
//usermods.add(new MyExampleUsermod());
//usermods.add(new UsermodTemperature());
usermods.add(new Smartnest());
}
```
## Configuration
Usermod has no configuration but relies on the MQTT configuration.\
Under Config > Sync Interfaces > MQTT:
* Enable MQTT check box
* Set the `Broker` field to: `smartnest.cz`
* The `Username` and `Password` fields are the login information from the `smartnest.cz` website.
* `Client ID` field is obtained from the device configuration panel in `smartnest.cz`.
## Change log
2022-09
* First implementation.

View File

@@ -0,0 +1,167 @@
#pragma once
#include "wled.h"
class Smartnest : public Usermod
{
private:
void sendToBroker(const char *const topic, const char *const message)
{
if (!WLED_MQTT_CONNECTED)
{
return;
}
String topic_ = String(mqttClientID) + "/" + String(topic);
mqtt->publish(topic_.c_str(), 0, true, message);
}
void turnOff()
{
setBrightness(0);
turnOnAtBoot = false;
offMode = true;
sendToBroker("report/powerState", "OFF");
}
void turnOn()
{
setBrightness(briLast);
turnOnAtBoot = true;
offMode = false;
sendToBroker("report/powerState", "ON");
}
void setBrightness(int value)
{
if (value == 0 && bri > 0) briLast = bri;
bri = value;
stateUpdated(CALL_MODE_DIRECT_CHANGE);
}
void setColor(int r, int g, int b)
{
strip.setColor(0, r, g, b);
stateUpdated(CALL_MODE_DIRECT_CHANGE);
char msg[18] {};
sprintf(msg, "rgb(%d,%d,%d)", r, g, b);
sendToBroker("report/color", msg);
}
int splitColor(const char *const color, int * const rgb)
{
char *color_ = NULL;
const char delim[] = ",";
char *cxt = NULL;
char *token = NULL;
int position = 0;
// We need to copy the string in order to keep it read only as strtok_r function requires mutable string
color_ = (char *)malloc(strlen(color));
if (NULL == color_) {
return -1;
}
strcpy(color_, color);
token = strtok_r(color_, delim, &cxt);
while (token != NULL)
{
rgb[position++] = (int)strtoul(token, NULL, 10);
token = strtok_r(NULL, delim, &cxt);
}
free(color_);
return position;
}
public:
// Functions called by WLED
/**
* handling of MQTT message
* topic should look like: /<mqttClientID>/<Command>/<Message>
*/
bool onMqttMessage(char *topic, char *message)
{
String topic_{topic};
String topic_prefix{mqttClientID + String("/directive/")};
if (!topic_.startsWith(topic_prefix))
{
return false;
}
String subtopic = topic_.substring(topic_prefix.length());
String message_(message);
if (subtopic == "powerState")
{
if (strcmp(message, "ON") == 0)
{
turnOn();
}
else if (strcmp(message, "OFF") == 0)
{
turnOff();
}
return true;
}
if (subtopic == "percentage")
{
int val = (int)strtoul(message, NULL, 10);
if (val >= 0 && val <= 100)
{
setBrightness(map(val, 0, 100, 0, 255));
}
return true;
}
if (subtopic == "color")
{
// Parse the message which is in the format "rgb(<0-255>,<0-255>,<0-255>)"
int rgb[3] = {};
String colors = message_.substring(String("rgb(").length(), message_.lastIndexOf(')'));
if (3 != splitColor(colors.c_str(), rgb))
{
return false;
}
setColor(rgb[0], rgb[1], rgb[2]);
return true;
}
return false;
}
/**
* subscribe to MQTT topic and send publish current status.
*/
void onMqttConnect(bool sessionPresent)
{
String topic = String(mqttClientID) + "/#";
mqtt->subscribe(topic.c_str(), 0);
sendToBroker("report/online", (bri ? "true" : "false")); // Reports that the device is online
delay(100);
sendToBroker("report/firmware", versionString); // Reports the firmware version
delay(100);
sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the ip
delay(100);
sendToBroker("report/network", (char *)WiFi.SSID().c_str()); // Reports the network name
delay(100);
String signal(WiFi.RSSI(), 10);
sendToBroker("report/signal", signal.c_str()); // Reports the signal strength
delay(100);
}
/**
* 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_SMARTNEST;
}
};

View File

@@ -0,0 +1,8 @@
# Ping Pong LED Clock
This Usermod File contains a modification to use WLED in combination with the Ping Pong Ball LED Clock as built in [Instructables](https://www.instructables.com/Ping-Pong-Ball-LED-Clock/).
## Installation
To install this Usermod you instruct PlatformIO to compile the Projekt with the USERMOD_PING_PONG_CLOCK flag. WLED then automatically provides you with various settings in the Usermod Page to configure this Usermod.
Note: If your clock is bigger or smaller then mine, you may have to update the led indices for the indivdual numbers and the base indices.

View File

@@ -0,0 +1,119 @@
#pragma once
#include "wled.h"
class PingPongClockUsermod : public Usermod
{
private:
// Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0;
bool colonOn = true;
// ---- Variables modified by settings below -----
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
bool pingPongClockEnabled = true;
int colorR = 0xFF;
int colorG = 0xFF;
int colorB = 0xFF;
// ---- Variables for correct LED numbering below, edit only if your clock is built different ----
int baseH = 43; // Adress for the one place of the hours
int baseHH = 7; // Adress for the tens place of the hours
int baseM = 133; // Adress for the one place of the minutes
int baseMM = 97; // Adress for the tens place of the minutes
int colon1 = 79; // Adress for the first colon led
int colon2 = 80; // Adress for the second colon led
// Matrix for the illumination of the numbers
// Note: These only define the increments of the base adress. e.g. to define the second Minute you have to add the baseMM to every led position
const int numbers[10][10] =
{
{ 0, 1, 4, 6, 13, 15, 18, 19, -1, -1 }, // 0: null
{ 13, 14, 15, 18, 19, -1, -1, -1, -1, -1 }, // 1: eins
{ 0, 4, 5, 6, 13, 14, 15, 19, -1, -1 }, // 2: zwei
{ 4, 5, 6, 13, 14, 15, 18, 19, -1, -1 }, // 3: drei
{ 1, 4, 5, 14, 15, 18, 19, -1, -1, -1 }, // 4: vier
{ 1, 4, 5, 6, 13, 14, 15, 18, -1, -1 }, // 5: fünf
{ 0, 5, 6, 10, 13, 14, 15, 18, -1, -1 }, // 6: sechs
{ 4, 6, 9, 13, 14, 19, -1, -1, -1, -1 }, // 7: sieben
{ 0, 1, 4, 5, 6, 13, 14, 15, 18, 19 }, // 8: acht
{ 1, 4, 5, 6, 9, 13, 14, 19, -1, -1 } // 9: neun
};
public:
void setup()
{ }
void loop()
{
if (millis() - lastTime > 1000)
{
lastTime = millis();
colonOn = !colonOn;
}
}
void addToJsonInfo(JsonObject& root)
{
JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u");
JsonArray lightArr = user.createNestedArray("Uhrzeit-Anzeige"); //name
lightArr.add(pingPongClockEnabled ? "aktiv" : "inaktiv"); //value
lightArr.add(""); //unit
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject("Ping Pong Clock");
top["enabled"] = pingPongClockEnabled;
top["colorR"] = colorR;
top["colorG"] = colorG;
top["colorB"] = colorB;
}
bool readFromConfig(JsonObject &root)
{
JsonObject top = root["Ping Pong Clock"];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top["enabled"], pingPongClockEnabled);
configComplete &= getJsonValue(top["colorR"], colorR);
configComplete &= getJsonValue(top["colorG"], colorG);
configComplete &= getJsonValue(top["colorB"], colorB);
return configComplete;
}
void drawNumber(int base, int number)
{
for(int i = 0; i < 10; i++)
{
if(numbers[number][i] > -1)
strip.setPixelColor(numbers[number][i] + base, RGBW32(colorR, colorG, colorB, 0));
}
}
void handleOverlayDraw()
{
if(pingPongClockEnabled){
if(colonOn)
{
strip.setPixelColor(colon1, RGBW32(colorR, colorG, colorB, 0));
strip.setPixelColor(colon2, RGBW32(colorR, colorG, colorB, 0));
}
drawNumber(baseHH, (hour(localTime) / 10) % 10);
drawNumber(baseH, hour(localTime) % 10);
drawNumber(baseM, (minute(localTime) / 10) % 10);
drawNumber(baseMM, minute(localTime) % 10);
}
}
uint16_t getId()
{
return USERMOD_ID_PING_PONG_CLOCK;
}
};

View File

@@ -8,6 +8,16 @@ active: enable/disable usermod
diplayItIs: enable/disable display of "Es ist" on the clock
ledOffset: number of LEDs before the wordclock LEDs
### Update for alternatative wiring pattern
Based on this fantastic work I added an alternative wiring pattern.
For original you have to use a long wire to connect DO - DI from first line to the next line.
I wired my clock in meander style. So the first LED in second line is in the right.
With this problem every second line was inverted and showed the wrong letter.
I added a switch in usermod called "meander wiring?" to enable/disable alternativ wiring pattern.
## Installation
Copy and update the example `platformio_override.ini.sample`
@@ -24,4 +34,6 @@ No special requirements.
## Change Log
2022/03/30 initial commit
2022/08/18 added meander wiring pattern.
2022/03/30 initial commit

View File

@@ -24,15 +24,19 @@ class WordClockUsermod : public Usermod
bool usermodActive = false;
bool displayItIs = false;
int ledOffset = 100;
bool meander = false;
// defines for mask sizes
#define maskSizeLeds 114
#define maskSizeMinutes 12
#define maskSizeMinutesMea 12
#define maskSizeHours 6
#define maskSizeHoursMea 6
#define maskSizeItIs 5
#define maskSizeMinuteDots 4
// "minute" masks
// Normal wiring
const int maskMinutes[12][maskSizeMinutes] =
{
{107, 108, 109, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00
@@ -49,7 +53,25 @@ class WordClockUsermod : public Usermod
{ 7, 8, 9, 10, 33, 34, 35, -1, -1, -1, -1, -1} // :55 fünf vor
};
// Meander wiring
const int maskMinutesMea[12][maskSizeMinutesMea] =
{
{ 99, 100, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // :00
{ 7, 8, 9, 10, 33, 34, 35, 36, -1, -1, -1, -1}, // :05 fünf nach
{ 18, 19, 20, 21, 33, 34, 35, 36, -1, -1, -1, -1}, // :10 zehn nach
{ 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1}, // :15 viertel
{ 11, 12, 13, 14, 15, 16, 17, 33, 34, 35, 36, -1}, // :20 zwanzig nach
{ 7, 8, 9, 10, 41, 42, 43, 44, 45, 46, 47, -1}, // :25 fünf vor halb
{ 44, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, // :30 halb
{ 7, 8, 9, 10, 33, 34, 35, 36, 44, 45, 46, 47}, // :35 fünf nach halb
{ 11, 12, 13, 14, 15, 16, 17, 41, 42, 43, -1, -1}, // :40 zwanzig vor
{ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1}, // :45 dreiviertel
{ 18, 19, 20, 21, 41, 42, 43, -1, -1, -1, -1, -1}, // :50 zehn vor
{ 7, 8, 9, 10, 41, 42, 43, -1, -1, -1, -1, -1} // :55 fünf vor
};
// hour masks
// Normal wiring
const int maskHours[13][maskSizeHours] =
{
{ 55, 56, 57, -1, -1, -1}, // 01: ein
@@ -66,6 +88,23 @@ class WordClockUsermod : public Usermod
{ 49, 50, 51, -1, -1, -1}, // 11: elf
{ 94, 95, 96, 97, 98, -1} // 12: zwölf and 00: null
};
// Meander wiring
const int maskHoursMea[13][maskSizeHoursMea] =
{
{ 63, 64, 65, -1, -1, -1}, // 01: ein
{ 62, 63, 64, 65, -1, -1}, // 01: eins
{ 55, 56, 57, 58, -1, -1}, // 02: zwei
{ 66, 67, 68, 69, -1, -1}, // 03: drei
{ 73, 74, 75, 76, -1, -1}, // 04: vier
{ 51, 52, 53, 54, -1, -1}, // 05: fünf
{ 83, 84, 85, 86, 87, -1}, // 06: sechs
{ 88, 89, 90, 91, 92, 93}, // 07: sieben
{ 77, 78, 79, 80, -1, -1}, // 08: acht
{103, 104, 105, 106, -1, -1}, // 09: neun
{106, 107, 108, 109, -1, -1}, // 10: zehn
{ 49, 50, 51, -1, -1, -1}, // 11: elf
{ 94, 95, 96, 97, 98, -1} // 12: zwölf and 00: null
};
// mask "it is"
const int maskItIs[maskSizeItIs] = {0, 1, 3, 4, 5};
@@ -128,14 +167,24 @@ class WordClockUsermod : public Usermod
}
// update led mask
if (meander)
{
updateLedMask(maskHoursMea[index], maskSizeHoursMea);
} else {
updateLedMask(maskHours[index], maskSizeHours);
}
}
// set minutes
void setMinutes(int index)
{
// update led mask
if (meander)
{
updateLedMask(maskMinutesMea[index], maskSizeMinutesMea);
} else {
updateLedMask(maskMinutes[index], maskSizeMinutes);
}
}
// set minutes dot
@@ -360,6 +409,7 @@ class WordClockUsermod : public Usermod
top["active"] = usermodActive;
top["displayItIs"] = displayItIs;
top["ledOffset"] = ledOffset;
top["Meander wiring?"] = meander;
}
/*
@@ -389,6 +439,7 @@ class WordClockUsermod : public Usermod
configComplete &= getJsonValue(top["active"], usermodActive);
configComplete &= getJsonValue(top["displayItIs"], displayItIs);
configComplete &= getJsonValue(top["ledOffset"], ledOffset);
configComplete &= getJsonValue(top["Meander wiring?"], meander);
return configComplete;
}

View File

@@ -300,6 +300,29 @@ void handleButton()
if (analog) lastRead = now;
}
// If enabled, RMT idle level is set to HIGH when off
// to prevent leakage current when using an N-channel MOSFET to toggle LED power
#ifdef ESP32_DATA_IDLE_HIGH
void esp32RMTInvertIdle()
{
bool idle_out;
for (uint8_t u = 0; u < busses.getNumBusses(); u++)
{
if (u > 7) return; // only 8 RMT channels, TODO: ESP32 variants have less RMT channels
Bus *bus = busses.getBus(u);
if (!bus || bus->getLength()==0 || !IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType())) continue;
//assumes that bus number to rmt channel mapping stays 1:1
rmt_channel_t ch = static_cast<rmt_channel_t>(u);
rmt_idle_level_t lvl;
rmt_get_idle_level(ch, &idle_out, &lvl);
if (lvl == RMT_IDLE_LEVEL_HIGH) lvl = RMT_IDLE_LEVEL_LOW;
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
else continue;
rmt_set_idle_level(ch, idle_out, lvl);
}
}
#endif
void handleIO()
{
handleButton();
@@ -310,6 +333,9 @@ void handleIO()
lastOnTime = millis();
if (offMode)
{
#ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle();
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);
digitalWrite(rlyPin, rlyMde);
@@ -328,6 +354,9 @@ void handleIO()
digitalWrite(LED_BUILTIN, HIGH);
}
#endif
#ifdef ESP32_DATA_IDLE_HIGH
esp32RMTInvertIdle();
#endif
if (rlyPin>=0) {
pinMode(rlyPin, OUTPUT);
digitalWrite(rlyPin, !rlyMde);

View File

@@ -359,8 +359,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(notifyAlexa, if_sync_send["va"]);
CJSON(notifyHue, if_sync_send["hue"]);
CJSON(notifyMacro, if_sync_send["macro"]);
CJSON(notifyTwice, if_sync_send[F("twice")]);
CJSON(syncGroups, if_sync_send["grp"]);
if (if_sync_send[F("twice")]) udpNumRetries = 1; // import setting from 0.13 and earlier
CJSON(udpNumRetries, if_sync_send["ret"]);
JsonObject if_nodes = interfaces["nodes"];
CJSON(nodeListEnabled, if_nodes[F("list")]);
@@ -807,8 +808,8 @@ void serializeConfig() {
if_sync_send["va"] = notifyAlexa;
if_sync_send["hue"] = notifyHue;
if_sync_send["macro"] = notifyMacro;
if_sync_send[F("twice")] = notifyTwice;
if_sync_send["grp"] = syncGroups;
if_sync_send["ret"] = udpNumRetries;
JsonObject if_nodes = interfaces.createNestedObject("nodes");
if_nodes[F("list")] = nodeListEnabled;

View File

@@ -91,7 +91,11 @@
#define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h"
#define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h"
#define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h
#define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h"
#define USERMOD_ID_SMARTNEST 31 //Usermod "usermod_smartnest.h"
#define USERMOD_ID_AUDIOREACTIVE 32 //Usermod "audioreactive.h"
#define USERMOD_ID_ANALOG_CLOCK 33 //Usermod "Analog_Clock.h"
#define USERMOD_ID_PING_PONG_CLOCK 34 //Usermod "usermod_v2_ping_pong_clock.h"
#define USERMOD_ID_ADS1115 35 //Usermod "usermod_ads1115.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -215,7 +219,7 @@
#define BTN_TYPE_ANALOG_INVERTED 8
//Ethernet board types
#define WLED_NUM_ETH_TYPES 8
#define WLED_NUM_ETH_TYPES 9
#define WLED_ETH_NONE 0
#define WLED_ETH_WT32_ETH01 1

View File

@@ -127,7 +127,7 @@ Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
Send Alexa notifications: <input type="checkbox" name="SA"><br>
Send Philips Hue change notifications: <input type="checkbox" name="SH"><br>
Send Macro notifications: <input type="checkbox" name="SM"><br>
Send notifications twice: <input type="checkbox" name="S2"><br>
UDP packet retransmissions: <input name="UR" type="number" min="0" max="30" class="d5" required><br><br>
<i>Reboot required to apply changes. </i>
<h3>Instance List</h3>
Enable instance list: <input type="checkbox" name="NL"><br>

View File

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

View File

@@ -94,6 +94,7 @@
<option value="2">ESP32-POE</option>
<option value="6">ESP32Deux</option>
<option value="7">KIT-VE</option>
<option value="8">QuinLED-Dig-Octa</option>
<option value="4">QuinLED-ESP32</option>
<option value="5">TwilightLord-ESP32</option>
<option value="3">WESP32</option>

File diff suppressed because it is too large Load Diff

View File

@@ -117,6 +117,7 @@ void publishMqtt()
if (!WLED_MQTT_CONNECTED) return;
DEBUG_PRINTLN(F("Publish MQTT"));
#ifndef USERMOD_SMARTNEST
char s[10];
char subuf[38];
@@ -139,6 +140,7 @@ void publishMqtt()
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/v"));
mqtt->publish(subuf, 0, false, apires); // do not retain message
#endif
}
@@ -166,9 +168,11 @@ bool initMqtt()
mqtt->setClientId(mqttClientID);
if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass);
#ifndef USERMOD_SMARTNEST
strlcpy(mqttStatusTopic, mqttDeviceTopic, 33);
strcat_P(mqttStatusTopic, PSTR("/status"));
mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message
#endif
mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME);
mqtt->connect();
return true;

View File

@@ -93,7 +93,17 @@ const ethernet_settings ethernetBoards[] = {
18, // eth_mdio,
ETH_PHY_IP101, // eth_type,
ETH_CLOCK_GPIO0_IN // eth_clk_mode
}
},
// QuinLed-Dig-Octa Brainboard-32-8L and LilyGO-T-ETH-POE
{
0, // eth_address,
-1, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO17_OUT // eth_clk_mode
},
};
#endif

View File

@@ -31,7 +31,8 @@ Timezone* tz;
#define TZ_HAWAII 18
#define TZ_NOVOSIBIRSK 19
#define TZ_ANCHORAGE 20
#define TZ_MX_CENTRAL 21
#define TZ_MX_CENTRAL 21
#define TZ_PAKISTAN 22
#define TZ_INIT 255
byte tzCurrent = TZ_INIT; //uninitialized
@@ -147,6 +148,11 @@ void updateTimezone() {
tcrStandard = {Last, Sun, Oct, 2, -360}; //CST = UTC - 6 hours
break;
}
case TZ_PAKISTAN : {
tcrDaylight = {Last, Sun, Mar, 1, 300}; //Pakistan Standard Time = UTC + 5 hours
tcrStandard = tcrDaylight;
break;
}
}
tzCurrent = currentTimezone;

View File

@@ -56,6 +56,7 @@ enum struct PinOwner : uint8_t {
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"
UM_BME280 = USERMOD_ID_BME280, // 0x18 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
UM_BH1750 = USERMOD_ID_BH1750, // 0x19 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod "audio_reactive.h"
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@@ -247,7 +247,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
notifyAlexa = request->hasArg(F("SA"));
notifyHue = request->hasArg(F("SH"));
notifyMacro = request->hasArg(F("SM"));
notifyTwice = request->hasArg(F("S2"));
t = request->arg(F("UR")).toInt();
if ((t>=0) && (t<30)) udpNumRetries = t;
nodeListEnabled = request->hasArg(F("NL"));
if (!nodeListEnabled) Nodes.clear();

View File

@@ -138,7 +138,7 @@ void notify(byte callMode, bool followUp)
notifierUdp.endPacket();
notificationSentCallMode = callMode;
notificationSentTime = millis();
notificationTwoRequired = (followUp)? false:notifyTwice;
notificationCount = followUp ? notificationCount + 1 : 0;
}
void realtimeLock(uint32_t timeoutMs, byte md)
@@ -207,7 +207,7 @@ void handleNotifications()
IPAddress localIP;
//send second notification if enabled
if(udpConnected && notificationTwoRequired && millis()-notificationSentTime > 250){
if(udpConnected && (notificationCount < udpNumRetries) && ((millis()-notificationSentTime) > 250)){
notify(notificationSentCallMode,true);
}

View File

@@ -12,39 +12,46 @@
//#include "../usermods/EXAMPLE_v2/usermod_v2_example.h"
#ifdef USERMOD_BATTERY_STATUS_BASIC
#include "../usermods/battery_status_basic/usermod_v2_battery_status_basic.h"
#include "../usermods/battery_status_basic/usermod_v2_battery_status_basic.h"
#endif
#ifdef USERMOD_DALLASTEMPERATURE
#include "../usermods/Temperature/usermod_temperature.h"
#include "../usermods/Temperature/usermod_temperature.h"
#endif
#ifdef USERMOD_SN_PHOTORESISTOR
#include "../usermods/SN_Photoresistor/usermod_sn_photoresistor.h"
#include "../usermods/SN_Photoresistor/usermod_sn_photoresistor.h"
#endif
#ifdef USERMOD_PWM_FAN
#include "../usermods/PWM_fan/usermod_PWM_fan.h"
#include "../usermods/PWM_fan/usermod_PWM_fan.h"
#endif
#ifdef USERMOD_BUZZER
#include "../usermods/buzzer/usermod_v2_buzzer.h"
#include "../usermods/buzzer/usermod_v2_buzzer.h"
#endif
#ifdef USERMOD_SENSORSTOMQTT
#include "../usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h"
#include "../usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h"
#endif
#ifdef USERMOD_PIRSWITCH
#include "../usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h"
#include "../usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h"
#endif
#ifdef USERMOD_MODE_SORT
#include "../usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h"
#include "../usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h"
#endif
#ifdef USERMOD_BH1750
#include "../usermods/BH1750_v2/usermod_BH1750.h"
#endif
// BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h
#ifdef USERMOD_BME280
#include "../usermods/BME280_v2/usermod_bme280.h"
#include "../usermods/BME280_v2/usermod_bme280.h"
#endif
#ifdef USERMOD_FOUR_LINE_DISPLAY
#ifdef USE_ALT_DISPlAY
#include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h"
@@ -52,6 +59,7 @@
#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h"
#endif
#endif
#ifdef USERMOD_ROTARY_ENCODER_UI
#ifdef USE_ALT_DISPlAY
#include "../usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h"
@@ -59,41 +67,41 @@
#include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h"
#endif
#endif
#ifdef USERMOD_AUTO_SAVE
#include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h"
#include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h"
#endif
#ifdef USERMOD_DHT
#include "../usermods/DHT/usermod_dht.h"
#include "../usermods/DHT/usermod_dht.h"
#endif
#ifdef USERMOD_VL53L0X_GESTURES
#include <Wire.h> //it's needed here to correctly resolve dependencies
#include "../usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h"
#endif
#ifdef USERMOD_ANIMATED_STAIRCASE
#include "../usermods/Animated_Staircase/Animated_Staircase.h"
#include "../usermods/Animated_Staircase/Animated_Staircase.h"
#endif
#ifdef USERMOD_MULTI_RELAY
#include "../usermods/multi_relay/usermod_multi_relay.h"
#include "../usermods/multi_relay/usermod_multi_relay.h"
#endif
#ifdef USERMOD_RTC
#include "../usermods/RTC/usermod_rtc.h"
#include "../usermods/RTC/usermod_rtc.h"
#endif
#ifdef USERMOD_ELEKSTUBE_IPS
#include "../usermods/EleksTube_IPS/usermod_elekstube_ips.h"
#include "../usermods/EleksTube_IPS/usermod_elekstube_ips.h"
#endif
#ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR
#include "../usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h"
#include "../usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h"
#endif
#ifdef RGB_ROTARY_ENCODER
#include "../usermods/rgb-rotary-encoder/rgb-rotary-encoder.h"
#include "../usermods/rgb-rotary-encoder/rgb-rotary-encoder.h"
#endif
#ifdef USERMOD_ST7789_DISPLAY
@@ -101,41 +109,57 @@
#endif
#ifdef USERMOD_SEVEN_SEGMENT
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#endif
#ifdef USERMOD_SSDR
#include "../usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h"
#include "../usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h"
#endif
#ifdef USERMOD_CRONIXIE
#include "../usermods/Cronixie/usermod_cronixie.h"
#include "../usermods/Cronixie/usermod_cronixie.h"
#endif
#ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
#ifdef USERMOD_WIZLIGHTS
#include "../usermods/wizlights/wizlights.h"
#include "../usermods/wizlights/wizlights.h"
#endif
#ifdef USERMOD_WORDCLOCK
#include "../usermods/usermod_v2_word_clock/usermod_v2_word_clock.h"
#include "../usermods/usermod_v2_word_clock/usermod_v2_word_clock.h"
#endif
#ifdef USERMOD_MY9291
#include "../usermods/MY9291/usermode_MY9291.h"
#include "../usermods/MY9291/usermode_MY9291.h"
#endif
#ifdef USERMOD_SI7021_MQTT_HA
#include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h"
#include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h"
#endif
#ifdef USERMOD_SMARTNEST
#include "../usermods/smartnest/usermod_smartnest.h"
#endif
#ifdef USERMOD_AUDIOREACTIVE
#include "../usermods/audioreactive/audio_reactive.h"
#endif
#ifdef USERMOD_ANALOG_CLOCK
#include "../usermods/Analog_Clock/Analog_Clock.h"
#endif
#ifdef USERMOD_PING_PONG_CLOCK
#include "../usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h"
#endif
#ifdef USERMOD_ADS1115
#include "../usermods/ADS1115_v2/usermod_ads1115.h"
#endif
void registerUsermods()
{
/*
@@ -144,82 +168,90 @@ void registerUsermods()
* \/ \/ \/
*/
//usermods.add(new MyExampleUsermod());
#ifdef USERMOD_BATTERY_STATUS_BASIC
usermods.add(new UsermodBatteryBasic());
#endif
#ifdef USERMOD_DALLASTEMPERATURE
usermods.add(new UsermodTemperature());
#endif
#ifdef USERMOD_SN_PHOTORESISTOR
usermods.add(new Usermod_SN_Photoresistor());
#endif
#ifdef USERMOD_PWM_FAN
usermods.add(new PWMFanUsermod());
#endif
#ifdef USERMOD_BUZZER
usermods.add(new BuzzerUsermod());
#endif
#ifdef USERMOD_BH1750
usermods.add(new Usermod_BH1750());
#endif
#ifdef USERMOD_BME280
usermods.add(new UsermodBME280());
#endif
#ifdef USERMOD_SENSORSTOMQTT
usermods.add(new UserMod_SensorsToMQTT());
#endif
#ifdef USERMOD_PIRSWITCH
usermods.add(new PIRsensorSwitch());
#endif
#ifdef USERMOD_MODE_SORT
usermods.add(new ModeSortUsermod());
#endif
#ifdef USERMOD_FOUR_LINE_DISPLAY
usermods.add(new FourLineDisplayUsermod());
#endif
#ifdef USERMOD_ROTARY_ENCODER_UI
usermods.add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY
#endif
#ifdef USERMOD_AUTO_SAVE
usermods.add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY
#endif
#ifdef USERMOD_DHT
usermods.add(new UsermodDHT());
#endif
#ifdef USERMOD_VL53L0X_GESTURES
usermods.add(new UsermodVL53L0XGestures());
#endif
#ifdef USERMOD_ANIMATED_STAIRCASE
usermods.add(new Animated_Staircase());
#endif
#ifdef USERMOD_MULTI_RELAY
usermods.add(new MultiRelay());
#endif
#ifdef USERMOD_RTC
usermods.add(new RTCUsermod());
#endif
#ifdef USERMOD_ELEKSTUBE_IPS
usermods.add(new ElekstubeIPSUsermod());
#endif
#ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR
usermods.add(new RotaryEncoderBrightnessColor());
#endif
#ifdef RGB_ROTARY_ENCODER
usermods.add(new RgbRotaryEncoderUsermod());
#endif
#ifdef USERMOD_ST7789_DISPLAY
usermods.add(new St7789DisplayUsermod());
#endif
@@ -227,19 +259,19 @@ void registerUsermods()
#ifdef USERMOD_SEVEN_SEGMENT
usermods.add(new SevenSegmentDisplay());
#endif
#ifdef USERMOD_SSDR
usermods.add(new UsermodSSDR());
#endif
#ifdef USERMOD_CRONIXIE
usermods.add(new UsermodCronixie());
#endif
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif
#ifdef USERMOD_WIZLIGHTS
usermods.add(new WizLightsUsermod());
#endif
@@ -247,7 +279,7 @@ void registerUsermods()
#ifdef USERMOD_WORDCLOCK
usermods.add(new WordClockUsermod());
#endif
#ifdef USERMOD_MY9291
usermods.add(new MY9291Usermod());
#endif
@@ -255,8 +287,24 @@ void registerUsermods()
#ifdef USERMOD_SI7021_MQTT_HA
usermods.add(new Si7021_MQTT_HA());
#endif
#ifdef USERMOD_SMARTNEST
usermods.add(new Smartnest());
#endif
#ifdef USERMOD_AUDIOREACTIVE
usermods.add(new AudioReactive());
#endif
#ifdef USERMOD_ANALOG_CLOCK
usermods.add(new AnalogClockUsermod());
#endif
#ifdef USERMOD_PING_PONG_CLOCK
usermods.add(new PingPongClockUsermod());
#endif
#ifdef USERMOD_ADS1115
usermods.add(new ADS1115Usermod());
#endif
}

View File

@@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2210181
#define VERSION 2210220
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -354,7 +354,7 @@ WLED_GLOBAL bool notifyButton _INIT(false); // send if upd
WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa
WLED_GLOBAL bool notifyMacro _INIT(false); // send notification for macro
WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes
WLED_GLOBAL bool notifyTwice _INIT(false); // notifications use UDP: enable if devices don't sync reliably
WLED_GLOBAL uint8_t udpNumRetries _INIT(0); // Number of times a UDP sync message is retransmitted. Increase to increase reliability
WLED_GLOBAL bool alexaEnabled _INIT(false); // enable device discovery by Amazon Echo
WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand
@@ -505,7 +505,7 @@ WLED_GLOBAL bool notifyDirectDefault _INIT(notifyDirect);
WLED_GLOBAL bool receiveNotifications _INIT(true);
WLED_GLOBAL unsigned long notificationSentTime _INIT(0);
WLED_GLOBAL byte notificationSentCallMode _INIT(CALL_MODE_INIT);
WLED_GLOBAL bool notificationTwoRequired _INIT(false);
WLED_GLOBAL uint8_t notificationCount _INIT(0);
// effects
WLED_GLOBAL byte effectCurrent _INIT(0);

View File

@@ -102,7 +102,7 @@ void loadSettingsFromEEPROM()
busses.add(bc);
notifyButton = EEPROM.read(230);
notifyTwice = EEPROM.read(231);
if (EEPROM.read(231)) udpNumRetries = 1;
buttonType[0] = EEPROM.read(232) ? BTN_TYPE_PUSH : BTN_TYPE_NONE;
staticIP[0] = EEPROM.read(234);

View File

@@ -37,17 +37,22 @@ void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t
return;
}
if (!index) {
request->_tempFile = WLED_FS.open(filename, "w");
String finalname = filename;
if (finalname.charAt(0) != '/') {
finalname = "/" + finalname; // prepend slash if missing
}
request->_tempFile = WLED_FS.open(finalname, "w");
DEBUG_PRINT("Uploading ");
DEBUG_PRINTLN(filename);
if (filename == F("/presets.json")) presetsModifiedTime = toki.second();
DEBUG_PRINTLN(finalname);
if (finalname == "/presets.json") presetsModifiedTime = toki.second();
}
if (len) {
request->_tempFile.write(data,len);
}
if (final) {
request->_tempFile.close();
if (filename == F("/cfg.json")) {
if (filename == "/cfg.json") {
doReboot = true;
request->send(200, "text/plain", F("Configuration restore successful.\nRebooting..."));
} else

View File

@@ -489,7 +489,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("SB"),notifyButton);
sappend('c',SET_F("SH"),notifyHue);
sappend('c',SET_F("SM"),notifyMacro);
sappend('c',SET_F("S2"),notifyTwice);
sappend('v',SET_F("UR"),udpNumRetries);
sappend('c',SET_F("NL"),nodeListEnabled);
sappend('c',SET_F("NB"),nodeBroadcastEnabled);