Tasmota/tasmota/xlgt_05_sonoff_l1.ino

257 lines
9.0 KiB
C++

/*
xlgt_05_sonoff_l1.ino - Sonoff L1 led support for Tasmota
Copyright (C) 2020 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_LIGHT
#ifdef USE_SONOFF_L1
/*********************************************************************************************\
* Sonoff L1
\*********************************************************************************************/
#define XLGT_05 5
#define SONOFF_L1_BUFFER_SIZE 140
#define SONOFF_L1_MODE_COLORFUL 1 // [Color key] Colorful (static color)
#define SONOFF_L1_MODE_COLORFUL_GRADIENT 2 // [SMOOTH] Colorful Gradient
#define SONOFF_L1_MODE_COLORFUL_BREATH 3 // [FADE] Colorful Breath
#define SONOFF_L1_MODE_DIY_GRADIENT 4 // DIY Gradient (fade in and out) [Speed 1- 100, color]
#define SONOFF_L1_MODE_DIY_PULSE 5 // DIY Pulse (faster fade in and out) [Speed 1- 100, color]
#define SONOFF_L1_MODE_DIY_BREATH 6 // DIY Breath (toggle on/off) [Speed 1- 100, color]
#define SONOFF_L1_MODE_DIY_STROBE 7 // DIY Strobe (faster toggle on/off) [Speed 1- 100, color]
#define SONOFF_L1_MODE_RGB_GRADIENT 8 // RGB Gradient
#define SONOFF_L1_MODE_RGB_PULSE 9 // [STROBE] RGB Pulse
#define SONOFF_L1_MODE_RGB_BREATH 10 // RGB Breath
#define SONOFF_L1_MODE_RGB_STROBE 11 // [FLASH] RGB strobe
#define SONOFF_L1_MODE_SYNC_TO_MUSIC 12 // Sync to music [Speed 1- 100, sensitivity 1 - 10]
struct SNFL1 {
uint32_t unlock = 0;
bool receive_ready = true;
} Snfl1;
/********************************************************************************************/
void SnfL1Send(const char *buffer)
{
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Send %s"), buffer);
Serial.print(buffer);
Serial.write(0x1B);
Serial.flush();
}
void SnfL1SerialSendOk(void)
{
char buffer[16];
snprintf_P(buffer, sizeof(buffer), PSTR("AT+SEND=ok"));
SnfL1Send(buffer);
}
bool SnfL1SerialInput(void)
{
if (TasmotaGlobal.serial_in_byte != 0x1B) {
if (TasmotaGlobal.serial_in_byte_counter >= 140) {
TasmotaGlobal.serial_in_byte_counter = 0;
}
if (TasmotaGlobal.serial_in_byte_counter || (!TasmotaGlobal.serial_in_byte_counter && ('A' == TasmotaGlobal.serial_in_byte))) { // A from AT
TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter++] = TasmotaGlobal.serial_in_byte;
}
} else {
TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter++] = 0x00;
// AT+RESULT="sequence":"1554682835320"
// AT+UPDATE="sequence":"34906","switch":"on","light_type":1,"colorR":0,"colorG":16,"colorB":0,"bright":6,"mode":1
// AT+UPDATE="switch":"on","light_type":1,"colorR":255,"colorG":0,"colorB":0,"bright":6,"mode":1,"speed":100,"sensitive":10
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Rcvd %s"), TasmotaGlobal.serial_in_buffer);
if (!strncmp(TasmotaGlobal.serial_in_buffer +3, "RESULT", 6)) {
Snfl1.receive_ready = true;
}
else if (!strncmp(TasmotaGlobal.serial_in_buffer +3, "UPDATE", 6)) {
char cmnd_dimmer[20];
char cmnd_color[20];
char *end_str;
char *string = TasmotaGlobal.serial_in_buffer +10;
char *token = strtok_r(string, ",", &end_str);
bool color_updated[3] = { false, false, false };
uint8_t current_color[3];
memcpy(current_color, Settings.light_color, 3);
bool switch_state = false;
bool is_power_change = false;
bool is_color_change = false;
bool is_brightness_change = false;
while (token != nullptr) {
char* end_token;
char* token2 = strtok_r(token, ":", &end_token);
char* token3 = strtok_r(nullptr, ":", &end_token);
if (!strncmp(token2, "\"sequence\"", 10)) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Rcvd sequence %s"), token3);
token = nullptr;
}
else if (!strncmp(token2, "\"switch\"", 8)) {
switch_state = !strncmp(token3, "\"on\"", 4) ? true : false;
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Rcvd switch %d (%d)"), switch_state, Light.power);
is_power_change = (switch_state != Light.power);
}
else if (!strncmp(token2, "\"color", 6)) {
char color_channel_name = token2[6];
int color_index;
switch(color_channel_name)
{
case 'R': color_index = 0;
break;
case 'G': color_index = 1;
break;
case 'B': color_index = 2;
break;
}
int color_value = atoi(token3);
current_color[color_index] = color_value;
color_updated[color_index] = true;
bool all_color_channels_updated = color_updated[0] && color_updated[1] && color_updated[2];
if (all_color_channels_updated) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Rcvd color R%d G%d B%d (R%d G%d B%d)"),
// current_color[0], current_color[1], current_color[2],
// Settings.light_color[0], Settings.light_color[1], Settings.light_color[2]);
is_color_change = (Light.power && (memcmp(current_color, Settings.light_color, 3) != 0));
}
snprintf_P(cmnd_color, sizeof(cmnd_color), PSTR(D_CMND_COLOR "2 %02x%02x%02x"), current_color[0], current_color[1], current_color[2]);
}
else if (!strncmp(token2, "\"bright\"", 8)) {
uint8_t dimmer = atoi(token3);
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SL1: Rcvd dimmer %d (%d)"), dimmer, Settings.light_dimmer);
is_brightness_change = (Light.power && (dimmer > 0) && (dimmer != Settings.light_dimmer));
snprintf_P(cmnd_dimmer, sizeof(cmnd_dimmer), PSTR(D_CMND_DIMMER " %d"), dimmer);
}
token = strtok_r(nullptr, ",", &end_str);
}
if (is_power_change) {
if (Settings.light_scheme > 0) {
if (!switch_state) { // If power off RC button pressed stop schemes
char cmnd_scheme[20];
snprintf_P(cmnd_scheme, sizeof(cmnd_scheme), PSTR(D_CMND_SCHEME " 0"));
ExecuteCommand(cmnd_scheme, SRC_SWITCH);
}
} else {
ExecuteCommandPower(1, switch_state, SRC_SWITCH);
}
}
else if (is_brightness_change) {
ExecuteCommand(cmnd_dimmer, SRC_SWITCH);
}
else if (Light.power && is_color_change) {
if (0 == Settings.light_scheme) { // Fix spurious color receptions when scheme > 0
if (Settings.light_fade) { // Disable fade as RC button colors overrule and are immediate supressing ghost colors
char cmnd_fade[20];
snprintf_P(cmnd_fade, sizeof(cmnd_fade), PSTR(D_CMND_FADE " 0"));
ExecuteCommand(cmnd_fade, SRC_SWITCH);
}
ExecuteCommand(cmnd_color, SRC_SWITCH);
}
}
}
SnfL1SerialSendOk();
return true;
}
TasmotaGlobal.serial_in_byte = 0;
return false;
}
/********************************************************************************************/
bool SnfL1SetChannels(void)
{
if (Snfl1.receive_ready || TimeReached(Snfl1.unlock)) {
uint8_t *scale_col = (uint8_t*)XdrvMailbox.topic;
char buffer[140];
snprintf_P(buffer, sizeof(buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d"),
LocalTime(), millis()%1000,
Light.power ? "on" : "off",
scale_col[0], scale_col[1], scale_col[2],
light_state.getDimmer(),
SONOFF_L1_MODE_COLORFUL);
SnfL1Send(buffer);
Snfl1.unlock = millis() + 500; // Allow time for the RC
Snfl1.receive_ready = false;
}
return true;
}
void SnfL1ModuleSelected(void)
{
if (SONOFF_L1 == TasmotaGlobal.module_type) {
if (PinUsed(GPIO_RXD) && PinUsed(GPIO_TXD)) {
SetSerial(19200, TS_SERIAL_8N1);
TasmotaGlobal.light_type = LT_RGB;
TasmotaGlobal.light_driver = XLGT_05;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Sonoff L1 Found"));
}
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xlgt05(uint8_t function)
{
bool result = false;
switch (function) {
case FUNC_SERIAL:
result = SnfL1SerialInput();
break;
case FUNC_SET_CHANNELS:
result = SnfL1SetChannels();
break;
case FUNC_MODULE_INIT:
SnfL1ModuleSelected();
break;
}
return result;
}
#endif // USE_SONOFF_L1
#endif // USE_LIGHT