Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Light updates #1826

Merged
merged 5 commits into from
Jul 17, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 102 additions & 60 deletions code/espurna/light.ino
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ Ticker _light_comms_ticker;
Ticker _light_save_ticker;
Ticker _light_transition_ticker;

typedef struct {
unsigned char pin;
bool reverse;
bool state;
unsigned char inputValue; // value that has been inputted
unsigned char value; // normalized value including brightness
struct channel_t {
unsigned char pin; // real GPIO pin
bool reverse; // wether we should invert the value before using it
bool state; // is the channel ON
unsigned char inputValue; // raw value, without the brightness
unsigned char value; // normalized value, including brightness
unsigned char target; // target value
double current; // transition value
} channel_t;
};
std::vector<channel_t> _light_channel;

bool _light_state = false;
Expand All @@ -51,15 +51,27 @@ unsigned long _light_steps_left = 1;
unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS;
unsigned int _light_mireds = round((LIGHT_COLDWHITE_MIRED+LIGHT_WARMWHITE_MIRED)/2);

using light_brightness_func_t = void();
light_brightness_func_t* _light_brightness_func = nullptr;

#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
#include <my92xx.h>
my92xx * _my92xx;
ARRAYINIT(unsigned char, _light_channel_map, MY92XX_MAPPING);
#endif

// UI hint about channel distribution
const char _light_channel_desc[5][5] PROGMEM = {
{'W', 0, 0, 0, 0},
{'W', 'C', 0, 0, 0},
{'R', 'G', 'B', 0, 0},
{'R', 'G', 'B', 'W', 0},
{'R', 'G', 'B', 'W', 'C'}
};
static_assert((LIGHT_CHANNELS * LIGHT_CHANNELS) <= (sizeof(_light_channel_desc)), "Out-of-bounds array access");

// Gamma Correction lookup table (8 bit)
// TODO: move to PROGMEM
const unsigned char _light_gamma_table[] = {
const unsigned char _light_gamma_table[] PROGMEM = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6,
Expand All @@ -77,6 +89,7 @@ const unsigned char _light_gamma_table[] = {
191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221,
223, 225, 227, 229, 231, 233, 235, 238, 240, 242, 244, 246, 248, 251, 253, 255
};
static_assert(LIGHT_MAX_VALUE <= sizeof(_light_gamma_table), "Out-of-bounds array access");

// -----------------------------------------------------------------------------
// UTILS
Expand All @@ -93,71 +106,88 @@ void _setCCTInputValue(unsigned char warm, unsigned char cold) {
_light_channel[1].inputValue = constrain(cold, 0, LIGHT_MAX_VALUE);
}

void _generateBrightness() {
void _lightApplyBrightness(unsigned char channels = lightChannels()) {

double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
double brightness = static_cast<double>(_light_brightness) / static_cast<double>(LIGHT_MAX_BRIGHTNESS);

// Convert RGB to RGBW(W)
if (_light_has_color && _light_use_white) {
channels = std::min(channels, lightChannels());

// Substract the common part from RGB channels and add it to white channel. So [250,150,50] -> [200,100,0,50]
unsigned char white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue));
for (unsigned int i=0; i < 3; i++) {
_light_channel[i].value = _light_channel[i].inputValue - white;
}
for (unsigned char i=0; i < lightChannels(); i++) {
if (i >= channels) brightness = 1;
_light_channel[i].value = _light_channel[i].inputValue * brightness;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. I think I missed this interaction with the real values. If bound, it needs to do value = inputValue for all of the leftover channels

}

// Split the White Value across 2 White LED Strips.
if (_light_use_cct) {
}

// This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end.
double miredFactor = ((double) _light_mireds - (double) LIGHT_COLDWHITE_MIRED)/((double) LIGHT_WARMWHITE_MIRED - (double) LIGHT_COLDWHITE_MIRED);
void _lightApplyBrightnessColor() {

// set cold white
_light_channel[3].inputValue = 0;
_light_channel[3].value = round(((double) 1.0 - miredFactor) * white);
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;

// set warm white
_light_channel[4].inputValue = 0;
_light_channel[4].value = round(miredFactor * white);
} else {
_light_channel[3].inputValue = 0;
_light_channel[3].value = white;
}
// Substract the common part from RGB channels and add it to white channel. So [250,150,50] -> [200,100,0,50]
unsigned char white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue));
for (unsigned int i=0; i < 3; i++) {
_light_channel[i].value = _light_channel[i].inputValue - white;
}

// Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63]
unsigned char max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue));
unsigned char max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value));
unsigned char channelSize = _light_use_cct ? 5 : 4;
// Split the White Value across 2 White LED Strips.
if (_light_use_cct) {

if (_light_use_cct) {
max_out = std::max(max_out, _light_channel[4].value);
}
// This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end.
double miredFactor = ((double) _light_mireds - (double) LIGHT_COLDWHITE_MIRED)/((double) LIGHT_WARMWHITE_MIRED - (double) LIGHT_COLDWHITE_MIRED);

double factor = (max_out > 0) ? (double) (max_in / max_out) : 0;
for (unsigned char i=0; i < channelSize; i++) {
_light_channel[i].value = round((double) _light_channel[i].value * factor * brightness);
}
// set cold white
_light_channel[3].inputValue = 0;
_light_channel[3].value = round(((double) 1.0 - miredFactor) * white);

// Scale white channel to match brightness
for (unsigned char i=3; i < channelSize; i++) {
_light_channel[i].value = constrain(_light_channel[i].value * LIGHT_WHITE_FACTOR, 0, LIGHT_MAX_BRIGHTNESS);
}
// set warm white
_light_channel[4].inputValue = 0;
_light_channel[4].value = round(miredFactor * white);
} else {
_light_channel[3].inputValue = 0;
_light_channel[3].value = white;
}

// For the rest of channels, don't apply brightness, it is already in the inputValue
// i should be 4 when RGBW and 5 when RGBWW
for (unsigned char i=channelSize; i < _light_channel.size(); i++) {
_light_channel[i].value = _light_channel[i].inputValue;
}
// Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63]
unsigned char max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue));
unsigned char max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value));
unsigned char channelSize = _light_use_cct ? 5 : 4;

} else {
if (_light_use_cct) {
max_out = std::max(max_out, _light_channel[4].value);
}

// Apply brightness equally to all channels
for (unsigned char i=0; i < _light_channel.size(); i++) {
_light_channel[i].value = _light_channel[i].inputValue * brightness;
}
double factor = (max_out > 0) ? (double) (max_in / max_out) : 0;
for (unsigned char i=0; i < channelSize; i++) {
_light_channel[i].value = round((double) _light_channel[i].value * factor * brightness);
}

// Scale white channel to match brightness
for (unsigned char i=3; i < channelSize; i++) {
_light_channel[i].value = constrain(_light_channel[i].value * LIGHT_WHITE_FACTOR, 0, LIGHT_MAX_BRIGHTNESS);
}

// For the rest of channels, don't apply brightness, it is already in the inputValue
// i should be 4 when RGBW and 5 when RGBWW
for (unsigned char i=channelSize; i < _light_channel.size(); i++) {
_light_channel[i].value = _light_channel[i].inputValue;
}

}

String lightDesc(unsigned char id) {
if (id >= _light_channel.size()) return F("UNKNOWN");

const char tag = pgm_read_byte(&_light_channel_desc[_light_channel.size() - 1][id]);
switch (tag) {
case 'W': return F("WARM WHITE");
case 'C': return F("COLD WHITE");
case 'R': return F("RED");
case 'G': return F("GREEN");
case 'B': return F("BLUE");
default: break;
}

return F("UNKNOWN");
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -428,7 +458,7 @@ void _toCSV(char * buffer, size_t len, bool applyBrightness) {

unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) {
value = constrain(value, 0, LIGHT_MAX_VALUE);
if (gamma) value = _light_gamma_table[value];
if (gamma) value = pgm_read_byte(_light_gamma_table + value);
if (LIGHT_MAX_VALUE != LIGHT_LIMIT_PWM) value = map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_LIMIT_PWM);
if (reverse) value = LIGHT_LIMIT_PWM - value;
return value;
Expand Down Expand Up @@ -752,7 +782,7 @@ void _lightComms(unsigned char mask) {

void lightUpdate(bool save, bool forward, bool group_forward) {

_generateBrightness();
_light_brightness_func();

// Update channels
for (unsigned int i=0; i < _light_channel.size(); i++) {
Expand Down Expand Up @@ -1088,7 +1118,7 @@ void _lightInitCommands() {
lightChannel(id, value);
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("Channel #%d (%s): %d\n"), id, lightDesc(id).c_str(), lightChannel(id));
terminalOK();
});

Expand Down Expand Up @@ -1172,6 +1202,16 @@ void _lightConfigure() {
setSetting("useWhite", _light_use_white);
}

if (_light_has_color) {
if (_light_use_white) {
_light_brightness_func = _lightApplyBrightnessColor;
} else {
_light_brightness_func = []() { _lightApplyBrightness(3); };
}
} else {
_light_brightness_func = []() { _lightApplyBrightness(); };
}

_light_use_cct = getSetting("useCCT", LIGHT_USE_CCT).toInt() == 1;
if (_light_use_cct && (((_light_channel.size() < 5) && (_light_channel.size() != 2)) || !_light_use_white)) {
_light_use_cct = false;
Expand All @@ -1191,6 +1231,8 @@ void lightSetup() {
digitalWrite(LIGHT_ENABLE_PIN, HIGH);
#endif

_light_channel.reserve(LIGHT_CHANNELS);

#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX

_my92xx = new my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND);
Expand Down