Merge branch 'main' into seg-groups
This commit is contained in:
commit
0a5aac724a
@ -21,9 +21,7 @@ click==8.1.3
|
|||||||
# platformio
|
# platformio
|
||||||
# uvicorn
|
# uvicorn
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
# via
|
# via platformio
|
||||||
# click
|
|
||||||
# platformio
|
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
# via
|
# via
|
||||||
# uvicorn
|
# uvicorn
|
||||||
@ -42,7 +40,7 @@ pyelftools==0.29
|
|||||||
# via platformio
|
# via platformio
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
# via platformio
|
# via platformio
|
||||||
requests==2.28.2
|
requests==2.31.0
|
||||||
# via platformio
|
# via platformio
|
||||||
semantic-version==2.10.0
|
semantic-version==2.10.0
|
||||||
# via platformio
|
# via platformio
|
||||||
@ -52,8 +50,6 @@ starlette==0.23.1
|
|||||||
# via platformio
|
# via platformio
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
# via platformio
|
# via platformio
|
||||||
typing-extensions==4.5.0
|
|
||||||
# via starlette
|
|
||||||
urllib3==1.26.15
|
urllib3==1.26.15
|
||||||
# via requests
|
# via requests
|
||||||
uvicorn==0.20.0
|
uvicorn==0.20.0
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
# Multi Relay
|
# Multi Relay
|
||||||
|
|
||||||
This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode.
|
This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode.
|
||||||
|
Usermod supports PCF8574 I2C port expander to reduce GPIO use.
|
||||||
|
PCF8574 supports 8 outputs and each output corresponds to a relay in WLED (relay 0 = port 0, etc). I you are using more than 8 relays with multiple PCF8574 make sure their addresses are set conscutively (e.g. 0x20 and 0x21). You can set address of first expander in settings.
|
||||||
|
(**NOTE:** Will require Wire library and global I2C pins defined.)
|
||||||
|
|
||||||
## HTTP API
|
## HTTP API
|
||||||
All responses are returned in JSON format.
|
All responses are returned in JSON format.
|
||||||
@ -81,13 +84,15 @@ void registerUsermods()
|
|||||||
Usermod can be configured via the Usermods settings page.
|
Usermod can be configured via the Usermods settings page.
|
||||||
|
|
||||||
* `enabled` - enable/disable usermod
|
* `enabled` - enable/disable usermod
|
||||||
|
* `use-PCF8574` - use PCF8574 port expander instead of GPIO pins
|
||||||
|
* `first-PCF8574` - I2C address of first expander (WARNING: enter *decimal* value)
|
||||||
|
* `broadcast`- time in seconds between MQTT relay-state broadcasts
|
||||||
|
* `HA-discovery`- enable Home Assistant auto discovery
|
||||||
* `pin` - ESP GPIO pin the relay is connected to (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`)
|
* `pin` - ESP GPIO pin the relay is connected to (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`)
|
||||||
* `delay-s` - delay in seconds after on/off command is received
|
* `delay-s` - delay in seconds after on/off command is received
|
||||||
* `active-high` - assign high/low activation of relay (can be used to reverse relay states)
|
* `active-high` - assign high/low activation of relay (can be used to reverse relay states)
|
||||||
* `external` - if enabled, WLED does not control relay, it can only be triggered by an external command (MQTT, HTTP, JSON or button)
|
* `external` - if enabled, WLED does not control relay, it can only be triggered by an external command (MQTT, HTTP, JSON or button)
|
||||||
* `button` - button (from LED Settings) that controls this relay
|
* `button` - button (from LED Settings) that controls this relay
|
||||||
* `broadcast`- time in seconds between MQTT relay-state broadcasts
|
|
||||||
* `HA-discovery`- enable Home Assistant auto discovery
|
|
||||||
|
|
||||||
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
|
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
|
||||||
|
|
||||||
@ -100,3 +105,6 @@ Have fun - @blazoncek
|
|||||||
2021-11
|
2021-11
|
||||||
* Added information about dynamic configuration options
|
* Added information about dynamic configuration options
|
||||||
* Added button support.
|
* Added button support.
|
||||||
|
|
||||||
|
2023-05
|
||||||
|
* Added support for PCF8574 I2C port expander (multiple)
|
@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
#ifndef MULTI_RELAY_MAX_RELAYS
|
#ifndef MULTI_RELAY_MAX_RELAYS
|
||||||
#define MULTI_RELAY_MAX_RELAYS 4
|
#define MULTI_RELAY_MAX_RELAYS 4
|
||||||
|
#else
|
||||||
|
#if MULTI_RELAY_MAX_RELAYS>16
|
||||||
|
#undef MULTI_RELAY_MAX_RELAYS
|
||||||
|
#define MULTI_RELAY_MAX_RELAYS 16
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MULTI_RELAY_PINS
|
#ifndef MULTI_RELAY_PINS
|
||||||
@ -15,21 +20,29 @@
|
|||||||
#define ON true
|
#define ON true
|
||||||
#define OFF false
|
#define OFF false
|
||||||
|
|
||||||
|
#ifndef PCF8574_ADDRESS
|
||||||
|
#define PCF8574_ADDRESS 0x20 // some may start at 0x38
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This usermod handles multiple relay outputs.
|
* This usermod handles multiple relay outputs.
|
||||||
* These outputs complement built-in relay output in a way that the activation can be delayed.
|
* These outputs complement built-in relay output in a way that the activation can be delayed.
|
||||||
* They can also activate/deactivate in reverse logic independently.
|
* They can also activate/deactivate in reverse logic independently.
|
||||||
|
*
|
||||||
|
* Written and maintained by @blazoncek
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
typedef struct relay_t {
|
typedef struct relay_t {
|
||||||
int8_t pin;
|
int8_t pin;
|
||||||
bool active;
|
struct { // reduces memory footprint
|
||||||
bool mode;
|
bool active : 1;
|
||||||
bool state;
|
bool mode : 1;
|
||||||
bool external;
|
bool state : 1;
|
||||||
|
bool external : 1;
|
||||||
|
int8_t button : 4;
|
||||||
|
};
|
||||||
uint16_t delay;
|
uint16_t delay;
|
||||||
int8_t button;
|
|
||||||
} Relay;
|
} Relay;
|
||||||
|
|
||||||
|
|
||||||
@ -48,7 +61,8 @@ class MultiRelay : public Usermod {
|
|||||||
bool enabled = false; // needs to be configured (no default config)
|
bool enabled = false; // needs to be configured (no default config)
|
||||||
// status of initialisation
|
// status of initialisation
|
||||||
bool initDone = false;
|
bool initDone = false;
|
||||||
|
bool usePcf8574 = false;
|
||||||
|
uint8_t addrPcf8574 = PCF8574_ADDRESS;
|
||||||
bool HAautodiscovery = false;
|
bool HAautodiscovery = false;
|
||||||
|
|
||||||
uint16_t periodicBroadcastSec = 60;
|
uint16_t periodicBroadcastSec = 60;
|
||||||
@ -64,8 +78,127 @@ class MultiRelay : public Usermod {
|
|||||||
static const char _button[];
|
static const char _button[];
|
||||||
static const char _broadcast[];
|
static const char _broadcast[];
|
||||||
static const char _HAautodiscovery[];
|
static const char _HAautodiscovery[];
|
||||||
|
static const char _pcf8574[];
|
||||||
|
static const char _pcfAddress[];
|
||||||
|
|
||||||
void publishMqtt(int relay) {
|
void handleOffTimer();
|
||||||
|
void InitHtmlAPIHandle();
|
||||||
|
int getValue(String data, char separator, int index);
|
||||||
|
uint8_t getActiveRelayCount();
|
||||||
|
|
||||||
|
byte IOexpanderWrite(byte address, byte _data);
|
||||||
|
byte IOexpanderRead(int address);
|
||||||
|
|
||||||
|
void publishMqtt(int relay);
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
void publishHomeAssistantAutodiscovery();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
*/
|
||||||
|
MultiRelay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* desctructor
|
||||||
|
*/
|
||||||
|
~MultiRelay() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable the usermod
|
||||||
|
*/
|
||||||
|
inline void enable(bool enable) { enabled = enable; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get usermod enabled/disabled state
|
||||||
|
*/
|
||||||
|
inline bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
inline uint16_t getId() { return USERMOD_ID_MULTI_RELAY; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* switch relay on/off
|
||||||
|
*/
|
||||||
|
void switchRelay(uint8_t relay, bool mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toggle relay
|
||||||
|
*/
|
||||||
|
inline void toggleRelay(uint8_t relay) {
|
||||||
|
switchRelay(relay, !_relay[relay].state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
inline void connected() { InitHtmlAPIHandle(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// class implementetion
|
||||||
|
|
||||||
|
void MultiRelay::publishMqtt(int relay) {
|
||||||
#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
|
||||||
if (WLED_MQTT_CONNECTED){
|
if (WLED_MQTT_CONNECTED){
|
||||||
@ -79,7 +212,7 @@ class MultiRelay : public Usermod {
|
|||||||
/**
|
/**
|
||||||
* switch off the strip if the delay has elapsed
|
* switch off the strip if the delay has elapsed
|
||||||
*/
|
*/
|
||||||
void handleOffTimer() {
|
void MultiRelay::handleOffTimer() {
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
bool activeRelays = false;
|
bool activeRelays = false;
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
@ -101,7 +234,7 @@ class MultiRelay : public Usermod {
|
|||||||
* https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h
|
* https://github.com/gsieben/WLED/blob/master/usermods/GeoGab-Relays/usermod_GeoGab.h
|
||||||
*/
|
*/
|
||||||
#define GEOGABVERSION "0.1.3"
|
#define GEOGABVERSION "0.1.3"
|
||||||
void InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer
|
void MultiRelay::InitHtmlAPIHandle() { // https://github.com/me-no-dev/ESPAsyncWebServer
|
||||||
DEBUG_PRINTLN(F("Relays: Initialize HTML API"));
|
DEBUG_PRINTLN(F("Relays: Initialize HTML API"));
|
||||||
|
|
||||||
server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
server.on("/relays", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||||
@ -163,7 +296,7 @@ class MultiRelay : public Usermod {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int getValue(String data, char separator, int index) {
|
int MultiRelay::getValue(String data, char separator, int index) {
|
||||||
int found = 0;
|
int found = 0;
|
||||||
int strIndex[] = {0, -1};
|
int strIndex[] = {0, -1};
|
||||||
int maxIndex = data.length()-1;
|
int maxIndex = data.length()-1;
|
||||||
@ -178,11 +311,27 @@ class MultiRelay : public Usermod {
|
|||||||
return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1;
|
return found>index ? data.substring(strIndex[0], strIndex[1]).toInt() : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
//Write a byte to the IO expander
|
||||||
/**
|
byte MultiRelay::IOexpanderWrite(byte address, byte _data ) {
|
||||||
* constructor
|
Wire.beginTransmission(addrPcf8574 + address);
|
||||||
*/
|
Wire.write(_data);
|
||||||
MultiRelay() {
|
return Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read a byte from the IO expander
|
||||||
|
byte MultiRelay::IOexpanderRead(int address) {
|
||||||
|
byte _data = 0;
|
||||||
|
Wire.requestFrom(addrPcf8574 + address, 1);
|
||||||
|
if (Wire.available()) {
|
||||||
|
_data = Wire.read();
|
||||||
|
}
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public methods
|
||||||
|
|
||||||
|
MultiRelay::MultiRelay() {
|
||||||
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;
|
||||||
@ -194,44 +343,35 @@ class MultiRelay : public Usermod {
|
|||||||
_relay[i].button = -1;
|
_relay[i].button = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* desctructor
|
|
||||||
*/
|
|
||||||
~MultiRelay() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable/Disable the usermod
|
|
||||||
*/
|
|
||||||
inline void enable(bool enable) { enabled = enable; }
|
|
||||||
/**
|
|
||||||
* Get usermod enabled/disabled state
|
|
||||||
*/
|
|
||||||
inline bool isEnabled() { return enabled; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* switch relay on/off
|
* switch relay on/off
|
||||||
*/
|
*/
|
||||||
void switchRelay(uint8_t relay, bool mode) {
|
void MultiRelay::switchRelay(uint8_t relay, bool mode) {
|
||||||
if (relay>=MULTI_RELAY_MAX_RELAYS || _relay[relay].pin<0) return;
|
if (relay>=MULTI_RELAY_MAX_RELAYS || (_relay[relay].pin<0 && !usePcf8574)) return;
|
||||||
_relay[relay].state = mode;
|
_relay[relay].state = mode;
|
||||||
|
if (usePcf8574) {
|
||||||
|
byte expander = relay/8;
|
||||||
|
uint16_t state = 0;
|
||||||
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) state |= (uint16_t)_relay[i].state << i; // fill relay states for all pins
|
||||||
|
state = (mode ? !_relay[relay].mode : _relay[relay].mode) ? state | (1<<relay) : state & ~(1<<relay); // take into account invert mode
|
||||||
|
IOexpanderWrite(expander, state>>(8*expander));
|
||||||
|
DEBUG_PRINT(F("PCF8574 Writing to ")); DEBUG_PRINT(addrPcf8574 + expander); DEBUG_PRINT(F(" with data ")); DEBUG_PRINTLN(state>>(8*expander));
|
||||||
|
} else {
|
||||||
pinMode(_relay[relay].pin, OUTPUT);
|
pinMode(_relay[relay].pin, OUTPUT);
|
||||||
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
|
digitalWrite(_relay[relay].pin, mode ? !_relay[relay].mode : _relay[relay].mode);
|
||||||
|
}
|
||||||
publishMqtt(relay);
|
publishMqtt(relay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
uint8_t MultiRelay::getActiveRelayCount() {
|
||||||
* toggle relay
|
|
||||||
*/
|
|
||||||
inline void toggleRelay(uint8_t relay) {
|
|
||||||
switchRelay(relay, !_relay[relay].state);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t getActiveRelayCount() {
|
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
|
if (usePcf8574) return MULTI_RELAY_MAX_RELAYS; // we don't know how many there are
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Functions called by WLED
|
//Functions called by WLED
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_MQTT
|
#ifndef WLED_DISABLE_MQTT
|
||||||
@ -240,7 +380,7 @@ class MultiRelay : public Usermod {
|
|||||||
* topic only contains stripped topic (part after /wled/MAC)
|
* topic only contains stripped topic (part after /wled/MAC)
|
||||||
* topic should look like: /relay/X/command; where X is relay number, 0 based
|
* topic should look like: /relay/X/command; where X is relay number, 0 based
|
||||||
*/
|
*/
|
||||||
bool onMqttMessage(char* topic, char* payload) {
|
bool MultiRelay::onMqttMessage(char* topic, char* payload) {
|
||||||
if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) {
|
if (strlen(topic) > 8 && strncmp_P(topic, PSTR("/relay/"), 7) == 0 && strncmp_P(topic+8, PSTR("/command"), 8) == 0) {
|
||||||
uint8_t relay = strtoul(topic+7, NULL, 10);
|
uint8_t relay = strtoul(topic+7, NULL, 10);
|
||||||
if (relay<MULTI_RELAY_MAX_RELAYS) {
|
if (relay<MULTI_RELAY_MAX_RELAYS) {
|
||||||
@ -263,7 +403,7 @@ class MultiRelay : public Usermod {
|
|||||||
/**
|
/**
|
||||||
* subscribe to MQTT topic for controlling relays
|
* subscribe to MQTT topic for controlling relays
|
||||||
*/
|
*/
|
||||||
void onMqttConnect(bool sessionPresent) {
|
void MultiRelay::onMqttConnect(bool sessionPresent) {
|
||||||
//(re)subscribe to required topics
|
//(re)subscribe to required topics
|
||||||
char subuf[64];
|
char subuf[64];
|
||||||
if (mqttDeviceTopic[0] != 0) {
|
if (mqttDeviceTopic[0] != 0) {
|
||||||
@ -278,7 +418,7 @@ class MultiRelay : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void publishHomeAssistantAutodiscovery() {
|
void MultiRelay::publishHomeAssistantAutodiscovery() {
|
||||||
for (int i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
char uid[24], json_str[1024], buf[128];
|
char uid[24], json_str[1024], buf[128];
|
||||||
size_t payload_size;
|
size_t payload_size;
|
||||||
@ -322,8 +462,21 @@ class MultiRelay : public Usermod {
|
|||||||
* 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 MultiRelay::setup() {
|
||||||
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
|
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
|
// if we want PCF8574 expander I2C pins need to be valid
|
||||||
|
if (i2c_sda == i2c_scl && i2c_sda == -1) usePcf8574 = false;
|
||||||
|
|
||||||
|
if (usePcf8574) {
|
||||||
|
uint16_t state = 0;
|
||||||
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) state |= (uint16_t)(_relay[i].external ? (_relay[i].mode ? !_relay[i].state : _relay[i].state) : (_relay[i].mode ? !offMode : offMode)) << i; // fill relay states for all pins
|
||||||
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i += 8) {
|
||||||
|
byte expander = i/8;
|
||||||
|
IOexpanderWrite(expander, state>>(8*expander)); // init expander (set all outputs)
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("PCF8574(s) inited."));
|
||||||
|
} else {
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
if (_relay[i].pin<0) continue;
|
if (_relay[i].pin<0) continue;
|
||||||
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
|
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
|
||||||
@ -334,22 +487,15 @@ class MultiRelay : public Usermod {
|
|||||||
_relay[i].active = false;
|
_relay[i].active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_oldMode = offMode;
|
_oldMode = offMode;
|
||||||
initDone = true;
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* connected() is called every time the WiFi is (re)connected
|
|
||||||
* Use it to initialize network interfaces
|
|
||||||
*/
|
|
||||||
void connected() {
|
|
||||||
InitHtmlAPIHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
void loop() {
|
void MultiRelay::loop() {
|
||||||
yield();
|
yield();
|
||||||
if (!enabled || strip.isUpdating()) return;
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
@ -362,7 +508,7 @@ class MultiRelay : public Usermod {
|
|||||||
_oldMode = offMode;
|
_oldMode = offMode;
|
||||||
_switchTimerStart = millis();
|
_switchTimerStart = millis();
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
|
if ((_relay[i].pin>=0 || usePcf8574) && !_relay[i].external) _relay[i].active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +520,7 @@ class MultiRelay : 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 MultiRelay::handleButton(uint8_t b) {
|
||||||
yield();
|
yield();
|
||||||
if (!enabled
|
if (!enabled
|
||||||
|| buttonType[b] == BTN_TYPE_NONE
|
|| buttonType[b] == BTN_TYPE_NONE
|
||||||
@ -466,7 +612,7 @@ class MultiRelay : public Usermod {
|
|||||||
/**
|
/**
|
||||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||||
*/
|
*/
|
||||||
void addToJsonInfo(JsonObject &root) {
|
void MultiRelay::addToJsonInfo(JsonObject &root) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
JsonObject user = root["u"];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull())
|
if (user.isNull())
|
||||||
@ -478,7 +624,7 @@ class MultiRelay : public Usermod {
|
|||||||
|
|
||||||
String uiDomString;
|
String uiDomString;
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
if (_relay[i].pin<0 || !_relay[i].external) continue;
|
if ((_relay[i].pin<0 && !usePcf8574) || !_relay[i].external) continue;
|
||||||
uiDomString = F("Relay "); uiDomString += i;
|
uiDomString = F("Relay "); uiDomString += i;
|
||||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||||
|
|
||||||
@ -503,7 +649,7 @@ class MultiRelay : 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 MultiRelay::addToJsonState(JsonObject &root) {
|
||||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||||
JsonObject multiRelay = root[FPSTR(_name)];
|
JsonObject multiRelay = root[FPSTR(_name)];
|
||||||
if (multiRelay.isNull()) {
|
if (multiRelay.isNull()) {
|
||||||
@ -527,7 +673,7 @@ class MultiRelay : public Usermod {
|
|||||||
* 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 MultiRelay::readFromJsonState(JsonObject &root) {
|
||||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||||
JsonObject usermod = root[FPSTR(_name)];
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
if (!usermod.isNull()) {
|
if (!usermod.isNull()) {
|
||||||
@ -557,11 +703,14 @@ class MultiRelay : public Usermod {
|
|||||||
/**
|
/**
|
||||||
* provide the changeable values
|
* provide the changeable values
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject &root) {
|
void MultiRelay::addToConfig(JsonObject &root) {
|
||||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
top[FPSTR(_enabled)] = enabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
top[FPSTR(_pcf8574)] = usePcf8574;
|
||||||
|
top[FPSTR(_pcfAddress)] = addrPcf8574;
|
||||||
top[FPSTR(_broadcast)] = periodicBroadcastSec;
|
top[FPSTR(_broadcast)] = periodicBroadcastSec;
|
||||||
|
top[FPSTR(_HAautodiscovery)] = HAautodiscovery;
|
||||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||||
JsonObject relay = top.createNestedObject(parName);
|
JsonObject relay = top.createNestedObject(parName);
|
||||||
@ -571,17 +720,22 @@ class MultiRelay : public Usermod {
|
|||||||
relay[FPSTR(_external)] = _relay[i].external;
|
relay[FPSTR(_external)] = _relay[i].external;
|
||||||
relay[FPSTR(_button)] = _relay[i].button;
|
relay[FPSTR(_button)] = _relay[i].button;
|
||||||
}
|
}
|
||||||
top[FPSTR(_HAautodiscovery)] = HAautodiscovery;
|
|
||||||
DEBUG_PRINTLN(F("MultiRelay config saved."));
|
DEBUG_PRINTLN(F("MultiRelay config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiRelay::appendConfigData() {
|
||||||
|
oappend(SET_F("addInfo('MultiRelay:first-PCF8574',1,'<i>(not hex!)</i>','address');"));
|
||||||
|
oappend(SET_F("addInfo('MultiRelay:broadcast-sec',1,'(MQTT message)');"));
|
||||||
|
oappend(SET_F("addInfo('MultiRelay:relay-0:pin',1,'(use -1 for PCF8574)');"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* restore the changeable values
|
* restore the changeable values
|
||||||
* 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 MultiRelay::readFromConfig(JsonObject &root) {
|
||||||
int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
|
int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
|
||||||
|
|
||||||
JsonObject top = root[FPSTR(_name)];
|
JsonObject top = root[FPSTR(_name)];
|
||||||
@ -591,7 +745,13 @@ class MultiRelay : public Usermod {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//bool configComplete = !top.isNull();
|
||||||
|
//configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||||
enabled = top[FPSTR(_enabled)] | enabled;
|
enabled = top[FPSTR(_enabled)] | enabled;
|
||||||
|
usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574;
|
||||||
|
addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574;
|
||||||
|
// if I2C is not globally initialised just ignore
|
||||||
|
if (i2c_sda == i2c_scl && i2c_sda == -1) usePcf8574 = false;
|
||||||
periodicBroadcastSec = top[FPSTR(_broadcast)] | periodicBroadcastSec;
|
periodicBroadcastSec = top[FPSTR(_broadcast)] | periodicBroadcastSec;
|
||||||
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
|
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
|
||||||
HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery;
|
HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery;
|
||||||
@ -640,19 +800,9 @@ class MultiRelay : public Usermod {
|
|||||||
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(_HAautodiscovery)].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_MULTI_RELAY;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
const char MultiRelay::_name[] PROGMEM = "MultiRelay";
|
const char MultiRelay::_name[] PROGMEM = "MultiRelay";
|
||||||
const char MultiRelay::_enabled[] PROGMEM = "enabled";
|
const char MultiRelay::_enabled[] PROGMEM = "enabled";
|
||||||
@ -663,3 +813,5 @@ const char MultiRelay::_external[] PROGMEM = "external";
|
|||||||
const char MultiRelay::_button[] PROGMEM = "button";
|
const char MultiRelay::_button[] PROGMEM = "button";
|
||||||
const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
|
const char MultiRelay::_broadcast[] PROGMEM = "broadcast-sec";
|
||||||
const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
|
const char MultiRelay::_HAautodiscovery[] PROGMEM = "HA-autodiscovery";
|
||||||
|
const char MultiRelay::_pcf8574[] PROGMEM = "use-PCF8574";
|
||||||
|
const char MultiRelay::_pcfAddress[] PROGMEM = "first-PCF8574";
|
||||||
|
@ -199,12 +199,12 @@ void Segment::setUpLeds() {
|
|||||||
#else
|
#else
|
||||||
leds = &Segment::_globalLeds[start];
|
leds = &Segment::_globalLeds[start];
|
||||||
#endif
|
#endif
|
||||||
else if (!leds) {
|
else if (leds == nullptr && length() > 0) { //softhack007 quickfix - avoid malloc(0) which is undefined behaviour (should not happen, but i've seen it)
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
|
//#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
|
||||||
if (psramFound())
|
//if (psramFound())
|
||||||
leds = (CRGB*)ps_malloc(sizeof(CRGB)*length());
|
// leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards
|
||||||
else
|
//else
|
||||||
#endif
|
//#endif
|
||||||
leds = (CRGB*)malloc(sizeof(CRGB)*length());
|
leds = (CRGB*)malloc(sizeof(CRGB)*length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1069,11 +1069,12 @@ void WS2812FX::finalizeInit(void)
|
|||||||
}
|
}
|
||||||
if (useLedsArray) {
|
if (useLedsArray) {
|
||||||
size_t arrSize = sizeof(CRGB) * getLengthTotal();
|
size_t arrSize = sizeof(CRGB) * getLengthTotal();
|
||||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
|
// softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards (see setUpLeds())
|
||||||
if (psramFound())
|
//#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
|
||||||
Segment::_globalLeds = (CRGB*) ps_malloc(arrSize);
|
//if (psramFound())
|
||||||
else
|
// Segment::_globalLeds = (CRGB*) ps_malloc(arrSize);
|
||||||
#endif
|
//else
|
||||||
|
//#endif
|
||||||
Segment::_globalLeds = (CRGB*) malloc(arrSize);
|
Segment::_globalLeds = (CRGB*) malloc(arrSize);
|
||||||
memset(Segment::_globalLeds, 0, arrSize);
|
memset(Segment::_globalLeds, 0, arrSize);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ void handleDDPPacket(e131_packet_t* p) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ddpChannelsPerLed = (p->dataType == DDP_TYPE_RGBW32) ? 4 : 3; // data type 0x1A is RGBW (type 3, 8 bit/channel)
|
uint8_t ddpChannelsPerLed = (p->dataType & 0b00111000 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel)
|
||||||
|
|
||||||
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
|
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
|
||||||
start += DMXAddress / ddpChannelsPerLed;
|
start += DMXAddress / ddpChannelsPerLed;
|
||||||
|
3956
wled00/html_ui.h
3956
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,7 @@ void onMqttConnect(bool sessionPresent)
|
|||||||
|
|
||||||
|
|
||||||
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||||
|
static char *payloadStr;
|
||||||
|
|
||||||
DEBUG_PRINT(F("MQTT msg: "));
|
DEBUG_PRINT(F("MQTT msg: "));
|
||||||
DEBUG_PRINTLN(topic);
|
DEBUG_PRINTLN(topic);
|
||||||
@ -62,11 +63,22 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
DEBUG_PRINTLN(F("no payload -> leave"));
|
DEBUG_PRINTLN(F("no payload -> leave"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//make a copy of the payload to 0-terminate it
|
|
||||||
char* payloadStr = new char[len+1];
|
if (index == 0) { // start (1st partial packet or the only packet)
|
||||||
if (payloadStr == nullptr) return; //no mem
|
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer
|
||||||
strncpy(payloadStr, payload, len);
|
payloadStr = new char[total+1]; // allocate new buffer
|
||||||
payloadStr[len] = '\0';
|
}
|
||||||
|
if (payloadStr == nullptr) return; // buffer not allocated
|
||||||
|
|
||||||
|
// copy (partial) packet to buffer and 0-terminate it if it is last packet
|
||||||
|
char* buff = payloadStr + index;
|
||||||
|
memcpy(buff, payload, len);
|
||||||
|
if (index + len >= total) { // at end
|
||||||
|
payloadStr[total] = '\0'; // terminate c style string
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("Partial packet received."));
|
||||||
|
return; // process next packet
|
||||||
|
}
|
||||||
DEBUG_PRINTLN(payloadStr);
|
DEBUG_PRINTLN(payloadStr);
|
||||||
|
|
||||||
size_t topicPrefixLen = strlen(mqttDeviceTopic);
|
size_t topicPrefixLen = strlen(mqttDeviceTopic);
|
||||||
@ -80,6 +92,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
|
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
|
||||||
usermods.onMqttMessage(topic, payloadStr);
|
usermods.onMqttMessage(topic, payloadStr);
|
||||||
delete[] payloadStr;
|
delete[] payloadStr;
|
||||||
|
payloadStr = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,16 +100,20 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
//Prefix is stripped from the topic at this point
|
//Prefix is stripped from the topic at this point
|
||||||
|
|
||||||
if (strcmp_P(topic, PSTR("/col")) == 0) {
|
if (strcmp_P(topic, PSTR("/col")) == 0) {
|
||||||
colorFromDecOrHexString(col, (char*)payloadStr);
|
colorFromDecOrHexString(col, payloadStr);
|
||||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||||
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
|
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
|
||||||
if (!requestJSONBufferLock(15)) { delete[] payloadStr; return; }
|
if (!requestJSONBufferLock(15)) {
|
||||||
if (payload[0] == '{') { //JSON API
|
delete[] payloadStr;
|
||||||
|
payloadStr = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payloadStr[0] == '{') { //JSON API
|
||||||
deserializeJson(doc, payloadStr);
|
deserializeJson(doc, payloadStr);
|
||||||
deserializeState(doc.as<JsonObject>());
|
deserializeState(doc.as<JsonObject>());
|
||||||
} else { //HTTP API
|
} else { //HTTP API
|
||||||
String apireq = "win"; apireq += '&'; // reduce flash string usage
|
String apireq = "win"; apireq += '&'; // reduce flash string usage
|
||||||
apireq += (char*)payloadStr;
|
apireq += payloadStr;
|
||||||
handleSet(nullptr, apireq);
|
handleSet(nullptr, apireq);
|
||||||
}
|
}
|
||||||
releaseJSONBufferLock();
|
releaseJSONBufferLock();
|
||||||
@ -108,6 +125,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
|
|||||||
parseMQTTBriPayload(payloadStr);
|
parseMQTTBriPayload(payloadStr);
|
||||||
}
|
}
|
||||||
delete[] payloadStr;
|
delete[] payloadStr;
|
||||||
|
payloadStr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,8 +53,8 @@ typedef struct ip_addr ip4_addr_t;
|
|||||||
#define DDP_PUSH_FLAG 0x01
|
#define DDP_PUSH_FLAG 0x01
|
||||||
#define DDP_TIMECODE_FLAG 0x10
|
#define DDP_TIMECODE_FLAG 0x10
|
||||||
|
|
||||||
#define DDP_TYPE_RGB24 0x0A
|
#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
|
||||||
#define DDP_TYPE_RGBW32 0x1A
|
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)
|
||||||
|
|
||||||
#define ARTNET_OPCODE_OPDMX 0x5000
|
#define ARTNET_OPCODE_OPDMX 0x5000
|
||||||
#define ARTNET_OPCODE_OPPOLL 0x2000
|
#define ARTNET_OPCODE_OPPOLL 0x2000
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* @title Espalexa library
|
* @title Espalexa library
|
||||||
* @version 2.7.0
|
* @version 2.7.1
|
||||||
* @author Christian Schwinne
|
* @author Christian Schwinne
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @contributors d-999
|
* @contributors d-999
|
||||||
@ -50,7 +50,7 @@
|
|||||||
#include "../network/Network.h"
|
#include "../network/Network.h"
|
||||||
|
|
||||||
#ifdef ESPALEXA_DEBUG
|
#ifdef ESPALEXA_DEBUG
|
||||||
#pragma message "Espalexa 2.7.0 debug mode"
|
#pragma message "Espalexa 2.7.1 debug mode"
|
||||||
#define EA_DEBUG(x) Serial.print (x)
|
#define EA_DEBUG(x) Serial.print (x)
|
||||||
#define EA_DEBUGLN(x) Serial.println (x)
|
#define EA_DEBUGLN(x) Serial.println (x)
|
||||||
#else
|
#else
|
||||||
@ -142,7 +142,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
//device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
|
//device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
|
||||||
void deviceJsonString(EspalexaDevice* dev, char* buf)
|
void deviceJsonString(EspalexaDevice* dev, char* buf, size_t maxBuf) // softhack007 "size" parameter added, to avoid buffer overrun
|
||||||
{
|
{
|
||||||
char buf_lightid[27];
|
char buf_lightid[27];
|
||||||
encodeLightId(dev->getId() + 1, buf_lightid);
|
encodeLightId(dev->getId() + 1, buf_lightid);
|
||||||
@ -153,19 +153,19 @@ private:
|
|||||||
//TODO: %f is not working for some reason on ESP8266 in v0.11.0 (was fine in 0.10.2). Need to investigate
|
//TODO: %f is not working for some reason on ESP8266 in v0.11.0 (was fine in 0.10.2). Need to investigate
|
||||||
//sprintf_P(buf_col,PSTR(",\"hue\":%u,\"sat\":%u,\"effect\":\"none\",\"xy\":[%f,%f]")
|
//sprintf_P(buf_col,PSTR(",\"hue\":%u,\"sat\":%u,\"effect\":\"none\",\"xy\":[%f,%f]")
|
||||||
// ,dev->getHue(), dev->getSat(), dev->getX(), dev->getY());
|
// ,dev->getHue(), dev->getSat(), dev->getX(), dev->getY());
|
||||||
sprintf_P(buf_col,PSTR(",\"hue\":%u,\"sat\":%u,\"effect\":\"none\",\"xy\":[%s,%s]"),dev->getHue(), dev->getSat(),
|
snprintf_P(buf_col, sizeof(buf_col), PSTR(",\"hue\":%u,\"sat\":%u,\"effect\":\"none\",\"xy\":[%s,%s]"),dev->getHue(), dev->getSat(),
|
||||||
((String)dev->getX()).c_str(), ((String)dev->getY()).c_str());
|
((String)dev->getX()).c_str(), ((String)dev->getY()).c_str());
|
||||||
|
|
||||||
char buf_ct[16] = "";
|
char buf_ct[16] = "";
|
||||||
//white spectrum support
|
//white spectrum support
|
||||||
if (static_cast<uint8_t>(dev->getType()) > 1 && dev->getType() != EspalexaDeviceType::color)
|
if (static_cast<uint8_t>(dev->getType()) > 1 && dev->getType() != EspalexaDeviceType::color)
|
||||||
sprintf(buf_ct, ",\"ct\":%u", dev->getCt());
|
snprintf(buf_ct, sizeof(buf_ct), ",\"ct\":%u", dev->getCt());
|
||||||
|
|
||||||
char buf_cm[20] = "";
|
char buf_cm[20] = "";
|
||||||
if (static_cast<uint8_t>(dev->getType()) > 1)
|
if (static_cast<uint8_t>(dev->getType()) > 1)
|
||||||
sprintf(buf_cm,PSTR("\",\"colormode\":\"%s"), modeString(dev->getColorMode()));
|
snprintf(buf_cm, sizeof(buf_cm), PSTR("\",\"colormode\":\"%s"), modeString(dev->getColorMode()));
|
||||||
|
|
||||||
sprintf_P(buf, PSTR("{\"state\":{\"on\":%s,\"bri\":%u%s%s,\"alert\":\"none%s\",\"mode\":\"homeautomation\",\"reachable\":true},"
|
snprintf_P(buf, maxBuf, PSTR("{\"state\":{\"on\":%s,\"bri\":%u%s%s,\"alert\":\"none%s\",\"mode\":\"homeautomation\",\"reachable\":true},"
|
||||||
"\"type\":\"%s\",\"name\":\"%s\",\"modelid\":\"%s\",\"manufacturername\":\"Philips\",\"productname\":\"E%u"
|
"\"type\":\"%s\",\"name\":\"%s\",\"modelid\":\"%s\",\"manufacturername\":\"Philips\",\"productname\":\"E%u"
|
||||||
"\",\"uniqueid\":\"%s\",\"swversion\":\"espalexa-2.7.0\"}")
|
"\",\"uniqueid\":\"%s\",\"swversion\":\"espalexa-2.7.0\"}")
|
||||||
|
|
||||||
@ -219,10 +219,10 @@ private:
|
|||||||
EA_DEBUGLN("# Responding to description.xml ... #\n");
|
EA_DEBUGLN("# Responding to description.xml ... #\n");
|
||||||
IPAddress localIP = Network.localIP();
|
IPAddress localIP = Network.localIP();
|
||||||
char s[16];
|
char s[16];
|
||||||
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
|
snprintf(s, sizeof(s), "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
|
|
||||||
sprintf_P(buf,PSTR("<?xml version=\"1.0\" ?>"
|
snprintf_P(buf, sizeof(buf), PSTR("<?xml version=\"1.0\" ?>"
|
||||||
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
||||||
"<specVersion><major>1</major><minor>0</minor></specVersion>"
|
"<specVersion><major>1</major><minor>0</minor></specVersion>"
|
||||||
"<URLBase>http://%s:80/</URLBase>"
|
"<URLBase>http://%s:80/</URLBase>"
|
||||||
@ -297,7 +297,7 @@ private:
|
|||||||
|
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
|
|
||||||
sprintf_P(buf,PSTR("HTTP/1.1 200 OK\r\n"
|
snprintf_P(buf, sizeof(buf), PSTR("HTTP/1.1 200 OK\r\n"
|
||||||
"EXT:\r\n"
|
"EXT:\r\n"
|
||||||
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
|
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
|
||||||
"LOCATION: http://%s:80/description.xml\r\n"
|
"LOCATION: http://%s:80/description.xml\r\n"
|
||||||
@ -493,7 +493,7 @@ public:
|
|||||||
unsigned idx = decodeLightKey(devId);
|
unsigned idx = decodeLightKey(devId);
|
||||||
EA_DEBUGLN(idx);
|
EA_DEBUGLN(idx);
|
||||||
char buf[50];
|
char buf[50];
|
||||||
sprintf_P(buf,PSTR("[{\"success\":{\"/lights/%u/state/\": true}}]"),devId);
|
snprintf_P(buf,sizeof(buf),PSTR("[{\"success\":{\"/lights/%u/state/\": true}}]"),devId);
|
||||||
server->send(200, "application/json", buf);
|
server->send(200, "application/json", buf);
|
||||||
if (idx >= currentDeviceCount) return true; //return if invalid ID
|
if (idx >= currentDeviceCount) return true; //return if invalid ID
|
||||||
EspalexaDevice* dev = devices[idx];
|
EspalexaDevice* dev = devices[idx];
|
||||||
@ -571,7 +571,7 @@ public:
|
|||||||
jsonTemp += ':';
|
jsonTemp += ':';
|
||||||
|
|
||||||
char buf[512];
|
char buf[512];
|
||||||
deviceJsonString(devices[i], buf);
|
deviceJsonString(devices[i], buf, sizeof(buf)-1);
|
||||||
jsonTemp += buf;
|
jsonTemp += buf;
|
||||||
if (i < currentDeviceCount-1) jsonTemp += ',';
|
if (i < currentDeviceCount-1) jsonTemp += ',';
|
||||||
}
|
}
|
||||||
@ -588,7 +588,7 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
char buf[512];
|
char buf[512];
|
||||||
deviceJsonString(devices[idx], buf);
|
deviceJsonString(devices[idx], buf, sizeof(buf)-1);
|
||||||
server->send(200, "application/json", buf);
|
server->send(200, "application/json", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
#include "EspalexaDevice.h"
|
#include "EspalexaDevice.h"
|
||||||
|
|
||||||
|
// debug macros
|
||||||
|
#ifdef ESPALEXA_DEBUG
|
||||||
|
#define EA_DEBUG(x) Serial.print (x)
|
||||||
|
#define EA_DEBUGLN(x) Serial.println (x)
|
||||||
|
#else
|
||||||
|
#define EA_DEBUG(x)
|
||||||
|
#define EA_DEBUGLN(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
EspalexaDevice::EspalexaDevice(){}
|
EspalexaDevice::EspalexaDevice(){}
|
||||||
|
|
||||||
EspalexaDevice::EspalexaDevice(String deviceName, BrightnessCallbackFunction gnCallback, uint8_t initialValue) { //constructor for dimmable device
|
EspalexaDevice::EspalexaDevice(String deviceName, BrightnessCallbackFunction gnCallback, uint8_t initialValue) { //constructor for dimmable device
|
||||||
@ -124,18 +133,21 @@ uint32_t EspalexaDevice::getRGB()
|
|||||||
{
|
{
|
||||||
//TODO tweak a bit to match hue lamp characteristics
|
//TODO tweak a bit to match hue lamp characteristics
|
||||||
//based on https://gist.github.com/paulkaplan/5184275
|
//based on https://gist.github.com/paulkaplan/5184275
|
||||||
float temp = 10000/ _ct; //kelvins = 1,000,000/mired (and that /100)
|
float temp = (_ct != 0) ? (10000/ _ct) : 2; //kelvins = 1,000,000/mired (and that /100) softhack007: avoid division by zero - using "2" as substitute
|
||||||
float r, g, b;
|
float r, g, b;
|
||||||
|
|
||||||
|
#ifdef ESPALEXA_DEBUG
|
||||||
|
if (_ct == 0) {EA_DEBUGLN(F("EspalexaDevice::getRGB() Warning: ct = 0!"));}
|
||||||
|
#endif
|
||||||
if (temp <= 66) {
|
if (temp <= 66) {
|
||||||
r = 255;
|
r = 255;
|
||||||
g = temp;
|
g = temp;
|
||||||
g = 99.470802 * log(g) - 161.119568;
|
g = 99.470802 * logf(g) - 161.119568;
|
||||||
if (temp <= 19) {
|
if (temp <= 19) {
|
||||||
b = 0;
|
b = 0;
|
||||||
} else {
|
} else {
|
||||||
b = temp-10;
|
b = temp-10;
|
||||||
b = 138.517731 * log(b) - 305.044793;
|
b = 138.517731 * logf(b) - 305.044793;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r = temp - 60;
|
r = temp - 60;
|
||||||
@ -192,9 +204,9 @@ uint32_t EspalexaDevice::getRGB()
|
|||||||
b = 1.0f;
|
b = 1.0f;
|
||||||
}
|
}
|
||||||
// Apply gamma correction
|
// Apply gamma correction
|
||||||
r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
|
r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * powf(r, (1.0f / 2.4f)) - 0.055f;
|
||||||
g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
|
g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * powf(g, (1.0f / 2.4f)) - 0.055f;
|
||||||
b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
|
b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * powf(b, (1.0f / 2.4f)) - 0.055f;
|
||||||
|
|
||||||
if (r > b && r > g) {
|
if (r > b && r > g) {
|
||||||
// red is biggest
|
// red is biggest
|
||||||
@ -328,8 +340,10 @@ void EspalexaDevice::setColor(uint8_t r, uint8_t g, uint8_t b)
|
|||||||
float X = r * 0.664511f + g * 0.154324f + b * 0.162028f;
|
float X = r * 0.664511f + g * 0.154324f + b * 0.162028f;
|
||||||
float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f;
|
float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f;
|
||||||
float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f;
|
float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f;
|
||||||
|
if ((r+g+b) > 0) { // softhack007: avoid division by zero
|
||||||
_x = X / (X + Y + Z);
|
_x = X / (X + Y + Z);
|
||||||
_y = Y / (X + Y + Z);
|
_y = Y / (X + Y + Z);
|
||||||
|
} else { _x = _y = 0.5f;} // softhack007: use default values in case of "black"
|
||||||
_rgb = ((r << 16) | (g << 8) | b);
|
_rgb = ((r << 16) | (g << 8) | b);
|
||||||
_mode = EspalexaColorMode::xy;
|
_mode = EspalexaColorMode::xy;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,10 @@ struct PSRAM_Allocator {
|
|||||||
if (psramFound()) return ps_malloc(size); // use PSRAM if it exists
|
if (psramFound()) return ps_malloc(size); // use PSRAM if it exists
|
||||||
else return malloc(size); // fallback
|
else return malloc(size); // fallback
|
||||||
}
|
}
|
||||||
|
void* reallocate(void* ptr, size_t new_size) {
|
||||||
|
if (psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists
|
||||||
|
else return realloc(ptr, new_size); // fallback
|
||||||
|
}
|
||||||
void deallocate(void* pointer) {
|
void deallocate(void* pointer) {
|
||||||
free(pointer);
|
free(pointer);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user