Beta-3 changes

- remove I2C init  from usermods
- PCF8574 (&co) port expander support
- refactor PIR &  Rotary encoder & 4LD
- reboot race condition
- optimisations
This commit is contained in:
Blaz Kristan 2023-05-30 19:36:14 +02:00
parent 680afe972e
commit 82e448de7c
31 changed files with 5527 additions and 5268 deletions

View File

@ -8,7 +8,6 @@
#pragma once #pragma once
#include "wled.h" #include "wled.h"
#include <Wire.h>
#include <BH1750.h> #include <BH1750.h>
// the max frequency to check photoresistor, 10 seconds // the max frequency to check photoresistor, 10 seconds
@ -56,15 +55,6 @@ private:
static const char _offset[]; static const char _offset[];
static const char _HomeAssistantDiscovery[]; static const char _HomeAssistantDiscovery[];
// set the default pins based on the architecture, these get overridden by Usermod menu settings
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
#define HW_PIN_SCL 22
#define HW_PIN_SDA 21
#else // ESP8266 boards
#define HW_PIN_SCL 5
#define HW_PIN_SDA 4
#endif
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
bool initDone = false; bool initDone = false;
bool sensorFound = false; bool sensorFound = false;
@ -123,14 +113,8 @@ private:
public: public:
void setup() void setup()
{ {
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins
PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) return;
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) return;
Wire.begin(ioPin[1], ioPin[0]);
sensorFound = lightMeter.begin(); sensorFound = lightMeter.begin();
initDone = true; initDone = true;
} }
@ -216,9 +200,6 @@ public:
top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_minReadInterval)] = minReadingInterval;
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
top[FPSTR(_offset)] = offset; top[FPSTR(_offset)] = offset;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BH1750 config saved.")); DEBUG_PRINTLN(F("BH1750 config saved."));
} }
@ -226,8 +207,6 @@ public:
// called before setup() to populate properties from values stored in cfg.json // called before setup() to populate properties from values stored in cfg.json
bool readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
// we look for JSON object. // we look for JSON object.
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) if (top.isNull())
@ -244,27 +223,12 @@ public:
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false);
configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BH1750;
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
} }
return configComplete; return configComplete;

View File

@ -9,7 +9,6 @@
#include "wled.h" #include "wled.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h>
#include <BME280I2C.h> // BME280 sensor #include <BME280I2C.h> // BME280 sensor
#include <EnvironmentCalculations.h> // BME280 extended measurements #include <EnvironmentCalculations.h> // BME280 extended measurements
@ -34,7 +33,6 @@ private:
#ifdef ESP8266 #ifdef ESP8266
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
#endif #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; bool initDone = false;
// BME280 sensor settings // BME280 sensor settings
@ -186,13 +184,8 @@ private:
public: public:
void setup() void setup()
{ {
bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { sensorType=0; return; }
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
Wire.begin(ioPin[1], ioPin[0]);
if (!bme.begin()) if (!bme.begin())
{ {
@ -415,9 +408,6 @@ public:
top[F("PublishAlways")] = PublishAlways; top[F("PublishAlways")] = PublishAlways;
top[F("UseCelsius")] = UseCelsius; top[F("UseCelsius")] = UseCelsius;
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery; top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
JsonArray io_pin = top.createNestedArray(F("pin"));
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
DEBUG_PRINTLN(F("BME280 config saved.")); DEBUG_PRINTLN(F("BME280 config saved."));
} }
@ -427,8 +417,6 @@ public:
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
DEBUG_PRINT(F(_name)); DEBUG_PRINT(F(_name));
@ -447,27 +435,14 @@ public:
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
DEBUG_PRINTLN(F(" config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
PinOwner po = PinOwner::UM_BME280;
if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
setup();
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[F("pin")].isNull();
} }
return configComplete; return configComplete;

View File

@ -20,7 +20,7 @@
* This usermod handles PIR sensor states. * This usermod handles PIR sensor states.
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
* * Maintained by: @blazoncek
* *
* Usermods allow you to add own functionality to WLED more easily * Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
@ -38,21 +38,21 @@ public:
~PIRsensorSwitch() {} ~PIRsensorSwitch() {}
//Enable/Disable the PIR sensor //Enable/Disable the PIR sensor
void EnablePIRsensor(bool en) { enabled = en; } inline void EnablePIRsensor(bool en) { enabled = en; }
// Get PIR sensor enabled/disabled state // Get PIR sensor enabled/disabled state
bool PIRsensorEnabled() { return enabled; } inline bool PIRsensorEnabled() { return enabled; }
private: private:
byte prevPreset = 0; byte prevPreset = 0;
byte prevPlaylist = 0; byte prevPlaylist = 0;
uint32_t offTimerStart = 0; // off timer start time volatile unsigned long offTimerStart = 0; // off timer start time
volatile bool PIRtriggered = false; // did PIR trigger?
byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
byte sensorPinState = LOW; // current PIR sensor pin state byte sensorPinState = LOW; // current PIR sensor pin state
bool initDone = false; // status of initialization bool initDone = false; // status of initialization
bool PIRtriggered = false;
unsigned long lastLoop = 0; unsigned long lastLoop = 0;
// configurable parameters // configurable parameters
@ -66,6 +66,7 @@ private:
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR) // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
bool m_offOnly = false; bool m_offOnly = false;
bool m_offMode = offMode; bool m_offMode = offMode;
bool m_override = false;
// Home Assistant // Home Assistant
bool HomeAssistantDiscovery = false; // is HA discovery turned on bool HomeAssistantDiscovery = false; // is HA discovery turned on
@ -81,12 +82,122 @@ private:
static const char _offOnly[]; static const char _offOnly[];
static const char _haDiscovery[]; static const char _haDiscovery[];
static const char _notify[]; static const char _notify[];
static const char _override[];
/** /**
* check if it is daytime * check if it is daytime
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
*/ */
bool isDayTime() { static bool isDayTime();
/**
* switch strip on/off
*/
void switchStrip(bool switchOn);
void publishMqtt(const char* state);
// Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
void publishHomeAssistantAutodiscovery();
/**
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState();
/**
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer();
public:
//Functions called by WLED
/**
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup();
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
//void connected();
/**
* onMqttConnect() is called when MQTT connection is established
*/
void onMqttConnect(bool sessionPresent);
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop();
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root);
/**
* onStateChanged() is used to detect WLED state change
*/
void onStateChange(uint8_t mode);
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject &root);
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root);
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root);
/**
* provide UI information and allow extending UI options
*/
void appendConfigData();
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root);
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() { return USERMOD_ID_PIRSWITCH; }
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
const char PIRsensorSwitch::_override[] PROGMEM = "override";
bool PIRsensorSwitch::isDayTime() {
updateLocalTime(); updateLocalTime();
uint8_t hr = hour(localTime); uint8_t hr = hour(localTime);
uint8_t mi = minute(localTime); uint8_t mi = minute(localTime);
@ -106,10 +217,7 @@ private:
return false; return false;
} }
/** void PIRsensorSwitch::switchStrip(bool switchOn)
* switch strip on/off
*/
void switchStrip(bool switchOn)
{ {
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
@ -158,7 +266,7 @@ private:
} }
} }
void publishMqtt(const char* state) void PIRsensorSwitch::publishMqtt(const char* state)
{ {
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
//Check if MQTT Connected, otherwise it will crash the 8266 //Check if MQTT Connected, otherwise it will crash the 8266
@ -171,8 +279,7 @@ private:
#endif #endif
} }
// Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
void publishHomeAssistantAutodiscovery()
{ {
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
if (WLED_MQTT_CONNECTED) { if (WLED_MQTT_CONNECTED) {
@ -207,11 +314,7 @@ private:
#endif #endif
} }
/** bool PIRsensorSwitch::updatePIRsensorState()
* Read and update PIR sensor state.
* Initilize/reset switch off timer
*/
bool updatePIRsensorState()
{ {
bool pinState = digitalRead(PIRsensorPin); bool pinState = digitalRead(PIRsensorPin);
if (pinState != sensorPinState) { if (pinState != sensorPinState) {
@ -232,10 +335,7 @@ private:
return false; return false;
} }
/** bool PIRsensorSwitch::handleOffTimer()
* switch off the strip if the delay has elapsed
*/
bool handleOffTimer()
{ {
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
offTimerStart = 0; offTimerStart = 0;
@ -249,14 +349,9 @@ private:
return false; return false;
} }
public:
//Functions called by WLED //Functions called by WLED
/** void PIRsensorSwitch::setup()
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup()
{ {
if (enabled) { if (enabled) {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
@ -275,27 +370,14 @@ public:
initDone = true; initDone = true;
} }
/** void PIRsensorSwitch::onMqttConnect(bool sessionPresent)
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{ {
}
/**
* onMqttConnect() is called when MQTT connection is established
*/
void onMqttConnect(bool sessionPresent) {
if (HomeAssistantDiscovery) { if (HomeAssistantDiscovery) {
publishHomeAssistantAutodiscovery(); publishHomeAssistantAutodiscovery();
} }
} }
/** void PIRsensorSwitch::loop()
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{ {
// only check sensors 4x/s // only check sensors 4x/s
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
@ -306,12 +388,7 @@ public:
} }
} }
/** void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*
* Add PIR sensor state and switch off timer duration to jsoninfo
*/
void addToJsonInfo(JsonObject &root)
{ {
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u"); if (user.isNull()) user = root.createNestedObject("u");
@ -371,13 +448,10 @@ public:
sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false;
} }
/** void PIRsensorSwitch::onStateChange(uint8_t mode) {
* onStateChanged() is used to detect WLED state change
*/
void onStateChange(uint8_t mode) {
if (!initDone) return; if (!initDone) return;
DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart);
if (PIRtriggered && offTimerStart) { if (m_override && PIRtriggered && offTimerStart) { // debounce
// checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger
DEBUG_PRINTLN(F("PIR: Canceled.")); DEBUG_PRINTLN(F("PIR: Canceled."));
offTimerStart = 0; offTimerStart = 0;
@ -385,22 +459,7 @@ public:
} }
} }
/** void PIRsensorSwitch::readFromJsonState(JsonObject &root)
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
/*
void addToJsonState(JsonObject &root)
{
}
*/
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject &root)
{ {
if (!initDone) return; // prevent crash on boot applyPreset() if (!initDone) return; // prevent crash on boot applyPreset()
JsonObject usermod = root[FPSTR(_name)]; JsonObject usermod = root[FPSTR(_name)];
@ -411,11 +470,7 @@ public:
} }
} }
void PIRsensorSwitch::addToConfig(JsonObject &root)
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root)
{ {
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
@ -426,24 +481,20 @@ public:
top[FPSTR(_nightTime)] = m_nightTimeOnly; top[FPSTR(_nightTime)] = m_nightTimeOnly;
top[FPSTR(_mqttOnly)] = m_mqttOnly; top[FPSTR(_mqttOnly)] = m_mqttOnly;
top[FPSTR(_offOnly)] = m_offOnly; top[FPSTR(_offOnly)] = m_offOnly;
top[FPSTR(_override)] = m_override;
top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
DEBUG_PRINTLN(F("PIR config saved.")); DEBUG_PRINTLN(F("PIR config saved."));
} }
void appendConfigData() void PIRsensorSwitch::appendConfigData()
{ {
oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field
oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field
} }
/** bool PIRsensorSwitch::readFromConfig(JsonObject &root)
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root)
{ {
bool oldEnabled = enabled; bool oldEnabled = enabled;
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
@ -469,6 +520,7 @@ public:
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
m_override = top[FPSTR(_override)] | m_override;
HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
@ -498,27 +550,5 @@ public:
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_haDiscovery)].isNull(); return !top[FPSTR(_override)].isNull();
} }
/**
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_PIRSWITCH;
}
};
// strings to reduce flash memory usage (used more than twice)
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";

View File

@ -17,12 +17,6 @@
#ifndef TFT_HEIGHT #ifndef TFT_HEIGHT
#error Please define TFT_HEIGHT #error Please define TFT_HEIGHT
#endif #endif
#ifndef TFT_MOSI
#error Please define TFT_MOSI
#endif
#ifndef TFT_SCLK
#error Please define TFT_SCLK
#endif
#ifndef TFT_DC #ifndef TFT_DC
#error Please define TFT_DC #error Please define TFT_DC
#endif #endif
@ -140,8 +134,14 @@ class St7789DisplayUsermod : public Usermod {
*/ */
void setup() void setup()
{ {
PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } };
if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; }
PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } };
if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) {
pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI);
enabled = false;
return;
}
tft.init(); tft.init();
tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip.
@ -365,9 +365,6 @@ class St7789DisplayUsermod : public Usermod {
{ {
JsonObject top = root.createNestedObject("ST7789"); JsonObject top = root.createNestedObject("ST7789");
JsonArray pins = top.createNestedArray("pin"); JsonArray pins = top.createNestedArray("pin");
pins.add(TFT_MOSI);
pins.add(TFT_MISO);
pins.add(TFT_SCLK);
pins.add(TFT_CS); pins.add(TFT_CS);
pins.add(TFT_DC); pins.add(TFT_DC);
pins.add(TFT_RST); pins.add(TFT_RST);
@ -376,6 +373,13 @@ class St7789DisplayUsermod : public Usermod {
} }
void appendConfigData() {
oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');"));
oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');"));
oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');"));
oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');"));
}
/* /*
* readFromConfig() can be used to read back the custom settings you added with addToConfig(). * readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot) * This is called by WLED when settings are loaded (currently this only happens once immediately after boot)

View File

@ -13,14 +13,6 @@
Adafruit_Si7021 si7021; Adafruit_Si7021 si7021;
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else //ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#endif
class Si7021_MQTT_HA : public Usermod class Si7021_MQTT_HA : public Usermod
{ {
private: private:
@ -184,7 +176,6 @@ class Si7021_MQTT_HA : public Usermod
{ {
if (enabled) { if (enabled) {
Serial.println("Si7021_MQTT_HA: Starting!"); Serial.println("Si7021_MQTT_HA: Starting!");
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Si7021_MQTT_HA: Initializing sensors.. "); Serial.println("Si7021_MQTT_HA: Initializing sensors.. ");
_initializeSensor(); _initializeSensor();
} }

View File

@ -52,7 +52,6 @@ class UsermodVL53L0XGestures : public Usermod {
void setup() { void setup() {
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
Wire.begin();
sensor.setTimeout(150); sensor.setTimeout(150);
if (!sensor.init()) if (!sensor.init())

View File

@ -569,16 +569,6 @@ class AudioReactive : public Usermod {
#else #else
int8_t i2sckPin = I2S_CKPIN; int8_t i2sckPin = I2S_CKPIN;
#endif #endif
#ifndef ES7243_SDAPIN
int8_t sdaPin = -1;
#else
int8_t sdaPin = ES7243_SDAPIN;
#endif
#ifndef ES7243_SCLPIN
int8_t sclPin = -1;
#else
int8_t sclPin = ES7243_SCLPIN;
#endif
#ifndef MCLK_PIN #ifndef MCLK_PIN
int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/
#else #else
@ -1136,7 +1126,7 @@ class AudioReactive : public Usermod {
DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only)."));
audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
break; break;
case 3: case 3:
DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
@ -1657,8 +1647,6 @@ class AudioReactive : public Usermod {
pinArray.add(i2swsPin); pinArray.add(i2swsPin);
pinArray.add(i2sckPin); pinArray.add(i2sckPin);
pinArray.add(mclkPin); pinArray.add(mclkPin);
pinArray.add(sdaPin);
pinArray.add(sclPin);
JsonObject cfg = top.createNestedObject("config"); JsonObject cfg = top.createNestedObject("config");
cfg[F("squelch")] = soundSquelch; cfg[F("squelch")] = soundSquelch;
@ -1719,8 +1707,6 @@ class AudioReactive : public Usermod {
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][4], sdaPin);
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][5], sclPin);
configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch); configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch);
configComplete &= getJsonValue(top["config"][F("gain")], sampleGain); configComplete &= getJsonValue(top["config"][F("gain")], sampleGain);
@ -1784,8 +1770,6 @@ class AudioReactive : public Usermod {
#else #else
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'<i>master clock</i>','I2S MCLK');")); oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'<i>master clock</i>','I2S MCLK');"));
#endif #endif
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'','I2C SDA');"));
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'','I2C SCL');"));
} }

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <Wire.h>
#include "wled.h" #include "wled.h"
#include <driver/i2s.h> #include <driver/i2s.h>
#include <driver/adc.h> #include <driver/adc.h>
@ -383,21 +382,12 @@ class I2SSource : public AudioSource {
*/ */
class ES7243 : public I2SSource { class ES7243 : public I2SSource {
private: private:
// I2C initialization functions for ES7243
void _es7243I2cBegin() {
bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
if (i2c_initialized == false) {
DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver."));
}
}
void _es7243I2cWrite(uint8_t reg, uint8_t val) { void _es7243I2cWrite(uint8_t reg, uint8_t val) {
#ifndef ES7243_ADDR #ifndef ES7243_ADDR
Wire.beginTransmission(0x13);
#define ES7243_ADDR 0x13 // default address #define ES7243_ADDR 0x13 // default address
#else
Wire.beginTransmission(ES7243_ADDR);
#endif #endif
Wire.beginTransmission(ES7243_ADDR);
Wire.write((uint8_t)reg); Wire.write((uint8_t)reg);
Wire.write((uint8_t)val); Wire.write((uint8_t)val);
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
@ -407,7 +397,6 @@ class ES7243 : public I2SSource {
} }
void _es7243InitAdc() { void _es7243InitAdc() {
_es7243I2cBegin();
_es7243I2cWrite(0x00, 0x01); _es7243I2cWrite(0x00, 0x01);
_es7243I2cWrite(0x06, 0x00); _es7243I2cWrite(0x06, 0x00);
_es7243I2cWrite(0x05, 0x1B); _es7243I2cWrite(0x05, 0x1B);
@ -422,44 +411,20 @@ public:
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
}; };
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
// check that pins are valid
if ((sdaPin < 0) || (sclPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
return;
}
if ((i2sckPin < 0) || (mclkPin < 0)) { if ((i2sckPin < 0) || (mclkPin < 0)) {
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
return; return;
} }
// Reserve SDA and SCL pins of the I2C interface
PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } };
if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) {
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
return;
}
pin_ES7243_SDA = sdaPin;
pin_ES7243_SCL = sclPin;
// First route mclk, then configure ADC over I2C, then configure I2S // First route mclk, then configure ADC over I2C, then configure I2S
_es7243InitAdc(); _es7243InitAdc();
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
} }
void deinitialize() { void deinitialize() {
// Release SDA and SCL pins of the I2C interface
PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } };
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
I2SSource::deinitialize(); I2SSource::deinitialize();
} }
private:
int8_t pin_ES7243_SDA;
int8_t pin_ES7243_SCL;
}; };

View File

@ -87,10 +87,8 @@ class MPU6050Driver : public Usermod {
void setup() { void setup() {
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin(); Wire.setClock(400000U); // 400kHz I2C clock. Comment this line if having compilation difficulties
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400, true); Fastwire::setup(400, true);
#endif #endif

View File

@ -20,6 +20,14 @@
#define ON true #define ON true
#define OFF false #define OFF false
#ifndef USERMOD_USE_PCF8574
#undef USE_PCF8574
#define USE_PCF8574 false
#else
#undef USE_PCF8574
#define USE_PCF8574 true
#endif
#ifndef PCF8574_ADDRESS #ifndef PCF8574_ADDRESS
#define PCF8574_ADDRESS 0x20 // some may start at 0x38 #define PCF8574_ADDRESS 0x20 // some may start at 0x38
#endif #endif
@ -52,21 +60,15 @@ class MultiRelay : public Usermod {
// array of relays // array of relays
Relay _relay[MULTI_RELAY_MAX_RELAYS]; Relay _relay[MULTI_RELAY_MAX_RELAYS];
// switch timer start time uint32_t _switchTimerStart; // switch timer start time
uint32_t _switchTimerStart = 0; bool _oldMode; // old brightness
// old brightness bool enabled; // usermod enabled
bool _oldMode; bool initDone; // status of initialisation
bool usePcf8574;
// usermod enabled uint8_t addrPcf8574;
bool enabled = false; // needs to be configured (no default config) bool HAautodiscovery;
// status of initialisation uint16_t periodicBroadcastSec;
bool initDone = false; unsigned long lastBroadcast;
bool usePcf8574 = false;
uint8_t addrPcf8574 = PCF8574_ADDRESS;
bool HAautodiscovery = false;
uint16_t periodicBroadcastSec = 60;
unsigned long lastBroadcast = 0;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
@ -103,7 +105,7 @@ class MultiRelay : public Usermod {
/** /**
* desctructor * desctructor
*/ */
~MultiRelay() {} //~MultiRelay() {}
/** /**
* Enable/Disable the usermod * Enable/Disable the usermod
@ -331,7 +333,16 @@ byte MultiRelay::IOexpanderRead(int address) {
// public methods // public methods
MultiRelay::MultiRelay() { MultiRelay::MultiRelay()
: _switchTimerStart(0)
, enabled(false)
, initDone(false)
, usePcf8574(USE_PCF8574)
, addrPcf8574(PCF8574_ADDRESS)
, HAautodiscovery(false)
, periodicBroadcastSec(60)
, lastBroadcast(0)
{
const int8_t defPins[] = {MULTI_RELAY_PINS}; const int8_t defPins[] = {MULTI_RELAY_PINS};
for (size_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (size_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1; _relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;

View File

@ -6,7 +6,6 @@
#include "wled.h" #include "wled.h"
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h> #include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h> #include <Adafruit_BMP280.h>
#include <Adafruit_CCS811.h> #include <Adafruit_CCS811.h>
@ -16,14 +15,6 @@ Adafruit_BMP280 bmp;
Adafruit_Si7021 si7021; Adafruit_Si7021 si7021;
Adafruit_CCS811 ccs811; Adafruit_CCS811 ccs811;
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
uint8_t SCL_PIN = 22;
uint8_t SDA_PIN = 21;
#else //ESP8266 boards
uint8_t SCL_PIN = 5;
uint8_t SDA_PIN = 4;
#endif
class UserMod_SensorsToMQTT : public Usermod class UserMod_SensorsToMQTT : public Usermod
{ {
private: private:
@ -231,7 +222,6 @@ public:
void setup() void setup()
{ {
Serial.println("Starting!"); Serial.println("Starting!");
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Initializing sensors.. "); Serial.println("Initializing sensors.. ");
_initialize(); _initialize();
} }

View File

@ -12,9 +12,8 @@
// for WLED. // for WLED.
// //
// Dependencies // Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best, by far, when coupled // * This Usermod works best, by far, when coupled
// with RotaryEncoderUIUsermod. // with RotaryEncoderUI ALT Usermod.
// //
// Make sure to enable NTP and set your time zone in WLED Config | Time. // Make sure to enable NTP and set your time zone in WLED Config | Time.
// //
@ -23,22 +22,14 @@
// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) // REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
// REQUIREMENT: * Wire // REQUIREMENT: * Wire
// //
// 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
//The SCL and SDA pins are defined here.
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL i2c_scl
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA i2c_sda
#endif
#ifndef FLD_PIN_CLOCKSPI
#define FLD_PIN_CLOCKSPI spi_sclk
#endif
#ifndef FLD_PIN_DATASPI
#define FLD_PIN_DATASPI spi_mosi
#endif
#ifndef FLD_PIN_CS #ifndef FLD_PIN_CS
#define FLD_PIN_CS spi_cs #define FLD_PIN_CS 15
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@ -112,10 +103,10 @@ class FourLineDisplayUsermod : public Usermod {
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
#ifndef FLD_SPI_DEFAULT #ifndef FLD_SPI_DEFAULT
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA int8_t ioPin[3] = {-1, -1, -1}; // I2C pins: SCL, SDA
uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000)
#else #else
int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST int8_t ioPin[3] = {FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // custom SPI pins: CS, DC, RST
uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz)
#endif #endif
@ -178,7 +169,193 @@ class FourLineDisplayUsermod : public Usermod {
// https://github.com/olikraus/u8g2/wiki/gallery // https://github.com/olikraus/u8g2/wiki/gallery
// some displays need this to properly apply contrast // some displays need this to properly apply contrast
void setVcomh(bool highContrast) { void setVcomh(bool highContrast);
void startDisplay();
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode);
void setContrast(uint8_t contrast);
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false);
void draw2x2String(uint8_t col, uint8_t row, const char *string);
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false);
void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font);
void draw2x2GlyphIcons();
uint8_t getCols();
void clear();
void setPowerSave(uint8_t save);
void center(String &line, uint8_t width);
/**
* Display the current date and time in large characters
* on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration.
*/
void showTime();
/**
* Enable sleep (turn the display off) or clock mode.
*/
void sleepOrClock(bool enabled);
public:
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup();
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void connected();
/**
* Da loop.
*/
void loop();
//function to update lastredraw
void updateRedrawTime() {
lastRedraw = millis();
}
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw);
void updateBrightness();
void updateSpeed();
void updateIntensity();
void drawStatusIcons();
/**
* marks the position of the arrow showing
* the current setting being changed
* pass line and colum info
*/
void setMarkLine(byte newMarkLineNum, byte newMarkColNum);
//Draw the arrow for the current setting beiong changed
void drawArrow();
//Display the current effect or palette (desiredEntry)
// on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row);
/**
* If there screen is off or in clock is displayed,
* this will return true. This allows us to throw away
* the first input from the rotary encoder but
* to wake up the screen.
*/
bool wakeDisplay();
/**
* Allows you to show one line and a glyph as overlay for a period of time.
* Clears the screen and prints.
* Used in Rotary Encoder usermod.
*/
void overlay(const char* line1, long showHowLong, byte glyphType);
/**
* Allows you to show Akemi WLED logo overlay for a period of time.
* Clears the screen and prints.
*/
void overlayLogo(long showHowLong);
/**
* Allows you to show two lines as overlay for a period of time.
* Clears the screen and prints.
* Used in Auto Save usermod
*/
void overlay(const char* line1, const char* line2, long showHowLong);
void networkOverlay(const char* line1, long showHowLong);
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
bool handleButton(uint8_t b);
void onUpdateBegin(bool init);
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
//void addToJsonInfo(JsonObject& root);
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject& root);
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void readFromJsonState(JsonObject& root);
void appendConfigData();
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop().
*
* CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks!
*
* addToConfig() will also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root);
/*
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
*
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/
bool readFromConfig(JsonObject& root);
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP;
}
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate-ms";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";
const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix";
FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr;
// some displays need this to properly apply contrast
void FourLineDisplayUsermod::setVcomh(bool highContrast) {
u8x8_t *u8x8_struct = u8x8->getU8x8(); u8x8_t *u8x8_struct = u8x8->getU8x8();
u8x8_cad_StartTransfer(u8x8_struct); u8x8_cad_StartTransfer(u8x8_struct);
u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value
@ -186,18 +363,31 @@ class FourLineDisplayUsermod : public Usermod {
u8x8_cad_EndTransfer(u8x8_struct); u8x8_cad_EndTransfer(u8x8_struct);
} }
void FourLineDisplayUsermod::startDisplay() {
lineHeight = u8x8->getRows() > 4 ? 2 : 1;
DEBUG_PRINTLN(F("Starting display."));
u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setVcomh(contrastFix);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
//drawString(0, 0, "Loading...");
overlayLogo(3500);
}
/** /**
* Wrappers for screen drawing * Wrappers for screen drawing
*/ */
void setFlipMode(uint8_t mode) { void FourLineDisplayUsermod::setFlipMode(uint8_t mode) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setFlipMode(mode); u8x8->setFlipMode(mode);
} }
void setContrast(uint8_t contrast) { void FourLineDisplayUsermod::setContrast(uint8_t contrast) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setContrast(contrast); u8x8->setContrast(contrast);
} }
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { void FourLineDisplayUsermod::drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
@ -205,14 +395,14 @@ class FourLineDisplayUsermod : public Usermod {
else u8x8->drawString(col, row, string); else u8x8->drawString(col, row, string);
drawing = false; drawing = false;
} }
void draw2x2String(uint8_t col, uint8_t row, const char *string) { void FourLineDisplayUsermod::draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(u8x8_font_chroma48medium8_r); u8x8->setFont(u8x8_font_chroma48medium8_r);
u8x8->draw2x2String(col, row, string); u8x8->draw2x2String(col, row, string);
drawing = false; drawing = false;
} }
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { void FourLineDisplayUsermod::drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(font); u8x8->setFont(font);
@ -220,35 +410,35 @@ class FourLineDisplayUsermod : public Usermod {
else u8x8->drawGlyph(col, row, glyph); else u8x8->drawGlyph(col, row, glyph);
drawing = false; drawing = false;
} }
void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { void FourLineDisplayUsermod::draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->setFont(font); u8x8->setFont(font);
u8x8->draw2x2Glyph(col, row, glyph); u8x8->draw2x2Glyph(col, row, glyph);
drawing = false; drawing = false;
} }
uint8_t getCols() { uint8_t FourLineDisplayUsermod::getCols() {
if (type==NONE || !enabled) return 0; if (type==NONE || !enabled) return 0;
return u8x8->getCols(); return u8x8->getCols();
} }
void clear() { void FourLineDisplayUsermod::clear() {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
drawing = true; drawing = true;
u8x8->clear(); u8x8->clear();
drawing = false; drawing = false;
} }
void setPowerSave(uint8_t save) { void FourLineDisplayUsermod::setPowerSave(uint8_t save) {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
u8x8->setPowerSave(save); u8x8->setPowerSave(save);
} }
void center(String &line, uint8_t width) { void FourLineDisplayUsermod::center(String &line, uint8_t width) {
int len = line.length(); int len = line.length();
if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line; if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line;
for (byte i=line.length(); i<width; i++) line += ' '; for (byte i=line.length(); i<width; i++) line += ' ';
} }
void draw2x2GlyphIcons() { void FourLineDisplayUsermod::draw2x2GlyphIcons() {
drawing = true; drawing = true;
if (lineHeight == 2) { if (lineHeight == 2) {
drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
@ -271,7 +461,7 @@ class FourLineDisplayUsermod : public Usermod {
* on the middle rows. Based 24 or 12 hour depending on * on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration. * the useAMPM configuration.
*/ */
void showTime() { void FourLineDisplayUsermod::showTime() {
if (type == NONE || !enabled || !displayTurnedOff) return; if (type == NONE || !enabled || !displayTurnedOff) return;
unsigned long now = millis(); unsigned long now = millis();
@ -317,7 +507,7 @@ class FourLineDisplayUsermod : public Usermod {
/** /**
* Enable sleep (turn the display off) or clock mode. * Enable sleep (turn the display off) or clock mode.
*/ */
void sleepOrClock(bool enabled) { void FourLineDisplayUsermod::sleepOrClock(bool enabled) {
if (enabled) { if (enabled) {
displayTurnedOff = true; displayTurnedOff = true;
if (clockMode && ntpEnabled) { if (clockMode && ntpEnabled) {
@ -331,143 +521,66 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
public:
// gets called once at boot. Do all initialization that doesn't depend on // gets called once at boot. Do all initialization that doesn't depend on
// network here // network here
void setup() { void FourLineDisplayUsermod::setup() {
if (type == NONE || !enabled) return; if (type == NONE || !enabled) return;
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
PinOwner po = PinOwner::UM_FourLineDisplay;
// check if pins are -1 and disable usermod as PinManager::allocateMultiplePins() will accept -1 as a valid pin
if (isSPI) { if (isSPI) {
uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { type=NONE; return; }
if (ioPin[0] < 0 || ioPin[1] < 0) {
ioPin[0] = hw_sclk;
ioPin[1] = hw_mosi;
}
isHW = (ioPin[0]==hw_sclk && ioPin[1]==hw_mosi);
PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } };
if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; }
if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } };
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) {
if (!pinManager.allocateMultiplePins(pins, 2, po)) {
pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay);
type = NONE; type = NONE;
return; return;
} }
} else { } else {
uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } };
uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { type=NONE; return; }
if (ioPin[0] < 0 || ioPin[1] < 0) {
ioPin[0] = hw_scl;
ioPin[1] = hw_sda;
}
isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda);
if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } };
if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; }
} }
DEBUG_PRINTLN(F("Allocating display.")); DEBUG_PRINTLN(F("Allocating display."));
/*
// At some point it may be good to not new/delete U8X8 object but use this instead
// (does not currently work)
//-------------------------------------------------------------------------------
switch (type) { switch (type) {
case SSD1306: // U8X8 uses Wire (or Wire1 with 2ND constructor) and will use existing Wire properties (calls Wire.begin() though)
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1306: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(); break;
break; case SH1106: u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(); break;
case SH1106: case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break;
u8x8_Setup(u8x8.getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break;
break; case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break;
case SSD1306_64: // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset
break; case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset
case SSD1305: // catchall
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); default: u8x8 = (U8X8 *) new U8X8_NULL(); break;
break;
case SSD1305_64:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
break;
case SSD1306_SPI:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino);
break;
case SSD1306_SPI64:
u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino);
break;
default:
type = NONE;
return;
}
if (isSPI) {
if (!isHW) u8x8_SetPin_4Wire_SW_SPI(u8x8.getU8x8(), ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8_SetPin_4Wire_HW_SPI(u8x8.getU8x8(), ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
} else {
if (!isHW) u8x8_SetPin_SW_I2C(u8x8.getU8x8(), ioPin[0], ioPin[1], U8X8_PIN_NONE); // SCL, SDA, reset
else u8x8_SetPin_HW_I2C(u8x8.getU8x8(), U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
}
*/
switch (type) {
case SSD1306:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SH1106:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1306_64:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1305:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1305_64:
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
break;
case SSD1306_SPI:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
case SSD1306_SPI64:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
default:
u8x8 = nullptr;
} }
if (nullptr == u8x8) { if (nullptr == u8x8) {
DEBUG_PRINTLN(F("Display init failed.")); DEBUG_PRINTLN(F("Display init failed."));
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po); if (isSPI) {
int8_t pins[] = {spi_sclk, spi_mosi};
pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_SPI);
pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay);
} else {
int8_t pins[] = {i2c_scl, i2c_sda};
pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_I2C);
}
type = NONE; type = NONE;
return; return;
} }
lineHeight = u8x8->getRows() > 4 ? 2 : 1; startDisplay();
DEBUG_PRINTLN(F("Starting display."));
u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
setFlipMode(flip);
setVcomh(contrastFix);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
//drawString(0, 0, "Loading...");
overlayLogo(3500);
onUpdateBegin(false); // create Display task onUpdateBegin(false); // create Display task
initDone = true; initDone = true;
} }
// gets called every time WiFi is (re-)connected. Initialize own network // gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here // interfaces here
void connected() { void FourLineDisplayUsermod::connected() {
knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
networkOverlay(PSTR("NETWORK INFO"),7000); networkOverlay(PSTR("NETWORK INFO"),7000);
@ -476,7 +589,7 @@ class FourLineDisplayUsermod : public Usermod {
/** /**
* Da loop. * Da loop.
*/ */
void loop() { void FourLineDisplayUsermod::loop() {
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
if (!enabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long now = millis(); unsigned long now = millis();
@ -486,16 +599,11 @@ class FourLineDisplayUsermod : public Usermod {
#endif #endif
} }
//function to update lastredraw
void updateRedrawTime() {
lastRedraw = millis();
}
/** /**
* Redraw the screen (but only if things have changed * Redraw the screen (but only if things have changed
* or if forceRedraw). * or if forceRedraw).
*/ */
void redraw(bool forceRedraw) { void FourLineDisplayUsermod::redraw(bool forceRedraw) {
bool needRedraw = false; bool needRedraw = false;
unsigned long now = millis(); unsigned long now = millis();
@ -607,7 +715,7 @@ class FourLineDisplayUsermod : public Usermod {
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info
} }
void updateBrightness() { void FourLineDisplayUsermod::updateBrightness() {
knownBrightness = bri; knownBrightness = bri;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -619,7 +727,7 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void updateSpeed() { void FourLineDisplayUsermod::updateSpeed() {
knownEffectSpeed = effectSpeed; knownEffectSpeed = effectSpeed;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -631,7 +739,7 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void updateIntensity() { void FourLineDisplayUsermod::updateIntensity() {
knownEffectIntensity = effectIntensity; knownEffectIntensity = effectIntensity;
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -643,7 +751,7 @@ class FourLineDisplayUsermod : public Usermod {
} }
} }
void drawStatusIcons() { void FourLineDisplayUsermod::drawStatusIcons() {
uint8_t col = 15; uint8_t col = 15;
uint8_t row = 0; uint8_t row = 0;
lockRedraw = true; lockRedraw = true;
@ -660,13 +768,13 @@ class FourLineDisplayUsermod : public Usermod {
* the current setting being changed * the current setting being changed
* pass line and colum info * pass line and colum info
*/ */
void setMarkLine(byte newMarkLineNum, byte newMarkColNum) { void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum) {
markLineNum = newMarkLineNum; markLineNum = newMarkLineNum;
markColNum = newMarkColNum; markColNum = newMarkColNum;
} }
//Draw the arrow for the current setting beiong changed //Draw the arrow for the current setting beiong changed
void drawArrow() { void FourLineDisplayUsermod::drawArrow() {
lockRedraw = true; lockRedraw = true;
if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1);
lockRedraw = false; lockRedraw = false;
@ -674,7 +782,7 @@ class FourLineDisplayUsermod : public Usermod {
//Display the current effect or palette (desiredEntry) //Display the current effect or palette (desiredEntry)
// on the appropriate line (row). // on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
char lineBuffer[MAX_JSON_CHARS]; char lineBuffer[MAX_JSON_CHARS];
if (overlayUntil == 0) { if (overlayUntil == 0) {
lockRedraw = true; lockRedraw = true;
@ -740,7 +848,7 @@ class FourLineDisplayUsermod : public Usermod {
* the first input from the rotary encoder but * the first input from the rotary encoder but
* to wake up the screen. * to wake up the screen.
*/ */
bool wakeDisplay() { bool FourLineDisplayUsermod::wakeDisplay() {
if (type == NONE || !enabled) return false; if (type == NONE || !enabled) return false;
if (displayTurnedOff) { if (displayTurnedOff) {
unsigned long now = millis(); unsigned long now = millis();
@ -761,7 +869,7 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints. * Clears the screen and prints.
* Used in Rotary Encoder usermod. * Used in Rotary Encoder usermod.
*/ */
void overlay(const char* line1, long showHowLong, byte glyphType) { void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) {
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing) return;
@ -786,7 +894,7 @@ class FourLineDisplayUsermod : public Usermod {
* Allows you to show Akemi WLED logo overlay for a period of time. * Allows you to show Akemi WLED logo overlay for a period of time.
* Clears the screen and prints. * Clears the screen and prints.
*/ */
void overlayLogo(long showHowLong) { void FourLineDisplayUsermod::overlayLogo(long showHowLong) {
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing) return;
@ -848,7 +956,7 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints. * Clears the screen and prints.
* Used in Auto Save usermod * Used in Auto Save usermod
*/ */
void overlay(const char* line1, const char* line2, long showHowLong) { void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long showHowLong) {
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing) return;
@ -870,7 +978,7 @@ class FourLineDisplayUsermod : public Usermod {
lockRedraw = false; lockRedraw = false;
} }
void networkOverlay(const char* line1, long showHowLong) { void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) {
unsigned long now = millis(); unsigned long now = millis();
while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing
if (drawing) return; if (drawing) return;
@ -915,7 +1023,7 @@ class FourLineDisplayUsermod : public Usermod {
* will prevent button working in a default way. * will prevent button working in a default way.
* Replicating button.cpp * Replicating button.cpp
*/ */
bool handleButton(uint8_t b) { bool FourLineDisplayUsermod::handleButton(uint8_t b) {
yield(); yield();
if (!enabled if (!enabled
|| b // butto 0 only || b // butto 0 only
@ -989,7 +1097,7 @@ class FourLineDisplayUsermod : public Usermod {
#else #else
#define ARDUINO_RUNNING_CORE 1 #define ARDUINO_RUNNING_CORE 1
#endif #endif
void onUpdateBegin(bool init) { void FourLineDisplayUsermod::onUpdateBegin(bool init) {
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (init && Display_Task) { if (init && Display_Task) {
vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash
@ -1026,7 +1134,7 @@ class FourLineDisplayUsermod : public Usermod {
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor * Below it is shown how this could be used for e.g. a light sensor
*/ */
//void addToJsonInfo(JsonObject& root) { //void FourLineDisplayUsermod::addToJsonInfo(JsonObject& root) {
//JsonObject user = root["u"]; //JsonObject user = root["u"];
//if (user.isNull()) user = root.createNestedObject("u"); //if (user.isNull()) user = root.createNestedObject("u");
//JsonArray data = user.createNestedArray(F("4LineDisplay")); //JsonArray data = user.createNestedArray(F("4LineDisplay"));
@ -1037,18 +1145,18 @@ class FourLineDisplayUsermod : public Usermod {
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void addToJsonState(JsonObject& root) { //void FourLineDisplayUsermod::addToJsonState(JsonObject& root) {
//} //}
/* /*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
//void readFromJsonState(JsonObject& root) { //void FourLineDisplayUsermod::readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset() // if (!initDone) return; // prevent crash on boot applyPreset()
//} //}
void appendConfigData() { void FourLineDisplayUsermod::appendConfigData() {
oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); oappend(SET_F("dd=addDropdown('4LineDisplay','type');"));
oappend(SET_F("addOption(dd,'None',0);")); oappend(SET_F("addOption(dd,'None',0);"));
oappend(SET_F("addOption(dd,'SSD1306',1);")); oappend(SET_F("addOption(dd,'SSD1306',1);"));
@ -1058,11 +1166,10 @@ class FourLineDisplayUsermod : public Usermod {
oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); oappend(SET_F("addOption(dd,'SSD1305 128x64',5);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); oappend(SET_F("addOption(dd,'SSD1306 SPI',6);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'<i>-1 use global</i>','I2C/SPI CLK');")); oappend(SET_F("addInfo('4LineDisplay:type',1,'<br><i class=\"warn\">Change may require reboot</i>','');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'<i>-1 use global</i>','I2C/SPI DTA');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI CS');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',3,'','SPI DC');")); oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');"));
oappend(SET_F("addInfo('4LineDisplay:pin[]',4,'','SPI RST');"));
} }
/* /*
@ -1079,27 +1186,13 @@ class FourLineDisplayUsermod : public Usermod {
* *
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/ */
void addToConfig(JsonObject& root) { void FourLineDisplayUsermod::addToConfig(JsonObject& root) {
// determine if we are using global HW pins (data & clock)
int8_t hw_dta, hw_clk;
if ((type == SSD1306_SPI || type == SSD1306_SPI64)) {
hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk;
hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi;
} else {
hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl;
hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda;
}
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
JsonArray io_pin = top.createNestedArray("pin");
for (int i=0; i<5; i++) {
if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin
else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin
else io_pin.add(ioPin[i]);
}
top["type"] = type; top["type"] = type;
JsonArray io_pin = top.createNestedArray("pin");
for (int i=0; i<3; i++) io_pin.add(ioPin[i]);
top[FPSTR(_flip)] = (bool) flip; top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast; top[FPSTR(_contrast)] = contrast;
top[FPSTR(_contrastFix)] = (bool) contrastFix; top[FPSTR(_contrastFix)] = (bool) contrastFix;
@ -1122,10 +1215,10 @@ class FourLineDisplayUsermod : public Usermod {
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :) * If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/ */
bool readFromConfig(JsonObject& root) { bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) {
bool needsRedraw = false; bool needsRedraw = false;
DisplayType newType = type; DisplayType newType = type;
int8_t oldPin[5]; for (byte i=0; i<5; i++) oldPin[i] = ioPin[i]; int8_t oldPin[3]; for (byte i=0; i<3; i++) oldPin[i] = ioPin[i];
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
@ -1136,7 +1229,7 @@ class FourLineDisplayUsermod : public Usermod {
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
newType = top["type"] | newType; newType = top["type"] | newType;
for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i]; for (byte i=0; i<3; i++) ioPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip; flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast; contrast = top[FPSTR(_contrast)] | contrast;
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
@ -1162,26 +1255,76 @@ class FourLineDisplayUsermod : public Usermod {
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
bool pinsChanged = false; bool pinsChanged = false;
for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } for (byte i=0; i<3; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; }
if (pinsChanged || type!=newType) { if (pinsChanged || type!=newType) {
if (type != NONE) delete u8x8;
PinOwner po = PinOwner::UM_FourLineDisplay;
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64);
if (isSPI) { if (isSPI) {
pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay);
uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; if (!newSPI) {
uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; // was SPI but is no longer SPI
bool isHW = (oldPin[0]==hw_sclk && oldPin[1]==hw_mosi); int8_t oldPins[] = {spi_sclk, spi_mosi};
if (isHW) po = PinOwner::HW_SPI; pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_SPI);
PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } };
if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { newType=NONE; }
} else { } else {
uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; // still SPI but pins changed
uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
bool isHW = (oldPin[0]==hw_scl && oldPin[1]==hw_sda); if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; }
if (isHW) po = PinOwner::HW_I2C; else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; }
}
} else if (newSPI) {
// was I2C but is now SPI
int8_t oldPins[] = {i2c_scl, i2c_sda};
pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_I2C);
PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } };
if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; }
else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; }
else {
PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } };
if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) {
pinManager.deallocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay);
newType = NONE;
}
}
} else {
// just I2C tye changed
} }
pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po);
type = newType; type = newType;
setup(); switch (type) {
case SSD1306:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SH1106:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1306_64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1305:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1305_64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE);
break;
case SSD1306_SPI:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset
break;
case SSD1306_SPI64:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset
break;
default:
u8x8_Setup(u8x8->getU8x8(), u8x8_d_null_cb, u8x8_cad_empty, u8x8_byte_empty, u8x8_dummy_cb);
break;
}
startDisplay();
needsRedraw |= true; needsRedraw |= true;
} else { } else {
u8x8->setBusClock(ioFrequency); // can be used for SPI too u8x8->setBusClock(ioFrequency); // can be used for SPI too
@ -1196,27 +1339,3 @@ class FourLineDisplayUsermod : public Usermod {
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_contrastFix)].isNull(); return !top[FPSTR(_contrastFix)].isNull();
} }
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP;
}
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_enabled[] PROGMEM = "enabled";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate-ms";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";
const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds";
const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz";
const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix";
FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr;

View File

@ -45,7 +45,19 @@
#define ENCODER_SW_PIN 19 #define ENCODER_SW_PIN 19
#endif #endif
// The last UI state, remove color and saturation option if diplay not active(too many options) #ifndef USERMOD_USE_PCF8574
#undef USE_PCF8574
#define USE_PCF8574 false
#else
#undef USE_PCF8574
#define USE_PCF8574 true
#endif
#ifndef PCF8574_ADDRESS
#define PCF8574_ADDRESS 0x20 // some may start at 0x38
#endif
// The last UI state, remove color and saturation option if display not active (too many options)
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
#define LAST_UI_STATE 11 #define LAST_UI_STATE 11
#else #else
@ -114,61 +126,67 @@ static int re_qstringCmp(const void *ap, const void *bp) {
class RotaryEncoderUIUsermod : public Usermod { class RotaryEncoderUIUsermod : public Usermod {
private: private:
int8_t fadeAmount = 5; // Amount to change every step (brightness)
const int8_t fadeAmount; // Amount to change every step (brightness)
unsigned long loopTime; unsigned long loopTime;
unsigned long buttonPressedTime = 0; unsigned long buttonPressedTime;
unsigned long buttonWaitTime = 0; unsigned long buttonWaitTime;
bool buttonPressedBefore = false; bool buttonPressedBefore;
bool buttonLongPressed = false; bool buttonLongPressed;
int8_t pinA = ENCODER_DT_PIN; // DT from encoder int8_t pinA; // DT from encoder
int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder int8_t pinB; // CLK from encoder
int8_t pinC = ENCODER_SW_PIN; // SW from encoder int8_t pinC; // SW from encoder
unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ... unsigned char select_state; // 0: brightness, 1: effect, 2: effect speed, ...
uint16_t currentHue1 = 16; // default boot color uint16_t currentHue1; // default boot color
byte currentSat1 = 255; byte currentSat1;
uint8_t currentCCT;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod *display; FourLineDisplayUsermod *display;
#else #else
void* display = nullptr; void* display;
#endif #endif
// Pointers the start of the mode names within JSON_mode_names // Pointers the start of the mode names within JSON_mode_names
const char **modes_qstrings = nullptr; const char **modes_qstrings;
// Array of mode indexes in alphabetical order. // Array of mode indexes in alphabetical order.
byte *modes_alpha_indexes = nullptr; byte *modes_alpha_indexes;
// Pointers the start of the palette names within JSON_palette_names // Pointers the start of the palette names within JSON_palette_names
const char **palettes_qstrings = nullptr; const char **palettes_qstrings;
// Array of palette indexes in alphabetical order. // Array of palette indexes in alphabetical order.
byte *palettes_alpha_indexes = nullptr; byte *palettes_alpha_indexes;
unsigned char Enc_A; struct { // reduce memory footprint
unsigned char Enc_B; bool Enc_A : 1;
unsigned char Enc_A_prev = 0; bool Enc_B : 1;
bool Enc_A_prev : 1;
};
bool currentEffectAndPaletteInitialized = false; bool currentEffectAndPaletteInitialized;
uint8_t effectCurrentIndex = 0; uint8_t effectCurrentIndex;
uint8_t effectPaletteIndex = 0; uint8_t effectPaletteIndex;
uint8_t knownMode = 0; uint8_t knownMode;
uint8_t knownPalette = 0; uint8_t knownPalette;
uint8_t currentCCT = 128; byte presetHigh;
byte presetLow;
byte presetHigh = 0; bool applyToAll;
byte presetLow = 0;
bool applyToAll = true; bool initDone;
bool enabled;
bool initDone = false; bool usePcf8574;
bool enabled = true; uint8_t addrPcf8574;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
@ -179,12 +197,184 @@ private:
static const char _presetHigh[]; static const char _presetHigh[];
static const char _presetLow[]; static const char _presetLow[];
static const char _applyToAll[]; static const char _applyToAll[];
static const char _pcf8574[];
static const char _pcfAddress[];
/**
* readPin() - read rotary encoder pin value
*/
byte readPin(uint8_t pin);
/** /**
* Sort the modes and palettes to the index arrays * Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes. * modes_alpha_indexes and palettes_alpha_indexes.
*/ */
void sortModesAndPalettes() { void sortModesAndPalettes();
byte *re_initIndexArray(int numModes);
/**
* Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'.
*/
const char **re_findModeStrings(const char json[], int numModes);
/**
* Sort either the modes or the palettes using quicksort.
*/
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip);
public:
RotaryEncoderUIUsermod()
: fadeAmount(5)
, buttonPressedTime(0)
, buttonWaitTime(0)
, buttonPressedBefore(false)
, buttonLongPressed(false)
, pinA(ENCODER_DT_PIN)
, pinB(ENCODER_CLK_PIN)
, pinC(ENCODER_SW_PIN)
, select_state(0)
, currentHue1(16)
, currentSat1(255)
, currentCCT(128)
, display(nullptr)
, modes_qstrings(nullptr)
, modes_alpha_indexes(nullptr)
, palettes_qstrings(nullptr)
, palettes_alpha_indexes(nullptr)
, currentEffectAndPaletteInitialized(false)
, effectCurrentIndex(0)
, effectPaletteIndex(0)
, knownMode(0)
, knownPalette(0)
, presetHigh(0)
, presetLow(0)
, applyToAll(true)
, initDone(false)
, enabled(true)
, usePcf8574(USE_PCF8574)
, addrPcf8574(PCF8574_ADDRESS)
{}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() { return USERMOD_ID_ROTARY_ENC_UI; }
/**
* Enable/Disable the usermod
*/
inline void enable(bool enable) { enabled = enable; }
/**
* Get usermod enabled/disabled state
*/
inline bool isEnabled() { return enabled; }
/**
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup();
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
//void connected();
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop();
#ifndef WLED_DISABLE_MQTT
//bool onMqttMessage(char* topic, char* payload);
//void onMqttConnect(bool sessionPresent);
#endif
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
//bool handleButton(uint8_t b);
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
//void addToJsonInfo(JsonObject &root);
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void addToJsonState(JsonObject &root);
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
//void readFromJsonState(JsonObject &root);
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root);
//void appendConfigData();
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root);
// custom methods
void displayNetworkInfo();
void findCurrentEffectAndPalette();
bool changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph);
void lampUdated();
void changeBrightness(bool increase);
void changeEffect(bool increase);
void changeEffectSpeed(bool increase);
void changeEffectIntensity(bool increase);
void changeCustom(uint8_t par, bool increase);
void changePalette(bool increase);
void changeHue(bool increase);
void changeSat(bool increase);
void changePreset(bool increase);
void changeCCT(bool increase);
};
/**
* readPin() - read rotary encoder pin value
*/
byte RotaryEncoderUIUsermod::readPin(uint8_t pin) {
if (usePcf8574) {
byte _data = 0;
if (pin < 8) {
Wire.requestFrom(addrPcf8574, 1U);
if (Wire.available()) {
_data = Wire.read();
_data = (_data>>pin) & 1;
}
}
return _data;
} else {
return digitalRead(pin);
}
}
/**
* Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes.
*/
void RotaryEncoderUIUsermod::sortModesAndPalettes() {
//modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
modes_qstrings = strip.getModeDataSrc(); modes_qstrings = strip.getModeDataSrc();
modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
@ -200,7 +390,7 @@ private:
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
} }
byte *re_initIndexArray(int numModes) { byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes); byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
for (byte i = 0; i < numModes; i++) { for (byte i = 0; i < numModes; i++) {
indexes[i] = i; indexes[i] = i;
@ -212,7 +402,7 @@ private:
* Return an array of mode or palette names from the JSON string. * Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'. * They don't end in '\0', they end in '"'.
*/ */
const char **re_findModeStrings(const char json[], int numModes) { const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
uint8_t modeIndex = 0; uint8_t modeIndex = 0;
bool insideQuotes = false; bool insideQuotes = false;
@ -250,7 +440,7 @@ private:
/** /**
* Sort either the modes or the palettes using quicksort. * Sort either the modes or the palettes using quicksort.
*/ */
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { void RotaryEncoderUIUsermod::re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) {
if (!modeNames) return; if (!modeNames) return;
listBeingSorted = modeNames; listBeingSorted = modeNames;
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
@ -258,14 +448,18 @@ private:
} }
public: // public methods
/* /*
* setup() is called once at boot. WiFi is not yet connected at this point. * setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar. * You can use it to initialize variables, sensors or similar.
*/ */
void setup() void RotaryEncoderUIUsermod::setup()
{ {
DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
if (!usePcf8574) {
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
// BUG: configuring this usermod with conflicting pins // BUG: configuring this usermod with conflicting pins
@ -284,6 +478,12 @@ public:
pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO);
pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO);
pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO);
} else {
if ((i2c_sda == i2c_scl && i2c_sda == -1) || pinA<0 || pinB<0 || pinC<0) {
enabled = false;
return;
}
}
loopTime = millis(); loopTime = millis();
@ -301,20 +501,11 @@ public:
#endif #endif
initDone = true; initDone = true;
Enc_A = digitalRead(pinA); // Read encoder pins Enc_A = readPin(pinA); // Read encoder pins
Enc_B = digitalRead(pinB); Enc_B = readPin(pinB);
Enc_A_prev = Enc_A; Enc_A_prev = Enc_A;
} }
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
//Serial.println("Connected to WiFi!");
}
/* /*
* loop() is called continuously. Here you can check for events, read sensors, etc. * loop() is called continuously. Here you can check for events, read sensors, etc.
* *
@ -325,7 +516,7 @@ public:
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
* Instead, use a timer check as shown here. * Instead, use a timer check as shown here.
*/ */
void loop() void RotaryEncoderUIUsermod::loop()
{ {
if (!enabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long currentTime = millis(); // get the current elapsed time unsigned long currentTime = millis(); // get the current elapsed time
@ -346,7 +537,7 @@ public:
{ {
loopTime = currentTime; // Updates loopTime loopTime = currentTime; // Updates loopTime
bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released bool buttonPressed = !readPin(pinC); //0=pressed, 1=released
if (buttonPressed) { if (buttonPressed) {
if (!buttonPressedBefore) buttonPressedTime = currentTime; if (!buttonPressedBefore) buttonPressedTime = currentTime;
buttonPressedBefore = true; buttonPressedBefore = true;
@ -413,8 +604,8 @@ public:
if (changedState) select_state = newState; if (changedState) select_state = newState;
} }
Enc_A = digitalRead(pinA); // Read encoder pins Enc_A = readPin(pinA); // Read encoder pins
Enc_B = digitalRead(pinB); Enc_B = readPin(pinB);
if ((Enc_A) && (!Enc_A_prev)) if ((Enc_A) && (!Enc_A_prev))
{ // A has gone from high to low { // A has gone from high to low
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
@ -456,13 +647,13 @@ public:
} }
} }
void displayNetworkInfo() { void RotaryEncoderUIUsermod::displayNetworkInfo() {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
display->networkOverlay(PSTR("NETWORK INFO"), 10000); display->networkOverlay(PSTR("NETWORK INFO"), 10000);
#endif #endif
} }
void findCurrentEffectAndPalette() { void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() {
currentEffectAndPaletteInitialized = true; currentEffectAndPaletteInitialized = true;
for (uint8_t i = 0; i < strip.getModeCount(); i++) { for (uint8_t i = 0; i < strip.getModeCount(); i++) {
if (modes_alpha_indexes[i] == effectCurrent) { if (modes_alpha_indexes[i] == effectCurrent) {
@ -479,7 +670,7 @@ public:
} }
} }
boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display != nullptr) { if (display != nullptr) {
if (display->wakeDisplay()) { if (display->wakeDisplay()) {
@ -494,7 +685,7 @@ public:
return true; return true;
} }
void lampUdated() { void RotaryEncoderUIUsermod::lampUdated() {
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
//setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
@ -502,7 +693,7 @@ public:
updateInterfaces(CALL_MODE_BUTTON); updateInterfaces(CALL_MODE_BUTTON);
} }
void changeBrightness(bool increase) { void RotaryEncoderUIUsermod::changeBrightness(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -519,7 +710,7 @@ public:
} }
void changeEffect(bool increase) { void RotaryEncoderUIUsermod::changeEffect(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -548,7 +739,7 @@ public:
} }
void changeEffectSpeed(bool increase) { void RotaryEncoderUIUsermod::changeEffectSpeed(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -576,7 +767,7 @@ public:
} }
void changeEffectIntensity(bool increase) { void RotaryEncoderUIUsermod::changeEffectIntensity(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -604,7 +795,7 @@ public:
} }
void changeCustom(uint8_t par, bool increase) { void RotaryEncoderUIUsermod::changeCustom(uint8_t par, bool increase) {
uint8_t val = 0; uint8_t val = 0;
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
@ -649,7 +840,7 @@ public:
} }
void changePalette(bool increase) { void RotaryEncoderUIUsermod::changePalette(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -678,7 +869,7 @@ public:
} }
void changeHue(bool increase){ void RotaryEncoderUIUsermod::changeHue(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -708,7 +899,7 @@ public:
#endif #endif
} }
void changeSat(bool increase){ void RotaryEncoderUIUsermod::changeSat(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -737,7 +928,7 @@ public:
#endif #endif
} }
void changePreset(bool increase) { void RotaryEncoderUIUsermod::changePreset(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -769,7 +960,7 @@ public:
} }
} }
void changeCCT(bool increase){ void RotaryEncoderUIUsermod::changeCCT(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY #ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) { if (display && display->wakeDisplay()) {
display->redraw(true); display->redraw(true);
@ -803,7 +994,7 @@ public:
* Below it is shown how this could be used for e.g. a light sensor * Below it is shown how this could be used for e.g. a light sensor
*/ */
/* /*
void addToJsonInfo(JsonObject& root) void RotaryEncoderUIUsermod::addToJsonInfo(JsonObject& root)
{ {
int reading = 20; int reading = 20;
//this code adds "u":{"Light":[20," lux"]} to the info object //this code adds "u":{"Light":[20," lux"]} to the info object
@ -820,7 +1011,7 @@ public:
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/* /*
void addToJsonState(JsonObject &root) void RotaryEncoderUIUsermod::addToJsonState(JsonObject &root)
{ {
//root["user0"] = userVar0; //root["user0"] = userVar0;
} }
@ -831,7 +1022,7 @@ public:
* Values in the state object may be modified by connected clients * Values in the state object may be modified by connected clients
*/ */
/* /*
void readFromJsonState(JsonObject &root) void RotaryEncoderUIUsermod::readFromJsonState(JsonObject &root)
{ {
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
@ -841,7 +1032,7 @@ public:
/** /**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json * addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/ */
void addToConfig(JsonObject &root) { void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) {
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled; top[FPSTR(_enabled)] = enabled;
@ -851,15 +1042,21 @@ public:
top[FPSTR(_presetLow)] = presetLow; top[FPSTR(_presetLow)] = presetLow;
top[FPSTR(_presetHigh)] = presetHigh; top[FPSTR(_presetHigh)] = presetHigh;
top[FPSTR(_applyToAll)] = applyToAll; top[FPSTR(_applyToAll)] = applyToAll;
top[FPSTR(_pcf8574)] = usePcf8574;
top[FPSTR(_pcfAddress)] = addrPcf8574;
DEBUG_PRINTLN(F("Rotary Encoder config saved.")); DEBUG_PRINTLN(F("Rotary Encoder config saved."));
} }
//void RotaryEncoderUIUsermod::appendConfigData() {
// oappend(SET_F("addInfo('RotaryEncoderUIUsermod:PCF8574-address',1,'<i>(not hex!)</i>');"));
//}
/** /**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
* *
* The function should return true if configuration was successfully loaded or false if there was no configuration. * The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) { bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) {
// we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
@ -879,6 +1076,9 @@ public:
enabled = top[FPSTR(_enabled)] | enabled; enabled = top[FPSTR(_enabled)] | enabled;
applyToAll = top[FPSTR(_applyToAll)] | applyToAll; applyToAll = top[FPSTR(_applyToAll)] | applyToAll;
usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574;
addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574;
DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
@ -890,12 +1090,14 @@ public:
DEBUG_PRINTLN(F(" config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page // changing parameters from settings page
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) {
if (!usePcf8574) {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
pinA = newDTpin; pinA = newDTpin;
pinB = newCLKpin; pinB = newCLKpin;
pinC = newSWpin; pinC = newSWpin;
}
if (pinA<0 || pinB<0 || pinC<0) { if (pinA<0 || pinB<0 || pinC<0) {
enabled = false; enabled = false;
return true; return true;
@ -904,18 +1106,9 @@ public:
} }
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_applyToAll)].isNull(); return !top[FPSTR(_pcf8574)].isNull();
} }
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_ROTARY_ENC_UI;
}
};
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder"; const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
@ -926,3 +1119,5 @@ const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";
const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high"; const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high";
const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low"; const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low";
const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg"; const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg";
const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574";
const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address";

View File

@ -297,9 +297,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } };
if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
#ifdef ESP32 #ifdef ESP32
Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called prior) if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior)
else Wire.begin();
#else
Wire.begin(i2c_sda, i2c_scl);
#endif #endif
Wire.begin();
} else { } else {
i2c_sda = -1; i2c_sda = -1;
i2c_scl = -1; i2c_scl = -1;

View File

@ -410,6 +410,7 @@ button {
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
bottom: 0; bottom: 0;
max-width: 300px;
} }
#sliders .labels { #sliders .labels {
@ -418,7 +419,7 @@ button {
} }
.slider { .slider {
max-width: 300px; /*max-width: 300px;*/
/* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */ /* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */
border-radius: 24px; border-radius: 24px;
position: relative; position: relative;
@ -434,12 +435,16 @@ button {
z-index: 0; z-index: 0;
} }
#sliders .slider {
padding-right: 64px; /* offset for bubble */
}
#sliders .slider, #info .slider { #sliders .slider, #info .slider {
background-color: var(--c-2); background-color: var(--c-2);
} }
#sliders .sliderwrap, .sbs .sliderwrap { #sliders .sliderwrap, .sbs .sliderwrap {
left: 16px; /* offset for icon */ left: 32px; /* offset for icon */
} }
.filter, .option { .filter, .option {
@ -685,18 +690,19 @@ img {
.sliderbubble { .sliderbubble {
width: 24px; width: 24px;
position: relative; position: absolute;
display: inline-block; display: inline-block;
border-radius: 10px; border-radius: 16px;
background: var(--c-3); background: var(--c-3);
color: var(--c-f); color: var(--c-f);
padding: 2px 4px; padding: 4px;
font-size: 14px; font-size: 14px;
right: 3px; right: 6px;
transition: visibility 0.25s ease, opacity 0.25s ease; transition: visibility .25s ease,opacity .25s ease;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
left: 8px; /* left: 8px; */
top: 4px;
} }
output.sliderbubbleshow { output.sliderbubbleshow {
@ -1022,8 +1028,9 @@ textarea {
/* segment power wrapper */ /* segment power wrapper */
.sbs { .sbs {
padding: 1px 0 1px 20px; /*padding: 1px 0 1px 20px;*/
display: var(--sgp); display: var(--sgp);
width: 100%;
} }
.pname { .pname {
@ -1538,8 +1545,11 @@ TD .checkmark, TD .radiomark {
#sliders .sliderbubble { #sliders .sliderbubble {
display: none; display: none;
} }
.sliderwrap { #sliders .sliderwrap, .sbs .sliderwrap {
width: calc(100% - 28px); width: calc(100% - 42px);
}
#sliders .slider {
padding-right: 0;
} }
#sliders .sliderwrap { #sliders .sliderwrap {
left: 12px; left: 12px;

View File

@ -497,6 +497,7 @@ function loadFX(callback = null)
populateEffects(); populateEffects();
}) })
.catch((e)=>{ .catch((e)=>{
//setTimeout(loadFX, 250); // retry
showToast(e, true); showToast(e, true);
}) })
.finally(()=>{ .finally(()=>{
@ -524,6 +525,7 @@ function loadFXData(callback = null)
}) })
.catch((e)=>{ .catch((e)=>{
fxdata = []; fxdata = [];
//setTimeout(loadFXData, 250); // retry
showToast(e, true); showToast(e, true);
}) })
.finally(()=>{ .finally(()=>{
@ -1253,6 +1255,7 @@ function updateSelectedFx()
// hide non-0D effects if segment only has 1 pixel (0D) // hide non-0D effects if segment only has 1 pixel (0D)
var fxs = parent.querySelectorAll('.lstI'); var fxs = parent.querySelectorAll('.lstI');
for (const fx of fxs) { for (const fx of fxs) {
if (!fx.dataset.opt) continue;
let opts = fx.dataset.opt.split(";"); let opts = fx.dataset.opt.split(";");
if (fx.dataset.id>0) { if (fx.dataset.id>0) {
if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects)

View File

@ -322,7 +322,7 @@ Y:<input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()">
<option value="1">Vertical</option> <option value="1">Vertical</option>
</select><br> </select><br>
Serpentine: <input type="checkbox" name="PS"><br> Serpentine: <input type="checkbox" name="PS"><br>
<i style="color:#fa0;">Pressing Populate will create LED panel layout with pre-arranged matrix.<br>Values above <i>will not</i> affect final layout.<br> <i class="warn">Pressing Populate will create LED panel layout with pre-arranged matrix.<br>Values above <i>will not</i> affect final layout.<br>
WARNING: You may need to update each panel parameters after they are generated.</i><br> WARNING: You may need to update each panel parameters after they are generated.</i><br>
<button type="button" onclick="gen();expand(gId('expGen'),gId('mxGen'));">Populate</button> <button type="button" onclick="gen();expand(gId('expGen'),gId('mxGen'));">Populate</button>
</div> </div>

View File

@ -576,7 +576,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br> Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br>
<div id="abl"> <div id="abl">
Maximum Current: <input name="MA" type="number" class="l" min="250" max="65000" oninput="UI()" required> mA<br> Maximum Current: <input name="MA" type="number" class="l" min="250" max="65000" oninput="UI()" required> mA<br>
<div id="ampwarning" style="color: orange; display: none;"> <div id="ampwarning" class="warn" style="display: none;">
&#9888; Your power supply provides high current.<br> &#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br> To improve the safety of your setup,<br>
please use thick cables,<br> please use thick cables,<br>
@ -604,7 +604,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br> <button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br>
LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<br> LED Memory Usage: <span id="m0">0</span> / <span id="m1">?</span> B<br>
<div id="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br> <div id="dbar" style="display:inline-block; width: 100px; height: 10px; border-radius: 20px;"></div><br>
<div id="ledwarning" style="color: orange; display: none;"> <div id="ledwarning" class="warn" style="display: none;">
&#9888; You might run into stability or lag issues.<br> &#9888; You might run into stability or lag issues.<br>
Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br> Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br>
</div> </div>

View File

@ -89,7 +89,7 @@
</div> </div>
<h2>Security & Update setup</h2> <h2>Security & Update setup</h2>
Settings PIN: <input type="password" id="PIN" name="PIN" size="4" maxlength="4" minlength="4" onkeydown="checkNum(this)" pattern="[0-9]*" inputmode="numeric" title="Please enter a 4 digit number"><br> Settings PIN: <input type="password" id="PIN" name="PIN" size="4" maxlength="4" minlength="4" onkeydown="checkNum(this)" pattern="[0-9]*" inputmode="numeric" title="Please enter a 4 digit number"><br>
<div style="color: #fa0;">&#9888; Unencrypted transmission. Be prudent when selecting PIN, do NOT use your banking, door, SIM, etc. pin!</div><br> <div class="warn">&#9888; Unencrypted transmission. Be prudent when selecting PIN, do NOT use your banking, door, SIM, etc. pin!</div><br>
Lock wireless (OTA) software update: <input type="checkbox" name="NO"><br> Lock wireless (OTA) software update: <input type="checkbox" name="NO"><br>
Passphrase: <input type="password" name="OP" maxlength="32"><br> Passphrase: <input type="password" name="OP" maxlength="32"><br>
To enable OTA, for security reasons you need to also enter the correct password!<br> To enable OTA, for security reasons you need to also enter the correct password!<br>
@ -99,7 +99,7 @@
Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br> Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br>
Factory reset: <input type="checkbox" name="RS"><br> Factory reset: <input type="checkbox" name="RS"><br>
All settings and presets will be erased.<br><br> All settings and presets will be erased.<br><br>
<div style="color: #fa0;">&#9888; Unencrypted transmission. An attacker on the same network can intercept form data!</div> <div class="warn">&#9888; Unencrypted transmission. An attacker on the same network can intercept form data!</div>
<hr> <hr>
<h3>Software Update</h3> <h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br> <button type="button" onclick="U()">Manual OTA Update</button><br>
@ -110,7 +110,7 @@
<div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br> <div>Restore presets<br><input type="file" name="data" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data,'/presets.json');">Upload</button><br></div><br>
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br> <a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div> <div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div>
<div style="color: #fa0;">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br> <div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div> Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
For security reasons, passwords are not backed up. For security reasons, passwords are not backed up.
<hr> <hr>

View File

@ -172,7 +172,7 @@ Realtime LED offset: <input name="WO" type="number" min="-255" max="255" require
<hr class="sml"> <hr class="sml">
<h3>Alexa Voice Assistant</h3> <h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide"> <div id="NoAlexa" class="hide">
<em style="color:#fa0;">This firmware build does not include Alexa support.<br></em><br> <i class="warn">This firmware build does not include Alexa support.<br></i><br>
</div> </div>
<div id="Alexa"> <div id="Alexa">
Emulate Alexa device: <input type="checkbox" name="AL"><br> Emulate Alexa device: <input type="checkbox" name="AL"><br>
@ -180,7 +180,7 @@ Alexa invocation name: <input type="text" name="AI" maxlength="32"><br>
Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9" required> presets<br><br> Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9" required> presets<br><br>
</div> </div>
<hr class="sml"> <hr class="sml">
<div style="color: #fa0;">&#9888; <b>MQTT and Hue sync all connect to external hosts!<br> <div class="warn">&#9888; <b>MQTT and Hue sync all connect to external hosts!<br>
This may impact the responsiveness of WLED.</b><br> This may impact the responsiveness of WLED.</b><br>
</div> </div>
For best results, only use one of these services at a time.<br> For best results, only use one of these services at a time.<br>
@ -188,7 +188,7 @@ For best results, only use one of these services at a time.<br>
<hr class="sml"> <hr class="sml">
<h3>MQTT</h3> <h3>MQTT</h3>
<div id="NoMQTT" class="hide"> <div id="NoMQTT" class="hide">
<em style="color:#fa0;">This firmware build does not include MQTT support.<br></em> <i class="warn">This firmware build does not include MQTT support.<br></i>
</div> </div>
<div id="MQTT"> <div id="MQTT">
Enable MQTT: <input type="checkbox" name="MQ"><br> Enable MQTT: <input type="checkbox" name="MQ"><br>
@ -207,7 +207,7 @@ Retain brightness & color messages: <input type="checkbox" name="RT"><br>
</div> </div>
<h3>Philips Hue</h3> <h3>Philips Hue</h3>
<div id="NoHue" class="hide"> <div id="NoHue" class="hide">
<em style="color:#fa0;">This firmware build does not include Philips Hue support.<br></em> <em class="warn">This firmware build does not include Philips Hue support.<br></em>
</div> </div>
<div id="Hue"> <div id="Hue">
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br> <i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>

View File

@ -249,7 +249,7 @@
Server description: <input type="text" name="DS" maxlength="32"><br> Server description: <input type="text" name="DS" maxlength="32"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br> Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
<div id="NoSimple" class="hide"> <div id="NoSimple" class="hide">
<em style="color:#fa0;">This firmware build does not include simplified UI support.<br></em> <i class="warn">This firmware build does not include simplified UI support.<br></i>
</div> </div>
<div id="Simple">Enable simplified UI: <input type="checkbox" name="SU"><br></div> <div id="Simple">Enable simplified UI: <input type="checkbox" name="SU"><br></div>
<i>The following UI customization settings are unique both to the WLED device and this browser.<br> <i>The following UI customization settings are unique both to the WLED device and this browser.<br>

View File

@ -242,15 +242,17 @@
</div> </div>
<h2>Usermod Setup</h2> <h2>Usermod Setup</h2>
Global I<sup>2</sup>C GPIOs (HW)<br> Global I<sup>2</sup>C GPIOs (HW)<br>
<i style="color: orange;">(only changable on ESP32, change requires reboot!)</i><br> <i class="warn">(change requires reboot!)</i><br>
SDA:<input type="number" min="-1" max="48" name="SDA" onchange="check(this,'if')" class="s" placeholder="SDA"> SDA:<input type="number" min="-1" max="48" name="SDA" onchange="check(this,'if')" class="s" placeholder="SDA">
SCL:<input type="number" min="-1" max="48" name="SCL" onchange="check(this,'if')" class="s" placeholder="SCL"> SCL:<input type="number" min="-1" max="48" name="SCL" onchange="check(this,'if')" class="s" placeholder="SCL">
<hr class="sml"> <hr class="sml">
Global SPI GPIOs (HW)<br> Global SPI GPIOs (HW)<br>
<i style="color: orange;">(only changable on ESP32, change requires reboot!)</i><br> <i class="warn">(only changable on ESP32, change requires reboot!)</i><br>
MOSI:<input type="number" min="-1" max="48" name="MOSI" onchange="check(this,'if')" class="s" placeholder="MOSI"> MOSI:<input type="number" min="-1" max="48" name="MOSI" onchange="check(this,'if')" class="s" placeholder="MOSI">
MISO:<input type="number" min="-1" max="48" name="MISO" onchange="check(this,'if')" class="s" placeholder="MISO"> MISO:<input type="number" min="-1" max="48" name="MISO" onchange="check(this,'if')" class="s" placeholder="MISO">
SCLK:<input type="number" min="-1" max="48" name="SCLK" onchange="check(this,'if')" class="s" placeholder="SCLK"> SCLK:<input type="number" min="-1" max="48" name="SCLK" onchange="check(this,'if')" class="s" placeholder="SCLK">
<hr class="sml">
Reboot after save? <input type="checkbox" name="RBT"><br>
<div id="um">Loading settings...</div> <div id="um">Loading settings...</div>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button> <hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form> </form>

View File

@ -61,6 +61,9 @@ button.sml {
.hide { .hide {
display: none; display: none;
} }
.warn {
color: #fa0;
}
input { input {
background: #333; background: #333;
color: #fff; color: #fff;

View File

@ -99,7 +99,7 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
void handleImprovPacket(); void handleImprovPacket();
void sendImprovStateResponse(uint8_t state, bool error = false); void sendImprovStateResponse(uint8_t state, bool error = false);
void sendImprovInfoResponse(); void sendImprovInfoResponse();
void sendImprovRPCResponse(uint8_t commandId); void sendImprovRPCResponse(byte commandId);
//ir.cpp //ir.cpp
void applyRepeatActions(); void applyRepeatActions();
@ -129,8 +129,8 @@ bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE,
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root); void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray arr, const char *qstring); void serializeModeNames(JsonArray root);
void serializeModeData(JsonObject root); void serializeModeData(JsonArray root);
void serveJson(AsyncWebServerRequest* request); void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE #ifdef WLED_ENABLE_JSONLIVE
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
@ -152,9 +152,11 @@ void handleTransitions();
void handleNightlight(); void handleNightlight();
byte scaledBri(byte in); byte scaledBri(byte in);
#ifdef WLED_ENABLE_LOXONE
//lx_parser.cpp //lx_parser.cpp
bool parseLx(int lxValue, byte* rgbw); bool parseLx(int lxValue, byte* rgbw);
void parseLxJson(int lxValue, byte segId, bool secondary); void parseLxJson(int lxValue, byte segId, bool secondary);
#endif
//mqtt.cpp //mqtt.cpp
bool initMqtt(); bool initMqtt();
@ -203,7 +205,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp //udp.cpp
void notify(byte callMode, bool followUp=false); void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false); uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime(); void exitRealtime();
void handleNotifications(); void handleNotifications();
@ -372,7 +374,6 @@ void serveIndexOrWelcome(AsyncWebServerRequest *request);
void serveIndex(AsyncWebServerRequest* request); void serveIndex(AsyncWebServerRequest* request);
String msgProcessor(const String& var); String msgProcessor(const String& var);
void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255);
String settingsProcessor(const String& var);
String dmxProcessor(const String& var); String dmxProcessor(const String& var);
void serveSettings(AsyncWebServerRequest* request, bool post = false); void serveSettings(AsyncWebServerRequest* request, bool post = false);
void serveSettingsJS(AsyncWebServerRequest* request); void serveSettingsJS(AsyncWebServerRequest* request);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
#define JSON_PATH_PALETTES 5 #define JSON_PATH_PALETTES 5
#define JSON_PATH_FXDATA 6 #define JSON_PATH_FXDATA 6
#define JSON_PATH_NETWORKS 7 #define JSON_PATH_NETWORKS 7
#define JSON_PATH_EFFECTS 8
/* /*
* JSON API (De)serialization * JSON API (De)serialization
@ -812,7 +813,7 @@ void setPaletteColors(JsonArray json, byte* tcp)
} }
} }
void serializePalettes(JsonObject root, AsyncWebServerRequest* request) void serializePalettes(JsonObject root, int page)
{ {
byte tcp[72]; byte tcp[72];
#ifdef ESP8266 #ifdef ESP8266
@ -821,11 +822,6 @@ void serializePalettes(JsonObject root, AsyncWebServerRequest* request)
int itemPerPage = 8; int itemPerPage = 8;
#endif #endif
int page = 0;
if (request->hasParam("page")) {
page = request->getParam("page")->value().toInt();
}
int palettesCount = strip.getPaletteCount(); int palettesCount = strip.getPaletteCount();
int customPalettes = strip.customPalettes.size(); int customPalettes = strip.customPalettes.size();
@ -1001,6 +997,7 @@ void serveJson(AsyncWebServerRequest* request)
else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO; else if (url.indexOf("info") > 0) subJson = JSON_PATH_INFO;
else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO; else if (url.indexOf("si") > 0) subJson = JSON_PATH_STATE_INFO;
else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES; else if (url.indexOf("nodes") > 0) subJson = JSON_PATH_NODES;
else if (url.indexOf("eff") > 0) subJson = JSON_PATH_EFFECTS;
else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES; else if (url.indexOf("palx") > 0) subJson = JSON_PATH_PALETTES;
else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA; else if (url.indexOf("fxda") > 0) subJson = JSON_PATH_FXDATA;
else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS; else if (url.indexOf("net") > 0) subJson = JSON_PATH_NETWORKS;
@ -1010,20 +1007,6 @@ void serveJson(AsyncWebServerRequest* request)
return; return;
} }
#endif #endif
else if (url.indexOf(F("eff")) > 0) {
// this serves just effect names without FX data extensions in names
if (requestJSONBufferLock(19)) {
AsyncJsonResponse* response = new AsyncJsonResponse(&doc, true); // array document
JsonArray lDoc = response->getRoot();
serializeModeNames(lDoc); // remove WLED-SR extensions from effect names
response->setLength();
request->send(response);
releaseJSONBufferLock();
} else {
request->send(503, "application/json", F("{\"error\":3}"));
}
return;
}
else if (url.indexOf("pal") > 0) { else if (url.indexOf("pal") > 0) {
request->send_P(200, "application/json", JSON_palette_names); request->send_P(200, "application/json", JSON_palette_names);
return; return;
@ -1040,7 +1023,7 @@ void serveJson(AsyncWebServerRequest* request)
request->send(503, "application/json", F("{\"error\":3}")); request->send(503, "application/json", F("{\"error\":3}"));
return; return;
} }
AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==6); AsyncJsonResponse *response = new AsyncJsonResponse(&doc, subJson==JSON_PATH_FXDATA || subJson==JSON_PATH_EFFECTS); // will clear and convert JsonDocument into JsonArray if necessary
JsonVariant lDoc = response->getRoot(); JsonVariant lDoc = response->getRoot();
@ -1053,9 +1036,11 @@ void serveJson(AsyncWebServerRequest* request)
case JSON_PATH_NODES: case JSON_PATH_NODES:
serializeNodes(lDoc); break; serializeNodes(lDoc); break;
case JSON_PATH_PALETTES: case JSON_PATH_PALETTES:
serializePalettes(lDoc, request); break; serializePalettes(lDoc, request->hasParam("page") ? request->getParam("page")->value().toInt() : 0); break;
case JSON_PATH_EFFECTS:
serializeModeNames(lDoc); break;
case JSON_PATH_FXDATA: case JSON_PATH_FXDATA:
serializeModeData(lDoc.as<JsonArray>()); break; serializeModeData(lDoc); break;
case JSON_PATH_NETWORKS: case JSON_PATH_NETWORKS:
serializeNetworks(lDoc); break; serializeNetworks(lDoc); break;
default: //all default: //all
@ -1074,7 +1059,9 @@ void serveJson(AsyncWebServerRequest* request)
DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson); DEBUG_PRINTF("JSON buffer size: %u for request: %d\n", lDoc.memoryUsage(), subJson);
response->setLength(); size_t len = response->setLength();
DEBUG_PRINT(F("JSON content length: ")); DEBUG_PRINTLN(len);
request->send(response); request->send(response);
releaseJSONBufferLock(); releaseJSONBufferLock();
} }

View File

@ -5,7 +5,7 @@
/* /*
* Parser for Loxone formats * Parser for Loxone formats
*/ */
bool parseLx(int lxValue, byte rgbw[4]) bool parseLx(int lxValue, byte* rgbw)
{ {
DEBUG_PRINT(F("LX: Lox = ")); DEBUG_PRINT(F("LX: Lox = "));
DEBUG_PRINTLN(lxValue); DEBUG_PRINTLN(lxValue);

View File

@ -462,7 +462,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
clearEEPROM(); clearEEPROM();
#endif #endif
serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255); serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255);
doReboot = true; doReboot = true; // may reboot immediately on dual-core system (race condition) which is desireable in this case
} }
if (request->hasArg(F("PIN"))) { if (request->hasArg(F("PIN"))) {
@ -539,27 +539,24 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// global I2C & SPI pins // global I2C & SPI pins
int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt(); int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt();
int8_t hw_scl_pin = !request->arg(F("SCL")).length() ? -1 : (int)request->arg(F("SCL")).toInt(); int8_t hw_scl_pin = !request->arg(F("SCL")).length() ? -1 : (int)request->arg(F("SCL")).toInt();
#ifdef ESP8266 if (i2c_sda != hw_sda_pin || i2c_scl != hw_scl_pin) {
// cannot change pins on ESP8266 // only if pins changed
if (hw_sda_pin >= 0 && hw_sda_pin != HW_PIN_SDA) hw_sda_pin = HW_PIN_SDA; uint8_t old_i2c[2] = { static_cast<uint8_t>(i2c_scl), static_cast<uint8_t>(i2c_sda) };
if (hw_scl_pin >= 0 && hw_scl_pin != HW_PIN_SCL) hw_scl_pin = HW_PIN_SCL; pinManager.deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins
#endif
PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } }; PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } };
if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
i2c_sda = hw_sda_pin; i2c_sda = hw_sda_pin;
i2c_scl = hw_scl_pin; i2c_scl = hw_scl_pin;
#ifdef ESP32 // no bus re-initialisation as usermods do not get any notification
Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called) //Wire.begin(i2c_sda, i2c_scl);
#endif
Wire.begin();
} else { } else {
// there is no Wire.end() // there is no Wire.end()
DEBUG_PRINTLN(F("Could not allocate I2C pins.")); DEBUG_PRINTLN(F("Could not allocate I2C pins."));
uint8_t i2c[2] = { static_cast<uint8_t>(i2c_scl), static_cast<uint8_t>(i2c_sda) };
pinManager.deallocateMultiplePins(i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins
i2c_sda = -1; i2c_sda = -1;
i2c_scl = -1; i2c_scl = -1;
} }
}
int8_t hw_mosi_pin = !request->arg(F("MOSI")).length() ? -1 : (int)request->arg(F("MOSI")).toInt(); int8_t hw_mosi_pin = !request->arg(F("MOSI")).length() ? -1 : (int)request->arg(F("MOSI")).toInt();
int8_t hw_miso_pin = !request->arg(F("MISO")).length() ? -1 : (int)request->arg(F("MISO")).toInt(); int8_t hw_miso_pin = !request->arg(F("MISO")).length() ? -1 : (int)request->arg(F("MISO")).toInt();
int8_t hw_sclk_pin = !request->arg(F("SCLK")).length() ? -1 : (int)request->arg(F("SCLK")).toInt(); int8_t hw_sclk_pin = !request->arg(F("SCLK")).length() ? -1 : (int)request->arg(F("SCLK")).toInt();
@ -569,6 +566,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
if (hw_miso_pin >= 0 && hw_miso_pin != HW_PIN_MISOSPI) hw_mosi_pin = HW_PIN_MISOSPI; if (hw_miso_pin >= 0 && hw_miso_pin != HW_PIN_MISOSPI) hw_mosi_pin = HW_PIN_MISOSPI;
if (hw_sclk_pin >= 0 && hw_sclk_pin != HW_PIN_CLOCKSPI) hw_sclk_pin = HW_PIN_CLOCKSPI; if (hw_sclk_pin >= 0 && hw_sclk_pin != HW_PIN_CLOCKSPI) hw_sclk_pin = HW_PIN_CLOCKSPI;
#endif #endif
if (spi_mosi != hw_mosi_pin || spi_miso != hw_miso_pin || spi_sclk != hw_sclk_pin) {
// only if pins changed
uint8_t old_spi[3] = { static_cast<uint8_t>(spi_mosi), static_cast<uint8_t>(spi_miso), static_cast<uint8_t>(spi_sclk) };
pinManager.deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins
PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } }; PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } };
if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) {
spi_mosi = hw_mosi_pin; spi_mosi = hw_mosi_pin;
@ -584,12 +585,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} else { } else {
//SPI.end(); //SPI.end();
DEBUG_PRINTLN(F("Could not allocate SPI pins.")); DEBUG_PRINTLN(F("Could not allocate SPI pins."));
uint8_t spi[3] = { static_cast<uint8_t>(spi_mosi), static_cast<uint8_t>(spi_miso), static_cast<uint8_t>(spi_sclk) };
pinManager.deallocateMultiplePins(spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins
spi_mosi = -1; spi_mosi = -1;
spi_miso = -1; spi_miso = -1;
spi_sclk = -1; spi_sclk = -1;
} }
}
JsonObject um = doc.createNestedObject("um"); JsonObject um = doc.createNestedObject("um");
@ -708,7 +708,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
#endif #endif
lastEditTime = millis(); lastEditTime = millis();
if (subPage != 2 && !doReboot) doSerializeConfig = true; //serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init) // do not save if factory reset or LED settings (which are saved after LED re-init)
doSerializeConfig = subPage != 2 && !(subPage == 6 && doReboot);
if (subPage == 8) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after doSerializeConfig has been set)
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
if (subPage == 4) alexaInit(); if (subPage == 4) alexaInit();
#endif #endif

View File

@ -73,13 +73,6 @@ void WLED::loop()
handleAlexa(); handleAlexa();
#endif #endif
yield();
if (doSerializeConfig) serializeConfig();
if (doReboot && !doInitBusses) // if busses have to be inited & saved, wait until next iteration
reset();
if (doCloseFile) { if (doCloseFile) {
closeFile(); closeFile();
yield(); yield();
@ -169,13 +162,14 @@ void WLED::loop()
strip.finalizeInit(); // also loads default ledmap if present strip.finalizeInit(); // also loads default ledmap if present
if (aligned) strip.makeAutoSegments(); if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments(); else strip.fixInvalidSegments();
yield(); doSerializeConfig = true;
serializeConfig();
} }
if (loadLedmap >= 0) { if (loadLedmap >= 0) {
if (!strip.deserializeMap(loadLedmap) && strip.isMatrix && loadLedmap == 0) strip.setUpMatrix(); if (!strip.deserializeMap(loadLedmap) && strip.isMatrix && loadLedmap == 0) strip.setUpMatrix();
loadLedmap = -1; loadLedmap = -1;
} }
yield();
if (doSerializeConfig) serializeConfig();
yield(); yield();
handleWs(); handleWs();
@ -229,6 +223,9 @@ void WLED::loop()
ESP.wdtFeed(); ESP.wdtFeed();
#endif #endif
#endif #endif
if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration
reset();
} }
void WLED::enableWatchdog() { void WLED::enableWatchdog() {

View File

@ -611,7 +611,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
} else { } else {
if (!s2[0]) strcpy_P(s2, s_redirecting); if (!s2[0]) strcpy_P(s2, s_redirecting);
serveMessage(request, 200, s, s2, (subPage == 1 || (subPage == 6 && doReboot)) ? 129 : (correctPIN ? 1 : 3)); serveMessage(request, 200, s, s2, (subPage == 1 || ((subPage == 6 || subPage == 8) && doReboot)) ? 129 : (correctPIN ? 1 : 3));
return; return;
} }
} }