Merge branch 'MoonModules:mdev' into downsample4x
This commit is contained in:
@@ -16,7 +16,7 @@ class Animated_Staircase : public Usermod {
|
||||
/* configuration (available in API and stored in flash) */
|
||||
bool enabled = false; // Enable this usermod
|
||||
unsigned long segment_delay_ms = 150; // Time between switching each segment
|
||||
unsigned long on_time_ms = 30000; // The time for the light to stay on
|
||||
unsigned long on_time_ms = 5000; // The time for the light to stay on - TroyHacks: 5s for testing
|
||||
int8_t topPIRorTriggerPin = -1; // disabled
|
||||
int8_t bottomPIRorTriggerPin = -1; // disabled
|
||||
int8_t topEchoPin = -1; // disabled
|
||||
@@ -131,7 +131,7 @@ class Animated_Staircase : public Usermod {
|
||||
* received within this time, an object is detected
|
||||
* and the function will return true.
|
||||
*
|
||||
* The speed of sound is 343 meters per second at 20 degress Celcius.
|
||||
* The speed of sound is 343 meters per second at 20 degrees Celsius.
|
||||
* Since the sound has to travel back and forth, the detection
|
||||
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
|
||||
*
|
||||
@@ -161,28 +161,37 @@ class Animated_Staircase : public Usermod {
|
||||
if ((millis() - lastScanTime) > scanDelay) {
|
||||
lastScanTime = millis();
|
||||
|
||||
bottomSensorRead = bottomSensorWrite ||
|
||||
(!useUSSensorBottom ?
|
||||
(bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) :
|
||||
ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us
|
||||
);
|
||||
topSensorRead = topSensorWrite ||
|
||||
(!useUSSensorTop ?
|
||||
(topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) :
|
||||
ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us
|
||||
);
|
||||
if (useUSSensorBottom) {
|
||||
bottomSensorRead = ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59); // US
|
||||
} else if (bottomPIRorTriggerPin > 0) {
|
||||
bottomSensorRead = digitalRead(bottomPIRorTriggerPin); // PIR
|
||||
} else {
|
||||
bottomSensorRead = false; // DUNNO
|
||||
}
|
||||
|
||||
if (useUSSensorTop) {
|
||||
topSensorRead = ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59); // US
|
||||
} else if (topPIRorTriggerPin > 0) {
|
||||
topSensorRead = digitalRead(topPIRorTriggerPin); // PIR
|
||||
} else {
|
||||
topSensorRead = false; // DUNNO
|
||||
}
|
||||
|
||||
if (bottomSensorRead != bottomSensorState) {
|
||||
bottomSensorState = bottomSensorRead; // change previous state
|
||||
sensorChanged = true;
|
||||
publishMqtt(true, bottomSensorState ? "on" : "off");
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
publishMqtt(true, bottomSensorState ? "on" : "off");
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Bottom sensor changed."));
|
||||
}
|
||||
|
||||
if (topSensorRead != topSensorState) {
|
||||
topSensorState = topSensorRead; // change previous state
|
||||
sensorChanged = true;
|
||||
publishMqtt(false, topSensorState ? "on" : "off");
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
publishMqtt(false, topSensorState ? "on" : "off");
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Top sensor changed."));
|
||||
}
|
||||
|
||||
@@ -224,7 +233,13 @@ class Animated_Staircase : public Usermod {
|
||||
if (bottomSensorState || topSensorState) return;
|
||||
|
||||
// Swipe OFF in the direction of the last sensor detection
|
||||
swipe = lastSensor;
|
||||
// WLED-MM/TroyHacks: This should follow you up/down the stairs.
|
||||
if (lastSensor == SWIPE_UP) {
|
||||
swipe = SWIPE_DOWN;
|
||||
} else {
|
||||
swipe = SWIPE_UP;
|
||||
}
|
||||
|
||||
on = false;
|
||||
|
||||
DEBUG_PRINT(F("OFF -> Swipe "));
|
||||
@@ -253,7 +268,7 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
// send sesnor values to JSON API
|
||||
// send sensor values to JSON API
|
||||
void writeSensorsToJson(JsonObject& staircase) {
|
||||
staircase[F("top-sensor")] = topSensorRead;
|
||||
staircase[F("bottom-sensor")] = bottomSensorRead;
|
||||
@@ -302,7 +317,7 @@ class Animated_Staircase : public Usermod {
|
||||
seg.setOption(SEG_OPTION_ON, true);
|
||||
}
|
||||
strip.trigger(); // force strip update
|
||||
stateChanged = true; // inform external dvices/UI of change
|
||||
stateChanged = true; // inform external devices/UI of change
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||
}
|
||||
@@ -486,7 +501,7 @@ class Animated_Staircase : public Usermod {
|
||||
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
|
||||
|
||||
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
|
||||
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected)
|
||||
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
|
||||
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
|
||||
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Maximum distance for ultrasonic sensor can be configured as the time needed for
|
||||
You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor.
|
||||
|
||||
## WLED configuration
|
||||
1. In the WLED UI, confgure a segment for each step. The lowest step of the stairs is the
|
||||
1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the
|
||||
lowest segment id.
|
||||
2. Save your segments into a preset.
|
||||
3. Ideally, add the preset in the config > LED setup menu to the "apply
|
||||
@@ -91,7 +91,7 @@ To enable the usermod again, use `"enabled":true`.
|
||||
Alternatively you can use _Usermod_ Settings page where you can change other parameters as well.
|
||||
|
||||
### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor
|
||||
Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation etc.
|
||||
Using _Usermod_ Settings page you can define different usermod parameters, including sensor pins, delay between segment activation etc.
|
||||
|
||||
When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ The luminance is displayed in both the Info section of the web UI, as well as pu
|
||||
- This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`).
|
||||
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||
|
||||
## Compiliation
|
||||
## Compilation
|
||||
|
||||
To enable, compile with `USERMOD_BH1750` defined (e.g. in `platformio_override.ini`)
|
||||
```ini
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
|
||||
#endif
|
||||
|
||||
// only report if differance grater than offset value
|
||||
// only report if difference grater than offset value
|
||||
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
||||
#define USERMOD_BH1750_OFFSET_VALUE 2 // WLEDMM this makes more sense
|
||||
#endif
|
||||
@@ -118,8 +118,8 @@ private:
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||
device[F("manufacturer")] = F("WLED");
|
||||
device[F("model")] = F("FOSS");
|
||||
device[F("manufacturer")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("model")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("sw_version")] = versionString;
|
||||
|
||||
String temp;
|
||||
|
||||
@@ -32,7 +32,7 @@ private:
|
||||
|
||||
// set the default pins based on the architecture, these get overridden by Usermod menu settings
|
||||
#ifdef ESP8266
|
||||
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
//uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
|
||||
bool initDone = false;
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
|
||||
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Fahrenheit being set in Usermod Menu)
|
||||
void UpdateBME280Data(int SensorType)
|
||||
{
|
||||
float _temperature, _humidity, _pressure;
|
||||
@@ -163,8 +163,8 @@ private:
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||
device[F("manufacturer")] = F("WLED");
|
||||
device[F("model")] = F("FOSS");
|
||||
device[F("manufacturer")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("model")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("sw_version")] = versionString;
|
||||
|
||||
String temp;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
// auto-off feature
|
||||
#ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED
|
||||
#define USERMOD_BATTERY_AUTO_OFF_ENABLED true
|
||||
#define USERMOD_BATTERY_AUTO_OFF_ENABLED false
|
||||
#endif
|
||||
|
||||
#ifndef USERMOD_BATTERY_AUTO_OFF_THRESHOLD
|
||||
@@ -78,4 +78,4 @@
|
||||
|
||||
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION
|
||||
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,7 @@ If you have an ESP32 board, connect the positive side of the battery to ADC1 (GP
|
||||
- 💯 Displays current battery voltage
|
||||
- 🚥 Displays battery level
|
||||
- 🚫 Auto-off with configurable Threshold
|
||||
- 🚨 Low power indicator with many configuration posibilities
|
||||
- 🚨 Low power indicator with many configuration possibilities
|
||||
|
||||
## 🎈 Installation
|
||||
|
||||
@@ -41,7 +41,7 @@ define `USERMOD_BATTERY` in `wled00/my_config.h`
|
||||
| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds |
|
||||
| `USERMOD_BATTERY_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) |
|
||||
| `USERMOD_BATTERY_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) |
|
||||
| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parralel sumed up |
|
||||
| `USERMOD_BATTERY_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up |
|
||||
| `USERMOD_BATTERY_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller |
|
||||
| Auto-Off | --- | --- |
|
||||
| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off |
|
||||
|
||||
@@ -118,7 +118,7 @@ class UsermodBattery : public Usermod
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if ((batteryPin <0) || !pinManager.isPinAnalog(batteryPin)) return(-1.0f); // WLEDMM avoid reading from invalid pin
|
||||
// use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value
|
||||
// use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attenuation) and divide by 1000 to get from milliVolts to volts and multiply by voltage multiplier and apply calibration value
|
||||
return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration;
|
||||
#else
|
||||
// use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value
|
||||
@@ -216,7 +216,7 @@ class UsermodBattery : public Usermod
|
||||
*/
|
||||
#ifdef USERMOD_BATTERY_USE_LIPO
|
||||
batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); // basic mapping
|
||||
// LiPo batteries have a differnt dischargin curve, see
|
||||
// LiPo batteries have a different dischargin curve, see
|
||||
// https://blog.ampow.com/lipo-voltage-chart/
|
||||
if (batteryLevel < 40.0f)
|
||||
batteryLevel = mapf(batteryLevel, 0, 40, 0, 12); // last 45% -> drops very quickly
|
||||
@@ -413,7 +413,7 @@ class UsermodBattery : public Usermod
|
||||
oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');"));
|
||||
oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');"));
|
||||
|
||||
// cannot quite get this mf to work. its exeeding some buffer limit i think
|
||||
// cannot quite get this mf to work. its exceeding some buffer limit i think
|
||||
// what i wanted is a list of all presets to select one from
|
||||
// oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');"));
|
||||
// the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);"));
|
||||
@@ -612,7 +612,7 @@ class UsermodBattery : public Usermod
|
||||
|
||||
|
||||
/*
|
||||
* Get the capacity of all cells in parralel sumed up
|
||||
* Get the capacity of all cells in parallel summed up
|
||||
* unit: mAh
|
||||
*/
|
||||
unsigned int getTotalBatteryCapacity()
|
||||
@@ -788,7 +788,7 @@ class UsermodBattery : public Usermod
|
||||
|
||||
|
||||
/*
|
||||
* Get low-power-indicator status when the indication is done thsi returns true
|
||||
* Get low-power-indicator status when the indication is done this returns true
|
||||
*/
|
||||
bool getLowPowerIndicatorDone()
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
#endif
|
||||
|
||||
// how many seconds after boot to take first measurement, 90 seconds
|
||||
// 90 gives enough time to OTA update firmware if this crashses
|
||||
// 90 gives enough time to OTA update firmware if this crashes
|
||||
#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
|
||||
#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
|
||||
#endif
|
||||
|
||||
@@ -58,7 +58,7 @@ class MyExampleUsermod : public Usermod {
|
||||
long testLong;
|
||||
int8_t testPins[2];
|
||||
|
||||
// any private methods should go here (non-inline methosd should be defined out of class)
|
||||
// any private methods should go here (non-inline method should be defined out of class)
|
||||
void publishMqtt(const char* state, bool retain = false); // example for publishing MQTT message
|
||||
|
||||
|
||||
|
||||
@@ -15,23 +15,23 @@ OneWire oneWire(13);
|
||||
DallasTemperature sensor(&oneWire);
|
||||
long temptimer = millis();
|
||||
long lastMeasure = 0;
|
||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
||||
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||
// or check the gallery:
|
||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
||||
// --> First choice of cheap I2C OLED 128X32 0.91"
|
||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void userSetup() {
|
||||
sensor.begin(); //Start Dallas temperature sensor
|
||||
u8x8.begin();
|
||||
//u8x8.setFlipMode(1); //Uncoment if using WLED Wemos shield
|
||||
//u8x8.setFlipMode(1); //Un-comment if using WLED Wemos shield
|
||||
u8x8.setPowerSave(0);
|
||||
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
@@ -71,7 +71,7 @@ void userLoop() {
|
||||
if (mqtt != nullptr)
|
||||
{
|
||||
sensor.requestTemperatures();
|
||||
//Gets prefered temperature scale based on selection in definitions section
|
||||
//Gets preferred temperature scale based on selection in definitions section
|
||||
#ifdef Celsius
|
||||
float board_temperature = sensor.getTempCByIndex(0);
|
||||
#else
|
||||
@@ -138,11 +138,11 @@ void userLoop() {
|
||||
// First row with Wifi name
|
||||
u8x8.setCursor(1, 0);
|
||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
// Print `~` char to indicate that SSID is longer than our display
|
||||
if (knownSsid.length() > u8x8.getCols())
|
||||
u8x8.print("~");
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Second row with IP or Password
|
||||
u8x8.setCursor(1, 1);
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
void UpdateBME280Data();
|
||||
|
||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
||||
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
|
||||
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
|
||||
|
||||
@@ -20,14 +20,14 @@ uint8_t SDA_PIN = 21;
|
||||
#else //ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
|
||||
#define U8X8_PIN_SCL SCL_PIN
|
||||
#define U8X8_PIN_SDA SDA_PIN
|
||||
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
|
||||
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
@@ -36,9 +36,9 @@ uint8_t SDA_PIN = 4;
|
||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
|
||||
// gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
|
||||
@@ -181,11 +181,11 @@ void userLoop() {
|
||||
// First row with Wifi name
|
||||
u8x8.setCursor(1, 0);
|
||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
// Print `~` char to indicate that SSID is longer than our display
|
||||
if (knownSsid.length() > u8x8.getCols())
|
||||
u8x8.print("~");
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Second row with IP or Password
|
||||
u8x8.setCursor(1, 1);
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Attention: This usermod compiles only for ESP8266**
|
||||
|
||||
This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WLAN environments.
|
||||
This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WiFi environments.
|
||||
|
||||
The modification works with static or DHCP IP address configuration.
|
||||
|
||||
@@ -24,7 +24,7 @@ The usermod supports the following state changes:
|
||||
|
||||
| JSON key | Value range | Description |
|
||||
|-------------|------------------|---------------------------------|
|
||||
| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor |
|
||||
| PingDelayMs | 5000 to 18000000 | Deactivate/activate the sensor |
|
||||
|
||||
Changes also persist after a reboot.
|
||||
|
||||
|
||||
26
usermods/LDR_Dusk_Dawn_v2/README.md
Normal file
26
usermods/LDR_Dusk_Dawn_v2/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# LDR_Dusk_Dawn_v2
|
||||
This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out.
|
||||
|
||||
# Installation
|
||||
Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build.
|
||||
|
||||
Example:
|
||||
```
|
||||
[common]
|
||||
build_flags =
|
||||
-D USERMOD_LDR_DUSK_DAWN # Enable LDR Dusk Dawn Usermod
|
||||
```
|
||||
|
||||
# Usermod Settings
|
||||
Setting | Description | Default
|
||||
--- | --- | ---
|
||||
Enabled | Enable/Disable the LDR functionality. | Disabled
|
||||
LDR Pin | The analog capable pin your LDR is connected to. | 34
|
||||
Threshold Minutes | The number of minutes of consistent readings above/below the on/off threshold before the LED state will change. | 5
|
||||
Threshold | The analog read value threshold from the LDR. Readings lower than this number will count towards changing the LED state to off. You can see the current LDR reading by going into the info section when LDR functionality is enabled. | 1000
|
||||
On Preset | The WLED preset to be used for the LED on state. | 1
|
||||
Off Preset | The WLED preset to be used for the LED off state. | 2
|
||||
|
||||
## Author
|
||||
[@jeffwdh](https://github.com/jeffwdh)
|
||||
jeffwdh@tarball.ca
|
||||
153
usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h
Normal file
153
usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// 8266 does not support analogRead on user selectable pins
|
||||
#error only ESP32 is supported by usermod LDR_DUSK_DAWN
|
||||
#endif
|
||||
|
||||
class LDR_Dusk_Dawn_v2 : public Usermod {
|
||||
private:
|
||||
// Defaults
|
||||
bool ldrEnabled = false;
|
||||
int ldrPin = 34; //A2 on Adafruit Huzzah32
|
||||
int ldrThresholdMinutes = 5; // How many minutes of readings above/below threshold until it switches LED state
|
||||
int ldrThreshold = 1000; // Readings higher than this number will turn off LED.
|
||||
int ldrOnPreset = 1; // Default "On" Preset
|
||||
int ldrOffPreset = 2; // Default "Off" Preset
|
||||
|
||||
// Variables
|
||||
bool initDone = false;
|
||||
bool ldrEnabledPreviously = false; // Was LDR enabled for the previous check? First check is always no.
|
||||
int ldrOffCount; // Number of readings above the threshold
|
||||
int ldrOnCount; // Number of readings below the threshold
|
||||
int ldrReading = 0; // Last LDR reading
|
||||
int ldrLEDState; // Current LED on/off state
|
||||
unsigned long lastMillis = 0;
|
||||
static const char _name[];
|
||||
|
||||
public:
|
||||
void setup() {
|
||||
// register ldrPin
|
||||
if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) {
|
||||
if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod
|
||||
else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input
|
||||
} else ldrEnabled = false; // invalid pin -> disable usermod
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Only update every 10 seconds
|
||||
if (millis() - lastMillis > 10000) {
|
||||
if ( (ldrEnabled == true)
|
||||
&& (ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0) ) { // make sure that pin is valid for analogread()
|
||||
// Default state is off
|
||||
if (ldrEnabledPreviously == false) {
|
||||
applyPreset(ldrOffPreset);
|
||||
ldrEnabledPreviously = true;
|
||||
ldrLEDState = 0;
|
||||
}
|
||||
|
||||
// Get LDR reading and increment counter by number of seconds since last read
|
||||
ldrReading = analogRead(ldrPin);
|
||||
if (ldrReading <= ldrThreshold) {
|
||||
ldrOnCount = ldrOnCount + 10;
|
||||
ldrOffCount = 0;
|
||||
} else {
|
||||
ldrOffCount = ldrOffCount + 10;
|
||||
ldrOnCount = 0;
|
||||
}
|
||||
|
||||
if (ldrOnCount >= (ldrThresholdMinutes * 60)) {
|
||||
ldrOnCount = 0;
|
||||
// If LEDs were previously off, turn on
|
||||
if (ldrLEDState == 0) {
|
||||
applyPreset(ldrOnPreset);
|
||||
ldrLEDState = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ldrOffCount >= (ldrThresholdMinutes * 60)) {
|
||||
ldrOffCount = 0;
|
||||
// If LEDs were previously on, turn off
|
||||
if (ldrLEDState == 1) {
|
||||
applyPreset(ldrOffPreset);
|
||||
ldrLEDState = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LDR is disabled, reset variables to default
|
||||
ldrReading = 0;
|
||||
ldrOnCount = 0;
|
||||
ldrOffCount = 0;
|
||||
ldrLEDState = 0;
|
||||
ldrEnabledPreviously = false;
|
||||
}
|
||||
lastMillis = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top["Enabled"] = ldrEnabled;
|
||||
top["LDR Pin"] = ldrPin;
|
||||
top["Threshold Minutes"] = ldrThresholdMinutes;
|
||||
top["Threshold"] = ldrThreshold;
|
||||
top["On Preset"] = ldrOnPreset;
|
||||
top["Off Preset"] = ldrOffPreset;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root) {
|
||||
int8_t oldLdrPin = ldrPin;
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
bool configComplete = !top.isNull();
|
||||
configComplete &= getJsonValue(top["Enabled"], ldrEnabled);
|
||||
configComplete &= getJsonValue(top["LDR Pin"], ldrPin);
|
||||
configComplete &= getJsonValue(top["Threshold Minutes"], ldrThresholdMinutes);
|
||||
configComplete &= getJsonValue(top["Threshold"], ldrThreshold);
|
||||
configComplete &= getJsonValue(top["On Preset"], ldrOnPreset);
|
||||
configComplete &= getJsonValue(top["Off Preset"], ldrOffPreset);
|
||||
|
||||
if (initDone && (ldrPin != oldLdrPin)) {
|
||||
// pin changed - un-register previous pin, register new pin
|
||||
if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN);
|
||||
setup(); // setup new pin
|
||||
}
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
// If "u" object does not exist yet we need to create it
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray LDR_Enabled = user.createNestedArray("LDR dusk/dawn enabled");
|
||||
LDR_Enabled.add(ldrEnabled);
|
||||
if (!ldrEnabled) return; // do not add more if usermod is disabled
|
||||
|
||||
JsonArray LDR_Reading = user.createNestedArray("LDR reading");
|
||||
LDR_Reading.add(ldrReading);
|
||||
|
||||
JsonArray LDR_State = user.createNestedArray("LDR turned LEDs on");
|
||||
LDR_State.add(bool(ldrLEDState));
|
||||
|
||||
// Optional debug information:
|
||||
//JsonArray LDR_On_Count = user.createNestedArray("LDR on count");
|
||||
//LDR_On_Count.add(ldrOnCount);
|
||||
|
||||
//JsonArray LDR_Off_Count = user.createNestedArray("LDR off count");
|
||||
//LDR_Off_Count.add(ldrOffCount);
|
||||
|
||||
//bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0));
|
||||
//if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false;
|
||||
//JsonArray LDR_valid = user.createNestedArray(F("LDR pin"));
|
||||
//LDR_valid.add(ldrPin);
|
||||
//LDR_valid.add(pinValid ? F(" OK"): F(" invalid"));
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_LDR_DUSK_DAWN;
|
||||
}
|
||||
};
|
||||
|
||||
const char LDR_Dusk_Dawn_v2::_name[] PROGMEM = "LDR_Dusk_Dawn_v2";
|
||||
@@ -23,7 +23,7 @@ You can also use usermod's off timer instead of sensor's. In such case rotate th
|
||||
|
||||
## Usermod installation
|
||||
|
||||
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`.
|
||||
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionally `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`.
|
||||
|
||||
## API to enable/disable the PIR sensor from outside. For example from another usermod:
|
||||
|
||||
@@ -31,7 +31,7 @@ To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` an
|
||||
|
||||
When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`.
|
||||
Usermod can also be configured to send just the MQTT message but not change WLED state using settings page as well as responding to motion only at night
|
||||
(assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
|
||||
(assuming NTP and latitude/longitude are set to determine sunrise/sunset times).
|
||||
|
||||
### There are two options to get access to the usermod instance:
|
||||
|
||||
@@ -85,7 +85,7 @@ Have fun - @gegu & @blazoncek
|
||||
|
||||
2021-11
|
||||
* Added information about dynamic configuration options
|
||||
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
|
||||
* Added option to temporary enable/disable usermod from WLED UI (Info dialog)
|
||||
|
||||
2022-11
|
||||
* Added compile time option for off timer.
|
||||
|
||||
@@ -189,8 +189,8 @@ private:
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("ids")] = String(F("wled-sensor-")) + mqttClientID;
|
||||
device[F("mf")] = "WLED";
|
||||
device[F("mdl")] = F("FOSS");
|
||||
device[F("mf")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("mdl")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native
|
||||
device[F("sw")] = versionString;
|
||||
|
||||
sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid);
|
||||
@@ -205,7 +205,7 @@ private:
|
||||
|
||||
/**
|
||||
* Read and update PIR sensor state.
|
||||
* Initilize/reset switch off timer
|
||||
* Initialize/reset switch off timer
|
||||
*/
|
||||
bool updatePIRsensorState()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ v2 Usermod to to control PWM fan with RPM feedback and temperature control
|
||||
This usermod requires the Dallas Temperature usermod to obtain temperature information. If it's not available, the fan will run at 100% speed.
|
||||
If the fan does not have _tachometer_ (RPM) output you can set the _tachometer-pin_ to -1 to disable that feature.
|
||||
|
||||
You can also set the thershold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%.
|
||||
You can also set the threshold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%.
|
||||
|
||||
If the _tachometer_ is supported, the current speed (in RPM) will be displayed on the WLED Info page.
|
||||
|
||||
@@ -22,7 +22,7 @@ This includes:
|
||||
* PWM output pin (can be configured at compile time `-D PWM_PIN=xx`)
|
||||
* tachometer input pin (can be configured at compile time `-D TACHO_PIN=xx`)
|
||||
* sampling frequency in seconds
|
||||
* threshold temperature in degees C
|
||||
* threshold temperature in degrees Celsius
|
||||
|
||||
_NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency to match PWM fan sampling frequency.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class PWMFanUsermod : public Usermod {
|
||||
pinMode(tachoPin, INPUT);
|
||||
digitalWrite(tachoPin, HIGH);
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
DEBUG_PRINTLN(F("Tacho sucessfully initialized."));
|
||||
DEBUG_PRINTLN(F("Tacho successfully initialized."));
|
||||
}
|
||||
|
||||
void deinitTacho(void) {
|
||||
@@ -118,12 +118,12 @@ class PWMFanUsermod : public Usermod {
|
||||
if (pwmChannel == 255) { //no more free LEDC channels
|
||||
deinitPWMfan(); return;
|
||||
}
|
||||
// configure LED PWM functionalitites
|
||||
// configure LED PWM functionalities
|
||||
ledcSetup(pwmChannel, 25000, 8);
|
||||
// attach the channel to the GPIO to be controlled
|
||||
ledcAttachPin(pwmPin, pwmChannel);
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
|
||||
DEBUG_PRINTLN(F("Fan PWM successfully initialized."));
|
||||
}
|
||||
|
||||
void deinitPWMfan(void) {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#define USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE 10000.0f
|
||||
#endif
|
||||
|
||||
// only report if differance grater than offset value
|
||||
// only report if difference grater than offset value
|
||||
#ifndef USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE
|
||||
#define USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE 5
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 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 const.h)
|
||||
* bytes 2400+ are currently ununsed, but might be used for future wled features
|
||||
* bytes 2400+ are currently unused, but might be used for future wled features
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -144,7 +144,7 @@ void userLoop() {
|
||||
// First row with Wifi name
|
||||
tft.setCursor(1, 1);
|
||||
tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than our dicplay
|
||||
// Print `~` char to indicate that SSID is longer than our display
|
||||
if (knownSsid.length() > tftcharwidth)
|
||||
tft.print("~");
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
||||
* `USERMOD_DALLASTEMPERATURE` - enables this user mod wled00/usermods_list.cpp
|
||||
* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - number of milliseconds between measurements, defaults to 60000 ms (60s)
|
||||
|
||||
All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Farenheit and measurement interval.
|
||||
All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Fahrenheit and measurement interval.
|
||||
|
||||
## Project link
|
||||
|
||||
|
||||
@@ -34,30 +34,30 @@ uint8_t DALLAS_PIN =23;
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
uint8_t DALLAS_PIN =13;
|
||||
// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
|
||||
#define U8X8_PIN_SCL SCL_PIN
|
||||
#define U8X8_PIN_SDA SDA_PIN
|
||||
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
|
||||
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
|
||||
|
||||
// Dallas sensor reading timer
|
||||
long temptimer = millis();
|
||||
long lastMeasure = 0;
|
||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
||||
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||
// or check the gallery:
|
||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
||||
// --> First choice of cheap I2C OLED 128X32 0.91"
|
||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
|
||||
// gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
void userSetup() {
|
||||
@@ -97,7 +97,7 @@ void userLoop() {
|
||||
|
||||
//----> Dallas temperature sensor MQTT publishing
|
||||
temptimer = millis();
|
||||
// Timer to publishe new temperature every 60 seconds
|
||||
// Timer to publish new temperature every 60 seconds
|
||||
if (temptimer - lastMeasure > 60000)
|
||||
{
|
||||
lastMeasure = temptimer;
|
||||
@@ -106,7 +106,7 @@ void userLoop() {
|
||||
if (mqtt != nullptr)
|
||||
{
|
||||
// Serial.println(Dallas(DALLAS_PIN,0));
|
||||
//Gets prefered temperature scale based on selection in definitions section
|
||||
//Gets preferred temperature scale based on selection in definitions section
|
||||
#ifdef Celsius
|
||||
int16_t board_temperature = Dallas(DALLAS_PIN,0);
|
||||
#else
|
||||
@@ -173,11 +173,11 @@ void userLoop() {
|
||||
// First row with Wifi name
|
||||
u8x8.setCursor(1, 0);
|
||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
// Print `~` char to indicate that SSID is longer than our display
|
||||
if (knownSsid.length() > u8x8.getCols())
|
||||
u8x8.print("~");
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Second row with IP or Password
|
||||
u8x8.setCursor(1, 1);
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
void UpdateBME280Data();
|
||||
|
||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
||||
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
|
||||
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
|
||||
|
||||
@@ -16,25 +16,25 @@ uint8_t SDA_PIN = 21;
|
||||
#else //ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
|
||||
#define U8X8_PIN_SCL SCL_PIN
|
||||
#define U8X8_PIN_SDA SDA_PIN
|
||||
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
|
||||
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
|
||||
|
||||
// If display does not work or looks corrupted check the
|
||||
// constructor reference:
|
||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||
// or check the gallery:
|
||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
||||
// --> First choice of cheap I2C OLED 128X32 0.91"
|
||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
|
||||
// gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
|
||||
@@ -179,11 +179,11 @@ void userLoop() {
|
||||
// First row with Wifi name
|
||||
u8x8.setCursor(1, 0);
|
||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
// Print `~` char to indicate that SSID is longer, than our display
|
||||
if (knownSsid.length() > u8x8.getCols())
|
||||
u8x8.print("~");
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Second row with IP or Password
|
||||
u8x8.setCursor(1, 1);
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- #define ARDUINOJSON_DEFAULT_NESTING_LIMIT 100 //set this in ArduinoJson!!!, currently not necessary...
|
||||
- IF UPDATING THIS FILE IN THE WLED REPO, SEND A PULL REQUEST TO https://github.com/ewoudwijma/ARTI AS WELL!!!
|
||||
@later
|
||||
- Code improvememt
|
||||
- Code improvement
|
||||
- See 'for some weird reason this causes a crash on esp32'
|
||||
- check why column/lineno not correct
|
||||
- Definition improvements
|
||||
@@ -2181,7 +2181,7 @@ public:
|
||||
ERROR_ARTI("%s %s %s unknown\n", spaces+50-depth, key, variable_name);
|
||||
valueStack->push(floatNull);
|
||||
}
|
||||
} // ! founnd
|
||||
} // ! fouund
|
||||
visitedAlready = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// For testing porposes, definitions should not only run on Arduino but also on Windows etc.
|
||||
// For testing purposes, definitions should not only run on Arduino but also on Windows etc.
|
||||
// Because compiling on arduino takes seriously more time than on Windows.
|
||||
// The plugin.h files replace native arduino calls by windows simulated calls (e.g. setPixelColor will become printf)
|
||||
|
||||
@@ -185,7 +185,7 @@ float ARTI::arti_external_function(uint8_t function, float par1, float par2, flo
|
||||
|
||||
float halfLength = (circleLength-1)/2.0;
|
||||
|
||||
//calculate circle positions, round to 5 digits and then round again to cater for radians inprecision (e.g. 3.49->3.5->4)
|
||||
//calculate circle positions, round to 5 digits and then round again to cater for radians imprecision (e.g. 3.49->3.5->4)
|
||||
int x = round(round((sin(radians(par1)) * halfLength + halfLength) * 10)/10) + deltaWidth;
|
||||
int y = round(round((halfLength - cos(radians(par1)) * halfLength) * 10)/10) + deltaHeight;
|
||||
return SEGMENT.XY(x,y);
|
||||
|
||||
@@ -82,7 +82,7 @@ function populateCEEditor(name, segID)
|
||||
<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>
|
||||
<i>Run log > 3 seconds is send to Serial Output.</i><br>
|
||||
<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>`;
|
||||
|
||||
@@ -19,7 +19,7 @@ ARTI * arti;
|
||||
//effect function
|
||||
uint16_t mode_ARTIFX(void) {
|
||||
//tbd: move statics to SEGMENT.data
|
||||
static bool succesful;
|
||||
static bool successful;
|
||||
static bool notEnoughHeap;
|
||||
|
||||
static char previousEffect[charLength];
|
||||
@@ -46,20 +46,20 @@ uint16_t mode_ARTIFX(void) {
|
||||
// artiWrapper = reinterpret_cast<ArtiWrapper*>(SEGENV.data);
|
||||
arti = new ARTI();
|
||||
|
||||
succesful = arti->setup("/wledv033.json", currentEffect);
|
||||
successful = arti->setup("/wledv033.json", currentEffect);
|
||||
|
||||
if (!succesful)
|
||||
ERROR_ARTI("Setup not succesful\n");
|
||||
if (!successful)
|
||||
ERROR_ARTI("Setup not successful\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (succesful) // && SEGENV.call < 250 for each frame
|
||||
if (successful) // && SEGENV.call < 250 for each frame
|
||||
{
|
||||
if (FREE_SIZE <= 20000)
|
||||
{
|
||||
ERROR_ARTI("Not enough free heap (%u <= 30000)\n", FREE_SIZE);
|
||||
notEnoughHeap = true;
|
||||
succesful = false;
|
||||
successful = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -71,7 +71,7 @@ uint16_t mode_ARTIFX(void) {
|
||||
// previousCall = SEGENV.call;
|
||||
// }
|
||||
|
||||
succesful = arti->loop();
|
||||
successful = arti->loop();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -79,7 +79,7 @@ uint16_t mode_ARTIFX(void) {
|
||||
arti->closeLog();
|
||||
if (notEnoughHeap && FREE_SIZE > 20000) {
|
||||
ERROR_ARTI("Again enough free heap, restart effect (%u > 30000)\n", FREE_SIZE);
|
||||
succesful = true;
|
||||
successful = true;
|
||||
notEnoughHeap = false;
|
||||
strcpy(previousEffect, ""); // force new create
|
||||
}
|
||||
|
||||
@@ -21,13 +21,18 @@
|
||||
* ....
|
||||
*/
|
||||
|
||||
#define FFT_PREFER_EXACT_PEAKS // use different FFT wndowing -> results in "sharper" peaks and less "leaking" into other frequencies
|
||||
#define FFT_PREFER_EXACT_PEAKS // use different FFT windowing -> results in "sharper" peaks and less "leaking" into other frequencies
|
||||
//#define SR_STATS
|
||||
|
||||
#if !defined(FFTTASK_PRIORITY)
|
||||
#if defined(WLEDMM_FASTPATH) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(ARDUINO_ARCH_ESP32)
|
||||
// FASTPATH: use higher priority, to avoid that webserver (ws, json, etc) delays sample processing
|
||||
//#define FFTTASK_PRIORITY 3 // competing with async_tcp
|
||||
#define FFTTASK_PRIORITY 4 // above async_tcp
|
||||
#else
|
||||
#define FFTTASK_PRIORITY 1 // standard: looptask prio
|
||||
//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp
|
||||
//#define FFTTASK_PRIORITY 4 // above asyc_tcp
|
||||
//#define FFTTASK_PRIORITY 2 // above looptask, below async_tcp
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
@@ -86,8 +91,27 @@
|
||||
#define PLOT_FLUSH()
|
||||
#endif
|
||||
|
||||
// sanity checks
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// we need more space in for oappend() stack buffer -> SETTINGS_STACK_BUF_SIZE and CONFIG_ASYNC_TCP_TASK_STACK_SIZE
|
||||
#if SETTINGS_STACK_BUF_SIZE < 3904 // 3904 is required for WLEDMM-0.14.0-b28
|
||||
#warning please increase SETTINGS_STACK_BUF_SIZE >= 3904
|
||||
#endif
|
||||
#if (CONFIG_ASYNC_TCP_TASK_STACK_SIZE - SETTINGS_STACK_BUF_SIZE) < 4352 // at least 4096+256 words of free task stack is needed by async_tcp alone
|
||||
#error remaining async_tcp stack will be too low - please increase CONFIG_ASYNC_TCP_TASK_STACK_SIZE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// audiosync constants
|
||||
#define AUDIOSYNC_NONE 0x00 // UDP sound sync off
|
||||
#define AUDIOSYNC_SEND 0x01 // UDP sound sync - send mode
|
||||
#define AUDIOSYNC_REC 0x02 // UDP sound sync - receiver mode
|
||||
#define AUDIOSYNC_REC_PLUS 0x06 // UDP sound sync - receiver + local mode (uses local input if no receiving udp sound)
|
||||
#define AUDIOSYNC_IDLE_MS 2500 // timeout for "receiver idle" (milliseconds)
|
||||
|
||||
static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks.
|
||||
static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value)
|
||||
static uint8_t audioSyncEnabled = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving
|
||||
static bool audioSyncSequence = true; // if true, the receiver will drop out-of-sequence packets
|
||||
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
|
||||
|
||||
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
|
||||
@@ -109,9 +133,13 @@ static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc
|
||||
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
|
||||
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
|
||||
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData
|
||||
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
|
||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
|
||||
volatile bool haveNewFFTResult = false; // flag to directly inform UDP sound sender when new FFT results are available (to reduce latency). Flag is reset at next UDP send
|
||||
|
||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; // Our calculated freq. channel result table to be used by effects
|
||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter)
|
||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||
|
||||
// TODO: probably best not used by receive nodes
|
||||
static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255
|
||||
@@ -123,7 +151,7 @@ static uint16_t decayTime = 300; // int: decay time in milliseconds
|
||||
|
||||
// peak detection
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) - no used for 8266 receive-only mode
|
||||
static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode
|
||||
#endif
|
||||
static void autoResetPeak(void); // peak auto-reset function
|
||||
static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
@@ -150,7 +178,7 @@ static uint8_t inputLevel = 128; // UI slider value
|
||||
#endif
|
||||
|
||||
// user settable options for FFTResult scaling
|
||||
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root
|
||||
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root
|
||||
#ifndef SR_FREQ_PROF
|
||||
static uint8_t pinkIndex = 0; // 0: default; 1: line-in; 2: IMNP441
|
||||
#else
|
||||
@@ -220,7 +248,7 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); //
|
||||
static TaskHandle_t FFT_Task = nullptr;
|
||||
|
||||
// Table of multiplication factors so that we can even out the frequency response.
|
||||
#define MAX_PINK 10 // 0 = standard, 1= line-in (pink moise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment)
|
||||
#define MAX_PINK 10 // 0 = standard, 1= line-in (pink noise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment)
|
||||
static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
{ 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }, // 0 default from SR WLED
|
||||
// { 1.30f, 1.32f, 1.40f, 1.46f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 2.39f, 3.09f, 4.34f }, // - Line-In Generic -> pink noise adjustment only
|
||||
@@ -244,7 +272,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
/* how to make your own profile:
|
||||
* ===============================
|
||||
* preparation: make sure your microphone has direct line-of-sigh with the speaker, 1-2meter distance is best
|
||||
* Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Trebble controls set to middle.
|
||||
* Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Treble controls set to middle.
|
||||
* Your HiFi equipment should receive its audio input from Line-In, SPDIF, HDMI, or another "undistorted" connection (like CDROM).
|
||||
* Try not to use Bluetooth or MP3 when playing the "pink noise" audio. BT-audio and MP3 both perform "acoustic adjustments" that we don't want now.
|
||||
|
||||
@@ -258,7 +286,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
|
||||
* Your own profile:
|
||||
* - Target for each LED bar is 50% to 75% of the max height --> 8(high) x 16(wide) panel means target = 5. 32 x 16 means target = 22.
|
||||
* - From left to right - count the LEDs in each of the 16 frequency colums (that's why you need the photo). This is the barheight for each channel.
|
||||
* - From left to right - count the LEDs in each of the 16 frequency columns (that's why you need the photo). This is the barheight for each channel.
|
||||
* - math time! Find the multiplier that will bring each bar to to target.
|
||||
* * in case of square root scale: multiplier = (target * target) / (barheight * barheight)
|
||||
* * in case of linear scale: multiplier = target / barheight
|
||||
@@ -280,8 +308,6 @@ static float sampleTime = 0; // avg (blocked) time for reading I2S sample
|
||||
|
||||
// FFT Task variables (filtering and post-processing)
|
||||
static float lastFftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // backup of last FFT channels (before postprocessing)
|
||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256.
|
||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
// audio source parameters and constant
|
||||
@@ -360,7 +386,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
// compute average of several FFT resut bins
|
||||
// compute average of several FFT result bins
|
||||
// linear average
|
||||
static float fftAddAvgLin(int from, int to) {
|
||||
float result = 0.0f;
|
||||
@@ -430,7 +456,7 @@ void FFTcode(void * parameter)
|
||||
for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++) {
|
||||
float binFreq = binInd * binWidth + binWidth/2.0f;
|
||||
if (binFreq > (SAMPLE_RATE * 0.42f))
|
||||
binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // supress noise and aliasing
|
||||
binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // suppress noise and aliasing
|
||||
pinkFactors[binInd] = sqrtf(binFreq) / pinkcenter;
|
||||
}
|
||||
pinkFactors[0] *= 0.5; // suppress 0-42hz bin
|
||||
@@ -442,7 +468,7 @@ void FFTcode(void * parameter)
|
||||
// taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work.
|
||||
|
||||
// Don't run FFT computing code if we're in Receive mode or in realtime mode
|
||||
if (disableSoundProcessing || (audioSyncEnabled & 0x02)) {
|
||||
if (disableSoundProcessing || (audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
isFirstRun = false;
|
||||
vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers
|
||||
continue;
|
||||
@@ -482,7 +508,7 @@ void FFTcode(void * parameter)
|
||||
#endif
|
||||
|
||||
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
||||
isFirstRun = !isFirstRun; // toggle throtte
|
||||
isFirstRun = !isFirstRun; // toggle throttle
|
||||
|
||||
#ifdef MIC_LOGGER
|
||||
float datMin = 0.0f;
|
||||
@@ -499,6 +525,11 @@ void FFTcode(void * parameter)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WLEDMM_FASTPATH) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(ARDUINO_ARCH_ESP32)
|
||||
// experimental - be nice to LED update task (trying to avoid flickering) - dual core only
|
||||
if (strip.isServicing()) delay(2);
|
||||
#endif
|
||||
|
||||
// band pass filter - can reduce noise floor by a factor of 50
|
||||
// downside: frequencies below 100Hz will be ignored
|
||||
if ((useInputFilter > 0) && (useInputFilter < 99)) {
|
||||
@@ -619,7 +650,7 @@ void FFTcode(void * parameter)
|
||||
*
|
||||
* Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap.
|
||||
* Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255.
|
||||
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins.
|
||||
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins.
|
||||
* End frequency = Start frequency * multiplier ^ 16
|
||||
* Multiplier = (End frequency/ Start frequency) ^ 1/16
|
||||
* Multiplier = 1.320367784
|
||||
@@ -673,7 +704,7 @@ void FFTcode(void * parameter)
|
||||
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
|
||||
else if (freqDist == 1) { //WLEDMM: Rightshift: note ewowi: frequencies in comments are not correct
|
||||
if (useInputFilter==1) {
|
||||
// skip frequencies below 100hz
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(1,1);
|
||||
@@ -717,7 +748,7 @@ void FFTcode(void * parameter)
|
||||
memcpy(fftCalc, lastFftCalc, sizeof(fftCalc)); // restore last "good" channels
|
||||
}
|
||||
|
||||
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
|
||||
// post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling)
|
||||
if (pinkIndex > MAX_PINK) pinkIndex = MAX_PINK;
|
||||
//postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS);
|
||||
postProcessFFTResults((fabsf(volumeSmth)>0.25f)? true : false , NUM_GEQ_CHANNELS); // this function modifies fftCalc, fftAvg and fftResult
|
||||
@@ -736,6 +767,9 @@ void FFTcode(void * parameter)
|
||||
autoResetPeak();
|
||||
detectSamplePeak();
|
||||
|
||||
// we have new results - notify UDP sound send
|
||||
haveNewFFTResult = true;
|
||||
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC
|
||||
#endif
|
||||
@@ -775,7 +809,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p
|
||||
// FIR lowpass, to remove high frequency noise
|
||||
float highFilteredSample;
|
||||
if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes
|
||||
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // spcial handling for last sample in array
|
||||
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array
|
||||
last_vals[1] = last_vals[0];
|
||||
last_vals[0] = sampleBuffer[i];
|
||||
sampleBuffer[i] = highFilteredSample;
|
||||
@@ -904,7 +938,7 @@ static void autoResetPeak(void) {
|
||||
uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
|
||||
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
|
||||
samplePeak = false;
|
||||
if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData
|
||||
if (audioSyncEnabled == AUDIOSYNC_NONE) udpSamplePeak = false; // this is normally reset by transmitAudioData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +1000,7 @@ class AudioReactive : public Usermod {
|
||||
float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting
|
||||
float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting
|
||||
uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude
|
||||
uint8_t reserved1; // 01 Bytes - for future extensions - not used yet
|
||||
uint8_t frameCounter; // 01 Bytes - track duplicate/out of order packets
|
||||
uint8_t fftResult[16]; // 16 Bytes
|
||||
float FFT_Magnitude; // 04 Bytes
|
||||
float FFT_MajorPeak; // 04 Bytes
|
||||
@@ -998,7 +1032,11 @@ class AudioReactive : public Usermod {
|
||||
// variables for UDP sound sync
|
||||
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
|
||||
unsigned long lastTime = 0; // last time of running UDP Microphone Sync
|
||||
#if defined(WLEDMM_FASTPATH)
|
||||
const uint16_t delayMs = 5; // I don't want to sample too often and overload WLED
|
||||
#else
|
||||
const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED
|
||||
#endif
|
||||
uint16_t audioSyncPort= 11988;// default port for UDP sound sync
|
||||
|
||||
bool updateIsRunning = false; // true during OTA.
|
||||
@@ -1010,7 +1048,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
// variables used by getSample() and agcAvg()
|
||||
int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed
|
||||
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler.
|
||||
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller.
|
||||
double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller
|
||||
float expAdjF = 0.0f; // Used for exponential filter.
|
||||
float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
|
||||
@@ -1048,7 +1086,7 @@ class AudioReactive : public Usermod {
|
||||
////////////////////
|
||||
void logAudio()
|
||||
{
|
||||
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable
|
||||
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & AUDIOSYNC_REC) == 0))) return; // no audio available
|
||||
#ifdef MIC_LOGGER
|
||||
// Debugging functions for audio input and sound processing. Comment out the values you want to see
|
||||
PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth + 256.0f); PLOT_PRINT("\t"); // +256 to move above other lines
|
||||
@@ -1141,13 +1179,13 @@ class AudioReactive : public Usermod {
|
||||
* 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal
|
||||
* 3. the amplification depends on signal level:
|
||||
* a) normal zone - very slow adjustment
|
||||
* b) emergency zome (<10% or >90%) - very fast adjustment
|
||||
* b) emergency zone (<10% or >90%) - very fast adjustment
|
||||
*/
|
||||
void agcAvg(unsigned long the_time)
|
||||
{
|
||||
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
|
||||
|
||||
float lastMultAgc = multAgc; // last muliplier used
|
||||
float lastMultAgc = multAgc; // last multiplier used
|
||||
float multAgcTemp = multAgc; // new multiplier
|
||||
float tmpAgc = sampleReal * multAgc; // what-if amplified signal
|
||||
|
||||
@@ -1187,13 +1225,13 @@ class AudioReactive : public Usermod {
|
||||
|
||||
if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping
|
||||
&& (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max)
|
||||
control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping
|
||||
control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping
|
||||
else
|
||||
control_integrated *= 0.9; // spin down that beasty integrator
|
||||
control_integrated *= 0.9; // spin down that integrator beast
|
||||
|
||||
// apply PI Control
|
||||
tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain
|
||||
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone
|
||||
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergency zone
|
||||
multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error;
|
||||
multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
|
||||
} else { // "normal zone"
|
||||
@@ -1201,7 +1239,7 @@ class AudioReactive : public Usermod {
|
||||
multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
|
||||
}
|
||||
|
||||
// limit amplification again - PI controler sometimes "overshoots"
|
||||
// limit amplification again - PI controller sometimes "overshoots"
|
||||
//multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32
|
||||
if (multAgcTemp > 32.0f) multAgcTemp = 32.0f;
|
||||
if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f;
|
||||
@@ -1231,7 +1269,7 @@ class AudioReactive : public Usermod {
|
||||
void getSample()
|
||||
{
|
||||
float sampleAdj; // Gain adjusted sample value
|
||||
float tmpSample; // An interim sample variable used for calculatioins.
|
||||
float tmpSample; // An interim sample variable used for calculations.
|
||||
#ifdef WLEDMM_FASTPATH
|
||||
constexpr float weighting = 0.35f; // slightly reduced filter strength, to reduce audio latency
|
||||
constexpr float weighting2 = 0.25f;
|
||||
@@ -1407,7 +1445,7 @@ class AudioReactive : public Usermod {
|
||||
if (limiterOn == false) return;
|
||||
|
||||
long delta_time = millis() - last_time;
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> silly lil hick-up
|
||||
float deltaSample = volumeSmth - last_volumeSmth;
|
||||
|
||||
if (attackTime > 0) { // user has defined attack time > 0
|
||||
@@ -1425,6 +1463,39 @@ class AudioReactive : public Usermod {
|
||||
last_time = millis();
|
||||
}
|
||||
|
||||
// MM experimental: limiter to smooth GEQ samples (only for UDP sound receiver mode)
|
||||
// target value (if gotNewSample) : fftCalc
|
||||
// last filtered value: fftAvg
|
||||
void limitGEQDynamics(bool gotNewSample) {
|
||||
constexpr float bigChange = 202; // just a representative number - a large, expected sample value
|
||||
constexpr float smooth = 0.8f; // a bit of filtering
|
||||
static unsigned long last_time = 0;
|
||||
|
||||
if (limiterOn == false) return;
|
||||
|
||||
if (gotNewSample) { // take new FFT samples as target values
|
||||
for(unsigned i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
fftCalc[i] = fftResult[i];
|
||||
fftResult[i] = fftAvg[i];
|
||||
}
|
||||
}
|
||||
|
||||
long delta_time = millis() - last_time;
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> silly lil hick-up
|
||||
float maxAttack = (attackTime <= 0) ? 255.0f : (bigChange * float(delta_time) / float(attackTime));
|
||||
float maxDecay = (decayTime <= 0) ? -255.0f : (-bigChange * float(delta_time) / float(decayTime));
|
||||
|
||||
for(unsigned i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
float deltaSample = fftCalc[i] - fftAvg[i];
|
||||
if (deltaSample > maxAttack) deltaSample = maxAttack;
|
||||
if (deltaSample < maxDecay) deltaSample = maxDecay;
|
||||
deltaSample = deltaSample * smooth;
|
||||
fftAvg[i] = fmaxf(0.0f, fminf(255.0f, fftAvg[i] + deltaSample));
|
||||
fftResult[i] = fftAvg[i];
|
||||
}
|
||||
last_time = millis();
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// UDP Sound Sync //
|
||||
//////////////////////
|
||||
@@ -1435,7 +1506,7 @@ class AudioReactive : public Usermod {
|
||||
// necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection
|
||||
static unsigned long last_connection_attempt = 0;
|
||||
|
||||
if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled
|
||||
if ((audioSyncPort <= 0) || (audioSyncEnabled == AUDIOSYNC_NONE)) return; // Sound Sync not enabled
|
||||
if (!(apActive || WLED_CONNECTED || interfacesInited)) {
|
||||
if (udpSyncConnected) {
|
||||
udpSyncConnected = false;
|
||||
@@ -1443,11 +1514,11 @@ class AudioReactive : public Usermod {
|
||||
receivedFormat = 0;
|
||||
DEBUGSR_PRINTLN(F("AR connectUDPSoundSync(): connection lost, UDP closed."));
|
||||
}
|
||||
return; // neither AP nor other connections availeable
|
||||
return; // neither AP nor other connections available
|
||||
}
|
||||
if (udpSyncConnected) return; // already connected
|
||||
if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds
|
||||
if (updateIsRunning) return; // don't reconect during OTA
|
||||
if (updateIsRunning) return; // don't reconnect during OTA
|
||||
|
||||
// if we arrive here, we need a UDP connection but don't have one
|
||||
last_connection_attempt = millis();
|
||||
@@ -1457,16 +1528,19 @@ class AudioReactive : public Usermod {
|
||||
void transmitAudioData()
|
||||
{
|
||||
if (!udpSyncConnected) return;
|
||||
static uint8_t frameCounter = 0;
|
||||
//DEBUGSR_PRINTLN("Transmitting UDP Mic Packet");
|
||||
|
||||
audioSyncPacket transmitData;
|
||||
memset(reinterpret_cast<void *>(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized
|
||||
|
||||
strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6);
|
||||
// transmit samples that were not modified by limitSampleDynamics()
|
||||
transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw;
|
||||
transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg;
|
||||
transmitData.samplePeak = udpSamplePeak ? 1:0;
|
||||
udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it
|
||||
transmitData.reserved1 = 0;
|
||||
transmitData.frameCounter = frameCounter;
|
||||
|
||||
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254);
|
||||
@@ -1479,7 +1553,8 @@ class AudioReactive : public Usermod {
|
||||
fftUdp.write(reinterpret_cast<uint8_t *>(&transmitData), sizeof(transmitData));
|
||||
fftUdp.endPacket();
|
||||
}
|
||||
return;
|
||||
|
||||
frameCounter++;
|
||||
} // transmitAudioData()
|
||||
#endif
|
||||
static bool isValidUdpSyncVersion(const char *header) {
|
||||
@@ -1489,8 +1564,29 @@ class AudioReactive : public Usermod {
|
||||
return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0;
|
||||
}
|
||||
|
||||
void decodeAudioData(int packetSize, uint8_t *fftBuff) {
|
||||
bool decodeAudioData(int packetSize, uint8_t *fftBuff) {
|
||||
if((0 == packetSize) || (nullptr == fftBuff)) return false; // sanity check
|
||||
audioSyncPacket *receivedPacket = reinterpret_cast<audioSyncPacket*>(fftBuff);
|
||||
|
||||
// validate sequence, discard out-of-sequence packets
|
||||
static uint8_t lastFrameCounter = 0;
|
||||
// add info for UI
|
||||
if ((receivedPacket->frameCounter > 0) && (lastFrameCounter > 0)) receivedFormat = 3; // v2+
|
||||
else receivedFormat = 2; // v2
|
||||
// check sequence
|
||||
bool sequenceOK = false;
|
||||
if(receivedPacket->frameCounter > lastFrameCounter) sequenceOK = true; // sequence OK
|
||||
if((lastFrameCounter < 12) && (receivedPacket->frameCounter > 248)) sequenceOK = false; // prevent sequence "roll-back" due to late packets (1->254)
|
||||
if((lastFrameCounter > 248) && (receivedPacket->frameCounter < 12)) sequenceOK = true; // handle roll-over (255 -> 0)
|
||||
if(audioSyncSequence == false) sequenceOK = true; // sequence checking disabled by user
|
||||
if((sequenceOK == false) && (receivedPacket->frameCounter != 0)) { // always accept "0" - its the legacy value
|
||||
DEBUGSR_PRINTF("Skipping audio frame out of order or duplicated - %u vs %u\n", lastFrameCounter, receivedPacket->frameCounter);
|
||||
return false; // reject out-of sequence frame
|
||||
}
|
||||
else {
|
||||
lastFrameCounter = receivedPacket->frameCounter;
|
||||
}
|
||||
|
||||
// update samples for effects
|
||||
volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f);
|
||||
volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f);
|
||||
@@ -1515,6 +1611,7 @@ class AudioReactive : public Usermod {
|
||||
my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f);
|
||||
FFT_Magnitude = my_magnitude;
|
||||
FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) {
|
||||
@@ -1577,16 +1674,15 @@ class AudioReactive : public Usermod {
|
||||
|
||||
// VERIFY THAT THIS IS A COMPATIBLE PACKET
|
||||
if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftUdpBuffer))) {
|
||||
decodeAudioData(packetSize, fftUdpBuffer);
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2");
|
||||
haveFreshData = true;
|
||||
receivedFormat = 2;
|
||||
haveFreshData = decodeAudioData(packetSize, fftUdpBuffer);
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2");
|
||||
} else {
|
||||
if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftUdpBuffer))) {
|
||||
decodeAudioData_v1(packetSize, fftUdpBuffer);
|
||||
receivedFormat = 1;
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1");
|
||||
haveFreshData = true;
|
||||
receivedFormat = 1;
|
||||
} else receivedFormat = 0; // unknown format
|
||||
}
|
||||
}
|
||||
@@ -1641,7 +1737,7 @@ class AudioReactive : public Usermod {
|
||||
um_data->u_type[10] = UMT_FLOAT;
|
||||
#else
|
||||
// ESP8266
|
||||
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explaination of these alternative sources of data
|
||||
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data
|
||||
|
||||
um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall)
|
||||
um_data->u_type[6] = UMT_BYTE;
|
||||
@@ -1739,6 +1835,23 @@ class AudioReactive : public Usermod {
|
||||
if (i2c_sda >= 0) sdaPin = -1; // -1 = use global
|
||||
if (i2c_scl >= 0) sclPin = -1;
|
||||
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
case 7:
|
||||
#ifdef use_wm8978_mic
|
||||
DEBUGSR_PRINTLN(F("AR: WM8978 Source (Mic)"));
|
||||
#else
|
||||
DEBUGSR_PRINTLN(F("AR: WM8978 Source (Line-In)"));
|
||||
#endif
|
||||
audioSource = new WM8978Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f);
|
||||
//useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour
|
||||
delay(100);
|
||||
// WLEDMM align global pins
|
||||
if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined)
|
||||
if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin;
|
||||
if (i2c_sda >= 0) sdaPin = -1; // -1 = use global
|
||||
if (i2c_scl >= 0) sclPin = -1;
|
||||
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
|
||||
@@ -1758,7 +1871,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
if (!audioSource) enabled = false; // audio failed to initialise
|
||||
#endif
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initailize network
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
|
||||
@@ -1803,7 +1916,7 @@ class AudioReactive : public Usermod {
|
||||
DEBUGSR_PRINTLN(F("AR connected(): old UDP connection closed."));
|
||||
}
|
||||
|
||||
if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) {
|
||||
if ((audioSyncPort > 0) && (audioSyncEnabled > AUDIOSYNC_NONE)) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort);
|
||||
#else
|
||||
@@ -1844,7 +1957,18 @@ class AudioReactive : public Usermod {
|
||||
return;
|
||||
}
|
||||
// We cannot wait indefinitely before processing audio data
|
||||
if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice
|
||||
if (strip.isServicing() && (millis() - lastUMRun < 2)) return; // WLEDMM isServicing() is the critical part (be nice, but not too nice)
|
||||
|
||||
// sound sync "receive or local"
|
||||
bool useNetworkAudio = false;
|
||||
if (audioSyncEnabled > AUDIOSYNC_SEND) { // we are in "receive" or "receive+local" mode
|
||||
if (udpSyncConnected && ((millis() - last_UDPTime) <= AUDIOSYNC_IDLE_MS))
|
||||
useNetworkAudio = true;
|
||||
else
|
||||
useNetworkAudio = false;
|
||||
if (audioSyncEnabled == AUDIOSYNC_REC)
|
||||
useNetworkAudio = true; // don't fall back to local audio in standard "receive mode"
|
||||
}
|
||||
|
||||
// suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET)
|
||||
if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed
|
||||
@@ -1855,27 +1979,32 @@ class AudioReactive : public Usermod {
|
||||
||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed
|
||||
{
|
||||
#ifdef WLED_DEBUG
|
||||
if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled"
|
||||
if ((disableSoundProcessing == false) && (audioSyncEnabled < AUDIOSYNC_REC)) { // we just switched to "disabled"
|
||||
DEBUG_PRINTLN("[AR userLoop] realtime mode active - audio processing suspended.");
|
||||
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
|
||||
}
|
||||
#endif
|
||||
disableSoundProcessing = true;
|
||||
useNetworkAudio = false;
|
||||
} else {
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG)
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled"
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled < AUDIOSYNC_REC) && audioSource->isInitialized()) { // we just switched to "enabled"
|
||||
DEBUG_PRINTLN("[AR userLoop] realtime mode ended - audio processing resumed.");
|
||||
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
|
||||
}
|
||||
#endif
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled < AUDIOSYNC_REC)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping
|
||||
disableSoundProcessing = false;
|
||||
}
|
||||
|
||||
if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode
|
||||
if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode
|
||||
if (audioSyncEnabled == AUDIOSYNC_REC) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode
|
||||
if (audioSyncEnabled & AUDIOSYNC_SEND) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source
|
||||
if (!audioSource->isInitialized()) { // no audio source
|
||||
disableSoundProcessing = true;
|
||||
if (audioSyncEnabled > AUDIOSYNC_SEND) useNetworkAudio = true;
|
||||
}
|
||||
if ((audioSyncEnabled == AUDIOSYNC_REC_PLUS) && useNetworkAudio) disableSoundProcessing = true; // UDP sound receiving - disable local audio
|
||||
|
||||
#ifdef SR_DEBUG
|
||||
// debug info in case that task stack usage changes
|
||||
@@ -1888,7 +2017,7 @@ class AudioReactive : public Usermod {
|
||||
#endif
|
||||
|
||||
// Only run the sampling code IF we're not in Receive mode or realtime mode
|
||||
if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) {
|
||||
if ((audioSyncEnabled != AUDIOSYNC_REC) && !disableSoundProcessing && !useNetworkAudio) {
|
||||
if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation)
|
||||
|
||||
unsigned long t_now = millis(); // remember current time
|
||||
@@ -1897,9 +2026,9 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#if defined(SR_DEBUG)
|
||||
// complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second.
|
||||
// softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS
|
||||
//if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
|
||||
//DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay);
|
||||
// softhack007 disabled temporarily - avoid serial console spam with MANY LEDs and low FPS
|
||||
//if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == AUDIOSYNC_NONE)) {
|
||||
//DEBUG_PRINTF("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n", userloopDelay);
|
||||
//}
|
||||
#endif
|
||||
|
||||
@@ -1945,27 +2074,33 @@ class AudioReactive : public Usermod {
|
||||
connectUDPSoundSync(); // ensure we have a connection - if needed
|
||||
|
||||
// UDP Microphone Sync - receive mode
|
||||
if ((audioSyncEnabled & 0x02) && udpSyncConnected) {
|
||||
if ((audioSyncEnabled & AUDIOSYNC_REC) && udpSyncConnected) {
|
||||
// Only run the audio listener code if we're in Receive mode
|
||||
static float syncVolumeSmth = 0;
|
||||
bool have_new_sample = false;
|
||||
if (millis() - lastTime > delayMs) {
|
||||
have_new_sample = receiveAudioData();
|
||||
if (have_new_sample) last_UDPTime = millis();
|
||||
if (have_new_sample) {
|
||||
last_UDPTime = millis();
|
||||
useNetworkAudio = true; // UDP input arrived - use it
|
||||
}
|
||||
lastTime = millis();
|
||||
} else {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
fftUdp.flush(); // WLEDMM: Flush this if we haven't read it. Does not work on 8266.
|
||||
#endif
|
||||
}
|
||||
if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample
|
||||
else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter
|
||||
limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups
|
||||
if (useNetworkAudio) {
|
||||
if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample
|
||||
else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter
|
||||
limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups
|
||||
limitGEQDynamics(have_new_sample); // WLEDMM experimental: smooth FFT (GEQ) samples
|
||||
}
|
||||
} else {
|
||||
receivedFormat = 0;
|
||||
}
|
||||
|
||||
if ( (audioSyncEnabled & 0x02) // receive mode
|
||||
if ( (audioSyncEnabled & AUDIOSYNC_REC) // receive mode
|
||||
&& udpSyncConnected // connected
|
||||
&& (receivedFormat > 0) // we actually received something in the past
|
||||
&& ((millis() - last_UDPTime) > 25000)) { // close connection after 25sec idle
|
||||
@@ -2011,7 +2146,12 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
//UDP Microphone Sync - transmit mode
|
||||
if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) {
|
||||
#if defined(WLEDMM_FASTPATH)
|
||||
if ((audioSyncEnabled & AUDIOSYNC_SEND) && (haveNewFFTResult || (millis() - lastTime > 24))) { // fastpath: send data once results are ready, or each 25ms as fallback (max sampling time is 23ms)
|
||||
#else
|
||||
if ((audioSyncEnabled & AUDIOSYNC_SEND) && (millis() - lastTime > 20)) { // standard: send data each 20ms
|
||||
#endif
|
||||
haveNewFFTResult = false; // reset notification
|
||||
// Only run the transmit code IF we're in Transmit mode
|
||||
transmitAudioData();
|
||||
lastTime = millis();
|
||||
@@ -2161,7 +2301,15 @@ class AudioReactive : public Usermod {
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
if (enabled) {
|
||||
bool audioSyncIDLE = false; // true if sound sync is not receiving
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// audio sync status
|
||||
if ((audioSyncEnabled & AUDIOSYNC_REC) && (!udpSyncConnected || (millis() - last_UDPTime > AUDIOSYNC_IDLE_MS))) // connected and nothing received in 2.5sec
|
||||
audioSyncIDLE = true;
|
||||
if ((audioSource == nullptr) || (!audioSource->isInitialized())) // local audio not configured
|
||||
audioSyncIDLE = false;
|
||||
|
||||
// Input Level Slider
|
||||
if (disableSoundProcessing == false) { // only show slider when audio processing is running
|
||||
if (soundAgc > 0) {
|
||||
@@ -2188,11 +2336,11 @@ class AudioReactive : public Usermod {
|
||||
// The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG
|
||||
// current Audio input
|
||||
infoArr = user.createNestedArray(F("Audio Source"));
|
||||
if (audioSyncEnabled & 0x02) {
|
||||
if ((audioSyncEnabled == AUDIOSYNC_REC) || (!audioSyncIDLE && (audioSyncEnabled == AUDIOSYNC_REC_PLUS))){
|
||||
// UDP sound sync - receive mode
|
||||
infoArr.add(F("UDP sound sync"));
|
||||
if (udpSyncConnected) {
|
||||
if (millis() - last_UDPTime < 2500)
|
||||
if (millis() - last_UDPTime < AUDIOSYNC_IDLE_MS)
|
||||
infoArr.add(F(" - receiving"));
|
||||
else
|
||||
infoArr.add(F(" - idle"));
|
||||
@@ -2207,7 +2355,7 @@ class AudioReactive : public Usermod {
|
||||
} else {
|
||||
// Analog or I2S digital input
|
||||
if (audioSource && (audioSource->isInitialized())) {
|
||||
// audio source sucessfully configured
|
||||
// audio source successfully configured
|
||||
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
|
||||
infoArr.add(F("ADC analog"));
|
||||
} else {
|
||||
@@ -2240,13 +2388,13 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
// AGC or manual Gain
|
||||
if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) {
|
||||
if ((soundAgc == 0) && (disableSoundProcessing == false) && !(audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
infoArr = user.createNestedArray(F("Manual Gain"));
|
||||
float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets
|
||||
infoArr.add(roundf(myGain*100.0f) / 100.0f);
|
||||
infoArr.add("x");
|
||||
}
|
||||
if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) {
|
||||
if ((soundAgc > 0) && (disableSoundProcessing == false) && !(audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
infoArr = user.createNestedArray(F("AGC Gain"));
|
||||
infoArr.add(roundf(multAgc*100.0f) / 100.0f);
|
||||
infoArr.add("x");
|
||||
@@ -2255,18 +2403,24 @@ class AudioReactive : public Usermod {
|
||||
// UDP Sound Sync status
|
||||
infoArr = user.createNestedArray(F("UDP Sound Sync"));
|
||||
if (audioSyncEnabled) {
|
||||
if (audioSyncEnabled & 0x01) {
|
||||
if (audioSyncEnabled & AUDIOSYNC_SEND) {
|
||||
infoArr.add(F("send mode"));
|
||||
if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2"));
|
||||
} else if (audioSyncEnabled & 0x02) {
|
||||
if ((udpSyncConnected) && (millis() - lastTime < AUDIOSYNC_IDLE_MS)) infoArr.add(F(" v2+"));
|
||||
} else if (audioSyncEnabled == AUDIOSYNC_REC) {
|
||||
infoArr.add(F("receive mode"));
|
||||
} else if (audioSyncEnabled == AUDIOSYNC_REC_PLUS) {
|
||||
infoArr.add(F("receive+local mode"));
|
||||
}
|
||||
} else
|
||||
infoArr.add("off");
|
||||
if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" <i>(unconnected)</i>");
|
||||
if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) {
|
||||
if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < AUDIOSYNC_IDLE_MS)) {
|
||||
if (receivedFormat == 1) infoArr.add(F(" v1"));
|
||||
if (receivedFormat == 2) infoArr.add(F(" v2"));
|
||||
if (receivedFormat == 3) {
|
||||
if (audioSyncSequence) infoArr.add(F(" v2+")); // Sequence checking enabled
|
||||
else infoArr.add(F(" v2"));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
|
||||
@@ -2418,6 +2572,7 @@ class AudioReactive : public Usermod {
|
||||
JsonObject sync = top.createNestedObject("sync");
|
||||
sync[F("port")] = audioSyncPort;
|
||||
sync[F("mode")] = audioSyncEnabled;
|
||||
sync[F("check_sequence")] = audioSyncSequence;
|
||||
}
|
||||
|
||||
|
||||
@@ -2486,6 +2641,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort);
|
||||
configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled);
|
||||
configComplete &= getJsonValue(top["sync"][F("check_sequence")], audioSyncSequence);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
@@ -2548,7 +2704,11 @@ class AudioReactive : public Usermod {
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'ES8388 ☾',6);"));
|
||||
#endif
|
||||
|
||||
#if SR_DMTYPE==7
|
||||
oappend(SET_F("addOption(dd,'WM8978 ☾ (⎌)',7);"));
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'WM8978 ☾',7);"));
|
||||
#endif
|
||||
#ifdef SR_SQUELCH
|
||||
oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'<i>⎌ ")); oappendi(SR_SQUELCH); oappend("</i>');"); // 0 is field type, 1 is actual field
|
||||
#endif
|
||||
@@ -2652,12 +2812,20 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("addInfo('AudioReactive:frequency:profile',1,'☾');"));
|
||||
#endif
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);")); // AUDIOSYNC_NONE
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
oappend(SET_F("addOption(dd,'Send',1);"));
|
||||
oappend(SET_F("addOption(dd,'Send',1);")); // AUDIOSYNC_SEND
|
||||
#endif
|
||||
oappend(SET_F("addOption(dd,'Receive',2);"));
|
||||
oappend(SET_F("addInfo('AudioReactive:sync:mode',1,'<br> Sync audio data with other WLEDs');"));
|
||||
oappend(SET_F("addOption(dd,'Receive',2);")); // AUDIOSYNC_REC
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
oappend(SET_F("addOption(dd,'Receive or Local',6);")); // AUDIOSYNC_REC_PLUS
|
||||
#endif
|
||||
// check_sequence: Receiver skips out-of-sequence packets when enabled
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','sync:check_sequence');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
oappend(SET_F("addOption(dd,'On',1);"));
|
||||
|
||||
oappend(SET_F("addInfo('AudioReactive:sync:check_sequence',1,'<i>when receiving</i> ☾<br> Sync audio data with other WLEDs');")); // must append this to the last field of 'sync'
|
||||
|
||||
oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'<i>requires reboot!</i>');")); // 0 is field type, 1 is actual field
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -2700,7 +2868,7 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");");
|
||||
#endif
|
||||
oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',5);")); // disable read only pins
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches"
|
||||
// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed;
|
||||
// for example if you want to read "analog buttons"
|
||||
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up
|
||||
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continuously sample analog ADC microphone. WARNING will cause analogRead() lock-up
|
||||
|
||||
// data type requested from the I2S driver - currently we always use 32bit
|
||||
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
|
||||
@@ -72,7 +72,7 @@
|
||||
* if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case.
|
||||
*/
|
||||
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4))
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) // fixed in IDF 4.4.5, however arduino-esp32 2.0.14 did an "I2S rollback" to 4.4.4
|
||||
// espressif bug: only_left has no sound, left and right are swapped
|
||||
// https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138)
|
||||
// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918)
|
||||
@@ -163,7 +163,7 @@ class AudioSource {
|
||||
SRate_t _sampleRate; // Microphone sampling rate
|
||||
int _blockSize; // I2S block size
|
||||
bool _initialized; // Gets set to true if initialization is successful
|
||||
bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only workds in newer IDF >= 4.4.x
|
||||
bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only works in newer IDF >= 4.4.x
|
||||
float _sampleScale; // pre-scaling factor for I2S samples
|
||||
I2S_datatype newSampleBuffer[I2S_SAMPLES_MAX+4] = { 0 }; // global buffer for i2s_read
|
||||
I2S_datatype newSampleBuffer4x[(I2S_SAMPLES_MAX*4)+4] = { 0 }; // global buffer for i2s_read
|
||||
@@ -438,7 +438,7 @@ class I2SSource : public AudioSource {
|
||||
};
|
||||
|
||||
/* ES7243 Microphone
|
||||
This is an I2S microphone that requires ininitialization over
|
||||
This is an I2S microphone that requires initialization over
|
||||
I2C before I2S data can be received
|
||||
*/
|
||||
class ES7243 : public I2SSource {
|
||||
@@ -503,8 +503,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/* ES8388 Sound Modude
|
||||
This is an I2S sound processing unit that requires ininitialization over
|
||||
/* ES8388 Sound Module
|
||||
This is an I2S sound processing unit that requires initialization over
|
||||
I2C before I2S data can be received.
|
||||
*/
|
||||
class ES8388Source : public I2SSource {
|
||||
@@ -552,7 +552,7 @@ class ES8388Source : public I2SSource {
|
||||
// The mics *and* line-in are BOTH connected to LIN2/RIN2 on the AudioKit
|
||||
// so there's no way to completely eliminate the mics. It's also hella noisy.
|
||||
// Line-in works OK on the AudioKit, generally speaking, as the mics really need
|
||||
// amplification to be noticable in a quiet room. If you're in a very loud room,
|
||||
// amplification to be noticeable in a quiet room. If you're in a very loud room,
|
||||
// the mics on the AudioKit WILL pick up sound even in line-in mode.
|
||||
// TL;DR: Don't use the AudioKit for anything, use the LyraT.
|
||||
//
|
||||
@@ -635,6 +635,97 @@ class ES8388Source : public I2SSource {
|
||||
|
||||
};
|
||||
|
||||
class WM8978Source : public I2SSource {
|
||||
private:
|
||||
// I2C initialization functions for WM8978
|
||||
void _wm8978I2cBegin() {
|
||||
Wire.setClock(400000);
|
||||
}
|
||||
|
||||
void _wm8978I2cWrite(uint8_t reg, uint16_t val) {
|
||||
#ifndef WM8978_ADDR
|
||||
#define WM8978_ADDR 0x1A
|
||||
#endif
|
||||
char buf[2];
|
||||
buf[0] = (reg << 1) | ((val >> 8) & 0X01);
|
||||
buf[1] = val & 0XFF;
|
||||
Wire.beginTransmission(WM8978_ADDR);
|
||||
Wire.write((const uint8_t*)buf, 2);
|
||||
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
|
||||
if (i2cErr != 0) {
|
||||
DEBUGSR_PRINTF("AR: WM8978 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, WM8978_ADDR, reg, val);
|
||||
}
|
||||
}
|
||||
|
||||
void _wm8978InitAdc() {
|
||||
// https://www.mouser.com/datasheet/2/76/WM8978_v4.5-1141768.pdf
|
||||
// Sets ADC to around what AudioReactive expects, and loops line-in to line-out/headphone for monitoring.
|
||||
// Registries are decimal, settings are 9-bit binary as that's how everything is listed in the docs
|
||||
// ...which makes it easier to reference the docs.
|
||||
//
|
||||
_wm8978I2cBegin();
|
||||
|
||||
_wm8978I2cWrite( 0,0b000000000); // Reset all settings
|
||||
_wm8978I2cWrite( 1,0b000001011); // Power Management 1 - power off most things
|
||||
_wm8978I2cWrite( 2,0b110110011); // Power Management 2 - enable output and amp stages (amps may lift signal but it works better on the ADCs)
|
||||
_wm8978I2cWrite( 3,0b000001100); // Power Management 3 - enable L&R output mixers
|
||||
_wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit
|
||||
_wm8978I2cWrite( 5,0b000000001); // Loopback Enable
|
||||
_wm8978I2cWrite( 6,0b000000000); // Clock generation control - use external mclk
|
||||
_wm8978I2cWrite( 7,0b000000100); // Sets sample rate to ~24kHz (only used for internal calculations, not I2S)
|
||||
_wm8978I2cWrite(14,0b010001000); // 128x ADC oversampling - high pass filter disabled as it kills the bass response
|
||||
_wm8978I2cWrite(43,0b000110000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(44,0b000000000); // Disconnect microphones
|
||||
_wm8978I2cWrite(45,0b111000000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(46,0b111000000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(47,0b001000000); // 0dB gain on left line-in
|
||||
_wm8978I2cWrite(48,0b001000000); // 0dB gain on right line-in
|
||||
_wm8978I2cWrite(49,0b000000010); // Mixer thermal shutdown enable
|
||||
_wm8978I2cWrite(50,0b000010110); // Output mixer enable only left bypass at 0dB gain
|
||||
_wm8978I2cWrite(51,0b000010110); // Output mixer enable only right bypass at 0dB gain
|
||||
_wm8978I2cWrite(52,0b110111001); // Left line-out enabled at 0dB gain
|
||||
_wm8978I2cWrite(53,0b110111001); // Right line-out enabled at 0db gain
|
||||
_wm8978I2cWrite(54,0b001000000); // Mute left speaker output
|
||||
_wm8978I2cWrite(55,0b101000000); // Mute right speaker output
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
WM8978Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) :
|
||||
I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) {
|
||||
_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
|
||||
};
|
||||
|
||||
void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
|
||||
DEBUGSR_PRINTLN("WM8978Source:: initialize();");
|
||||
|
||||
// if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too
|
||||
// ERRORSR_PRINTF("\nAR: invalid I2S WM8978 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
|
||||
// return;
|
||||
// }
|
||||
// BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here.
|
||||
// Workaround: Set I2C pins here, which will also set them globally.
|
||||
// Bug also exists in ES7243.
|
||||
if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined"
|
||||
ERRORSR_PRINTF("\nAR: invalid WM8978 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
|
||||
return;
|
||||
}
|
||||
if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
|
||||
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
|
||||
return;
|
||||
}
|
||||
|
||||
// First route mclk, then configure ADC over I2C, then configure I2S
|
||||
_wm8978InitAdc();
|
||||
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
I2SSource::deinitialize();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
#if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC)
|
||||
#warning this MCU does not support analog sound input
|
||||
@@ -642,7 +733,7 @@ class ES8388Source : public I2SSource {
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only availeable in "classic" ESP32
|
||||
// ADC over I2S is only available in "classic" ESP32
|
||||
|
||||
/* ADC over I2S Microphone
|
||||
This microphone is an ADC pin sampled via the I2S interval
|
||||
@@ -686,7 +777,7 @@ class I2SAdcSource : public I2SSource {
|
||||
|
||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
||||
if (channel > 9) {
|
||||
if ((channel < 0) || (channel > 9)) { // channel == -1 means "not an ADC pin"
|
||||
USER_PRINTF("AR: Incompatible GPIO used for analog audio input: %d\n", _audioPin);
|
||||
return;
|
||||
} else {
|
||||
@@ -701,7 +792,7 @@ class I2SAdcSource : public I2SSource {
|
||||
return;
|
||||
}
|
||||
|
||||
adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution
|
||||
// adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution - should not be needed, because i2s_set_adc_mode does that any way
|
||||
|
||||
// Enable I2S mode of ADC
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel));
|
||||
@@ -859,4 +950,4 @@ class SPH0654 : public I2SSource {
|
||||
#endif
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Audioreactive usermod
|
||||
|
||||
Enabless controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
|
||||
Enables controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
|
||||
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).
|
||||
|
||||
Does audio processing and provides data structure that specially written effects can use.
|
||||
@@ -19,7 +19,7 @@ This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and
|
||||
## Supported MCUs
|
||||
This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support.
|
||||
|
||||
It will compile succesfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
|
||||
It will compile successfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
|
||||
|
||||
Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3.
|
||||
|
||||
@@ -35,7 +35,7 @@ Customised _arduinoFFT_ library for use with this usermod can be found at https:
|
||||
|
||||
### using latest (develop) _arduinoFFT_ library
|
||||
Alternatively, you can use the latest arduinoFFT development version.
|
||||
ArduinoFFT `develop` library is slightly more accurate, and slighly faster than our customised library, however also needs additional 2kB RAM.
|
||||
ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM.
|
||||
|
||||
* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT`
|
||||
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`
|
||||
@@ -63,7 +63,7 @@ You can use the following additional flags in your `build_flags`
|
||||
* `-D SR_GAIN=x` : Default "gain" setting (60)
|
||||
* `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this).
|
||||
* `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM ressources (not recommended unless you absolutely need this).
|
||||
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
|
||||
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continuously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
|
||||
* `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE)
|
||||
* `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB.
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
// constants
|
||||
#define MCUT_READ_TIME_MS 7500 // read once in 7.5 seconds
|
||||
|
||||
// class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||
class mcuTemp : public Usermod
|
||||
{
|
||||
@@ -9,7 +12,7 @@ class mcuTemp : public Usermod
|
||||
private:
|
||||
float mcutemp = 0;
|
||||
|
||||
// any private methods should go here (non-inline methosd should be defined out of class)
|
||||
// any private methods should go here (non-inline method should be defined out of class)
|
||||
void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message
|
||||
|
||||
public:
|
||||
@@ -25,27 +28,34 @@ public:
|
||||
|
||||
void loop()
|
||||
{
|
||||
static unsigned long lastMQQTTime = 0;
|
||||
// if usermod is disabled or called during strip updating just exit
|
||||
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||
if (!enabled || strip.isUpdating())
|
||||
if (!enabled || (strip.isUpdating() && (millis() - lastTime < MCUT_READ_TIME_MS)))
|
||||
return;
|
||||
|
||||
if (millis() - lastTime < MCUT_READ_TIME_MS) return; // reading each 8 seconds should be enough
|
||||
|
||||
#ifdef ESP8266 // ESP8266
|
||||
// does not seem possible
|
||||
mcutemp = -1;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32S2
|
||||
mcutemp = -1;
|
||||
#else // ESP32 ESP32S3 and ESP32C3
|
||||
mcutemp = roundf(temperatureRead() * 100) / 100;
|
||||
float newmcutemp = roundf(temperatureRead() * 10) / 10;
|
||||
if (abs(newmcutemp - 53.3f) > 0.05f) mcutemp = (mcutemp + 2.0f * newmcutemp) / 3.0f; // skip error value (128 => 53.3deg), apply some filtering
|
||||
#endif
|
||||
|
||||
if (millis() - lastTime > 10000)
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
if (millis() - lastMQQTTime > 15000)
|
||||
{
|
||||
char array[10];
|
||||
snprintf(array, sizeof(array), "%f", mcutemp);
|
||||
snprintf(array, sizeof(array), "%3.1f", mcutemp);
|
||||
publishMqtt(array);
|
||||
lastTime = millis();
|
||||
lastMQQTTime = millis();
|
||||
}
|
||||
#endif
|
||||
lastTime = millis();
|
||||
}
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
@@ -61,8 +71,9 @@ public:
|
||||
|
||||
// this code adds "u":{"ExampleUsermod":[20," lux"]} to the info object
|
||||
// int reading = 20;
|
||||
if (!enabled) return;
|
||||
JsonArray lightArr = user.createNestedArray(FPSTR(_name)); // name
|
||||
lightArr.add(mcutemp); // value
|
||||
lightArr.add(roundf(10.0f * mcutemp)/10.0f); // value, rounded to 1 decimal
|
||||
lightArr.add(F(" °C")); // unit
|
||||
|
||||
// if you are implementing a sensor usermod, you may publish sensor data
|
||||
@@ -72,7 +83,7 @@ public:
|
||||
// temp.add(reading);
|
||||
// temp.add(F("lux"));
|
||||
}
|
||||
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
@@ -96,7 +107,7 @@ public:
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
* 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.
|
||||
|
||||
@@ -185,7 +185,7 @@ class MPU6050Driver : public Usermod {
|
||||
//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
|
||||
if ((INTERRUPT_PIN >= 0) && (pinManager.getPinOwner(INTERRUPT_PIN) != PinOwner::UM_IMU) // only allocate pin if we don't own it already
|
||||
&& !pinManager.allocatePin(INTERRUPT_PIN, false, PinOwner::UM_IMU))
|
||||
{
|
||||
//enabled = false;
|
||||
|
||||
@@ -50,5 +50,5 @@ This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced wi
|
||||
Feedback about the current state is provided at `[mqttDeviceTopic]/switch/0/state`.
|
||||
|
||||
### Home Assistant auto-discovery
|
||||
Auto-discovery information is automatically published and you shoudn't have to do anything to register the switches in Home Assistant.
|
||||
Auto-discovery information is automatically published and you shouldn't have to do anything to register the switches in Home Assistant.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode.
|
||||
Usermod supports PCF8574 I2C port expander to reduce GPIO use.
|
||||
PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set conscutively (e.g. 0x20 and 0x21). You can set address of first expander in settings.
|
||||
PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set in sequence (e.g. 0x20 and 0x21). You can set address of first expander in settings.
|
||||
(**NOTE:** Will require Wire library and global I2C pins defined.)
|
||||
|
||||
## HTTP API
|
||||
|
||||
@@ -196,7 +196,7 @@ class MultiRelay : public Usermod {
|
||||
};
|
||||
|
||||
|
||||
// class implementetion
|
||||
// class implementation
|
||||
|
||||
void MultiRelay::publishMqtt(int relay) {
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
@@ -496,10 +496,10 @@ void MultiRelay::setup() {
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void MultiRelay::loop() {
|
||||
yield();
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
static unsigned long lastUpdate = 0;
|
||||
yield();
|
||||
if (!enabled || (strip.isUpdating() && millis() - lastUpdate < 100)) return;
|
||||
|
||||
if (millis() - lastUpdate < 100) return; // update only 10 times/s
|
||||
lastUpdate = millis();
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), e.g. using the OLED and the SHT30 temperature/humidity sensor.
|
||||
|
||||
## Requirements
|
||||
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
|
||||
* "u8g2" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
|
||||
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
|
||||
|
||||
## Usermod installation
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
| `pinSourceSelect` | GPIO that is connected to SD's `SS`(source select) / `CS`(chip select) | 16 |
|
||||
| `pinSourceClock` | GPIO that is connected to SD's `SCLK` (source clock) / `CLK`(clock) | 14 |
|
||||
| `pinPoci` | GPIO that is connected to SD's `POCI`<sup>☨</sup> (Peripheral-Out-Ctrl-In) / `MISO` (deprecated) | 36 |
|
||||
| `pinPico` | GPIO that is connected to SD's `PICO`<sup>☨</sup> (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 14 |
|
||||
| `pinPico` | GPIO that is connected to SD's `PICO`<sup>☨</sup> (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 15 |
|
||||
| `sdEnable` | Enable to read data from the SD-card | true |
|
||||
|
||||
<sup>☨</sup><sub>Following new naming convention of [OSHWA](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/)</sub>
|
||||
@@ -31,4 +31,4 @@
|
||||
- checks if the specified file is available on the SD card
|
||||
```cpp
|
||||
bool file_onSD(const char *filepath) {...}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ The number of individual LEDs per segment. 7 segments per digit.
|
||||
#### perPeriod -- ssLEDPerPeriod
|
||||
The number of individual LEDs per period. A ':' (colon) has two periods.
|
||||
#### startIdx -- ssStartLED
|
||||
Index of the LED the display starts at. Enabless a seven segment display to be in the middle of a string.
|
||||
Index of the LED the display starts at. Enables a seven segment display to be in the middle of a string.
|
||||
#### timeEnable -- ssTimeEnabled
|
||||
When true, when displayMask is configured for a time output and no message is set, the time will be displayed.
|
||||
#### scrollSpd -- ssScrollSpeed
|
||||
|
||||
@@ -409,7 +409,7 @@ public:
|
||||
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
//subcribe for sevenseg messages on the group topic
|
||||
//subscribe for sevenseg messages on the group topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
@@ -417,7 +417,7 @@ public:
|
||||
|
||||
bool onMqttMessage(char *topic, char *payload)
|
||||
{
|
||||
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
|
||||
//If topic beings with sevenSeg cut it off, otherwise not our message.
|
||||
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
|
||||
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
|
||||
topic += topicPrefixLen;
|
||||
|
||||
@@ -470,14 +470,14 @@ public:
|
||||
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
//subcribe for sevenseg messages on the group topic
|
||||
//subscribe for sevenseg messages on the group topic
|
||||
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
|
||||
mqtt->subscribe(subBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool onMqttMessage(char *topic, char *payload) {
|
||||
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
|
||||
//If topic begins with sevenSeg cut it off, otherwise not our message.
|
||||
size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
|
||||
if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
|
||||
topic += topicPrefixLen;
|
||||
|
||||
@@ -331,7 +331,7 @@ void ShtUsermod::loop()
|
||||
/**
|
||||
* Whenever MQTT is connected, publish HA autodiscovery topics.
|
||||
*
|
||||
* Is only donce once.
|
||||
* Is only done once.
|
||||
*
|
||||
* @see Usermod::onMqttConnect()
|
||||
* @see UsermodManager::onMqttConnect()
|
||||
|
||||
@@ -23,7 +23,7 @@ private:
|
||||
unsigned char Enc_B;
|
||||
unsigned char Enc_A_prev = 0;
|
||||
|
||||
// private class memebers configurable by Usermod Settings (defaults set inside readFromConfig())
|
||||
// private class members configurable by Usermod Settings (defaults set inside readFromConfig())
|
||||
int8_t pins[3]; // pins[0] = DT from encoder, pins[1] = CLK from encoder, pins[2] = CLK from encoder (optional)
|
||||
int fadeAmount; // how many points to fade the Neopixel with each step
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
* - configComplete is used to return false if any value is missing, not just if the main object is missing
|
||||
* - The defaults are loaded every time readFromConfig() is run, not just once after boot
|
||||
*
|
||||
* This ensures that missing values are added to the config, with their default values, in the rare but plauible cases of:
|
||||
* This ensures that missing values are added to the config, with their default values, in the rare but plausible cases of:
|
||||
* - a single value being missing at boot, e.g. if the Usermod was upgraded and a new setting was added
|
||||
* - a single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
*
|
||||
|
||||
@@ -109,7 +109,7 @@ class AutoSaveUsermod : public Usermod {
|
||||
// network here
|
||||
void setup() {
|
||||
#ifdef USERMOD_FOUR_LINE_DISPLAY
|
||||
// This Usermod has enhanced funcionality if
|
||||
// This Usermod has enhanced functionality if
|
||||
// FourLineDisplayUsermod is available.
|
||||
display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP);
|
||||
#endif
|
||||
@@ -156,7 +156,7 @@ class AutoSaveUsermod : public Usermod {
|
||||
|
||||
if (autoSaveAfter && now > autoSaveAfter) {
|
||||
autoSaveAfter = 0;
|
||||
// Time to auto save. You may have some flickry?
|
||||
// Time to auto save. You may have some flickery?
|
||||
saveSettings();
|
||||
displayOverlay();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ This file should be placed in the same directory as `platformio.ini`.
|
||||
* `FLD_PIN_SCL` - The display SCL pin, defaults to 5
|
||||
* `FLD_PIN_SDA` - The display SDA pin, defaults to 4
|
||||
|
||||
All of the parameters can be configured via the Usermods settings page, inluding GPIO pins.
|
||||
All of the parameters can be configured via the Usermods settings page, including GPIO pins.
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// for WLED.
|
||||
//
|
||||
// Dependencies
|
||||
// * This usermod REQURES the ModeSortUsermod
|
||||
// * This usermod REQUIRES the ModeSortUsermod
|
||||
// * This Usermod works best, by far, when coupled
|
||||
// with RotaryEncoderUIUsermod.
|
||||
//
|
||||
@@ -398,7 +398,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
drawString(getCols() - 1, 0, "~");
|
||||
}
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Second row with IP or Password
|
||||
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // wifi icon
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "4LD_wled_fonts.c"
|
||||
|
||||
#ifndef FLD_ESP32_NO_THREADS
|
||||
#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!!
|
||||
#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)
|
||||
@@ -671,7 +671,7 @@ void FourLineDisplayUsermod::setup() {
|
||||
}
|
||||
// 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.
|
||||
if (isHW) SPI.begin(spi_sclk, spi_miso, spi_mosi); // ESP32 - will silently fail if SPI already active.
|
||||
#else
|
||||
if (isHW) SPI.begin(); // ESP8266 - SPI pins are fixed
|
||||
#endif
|
||||
@@ -1339,7 +1339,7 @@ void FourLineDisplayUsermod::sleepOrClock(bool sleepEnable) {
|
||||
bool FourLineDisplayUsermod::handleButton(uint8_t b) {
|
||||
yield();
|
||||
if (!enabled
|
||||
|| b // butto 0 only
|
||||
|| b // button 0 only
|
||||
|| buttonType[b] == BTN_TYPE_SWITCH
|
||||
|| buttonType[b] == BTN_TYPE_NONE
|
||||
|| buttonType[b] == BTN_TYPE_RESERVED
|
||||
|
||||
@@ -10,7 +10,7 @@ curl --location --request GET 'http://[]/printer/objects/query?virtual_sdcard=pr
|
||||
## 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:
|
||||
You can also use the WLBD bot in the Discord by simply extending an existing build environment:
|
||||
```
|
||||
[env:esp32klipper]
|
||||
extends = env:esp32dev
|
||||
@@ -23,7 +23,7 @@ build_flags = ${common.build_flags_esp32} -D USERMOD_KLIPPER_PERCENTAGE
|
||||
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
|
||||
IP address of your Klipper instance you want to poll. ESP has to be restarted after change
|
||||
|
||||
### Direction :
|
||||
0 = normal
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
httpGet(wifiClient, errorMessage);
|
||||
if (strcmp(errorMessage, "") == 0)
|
||||
{
|
||||
PSRAMDynamicJsonDocument klipperDoc(4096); // in practive about 2673
|
||||
PSRAMDynamicJsonDocument klipperDoc(4096); // in practice about 2673
|
||||
DeserializationError error = deserializeJson(klipperDoc, wifiClient);
|
||||
if (error)
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ int re_qstringCmp(const void *ap, const void *bp) {
|
||||
// Lowercase
|
||||
bVal -= 32;
|
||||
}
|
||||
// Relly we shouldn't ever get to '\0'
|
||||
// Really we shouldn't ever get to '\0'
|
||||
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
|
||||
// We're done. one is a substring of the other
|
||||
// or something happenend and the quote didn't stop us.
|
||||
|
||||
@@ -7,4 +7,4 @@ Contains a modification to use WLED in combination with the Ping Pong Ball LED C
|
||||
To install this Usermod, you instruct PlatformIO to compile the Project with the USERMOD_PING_PONG_CLOCK flag.
|
||||
WLED then automatically provides you with various settings on the Usermod Page.
|
||||
|
||||
Note: Depending on the size of your clock, you may have to update the led indices for the indivdual numbers and the base indices.
|
||||
Note: Depending on the size of your clock, you may have to update the led indices for the individual numbers and the base indices.
|
||||
|
||||
@@ -18,15 +18,15 @@ private:
|
||||
|
||||
// ---- Variables for correct LED numbering below, edit only if your clock is built different ----
|
||||
|
||||
int baseH = 43; // Adress for the one place of the hours
|
||||
int baseHH = 7; // Adress for the tens place of the hours
|
||||
int baseM = 133; // Adress for the one place of the minutes
|
||||
int baseMM = 97; // Adress for the tens place of the minutes
|
||||
int colon1 = 79; // Adress for the first colon led
|
||||
int colon2 = 80; // Adress for the second colon led
|
||||
int baseH = 43; // Address for the one place of the hours
|
||||
int baseHH = 7; // Address for the tens place of the hours
|
||||
int baseM = 133; // Address for the one place of the minutes
|
||||
int baseMM = 97; // Address for the tens place of the minutes
|
||||
int colon1 = 79; // Address for the first colon led
|
||||
int colon2 = 80; // Address for the second colon led
|
||||
|
||||
// Matrix for the illumination of the numbers
|
||||
// Note: These only define the increments of the base adress. e.g. to define the second Minute you have to add the baseMM to every led position
|
||||
// Note: These only define the increments of the base address. e.g. to define the second Minute you have to add the baseMM to every led position
|
||||
const int numbers[10][10] =
|
||||
{
|
||||
{ 0, 1, 4, 6, 13, 15, 18, 19, -1, -1 }, // 0: null
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// Change between modes by pressing a button.
|
||||
//
|
||||
// Dependencies
|
||||
// * This usermod REQURES the ModeSortUsermod
|
||||
// * This usermod REQUIRES the ModeSortUsermod
|
||||
// * This Usermod works best coupled with
|
||||
// FourLineDisplayUsermod.
|
||||
//
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
// tracking the owner tags....
|
||||
pinA = pinB = pinC = -1;
|
||||
enabled = false;
|
||||
DEBUG_PRINTLN(F("Failed to alocate GPIO pins for Usermod Rotary Encoder.")); //WLEDMM add debug info
|
||||
DEBUG_PRINTLN(F("Failed to allocate GPIO pins for Usermod Rotary Encoder.")); //WLEDMM add debug info
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
//
|
||||
// Inspired by the original v2 usermods
|
||||
// * usermod_v2_rotaty_encoder_ui
|
||||
// * usermod_v2_rotary_encoder_ui
|
||||
//
|
||||
// v2 usermod that provides a rotary encoder-based UI.
|
||||
//
|
||||
@@ -79,7 +79,7 @@ static int re_qstringCmp(const void *ap, const void *bp) {
|
||||
// Lowercase
|
||||
bVal -= 32;
|
||||
}
|
||||
// Relly we shouldn't ever get to '\0'
|
||||
// Really we shouldn't ever get to '\0'
|
||||
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
|
||||
// We're done. one is a substring of the other
|
||||
// or something happenend and the quote didn't stop us.
|
||||
@@ -436,7 +436,7 @@ void RotaryEncoderUIUsermod::setup()
|
||||
// tracking the owner tags....
|
||||
pinA = pinB = pinC = -1;
|
||||
enabled = false;
|
||||
DEBUG_PRINTLN(F("Failed to alocate GPIO pins for Usermod Rotary Encoder (ALT).")); //WLEDMM add debug info
|
||||
DEBUG_PRINTLN(F("Failed to allocate GPIO pins for Usermod Rotary Encoder (ALT).")); //WLEDMM add debug info
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ void RotaryEncoderUIUsermod::loop()
|
||||
bool changedState = false;
|
||||
char lineBuffer[64] = { '\0' };
|
||||
do {
|
||||
// finde new state
|
||||
// find new state
|
||||
switch (newState) {
|
||||
case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break;
|
||||
case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
// #define WEATHER_DEBUG
|
||||
|
||||
//declare weathermod global variables (always precede with weather_ (psuedo class static variables)
|
||||
//declare weathermod global variables (always precede with weather_ (pseudo class static variables)
|
||||
static uint32_t usermods_pushLoop = 0; //effect pushes loop to execute. might be interesting for audioreactive too
|
||||
static uint8_t weather_units = 1; //config var metric (celsius) is default. (Standard=Kelvin, Imperial is Fahrenheit)
|
||||
static float weather_minTemp = 0; //config var
|
||||
@@ -159,7 +159,7 @@ class WeatherUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// return if no location or no api key (reset lastTume to force loop)
|
||||
// return if no location or no api key (reset lastTime to force loop)
|
||||
if (fabs(latitude) < 0.00001 && fabs(latitude) < 0.00001) {strcpy(errorMessage, PSTR("No location")); lastTime = 0; return;}
|
||||
if (strcmp(apiKey.c_str(), "") == 0) {strcpy(errorMessage, PSTR("No api key")); lastTime = 0; return;}
|
||||
|
||||
@@ -181,12 +181,12 @@ class WeatherUsermod : public Usermod {
|
||||
if (strcmp(errorMessage, "") == 0) {
|
||||
|
||||
// https://arduinojson.org/v6/how-to/deserialize-a-very-large-document/
|
||||
StaticJsonDocument<256> filter; //in practive about 128
|
||||
StaticJsonDocument<256> filter; //in practice about 128
|
||||
filter["list"][0]["dt"] = true;
|
||||
filter["list"][0]["main"]["temp"] = true;
|
||||
filter["city"]["name"] = true;
|
||||
filter["city"]["country"] = true;
|
||||
PSRAMDynamicJsonDocument weatherDoc(4096); //in practive about 2673
|
||||
PSRAMDynamicJsonDocument weatherDoc(4096); //in practice about 2673
|
||||
|
||||
// Parse JSON object
|
||||
DeserializationError error = deserializeJson(weatherDoc, client, DeserializationOption::Filter(filter));
|
||||
@@ -206,7 +206,7 @@ class WeatherUsermod : public Usermod {
|
||||
JsonObject weatherDocObject = weatherDoc.as<JsonObject>();
|
||||
JsonArray list = weatherDocObject[F("list")];
|
||||
JsonObject city = weatherDocObject["city"];
|
||||
strcat(errorMessage, city["name"]); //api succesfull
|
||||
strcat(errorMessage, city["name"]); //api successful
|
||||
strcat(errorMessage, city["country"]);
|
||||
|
||||
uint8_t i = 0;
|
||||
@@ -319,10 +319,10 @@ class WeatherUsermod : public Usermod {
|
||||
|
||||
oappend(SET_F("dd=addDropdown('Weather','units');"));
|
||||
oappend(SET_F("addOption(dd,'Kelvin',0);"));
|
||||
oappend(SET_F("addOption(dd,'Celcius',1);"));
|
||||
oappend(SET_F("addOption(dd,'Celsius',1);"));
|
||||
oappend(SET_F("addOption(dd,'Fahrenheit',2);"));
|
||||
oappend(SET_F("addInfo('Weather:units',1,'<i>Set time and location in time settings</i>');"));
|
||||
oappend(SET_F("addInfo('Weather:apiKey',1,'<i>Create acount on openweathermap.org and copy the key</i>');"));
|
||||
oappend(SET_F("addInfo('Weather:apiKey',1,'<i>Create account on openweathermap.org and copy the key</i>');"));
|
||||
oappend(SET_F("addInfo('Weather:minTemp',1,'<i>Changing values: Reboot to (re)load forecast</i>');"));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ active: enable/disable usermod
|
||||
diplayItIs: enable/disable display of "Es ist" on the clock
|
||||
ledOffset: number of LEDs before the wordclock LEDs
|
||||
|
||||
### Update for alternatative wiring pattern
|
||||
### Update for alternative wiring pattern
|
||||
Based on this fantastic work I added an alternative wiring pattern.
|
||||
The original used a long wire to connect DO to DI, from one line to the next line.
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes.
|
||||
* The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
|
||||
* There are 2 parameters to chnage the behaviour:
|
||||
* The visualisation is described in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr").
|
||||
* There are 2 parameters to change the behaviour:
|
||||
*
|
||||
* active: enable/disable usermod
|
||||
* diplayItIs: enable/disable display of "Es ist" on the clock.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Controlling Wiz lights
|
||||
|
||||
Enabless controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller.
|
||||
Enables controlling [WiZ](https://www.wizconnected.com/en/consumer/) lights that are part of the same network as the WLED controller.
|
||||
|
||||
The mod takes the colors from the first few pixels and sends them to the lights.
|
||||
|
||||
@@ -8,7 +8,7 @@ The mod takes the colors from the first few pixels and sends them to the lights.
|
||||
|
||||
- Interval (ms)
|
||||
- How frequently to update the WiZ lights, in milliseconds.
|
||||
- Setting it too low may causse the ESP to become unresponsive.
|
||||
- Setting it too low may cause the ESP to become unresponsive.
|
||||
- Send Delay (ms)
|
||||
- An optional millisecond delay after updating each WiZ light.
|
||||
- Can help smooth out effects when using a large number of WiZ lights
|
||||
|
||||
Reference in New Issue
Block a user