Add SetOption68 to enable multi-channel PWM instead of a single light (#6134)

This commit is contained in:
Stephan Hadinger 2019-08-06 10:57:50 +02:00
parent d6e475e73a
commit 81ca44dba2
8 changed files with 376 additions and 124 deletions

View File

@ -9,6 +9,7 @@
* Add define USE_ENERGY_POWER_LIMIT to disable Energy Power Limit detection while Energy Margin detection is active
* Add allow repeat/longpress for IRSend raw, introduced IRSend<r> option (#6074)
* Change Store AWS IoT Private Key and Certificate in SPI Flash avoiding device-specific compilations
* Add SetOption68 to enable multi-channel PWM instead of a single light (#6134)
*
* 6.6.0.2 20190714
* Change commands Var and Mem to show all parameters when no index is given (#6107)

View File

@ -81,7 +81,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t tuya_show_dimmer : 1; // bit 15 (v6.5.0.15) - SetOption65 - Enable or Disable Dimmer slider control
uint32_t tuya_dimmer_range_255 : 1; // bit 16 (v6.6.0.1) - SetOption66 - Enable or Disable Dimmer range 255 slider control
uint32_t buzzer_enable : 1; // bit 17 (v6.6.0.1) - SetOption67 - Enable buzzer when available
uint32_t spare18 : 1;
uint32_t pmw_multi_channels : 1; // bit 18 (v6.6.0.3) - SetOption68 - Enable multi-channels PWM insteas of Color PWM
uint32_t spare19 : 1;
uint32_t spare20 : 1;
uint32_t spare21 : 1;

View File

@ -59,6 +59,7 @@ const uint8_t MAX_COUNTERS = 4; // Max number of counter sensors
const uint8_t MAX_TIMERS = 16; // Max number of Timers
const uint8_t MAX_PULSETIMERS = 8; // Max number of supported pulse timers
const uint8_t MAX_FRIENDLYNAMES = 4; // Max number of Friendly names
const uint8_t MAX_HUE_DEVICES = 15; // Max number of Philips Hue device per emulation
const uint8_t MAX_DOMOTICZ_IDX = 4; // Max number of Domoticz device, key and switch indices
const uint8_t MAX_DOMOTICZ_SNS_IDX = 12; // Max number of Domoticz sensors indices
const uint8_t MAX_KNX_GA = 10; // Max number of KNX Group Addresses to read that can be set
@ -264,7 +265,7 @@ const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48,
\*********************************************************************************************/
extern uint8_t light_device; // Light device number
extern uint8_t light_power; // Light power
extern power_t light_power; // Light power
extern uint8_t rotary_changed; // Rotary switch changed
#endif // _SONOFF_H_

View File

@ -1451,6 +1451,13 @@ void GpioInit(void)
light_type |= LT_SM16716;
}
#endif // USE_SM16716
// post-process for lights
if (Settings.flag3.pmw_multi_channels) {
uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7);
if (0 == pwm_channels) pwm_channels = 1;
devices_present += pwm_channels - 1; // add the pwm channels controls at the end
}
#endif // USE_LIGHT
if (!light_type) {
for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only

View File

@ -585,6 +585,9 @@ void CmndSetoption(void)
if (10 == pindex) { // SetOption60 enable or disable traditional sleep
WiFiSetSleepMode(); // Update WiFi sleep mode accordingly
}
if (18 == pindex) { // SetOption68 for multi-channel PWM, requires a reboot
restart_flag = 2;
}
}
}
else { // SetOption32 .. 49

View File

@ -46,6 +46,8 @@ uint8_t *efm8bb1_update = nullptr;
enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1 };
static const char * HEADER_KEYS[] = { "User-Agent", };
const char HTTP_HEAD[] PROGMEM =
"<!DOCTYPE html><html lang=\"" D_HTML_LANGUAGE "\" class=\"\">"
"<head>"
@ -543,6 +545,11 @@ void StartWebserver(int type, IPAddress ipweb)
#endif // Not FIRMWARE_MINIMAL
}
reset_web_log_flag = false;
// Collect User-Agent for Alexa Hue Emulation
// This is used in xdrv_20_hue.ino in function findEchoGeneration()
WebServer->collectHeaders(HEADER_KEYS, sizeof(HEADER_KEYS)/sizeof(char*));
WebServer->begin(); // Web server start
}
if (webserver_state != type) {

View File

@ -126,7 +126,7 @@
\*********************************************************************************************/
#define XDRV_04 4
//#define DEBUG_LIGHT
// #define DEBUG_LIGHT
const uint8_t LIGHT_COLOR_SIZE = 25; // Char array scolor size
const uint8_t WS2812_SCHEMES = 7; // Number of additional WS2812 schemes supported by xdrv_ws2812.ino
@ -241,10 +241,11 @@ uint8_t light_new_color[LST_MAX];
uint8_t light_last_color[LST_MAX];
uint8_t light_color_remap[LST_MAX];
power_t light_power = 0; // Power<x> for each channel if SetOption68, or boolean if single light
uint8_t light_wheel = 0;
uint8_t light_subtype = 0; // LST_ subtype
bool light_pwm_multi_channels = false; // SetOption68, treat each PWM channel as an independant dimmer
uint8_t light_device = 0;
uint8_t light_power = 0;
uint8_t light_old_power = 1;
uint8_t light_update = 1;
uint8_t light_wakeup_active = 0;
@ -772,6 +773,7 @@ private:
// are RGB and CT linked, i.e. if we set CT then RGB channels are off
bool _ct_rgb_linked = true;
bool _pwm_multi_channels = false; // treat each channel as independant dimmer
public:
LightControllerClass(LightStateClass& state) {
@ -784,7 +786,11 @@ public:
inline bool setCTRGBLinked(bool ct_rgb_linked) {
bool prev = _ct_rgb_linked;
_ct_rgb_linked = ct_rgb_linked;
if (_pwm_multi_channels) {
_ct_rgb_linked = false; // force to false if _pwm_multi_channels is set
} else {
_ct_rgb_linked = ct_rgb_linked;
}
return prev;
}
@ -792,6 +798,17 @@ public:
return _ct_rgb_linked;
}
inline bool setPWMMultiChannel(bool pwm_multi_channels) {
bool prev = _pwm_multi_channels;
_pwm_multi_channels = pwm_multi_channels;
if (pwm_multi_channels) setCTRGBLinked(false); // if pwm multi channel, then unlink RGB and CT
return prev;
}
inline bool isPWMMultiChannel(void) {
return _pwm_multi_channels;
}
#ifdef DEBUG_LIGHT
void debugLogs() {
uint8_t r,g,b,c,w;
@ -815,12 +832,15 @@ public:
// first try setting CW, if zero, it select RGB mode
_state->setCW(Settings.light_color[3], Settings.light_color[4], true);
_state->setRGB(Settings.light_color[0], Settings.light_color[1], Settings.light_color[2]);
// We apply dimmer in priority to RGB
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
_state->setBriRGB(bri);
} else {
_state->setBriCT(bri);
if (!_pwm_multi_channels) {
// only if non-multi channel
// We apply dimmer in priority to RGB
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
_state->setBriRGB(bri);
} else {
_state->setBriCT(bri);
}
}
}
@ -864,6 +884,15 @@ public:
void calcLevels() {
uint8_t r,g,b,c,w,briRGB,briCT;
_state->getActualRGBCW(&r,&g,&b,&c,&w);
if (_pwm_multi_channels) { // if PWM multi channel, no more transformation required
light_current_color[0] = r;
light_current_color[1] = g;
light_current_color[2] = b;
light_current_color[3] = c;
light_current_color[4] = w;
return;
}
briRGB = _state->getBriRGB();
briCT = _state->getBriCT();
@ -907,20 +936,28 @@ public:
// save the current light state to Settings.
void saveSettings() {
uint8_t cm = _state->getColorMode();
if (light_pwm_multi_channels) {
// simply save each channel
_state->getActualRGBCW(&Settings.light_color[0], &Settings.light_color[1],
&Settings.light_color[2], &Settings.light_color[3],
&Settings.light_color[4]);
Settings.light_dimmer = 100; // arbitrary value, unused in this mode
} else {
uint8_t cm = _state->getColorMode();
memset(&Settings.light_color[0], 0, sizeof(Settings.light_color));
if (LCM_RGB & cm) { // can be either LCM_RGB or LCM_BOTH
_state->getRGB(&Settings.light_color[0], &Settings.light_color[1], &Settings.light_color[2]);
Settings.light_dimmer = _state->BriToDimmer(_state->getBriRGB());
// anyways we always store RGB with BrightnessRGB
if (LCM_BOTH == cm) {
// then store at actual brightness CW/WW if dual mode
_state->getActualRGBCW(nullptr, nullptr, nullptr, &Settings.light_color[3], &Settings.light_color[4]);
memset(&Settings.light_color[0], 0, sizeof(Settings.light_color));
if (LCM_RGB & cm) { // can be either LCM_RGB or LCM_BOTH
_state->getRGB(&Settings.light_color[0], &Settings.light_color[1], &Settings.light_color[2]);
Settings.light_dimmer = _state->BriToDimmer(_state->getBriRGB());
// anyways we always store RGB with BrightnessRGB
if (LCM_BOTH == cm) {
// then store at actual brightness CW/WW if dual mode
_state->getActualRGBCW(nullptr, nullptr, nullptr, &Settings.light_color[3], &Settings.light_color[4]);
}
} else if (LCM_CT == cm) { // cm can only be LCM_CT
_state->getCW(&Settings.light_color[3], &Settings.light_color[4]);
Settings.light_dimmer = _state->BriToDimmer(_state->getBriCT());
}
} else if (LCM_CT == cm) { // cm can only be LCM_CT
_state->getCW(&Settings.light_color[3], &Settings.light_color[4]);
Settings.light_dimmer = _state->BriToDimmer(_state->getBriCT());
}
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::saveSettings Settings.light_color (%d %d %d %d %d - %d)",
@ -1322,6 +1359,7 @@ void LightInit(void)
light_device = devices_present;
light_subtype = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7); // Always 0 - LST_MAX (5)
light_pwm_multi_channels = Settings.flag3.pmw_multi_channels;
#if defined(USE_WS2812) && (USE_WS2812_CTYPE > NEO_3LED)
if (LT_WS2812 == light_type) {
@ -1329,6 +1367,16 @@ void LightInit(void)
}
#endif
if ((LST_SINGLE < light_subtype) && light_pwm_multi_channels) {
// we treat each PWM channel as an independant one, hence we switch to
light_controller.setPWMMultiChannel(true);
light_device = devices_present - light_subtype + 1; // adjust if we also have relays
}
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightInit light_pwm_multi_channels=%d light_subtype=%d light_device=%d devices_present=%d",
light_pwm_multi_channels, light_subtype, light_device, devices_present);
#endif
light_controller.setSubType(light_subtype);
light_controller.loadSettings();
@ -1456,6 +1504,32 @@ void LightSetDimmer(uint8_t dimmer) {
light_controller.changeDimmer(dimmer);
}
// If SetOption68 is set, get the brightness for a specific device
uint8_t LightGetBri(uint8_t device) {
uint8_t bri = 254; // default value if relay
if (light_pwm_multi_channels) {
if ((device >= light_device) && (device < light_device + LST_MAX) && (device <= devices_present)) {
bri = light_current_color[device - light_device];
}
} else if (device == light_device) {
bri = light_state.getBri();
}
return bri;
}
// If SetOption68 is set, get the brightness for a specific device
void LightSetBri(uint8_t device, uint8_t bri) {
if (light_pwm_multi_channels) {
if ((device >= light_device) && (device < light_device + LST_MAX) && (device <= devices_present)) {
light_current_color[device - light_device] = bri;
light_controller.changeChannels(light_current_color);
}
} else if (device == light_device) {
light_controller.changeBri(bri);
}
}
void LightSetColorTemp(uint16_t ct)
{
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
@ -1568,19 +1642,52 @@ void LightState(uint8_t append)
void LightPreparePower(void)
{
if (light_state.getBri() && !(light_power)) {
if (!Settings.flag.not_power_linked) {
ExecuteCommandPower(light_device, POWER_ON_NO_STATE, SRC_LIGHT);
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d light_power=%d", power, light_power);
#endif
// If multi-channels, then we only switch off channels with a value of zero
if (light_pwm_multi_channels) {
// for (uint32_t i = 0; i < light_subtype; i++) {
// // if channel is non-null, channel is supposed to be on, but it is off, do Power On
// if ((light_current_color[i]) && (bitRead(light_power, i)) && (0 == bitRead(power, i + light_device - 1))) {
// ExecuteCommandPower(light_device + i, POWER_ON_NO_STATE, SRC_LIGHT);
// //bitSet(Settings.power, i + light_device - 1);
// #ifdef DEBUG_LIGHT
// AddLog_P2(LOG_LEVEL_DEBUG, "ExecuteCommandPower ON device=%d", light_device + i);
// #endif
// }
// // if channel is zero and channel is on, set it off
// if ((0 == light_current_color[i]) && bitRead(power, i + light_device - 1)) {
// ExecuteCommandPower(light_device + i, POWER_OFF_NO_STATE, SRC_LIGHT);
// //bitClear(Settings.power, i + light_device - 1);
// #ifdef DEBUG_LIGHT
// AddLog_P2(LOG_LEVEL_DEBUG, "ExecuteCommandPower OFF device=%d", light_device + i);
// #endif
// }
// #ifdef USE_DOMOTICZ
// DomoticzUpdatePowerState(light_device + i);
// #endif // USE_DOMOTICZ
// }
} else {
if (light_state.getBri() && !(light_power)) {
if (!Settings.flag.not_power_linked) {
ExecuteCommandPower(light_device, POWER_ON_NO_STATE, SRC_LIGHT);
}
}
else if (!light_state.getBri() && light_power) {
ExecuteCommandPower(light_device, POWER_OFF_NO_STATE, SRC_LIGHT);
}
}
else if (!light_state.getBri() && light_power) {
ExecuteCommandPower(light_device, POWER_OFF_NO_STATE, SRC_LIGHT);
}
#ifdef USE_DOMOTICZ
DomoticzUpdatePowerState(light_device);
DomoticzUpdatePowerState(light_device);
#endif // USE_DOMOTICZ
}
if (Settings.flag3.hass_tele_on_power) { MqttPublishTeleState(); }
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower End power=%d light_power=%d", power, light_power);
#endif
light_power = power >> (light_device - 1); // reset next state
LightState(0);
}
@ -1667,11 +1774,26 @@ void LightSetPower(void)
{
// light_power = XdrvMailbox.index;
light_old_power = light_power;
light_power = bitRead(XdrvMailbox.index, light_device -1);
//light_power = bitRead(XdrvMailbox.index, light_device -1);
uint32_t mask = 1; // default mask
if (light_pwm_multi_channels) {
mask = (1 << light_subtype) - 1; // wider mask
}
uint32_t shift = light_device - 1;
// If PWM multi_channels
// Ex: 3 Relays and 4 PWM - devices_present = 7, light_device = 4, light_subtype = 4
// Result: mask = 0b00001111 = 0x0F, shift = 3.
// Power bits we consider are: 0b01111000 = 0x78
// If regular situation: devices_present == light_subtype
light_power = (XdrvMailbox.index & (mask << shift)) >> shift;
if (light_wakeup_active) {
light_wakeup_active--;
}
if (light_power && !light_old_power) {
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightSetPower XdrvMailbox.index=%d light_old_power=%d light_power=%d mask=%d shift=%d",
XdrvMailbox.index, light_old_power, light_power, mask, shift);
#endif
if (light_power != light_old_power) {
light_update = 1;
}
LightAnimate();
@ -1762,6 +1884,22 @@ void LightAnimate(void)
}
if ((Settings.light_scheme < LS_MAX) || !light_power) {
// If SetOption68, multi_channels
if (light_pwm_multi_channels) {
// if multi-channels, specifically apply the light_power bits
for (uint32_t i = 0; i < LST_MAX; i++) {
if (0 == bitRead(light_power,i)) { // if power down bit is zero
light_new_color[i] = 0; // shut down this channel
}
}
// #ifdef DEBUG_LIGHT
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, "Animate>> light_power=%d light_new_color=[%d,%d,%d,%d,%d]",
// light_power, light_new_color[0], light_new_color[1], light_new_color[2],
// light_new_color[3], light_new_color[4]);
// #endif
}
if (memcmp(light_last_color, light_new_color, light_subtype)) {
light_update = 1;
}
@ -1777,62 +1915,11 @@ void LightAnimate(void)
}
if (PHILIPS == my_module_type) {
// Xiaomi Philips bulbs follow a different scheme:
uint8_t cold; // channel 1 is the color tone, mapped to cold channel (0..255)
light_state.getCW(&cold, nullptr);
cur_col[1] = cold;
cur_col_10bits[1] = changeUIntScale(cur_col[1], 0, 255, 0, 1023);
// now set channel 0 to overall brightness
uint8_t pxBri = light_state.getBriCT();
// channel 0=intensity, channel1=temperature
if (Settings.light_correction) { // gamma correction
cur_col[0] = ledGamma(pxBri);
cur_col_10bits[0] = ledGamma(pxBri, 10); // 10 bits gamma correction
} else {
cur_col[0] = pxBri;
cur_col_10bits[0] = changeUIntScale(pxBri, 0, 255, 0, 1023); // no gamma, extend to 10 bits
}
calcGammaXiaomiBulbs(cur_col, cur_col_10bits);
} else if (light_pwm_multi_channels) {
calcGammaMultiChannels(cur_col, cur_col_10bits);
} else { // PHILIPS != my_module_type
// Apply gamma correction for 8 and 10 bits resolutions, if needed
if (Settings.light_correction) {
// First apply combined correction to the overall white power
if ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype)) {
uint8_t w_idx[2] = {0, 1}; // if LST_COLDWARM, channels 0 and 1
if (LST_RGBWC == light_subtype) { // if LST_RGBWC, channels 3 and 4
w_idx[0] = 3;
w_idx[1] = 4;
}
uint16_t white_bri = cur_col[w_idx[0]] + cur_col[w_idx[1]];
// if sum of both channels is > 255, then channels are probablu uncorrelated
if (white_bri <= 255) {
// we calculate the gamma corrected sum of CW + WW
uint16_t white_bri_10bits = ledGamma(white_bri, 10);
uint8_t white_bri_8bits = ledGamma(white_bri);
// then we split the total energy among the cold and warm leds
cur_col_10bits[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_10bits);
cur_col_10bits[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_10bits);
cur_col[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_8bits);
cur_col[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_8bits);
} else {
cur_col_10bits[w_idx[0]] = ledGamma(cur_col[w_idx[0]], 10);
cur_col_10bits[w_idx[1]] = ledGamma(cur_col[w_idx[1]], 10);
cur_col[w_idx[0]] = ledGamma(cur_col[w_idx[0]]);
cur_col[w_idx[1]] = ledGamma(cur_col[w_idx[1]]);
}
}
// then apply gamma correction to RGB channels
if (LST_RGB <= light_subtype) {
for (uint32_t i = 0; i < 3; i++) {
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
cur_col[i] = ledGamma(cur_col[i]);
}
}
// If RGBW or Single channel, also adjust White channel
if (LST_COLDWARM != light_subtype) {
cur_col_10bits[3] = ledGamma(cur_col[3], 10);
cur_col[3] = ledGamma(cur_col[3]);
}
}
calcGammaBulbs(cur_col, cur_col_10bits);
// Now see if we need to mix RGB and True White
// Valid only for LST_RGBW, LST_RGBWC, rgbwwTable[4] is zero, and white is zero (see doc)
@ -1931,6 +2018,79 @@ void LightAnimate(void)
}
}
// Do specific computation for Xiaomi Bulbs
void calcGammaXiaomiBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
// Xiaomi Philips bulbs follow a different scheme:
uint8_t cold; // channel 1 is the color tone, mapped to cold channel (0..255)
light_state.getCW(&cold, nullptr);
cur_col[1] = cold;
cur_col_10bits[1] = changeUIntScale(cur_col[1], 0, 255, 0, 1023);
// now set channel 0 to overall brightness
uint8_t pxBri = light_state.getBriCT();
// channel 0=intensity, channel1=temperature
if (Settings.light_correction) { // gamma correction
cur_col[0] = ledGamma(pxBri);
cur_col_10bits[0] = ledGamma(pxBri, 10); // 10 bits gamma correction
} else {
cur_col[0] = pxBri;
cur_col_10bits[0] = changeUIntScale(pxBri, 0, 255, 0, 1023); // no gamma, extend to 10 bits
}
}
// Just apply basic Gamma to each channel
void calcGammaMultiChannels(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
// Apply gamma correction for 8 and 10 bits resolutions, if needed
if (Settings.light_correction) {
for (uint32_t i = 0; i < LST_MAX; i++) {
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
cur_col[i] = ledGamma(cur_col[i]);
}
}
}
void calcGammaBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
// Apply gamma correction for 8 and 10 bits resolutions, if needed
if (Settings.light_correction) {
// First apply combined correction to the overall white power
if ((LST_COLDWARM == light_subtype) || (LST_RGBWC == light_subtype)) {
uint8_t w_idx[2] = {0, 1}; // if LST_COLDWARM, channels 0 and 1
if (LST_RGBWC == light_subtype) { // if LST_RGBWC, channels 3 and 4
w_idx[0] = 3;
w_idx[1] = 4;
}
uint16_t white_bri = cur_col[w_idx[0]] + cur_col[w_idx[1]];
// if sum of both channels is > 255, then channels are probablu uncorrelated
if (white_bri <= 255) {
// we calculate the gamma corrected sum of CW + WW
uint16_t white_bri_10bits = ledGamma(white_bri, 10);
uint8_t white_bri_8bits = ledGamma(white_bri);
// then we split the total energy among the cold and warm leds
cur_col_10bits[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_10bits);
cur_col_10bits[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_10bits);
cur_col[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_8bits);
cur_col[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_8bits);
} else {
cur_col_10bits[w_idx[0]] = ledGamma(cur_col[w_idx[0]], 10);
cur_col_10bits[w_idx[1]] = ledGamma(cur_col[w_idx[1]], 10);
cur_col[w_idx[0]] = ledGamma(cur_col[w_idx[0]]);
cur_col[w_idx[1]] = ledGamma(cur_col[w_idx[1]]);
}
}
// then apply gamma correction to RGB channels
if (LST_RGB <= light_subtype) {
for (uint32_t i = 0; i < 3; i++) {
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
cur_col[i] = ledGamma(cur_col[i]);
}
}
// If RGBW or Single channel, also adjust White channel
if (LST_COLDWARM != light_subtype) {
cur_col_10bits[3] = ledGamma(cur_col[3], 10);
cur_col[3] = ledGamma(cur_col[3]);
}
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
@ -2080,10 +2240,16 @@ void CmndChannel(void)
bool coldim = false;
// Set "Channel" directly - this allows Color and Direct PWM control to coexist
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
light_current_color[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
// if we change channels 1,2,3 then turn off CT mode (unless non-linked)
if ((XdrvMailbox.index <= 3) && (light_controller.isCTRGBLinked())) {
light_current_color[3] = light_current_color[4] = 0;
light_current_color[XdrvMailbox.index - light_device] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
if (light_pwm_multi_channels) {
// if (!Settings.flag.not_power_linked) { // SetOption20
// light_power = light_power | (1 << (XdrvMailbox.index - light_device)); // ask to turn on channel
// }
} else {
// if we change channels 1,2,3 then turn off CT mode (unless non-linked)
if ((XdrvMailbox.index <= 3) && (light_controller.isCTRGBLinked())) {
light_current_color[3] = light_current_color[4] = 0;
}
}
light_controller.changeChannels(light_current_color);
coldim = true;

View File

@ -243,6 +243,22 @@ uint16_t prev_ct = 254;
char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
char prev_y_str[24] = "\0";
uint8_t getLocalLightSubtype(uint8_t device) {
if (light_type) {
if (device >= light_device) {
if (Settings.flag3.pmw_multi_channels) {
return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
} else {
return light_subtype; // the actual light
}
} else {
return LST_NONE; // relays
}
} else {
return LST_NONE;
}
}
void HueLightStatus1(uint8_t device, String *response)
{
uint16_t ct = 0;
@ -251,14 +267,18 @@ void HueLightStatus1(uint8_t device, String *response)
uint16_t hue = 0;
uint8_t sat = 0;
uint8_t bri = 254;
uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
// local_light_subtype simulates the light_subtype for 'device'
// For relays LST_NONE, for dimmers LST_SINGLE
uint8_t local_light_subtype = getLocalLightSubtype(device);
bri = LightGetBri(device); // get Dimmer corrected with SetOption68
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
if (bri < 1) bri = 1;
if (light_type) {
light_state.getHSB(&hue, &sat, nullptr);
bri = light_state.getBri();
if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
if (bri < 1) bri = 1;
if ((bri > prev_bri ? bri - prev_bri : prev_bri - bri) < 1)
bri = prev_bri;
@ -293,17 +313,17 @@ void HueLightStatus1(uint8_t device, String *response)
*response += FPSTR(HUE_LIGHTS_STATUS_JSON1);
response->replace("{state}", (power & (1 << (device-1))) ? "true" : "false");
// Brightness for all devices with PWM
//if (LST_SINGLE <= light_subtype) {
light_status += "\"bri\":";
light_status += String(bri);
light_status += ",";
//}
if (LST_COLDWARM <= light_subtype) {
if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
light_status += "\"bri\":";
light_status += String(bri);
light_status += ",";
}
if (LST_COLDWARM <= local_light_subtype) {
light_status += F("\"colormode\":\"");
light_status += (g_gotct ? "ct" : "hs");
light_status += "\",";
}
if (LST_RGB <= light_subtype) { // colors
if (LST_RGB <= local_light_subtype) { // colors
if (prev_x_str[0] && prev_y_str[0]) {
light_status += "\"xy\":[";
light_status += prev_x_str;
@ -327,10 +347,10 @@ void HueLightStatus1(uint8_t device, String *response)
light_status += String(sat);
light_status += ",";
}
if (LST_COLDWARM == light_subtype || LST_RGBW <= light_subtype) { // white temp
if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
light_status += "\"ct\":";
light_status += String(ct > 0 ? ct : 284);
light_status += ","; // if no ct, default to medium white
light_status += String(ct > 0 ? ct : 284); // if no ct, default to medium white
light_status += ",";
}
response->replace("{light_status}", light_status);
}
@ -338,7 +358,20 @@ void HueLightStatus1(uint8_t device, String *response)
void HueLightStatus2(uint8_t device, String *response)
{
*response += FPSTR(HUE_LIGHTS_STATUS_JSON2);
response->replace("{j1", Settings.friendlyname[device-1]);
if (device <= MAX_FRIENDLYNAMES) {
response->replace("{j1", Settings.friendlyname[device-1]);
} else {
char fname[33];
strcpy(fname, Settings.friendlyname[MAX_FRIENDLYNAMES-1]);
uint32_t fname_len = strlen(fname);
if (fname_len >= 33-3) {
fname[33-3] = 0x00;
fname_len = 33-3;
}
fname[fname_len++] = '-';
fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
response->replace("{j1", fname);
}
response->replace("{j2", GetHueDeviceId(device));
}
@ -357,10 +390,32 @@ uint32_t DecodeLightId(uint32_t id) {
return id & 0xF;
}
static const char * FIRST_GEN_UA[] = { // list of User-Agents signature
"AEOBC", // Echo Dot 2ng Generation
};
// Check if the Echo device is of 1st generation, which triggers different results
uint32_t findEchoGeneration(void) {
// result is 1 for 1st gen, 2 for 2nd gen and further
String user_agent = WebServer->header("User-Agent");
uint32_t gen = 2;
for (uint32_t i = 0; i < sizeof(FIRST_GEN_UA)/sizeof(char*); i++) {
if (user_agent.indexOf(FIRST_GEN_UA[i]) >= 0) { // found
gen = 1;
break;
}
}
AddLog_P2(LOG_LEVEL_DEBUG_MORE, D_LOG_HTTP D_HUE " User-Agent: %s, gen=%d", user_agent.c_str(), gen); // Header collection is set in xdrv_01_webserver.ino, in StartWebserver()
return gen;
}
void HueGlobalConfig(String *path)
{
String response;
uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
path->remove(0,1); // cut leading / to get <id>
response = F("{\"lights\":{\"");
@ -403,7 +458,8 @@ void HueLights(String *path)
bool on = false;
bool change = false; // need to change a parameter to the light
uint8_t device = 1;
uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
uint8_t local_light_subtype = light_subtype;
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
path->remove(0,path->indexOf("/lights")); // Remove until /lights
if (path->endsWith("/lights")) { // Got /lights
@ -426,6 +482,8 @@ void HueLights(String *path)
if ((device < 1) || (device > maxhue)) {
device = 1;
}
local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
if (WebServer->args()) {
response = "[";
@ -452,14 +510,18 @@ void HueLights(String *path)
resp = true;
}
if (light_type) {
light_state.getHSB(&hue, &sat, nullptr);
bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
ct = light_state.getCT();
uint8_t color_mode = light_state.getColorMode();
if (LCM_RGB == color_mode) { g_gotct = false; }
if (LCM_CT == color_mode) { g_gotct = true; }
// If LCM_BOTH == color_mode, leave g_gotct unchanged
if (light_type && (local_light_subtype >= LST_SINGLE)) {
if (!Settings.flag3.pmw_multi_channels) {
light_state.getHSB(&hue, &sat, nullptr);
bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
ct = light_state.getCT();
uint8_t color_mode = light_state.getColorMode();
if (LCM_RGB == color_mode) { g_gotct = false; }
if (LCM_CT == color_mode) { g_gotct = true; }
// If LCM_BOTH == color_mode, leave g_gotct unchanged
} else { // treat each channel as simple dimmer
bri = LightGetBri(device);
}
}
prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
@ -551,14 +613,18 @@ void HueLights(String *path)
resp = true;
}
if (change) {
if (light_type) {
if (g_gotct) {
light_controller.changeCTB(ct, bri);
} else {
light_controller.changeHSB(hue, sat, bri);
if (light_type && (local_light_subtype > LST_NONE)) { // not relay
if (!Settings.flag3.pmw_multi_channels) {
if (g_gotct) {
light_controller.changeCTB(ct, bri);
} else {
light_controller.changeHSB(hue, sat, bri);
}
LightPreparePower();
} else { // SetOption68 On, each channel is a dimmer
LightSetBri(device, bri);
}
LightPreparePower();
if (LST_COLDWARM <= light_subtype) {
if (LST_COLDWARM <= local_light_subtype) {
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
} else {
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
@ -577,6 +643,7 @@ void HueLights(String *path)
}
}
else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "/lights path=%s", path->c_str());
path->remove(0,8); // Remove /lights/
device = DecodeLightId(atoi(path->c_str()));
if ((device < 1) || (device > maxhue)) {
@ -600,7 +667,7 @@ void HueGroups(String *path)
* http://sonoff/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
*/
String response = "{}";
uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
if (path->endsWith("/0")) {
response = FPSTR(HUE_GROUP0_STATUS_JSON);