mirror of https://github.com/arendst/Tasmota.git
Commands `CTRange` and `VirtualCT`
This commit is contained in:
parent
37bd2a99f6
commit
488712c3f0
|
@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
|
|||
## [9.2.0.2]
|
||||
### Added
|
||||
- Basic support for ESP32 Odroid Go 16MB binary tasmota32-odroidgo.bin (#8630)
|
||||
- Command ``CTRange`` to specify the visible CT range the bulb is capable of
|
||||
- Command ``VirtualCT`` to simulate or fine tune CT bulbs with 3,4,5 channels
|
||||
|
||||
### Breaking Changed
|
||||
- Replaced MFRC522 13.56MHz rfid card reader GPIO selection from ``GPIO_SPI_CS`` by ``GPIO_RC522_CS``
|
||||
|
|
|
@ -425,6 +425,8 @@
|
|||
#define D_CMND_DIMMER_RANGE "DimmerRange"
|
||||
#define D_CMND_DIMMER_STEP "DimmerStep"
|
||||
#define D_CMND_HSBCOLOR "HSBColor"
|
||||
#define D_CMND_VIRTUALCT "VirtualCT"
|
||||
#define D_CMND_CTRANGE "CTRange"
|
||||
#define D_CMND_LED "Led"
|
||||
#define D_CMND_LEDTABLE "LedTable"
|
||||
#define D_CMND_FADE "Fade"
|
||||
|
|
|
@ -309,6 +309,7 @@
|
|||
#define LIGHT_WHITE_BLEND_MODE false // [SetOption105] White Blend Mode - used to be `RGBWWTable` last value `0`, now deprecated in favor of this option
|
||||
#define LIGHT_VIRTUAL_CT false // [SetOption106] Virtual CT - Creates a virtual White ColorTemp for RGBW lights
|
||||
#define LIGHT_VIRTUAL_CT_CW false // [SetOption107] Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
|
||||
#define LIGHT_VIRTUAL_CT_POINTS 3 // Number of reference points for Virtual CT (min 2, default 3)
|
||||
|
||||
// -- Energy --------------------------------------
|
||||
#define ENERGY_VOLTAGE_ALWAYS false // [SetOption21] Enable show voltage even if powered off
|
||||
|
@ -494,6 +495,7 @@
|
|||
#define USE_SONOFF_L1 // Add support for Sonoff L1 led control
|
||||
#define USE_ELECTRIQ_MOODL // Add support for ElectriQ iQ-wifiMOODL RGBW LED controller (+0k3 code)
|
||||
#define USE_LIGHT_PALETTE // Add support for color palette (+0k7 code)
|
||||
#define USE_LIGHT_VIRTUAL_CT // Add support for Virtual White Color Temperature (+1.1k code)
|
||||
#define USE_DGR_LIGHT_SEQUENCE // Add support for device group light sequencing (requires USE_DEVICE_GROUPS) (+0k2 code)
|
||||
|
||||
// -- Counter input -------------------------------
|
||||
|
|
|
@ -941,6 +941,11 @@ void CmndSetoption(void)
|
|||
else if (4 == ptype) { // SetOption82 .. 113
|
||||
bitWrite(Settings.flag4.data, pindex, XdrvMailbox.payload);
|
||||
switch (pindex) {
|
||||
#ifdef USE_LIGHT
|
||||
case 0: // SetOption 82 - (Alexa) Reduced CT range for Alexa (1)
|
||||
setAlexaCTRange();
|
||||
break;
|
||||
#endif
|
||||
case 3: // SetOption85 - Enable Device Groups
|
||||
case 6: // SetOption88 - PWM Dimmer Buttons control remote devices
|
||||
case 15: // SetOption97 - Set Baud rate for TuyaMCU serial communication (0 = 9600 or 1 = 115200)
|
||||
|
|
|
@ -508,6 +508,7 @@
|
|||
|
||||
// -- Optional light modules ----------------------
|
||||
//#undef USE_LIGHT // Enable Dimmer/Light support
|
||||
#undef USE_LIGHT_VIRTUAL_CT // Disable support for Virtual White Color Temperature (SO106)
|
||||
#undef USE_WS2812 // Disable WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
||||
#undef USE_MY92X1 // Disable support for MY92X1 RGBCW led controller as used in Sonoff B1, Ailight and Lohas
|
||||
#undef USE_SM16716 // Disable support for SM16716 RGB LED controller (+0k7 code)
|
||||
|
@ -643,6 +644,7 @@
|
|||
//#undef USE_SONOFF_D1 // Disable support for Sonoff D1 Dimmer (+0k7 code)
|
||||
|
||||
// -- Optional light modules ----------------------
|
||||
#undef USE_LIGHT_VIRTUAL_CT // Disable support for Virtual White Color Temperature (SO106)
|
||||
//#undef USE_LIGHT // Also disable all Dimmer/Light support
|
||||
#undef USE_WS2812 // Disable WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
||||
#undef USE_MY92X1 // Disable support for MY92X1 RGBCW led controller as used in Sonoff B1, Ailight and Lohas
|
||||
|
|
|
@ -132,6 +132,10 @@ const char kLightCommands[] PROGMEM = "|" // No prefix
|
|||
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_DIMMER_STEP "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
|
||||
D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
|
||||
D_CMND_WHITE "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR
|
||||
"|" D_CMND_CTRANGE
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
"|" D_CMND_VIRTUALCT
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
#ifdef USE_LIGHT_PALETTE
|
||||
"|" D_CMND_PALETTE
|
||||
#endif // USE_LIGHT_PALETTE
|
||||
|
@ -144,6 +148,10 @@ void (* const LightCommand[])(void) PROGMEM = {
|
|||
&CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndDimmerRange, &CmndDimmerStep, &CmndLedTable, &CmndFade,
|
||||
&CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration,
|
||||
&CmndWhite, &CmndChannel, &CmndHsbColor,
|
||||
&CmndCTRange,
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
&CmndVirtualCT,
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
#ifdef USE_LIGHT_PALETTE
|
||||
&CmndPalette,
|
||||
#endif // USE_LIGHT_PALETTE
|
||||
|
@ -181,6 +189,12 @@ const uint16_t CT_MAX = 500; // 2000K
|
|||
// Ranges used for Alexa
|
||||
const uint16_t CT_MIN_ALEXA = 200; // also 5000K
|
||||
const uint16_t CT_MAX_ALEXA = 380; // also 2600K
|
||||
// Virtual CT default values
|
||||
typedef uint8_t vct_pivot_t[LST_MAX];
|
||||
const size_t CT_PIVOTS = LIGHT_VIRTUAL_CT_POINTS;
|
||||
const vct_pivot_t CT_PIVOTS_RGB PROGMEM = { 255, 255, 255, 0, 0 };
|
||||
const vct_pivot_t CT_PIVOTS_CWW PROGMEM = { 0, 0, 0, 255, 0 };
|
||||
const vct_pivot_t CT_PIVOTS_WWW PROGMEM = { 0, 0, 0, 0, 255 };
|
||||
|
||||
// New version of Gamma correction compute
|
||||
// Instead of a table, we do a multi-linear approximation, which is close enough
|
||||
|
@ -308,6 +322,12 @@ struct LIGHT {
|
|||
|
||||
uint16_t pwm_min = 0; // minimum value for PWM, from DimmerRange, 0..1023
|
||||
uint16_t pwm_max = 1023; // maxumum value for PWM, from DimmerRange, 0..1023
|
||||
|
||||
// Virtual CT
|
||||
uint16_t vct_ct[CT_PIVOTS]; // CT value for each segment
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
vct_pivot_t vct_color[CT_PIVOTS]; // array of 3 colors each with 5 values
|
||||
#endif
|
||||
} Light;
|
||||
|
||||
power_t LightPower(void)
|
||||
|
@ -378,13 +398,6 @@ class LightStateClass {
|
|||
uint8_t _briCT = 255;
|
||||
|
||||
uint8_t _color_mode = LCM_RGB; // RGB by default
|
||||
// the CT range below represents the rendered range,
|
||||
// This is due to Alexa whose CT range is 199..383
|
||||
// Hence setting Min=200 and Max=380 makes Alexa use the full range
|
||||
// Please note that you can still set CT to 153..500, but any
|
||||
// value below _ct_min_range or above _ct_max_range not change the CT
|
||||
uint16_t _ct_min_range = CT_MIN; // the minimum CT rendered range
|
||||
uint16_t _ct_max_range = CT_MAX; // the maximum CT rendered range
|
||||
|
||||
public:
|
||||
LightStateClass() {
|
||||
|
@ -535,22 +548,7 @@ class LightStateClass {
|
|||
|
||||
inline uint16_t getCT() const {
|
||||
return _ct; // 153..500, or CT_MIN..CT_MAX
|
||||
}
|
||||
|
||||
// get the CT value within the range into a 10 bits 0..1023 value
|
||||
uint16_t getCT10bits() const {
|
||||
return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023);
|
||||
}
|
||||
|
||||
inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) {
|
||||
_ct_min_range = ct_min_range;
|
||||
_ct_max_range = ct_max_range;
|
||||
}
|
||||
|
||||
inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const {
|
||||
if (ct_min_range) { *ct_min_range = _ct_min_range; }
|
||||
if (ct_max_range) { *ct_max_range = _ct_max_range; }
|
||||
}
|
||||
}
|
||||
|
||||
// get current color in XY format
|
||||
void getXY(float *x, float *y) {
|
||||
|
@ -603,7 +601,7 @@ class LightStateClass {
|
|||
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
|
||||
} else {
|
||||
ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct));
|
||||
_ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255);
|
||||
_ww = changeUIntScale(ct, Light.vct_ct[0], Light.vct_ct[CT_PIVOTS-1], 0, 255);
|
||||
_wc = 255 - _ww;
|
||||
_ct = ct;
|
||||
addCTMode();
|
||||
|
@ -932,15 +930,6 @@ public:
|
|||
return prev;
|
||||
}
|
||||
|
||||
void setAlexaCTRange(bool alexa_ct_range) {
|
||||
// depending on SetOption82, full or limited CT range
|
||||
if (alexa_ct_range) {
|
||||
_state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA); // 200..380
|
||||
} else {
|
||||
_state->setCTRange(CT_MIN, CT_MAX); // 153..500
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isCTRGBLinked() {
|
||||
return _ct_rgb_linked;
|
||||
}
|
||||
|
@ -1190,6 +1179,63 @@ uint8_t change10to8(uint16_t v) {
|
|||
return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* CT (White Color Temperature)
|
||||
\*********************************************************************************************/
|
||||
//
|
||||
// Ensure that invariants for Virtual CT are good:
|
||||
// - CT_MIN <= ct[0] <= ct[1] <= ct[2] <= CT_MAX
|
||||
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
void checkVirtualCT(void) {
|
||||
if (Light.vct_ct[0] < CT_MIN) { Light.vct_ct[0] = CT_MIN; }
|
||||
if (Light.vct_ct[CT_PIVOTS-1] > CT_MAX) { Light.vct_ct[CT_PIVOTS-1] = CT_MAX; }
|
||||
for (uint32_t i = 0; i < CT_PIVOTS-1; i++) {
|
||||
if (Light.vct_ct[i+1] < Light.vct_ct[i]) { Light.vct_ct[i+1] = Light.vct_ct[i]; }
|
||||
}
|
||||
}
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
// Init default values for virtual CT, depending on the number of channels
|
||||
void initCTRange(uint32_t channels) {
|
||||
if (channels == 4) {
|
||||
if (Settings.flag4.virtual_ct_cw) { // Hardware White is Cold White
|
||||
memcpy_P(Light.vct_color[0], CT_PIVOTS_CWW, sizeof(Light.vct_color[0])); // Cold white
|
||||
memcpy_P(Light.vct_color[1], CT_PIVOTS_RGB, sizeof(Light.vct_color[1])); // Warm white
|
||||
} else { // Hardware White is Warm White
|
||||
memcpy_P(Light.vct_color[0], CT_PIVOTS_RGB, sizeof(Light.vct_color[0])); // Cold white
|
||||
memcpy_P(Light.vct_color[1], CT_PIVOTS_CWW, sizeof(Light.vct_color[1])); // Warm white
|
||||
}
|
||||
} else if (channels == 5) {
|
||||
memcpy_P(Light.vct_color[0], CT_PIVOTS_CWW, sizeof(Light.vct_color[0])); // Cold white
|
||||
memcpy_P(Light.vct_color[1], CT_PIVOTS_WWW, sizeof(Light.vct_color[1])); // Warm white
|
||||
} else {
|
||||
memcpy_P(Light.vct_color[0], CT_PIVOTS_RGB, sizeof(Light.vct_color[0])); // Cold white
|
||||
memcpy_P(Light.vct_color[1], CT_PIVOTS_RGB, sizeof(Light.vct_color[1])); // Warm white
|
||||
}
|
||||
for (uint32_t i = 1; i < CT_PIVOTS-1; i++) {
|
||||
memcpy_P(Light.vct_color[i+1], Light.vct_color[i], sizeof(Light.vct_color[0])); // Copy slot 1 into slot 2 (slot 2 in unused)
|
||||
}
|
||||
checkVirtualCT();
|
||||
}
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
|
||||
void setCTRange(uint16_t ct_min, uint16_t ct_max) {
|
||||
Light.vct_ct[0] = ct_min;
|
||||
for (uint32_t i = 1; i < CT_PIVOTS; i++) {
|
||||
Light.vct_ct[i] = ct_max; // all slots above [1] are not used
|
||||
}
|
||||
}
|
||||
|
||||
void setAlexaCTRange(void) { // depending on SetOption82, full or limited CT range
|
||||
if (Settings.flag4.alexa_ct_range) {
|
||||
setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA);
|
||||
} else {
|
||||
setCTRange(CT_MIN, CT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Gamma correction
|
||||
\*********************************************************************************************/
|
||||
|
@ -1299,9 +1345,14 @@ bool LightModuleInit(void)
|
|||
} else if ((Settings.param[P_RGB_REMAP] & 128) && (LST_RGBW <= pwm_channels)) { // SetOption37
|
||||
// if RGBW or RGBCW, and SetOption37 >= 128, we manage RGB and W separately, hence adding a device
|
||||
TasmotaGlobal.devices_present++;
|
||||
} else if ((Settings.flag4.virtual_ct) && (LST_RGBW == pwm_channels)) {
|
||||
Light.virtual_ct = true; // enabled
|
||||
TasmotaGlobal.light_type++; // create an additional virtual 5th channel
|
||||
} else {
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
initCTRange(pwm_channels);
|
||||
if ((Settings.flag4.virtual_ct) && (LST_RGB <= pwm_channels)) {
|
||||
Light.virtual_ct = true; // enabled
|
||||
TasmotaGlobal.light_type += 5 - pwm_channels; // pretend it is a 5 channels bulb
|
||||
}
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
}
|
||||
|
||||
return (TasmotaGlobal.light_type > LT_BASIC);
|
||||
|
@ -1367,7 +1418,7 @@ void LightInit(void)
|
|||
|
||||
light_controller.setSubType(Light.subtype);
|
||||
light_controller.loadSettings();
|
||||
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
||||
setAlexaCTRange();
|
||||
light_controller.calcLevels(); // calculate the initial values (#8058)
|
||||
|
||||
if (LST_SINGLE == Light.subtype) {
|
||||
|
@ -1859,7 +1910,6 @@ void LightAnimate(void)
|
|||
bool power_off = false;
|
||||
|
||||
// make sure we update CT range in case SetOption82 was changed
|
||||
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
||||
Light.strip_timer_counter++;
|
||||
|
||||
// set sleep parameter: either settings,
|
||||
|
@ -1966,7 +2016,6 @@ void LightAnimate(void)
|
|||
|
||||
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
|
||||
Light.update = false;
|
||||
bool rgbwwtable_applied = false; // did we already applied RGBWWTable (ex: in white_blend_mode or virtual_ct)
|
||||
|
||||
// first set 8 and 10 bits channels
|
||||
for (uint32_t i = 0; i < LST_MAX; i++) {
|
||||
|
@ -1975,58 +2024,19 @@ void LightAnimate(void)
|
|||
cur_col_10[i] = change8to10(Light.new_color[i]);
|
||||
}
|
||||
|
||||
bool rgbwwtable_applied_white = false; // did we already applied RGBWWTable to white channels (ex: in white_blend_mode or virtual_ct)
|
||||
if (Light.pwm_multi_channels) {
|
||||
calcGammaMultiChannels(cur_col_10);
|
||||
} else {
|
||||
calcGammaBulbs(cur_col_10);
|
||||
|
||||
// Now see if we need to mix RGB and True White
|
||||
// Valid only for LST_RGBW, LST_RGBCW, rgbwwTable[4] is zero, and white is zero (see doc)
|
||||
if ((LST_RGBW <= Light.subtype) && (Settings.flag4.white_blend_mode) && (0 == cur_col_10[3]+cur_col_10[4])) {
|
||||
uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
|
||||
for (uint32_t i=0; i<3; i++) {
|
||||
// substract white and adjust according to rgbwwTable
|
||||
uint32_t adjust10 = change8to10(Settings.rgbwwTable[i]);
|
||||
cur_col_10[i] = changeUIntScale(cur_col_10[i] - min_rgb_10, 0, 1023, 0, adjust10);
|
||||
}
|
||||
|
||||
// compute the adjusted white levels for 10 and 8 bits
|
||||
uint32_t adjust_w_10 = changeUIntScale(Settings.rgbwwTable[3], 0, 255, 0, 1023);
|
||||
uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10); // set white power down corrected with rgbwwTable[3]
|
||||
if (LST_RGBW == Light.subtype) {
|
||||
// we simply set the white channel
|
||||
cur_col_10[3] = white_10;
|
||||
} else { // LST_RGBCW
|
||||
// we distribute white between cold and warm according to CT value
|
||||
uint32_t ct = light_state.getCT10bits();
|
||||
cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10);
|
||||
cur_col_10[3] = white_10 - cur_col_10[4];
|
||||
}
|
||||
rgbwwtable_applied = true;
|
||||
} else if ((Light.virtual_ct) && (0 == cur_col_10[0]+cur_col_10[1]+cur_col_10[2])) {
|
||||
// virtual_ct is on and we don't have any RGB set
|
||||
uint16_t sw_white = Settings.flag4.virtual_ct_cw ? cur_col_10[4] : cur_col_10[3]; // white power for virtual RGB
|
||||
uint16_t hw_white = Settings.flag4.virtual_ct_cw ? cur_col_10[3] : cur_col_10[4]; // white for hardware LED
|
||||
uint32_t adjust_sw = change8to10(Settings.flag4.virtual_ct_cw ? Settings.rgbwwTable[4] : Settings.rgbwwTable[3]);
|
||||
uint32_t adjust_hw = change8to10(Settings.flag4.virtual_ct_cw ? Settings.rgbwwTable[3] : Settings.rgbwwTable[4]);
|
||||
// set the target channels. Note: Gamma correction was arleady applied
|
||||
cur_col_10[3] = changeUIntScale(hw_white, 0, 1023, 0, adjust_hw);
|
||||
cur_col_10[4] = 0; // we don't actually have a 5the channel
|
||||
sw_white = changeUIntScale(sw_white, 0, 1023, 0, adjust_sw); // pre-adjust virtual channel
|
||||
for (uint32_t i=0; i<3; i++) {
|
||||
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
|
||||
cur_col_10[i] = changeUIntScale(sw_white, 0, 1023, 0, adjust);
|
||||
}
|
||||
rgbwwtable_applied = true;
|
||||
}
|
||||
// AddLog_P(LOG_LEVEL_INFO, PSTR(">>> calcGammaBulbs In %03X,%03X,%03X,%03X,%03X"), cur_col_10[0], cur_col_10[1], cur_col_10[2], cur_col_10[3], cur_col_10[4]);
|
||||
rgbwwtable_applied_white = calcGammaBulbs(cur_col_10); // true means that one PWM channel is used for CT
|
||||
// AddLog_P(LOG_LEVEL_INFO, PSTR(">>> calcGammaBulbs Out %03X,%03X,%03X,%03X,%03X"), cur_col_10[0], cur_col_10[1], cur_col_10[2], cur_col_10[3], cur_col_10[4]);
|
||||
}
|
||||
|
||||
// Apply RGBWWTable only if not Settings.flag4.white_blend_mode
|
||||
if (!rgbwwtable_applied) {
|
||||
for (uint32_t i = 0; i<Light.subtype; i++) {
|
||||
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
|
||||
cur_col_10[i] = changeUIntScale(cur_col_10[i], 0, 1023, 0, adjust);
|
||||
}
|
||||
for (uint32_t i = 0; i < (rgbwwtable_applied_white ? 3 : Light.subtype); i++) {
|
||||
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
|
||||
cur_col_10[i] = changeUIntScale(cur_col_10[i], 0, 1023, 0, adjust);
|
||||
}
|
||||
|
||||
// final adjusments for PMW ranges, post-gamma correction
|
||||
|
@ -2272,56 +2282,170 @@ void calcGammaMultiChannels(uint16_t cur_col_10[5]) {
|
|||
}
|
||||
}
|
||||
|
||||
void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
||||
// Apply gamma correction for 8 and 10 bits resolutions, if needed
|
||||
|
||||
// First apply combined correction to the overall white power
|
||||
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
||||
// channels for white are always the last two channels
|
||||
uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
|
||||
uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
|
||||
uint16_t white_bri10 = cur_col_10[cw0] + cur_col_10[cw1]; // cumulated brightness
|
||||
uint16_t white_bri10_1023 = (white_bri10 > 1023) ? 1023 : white_bri10; // max 1023
|
||||
|
||||
#ifdef ESP8266
|
||||
if ((PHILIPS == TasmotaGlobal.module_type) || (Settings.flag4.pwm_ct_mode)) { // channel 1 is the color tone, mapped to cold channel (0..255)
|
||||
// Xiaomi Philips bulbs follow a different scheme:
|
||||
cur_col_10[cw1] = light_state.getCT10bits();
|
||||
// channel 0=intensity, channel1=temperature
|
||||
if (Settings.light_correction) { // gamma correction
|
||||
cur_col_10[cw0] = ledGamma10_10(white_bri10_1023); // 10 bits gamma correction
|
||||
} else {
|
||||
cur_col_10[cw0] = white_bri10_1023; // no gamma, extend to 10 bits
|
||||
}
|
||||
} else
|
||||
#endif // ESP8266
|
||||
if (Settings.light_correction) {
|
||||
// if sum of both channels is > 255, then channels are probably uncorrelated
|
||||
if (white_bri10 <= 1031) { // take a margin of 8 above 1023 to account for rounding errors
|
||||
// we calculate the gamma corrected sum of CW + WW
|
||||
uint16_t white_bri_gamma10 = ledGamma10_10(white_bri10_1023);
|
||||
// then we split the total energy among the cold and warm leds
|
||||
cur_col_10[cw0] = changeUIntScale(cur_col_10[cw0], 0, white_bri10_1023, 0, white_bri_gamma10);
|
||||
cur_col_10[cw1] = changeUIntScale(cur_col_10[cw1], 0, white_bri10_1023, 0, white_bri_gamma10);
|
||||
} else {
|
||||
cur_col_10[cw0] = ledGamma10_10(cur_col_10[cw0]);
|
||||
cur_col_10[cw1] = ledGamma10_10(cur_col_10[cw1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Compute the Gamma correction for CW/WW
|
||||
// Can be used for 2-channels (channels 0,1) or 5 channels (channels 3,4)
|
||||
//
|
||||
// It is implicitly called by calcGammaBulb5Channels()
|
||||
//
|
||||
// In:
|
||||
// - 2 channels CW/WW in 10 bits format (0..1023)
|
||||
// Out:
|
||||
// - 2 channels CW/WW in 10 bits format, with Gamma corretion (if enabled), replaced in place
|
||||
// - white_bri10: global brightness of white channel, split over CW/WW (basically the sum of CW+WW, but it's easier to compute on this basis)
|
||||
// - white_free_cw: signals that CW/WW are free mode, and not linked via CT. This is used when channels are manually set on a channel per channel basis. CT is ignored
|
||||
//
|
||||
void calcGammaBulbCW(uint16_t cw10[2], uint16_t *white_bri10_out, bool *white_free_cw_out) {
|
||||
uint16_t white_bri10 = cw10[0] + cw10[1]; // cumulated brightness
|
||||
bool white_free_cw = (white_bri10 > 1031); // take a margin of 8 above 1023 to account for rounding errors
|
||||
white_bri10 = (white_bri10 > 1023) ? 1023 : white_bri10; // max 1023
|
||||
|
||||
if (Settings.light_correction) {
|
||||
// then apply gamma correction to RGB channels
|
||||
if (LST_RGB <= Light.subtype) {
|
||||
for (uint32_t i = 0; i < 3; i++) {
|
||||
cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
|
||||
}
|
||||
}
|
||||
// If RGBW or Single channel, also adjust White channel
|
||||
if ((LST_SINGLE == Light.subtype) || (LST_RGBW == Light.subtype)) {
|
||||
cur_col_10[Light.subtype - 1] = ledGamma10_10(cur_col_10[Light.subtype - 1]);
|
||||
if (white_free_cw) {
|
||||
cw10[0] = ledGamma10_10(cw10[0]);
|
||||
cw10[1] = ledGamma10_10(cw10[1]);
|
||||
} else {
|
||||
uint16_t white_bri10_gamma = ledGamma10_10(white_bri10); // gamma corrected white
|
||||
// now distributed among both channels
|
||||
cw10[0] = changeUIntScale(cw10[0], 0, white_bri10, 0, white_bri10_gamma);
|
||||
cw10[1] = changeUIntScale(cw10[1], 0, white_bri10, 0, white_bri10_gamma);
|
||||
// now use white_bri10_gamma as a reference
|
||||
white_bri10 = white_bri10_gamma;
|
||||
}
|
||||
}
|
||||
if (white_bri10_out != nullptr) { *white_bri10_out = white_bri10; }
|
||||
if (white_free_cw_out != nullptr) { *white_free_cw_out = white_free_cw; }
|
||||
}
|
||||
|
||||
//
|
||||
// Calculate the gamma correction for all 5 channels RGBCW
|
||||
// Computation is valid for 1,3,4,5 channels
|
||||
// 2-channels bulbs must be handled separately
|
||||
//
|
||||
// In:
|
||||
// - 5 channels RGBCW in 10 bits format (0..1023)
|
||||
// Out:
|
||||
// - 5 channels RGBCW in 10 bits format, with Gamma corretion (if enabled), replaced in place
|
||||
// - white_bri10: global brightness of white channel, split over CW/WW (basically the sum of CW+WW, but it's easier to compute on this basis)
|
||||
// - white_free_cw: signals that CW/WW are free mode, and not linked via CT. This is used when channels are manually set on a channel per channel basis. CT is ignored
|
||||
//
|
||||
void calcGammaBulb5Channels(uint16_t col10[LST_MAX], uint16_t *white_bri10_out, bool *white_free_cw) {
|
||||
for (uint32_t i = 0; i < 3; i++) {
|
||||
if (Settings.light_correction) {
|
||||
col10[i] = ledGamma10_10(col10[i]);
|
||||
}
|
||||
}
|
||||
calcGammaBulbCW(&col10[3], white_bri10_out, white_free_cw);
|
||||
}
|
||||
|
||||
// sale but converts from 8 bits to 10 bits first
|
||||
void calcGammaBulb5Channels_8(uint8_t in8[LST_MAX], uint16_t col10[LST_MAX]) {
|
||||
for (uint32_t i = 0; i < LST_MAX; i++) {
|
||||
col10[i] = change8to10(in8[i]);
|
||||
}
|
||||
calcGammaBulb5Channels(col10, nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool calcGammaBulbs(uint16_t cur_col_10[5]) {
|
||||
bool rgbwwtable_applied_white = false;
|
||||
bool pwm_ct = false;
|
||||
bool white_free_cw = false; // true if White channels are uncorrelated. Happens when CW+WW>255, i.e. manually setting white channels to exceed to total power of a single channel (may harm the power supply)
|
||||
// Various values needed for accurate White calculation
|
||||
// CT value streteched to 0..1023 (from within CT range, so not necessarily from 153 to 500). 0=Cold, 1023=Warm
|
||||
uint16_t ct = light_state.getCT();
|
||||
uint16_t ct_10 = changeUIntScale(ct, Light.vct_ct[0], Light.vct_ct[CT_PIVOTS-1], 0, 1023);
|
||||
|
||||
uint16_t white_bri10 = 0; // White total brightness normalized to 0..1023
|
||||
// uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
|
||||
uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
|
||||
|
||||
// calc basic gamma correction for all types
|
||||
if ((LST_SINGLE == Light.subtype) || (LST_RGB <= Light.subtype)) {
|
||||
calcGammaBulb5Channels(cur_col_10, &white_bri10, &white_free_cw);
|
||||
} else if (LST_COLDWARM == Light.subtype) {
|
||||
calcGammaBulbCW(cur_col_10, &white_bri10, &white_free_cw);
|
||||
}
|
||||
|
||||
// Now we know ct_10 and white_bri10 (gamma corrected if needed)
|
||||
|
||||
#ifdef ESP8266
|
||||
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
||||
if ((PHILIPS == TasmotaGlobal.module_type) || (Settings.flag4.pwm_ct_mode)) { // channel 1 is the color tone, mapped to cold channel (0..255)
|
||||
pwm_ct = true;
|
||||
// Xiaomi Philips bulbs follow a different scheme:
|
||||
// channel 0=intensity, channel1=temperature
|
||||
cur_col_10[cw0] = white_bri10;
|
||||
cur_col_10[cw0+1] = ct_10;
|
||||
return false; // avoid any interference
|
||||
}
|
||||
}
|
||||
#endif // ESP8266
|
||||
|
||||
// Now see if we need to mix RGB and White
|
||||
// Valid only for LST_RGBW, LST_RGBCW, SetOption105 1, and white is zero (see doc)
|
||||
if ((LST_RGBW <= Light.subtype) && (Settings.flag4.white_blend_mode) && (0 == cur_col_10[3]+cur_col_10[4])) {
|
||||
uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
|
||||
cur_col_10[0] -= min_rgb_10;
|
||||
cur_col_10[1] -= min_rgb_10;
|
||||
cur_col_10[2] -= min_rgb_10;
|
||||
|
||||
// Add to white level
|
||||
uint32_t adjust_w_10 = change8to10(Settings.rgbwwTable[3]); // take the correction factor, bought back to 10 bits
|
||||
white_bri10 += changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10); // set white power down corrected with rgbwwTable[3]
|
||||
white_bri10 = (white_bri10 > 1023) ? 1023 : white_bri10; // max 1023
|
||||
rgbwwtable_applied_white = true;
|
||||
}
|
||||
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
// compute virtual CT, which is suppsed to be compatible with white_blend_mode
|
||||
if (Light.virtual_ct && (!white_free_cw) && (LST_RGBW <= Light.subtype)) { // any light with a white channel
|
||||
vct_pivot_t *pivot = &Light.vct_color[0];
|
||||
uint16_t *from_ct = &Light.vct_ct[0];
|
||||
|
||||
for (uint32_t i = 1; i < CT_PIVOTS-1; i++) {
|
||||
if (ct > Light.vct_ct[i]) { // if above mid-point, take range [1]..[2] instead of [0]..[1]
|
||||
pivot++;
|
||||
from_ct++;
|
||||
}
|
||||
}
|
||||
uint16_t from10[LST_MAX];
|
||||
uint16_t to10[LST_MAX];
|
||||
calcGammaBulb5Channels_8(*pivot, from10);
|
||||
calcGammaBulb5Channels_8(*(pivot+1), to10);
|
||||
|
||||
vct_pivot_t *pivot1 = pivot + 1;
|
||||
// AddLog_P(LOG_LEVEL_INFO, PSTR("+++ from_ct %d, to_ct %d [%03X,%03X,%03X,%03X,%03X] - [%03X,%03X,%03X,%03X,%03X]"),
|
||||
// *from_ct, *(from_ct+1), (*pivot)[0], (*pivot)[1], (*pivot)[2], (*pivot)[3], (*pivot)[4],
|
||||
// (*pivot1)[0], (*pivot1)[1], (*pivot1)[2], (*pivot1)[3], (*pivot1)[4]);
|
||||
// AddLog_P(LOG_LEVEL_INFO, PSTR("+++ from10 [%03X,%03X,%03X,%03X,%03X] - to 10 [%03X,%03X,%03X,%03X,%03X]"),
|
||||
// from10[0],from10[0],from10[0],from10[0],from10[4],
|
||||
// to10[0],to10[0],to10[0],to10[0],to10[4]);
|
||||
|
||||
// set both CW/WW to zero since their previous value don't count anymore
|
||||
cur_col_10[3] = 0;
|
||||
cur_col_10[4] = 0;
|
||||
|
||||
// Add the interpolated point to each component
|
||||
for (uint32_t i = 0; i < LST_MAX; i++) {
|
||||
cur_col_10[i] += changeUIntScale(changeUIntScale(ct, *from_ct, *(from_ct+1), from10[i], to10[i]), 0, 1023, 0, white_bri10);
|
||||
if (cur_col_10[i] > 1023) { cur_col_10[i] = 1023; }
|
||||
}
|
||||
} else
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
// compute the actual levels for CW/WW
|
||||
// We know ct_10 and white_bri_10 (which may be Gamma corrected)
|
||||
// cur_col_10[cw0] and cur_col_10[cw1] were unmodified up to now
|
||||
if (LST_RGBW == Light.subtype) {
|
||||
cur_col_10[3] = white_bri10; // simple case, we set the White level to the required brightness
|
||||
} else if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
||||
// if sum of both channels is > 255, then channels are probably uncorrelated
|
||||
if (!white_free_cw) {
|
||||
// then we split the total energy among the cold and warm leds
|
||||
cur_col_10[cw0+1] = changeUIntScale(ct_10, 0, 1023, 0, white_bri10);
|
||||
cur_col_10[cw0] = white_bri10 - cur_col_10[cw0+1];
|
||||
}
|
||||
}
|
||||
return rgbwwtable_applied_white;
|
||||
}
|
||||
|
||||
#ifdef USE_DEVICE_GROUPS
|
||||
|
@ -3045,6 +3169,83 @@ void CmndWakeupDuration(void)
|
|||
ResponseCmndNumber(Settings.light_wakeup);
|
||||
}
|
||||
|
||||
void CmndCTRange(void)
|
||||
{
|
||||
// Format is "CTRange ctmin,ctmax"
|
||||
// Ex:
|
||||
// CTRange 153,500
|
||||
// CTRange
|
||||
// CTRange 200,350
|
||||
char *p;
|
||||
strtok_r(XdrvMailbox.data, ",", &p);
|
||||
if (p != nullptr) {
|
||||
int32_t ct_min = strtol(XdrvMailbox.data, nullptr, 0);
|
||||
int32_t ct_max = strtol(p, nullptr, 0);
|
||||
if ( (ct_min >= CT_MIN) && (ct_min <= CT_MAX) &&
|
||||
(ct_max >= CT_MIN) && (ct_max <= CT_MAX) &&
|
||||
(ct_min <= ct_max)
|
||||
) {
|
||||
setCTRange(ct_min, ct_max);
|
||||
} else {
|
||||
return; // error
|
||||
}
|
||||
}
|
||||
Response_P(PSTR("{\"%s\":\"%d,%d\"}"), XdrvMailbox.command, Light.vct_ct[0], Light.vct_ct[CT_PIVOTS-1]);
|
||||
}
|
||||
|
||||
#ifdef USE_LIGHT_VIRTUAL_CT
|
||||
void CmndVirtualCT(void)
|
||||
{
|
||||
if (!Settings.flag4.virtual_ct) {
|
||||
ResponseCmndChar_P(PSTR("You need to enable `SetOption106 1`"));
|
||||
return;
|
||||
}
|
||||
if (XdrvMailbox.data[0] == ('{')) {
|
||||
// parse JSON
|
||||
JsonParser parser(XdrvMailbox.data);
|
||||
JsonParserObject root = parser.getRootObject();
|
||||
if (!root) { return; }
|
||||
|
||||
uint32_t idx = 0;
|
||||
for (auto key : root) {
|
||||
if (idx >= CT_PIVOTS) { ResponseCmndChar_P(PSTR("Too many points")); return; }
|
||||
|
||||
int32_t ct_val = strtol(key.getStr(), nullptr, 0);
|
||||
if ((ct_val < CT_MIN) || (ct_val > CT_MAX)) { ResponseCmndChar_P(PSTR("CT out of range")); return; }
|
||||
char * color = (char*) key.getValue().getStr();
|
||||
// call color parser
|
||||
Light.vct_ct[idx] = ct_val;
|
||||
if (LightColorEntry(color, strlen(color))) {
|
||||
memcpy(&Light.vct_color[idx], Light.entry_color, sizeof(Light.vct_color[idx]));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
for (uint32_t i = idx-1; i < CT_PIVOTS-1; i++) {
|
||||
Light.vct_ct[i+1] = Light.vct_ct[i];
|
||||
memcpy(&Light.vct_color[i+1], &Light.vct_color[i], sizeof(Light.vct_color[0]));
|
||||
}
|
||||
}
|
||||
checkVirtualCT();
|
||||
|
||||
Response_P(PSTR("{\"%s\":{"), XdrvMailbox.command);
|
||||
uint32_t pivot_len = CT_PIVOTS;
|
||||
vct_pivot_t * pivot = &Light.vct_color[0];
|
||||
if (Light.vct_ct[1] >= Light.vct_ct[2]) { pivot_len = 2; } // only 2 points are valid
|
||||
|
||||
bool end = false;
|
||||
for (uint32_t i = 0; (i < CT_PIVOTS) && !end; i++) {
|
||||
if ((i >= CT_PIVOTS-1) || (Light.vct_ct[i] >= Light.vct_ct[i+1])) {
|
||||
end = true;
|
||||
}
|
||||
ResponseAppend_P(PSTR("\"%d\":\"%02X%02X%02X%02X%02X\"%c"), Light.vct_ct[i],
|
||||
(*pivot)[0], (*pivot)[1], (*pivot)[2], (*pivot)[3], (*pivot)[4],
|
||||
end ? '}' : ',');
|
||||
pivot++;
|
||||
}
|
||||
ResponseJsonEnd();
|
||||
}
|
||||
#endif // USE_LIGHT_VIRTUAL_CT
|
||||
|
||||
#ifdef USE_LIGHT_PALETTE
|
||||
void CmndPalette(void)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue