Merge branch 'mdev' into ES8388
This commit is contained in:
@@ -2,13 +2,12 @@
|
||||
// #warning **** Included USERMOD_BH1750 ****
|
||||
|
||||
#ifndef WLED_ENABLE_MQTT
|
||||
#error "This user mod requires MQTT to be enabled."
|
||||
#warning "This user mod expects MQTT to be enabled."
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // WLEDMM: make sure that I2C drivers have the "right" Wire Object
|
||||
#include <Wire.h>
|
||||
|
||||
#include "wled.h"
|
||||
#include <BH1750.h>
|
||||
@@ -20,7 +19,8 @@
|
||||
|
||||
// the min frequency to check photoresistor, 500 ms
|
||||
#ifndef USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL
|
||||
#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500
|
||||
//#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 500
|
||||
#define USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL 2500 // WLEDMM this makes more sense
|
||||
#endif
|
||||
|
||||
// how many seconds after boot to take first measurement, 10 seconds
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
// only report if differance grater than offset value
|
||||
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
||||
#define USERMOD_BH1750_OFFSET_VALUE 1
|
||||
#define USERMOD_BH1750_OFFSET_VALUE 2 // WLEDMM this makes more sense
|
||||
#endif
|
||||
|
||||
class Usermod_BH1750 : public Usermod
|
||||
@@ -91,14 +91,17 @@ private:
|
||||
// set up Home Assistant discovery entries
|
||||
void _mqttInitialize()
|
||||
{
|
||||
#ifdef WLED_ENABLED_MQTT
|
||||
mqttLuminanceTopic = String(mqttDeviceTopic) + F("/brightness");
|
||||
|
||||
if (HomeAssistantDiscovery) _createMqttSensor(F("Brightness"), mqttLuminanceTopic, F("Illuminance"), F(" lx"));
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
#ifdef WLED_ENABLED_MQTT
|
||||
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||
|
||||
StaticJsonDocument<600> doc;
|
||||
@@ -125,11 +128,13 @@ private:
|
||||
DEBUG_PRINTLN(temp);
|
||||
|
||||
mqtt->publish(t.c_str(), 0, true, temp.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
#if 0
|
||||
bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used
|
||||
PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins
|
||||
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
@@ -147,13 +152,18 @@ public:
|
||||
#else
|
||||
//Wire.begin(); // WLEDMM - i2c pins on 8266 are fixed.
|
||||
#endif
|
||||
#endif
|
||||
if (!enabled) return;
|
||||
if (!pinManager.joinWire()) { // WLEDMM - this allocates global I2C pins, then starts Wire - if not started previously
|
||||
sensorFound = false;
|
||||
enabled = false;
|
||||
//enabled = false;
|
||||
USER_PRINTLN(F("BH1750: failed to join I2C bus."));
|
||||
return;
|
||||
}
|
||||
|
||||
sensorFound = lightMeter.begin();
|
||||
if (sensorFound) { USER_PRINTLN(F("BH1750 sensor found.")); }
|
||||
else{ USER_PRINTLN(F("BH1750 sensor not found.")); }
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -226,7 +236,7 @@ public:
|
||||
lux_json.add(F(" sec until read"));
|
||||
return;
|
||||
} else {
|
||||
lux_json.add(lastLux);
|
||||
lux_json.add(round(lastLux)); // WLEDMM
|
||||
lux_json.add(F(" lx"));
|
||||
}
|
||||
}
|
||||
@@ -295,6 +305,7 @@ public:
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing parameters from settings page
|
||||
#if 0
|
||||
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
|
||||
@@ -305,6 +316,9 @@ public:
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
setup();
|
||||
}
|
||||
#else
|
||||
if (enabled && !sensorFound) setup();
|
||||
#endif
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[F("pin")].isNull();
|
||||
}
|
||||
|
||||
@@ -20,15 +20,32 @@
|
||||
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
|
||||
*/
|
||||
|
||||
/* WLEDMM: move usermod variables to class.
|
||||
|
||||
As of March 2023 this is work in progress, more variables will be moved in the future.
|
||||
See Example v2, Temperature, MPU6050 and weather and fastled (rest to be done) as examples which has been converted using the steps below:
|
||||
|
||||
Part 1
|
||||
- remove bool enabled = false/true (now default false)
|
||||
- remove static const char _name[] and _enabled[]
|
||||
- add constructor which calls superclass (temp?): XXXUsermod(const char *name, bool enabled):Usermod(name, enabled) {}
|
||||
- replace _enabled with "enabled"
|
||||
- remove const char PROGMEM init for _name[] and _enabled[]
|
||||
Part 2
|
||||
- Remove bool initDone = false;
|
||||
- addToConfig: replace createNestedObject with Usermod::addToConfig(root); JsonObject top = root[FPSTR(_name)];
|
||||
- readFromConfig: replace !top.isNull and enabled with bool configComplete = Usermod::readFromConfig(root);JsonObject top = root[FPSTR(_name)];
|
||||
Part 3
|
||||
- remove unsigned long lastTime = 0; //WLEDMM
|
||||
|
||||
*/
|
||||
|
||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||
class MyExampleUsermod : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
// Private class members. You can declare variables and functions only accessible to your usermod here
|
||||
bool enabled = false;
|
||||
bool initDone = false;
|
||||
unsigned long lastTime = 0;
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
bool testBool = false;
|
||||
@@ -41,28 +58,25 @@ class MyExampleUsermod : public Usermod {
|
||||
long testLong;
|
||||
int8_t testPins[2];
|
||||
|
||||
// string that are used multiple time (this will save some flash memory)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
|
||||
// any private methods should go here (non-inline methosd should be defined out of class)
|
||||
void publishMqtt(const char* state, bool retain = false); // example for publishing MQTT message
|
||||
|
||||
|
||||
public:
|
||||
|
||||
MyExampleUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM
|
||||
|
||||
// non WLED related methods, may be used for data exchange between usermods (non-inline methods should be defined out of class)
|
||||
|
||||
/**
|
||||
* Enable/Disable the usermod
|
||||
*/
|
||||
inline void enable(bool enable) { enabled = enable; }
|
||||
// inline void enable(bool enable) { enabled = enable; }
|
||||
|
||||
/**
|
||||
* Get usermod enabled/disabled state
|
||||
*/
|
||||
inline bool isEnabled() { return enabled; }
|
||||
// inline bool isEnabled() { return enabled; }
|
||||
|
||||
// in such case add the following to another usermod:
|
||||
// in private vars:
|
||||
@@ -222,8 +236,8 @@ class MyExampleUsermod : public Usermod {
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
Usermod::addToConfig(root); JsonObject top = root[FPSTR(_name)]; //WLEDMM
|
||||
|
||||
//save these vars persistently whenever settings are saved
|
||||
top["great"] = userVar0;
|
||||
top["testBool"] = testBool;
|
||||
@@ -258,9 +272,7 @@ class MyExampleUsermod : public Usermod {
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
bool configComplete = Usermod::readFromConfig(root);JsonObject top = root[FPSTR(_name)]; //WLEDMM
|
||||
|
||||
configComplete &= getJsonValue(top["great"], userVar0);
|
||||
configComplete &= getJsonValue(top["testBool"], testBool);
|
||||
@@ -386,8 +398,6 @@ class MyExampleUsermod : public Usermod {
|
||||
|
||||
|
||||
// add more strings here to reduce flash memory usage
|
||||
const char MyExampleUsermod::_name[] PROGMEM = "ExampleUsermod";
|
||||
const char MyExampleUsermod::_enabled[] PROGMEM = "enabled";
|
||||
|
||||
|
||||
// implementation of non-inline member methods
|
||||
|
||||
@@ -371,6 +371,7 @@ public:
|
||||
* onStateChanged() is used to detect WLED state change
|
||||
*/
|
||||
void onStateChange(uint8_t mode) {
|
||||
if (!initDone) return;
|
||||
DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart);
|
||||
if (PIRtriggered && offTimerStart) {
|
||||
// checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "src/dependencies/time/DS1307RTC.h"
|
||||
#include "wled.h"
|
||||
|
||||
#define RTC_DELTA 2 // only modify RTC time if delta exceeds this number of seconds
|
||||
|
||||
//Connect DS1307 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL))
|
||||
|
||||
class RTCUsermod : public Usermod {
|
||||
@@ -36,6 +38,7 @@ class RTCUsermod : public Usermod {
|
||||
if (rtcTime) {
|
||||
toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC);
|
||||
updateLocalTime();
|
||||
USER_PRINTLN(F("Localtime updated from RTC."));
|
||||
} else {
|
||||
if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error
|
||||
}
|
||||
@@ -45,7 +48,24 @@ class RTCUsermod : public Usermod {
|
||||
if (strip.isUpdating()) return;
|
||||
if (!disabled && toki.isTick()) {
|
||||
time_t t = toki.second();
|
||||
if (abs(t - RTC.get())> 2) RTC.set(t); //set RTC to NTP/UI-provided value - WLEDMM allow up to 3 sec deviation
|
||||
|
||||
if (abs(t - RTC.get())> RTC_DELTA) { // WLEDMM only consider time diffs > 2 seconds
|
||||
if ( (toki.getTimeSource() == TOKI_TS_NTP)
|
||||
||( (toki.getTimeSource() != TOKI_TS_NONE) && (toki.getTimeSource() != TOKI_TS_RTC)
|
||||
&& (toki.getTimeSource() != TOKI_TS_BAD) && (toki.getTimeSource() != TOKI_TS_UDP_SEC) && (toki.getTimeSource() != TOKI_TS_UDP)))
|
||||
{ // WLEMM update RTC if we have a reliable time source
|
||||
RTC.set(t); //set RTC to NTP/UI-provided value - WLEDMM allow up to 3 sec deviation
|
||||
USER_PRINTLN(F("RTC updated using localtime."));
|
||||
} else {
|
||||
// WLEDMM if no reliable time -> update from RTC
|
||||
time_t rtcTime = RTC.get();
|
||||
if (rtcTime) {
|
||||
toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC);
|
||||
updateLocalTime();
|
||||
USER_PRINTLN(F("Localtime updated from RTC."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ class UsermodTemperature : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
bool initDone = false;
|
||||
OneWire *oneWire;
|
||||
// GPIO pin used for sensor (with a default compile-time fallback)
|
||||
int8_t temperaturePin = TEMPERATURE_PIN;
|
||||
@@ -45,13 +44,9 @@ class UsermodTemperature : public Usermod {
|
||||
// temperature if flashed to a board without a sensor attached
|
||||
byte sensorFound;
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
bool HApublished = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _readInterval[];
|
||||
static const char _parasite[];
|
||||
static const char _parasitePin[];
|
||||
@@ -163,6 +158,7 @@ class UsermodTemperature : public Usermod {
|
||||
#endif
|
||||
|
||||
public:
|
||||
UsermodTemperature(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class
|
||||
|
||||
void setup() {
|
||||
int retries = 10;
|
||||
@@ -325,9 +321,10 @@ class UsermodTemperature : public Usermod {
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root) {
|
||||
Usermod::addToConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top["pin"] = temperaturePin; // usermodparam
|
||||
top["degC"] = degC; // usermodparam
|
||||
top[FPSTR(_readInterval)] = readingInterval / 1000;
|
||||
@@ -342,17 +339,18 @@ class UsermodTemperature : public Usermod {
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
bool configComplete = Usermod::readFromConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
||||
int8_t newTemperaturePin = temperaturePin;
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
newTemperaturePin = top["pin"] | newTemperaturePin;
|
||||
degC = top["degC"] | degC;
|
||||
readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
|
||||
@@ -398,8 +396,6 @@ class UsermodTemperature : public Usermod {
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char UsermodTemperature::_name[] PROGMEM = "Temperature";
|
||||
const char UsermodTemperature::_enabled[] PROGMEM = "enabled";
|
||||
const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
|
||||
const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr";
|
||||
const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin";
|
||||
|
||||
@@ -93,22 +93,23 @@ function populateCEEditor(name, segID)
|
||||
{
|
||||
fetchAndExecute((loc?`http://${locip}`:'.') + "/", name + ".wled", function(text)
|
||||
{
|
||||
var cn=`Custom Effects Editor<br>
|
||||
var cn=`ARTI-FX Editor<br>
|
||||
<i>${name}.wled</i><br>
|
||||
<textarea class="ceTextarea" id="ceProgramArea">${text}</textarea><br>
|
||||
<button class="btn infobtn" onclick="toggleCEEditor()">Close</button>
|
||||
<button class="btn infobtn" onclick="saveCE('${name}.wled', ${segID})">Save and Run</button><br>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('CE','${name}.wled')">Download ${name}.wled</button>
|
||||
<button class="btn infobtn" onclick="downloadGHFile('CE','${name}.wled')">Download ${name}.wled</button>
|
||||
<button class="btn infobtn" onclick="loadCETemplate('${name}')">Load template</button><br>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('CE','wledv033.json')">Download wled json</button>
|
||||
<button class="btn infobtn" onclick="downloadCEFile('CE','presets.json')">Download presets.json</button><br>
|
||||
<button class="btn infobtn" onclick="location.href='https://github.com/MoonModules/WLED-Effects/tree/master/CustomEffects/wled'" type="button">Custom Effects Library</button>
|
||||
<button class="btn infobtn btn-xs" onclick="location.href='https://mm.kno.wled.ge/moonmodules/custom-effects'" type="button">?</button><br>
|
||||
<button class="btn infobtn" onclick="downloadGHFile('CE','wledv033.json',true,true)">Download wled json</button>
|
||||
<button class="btn infobtn" onclick="downloadGHFile('CE','presets.json',true,true)">Download presets.json</button><br>
|
||||
<button class="btn infobtn" onclick="location.href='https://github.com/MoonModules/WLED-Effects/tree/master/ARTIFX/wled'" type="button">ARTI-FX Library</button>
|
||||
<button class="btn infobtn btn-xs" onclick="location.href='https://mm.kno.wled.ge/moonmodules/arti-fx'" type="button">?</button><br>
|
||||
<br><i>Compile and Run Log</i><br>
|
||||
<textarea class="ceTextarea" id="ceLogArea"></textarea><br>
|
||||
<i>Run log > 3 seconds is send to Serial Ouput.</i><br>
|
||||
<a href="#" onclick="downloadCEFile('HBB','presets.json');return false;" title="Download HBaas Base presets">🥚</a>
|
||||
<a href="#" onclick="downloadCEFile('HBE','presets.json');return false;" title="Download HBaas Effects presets">🥚</a>`;
|
||||
<a href="#" onclick="downloadGHFile('HBB','presets.json',true,true);return false;" title="Download HBaas Base presets">🥚</a>
|
||||
<a href="#" onclick="downloadGHFile('HBE','presets.json',true,true);return false;" title="Download HBaas Effects presets">🥚</a>
|
||||
<a href="#" onclick="downloadGHFile('LM','presets.json',true,true);return false;" title="Download Ledmap presets">🥚</a>`;
|
||||
|
||||
d.getElementById('kceEditor').innerHTML = cn;
|
||||
|
||||
@@ -122,14 +123,15 @@ function populateCEEditor(name, segID)
|
||||
});
|
||||
}
|
||||
|
||||
function downloadCEFile(url, name) {
|
||||
if (url == "CE") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/CustomEffects/wled/";
|
||||
function downloadGHFile(url, name, save=false, warn=false) { //Githubfile
|
||||
if (url == "CE") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/ARTIFX/wled/";
|
||||
if (url == "HBB") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/Presets/HB_PresetPack210808_32x32_16seg/Base%20pack/";
|
||||
if (url == "HBE") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/Presets/HB_PresetPack210808_32x32_16seg/Effects%20pack/";
|
||||
if (url == "LM") url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/Ledmaps/";
|
||||
|
||||
fetchAndExecute(url, name, function(text) {
|
||||
if (name == "wledv033.json" || name == "presets.json") {
|
||||
if (!confirm('Are you sure to download/overwrite ' + name + '?'))
|
||||
if (save) {
|
||||
if (warn && !confirm('Are you sure to download/overwrite ' + name + '?'))
|
||||
return;
|
||||
uploadFileWithText("/" + name, text);
|
||||
}
|
||||
@@ -140,7 +142,7 @@ function downloadCEFile(url, name) {
|
||||
}
|
||||
}, function(error){
|
||||
showToast(error);
|
||||
console.log(error);
|
||||
console.log(url + name,error);
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -165,7 +167,7 @@ function downloadCEFile(url, name) {
|
||||
function loadCETemplate(name) {
|
||||
var ceProgramArea = d.getElementById("ceProgramArea");
|
||||
ceProgramArea.value = `/*
|
||||
Custom Effects Template
|
||||
ARTIFX Template
|
||||
*/
|
||||
program ${name}
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
@title Usermod Custom Effects (CE)
|
||||
@file usermod_v2_customeffects.h
|
||||
@title Usermod ARTIFX (AF)
|
||||
@file usermod_v2_artifx.h
|
||||
@date 20220818
|
||||
@author Ewoud Wijma
|
||||
@Copyright (c) 2023 Ewoud Wijma
|
||||
@@ -17,7 +17,7 @@
|
||||
ARTI * arti;
|
||||
|
||||
//effect function
|
||||
uint16_t mode_customEffect(void) {
|
||||
uint16_t mode_ARTIFX(void) {
|
||||
//tbd: move statics to SEGMENT.data
|
||||
static bool succesful;
|
||||
static bool notEnoughHeap;
|
||||
@@ -99,9 +99,9 @@ uint16_t mode_customEffect(void) {
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
static const char _data_FX_MODE_CUSTOMEFFECT[] PROGMEM = "⚙️ Custom Effect ☾@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!;1;mp12=0";
|
||||
static const char _data_FX_MODE_ARTIFX[] PROGMEM = "⚙️ ARTI-FX ☾@Speed,Intensity,Custom 1, Custom 2, Custom 3;!;!;1;mp12=0";
|
||||
|
||||
class CustomEffectsUserMod : public Usermod {
|
||||
class ARTIFXUserMod : public Usermod {
|
||||
private:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[]; //usermod name
|
||||
@@ -115,7 +115,7 @@ class CustomEffectsUserMod : public Usermod {
|
||||
|
||||
void setup() {
|
||||
if (!initDone)
|
||||
strip.addEffect(FX_MODE_CUSTOMEFFECT, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT);
|
||||
strip.addEffect(FX_MODE_ARTIFX, &mode_ARTIFX, _data_FX_MODE_ARTIFX);
|
||||
initDone = true;
|
||||
enabled = true;
|
||||
}
|
||||
@@ -200,9 +200,9 @@ class CustomEffectsUserMod : public Usermod {
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_CUSTOMEFFECTS;
|
||||
return USERMOD_ID_ARTIFX;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char CustomEffectsUserMod::_name[] PROGMEM = "CustomEffects";
|
||||
const char ARTIFXUserMod::_name[] PROGMEM = "ARTIFX";
|
||||
@@ -29,9 +29,9 @@
|
||||
// #define SR_DEBUG // generic SR DEBUG messages
|
||||
|
||||
#ifdef SR_DEBUG
|
||||
#define DEBUGSR_PRINT(x) DEBUGOUT.print(x)
|
||||
#define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x)
|
||||
#define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x)
|
||||
#define DEBUGSR_PRINT(x) DEBUGOUT(x)
|
||||
#define DEBUGSR_PRINTLN(x) DEBUGOUTLN(x)
|
||||
#define DEBUGSR_PRINTF(x...) DEBUGOUTF(x)
|
||||
#else
|
||||
#define DEBUGSR_PRINT(x)
|
||||
#define DEBUGSR_PRINTLN(x)
|
||||
@@ -55,10 +55,10 @@
|
||||
#endif
|
||||
|
||||
#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG)
|
||||
#define PLOT_PRINT(x) DEBUGOUT.print(x)
|
||||
#define PLOT_PRINTLN(x) DEBUGOUT.println(x)
|
||||
#define PLOT_PRINTF(x...) DEBUGOUT.printf(x)
|
||||
#define PLOT_FLUSH() DEBUGOUT.flush()
|
||||
#define PLOT_PRINT(x) DEBUGOUT(x)
|
||||
#define PLOT_PRINTLN(x) DEBUGOUTLN(x)
|
||||
#define PLOT_PRINTF(x...) DEBUGOUTF(x)
|
||||
#define PLOT_FLUSH() DEBUGOUTFlush()
|
||||
#else
|
||||
#define PLOT_PRINT(x)
|
||||
#define PLOT_PRINTLN(x)
|
||||
@@ -123,6 +123,15 @@ static AudioSource *audioSource = nullptr;
|
||||
static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks.
|
||||
static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT.
|
||||
|
||||
//WLEDMM add experimental settings
|
||||
static uint8_t micLevelMethod = 0; // 0=old "floating" miclev, 1=new "freeze" mode, 2=fast freeze mode (mode 2 may not work for you)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
static uint8_t averageByRMS = false; // false: use mean value, true: use RMS (root mean squared). use simpler method on slower MCUs.
|
||||
#else
|
||||
static uint8_t averageByRMS = true; // false: use mean value, true: use RMS (root mean squared). use better method on fast MCUs.
|
||||
#endif
|
||||
static uint8_t freqDist = 0; // 0=old 1=rightshift mode
|
||||
|
||||
// audioreactive variables shared with FFT task
|
||||
static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point
|
||||
static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier
|
||||
@@ -136,7 +145,7 @@ static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending
|
||||
|
||||
// peak detection
|
||||
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
||||
static uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated)
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
|
||||
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
|
||||
@@ -182,7 +191,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
{ 12.0f, 6.60f, 2.60f, 1.15f, 1.35f, 2.05f, 2.85f, 2.50f, 2.85f, 3.30f, 2.25f, 4.35f, 3.80f, 3.75f, 6.50f, 9.00f}, // 4 IMNP441 - voice, or small speaker
|
||||
|
||||
{ 2.75f, 1.60f, 1.40f, 1.46f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 1.75f, 2.55f, 3.60f }, // 5 ICS-43434 datasheet response * pink noise
|
||||
{ 2.25f, 1.20f, 1.00f, 1.20f, 1.80f, 3.20f, 5.10f, 5.50f, 4.00f, 4.80f, 6.70f, 6.40f, 5.80f, 3.90f, 6.00f, 5.10f }, // 6 ICS-43434 - big speaker, strong bass
|
||||
{ 2.90f, 1.25f, 0.75f, 1.08f, 2.35f, 3.55f, 3.60f, 3.40f, 2.75f, 3.45f, 4.40f, 6.35f, 6.80f, 6.80f, 8.50f,10.64f }, // 6 ICS-43434 - big speaker, strong bass
|
||||
|
||||
{ 1.65f, 1.00f, 1.05f, 1.30f, 1.48f, 1.30f, 1.80f, 3.00f, 1.50f, 1.65f, 2.56f, 3.00f, 2.60f, 2.30f, 5.00f, 3.00f }, // 7 SPM1423
|
||||
{ 2.25f, 1.60f, 1.30f, 1.60f, 2.20f, 3.20f, 3.06f, 2.60f, 2.85f, 3.50f, 4.10f, 4.80f, 5.70f, 6.05f,10.50f,14.85f }, // 8 userdef #1 for ewowi (enhance median/high freqs)
|
||||
@@ -264,7 +273,8 @@ constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - Thi
|
||||
constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information.
|
||||
// the following are observed values, supported by a bit of "educated guessing"
|
||||
//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels
|
||||
#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
||||
//#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
||||
#define FFT_DOWNSCALE 0.40f // downscaling factor for FFT results, RMS averaging
|
||||
#define LOG_256 5.54517744f // log(256)
|
||||
|
||||
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
||||
@@ -303,23 +313,28 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_
|
||||
}
|
||||
|
||||
// compute average of several FFT resut bins
|
||||
#if 1 // linear average
|
||||
static float fftAddAvg(int from, int to) {
|
||||
// linear average
|
||||
static float fftAddAvgLin(int from, int to) {
|
||||
float result = 0.0f;
|
||||
for (int i = from; i <= to; i++) {
|
||||
result += vReal[i];
|
||||
}
|
||||
return result / float(to - from + 1);
|
||||
}
|
||||
#else // RMS average
|
||||
static float fftAddAvg(int from, int to) {
|
||||
// RMS average
|
||||
static float fftAddAvgRMS(int from, int to) {
|
||||
double result = 0.0;
|
||||
for (int i = from; i <= to; i++) {
|
||||
result += vReal[i] * vReal[i];
|
||||
}
|
||||
return sqrtf(result / float(to - from + 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
static float fftAddAvg(int from, int to) {
|
||||
if (from == to) return vReal[from]; // small optimization
|
||||
if (averageByRMS) return fftAddAvgRMS(from, to); // use SMS
|
||||
else return fftAddAvgLin(from, to); // use linear average
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
constexpr bool skipSecondFFT = true;
|
||||
@@ -512,35 +527,67 @@ void FFTcode(void * parameter)
|
||||
fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900
|
||||
fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate
|
||||
#else
|
||||
/* new mapping, optimized for 22050 Hz by softhack007 */
|
||||
//WLEDMM: different distributions
|
||||
if (freqDist == 0) {
|
||||
/* new mapping, optimized for 22050 Hz by softhack007 --- update: removed overlap */
|
||||
// bins frequency range
|
||||
if (useBandPassFilter) {
|
||||
// skip frequencies below 100hz
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(3,4);
|
||||
fftCalc[ 1] = 0.9f * fftAddAvg(4,5);
|
||||
fftCalc[ 2] = fftAddAvg(5,6);
|
||||
fftCalc[ 3] = fftAddAvg(6,7);
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(3,3);
|
||||
fftCalc[ 1] = 0.9f * fftAddAvg(4,4);
|
||||
fftCalc[ 2] = fftAddAvg(5,5);
|
||||
fftCalc[ 3] = fftAddAvg(6,6);
|
||||
// don't use the last bins from 206 to 255.
|
||||
fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping
|
||||
} else {
|
||||
fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass
|
||||
fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass
|
||||
fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass
|
||||
fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange
|
||||
fftCalc[ 0] = fftAddAvg(1,1); // 1 43 - 86 sub-bass
|
||||
fftCalc[ 1] = fftAddAvg(2,2); // 1 86 - 129 bass
|
||||
fftCalc[ 2] = fftAddAvg(3,4); // 2 129 - 216 bass
|
||||
fftCalc[ 3] = fftAddAvg(5,6); // 2 216 - 301 bass + midrange
|
||||
// don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise)
|
||||
fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping
|
||||
}
|
||||
fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange
|
||||
fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange
|
||||
fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange
|
||||
fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center !
|
||||
fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange
|
||||
fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange
|
||||
fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid
|
||||
fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid
|
||||
fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid
|
||||
fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid
|
||||
fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping
|
||||
fftCalc[ 4] = fftAddAvg(7,9); // 3 301 - 430 midrange
|
||||
fftCalc[ 5] = fftAddAvg(10,12); // 3 430 - 560 midrange
|
||||
fftCalc[ 6] = fftAddAvg(13,18); // 5 560 - 818 midrange
|
||||
fftCalc[ 7] = fftAddAvg(19,25); // 7 818 - 1120 midrange -- 1Khz should always be the center !
|
||||
fftCalc[ 8] = fftAddAvg(26,32); // 7 1120 - 1421 midrange
|
||||
fftCalc[ 9] = fftAddAvg(33,43); // 9 1421 - 1895 midrange
|
||||
fftCalc[10] = fftAddAvg(44,55); // 12 1895 - 2412 midrange + high mid
|
||||
fftCalc[11] = fftAddAvg(56,69); // 14 2412 - 3015 high mid
|
||||
fftCalc[12] = fftAddAvg(70,85); // 16 3015 - 3704 high mid
|
||||
fftCalc[13] = fftAddAvg(86,103); // 18 3704 - 4479 high mid
|
||||
fftCalc[14] = fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping
|
||||
}
|
||||
else if (freqDist == 1) { //WLEDMM: Rightshft: note ewowi: frequencies in comments are not correct
|
||||
if (useBandPassFilter) {
|
||||
// skip frequencies below 100hz
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(1,1);
|
||||
fftCalc[ 1] = 0.9f * fftAddAvg(2,2);
|
||||
fftCalc[ 2] = fftAddAvg(3,3);
|
||||
fftCalc[ 3] = fftAddAvg(4,4);
|
||||
// don't use the last bins from 206 to 255.
|
||||
fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping
|
||||
} else {
|
||||
fftCalc[ 0] = fftAddAvg(1,1); // 1 43 - 86 sub-bass
|
||||
fftCalc[ 1] = fftAddAvg(2,2); // 1 86 - 129 bass
|
||||
fftCalc[ 2] = fftAddAvg(3,3); // 2 129 - 216 bass
|
||||
fftCalc[ 3] = fftAddAvg(4,4); // 2 216 - 301 bass + midrange
|
||||
// don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise)
|
||||
fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping
|
||||
}
|
||||
fftCalc[ 4] = fftAddAvg(5,6); // 3 301 - 430 midrange
|
||||
fftCalc[ 5] = fftAddAvg(7,8); // 3 430 - 560 midrange
|
||||
fftCalc[ 6] = fftAddAvg(9,10); // 5 560 - 818 midrange
|
||||
fftCalc[ 7] = fftAddAvg(11,13); // 7 818 - 1120 midrange -- 1Khz should always be the center !
|
||||
fftCalc[ 8] = fftAddAvg(14,18); // 7 1120 - 1421 midrange
|
||||
fftCalc[ 9] = fftAddAvg(19,25); // 9 1421 - 1895 midrange
|
||||
fftCalc[10] = fftAddAvg(26,36); // 12 1895 - 2412 midrange + high mid
|
||||
fftCalc[11] = fftAddAvg(37,45); // 14 2412 - 3015 high mid
|
||||
fftCalc[12] = fftAddAvg(46,66); // 16 3015 - 3704 high mid
|
||||
fftCalc[13] = fftAddAvg(67,97); // 18 3704 - 4479 high mid
|
||||
fftCalc[14] = fftAddAvg(98,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping
|
||||
}
|
||||
#endif
|
||||
} else { // noise gate closed - just decay old values
|
||||
isFirstRun = false;
|
||||
@@ -861,7 +908,9 @@ class AudioReactive : public Usermod {
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _inputLvl[];
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
static const char _analogmic[];
|
||||
#endif
|
||||
static const char _digitalmic[];
|
||||
static const char UDP_SYNC_HEADER[];
|
||||
static const char UDP_SYNC_HEADER_v1[];
|
||||
@@ -1057,6 +1106,10 @@ class AudioReactive : public Usermod {
|
||||
const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release.
|
||||
const float weighting2 = 0.073f; // Exponential filter weighting, for rising signal (a bit more robust against spikes)
|
||||
const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function
|
||||
static bool isFrozen = false;
|
||||
static bool haveSilence = true;
|
||||
static unsigned long lastSoundTime = 0; // for delaying un-freeze
|
||||
static unsigned long startuptime = 0; // "fast freeze" mode: do not interfere during first 12 seconds (filter startup time)
|
||||
|
||||
#ifdef WLED_DISABLE_SOUND
|
||||
micIn = inoise8(millis(), millis()); // Simulated analog read
|
||||
@@ -1079,8 +1132,15 @@ class AudioReactive : public Usermod {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
micLev += (micDataReal-micLev) / 12288.0f;
|
||||
if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal
|
||||
if (startuptime == 0) startuptime = millis(); // fast freeze mode - remember filter startup time
|
||||
if ((micLevelMethod < 1) || !isFrozen) { // following the input level, UNLESS mic Level was frozen
|
||||
micLev += (micDataReal-micLev) / 12288.0f;
|
||||
}
|
||||
|
||||
if(micDataReal < (micLev-0.24)) { // MicLev above input signal:
|
||||
micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // always align MicLev to lowest input signal
|
||||
if (!haveSilence) isFrozen = true; // freeze mode: freeze micLevel so it cannot rise again
|
||||
}
|
||||
|
||||
micIn -= micLev; // Let's center it to 0 now
|
||||
// Using an exponential filter to smooth out the signal. We'll add controls for this in a future release.
|
||||
@@ -1093,10 +1153,26 @@ class AudioReactive : public Usermod {
|
||||
|
||||
expAdjF = fabsf(expAdjF); // Now (!) take the absolute value
|
||||
|
||||
if ((micLevelMethod == 2) && !haveSilence && (expAdjF >= (1.5f * float(soundSquelch))))
|
||||
isFrozen = true; // fast freeze mode: freeze micLevel once the volume rises 50% above squelch
|
||||
|
||||
//expAdjF = (micInNoDC <= soundSquelch) ? 0: expAdjF; // simple noise gate - experimental
|
||||
expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate
|
||||
if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0"
|
||||
|
||||
if (expAdjF <= 0.5f)
|
||||
haveSilence = true;
|
||||
else {
|
||||
lastSoundTime = millis();
|
||||
haveSilence = false;
|
||||
}
|
||||
|
||||
// un-freeze micLev
|
||||
if (micLevelMethod == 0) isFrozen = false;
|
||||
if ((micLevelMethod == 1) && isFrozen && haveSilence && ((millis() - lastSoundTime) > 4000)) isFrozen = false; // normal freeze: 4 seconds silence needed
|
||||
if ((micLevelMethod == 2) && isFrozen && haveSilence && ((millis() - lastSoundTime) > 6000)) isFrozen = false; // fast freeze: 6 seconds silence needed
|
||||
if ((micLevelMethod == 2) && (millis() - startuptime < 12000)) isFrozen = false; // fast freeze: no freeze in first 12 seconds (filter startup phase)
|
||||
|
||||
tmpSample = expAdjF;
|
||||
micIn = abs(micIn); // And get the absolute value of each sample
|
||||
|
||||
@@ -1360,6 +1436,7 @@ class AudioReactive : public Usermod {
|
||||
case 0: //ADC analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
case 5: //PDM Microphone
|
||||
case 51: //legacy PDM Microphone
|
||||
#endif
|
||||
#endif
|
||||
case 1:
|
||||
@@ -1397,6 +1474,13 @@ class AudioReactive : public Usermod {
|
||||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin);
|
||||
break;
|
||||
case 51:
|
||||
DEBUGSR_PRINT(F("AR: Legacy PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT));
|
||||
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f);
|
||||
useBandPassFilter = true;
|
||||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin);
|
||||
break;
|
||||
#endif
|
||||
case 6:
|
||||
DEBUGSR_PRINTLN(F("AR: ES8388 Source"));
|
||||
@@ -1537,7 +1621,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
|
||||
// complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second.
|
||||
if ((userloopDelay > /*23*/ 30) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
|
||||
if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
|
||||
USER_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay);
|
||||
}
|
||||
#endif
|
||||
@@ -1673,14 +1757,15 @@ class AudioReactive : public Usermod {
|
||||
connected(); // resume UDP
|
||||
} else
|
||||
// xTaskCreatePinnedToCore(
|
||||
xTaskCreate( // no need to "pin" this task to core #0
|
||||
// xTaskCreate( // no need to "pin" this task to core #0
|
||||
xTaskCreateUniversal(
|
||||
FFTcode, // Function to implement the task
|
||||
"FFT", // Name of the task
|
||||
5000, // Stack size in words
|
||||
NULL, // Task input parameter
|
||||
1, // Priority of the task
|
||||
&FFT_Task // Task handle
|
||||
// , 0 // Core where the task should run
|
||||
, 0 // Core where the task should run
|
||||
);
|
||||
}
|
||||
micDataReal = 0.0f; // just to be sure
|
||||
@@ -1780,7 +1865,10 @@ class AudioReactive : public Usermod {
|
||||
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
|
||||
infoArr.add(F("ADC analog"));
|
||||
} else {
|
||||
infoArr.add(F("I2S digital"));
|
||||
if (dmType != 51)
|
||||
infoArr.add(F("I2S digital"));
|
||||
else
|
||||
infoArr.add(F("legacy I2S PDM"));
|
||||
}
|
||||
// input level or "silence"
|
||||
if (maxSample5sec > 1.0) {
|
||||
@@ -1793,7 +1881,7 @@ class AudioReactive : public Usermod {
|
||||
} else {
|
||||
// error during audio source setup
|
||||
infoArr.add(F("not initialized"));
|
||||
infoArr.add(F(" - check GPIO config"));
|
||||
infoArr.add(F(" - check pin settings"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1957,6 +2045,12 @@ class AudioReactive : public Usermod {
|
||||
cfg[F("gain")] = sampleGain;
|
||||
cfg[F("AGC")] = soundAgc;
|
||||
|
||||
//WLEDMM: experimental settings
|
||||
JsonObject poweruser = top.createNestedObject("experiments");
|
||||
poweruser[F("micLev")] = micLevelMethod;
|
||||
poweruser[F("freqDist")] = freqDist;
|
||||
poweruser[F("freqRMS")] = averageByRMS;
|
||||
|
||||
JsonObject dynLim = top.createNestedObject("dynamics");
|
||||
dynLim[F("limiter")] = limiterOn;
|
||||
dynLim[F("rise")] = attackTime;
|
||||
@@ -2005,7 +2099,11 @@ class AudioReactive : public Usermod {
|
||||
if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM
|
||||
if (dmType == 51) dmType = SR_DMTYPE; // MCU does not support legacy PDM
|
||||
#endif
|
||||
#else
|
||||
if (dmType == 5) useBandPassFilter = true; // enable filter for PDM
|
||||
if (dmType == 51) useBandPassFilter = true /*false*/; // switch on filter for legacy PDM
|
||||
#endif
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin);
|
||||
@@ -2019,6 +2117,11 @@ class AudioReactive : public Usermod {
|
||||
configComplete &= getJsonValue(top["config"][F("gain")], sampleGain);
|
||||
configComplete &= getJsonValue(top["config"][F("AGC")], soundAgc);
|
||||
|
||||
//WLEDMM: experimental settings
|
||||
configComplete &= getJsonValue(top["experiments"][F("micLev")], micLevelMethod);
|
||||
configComplete &= getJsonValue(top["experiments"][F("freqDist")], freqDist);
|
||||
configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS);
|
||||
|
||||
configComplete &= getJsonValue(top["dynamics"][F("limiter")], limiterOn);
|
||||
configComplete &= getJsonValue(top["dynamics"][F("rise")], attackTime);
|
||||
configComplete &= getJsonValue(top["dynamics"][F("fall")], decayTime);
|
||||
@@ -2038,10 +2141,12 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("addInfo('AudioReactive:help',0,'<button onclick=\"location.href="https://mm.kno.wled.ge/soundreactive/Sound-Settings"\" type=\"button\">?</button>');"));
|
||||
|
||||
//WLEDMM: add defaults
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // -S3/-S2/-C3 don't support analog audio
|
||||
#ifdef AUDIOPIN
|
||||
oappend(SET_F("xOpt('AudioReactive:analogmic:pin',1,' ⎌',")); oappendi(AUDIOPIN); oappend(");");
|
||||
#endif
|
||||
oappend(SET_F("aOpt('AudioReactive:analogmic:pin',1);")); //only analog options
|
||||
#endif
|
||||
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');"));
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
@@ -2071,12 +2176,17 @@ class AudioReactive : public Usermod {
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);"));
|
||||
#endif
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if SR_DMTYPE==5
|
||||
oappend(SET_F("addOption(dd,'Generic I2S PDM (⎌)',5);"));
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'Generic I2S PDM',5);"));
|
||||
#endif
|
||||
#if SR_DMTYPE==51
|
||||
oappend(SET_F("addOption(dd,'.Legacy I2S PDM ☾ (⎌)',51);"));
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'.Legacy I2S PDM ☾',51);"));
|
||||
#endif
|
||||
#endif
|
||||
oappend(SET_F("addOption(dd,'ES8388',6);"));
|
||||
|
||||
@@ -2093,6 +2203,23 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("addOption(dd,'Vivid',2);"));
|
||||
oappend(SET_F("addOption(dd,'Lazy',3);"));
|
||||
|
||||
//WLEDMM: experimental settings
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','experiments:micLev');"));
|
||||
oappend(SET_F("addOption(dd,'Floating (⎌)',0);"));
|
||||
oappend(SET_F("addOption(dd,'Freeze',1);"));
|
||||
oappend(SET_F("addOption(dd,'Fast Freeze',2);"));
|
||||
oappend(SET_F("addInfo('AudioReactive:experiments:micLev',1,'☾');"));
|
||||
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqDist');"));
|
||||
oappend(SET_F("addOption(dd,'Normal (⎌)',0);"));
|
||||
oappend(SET_F("addOption(dd,'RightShift',1);"));
|
||||
oappend(SET_F("addInfo('AudioReactive:experiments:freqDist',1,'☾');"));
|
||||
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqRMS');"));
|
||||
oappend(SET_F("addOption(dd,'Off (⎌)',0);"));
|
||||
oappend(SET_F("addOption(dd,'On',1);"));
|
||||
oappend(SET_F("addInfo('AudioReactive:experiments:freqRMS',1,'☾');"));
|
||||
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
oappend(SET_F("addOption(dd,'On',1);"));
|
||||
@@ -2163,6 +2290,7 @@ class AudioReactive : public Usermod {
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'userdefined #2',9);"));
|
||||
#endif
|
||||
oappend(SET_F("addInfo('AudioReactive:frequency:profile',1,'☾');"));
|
||||
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
@@ -2237,7 +2365,9 @@ class AudioReactive : public Usermod {
|
||||
const char AudioReactive::_name[] PROGMEM = "AudioReactive";
|
||||
const char AudioReactive::_enabled[] PROGMEM = "enabled";
|
||||
const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
|
||||
#endif
|
||||
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
|
||||
const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure
|
||||
const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Blynk controllable relay
|
||||
Enables controlling a relay state via user variables. Allows the user variables to be set via Blynk.
|
||||
|
||||
Optionally, the servo can have a reset timer to return to its default state after a user definable interval. The interval is set via userVar1.
|
||||
|
||||
## Instalation
|
||||
|
||||
Replace the WLED06_usermod.ino file in Aircoookies WLED folder, with the one here.
|
||||
|
||||
## Customizations
|
||||
|
||||
Update the following parameters in WLED06_usermod.ino to configure the mod's behavior:
|
||||
|
||||
```cpp
|
||||
//Which pin is the relay connected to
|
||||
#define RELAY_PIN 5
|
||||
//Which pin state should the relay default to
|
||||
#define RELAY_PIN_DEFAULT LOW
|
||||
//If >0 The controller returns to RELAY_PIN_DEFAULT after this time, in milliseconds
|
||||
#define RELAY_PIN_TIMER_DEFAULT 3000
|
||||
|
||||
//Blynk virtual pin for controlling relay
|
||||
#define BLYNK_USER_VAR0_PIN V9
|
||||
//Blynk virtual pin for controlling relay timer
|
||||
#define BLYNK_USER_VAR1_PIN V10
|
||||
//Number of milliseconds between Blynk updates
|
||||
#define BLYNK_RELAY_UPDATE_INTERVAL 5000
|
||||
```
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* This file allows you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h)
|
||||
* bytes 2400+ are currently ununsed, but might be used for future wled features
|
||||
*/
|
||||
|
||||
//Use userVar0 (API calls &U0=, uint16_t) to set relay state
|
||||
#define relayPinState userVar0
|
||||
//Use userVar1 (API calls &U1=, uint16_t) to set relay timer duration
|
||||
//Ignored if 0, otherwise number of milliseconds to allow relay to stay in
|
||||
//non default state.
|
||||
#define relayTimerInterval userVar1
|
||||
|
||||
//Which pin is the relay connected to
|
||||
#define RELAY_PIN 5
|
||||
//Which pin state should the relay default to
|
||||
#define RELAY_PIN_DEFAULT LOW
|
||||
//If >0 The controller returns to RELAY_PIN_DEFAULT after this time in milliseconds
|
||||
#define RELAY_PIN_TIMER_DEFAULT 3000
|
||||
|
||||
//Blynk virtual pin for controlling relay
|
||||
#define BLYNK_USER_VAR0_PIN V9
|
||||
//Blynk virtual pin for controlling relay timer
|
||||
#define BLYNK_USER_VAR1_PIN V10
|
||||
//Number of milliseconds between updating blynk
|
||||
#define BLYNK_RELAY_UPDATE_INTERVAL 5000
|
||||
|
||||
//Is the timer for resetting the relay active
|
||||
bool relayTimerStarted = false;
|
||||
//millis() time after which relay will be reset
|
||||
unsigned long relayTimeToDefault = 0;
|
||||
//millis() time after which relay vars in Blynk will be sent
|
||||
unsigned long relayBlynkUpdateTime = 0;
|
||||
|
||||
//gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
void userSetup()
|
||||
{
|
||||
relayPinState = RELAY_PIN_DEFAULT;
|
||||
relayTimerInterval = RELAY_PIN_TIMER_DEFAULT;
|
||||
pinMode(RELAY_PIN, OUTPUT);
|
||||
digitalWrite(RELAY_PIN, relayPinState);
|
||||
}
|
||||
|
||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
||||
void userConnected()
|
||||
{
|
||||
}
|
||||
|
||||
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
|
||||
void userLoop()
|
||||
{
|
||||
//Normalize relayPinState to an accepted value
|
||||
if (relayPinState != HIGH && relayPinState != LOW) {
|
||||
relayPinState = RELAY_PIN_DEFAULT;
|
||||
}
|
||||
//If relay changes and relayTimerInterval is set, start a timer to change back
|
||||
if (relayTimerInterval != 0 &&
|
||||
relayPinState != RELAY_PIN_DEFAULT &&
|
||||
!relayTimerStarted ) {
|
||||
relayTimerStarted = true;
|
||||
relayTimeToDefault = millis() + relayTimerInterval;
|
||||
}
|
||||
//If manually changed back to default, cancel timer
|
||||
if (relayTimerStarted && relayPinState == RELAY_PIN_DEFAULT ) {
|
||||
relayTimerStarted = false;
|
||||
}
|
||||
//If timer completes, set relay back to default
|
||||
if (relayTimerStarted && millis() > relayTimeToDefault) {
|
||||
relayPinState = RELAY_PIN_DEFAULT;
|
||||
relayTimerStarted = false;
|
||||
}
|
||||
digitalWrite(RELAY_PIN, relayPinState);
|
||||
updateRelayBlynk();
|
||||
}
|
||||
|
||||
//Update Blynk with state of userVars at BLYNK_RELAY_UPDATE_INTERVAL
|
||||
void updateRelayBlynk()
|
||||
{
|
||||
if (!WLED_CONNECTED) return;
|
||||
if (relayBlynkUpdateTime > millis()) return;
|
||||
Blynk.virtualWrite(BLYNK_USER_VAR0_PIN, userVar0);
|
||||
Blynk.virtualWrite(BLYNK_USER_VAR1_PIN, userVar1);
|
||||
relayBlynkUpdateTime = millis() + BLYNK_RELAY_UPDATE_INTERVAL;
|
||||
}
|
||||
|
||||
//Add Blynk callback for setting userVar0
|
||||
BLYNK_WRITE(BLYNK_USER_VAR0_PIN)
|
||||
{
|
||||
userVar0 = param.asInt();
|
||||
}
|
||||
//Add Blynk callback for setting userVar1
|
||||
BLYNK_WRITE(BLYNK_USER_VAR1_PIN)
|
||||
{
|
||||
userVar1 = param.asInt();
|
||||
}
|
||||
@@ -65,9 +65,9 @@
|
||||
#undef DEBUG_PRINTF
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
#define DEBUG_PRINT(x) DEBUGOUT.print(x)
|
||||
#define DEBUG_PRINTLN(x) DEBUGOUT.println(x)
|
||||
#define DEBUG_PRINTF(x...) DEBUGOUT.printf(x)
|
||||
#define DEBUG_PRINT(x) DEBUGOUT(x)
|
||||
#define DEBUG_PRINTLN(x) DEBUGOUTLN(x)
|
||||
#define DEBUG_PRINTF(x...) DEBUGOUTF(x)
|
||||
#else
|
||||
#define DEBUG_PRINT(x)
|
||||
#define DEBUG_PRINTLN(x)
|
||||
@@ -93,18 +93,22 @@ void IRAM_ATTR dmpDataReady() {
|
||||
class MPU6050Driver : public Usermod {
|
||||
private:
|
||||
MPU6050 mpu;
|
||||
bool enabled = true;
|
||||
unsigned long lastUMRun = millis();
|
||||
|
||||
// MPU control/status vars
|
||||
bool dmpReady = false; // set true if DMP init was successful
|
||||
uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
|
||||
uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
|
||||
uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
|
||||
uint16_t fifoCount; // count of all bytes currently in FIFO
|
||||
uint8_t fifoBuffer[64]; // FIFO storage buffer
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _INT_pin[];
|
||||
|
||||
public:
|
||||
MPU6050Driver(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class
|
||||
|
||||
bool dmpReady = false; // set true if DMP init was successful // WLEDMM expose this info in public interface
|
||||
// orientation/motion vars
|
||||
Quaternion qat; // [w, x, y, z] quaternion container
|
||||
VectorInt16 aa; // [x, y, z] accel sensor measurements
|
||||
@@ -122,7 +126,11 @@ class MPU6050Driver : public Usermod {
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
// WLEDMM begin
|
||||
// WLEDMM begin
|
||||
if (!enabled) {
|
||||
dmpReady = false;
|
||||
return;
|
||||
}
|
||||
USER_PRINTLN(F("mpu setup"));
|
||||
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
|
||||
if ((i2c_scl < 0) || (i2c_sda < 0)) {
|
||||
@@ -171,16 +179,26 @@ class MPU6050Driver : public Usermod {
|
||||
// initialize device
|
||||
DEBUG_PRINT_IMULN(F("Initializing I2C devices..."));
|
||||
// WLEDMM begin
|
||||
if (!pinManager.allocatePin(INTERRUPT_PIN, false, PinOwner::UM_Unspecified))
|
||||
if ((INTERRUPT_PIN < 0) || (!pinManager.isPinINT(INTERRUPT_PIN))) {
|
||||
//enabled = false;
|
||||
USER_PRINTF("mpu6050: warning - interrupt GPIO %d does not support interrupts.\n", INTERRUPT_PIN);
|
||||
//INTERRUPT_PIN = -1;
|
||||
//return;
|
||||
}
|
||||
if ((INTERRUPT_PIN >= 0) && (pinManager.getPinOwner(INTERRUPT_PIN) != PinOwner::UM_IMU) // only allocate pin if we don't ownn it already
|
||||
&& !pinManager.allocatePin(INTERRUPT_PIN, false, PinOwner::UM_IMU))
|
||||
{
|
||||
//enabled = false;
|
||||
USER_PRINTF("mpu6050: warning - failed to allocate interrupt GPIO %d\n", INTERRUPT_PIN);
|
||||
//INTERRUPT_PIN = -1;
|
||||
//return;
|
||||
}
|
||||
// WLEDMM end
|
||||
|
||||
mpu.initialize();
|
||||
pinMode(INTERRUPT_PIN, INPUT);
|
||||
if (INTERRUPT_PIN >= 0) { // WLEDMM only if pin is valid
|
||||
pinMode(INTERRUPT_PIN, INPUT);
|
||||
}
|
||||
|
||||
// verify connection
|
||||
DEBUG_PRINT_IMULN(F("Testing device connections..."));
|
||||
@@ -214,11 +232,13 @@ class MPU6050Driver : public Usermod {
|
||||
DEBUG_PRINT_IMULN(F("Enabling DMP..."));
|
||||
mpu.setDMPEnabled(true);
|
||||
|
||||
// enable Arduino interrupt detection
|
||||
DEBUG_PRINT_IMU(F("Enabling interrupt detection (Arduino external interrupt "));
|
||||
DEBUG_PRINT_IMU(digitalPinToInterrupt(INTERRUPT_PIN));
|
||||
DEBUG_PRINT_IMULN(F(")..."));
|
||||
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
|
||||
if (INTERRUPT_PIN >= 0) {
|
||||
// enable Arduino interrupt detection
|
||||
DEBUG_PRINT_IMU(F("Enabling interrupt detection (Arduino external interrupt "));
|
||||
DEBUG_PRINT_IMU(digitalPinToInterrupt(INTERRUPT_PIN));
|
||||
DEBUG_PRINT_IMULN(F(")..."));
|
||||
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
|
||||
}
|
||||
mpuIntStatus = mpu.getIntStatus();
|
||||
|
||||
// set our DMP Ready flag so the main loop() function knows it's okay to use it
|
||||
@@ -235,7 +255,9 @@ class MPU6050Driver : public Usermod {
|
||||
DEBUG_PRINT(F("DMP Initialization failed (code "));
|
||||
DEBUG_PRINT(devStatus);
|
||||
DEBUG_PRINTLN(")");
|
||||
dmpReady = false;
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void connected() {
|
||||
@@ -244,6 +266,7 @@ class MPU6050Driver : public Usermod {
|
||||
|
||||
void loop() {
|
||||
// if programming failed, don't try to do anything
|
||||
if (!initDone) return;
|
||||
if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice
|
||||
lastUMRun = millis(); // update time keeping
|
||||
|
||||
@@ -265,12 +288,15 @@ class MPU6050Driver : public Usermod {
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
if (!initDone) return;
|
||||
if (!enabled && !dmpReady) return; // WLEDMM no info when usermod disabled
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
StaticJsonDocument<800> doc; //measured 528 // WLEDMM added some margin (was 600)
|
||||
|
||||
JsonObject imu_meas = doc.createNestedObject("IMU");
|
||||
//JsonObject imu_meas = user.createNestedObject("IMU");
|
||||
#ifdef WLED_DEBUG
|
||||
JsonArray quat_json = imu_meas.createNestedArray("Quat");
|
||||
quat_json.add(qat.w);
|
||||
@@ -328,29 +354,64 @@ class MPU6050Driver : public Usermod {
|
||||
//{
|
||||
//}
|
||||
|
||||
// void addToConfig(JsonObject& root)
|
||||
// {
|
||||
// JsonObject top = root.createNestedObject("MPU6050");
|
||||
// top[FPSTR("enabled")] = enabled;
|
||||
// void addToConfig(JsonObject& root)
|
||||
// {
|
||||
// Usermod::addToConfig(root);
|
||||
// JsonObject top = root[FPSTR(_name)];
|
||||
// // //JsonObject interruptPin = top.createNestedObject(FPSTR(_INT_pin));
|
||||
// // //interruptPin["pin"] = INTERRUPT_PIN;
|
||||
// // DEBUG_PRINTLN(F("MPU6050 IMU config saved."));
|
||||
// }
|
||||
|
||||
// JsonObject interruptPin = top.createNestedObject(FPSTR("interruptPin"));
|
||||
// interruptPin["pin"] = interruptPin;
|
||||
// }
|
||||
//WLEDMM: add appendConfigData
|
||||
void appendConfigData()
|
||||
{
|
||||
oappend(SET_F("addHB('mpu6050-IMU');"));
|
||||
/*
|
||||
#ifdef MPU6050_INT_GPIO
|
||||
oappend(SET_F("xOpt('mpu6050-IMU:interrupt_pin',0,' ⎌',")); oappendi(MPU6050_INT_GPIO); oappend(");");
|
||||
#endif
|
||||
//WLEDMM add errorMessage to um settings
|
||||
if (strcmp(errorMessage, "") != 0) {
|
||||
oappend(SET_F("addInfo('errorMessage', 0, '<i>error: ")); oappend(errorMessage); oappend("! Correct and reboot</i>');");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// bool readFromConfig(JsonObject& root)
|
||||
// {
|
||||
// JsonObject top = root[FPSTR("MPU6050")];
|
||||
// bool configComplete = !top.isNull();
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
bool configComplete = Usermod::readFromConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
// configComplete &= getJsonValue(top[FPSTR("enabled")], enabled);
|
||||
// configComplete &= getJsonValue(top[FPSTR("interruptPin")]["pin"], interruptPin);
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// return configComplete;
|
||||
// }
|
||||
//configComplete &= getJsonValue(top[FPSTR(_INT_pin)]["pin"], INTERRUPT_PIN);
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
if (enabled || dmpReady) setup(); // re-run setup if user has checked "enabled"
|
||||
if (!enabled) dmpReady = false; // not enabled inplies "no DMP data ready"
|
||||
}
|
||||
|
||||
return configComplete;
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
//return !top[FPSTR(_INT_pin)].isNull();
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_IMU;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char MPU6050Driver::_INT_pin[] PROGMEM = "interrupt_pin";
|
||||
|
||||
@@ -202,11 +202,11 @@ class MultiRelay : public Usermod {
|
||||
/**
|
||||
* Enable/Disable the usermod
|
||||
*/
|
||||
inline void enable(bool enable) { enabled = enable; }
|
||||
// inline void enable(bool enable) { enabled = enable; }
|
||||
/**
|
||||
* Get usermod enabled/disabled state
|
||||
*/
|
||||
inline bool isEnabled() { return enabled; }
|
||||
// inline bool isEnabled() { return enabled; }
|
||||
|
||||
/**
|
||||
* switch relay on/off
|
||||
|
||||
@@ -79,6 +79,10 @@ class AutoSaveUsermod : public Usermod {
|
||||
month(localTime), day(localTime),
|
||||
hour(localTime), minute(localTime), second(localTime));
|
||||
cacheInvalidate++; // force reload of presets
|
||||
DEBUG_PRINT(F("UM autosave: saving preset "));
|
||||
DEBUG_PRINT(autoSavePreset);
|
||||
DEBUG_PRINT(F(" => "));
|
||||
DEBUG_PRINTLN(presetNameBuffer);
|
||||
savePreset(autoSavePreset, presetNameBuffer);
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ class AutoSaveUsermod : public Usermod {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display != nullptr) {
|
||||
display->wakeDisplay();
|
||||
display->overlay("Settings", "Auto Saved", 1500);
|
||||
if (display->canDraw()) display->overlay("Settings", "Auto Saved", 1500); // WLEDMM bugfix
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
10
usermods/usermod_v2_fastled/readme.md
Normal file
10
usermods/usermod_v2_fastled/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Usermods API v2 example usermod
|
||||
|
||||
In this usermod file you can find the documentation on how to take advantage of the new version 2 usermods!
|
||||
|
||||
## Installation
|
||||
|
||||
Copy `usermod_v2_fastled.h` to the wled00 directory.
|
||||
Uncomment the corresponding lines in `usermods_list.cpp` and compile!
|
||||
_(You shouldn't need to actually install this, it does nothing useful)_
|
||||
|
||||
358
usermods/usermod_v2_fastled/usermod_v2_fastled.h
Normal file
358
usermods/usermod_v2_fastled/usermod_v2_fastled.h
Normal file
@@ -0,0 +1,358 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
//WLEDMM
|
||||
|
||||
|
||||
// Polar basics demo for the
|
||||
// FastLED Podcast #2
|
||||
// https://www.youtube.com/watch?v=KKjFRZFBUrQ
|
||||
//
|
||||
// VO.1 preview version
|
||||
// by Stefan Petrick 2023
|
||||
// This code is licenced under a
|
||||
// Creative Commons Attribution
|
||||
// License CC BY-NC 3.0
|
||||
|
||||
class PolarBasics {
|
||||
private:
|
||||
|
||||
public:
|
||||
float runtime; // elapse ms since startup
|
||||
float newdist, newangle; // parameters for image reconstruction
|
||||
float z; // 3rd dimension for the 3d noise function
|
||||
float offset_x, offset_y; // wanna shift the cartesians during runtime?
|
||||
float scale_x, scale_y; // cartesian scaling in 2 dimensions
|
||||
float dist, angle; // the actual polar coordinates
|
||||
|
||||
int x, y; // the cartesian coordiantes
|
||||
int num_x;// = WIDTH; // horizontal pixel count
|
||||
int num_y;// = HEIGHT; // vertical pixel count
|
||||
|
||||
// Background for setting the following 2 numbers: the FastLED inoise16() function returns
|
||||
// raw values ranging from 0-65535. In order to improve contrast we filter this output and
|
||||
// stretch the remains. In histogram (photography) terms this means setting a blackpoint and
|
||||
// a whitepoint. low_limit MUST be smaller than high_limit.
|
||||
|
||||
uint16_t low_limit;// = 30000; // everything lower drawns in black
|
||||
// higher numer = more black & more contrast present
|
||||
uint16_t high_limit;// = 50000; // everything higher gets maximum brightness & bleeds out
|
||||
// lower number = the result will be more bright & shiny
|
||||
|
||||
float center_x;// = (num_x / 2) - 0.5; // the reference point for polar coordinates
|
||||
float center_y;// = (num_y / 2) - 0.5; // (can also be outside of the actual xy matrix)
|
||||
//float center_x = 20; // the reference point for polar coordinates
|
||||
//float center_y = 20;
|
||||
|
||||
//WLEDMM: assign 32x32 fixed for the time being
|
||||
float theta [32] [32]; // look-up table for all angles
|
||||
float distance[32] [32]; // look-up table for all distances
|
||||
float vignette[32] [32];
|
||||
float inverse_vignette[32] [32];
|
||||
|
||||
// std::vector<std::vector<float>> theta; // look-up table for all angles
|
||||
// std::vector<std::vector<float>> distance; // look-up table for all distances
|
||||
// std::vector<std::vector<float>> vignette;
|
||||
// std::vector<std::vector<float>> inverse_vignette;
|
||||
|
||||
float spd; // can be used for animation speed manipulation during runtime
|
||||
|
||||
float show1, show2, show3, show4, show5; // to save the rendered values of all animation layers
|
||||
float red, green, blue; // for the final RGB results after the colormapping
|
||||
|
||||
float c, d, e, f; // factors for oscillators
|
||||
float linear_c, linear_d, linear_e, linear_f; // linear offsets
|
||||
float angle_c, angle_d, angle_e, angle_f; // angle offsets
|
||||
float noise_angle_c, noise_angle_d, noise_angle_e, noise_angle_f; // angles based on linear noise travel
|
||||
float dir_c, dir_d, dir_e, dir_f; // direction multiplicators
|
||||
|
||||
void init () {
|
||||
num_x = SEGMENT.virtualWidth(); // horizontal pixel count
|
||||
num_y = SEGMENT.virtualHeight(); // vertical pixel count
|
||||
low_limit = 30000; // everything lower drawns in black
|
||||
// higher numer = more black & more contrast present
|
||||
high_limit = 50000; // everything higher gets maximum brightness & bleeds out
|
||||
// lower number = the result will be more bright & shiny
|
||||
center_x = (num_x / 2) - 0.5; // the reference point for polar coordinates
|
||||
center_y = (num_y / 2) - 0.5; // (can also be outside of the actual xy matrix)
|
||||
|
||||
//allocate memory for the 2D arrays
|
||||
// theta.resize(num_x, std::vector<float>(num_y, 0));
|
||||
// distance.resize(num_x, std::vector<float>(num_y, 0));
|
||||
// vignette.resize(num_x, std::vector<float>(num_y, 0));
|
||||
// inverse_vignette.resize(num_x, std::vector<float>(num_y, 0));
|
||||
}
|
||||
|
||||
PolarBasics() {
|
||||
USER_PRINTLN("constructor");
|
||||
}
|
||||
~PolarBasics() {
|
||||
USER_PRINTLN("destructor");
|
||||
}
|
||||
|
||||
void speedratiosAndOscillators() {
|
||||
// set speedratios for the offsets & oscillators
|
||||
|
||||
spd = 0.05 ;
|
||||
c = 0.013 ;
|
||||
d = 0.017 ;
|
||||
e = 0.2 ;
|
||||
f = 0.007 ;
|
||||
|
||||
calculate_oscillators(); // get linear offsets and oscillators going
|
||||
}
|
||||
|
||||
void forLoop() {
|
||||
// pick polar coordinates from look the up table
|
||||
|
||||
dist = distance [x] [y];
|
||||
angle = theta [y] [x];
|
||||
|
||||
// Generation of one layer. Explore the parameters and what they do.
|
||||
|
||||
scale_x = 10000; // smaller value = zoom in, bigger structures, less detail
|
||||
scale_y = 10000; // higher = zoom out, more pixelated, more detail
|
||||
z = 0; // must be >= 0
|
||||
newangle = angle + angle_c;
|
||||
newdist = dist;
|
||||
offset_x = 0; // must be >=0
|
||||
offset_y = 0; // must be >=0
|
||||
|
||||
show1 = render_pixel();
|
||||
|
||||
|
||||
// Colormapping - Assign rendered values to colors
|
||||
|
||||
red = show1;
|
||||
green = 0;
|
||||
blue = 0;
|
||||
|
||||
// Check the final results.
|
||||
// Discard faulty RGB values & write the valid results into the framebuffer.
|
||||
|
||||
write_pixel_to_framebuffer();
|
||||
}
|
||||
|
||||
void calculate_oscillators() {
|
||||
|
||||
runtime = millis(); // save elapsed ms since start up
|
||||
|
||||
runtime = runtime * spd; // global anaimation speed
|
||||
|
||||
linear_c = runtime * c; // some linear rising offsets 0 to max
|
||||
linear_d = runtime * d;
|
||||
linear_e = runtime * e;
|
||||
linear_f = runtime * f;
|
||||
|
||||
angle_c = fmodf(linear_c, 2 * PI); // some cyclic angle offsets 0 to 2*PI
|
||||
angle_d = fmodf(linear_d, 2 * PI);
|
||||
angle_e = fmodf(linear_e, 2 * PI);
|
||||
angle_f = fmodf(linear_f, 2 * PI);
|
||||
|
||||
dir_c = sinf(angle_c); // some direction oscillators -1 to 1
|
||||
dir_d = sinf(angle_d);
|
||||
dir_e = sinf(angle_e);
|
||||
dir_f = sinf(angle_f);
|
||||
|
||||
uint16_t noi;
|
||||
noi = inoise16(10000 + linear_c * 100000); // some noise controlled angular offsets
|
||||
noise_angle_c = map_float(noi, 0, 65535 , 0, 4*PI);
|
||||
noi = inoise16(20000 + linear_d * 100000);
|
||||
noise_angle_d = map_float(noi, 0, 65535 , 0, 4*PI);
|
||||
noi = inoise16(30000 + linear_e * 100000);
|
||||
noise_angle_e = map_float(noi, 0, 65535 , 0, 4*PI);
|
||||
noi = inoise16(40000 + linear_f * 100000);
|
||||
noise_angle_f = map_float(noi, 0, 65535 , 0, 4*PI);
|
||||
}
|
||||
|
||||
|
||||
// given a static polar origin we can precalculate
|
||||
// all the (expensive) polar coordinates
|
||||
|
||||
void render_polar_lookup_table() {
|
||||
|
||||
for (int xx = 0; xx < num_x; xx++) {
|
||||
for (int yy = 0; yy < num_y; yy++) {
|
||||
|
||||
float dx = xx - center_x;
|
||||
float dy = yy - center_y;
|
||||
|
||||
distance[xx] [yy] = hypotf(dx, dy);
|
||||
theta[xx] [yy] = atan2f(dy, dx);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert polar coordinates back to cartesian
|
||||
// & render noise value there
|
||||
|
||||
float render_pixel() {
|
||||
|
||||
// convert polar coordinates back to cartesian ones
|
||||
|
||||
float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
|
||||
float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
|
||||
|
||||
// render noisevalue at this new cartesian point
|
||||
|
||||
uint16_t raw_noise_field_value = inoise16(newx, newy, z);
|
||||
|
||||
// a lot is happening here, namely
|
||||
// A) enhance histogram (improve contrast) by setting the black and white point
|
||||
// B) scale the result to a 0-255 range
|
||||
// it's the contrast boosting & the "colormapping" (technically brightness mapping)
|
||||
|
||||
if (raw_noise_field_value < low_limit) raw_noise_field_value = low_limit;
|
||||
if (raw_noise_field_value > high_limit) raw_noise_field_value = high_limit;
|
||||
|
||||
float scaled_noise_value = map_float(raw_noise_field_value, low_limit, high_limit, 0, 255);
|
||||
|
||||
return scaled_noise_value;
|
||||
|
||||
// done, we've just rendered one color value for one single pixel
|
||||
}
|
||||
|
||||
// float mapping maintaining 32 bit precision
|
||||
// we keep values with high resolution for potential later usage
|
||||
|
||||
float map_float(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
|
||||
float result = (x-in_min) * (out_max-out_min) / (in_max-in_min) + out_min;
|
||||
if (result < out_min) result = out_min;
|
||||
if( result > out_max) result = out_max;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Avoid any possible color flicker by forcing the raw RGB values to be 0-255.
|
||||
// This enables to play freely with random equations for the colormapping
|
||||
// without causing flicker by accidentally missing the valid target range.
|
||||
|
||||
void rgb_sanity_check() {
|
||||
|
||||
// rescue data if possible: when negative return absolute value
|
||||
if (red < 0) red = abs(red);
|
||||
if (green < 0) green = abs(green);
|
||||
if (blue < 0) blue = abs(blue);
|
||||
|
||||
// discard everything above the valid 0-255 range
|
||||
if (red > 255) red = 255;
|
||||
if (green > 255) green = 255;
|
||||
if (blue > 255) blue = 255;
|
||||
|
||||
}
|
||||
|
||||
void write_pixel_to_framebuffer() {
|
||||
|
||||
// the final color values shall not exceed 255 (to avoid flickering pixels caused by >255 = black...)
|
||||
// negative values * -1
|
||||
|
||||
rgb_sanity_check();
|
||||
|
||||
CRGB finalcolor = CRGB(red, green, blue);
|
||||
|
||||
// write the rendered pixel into the framebutter
|
||||
SEGMENT.setPixelColorXY(x,y,finalcolor);
|
||||
}
|
||||
|
||||
// precalculate a radial brightness mask
|
||||
|
||||
void render_vignette_table(float filter_radius) {
|
||||
|
||||
for (int xx = 0; xx < num_x; xx++) {
|
||||
for (int yy = 0; yy < num_y; yy++) {
|
||||
|
||||
vignette[xx] [yy] = (filter_radius - distance[xx] [yy]) / filter_radius;
|
||||
if (vignette[xx] [yy] < 0) vignette[xx] [yy] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//effect functions
|
||||
uint16_t mode_PolarBasics(void) {
|
||||
|
||||
PolarBasics* pb;
|
||||
|
||||
|
||||
if(!SEGENV.allocateData(sizeof(PolarBasics))) {SEGMENT.fill(SEGCOLOR(0)); return 350;} //mode_static(); //allocation failed
|
||||
|
||||
pb = reinterpret_cast<PolarBasics*>(SEGENV.data);
|
||||
|
||||
//first time init
|
||||
if (SEGENV.call == 0) {
|
||||
|
||||
USER_PRINTF("mode_PolarBasics %d\n", sizeof(PolarBasics));
|
||||
// if (SEGENV.call == 0) SEGMENT.setUpLeds();
|
||||
|
||||
pb->init();
|
||||
|
||||
pb->render_polar_lookup_table(); // precalculate all polar coordinates
|
||||
// to improve the framerate
|
||||
pb->render_vignette_table(9.5); // the number is the desired radius in pixel
|
||||
// WIDTH/2 generates a circle
|
||||
}
|
||||
|
||||
pb->speedratiosAndOscillators();
|
||||
|
||||
// ...and now let's generate a frame
|
||||
|
||||
for (pb->x = 0; pb->x < pb->num_x; pb->x++) {
|
||||
for (pb->y = 0; pb->y < pb->num_y; pb->y++) {
|
||||
|
||||
pb->forLoop();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// FastLED.show();
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
static const char _data_FX_mode_PolarBasics[] PROGMEM = "💡Polar Basics ☾@;;;2";
|
||||
|
||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||
class FastledUsermod : public Usermod {
|
||||
|
||||
private:
|
||||
|
||||
|
||||
public:
|
||||
|
||||
FastledUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM
|
||||
|
||||
void setup() {
|
||||
strip.addEffect(255, &mode_PolarBasics, _data_FX_mode_PolarBasics);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
|
||||
void connected() {
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
// do your magic here
|
||||
if (millis() - lastTime > 1000) {
|
||||
//Serial.println("I'm alive!");
|
||||
lastTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_FASTLED;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,35 +2,37 @@
|
||||
|
||||
#include <Arduino.h> // WLEDMM: make sure that I2C drivers have the "right" Wire Object
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#undef U8X8_NO_HW_I2C // WLEDMM: we do want I2C hardware drivers - if possible
|
||||
//#define WIRE_INTERFACES_COUNT 2 // experimental - tell U8x8Lib that there is a econd Wire unit
|
||||
//#define WIRE_INTERFACES_COUNT 2 // experimental - tell U8x8Lib that there is a second Wire unit
|
||||
|
||||
#include "wled.h"
|
||||
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
||||
#include "4LD_wled_fonts.c"
|
||||
|
||||
#ifndef FLD_ESP32_NO_THREADS
|
||||
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
|
||||
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behaviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
|
||||
#endif
|
||||
|
||||
//#define OLD_4LD_FONTS // comment out if you prefer the "classic" look with blocky fonts (saves 1K flash)
|
||||
|
||||
//
|
||||
// Insired by the usermod_v2_four_line_display
|
||||
// Inspired by the usermod_v2_four_line_display
|
||||
//
|
||||
// v2 usermod for using 128x32 or 128x64 i2c
|
||||
// OLED displays to provide a four line display
|
||||
// for WLED.
|
||||
//
|
||||
// Dependencies
|
||||
// * This usermod REQURES the ModeSortUsermod
|
||||
// * This Usermod works best, by far, when coupled
|
||||
// with RotaryEncoderUIUsermod.
|
||||
// * This usermod does not REQUIRE the ModeSortUsermod any more
|
||||
// * This usermod works best, by far, when coupled
|
||||
// with RotaryEncoderUI_ALT usermod.
|
||||
//
|
||||
// Make sure to enable NTP and set your time zone in WLED Config | Time.
|
||||
//
|
||||
// REQUIREMENT: You must add the following requirements to
|
||||
// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini
|
||||
// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
|
||||
// REQUIREMENT: * Wire
|
||||
// REQUIREMENT: olikraus/U8g2@ ^2.34.15 (the version already in platformio.ini is fine)
|
||||
//
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
@@ -92,7 +94,9 @@ typedef enum {
|
||||
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
|
||||
SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
|
||||
SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI
|
||||
SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI
|
||||
SSD1306_SPI64=7, // U8X8_SSD1306_128X64_NONAME_HW_SPI
|
||||
SSD1309_SPI64=8, // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI
|
||||
SSD1327_SPI128=9 // U8X8_SSD1327_WS_128X128_4W_SW_SPI
|
||||
} DisplayType;
|
||||
|
||||
|
||||
@@ -115,6 +119,23 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// HW interface & configuration
|
||||
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
|
||||
// semaphores - needed on ESP32 only, as we use a separate task to update the display
|
||||
SemaphoreHandle_t drawMux = xSemaphoreCreateBinary(); // for drawstring and drawglyph functions (to prevent concurrent access to HW)
|
||||
SemaphoreHandle_t drawMuxBig = xSemaphoreCreateBinary(); // for draw2x2GlyphIcons() and showCurrentEffectOrPalette() - more complex and not thread-safe
|
||||
const TickType_t maxWait = 300 * portTICK_PERIOD_MS; // wait max. 300ms (drawstring semaphore)
|
||||
const TickType_t maxWaitLong = 800 * portTICK_PERIOD_MS; // wait max. 800ms (big drawing semaphore)
|
||||
#define FLD_SemaphoreTake(x,t) xSemaphoreTake((x),(t))
|
||||
#define FLD_SemaphoreGive(x) xSemaphoreGive(x)
|
||||
#else
|
||||
// 8266 or no tasks - no semaphores
|
||||
#define FLD_SemaphoreTake(x,t) pdTRUE
|
||||
#define FLD_SemaphoreGive(x)
|
||||
#if !defined(ARDUINO_ARCH_ESP32) && !defined(pdTRUE)
|
||||
#define pdTRUE true
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef FLD_SPI_DEFAULT
|
||||
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
|
||||
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
|
||||
@@ -124,7 +145,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
#endif
|
||||
|
||||
DisplayType type = FLD_TYPE; // display type
|
||||
bool typeOK = false; //WLEDMM: instead of type == NULL and type=NULL. Intially false, as display was not initialized yet
|
||||
bool typeOK = false; //WLEDMM: instead of type == NULL and type=NULL. Initially false, as display was not initialized yet
|
||||
bool flip = false; // flip display 180°
|
||||
uint8_t contrast = 10; // screen contrast
|
||||
uint8_t lineHeight = 1; // 1 row or 2 rows
|
||||
@@ -143,6 +164,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
bool enabled = true;
|
||||
#endif
|
||||
bool contrastFix = false;
|
||||
bool driverHW = false;
|
||||
bool driverSPI = false;
|
||||
|
||||
// Next variables hold the previous known values to determine if redraw is
|
||||
// required.
|
||||
@@ -193,7 +216,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// some displays need this to properly apply contrast
|
||||
void setVcomh(bool highContrast) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (!canDraw()) return; // don't interfere with ongoing draw
|
||||
if (u8x8 == nullptr) return;
|
||||
|
||||
u8x8_t *u8x8_struct = u8x8->getU8x8();
|
||||
u8x8_cad_StartTransfer(u8x8_struct);
|
||||
@@ -207,33 +230,77 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
*/
|
||||
void setFlipMode(uint8_t mode) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (canDraw()) u8x8->setFlipMode(mode);
|
||||
if (u8x8 == nullptr) return;
|
||||
u8x8->setFlipMode(mode);
|
||||
}
|
||||
void setContrast(uint8_t contrast) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (canDraw()) u8x8->setContrast(contrast);
|
||||
if (u8x8 == nullptr) return;
|
||||
u8x8->setContrast(contrast);
|
||||
}
|
||||
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r); // crashes randomly on ESP32
|
||||
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); // crashes randomly on ESP32
|
||||
else u8x8->drawString(col, row, string);
|
||||
if (u8x8 == nullptr) return;
|
||||
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
|
||||
if (!ignoreLH && lineHeight>1) {
|
||||
if(strlen(string) > 3) // WLEDMM little hack - less than 3 chars -> show in bold
|
||||
u8x8->setFont(u8x8_font_7x14_1x2_r); // normal
|
||||
else
|
||||
u8x8->setFont(u8x8_font_8x13B_1x2_r); // bold
|
||||
u8x8->drawString(col, row, string);
|
||||
}
|
||||
else {
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8->drawString(col, row, string);
|
||||
}
|
||||
#else
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r);
|
||||
if (!ignoreLH && lineHeight>1) u8x8->draw1x2String(col, row, string);
|
||||
else u8x8->drawString(col, row, string);
|
||||
#endif
|
||||
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
|
||||
}
|
||||
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
|
||||
if (!typeOK || !enabled) return;
|
||||
if (u8x8 == nullptr) return;
|
||||
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
|
||||
if (lineHeight>1) { // WLEDMM use 2x3 on 128x64 displays
|
||||
//u8x8->setFont(u8x8_font_profont29_2x3_r); // sans serif 2x3
|
||||
u8x8->setFont(u8x8_font_courB18_2x3_r); // courier bold 2x3
|
||||
u8x8->drawString(col, row + (row >3? 1:0), string);
|
||||
} else {
|
||||
//u8x8->setFont(u8x8_font_lucasarts_scumm_subtitle_o_2x2_r);
|
||||
//u8x8->setFont(u8x8_font_lucasarts_scumm_subtitle_r_2x2_r);
|
||||
u8x8->setFont(u8x8_font_px437wyse700b_2x2_r);
|
||||
u8x8->drawString(col, row, string);
|
||||
}
|
||||
#else
|
||||
u8x8->setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8->draw2x2String(col, row, string);
|
||||
if (lineHeight>1) { // WLEDMM use 2x3 on 128x64 displays
|
||||
u8x8->draw2x2String(col, row + (row >3? 1:0), string);
|
||||
} else {
|
||||
u8x8->draw2x2String(col, row, string);
|
||||
}
|
||||
#endif
|
||||
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
|
||||
}
|
||||
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
|
||||
if (!typeOK || !enabled) return;
|
||||
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
|
||||
u8x8->setFont(font);
|
||||
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
|
||||
if (!ignoreLH && lineHeight>1) u8x8->draw1x2Glyph(col, row, glyph);
|
||||
else u8x8->drawGlyph(col, row, glyph);
|
||||
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
|
||||
}
|
||||
void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
|
||||
if (!typeOK || !enabled) return;
|
||||
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
|
||||
u8x8->setFont(font);
|
||||
u8x8->draw2x2Glyph(col, row, glyph);
|
||||
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
|
||||
}
|
||||
uint8_t getCols() {
|
||||
if (!typeOK || !enabled) return 0;
|
||||
@@ -242,10 +309,13 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
void clear() {
|
||||
if (!typeOK || !enabled) return;
|
||||
if (nullptr == u8x8) return; // prevents some crashes
|
||||
if (FLD_SemaphoreTake(drawMux, maxWaitLong ) != pdTRUE) return; // WLEDMM acquire draw mutex - clear() can take very long in software I2C mode
|
||||
u8x8->clear(); // crashes randomly on ESP32
|
||||
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
|
||||
}
|
||||
void setPowerSave(uint8_t save) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (u8x8 == nullptr) return;
|
||||
u8x8->setPowerSave(save);
|
||||
}
|
||||
|
||||
@@ -257,7 +327,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
|
||||
void draw2x2GlyphIcons() {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (lineHeight == 2) {
|
||||
if (FLD_SemaphoreTake(drawMuxBig, maxWaitLong) != pdTRUE) return; // WLEDMM acquire BIG draw mutex
|
||||
if (lineHeight > 1) {
|
||||
drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
|
||||
drawGlyph( 5, 0, 2, u8x8_4LineDisplay_WLED_icons_2x2, true); //speed icon
|
||||
drawGlyph( 9, 0, 3, u8x8_4LineDisplay_WLED_icons_2x2, true); //intensity icon
|
||||
@@ -270,6 +341,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
drawGlyph(15, 2, 4, u8x8_4LineDisplay_WLED_icons_1x1); //palette icon
|
||||
drawGlyph(15, 3, 5, u8x8_4LineDisplay_WLED_icons_1x1); //effect icon
|
||||
}
|
||||
FLD_SemaphoreGive(drawMuxBig); // WLEDMM release BIG draw mutex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,10 +377,25 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
snprintf_P(lineBuffer,LINE_BUFFER_SIZE, PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent);
|
||||
draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
|
||||
if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
|
||||
if (useAMPM) drawString(12, lineHeight*2 + (lineHeight-1), (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
|
||||
|
||||
drawStatusIcons(); //icons power, wifi, timer, etc
|
||||
|
||||
if (lineHeight > 1) { // WLEDMM use extra space for useful information
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
|
||||
snprintf_P(lineBuffer, LINE_BUFFER_SIZE, PSTR(" %-3.3s %-2.2s "), driverSPI? "SPI" : "I2C", driverHW? "hw" : "sw"); // WLEDMM driver info
|
||||
#else
|
||||
strncpy_P(lineBuffer, PSTR(" "), LINE_BUFFER_SIZE);
|
||||
#endif
|
||||
if (apActive) strncpy_P(lineBuffer, PSTR(" AP mode "), LINE_BUFFER_SIZE);
|
||||
else if (!WLED_CONNECTED) strncpy_P(lineBuffer, PSTR(" NO NET "), LINE_BUFFER_SIZE);
|
||||
if (WLED_MQTT_CONNECTED) lineBuffer[9] = 'M'; // "MQTT"
|
||||
if (realtimeMode && !realtimeOverride) lineBuffer[10] = 'X'; // "eXternal control"
|
||||
//if (transitionActive) lineBuffer[11] = 'T';
|
||||
//if (stateChanged) lineBuffer[12] = 'C';
|
||||
drawString(1, 0, lineBuffer, false);
|
||||
}
|
||||
|
||||
knownMinute = minuteCurrent;
|
||||
knownHour = hourCurrent;
|
||||
}
|
||||
@@ -316,7 +403,10 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
lastSecond = secondCurrent;
|
||||
draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":");
|
||||
snprintf_P(lineBuffer, LINE_BUFFER_SIZE, PSTR("%02d"), secondCurrent);
|
||||
drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
|
||||
if (useAMPM)
|
||||
drawString(12, lineHeight*2+1 + (lineHeight-1), lineBuffer, true); // even with double sized rows print seconds in 1 line // WLEDMM move it a bit lower
|
||||
else
|
||||
drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
|
||||
}
|
||||
drawing = false;
|
||||
}
|
||||
@@ -326,16 +416,26 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void setup() {
|
||||
if (!enabled) return; // typeOK = true will be set after successfull setup
|
||||
if (!enabled) return; // typeOK = true will be set after successful setup
|
||||
|
||||
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
|
||||
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
|
||||
PinOwner po = PinOwner::UM_FourLineDisplay;
|
||||
if (isSPI) {
|
||||
if (ioPin[0] < 0 || ioPin[1] < 0) {
|
||||
ioPin[0] = spi_sclk;
|
||||
ioPin[1] = spi_mosi;
|
||||
} else {
|
||||
if ((spi_sclk < 0) && (spi_mosi < 0)) { // WLEDMM UM pins are valid, but global = -1 --> copy pins to "global"
|
||||
spi_sclk = ioPin[0];
|
||||
spi_mosi = ioPin[1];
|
||||
}
|
||||
}
|
||||
if ((ioPin[0] < 0 || ioPin[1] < 0) && (spi_sclk < 0 || spi_mosi < 0)) { // invalid pins, or "use global" and global pins not defined
|
||||
typeOK=false; strcpy(errorMessage, PSTR("SPI No Pins defined")); return; } //WLEDMM bugfix - ensure that "final" GPIO are valid
|
||||
|
||||
isHW = (ioPin[0]==spi_sclk && ioPin[1]==spi_mosi);
|
||||
if ((ioPin[0] == -1) || (ioPin[1] == -1)) isHW = true; // WLEDMM "use global" = hardware
|
||||
if ((spi_sclk <0) || (spi_mosi < 0)) isHW = false; // no global pins - use software emulation
|
||||
PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } };
|
||||
if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { typeOK=false; strcpy(errorMessage, PSTR("SPI3 alloc pins failed")); return; }
|
||||
if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins
|
||||
@@ -346,25 +446,36 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
strcpy(errorMessage, PSTR("SPI2 alloc pins failed"));
|
||||
return;
|
||||
}
|
||||
// start SPI now!
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (isHW) SPI.begin(spi_sclk, spi_miso, spi_mosi); // ESP32 - will silently fail if SPI alread active.
|
||||
#else
|
||||
if (isHW) SPI.begin(); // ESP8266 - SPI pins are fixed
|
||||
#endif
|
||||
|
||||
} else {
|
||||
if (ioPin[0] < 0 || ioPin[1] < 0) {
|
||||
ioPin[0] = i2c_scl;
|
||||
ioPin[1] = i2c_sda;
|
||||
}
|
||||
//if (ioPin[0] < 0 || ioPin[1] < 0) { //WLEDMM do _not_ copy global pins !!
|
||||
// ioPin[0] = i2c_scl;
|
||||
// ioPin[1] = i2c_sda;
|
||||
//}
|
||||
isHW = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda);
|
||||
if ((ioPin[0] == -1) || (ioPin[1] == -1)) isHW = true; // WLEDMM "use global" = hardware
|
||||
// isHW = true;
|
||||
if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } };
|
||||
|
||||
if (ioPin[0] < 0 || ioPin[1] < 0) { typeOK=false; strcpy(errorMessage, PSTR("I2C No Pins defined")); return; } //WLEDMM bugfix - ensure that "final" GPIO are valid
|
||||
if ((ioPin[0] < 0 || ioPin[1] < 0) && (i2c_scl < 0 || i2c_sda < 0)) { // invalid pins, or "use global" and global pins not defined
|
||||
typeOK=false; strcpy(errorMessage, PSTR("I2C No Pins defined")); return; } //WLEDMM bugfix - ensure that "final" GPIO are valid
|
||||
|
||||
if (isHW) {
|
||||
if (!pinManager.joinWire(ioPin[1], ioPin[0])) { typeOK=false; strcpy(errorMessage, PSTR("I2C init failed")); return; } // WLEDMM join the HW bus
|
||||
if (!pinManager.joinWire(i2c_sda, i2c_scl)) { typeOK=false; strcpy(errorMessage, PSTR("I2C HW init failed")); return; } // WLEDMM join the HW bus
|
||||
} else {
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, po)) { typeOK=false; strcpy(errorMessage, PSTR("I2C Alloc pins failed")); return; } // WLEDMM use software bus
|
||||
}
|
||||
}
|
||||
|
||||
driverHW = isHW;
|
||||
driverSPI= isSPI;
|
||||
DEBUG_PRINTLN(F("Allocating display."));
|
||||
/*
|
||||
// At some point it may be good to not new/delete U8X8 object but use this instead
|
||||
@@ -436,19 +547,31 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
break;
|
||||
case SSD1309_SPI64:
|
||||
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
break;
|
||||
case SSD1327_SPI128:
|
||||
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1327_WS_128X128_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1327_WS_128X128_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
break;
|
||||
default:
|
||||
u8x8 = nullptr;
|
||||
}
|
||||
|
||||
if (nullptr == u8x8) {
|
||||
USER_PRINTLN(F("Display init failed."));
|
||||
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po);
|
||||
if (!isHW || !isSPI) pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po); // WLEDMM do not de-alloc global pins
|
||||
typeOK=false;
|
||||
strcpy(errorMessage, PSTR("Display init failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
lineHeight = u8x8->getRows() > 4 ? 2 : 1;
|
||||
if (u8x8->getRows() > 8) lineHeight =3;
|
||||
//if (u8x8->getRows() > 12) lineHeight =4;
|
||||
if (isSPI) {
|
||||
USER_PRINTLN(isHW ? F("Starting display (SPI HW).") : F("Starting display (SPI Soft)."));
|
||||
} else {
|
||||
@@ -457,15 +580,24 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
u8x8->setBusClock(ioFrequency); // can be used for SPI too
|
||||
u8x8->begin();
|
||||
typeOK = true;
|
||||
drawing = false;
|
||||
|
||||
reDrawing = false;
|
||||
drawing = true;
|
||||
setFlipMode(flip);
|
||||
setVcomh(contrastFix);
|
||||
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
setPowerSave(0);
|
||||
drawing = false;
|
||||
|
||||
// init semaphores to allow drawing
|
||||
FLD_SemaphoreGive(drawMux);
|
||||
FLD_SemaphoreGive(drawMuxBig);
|
||||
|
||||
onUpdateBegin(false); // create Display task // WLEDMM bugfix: before drawing anything
|
||||
delay(200);
|
||||
|
||||
//drawString(0, 0, "Loading...");
|
||||
overlayLogo(3500);
|
||||
onUpdateBegin(false); // create Display task
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -502,6 +634,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
|
||||
//function to to check if a redraw or overlay draw is active. Needed for UM Rotary, to avoid random/concurrent drawing
|
||||
bool canDraw(void) {
|
||||
if (!typeOK || !enabled || !initDone) return(false); // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) // only necessary on ESP32
|
||||
if (drawing) return(false); // overlay draws someting
|
||||
if (reDrawing) return(false); // redraw task draws something
|
||||
@@ -609,7 +742,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// and turn it back on if it changed.
|
||||
clear();
|
||||
sleepOrClock(true);
|
||||
} else if (displayTurnedOff && ntpEnabled) {
|
||||
} else if (displayTurnedOff) { // WLEDMM removed "&& ntpEnabled"
|
||||
showTime();
|
||||
}
|
||||
return;
|
||||
@@ -686,10 +819,10 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
uint8_t col = 15;
|
||||
uint8_t row = 0;
|
||||
drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon
|
||||
if (lineHeight==2) { col--; } else { row++; }
|
||||
if (lineHeight>1) { col--; } else { row++; }
|
||||
drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon
|
||||
if (lineHeight==2) { col--; } else { col = row = 0; }
|
||||
drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode
|
||||
if (lineHeight>1) { col--; } else { col = row = 0; }
|
||||
drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nightlight mode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,7 +835,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
markColNum = newMarkColNum;
|
||||
}
|
||||
|
||||
//Draw the arrow for the current setting beiong changed
|
||||
//Draw the arrow for the current setting being changed
|
||||
void drawArrow() {
|
||||
if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1);
|
||||
}
|
||||
@@ -712,6 +845,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
|
||||
char lineBuffer[MAX_JSON_CHARS] = { '\0' };
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (FLD_SemaphoreTake(drawMuxBig, maxWaitLong) != pdTRUE) return; // WLEDMM acquire BIG draw mutex
|
||||
|
||||
if (overlayUntil == 0) {
|
||||
// Find the mode name in JSON
|
||||
uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1);
|
||||
@@ -725,7 +860,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0'
|
||||
printedChars -= 5;
|
||||
}
|
||||
if (lineHeight == 2) { // use this code for 8 line display
|
||||
if (lineHeight > 1) { // use this code for 8 line display
|
||||
char smallBuffer1[MAX_MODE_LINE_SPACE+1] = { '\0' };
|
||||
char smallBuffer2[MAX_MODE_LINE_SPACE+1] = { '\0' };
|
||||
uint8_t smallChars1 = 0;
|
||||
@@ -754,11 +889,11 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' ';
|
||||
smallBuffer1[smallChars1] = 0;
|
||||
smallBuffer1[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
|
||||
smallBuffer1[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop above can overshoot by 1)
|
||||
drawString(1, row*lineHeight, smallBuffer1, true);
|
||||
while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' ';
|
||||
smallBuffer2[smallChars2] = 0;
|
||||
smallBuffer2[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
|
||||
smallBuffer2[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop above can overshoot by 1)
|
||||
drawString(1, row*lineHeight+1, smallBuffer2, true);
|
||||
}
|
||||
} else { // use this code for 4 ling displays
|
||||
@@ -769,6 +904,8 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
drawString(1, row*lineHeight, smallBuffer3, true);
|
||||
}
|
||||
}
|
||||
|
||||
FLD_SemaphoreGive(drawMuxBig); // WLEDMM release BIG draw mutex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -779,6 +916,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
*/
|
||||
bool wakeDisplay() {
|
||||
if (!typeOK || !enabled) return false;
|
||||
if (!initDone) return false;
|
||||
if (displayTurnedOff) {
|
||||
unsigned long now = millis();
|
||||
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
|
||||
@@ -800,6 +938,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
*/
|
||||
void overlay(const char* line1, long showHowLong, byte glyphType) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (!initDone) return; // WLEDMM bugfix
|
||||
unsigned long now = millis();
|
||||
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
|
||||
while ((reDrawing && overlayUntil == 0) && (millis()-now < 250)) delay(10); // wait if someone else is re-drawing
|
||||
@@ -808,7 +947,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
if (!wakeDisplay()) clear();
|
||||
// Print the overlay
|
||||
if (glyphType>0 && glyphType<255) {
|
||||
if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font
|
||||
if (lineHeight > 1) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font
|
||||
else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true);
|
||||
}
|
||||
if (line1) {
|
||||
@@ -832,7 +971,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// Turn the display back on
|
||||
if (!wakeDisplay()) clear();
|
||||
// Print the overlay
|
||||
if (lineHeight == 2) {
|
||||
if (lineHeight > 1) {
|
||||
//add a bit of randomness
|
||||
switch (millis()%3) {
|
||||
case 0:
|
||||
@@ -888,6 +1027,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
*/
|
||||
void overlay(const char* line1, const char* line2, long showHowLong) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (!initDone) return; // WLEDMM bugfix
|
||||
unsigned long now = millis();
|
||||
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
|
||||
while ((reDrawing && overlayUntil == 0) && (millis()-now < 250)) delay(10); // wait if someone else is re-drawing
|
||||
@@ -911,6 +1051,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
|
||||
void networkOverlay(const char* line1, long showHowLong) {
|
||||
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
|
||||
if (!initDone) return; // WLEDMM bugfix
|
||||
unsigned long now = millis();
|
||||
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
|
||||
drawing = true;
|
||||
@@ -952,10 +1093,11 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
/**
|
||||
* Enable sleep (turn the display off) or clock mode.
|
||||
*/
|
||||
void sleepOrClock(bool enabled) {
|
||||
if (enabled) {
|
||||
void sleepOrClock(bool sleepEnable) {
|
||||
if (sleepEnable) {
|
||||
displayTurnedOff = true;
|
||||
if (clockMode && ntpEnabled) {
|
||||
//setContrast(contrastFix? 2+ contrast/4 : 0); // un-comment to dim display in "clock mode"
|
||||
if (clockMode) { // WLEDMM removed " && ntpEnabled"
|
||||
knownMinute = knownHour = 99;
|
||||
showTime();
|
||||
} else
|
||||
@@ -963,6 +1105,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
} else {
|
||||
displayTurnedOff = false;
|
||||
setPowerSave(0);
|
||||
//setContrast(contrast); // un-comment to restore display brightness on wakeup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,7 +1202,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
xTaskCreateUniversal( // this is guaranteed to work on any ESP32 (single or dual core)
|
||||
[](void * par) { // Function to implement the task
|
||||
// see https://www.freertos.org/vtaskdelayuntil.html
|
||||
const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2;
|
||||
const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2;
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
for(;;) {
|
||||
delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy.
|
||||
@@ -1120,7 +1263,9 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
oappend(SET_F("addOption(dd,'SSD1305 128x64',5);"));
|
||||
oappend(SET_F("addOption(dd,'SSD1306 SPI',6);"));
|
||||
oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);"));
|
||||
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
|
||||
oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);"));
|
||||
oappend(SET_F("addOption(dd,'SSD1327 SPI 128x128',9);"));
|
||||
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
|
||||
// WLEDMM add defaults
|
||||
oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','I2C/SPI CLK');"));
|
||||
oappend(SET_F("dRO('4LineDisplay:pin[]',0);")); // disable read only pins
|
||||
@@ -1183,7 +1328,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
void addToConfig(JsonObject& root) {
|
||||
// determine if we are using global HW pins (data & clock)
|
||||
int8_t hw_dta, hw_clk;
|
||||
if ((type == SSD1306_SPI || type == SSD1306_SPI64)) {
|
||||
if ((type == SSD1306_SPI || type == SSD1306_SPI64) || (type > 7)) {
|
||||
hw_clk = spi_sclk;
|
||||
hw_dta = spi_mosi;
|
||||
} else {
|
||||
@@ -1249,7 +1394,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
clockMode = top[FPSTR(_clockMode)] | clockMode;
|
||||
showSeconds = top[FPSTR(_showSeconds)] | showSeconds;
|
||||
contrastFix = top[FPSTR(_contrastFix)] | contrastFix;
|
||||
if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
|
||||
if (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType > 7)
|
||||
ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
|
||||
else
|
||||
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
|
||||
@@ -1279,14 +1424,17 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
USER_PRINTLN(F("Display terminated."));
|
||||
}
|
||||
PinOwner po = PinOwner::UM_FourLineDisplay;
|
||||
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
|
||||
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
|
||||
if (isSPI) {
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po);
|
||||
bool isHW = (oldPin[0]==spi_sclk && oldPin[1]==spi_mosi);
|
||||
if (oldPin[0]==-1 && oldPin[1]==-1) isHW = true; // WLEDMM "use global" means hardware driver
|
||||
if (spi_sclk==-1 && spi_mosi==-1) isHW = false; // WLEDMM global pins not set -> software driver
|
||||
if (isHW) po = PinOwner::HW_SPI;
|
||||
} else {
|
||||
bool isHW = (oldPin[0]==i2c_scl && oldPin[1]==i2c_sda);
|
||||
if (isHW) po = PinOwner::HW_I2C;
|
||||
//bool isHW = (oldPin[0]==i2c_scl && oldPin[1]==i2c_sda);
|
||||
//if ((ioPin[0] == -1) || (ioPin[1] == -1)) isHW = true; // WLEDMM "use global" = hardware
|
||||
//if (isHW) po = PinOwner::HW_I2C; // WLEDMM don't try to de-alloc HW pins.
|
||||
}
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po);
|
||||
type = newType;
|
||||
@@ -1333,3 +1481,8 @@ const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix";
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr;
|
||||
#endif
|
||||
|
||||
// WLEDMM clean up some macros, so they don't cause problems in other usermods
|
||||
#undef FLD_SemaphoreTake
|
||||
#undef FLD_SemaphoreGive
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ uint16_t mode_IMUTest(void) {
|
||||
|
||||
uint8_t y = 0;
|
||||
|
||||
if (IMU != nullptr) {
|
||||
if ((IMU != nullptr) && (IMU->dmpReady)) {
|
||||
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
|
||||
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
|
||||
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
|
||||
@@ -280,7 +280,7 @@ uint16_t mode_3DIMUCube(void) {
|
||||
float roll = 0;
|
||||
|
||||
#ifdef USERMOD_MPU6050_IMU
|
||||
if (IMU != nullptr) {
|
||||
if ((IMU != nullptr) && (IMU->dmpReady)) {
|
||||
yaw = -IMU->ypr[0];
|
||||
pitch = IMU->ypr[1];
|
||||
roll = IMU->ypr[2];
|
||||
@@ -319,7 +319,7 @@ uint16_t mode_3DIMUCube(void) {
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_3DIMUCube[] PROGMEM = "🎮 3DIMUCube ☾@,Perspective;!;!;2;pal=1"; //random cycle
|
||||
static const char _data_FX_MODE_3DIMUCube[] PROGMEM = "🎮 3DIMUCube ☾@,Perspective;!;!;2;pal=1"; //WLEDMM random smooth
|
||||
|
||||
class GamesUsermod : public Usermod {
|
||||
private:
|
||||
@@ -330,9 +330,9 @@ class GamesUsermod : public Usermod {
|
||||
strip.addEffect(255, &mode_pongGame, _data_FX_MODE_PONGGAME);
|
||||
#ifdef USERMOD_MPU6050_IMU
|
||||
IMU = (MPU6050Driver *)usermods.lookup(USERMOD_ID_IMU);
|
||||
// #ifdef WLED_DEBUG
|
||||
#ifdef WLED_DEBUG
|
||||
strip.addEffect(255, &mode_IMUTest, _data_FX_MODE_IMUTest);
|
||||
// #endif
|
||||
#endif
|
||||
#endif
|
||||
strip.addEffect(255, &mode_3DIMUCube, _data_FX_MODE_3DIMUCube); //works also without IMU
|
||||
}
|
||||
|
||||
40
usermods/usermod_v2_klipper_percentage/readme.md
Normal file
40
usermods/usermod_v2_klipper_percentage/readme.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Klipper Percentage Usermod
|
||||
This usermod polls the Klipper API every 10s for the progressvalue.
|
||||
The leds are then filled with a solid color according to that progress percentage.
|
||||
the solid color is the secondary color of the segment.
|
||||
|
||||
A corresponding curl command would be:
|
||||
```
|
||||
curl --location --request GET 'http://[]/printer/objects/query?virtual_sdcard=progress'
|
||||
```
|
||||
## Usage
|
||||
Compile the source with the buildflag `-D USERMOD_KLIPPER_PERCENTAGE` added.
|
||||
|
||||
You can also use the WLBD bot in the Discord by simply extending an exsisting build enviroment:
|
||||
```
|
||||
[env:esp32klipper]
|
||||
extends = env:esp32dev
|
||||
build_flags = ${common.build_flags_esp32} -D USERMOD_KLIPPER_PERCENTAGE
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
### Enabled:
|
||||
Checkbox to enable or disable the overlay
|
||||
|
||||
### Klipper IP:
|
||||
IP adress of your Klipper instance you want to poll. ESP has to be restarted after change
|
||||
|
||||
### Direction :
|
||||
0 = normal
|
||||
|
||||
1 = reversed
|
||||
|
||||
2 = center
|
||||
|
||||
-----
|
||||
Author:
|
||||
|
||||
Sören Willrodt
|
||||
|
||||
Discord: Sören#5281
|
||||
@@ -0,0 +1,222 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class klipper_percentage : public Usermod
|
||||
{
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
String ip = "192.168.25.207";
|
||||
WiFiClient wifiClient;
|
||||
char errorMessage[100] = "";
|
||||
int printPercent = 0;
|
||||
int direction = 0; // 0 for along the strip, 1 for reversed direction
|
||||
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
bool enabled = false;
|
||||
|
||||
void httpGet(WiFiClient &client, char *errorMessage)
|
||||
{
|
||||
// https://arduinojson.org/v6/example/http-client/
|
||||
// is this the most compact way to do http get and put it in arduinojson object???
|
||||
// would like async response ... ???
|
||||
client.setTimeout(10000);
|
||||
if (!client.connect(ip.c_str(), 80))
|
||||
{
|
||||
strcat(errorMessage, PSTR("Connection failed"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send HTTP request
|
||||
client.println(F("GET /printer/objects/query?virtual_sdcard=progress HTTP/1.0"));
|
||||
client.println("Host: " + ip);
|
||||
client.println(F("Connection: close"));
|
||||
if (client.println() == 0)
|
||||
{
|
||||
strcat(errorMessage, PSTR("Failed to send request"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check HTTP status
|
||||
char status[32] = {0};
|
||||
client.readBytesUntil('\r', status, sizeof(status));
|
||||
if (strcmp(status, "HTTP/1.1 200 OK") != 0)
|
||||
{
|
||||
strcat(errorMessage, PSTR("Unexpected response: "));
|
||||
strcat(errorMessage, status);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip HTTP headers
|
||||
char endOfHeaders[] = "\r\n\r\n";
|
||||
if (!client.find(endOfHeaders))
|
||||
{
|
||||
strcat(errorMessage, PSTR("Invalid response"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
}
|
||||
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
if (WLED_CONNECTED)
|
||||
{
|
||||
if (millis() - lastTime > 10000)
|
||||
{
|
||||
httpGet(wifiClient, errorMessage);
|
||||
if (strcmp(errorMessage, "") == 0)
|
||||
{
|
||||
PSRAMDynamicJsonDocument klipperDoc(4096); // in practive about 2673
|
||||
DeserializationError error = deserializeJson(klipperDoc, wifiClient);
|
||||
if (error)
|
||||
{
|
||||
strcat(errorMessage, PSTR("deserializeJson() failed: "));
|
||||
strcat(errorMessage, error.c_str());
|
||||
}
|
||||
printPercent = (int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as<float>() * 100);
|
||||
|
||||
DEBUG_PRINT("Percent: ");
|
||||
DEBUG_PRINTLN((int)(klipperDoc["result"]["status"]["virtual_sdcard"]["progress"].as<float>() * 100));
|
||||
DEBUG_PRINT("LEDs: ");
|
||||
DEBUG_PRINTLN(direction == 2 ? (strip.getLengthTotal() / 2) * printPercent / 100 : strip.getLengthTotal() * printPercent / 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINTLN(errorMessage);
|
||||
DEBUG_PRINTLN(ip);
|
||||
}
|
||||
lastTime = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("Klipper Printing Percentage");
|
||||
top["Enabled"] = enabled;
|
||||
top["Klipper IP"] = ip;
|
||||
top["Direction"] = direction;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
JsonObject top = root["Klipper Printing Percentage"];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
configComplete &= getJsonValue(top["Klipper IP"], ip);
|
||||
configComplete &= getJsonValue(top["Enabled"], enabled);
|
||||
configComplete &= getJsonValue(top["Direction"], direction);
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull())
|
||||
user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
uiDomString += enabled ? F(":false}});\">") : F(":true}});\">");
|
||||
uiDomString += F("<i class=\"icons");
|
||||
uiDomString += enabled ? F(" on") : F(" off");
|
||||
uiDomString += F("\"></i>");
|
||||
uiDomString += F("</button>");
|
||||
infoArr.add(uiDomString);
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (usermod.isNull())
|
||||
{
|
||||
usermod = root.createNestedObject(FPSTR(_name));
|
||||
}
|
||||
usermod["on"] = enabled;
|
||||
}
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull())
|
||||
{
|
||||
if (usermod[FPSTR(_enabled)].is<bool>())
|
||||
{
|
||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
if (direction == 0) // normal
|
||||
{
|
||||
for (int i = 0; i < strip.getLengthTotal() * printPercent / 100; i++)
|
||||
{
|
||||
strip.setPixelColor(i, strip.getSegment(0).colors[1]);
|
||||
}
|
||||
}
|
||||
else if (direction == 1) // reversed
|
||||
{
|
||||
for (int i = 0; i < strip.getLengthTotal() * printPercent / 100; i++)
|
||||
{
|
||||
strip.setPixelColor(strip.getLengthTotal() - i, strip.getSegment(0).colors[1]);
|
||||
}
|
||||
}
|
||||
else if (direction == 2) // center
|
||||
{
|
||||
for (int i = 0; i < (strip.getLengthTotal() / 2) * printPercent / 100; i++)
|
||||
{
|
||||
strip.setPixelColor((strip.getLengthTotal() / 2) + i, strip.getSegment(0).colors[1]);
|
||||
strip.setPixelColor((strip.getLengthTotal() / 2) - i, strip.getSegment(0).colors[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
direction = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_KLIPPER;
|
||||
}
|
||||
};
|
||||
const char klipper_percentage::_name[] PROGMEM = "Klipper_Percentage";
|
||||
const char klipper_percentage::_enabled[] PROGMEM = "enabled";
|
||||
@@ -348,8 +348,10 @@ public:
|
||||
findCurrentEffectAndPalette();
|
||||
}
|
||||
|
||||
if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
|
||||
currentEffectAndPaletteInitialized = false;
|
||||
if (modes_alpha_indexes != nullptr) { // WLEDMM bugfix
|
||||
if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
|
||||
currentEffectAndPaletteInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTime - loopTime >= 2) // 2ms since last check of encoder = 500Hz
|
||||
@@ -469,11 +471,13 @@ public:
|
||||
|
||||
void displayNetworkInfo() {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display != nullptr)
|
||||
display->networkOverlay(PSTR("NETWORK INFO"), 10000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void findCurrentEffectAndPalette() {
|
||||
if (modes_alpha_indexes == nullptr) return; // WLEDMM bugfix
|
||||
currentEffectAndPaletteInitialized = true;
|
||||
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
|
||||
if (modes_alpha_indexes[i] == effectCurrent) {
|
||||
@@ -510,7 +514,8 @@ public:
|
||||
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
|
||||
//setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
|
||||
stateUpdated(CALL_MODE_BUTTON);
|
||||
updateInterfaces(CALL_MODE_BUTTON);
|
||||
if ((millis() - lastInterfaceUpdate) > INTERFACE_UPDATE_COOLDOWN) // WLEDMM respect cooldown times, to avoid crash in AsyncWebSocketMessageBuffer
|
||||
updateInterfaces(CALL_MODE_BUTTON);
|
||||
}
|
||||
|
||||
void changeBrightness(bool increase) {
|
||||
@@ -522,7 +527,10 @@ public:
|
||||
}
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
|
||||
byte lastBri = bri;
|
||||
if (bri < 40) bri = max(min((increase ? bri+fadeAmount/2 : bri-fadeAmount/2), 255), 0); // WLEDMM slower steps when brightness < 16%
|
||||
else bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
|
||||
if (lastBri != bri) stateChanged = true; // WLEDMM bugfix
|
||||
lampUdated();
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
if (display->canDraw()) // only draw if nothing else is drawing
|
||||
@@ -541,7 +549,7 @@ public:
|
||||
display->updateRedrawTime();
|
||||
#endif
|
||||
effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0);
|
||||
effectCurrent = modes_alpha_indexes[effectCurrentIndex];
|
||||
if (modes_alpha_indexes != nullptr) effectCurrent = modes_alpha_indexes[effectCurrentIndex];
|
||||
stateChanged = true;
|
||||
if (applyToAll) {
|
||||
for (byte i=0; i<strip.getSegmentsNum(); i++) {
|
||||
@@ -936,7 +944,7 @@ public:
|
||||
enabled = false;
|
||||
return true;
|
||||
}
|
||||
setup();
|
||||
if (enabled) setup(); // WLEDMM no pin stealing!
|
||||
}
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
|
||||
@@ -94,7 +94,7 @@ uint16_t mode_2DWeather(void) {
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
static const char _data_FX_MODE_2DWEATHER[] PROGMEM = "Weather ☾@;!;!;2;pal=54"; //temperature palette
|
||||
static const char _data_FX_MODE_2DWEATHER[] PROGMEM = "🌦Weather ☾@;!;!;2;pal=54"; //temperature palette
|
||||
|
||||
//utility function, move somewhere else???
|
||||
void epochToString(time_t time, char *timeString) {
|
||||
@@ -142,14 +142,13 @@ void httpGet(WiFiClient &client, const char *url, char *errorMessage) {
|
||||
class WeatherUsermod : public Usermod {
|
||||
private:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[]; //usermod name
|
||||
String apiKey = ""; //config var
|
||||
|
||||
unsigned long lastTime = 0; //will be used to download new forecast every hour
|
||||
char errorMessage[100] = "";
|
||||
bool isConnected = false; //only call openweathermap if connected
|
||||
|
||||
public:
|
||||
WeatherUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class
|
||||
|
||||
void setup() {
|
||||
strip.addEffect(255, &mode_2DWeather, _data_FX_MODE_2DWEATHER);
|
||||
@@ -256,7 +255,6 @@ class WeatherUsermod : public Usermod {
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
@@ -292,7 +290,8 @@ class WeatherUsermod : public Usermod {
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
Usermod::addToConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
top[F("apiKey")] = apiKey;
|
||||
top[F("units")] = weather_units;
|
||||
top[F("minTemp")] = weather_minTemp;
|
||||
@@ -302,10 +301,9 @@ class WeatherUsermod : public Usermod {
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
bool configComplete = Usermod::readFromConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top[F("apiKey")], apiKey);
|
||||
configComplete &= getJsonValue(top[F("units")], weather_units);
|
||||
configComplete &= getJsonValue(top[F("minTemp")], weather_minTemp);
|
||||
@@ -317,8 +315,8 @@ class WeatherUsermod : public Usermod {
|
||||
|
||||
void appendConfigData()
|
||||
{
|
||||
oappend(SET_F("addInfo('Weather:help',0,'<button onclick=\"location.href="https://mm.kno.wled.ge/moonmodules/Weather"\" type=\"button\">?</button>');"));
|
||||
|
||||
oappend(SET_F("addHB('Weather');")); // WLEDMM
|
||||
|
||||
oappend(SET_F("dd=addDropdown('Weather','units');"));
|
||||
oappend(SET_F("addOption(dd,'Kelvin',0);"));
|
||||
oappend(SET_F("addOption(dd,'Celcius',1);"));
|
||||
@@ -349,9 +347,6 @@ class WeatherUsermod : public Usermod {
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char WeatherUsermod::_name[] PROGMEM = "Weather";
|
||||
|
||||
// example openweathermap data
|
||||
// {"cod":"200","message":0,"cnt":40,"list":[
|
||||
// {"dt":1663945200,"main":{"temp":18.05,"feels_like":17.79,"temp_min":17.64,"temp_max":18.05,"pressure":1014,"sea_level":1014,"grnd_level":1013,"humidity":72,"temp_kf":0.41},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":100},"wind":{"speed":4.32,"deg":238,"gust":5.6},"visibility":10000,"pop":0.48,"rain":{"3h":0.15},"sys":{"pod":"d"},"dt_txt":"2022-09-23 15:00:00"},
|
||||
|
||||
Reference in New Issue
Block a user