/* 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 . */ #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 (serial_in_byte != 0x1B) { if (serial_in_byte_counter >= 140) { serial_in_byte_counter = 0; } if (serial_in_byte_counter || (!serial_in_byte_counter && ('A' == serial_in_byte))) { // A from AT serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } } else { serial_in_buffer[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"), serial_in_buffer); if (!strncmp(serial_in_buffer +3, "RESULT", 6)) { Snfl1.receive_ready = true; } else if (!strncmp(serial_in_buffer +3, "UPDATE", 6)) { char cmnd_dimmer[20]; char cmnd_color[20]; char *end_str; char *string = 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; } 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 == my_module_type) { if ((Pin(GPIO_RXD) < 99) && (Pin(GPIO_TXD) < 99)) { SetSerial(19200, TS_SERIAL_8N1); light_type = LT_RGB; light_flg = 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