mirror of
https://github.com/xoseperez/espurna.git
synced 2026-03-12 03:07:13 +01:00
lights: handle value transformations in the transition
This commit is contained in:
@@ -86,22 +86,32 @@ public:
|
||||
|
||||
struct channel_t {
|
||||
channel_t() = default;
|
||||
explicit channel_t(unsigned char pin_, bool inverse_) :
|
||||
|
||||
// TODO: set & store pin in the provider
|
||||
explicit channel_t(unsigned char pin_, bool inverse_, bool gamma_) :
|
||||
pin(pin_),
|
||||
inverse(inverse_)
|
||||
inverse(inverse_),
|
||||
gamma(gamma_)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
}
|
||||
|
||||
explicit channel_t(unsigned char pin_) :
|
||||
pin(pin_)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
}
|
||||
|
||||
unsigned char pin { GPIO_NONE }; // real GPIO pin
|
||||
bool inverse { false }; // whether we should invert the value before using it
|
||||
bool inverse { false }; // re-map the value from [ValueMin:ValueMax] to [ValueMax:ValueMin]
|
||||
bool gamma { false }; // apply gamma correction to the target value
|
||||
|
||||
bool state { true }; // is the channel ON
|
||||
|
||||
unsigned char inputValue { Light::ValueMin }; // raw value, without the brightness
|
||||
unsigned char value { Light::ValueMin }; // normalized value, including brightness
|
||||
unsigned char target { Light::ValueMin }; // target value
|
||||
float current { Light::ValueMin }; // transition value
|
||||
unsigned char inputValue { Light::ValueMin }; // raw, without the brightness
|
||||
unsigned char value { Light::ValueMin }; // normalized, including brightness
|
||||
unsigned char target { Light::ValueMin }; // resulting value that will be given to the provider
|
||||
float current { Light::ValueMin }; // interim between input and target, used by the transition handler
|
||||
};
|
||||
|
||||
std::vector<channel_t> _light_channels;
|
||||
@@ -161,27 +171,6 @@ my92xx* _my92xx { nullptr };
|
||||
std::unique_ptr<LightProvider> _light_provider;
|
||||
#endif
|
||||
|
||||
// Gamma Correction lookup table (8 bit)
|
||||
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,
|
||||
6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11,
|
||||
12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19,
|
||||
19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28,
|
||||
29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40,
|
||||
41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71,
|
||||
72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 89,
|
||||
91, 92, 93, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 110,
|
||||
112, 113, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 130, 131, 133, 134,
|
||||
136, 137, 139, 140, 142, 144, 145, 147, 149, 150, 152, 154, 155, 157, 159, 160,
|
||||
162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 187, 189,
|
||||
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::ValueMax <= sizeof(_light_gamma_table), "Out-of-bounds array access");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// UTILS
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -292,44 +281,57 @@ bool _lightApplyBrightnessColor() {
|
||||
return changed.get();
|
||||
}
|
||||
|
||||
// UI hint about channel distribution
|
||||
const char* lightDesc(unsigned char id) {
|
||||
char _lightTag(size_t channels, unsigned char index) {
|
||||
constexpr size_t Columns { 5ul };
|
||||
constexpr size_t Rows { 5ul };
|
||||
constexpr char tags[Rows][Columns] = {
|
||||
{'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) <= (Rows * Columns), "Out-of-bounds array access");
|
||||
|
||||
if (channels < Rows) {
|
||||
constexpr char tags[Rows][Columns] = {
|
||||
{'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'},
|
||||
};
|
||||
|
||||
return tags[channels][index];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _lightTag(unsigned char index) {
|
||||
return _lightTag(_light_channels.size(), index);
|
||||
}
|
||||
|
||||
// UI hint about channel distribution
|
||||
const char* _lightDesc(size_t channels, unsigned char index) {
|
||||
const __FlashStringHelper* ptr { F("UNKNOWN") };
|
||||
const size_t channels { _light_channels.size() };
|
||||
if (id < channels) {
|
||||
switch (tags[channels - 1][id]) {
|
||||
case 'W':
|
||||
ptr = F("WARM WHITE");
|
||||
break;
|
||||
case 'C':
|
||||
ptr = F("COLD WHITE");
|
||||
break;
|
||||
case 'R':
|
||||
ptr = F("RED");
|
||||
break;
|
||||
case 'G':
|
||||
ptr = F("GREEN");
|
||||
break;
|
||||
case 'B':
|
||||
ptr = F("BLUE");
|
||||
break;
|
||||
}
|
||||
switch (_lightTag(channels, index)) {
|
||||
case 'W':
|
||||
ptr = F("WARM WHITE");
|
||||
break;
|
||||
case 'C':
|
||||
ptr = F("COLD WHITE");
|
||||
break;
|
||||
case 'R':
|
||||
ptr = F("RED");
|
||||
break;
|
||||
case 'G':
|
||||
ptr = F("GREEN");
|
||||
break;
|
||||
case 'B':
|
||||
ptr = F("BLUE");
|
||||
break;
|
||||
}
|
||||
|
||||
return reinterpret_cast<const char*>(ptr);
|
||||
}
|
||||
|
||||
const char* _lightDesc(unsigned char index) {
|
||||
return _lightDesc(_light_channels.size(), index);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Input Values
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -655,6 +657,32 @@ void _lightAdjustMireds(const String& payload) {
|
||||
|
||||
namespace {
|
||||
|
||||
// Gamma Correction lookup table (8 bit, ~2.2)
|
||||
// (note that the table could be constexpr, *but* the whole function needs to be constexpr as well)
|
||||
uint8_t _lightGammaMap(unsigned char value) {
|
||||
static uint8_t gamma[256] 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,
|
||||
6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11,
|
||||
12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19,
|
||||
19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28,
|
||||
29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40,
|
||||
41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, 54,
|
||||
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71,
|
||||
72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 89,
|
||||
91, 92, 93, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 110,
|
||||
112, 113, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 130, 131, 133, 134,
|
||||
136, 137, 139, 140, 142, 144, 145, 147, 149, 150, 152, 154, 155, 157, 159, 160,
|
||||
162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 187, 189,
|
||||
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::ValueMax < (sizeof(gamma) / sizeof(gamma[0])), "Out-of-bounds array access");
|
||||
|
||||
return pgm_read_byte(&gamma[value]);
|
||||
}
|
||||
|
||||
class LightTransitionHandler {
|
||||
public:
|
||||
using Channels = std::vector<channel_t>;
|
||||
@@ -692,7 +720,17 @@ public:
|
||||
|
||||
bool prepare(channel_t& channel, bool state) {
|
||||
bool target_state = state && channel.state;
|
||||
|
||||
channel.target = target_state ? channel.value : Light::ValueMin;
|
||||
if (target_state) {
|
||||
if (channel.gamma) {
|
||||
channel.target = _lightGammaMap(channel.target);
|
||||
}
|
||||
|
||||
if (channel.inverse) {
|
||||
channel.target = Light::ValueMax - channel.target;
|
||||
}
|
||||
}
|
||||
|
||||
float diff = static_cast<float>(channel.target) - channel.current;
|
||||
if (isImmediateTransition(target_state, diff)) {
|
||||
@@ -868,29 +906,11 @@ inline bool _lightPwmMap(long value, long& result) {
|
||||
|
||||
// both require original values to be scaled into a PWM frequency
|
||||
void _lightProviderHandleValue(unsigned char channel, float value) {
|
||||
// TODO: strict rule in the transition itself?
|
||||
if (value < 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: have 'red', 'green' or 'blue' tag instead of using hard-coded index offset?
|
||||
auto gamma = _light_use_gamma && _light_has_color && (channel < 3);
|
||||
auto inverse = _light_channels[channel].inverse;
|
||||
|
||||
auto rounded = std::lround(value);
|
||||
if (gamma) {
|
||||
rounded = pgm_read_byte(_light_gamma_table + rounded);
|
||||
}
|
||||
|
||||
long pwm;
|
||||
if (!_lightPwmMap(rounded, pwm)) {
|
||||
if (!_lightPwmMap(std::lround(value), pwm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inverse) {
|
||||
pwm = Light::PwmLimit + Light::PwmMin - pwm;
|
||||
}
|
||||
|
||||
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
|
||||
pwm_set_duty(pwm, channel);
|
||||
#elif LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
|
||||
@@ -1586,7 +1606,8 @@ void _lightInitCommands() {
|
||||
});
|
||||
|
||||
terminalRegisterCommand(F("CHANNEL"), [](const terminal::CommandContext& ctx) {
|
||||
if (!lightChannels()) {
|
||||
auto channels = lightChannels();
|
||||
if (channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1596,11 +1617,11 @@ void _lightInitCommands() {
|
||||
}
|
||||
|
||||
auto description = [&](unsigned char channel) {
|
||||
ctx.output.printf("#%u (%s): %ld\n", channel, lightDesc(channel), lightChannel(channel));
|
||||
ctx.output.printf("#%hhu (%s): %hhu\n", channel, _lightDesc(channels, channel), _light_channels[channel].inputValue);
|
||||
};
|
||||
|
||||
if (id < 0 || id >= static_cast<decltype(id)>(lightChannels())) {
|
||||
for (unsigned char index = 0; index < lightChannels(); ++index) {
|
||||
if (id < 0 || id >= static_cast<decltype(id)>(channels)) {
|
||||
for (unsigned char index = 0; index < channels; ++index) {
|
||||
description(index);
|
||||
}
|
||||
return;
|
||||
@@ -1909,16 +1930,39 @@ const unsigned long _light_iofunc[16] PROGMEM = {
|
||||
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
inline bool _lightUseGamma(size_t channels, unsigned char index) {
|
||||
if (_light_has_color && _light_use_gamma) {
|
||||
switch (_lightTag(channels, index)) {
|
||||
case 'R':
|
||||
case 'G':
|
||||
case 'B':
|
||||
return true;
|
||||
case 'W':
|
||||
case 'C':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool _lightUseGamma(unsigned char index) {
|
||||
return _lightUseGamma(_light_channels.size(), index);
|
||||
}
|
||||
|
||||
void _lightConfigure() {
|
||||
auto channels = _light_channels.size();
|
||||
|
||||
_light_has_color = getSetting("useColor", 1 == LIGHT_USE_COLOR);
|
||||
if (_light_has_color && (_light_channels.size() < 3)) {
|
||||
if (_light_has_color && (channels < 3)) {
|
||||
_light_has_color = false;
|
||||
setSetting("useColor", _light_has_color);
|
||||
}
|
||||
|
||||
_light_use_white = getSetting("useWhite", 1 == LIGHT_USE_WHITE);
|
||||
if (_light_use_white && (_light_channels.size() < 4) && (_light_channels.size() != 2)) {
|
||||
if (_light_use_white && (channels < 4) && (channels != 2)) {
|
||||
_light_use_white = false;
|
||||
setSetting("useWhite", _light_use_white);
|
||||
}
|
||||
@@ -1934,23 +1978,27 @@ void _lightConfigure() {
|
||||
}
|
||||
|
||||
_light_use_cct = getSetting("useCCT", 1 == LIGHT_USE_CCT);
|
||||
if (_light_use_cct && (((_light_channels.size() < 5) && (_light_channels.size() != 2)) || !_light_use_white)) {
|
||||
if (_light_use_cct && (((channels < 5) && (channels != 2)) || !_light_use_white)) {
|
||||
_light_use_cct = false;
|
||||
setSetting("useCCT", _light_use_cct);
|
||||
}
|
||||
|
||||
_light_cold_mireds = getSetting("lightColdMired", Light::MiredsCold);
|
||||
_light_warm_mireds = getSetting("lightWarmMired", Light::MiredsWarm);
|
||||
_light_cold_mireds = getSetting("ltColdMired", Light::MiredsCold);
|
||||
_light_warm_mireds = getSetting("ltWarmMired", Light::MiredsWarm);
|
||||
_light_cold_kelvin = (1000000L / _light_cold_mireds);
|
||||
_light_warm_kelvin = (1000000L / _light_warm_mireds);
|
||||
|
||||
_light_use_gamma = getSetting("useGamma", 1 == LIGHT_USE_GAMMA);
|
||||
_light_use_transitions = getSetting("useTransitions", 1 == LIGHT_USE_TRANSITIONS);
|
||||
_light_save = getSetting("ltSave", 1 == LIGHT_SAVE_ENABLED);
|
||||
_light_save_delay = getSetting("ltSaveDelay", LIGHT_SAVE_DELAY);
|
||||
_light_transition_time = getSetting("ltTime", LIGHT_TRANSITION_TIME);
|
||||
_light_transition_step = getSetting("ltStep", LIGHT_TRANSITION_STEP);
|
||||
|
||||
_light_use_gamma = getSetting("useGamma", 1 == LIGHT_USE_GAMMA);
|
||||
for (unsigned char index = 0; index < lightChannels(); ++index) {
|
||||
_light_channels[index].inverse = getSetting({"ltInv", index}, Light::build::inverse(index));
|
||||
_light_channels[index].gamma = _lightUseGamma(channels, index);
|
||||
}
|
||||
}
|
||||
|
||||
#if RELAY_SUPPORT
|
||||
@@ -1992,6 +2040,8 @@ void _lightBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if LIGHT_PROVIDER == LIGHT_PROVIDER_CUSTOM
|
||||
|
||||
// Custom provider is expected to:
|
||||
@@ -2016,7 +2066,7 @@ bool lightAdd() {
|
||||
}
|
||||
|
||||
if (_light_channels.size() < Light::ChannelsMax) {
|
||||
_light_channels.push_back(channel_t());
|
||||
_light_channels.emplace_back(GPIO_NONE);
|
||||
if (State::Scheduled != state) {
|
||||
state = State::Scheduled;
|
||||
schedule_function([]() {
|
||||
@@ -2068,12 +2118,14 @@ void _lightSettingsMigrate(int version) {
|
||||
delSetting("lightProvider");
|
||||
|
||||
moveSetting("lightTime", "ltTime");
|
||||
moveSetting("lightColdMired", "ltColdMired");
|
||||
moveSetting("lightWarmMired", "ltWarmMired");
|
||||
}
|
||||
|
||||
void lightSetup() {
|
||||
_lightSettingsMigrate(migrateVersion());
|
||||
|
||||
const auto enable_pin = getSetting("ltEnableGPIO", _lightEnablePin());
|
||||
const auto enable_pin = getSetting("ltEnableGPIO", Light::build::enablePin());
|
||||
if (enable_pin != GPIO_NONE) {
|
||||
pinMode(enable_pin, OUTPUT);
|
||||
digitalWrite(enable_pin, HIGH);
|
||||
@@ -2087,7 +2139,7 @@ void lightSetup() {
|
||||
{
|
||||
_my92xx = new my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND);
|
||||
for (unsigned char index = 0; index < Light::Channels; ++index) {
|
||||
_light_channels.emplace_back(GPIO_NONE, getSetting({"ltMy92xxInv", index}, _lightInverse(index)));
|
||||
_light_channels.emplace_back(GPIO_NONE);
|
||||
}
|
||||
}
|
||||
#elif LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
|
||||
@@ -2101,12 +2153,12 @@ void lightSetup() {
|
||||
for (unsigned char index = 0; index < Light::ChannelsMax; ++index) {
|
||||
|
||||
// Load up until first GPIO_NONE. Allow settings to override, but not remove values
|
||||
const auto pin = getSetting({"ltDimmerGPIO", index}, _lightChannelPin(index));
|
||||
const auto pin = getSetting({"ltDimmerGPIO", index}, Light::build::channelPin(index));
|
||||
if (!gpioValid(pin)) {
|
||||
break;
|
||||
}
|
||||
|
||||
_light_channels.emplace_back(pin, getSetting({"ltDimmerInv", index}, _lightInverse(index)));
|
||||
_light_channels.emplace_back(pin);
|
||||
|
||||
io_info[index][0] = pgm_read_dword(&_light_iomux[pin]);
|
||||
io_info[index][1] = pgm_read_dword(&_light_iofunc[pin]);
|
||||
|
||||
Reference in New Issue
Block a user