├── .editorconfig ├── .gitignore ├── .travis.yml ├── Arilux.cpp ├── Arilux.h ├── Arilux_AL-LC0X.ino ├── LICENSE ├── README.md ├── config.example.h ├── images ├── Arilux.png ├── ESP12-F_pinout3.jpg ├── Layout.JPG ├── Youtube.png └── option2.jpg └── travis.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | # Trailing whitespace is significant in markdown files. 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | install: 3 | - source <(curl -SLs https://raw.githubusercontent.com/fede2cr/travis-ci-arduino/master/install.sh) 4 | before_script: 5 | - arduino --install-library "ArduinoJson,IRremoteESP8266,PubSubClient,rc-switch" 6 | - ./travis.sh 7 | script: build_platform esp8266 8 | env: 9 | - RGB_TYPE=RGB 10 | - RGB_TYPE=RGBW 11 | - RGB_TYPE=RGBWW 12 | - REMOTE_TYPE=IR_REMOTE 13 | - REMOTE_TYPE=RF_REMOTE 14 | - MQTT_MODE=JSON 15 | - MQTT_MODE=JSON RGB_TYPE=RGBW 16 | - MQTT_DISCOVERY=HOME_ASSISTANT_MQTT_DISCOVERY 17 | - MQTT_DISCOVERY=HOME_ASSISTANT_MQTT_DISCOVERY RGB_TYPE=RGBW 18 | matrix: 19 | fast_finish: true 20 | -------------------------------------------------------------------------------- /Arilux.cpp: -------------------------------------------------------------------------------- 1 | #include "Arilux.h" 2 | 3 | Arilux::Arilux(void): 4 | m_redPin(ARILUX_RED_PIN), 5 | m_greenPin(ARILUX_GREEN_PIN), 6 | m_bluePin(ARILUX_BLUE_PIN) 7 | #if defined(RGBW) || defined (RGBWW) 8 | , m_white1Pin(ARILUX_WHITE1_PIN) 9 | #ifdef RGBWW 10 | , m_white2Pin(ARILUX_WHITE2_PIN) 11 | #endif 12 | #endif 13 | { 14 | m_state = false; 15 | m_brightness = ARILUX_PWM_RANGE / 3; 16 | m_color.red = ARILUX_PWM_RANGE; 17 | m_color.green = ARILUX_PWM_RANGE; 18 | m_color.blue = ARILUX_PWM_RANGE; 19 | m_color.white1 = ARILUX_PWM_RANGE; 20 | m_color.white2 = ARILUX_PWM_RANGE; 21 | } 22 | 23 | uint8_t Arilux::init(void) { 24 | pinMode(m_redPin, OUTPUT); 25 | pinMode(m_greenPin, OUTPUT); 26 | pinMode(m_bluePin, OUTPUT); 27 | 28 | #if defined(RGBW) || defined (RGBWW) 29 | pinMode(m_white1Pin, OUTPUT); 30 | #ifdef RGBWW 31 | pinMode(m_white2Pin, OUTPUT); 32 | #endif 33 | #endif 34 | analogWriteFreq(ARILUX_PWM_FREQUENCY); 35 | analogWriteRange(ARILUX_PWM_RANGE); 36 | 37 | return true; 38 | } 39 | 40 | uint8_t Arilux::getState(void) { 41 | return m_state; 42 | } 43 | 44 | uint8_t Arilux::turnOn(void) { 45 | return setState(true); 46 | } 47 | 48 | uint8_t Arilux::turnOff(void) { 49 | return setState(false); 50 | } 51 | 52 | char *Arilux::getColorString(void) { 53 | return (char *)ARILUX_COLOR_STRING; 54 | } 55 | 56 | uint8_t Arilux::setState(uint8_t p_state) { 57 | if (p_state == true) { 58 | m_state = true; 59 | return setAll(m_color.red, m_color.green, m_color.blue, m_color.white1, m_color.white2, false); 60 | } else { 61 | m_state = false; 62 | return setAll(0, 0, 0, 0, 0, false); 63 | } 64 | } 65 | 66 | uint8_t Arilux::getBrightness(void) { 67 | return m_brightness; 68 | } 69 | 70 | uint8_t Arilux::increaseBrightness(void) { 71 | if (!m_state) 72 | return false; 73 | if ((m_brightness + ARILUX_BRIGHTNESS_STEP) >= ARILUX_PWM_RANGE) 74 | return setBrightness(ARILUX_PWM_RANGE); 75 | if (m_brightness < (ARILUX_PWM_RANGE - ARILUX_BRIGHTNESS_STEP)) 76 | return setBrightness(m_brightness + ARILUX_BRIGHTNESS_STEP); 77 | return false; 78 | } 79 | 80 | uint8_t Arilux::decreaseBrightness(void) { 81 | if (!m_state) 82 | return false; 83 | 84 | if (m_brightness > (1 + ARILUX_BRIGHTNESS_STEP)) 85 | return setBrightness(m_brightness - ARILUX_BRIGHTNESS_STEP); 86 | return false; 87 | } 88 | 89 | uint8_t Arilux::setBrightness(uint8_t p_brightness) { 90 | if (p_brightness < 0 || p_brightness > ARILUX_PWM_RANGE) 91 | return false; 92 | 93 | m_brightness = p_brightness; 94 | 95 | if (!m_state) 96 | return false; 97 | 98 | analogWrite(m_redPin, map(m_color.red, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 99 | analogWrite(m_greenPin, map(m_color.green, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 100 | analogWrite(m_bluePin, map(m_color.blue, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 101 | #if defined(RGBW) || defined (RGBWW) 102 | analogWrite(m_white1Pin, map(m_color.white1, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 103 | #ifdef RGBWW 104 | analogWrite(m_white2Pin, map(m_color.white2, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 105 | #endif 106 | #endif 107 | return true; 108 | } 109 | 110 | uint8_t Arilux::getRedValue(void) { 111 | return m_color.red; 112 | } 113 | 114 | uint8_t Arilux::getGreenValue(void) { 115 | return m_color.green; 116 | } 117 | 118 | uint8_t Arilux::getBlueValue(void) { 119 | return m_color.blue; 120 | } 121 | 122 | uint8_t Arilux::getWhite1Value(void) { 123 | return m_color.white1; 124 | } 125 | 126 | uint8_t Arilux::getWhite2Value(void) { 127 | return m_color.white2; 128 | } 129 | 130 | uint8_t Arilux::setAll(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white1, uint8_t p_white2) { 131 | if (!m_state) 132 | return false; 133 | 134 | return setAll(p_red, p_green, p_blue, p_white1, p_white2, true); 135 | } 136 | 137 | uint8_t Arilux::setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) { 138 | if (!m_state) 139 | m_state = true; 140 | return setAll(p_red, p_green, p_blue, getWhite1Value(), getWhite2Value(), true); 141 | } 142 | 143 | uint8_t Arilux::setFadeToColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) { 144 | m_color.red = p_red; 145 | m_color.green = p_green; 146 | m_color.blue = p_blue; 147 | } 148 | 149 | uint8_t Arilux::setFadeColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) { 150 | if (!m_state) { 151 | m_state = true; 152 | } 153 | return setAll(p_red, p_green, p_blue, getWhite1Value(), getWhite2Value(), false); 154 | } 155 | 156 | uint8_t Arilux::setWhite(uint8_t p_white1, uint8_t p_white2) { 157 | return setAll(getRedValue(), getGreenValue(), getBlueValue(), p_white1, p_white2, true); 158 | } 159 | 160 | uint8_t Arilux::setAll(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white1, uint8_t p_white2, uint8_t p_retain) { 161 | if ((p_red < 0 || p_red > ARILUX_PWM_RANGE) || (p_green < 0 || p_green > ARILUX_PWM_RANGE) || (p_blue < 0 || p_blue > ARILUX_PWM_RANGE || p_white1 < 0 || p_white1 > ARILUX_PWM_RANGE || p_white2 < 0 || p_white2 > ARILUX_PWM_RANGE)) 162 | return false; 163 | 164 | if (p_retain) { 165 | m_color.red = p_red; 166 | m_color.green = p_green; 167 | m_color.blue = p_blue; 168 | m_color.white1 = p_white1; 169 | m_color.white2 = p_white2; 170 | } 171 | 172 | analogWrite(m_redPin, map(p_red, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 173 | analogWrite(m_greenPin, map(p_green, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 174 | analogWrite(m_bluePin, map(p_blue, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 175 | #if defined(RGBW) || defined (RGBWW) 176 | analogWrite(m_white1Pin, map(p_white1, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 177 | #ifdef RGBWW 178 | analogWrite(m_white2Pin, map(p_white2, 0, ARILUX_PWM_RANGE, 0, m_brightness)); 179 | #endif 180 | #endif 181 | return true; 182 | } 183 | uint8_t Arilux::setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_retain) { 184 | return setAll(p_red, p_green, p_blue, getWhite1Value(), getWhite2Value(), p_retain); 185 | } 186 | uint8_t Arilux::setWhite( uint8_t p_white1, uint8_t p_white2, uint8_t p_retain) { 187 | return setAll(getRedValue(), getGreenValue(), getBlueValue(), p_white1, p_white2, p_retain); 188 | } 189 | 190 | -------------------------------------------------------------------------------- /Arilux.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #pragma once 3 | #ifndef _ARILUX_H_ 4 | #define _ARILUX_H_ 5 | 6 | #include // https://github.com/esp8266/Arduino 7 | #if defined(RGBWW) 8 | #define ARILUX_RED_PIN 5 9 | #define ARILUX_GREEN_PIN 4 10 | #define ARILUX_BLUE_PIN 14 11 | #define ARILUX_WHITE1_PIN 12 12 | #define ARILUX_WHITE2_PIN 13 13 | #define ARILUX_COLOR_STRING "RGBWW" 14 | #elif defined(RGBW) 15 | #define ARILUX_RED_PIN 5 16 | #define ARILUX_GREEN_PIN 14 17 | #define ARILUX_BLUE_PIN 12 18 | #define ARILUX_WHITE1_PIN 13 19 | #define ARILUX_COLOR_STRING "RGBW" 20 | #else 21 | #define ARILUX_RED_PIN 14 22 | #define ARILUX_GREEN_PIN 5 23 | #define ARILUX_BLUE_PIN 12 24 | #define ARILUX_COLOR_STRING "RGB" 25 | #endif 26 | 27 | #define ARILUX_IR_PIN 4 28 | #define ARILUX_RF_PIN 4 29 | 30 | #define ARILUX_PWM_RANGE 255 31 | #define ARILUX_PWM_FREQUENCY 500 32 | 33 | #define ARILUX_BRIGHTNESS_STEP 25 34 | 35 | /* 36 | IR Remote 37 | Encoding: NEC 38 | +------+------+------+------+ 39 | | UP | Down | OFF | ON | 40 | +------+------+------+------+ 41 | | R | G | B | W | 42 | +------+------+------+------+ 43 | | 1 | 2 | 3 |FLASH | 44 | +------+------+------+------+ 45 | | 4 | 5 | 6 |STROBE| 46 | +------+------+------+------+ 47 | | 7 | 8 | 9 | FADE | 48 | +------+------+------+------+ 49 | | 10 | 11 | 12 |SMOOTH| 50 | +------+------+------+------+ 51 | */ 52 | 53 | #ifdef IR_REMOTE 54 | #define ARILUX_IR_CODE_KEY_UP 0xFF906F 55 | #define ARILUX_IR_CODE_KEY_DOWN 0xFFB847 56 | #define ARILUX_IR_CODE_KEY_OFF 0xFFF807 57 | #define ARILUX_IR_CODE_KEY_ON 0xFFB04F 58 | #define ARILUX_IR_CODE_KEY_R 0xFF9867 59 | #define ARILUX_IR_CODE_KEY_G 0xFFD827 60 | #define ARILUX_IR_CODE_KEY_B 0xFF8877 61 | #define ARILUX_IR_CODE_KEY_W 0xFFA857 62 | #define ARILUX_IR_CODE_KEY_1 0xFFE817 63 | #define ARILUX_IR_CODE_KEY_2 0xFF48B7 64 | #define ARILUX_IR_CODE_KEY_3 0xFF6897 65 | #define ARILUX_IR_CODE_KEY_FLASH 0xFFB24D 66 | #define ARILUX_IR_CODE_KEY_4 0xFF02FD 67 | #define ARILUX_IR_CODE_KEY_5 0xFF32CD 68 | #define ARILUX_IR_CODE_KEY_6 0xFF20DF 69 | #define ARILUX_IR_CODE_KEY_STROBE 0xFF00FF 70 | #define ARILUX_IR_CODE_KEY_7 0xFF50AF 71 | #define ARILUX_IR_CODE_KEY_8 0xFF7887 72 | #define ARILUX_IR_CODE_KEY_9 0xFF708F 73 | #define ARILUX_IR_CODE_KEY_FADE 0xFF58A7 74 | #define ARILUX_IR_CODE_KEY_10 0xFF38C7 75 | #define ARILUX_IR_CODE_KEY_11 0xFF28D7 76 | #define ARILUX_IR_CODE_KEY_12 0xFFF00F 77 | #define ARILUX_IR_CODE_KEY_SMOOTH 0xFF30CF 78 | #endif 79 | 80 | /* 81 | RF Remote 82 | Encoding: Chinese Protocol 1 83 | Codes provided by KmanOz (https://github.com/KmanOz) 84 | +--------+--------+--------+ 85 | | ON | Toggle | OFF | 86 | +--------+--------+--------+ 87 | | Speed+ | Mode+ | Bright+| 88 | +--------+--------+--------+ 89 | | Speed- | Mode- | Bright-| 90 | +--------+--------+--------+ 91 | | RED | GREEN | BLUE | 92 | +--------+--------+--------+ 93 | | ORANGE | LT GRN | LT BLUE| 94 | +--------+--------+--------+ 95 | | AMBER | CYAN | PURPLE | 96 | +--------+--------+--------+ 97 | | YELLOW | PINK | WHITE | 98 | +--------+--------+--------+ 99 | */ 100 | 101 | #ifdef RF_REMOTE 102 | #define ARILUX_RF_CODE_KEY_ON 7808513 103 | #define ARILUX_RF_CODE_KEY_TOGGLE 7808514 104 | #define ARILUX_RF_CODE_KEY_OFF 7808515 105 | #define ARILUX_RF_CODE_KEY_SPEED_PLUS 7808516 106 | #define ARILUX_RF_CODE_KEY_MODE_PLUS 7808517 107 | #define ARILUX_RF_CODE_KEY_BRIGHT_PLUS 7808518 108 | #define ARILUX_RF_CODE_KEY_SPEED_MINUS 7808519 109 | #define ARILUX_RF_CODE_KEY_MODE_MINUS 7808520 110 | #define ARILUX_RF_CODE_KEY_BRIGHT_MINUS 7808521 111 | #define ARILUX_RF_CODE_KEY_RED 7808522 112 | #define ARILUX_RF_CODE_KEY_GREEN 7808523 113 | #define ARILUX_RF_CODE_KEY_BLUE 7808524 114 | #define ARILUX_RF_CODE_KEY_ORANGE 7808525 115 | #define ARILUX_RF_CODE_KEY_LTGRN 7808526 116 | #define ARILUX_RF_CODE_KEY_LTBLUE 7808527 117 | #define ARILUX_RF_CODE_KEY_AMBER 7808528 118 | #define ARILUX_RF_CODE_KEY_CYAN 7808529 119 | #define ARILUX_RF_CODE_KEY_PURPLE 7808530 120 | #define ARILUX_RF_CODE_KEY_YELLOW 7808531 121 | #define ARILUX_RF_CODE_KEY_PINK 7808532 122 | #define ARILUX_RF_CODE_KEY_WHITE 7808533 123 | #endif 124 | 125 | enum { 126 | ARILUX_CMD_NOT_DEFINED, 127 | ARILUX_CMD_STATE_CHANGED, 128 | ARILUX_CMD_BRIGHTNESS_CHANGED, 129 | ARILUX_CMD_COLOR_CHANGED, 130 | ARILUX_CMD_WHITE_CHANGED, 131 | ARILUX_CMD_JSON, 132 | ARILUX_CMD_PING 133 | }; 134 | 135 | typedef struct Color { 136 | uint8_t red; 137 | uint8_t green; 138 | uint8_t blue; 139 | uint8_t white1; 140 | uint8_t white2; 141 | }; 142 | 143 | class Arilux { 144 | public: 145 | Arilux(void); 146 | uint8_t init(void); 147 | uint8_t getState(void); 148 | uint8_t turnOn(void); 149 | uint8_t turnOff(void); 150 | uint8_t getBrightness(void); 151 | uint8_t increaseBrightness(void); 152 | uint8_t decreaseBrightness(void); 153 | uint8_t setBrightness(uint8_t p_brightness); 154 | uint8_t getRedValue(void); 155 | uint8_t getGreenValue(void); 156 | uint8_t getBlueValue(void); 157 | uint8_t getWhite1Value(void); 158 | uint8_t getWhite2Value(void); 159 | char * getColorString(void); 160 | 161 | uint8_t setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue); 162 | uint8_t setFadeColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue); 163 | uint8_t setFadeToColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue); 164 | uint8_t setAll(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white1, uint8_t p_white2); 165 | uint8_t setWhite(uint8_t p_white1, uint8_t p_white2); 166 | uint8_t setState(uint8_t p_state); 167 | private: 168 | uint8_t m_redPin; 169 | uint8_t m_greenPin; 170 | uint8_t m_bluePin; 171 | #if defined(RGBW) || defined (RGBWW) 172 | uint8_t m_white1Pin; 173 | #ifdef RGBWW 174 | uint8_t m_white2Pin; 175 | #endif 176 | #endif 177 | uint8_t m_state; 178 | uint8_t m_brightness; 179 | Color m_color; 180 | uint8_t setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_retain); 181 | uint8_t setAll(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white1, uint8_t p_white2, uint8_t p_retain); 182 | uint8_t setWhite(uint8_t p_white1, uint8_t p_white2, uint8_t p_retain); 183 | }; 184 | #endif 185 | -------------------------------------------------------------------------------- /Arilux_AL-LC0X.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Alternative firmware for Arilux AL-LC0X series of ESP8266 based RGB LED controllers. 3 | See the README at https://github.com/mertenats/Arilux_AL-LC0X for more information. 4 | Licensed under the MIT license. 5 | */ 6 | 7 | #include "config.h" 8 | #include // https://github.com/esp8266/Arduino 9 | #include // https://github.com/knolleary/pubsubclient - v2.7 10 | #ifdef IR_REMOTE 11 | #include // https://github.com/markszabo/IRremoteESP8266 - v2.5.4 12 | #include 13 | #include 14 | #endif 15 | #ifdef RF_REMOTE 16 | #include // https://github.com/sui77/rc-switch 17 | #endif 18 | #include 19 | #if defined(HOME_ASSISTANT_MQTT_DISCOVERY) || defined(HOME_ASSISTANT_MQTT_ATTRIBUTES) || defined (JSON) 20 | #include // https://github.com/bblanchon/ArduinoJson - Version 6.9.1 21 | #endif 22 | #include "Arilux.h" 23 | 24 | // in a terminal: telnet arilux.local 25 | #ifdef DEBUG_TELNET 26 | WiFiServer telnetServer(23); 27 | WiFiClient telnetClient; 28 | #endif 29 | 30 | // Macros for debugging 31 | #ifdef DEBUG_TELNET 32 | #define DEBUG_PRINT(x) telnetClient.print(x) 33 | #define DEBUG_PRINT_WITH_FMT(x, fmt) telnetClient.print(x, fmt) 34 | #define DEBUG_PRINTLN(x) telnetClient.println(x) 35 | #define DEBUG_PRINTLN_WITH_FMT(x, fmt) telnetClient.println(x, fmt) 36 | #else 37 | #define DEBUG_PRINT(x) Serial.print(x) 38 | #define DEBUG_PRINT_WITH_FMT(x, fmt) Serial.print(x, fmt) 39 | #define DEBUG_PRINTLN(x) Serial.println(x) 40 | #define DEBUG_PRINTLN_WITH_FMT(x, fmt) Serial.println(x, fmt) 41 | #endif 42 | 43 | char chipid[12]; 44 | char MQTT_CLIENT_ID[32]; 45 | char MQTT_TOPIC_PREFIX[32]; 46 | 47 | // MQTT topics 48 | char ARILUX_MQTT_STATUS_TOPIC[44]; 49 | #ifdef HOME_ASSISTANT_MQTT_ATTRIBUTES 50 | char HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC[44]; 51 | #endif 52 | #ifdef HOME_ASSISTANT_MQTT_DISCOVERY 53 | char HOME_ASSISTANT_MQTT_DISCOVERY_TOPIC[56]; 54 | #endif 55 | #ifdef JSON 56 | char ARILUX_MQTT_JSON_STATE_TOPIC[44]; 57 | char ARILUX_MQTT_JSON_COMMAND_TOPIC[44]; 58 | #else 59 | char ARILUX_MQTT_STATE_STATE_TOPIC[44]; 60 | char ARILUX_MQTT_STATE_COMMAND_TOPIC[44]; 61 | char ARILUX_MQTT_BRIGHTNESS_STATE_TOPIC[44]; 62 | char ARILUX_MQTT_BRIGHTNESS_COMMAND_TOPIC[44]; 63 | char ARILUX_MQTT_COLOR_STATE_TOPIC[44]; 64 | char ARILUX_MQTT_COLOR_COMMAND_TOPIC[44]; 65 | #if defined(RGBW) || defined (RGBWW) 66 | char ARILUX_MQTT_WHITE_STATE_TOPIC[44]; 67 | char ARILUX_MQTT_WHITE_COMMAND_TOPIC[44]; 68 | #endif 69 | #endif 70 | 71 | // MQTT buffer 72 | char msgBuffer[32]; 73 | 74 | char friendlyName[64]; 75 | 76 | volatile uint8_t cmd = ARILUX_CMD_NOT_DEFINED; 77 | 78 | Arilux arilux; 79 | #ifdef IR_REMOTE 80 | IRrecv irRecv(ARILUX_IR_PIN); 81 | #endif 82 | #ifdef RF_REMOTE 83 | RCSwitch rcSwitch = RCSwitch(); 84 | #endif 85 | #ifdef TLS 86 | WiFiClientSecure wifiClient; 87 | #else 88 | WiFiClient wifiClient; 89 | #endif 90 | PubSubClient mqttClient(wifiClient); 91 | 92 | // Real values to write to the LEDs (ex. including brightness and state) 93 | byte realRed = 0; 94 | byte realGreen = 0; 95 | byte realBlue = 0; 96 | 97 | // Globals for fade/transitions 98 | bool startFade = false; 99 | unsigned long lastLoop = 0; 100 | int transitionTime = 0; 101 | bool inFade = false; 102 | int loopCount = 0; 103 | int stepR, stepG, stepB; 104 | int redVal, grnVal, bluVal; 105 | 106 | // Globals for flash 107 | bool flash = false; 108 | bool startFlash = false; 109 | int flashLength = 0; 110 | unsigned long flashStartTime = 0; 111 | byte flashRed = 0; 112 | byte flashGreen = 0; 113 | byte flashBlue = 0; 114 | byte flashBrightness = 0; 115 | 116 | /////////////////////////////////////////////////////////////////////////// 117 | // SSL/TLS 118 | /////////////////////////////////////////////////////////////////////////// 119 | /* 120 | Function called to verify the fingerprint of the MQTT server certificate 121 | */ 122 | #ifdef TLS 123 | void verifyFingerprint() { 124 | DEBUG_PRINT(F("INFO: Connecting to ")); 125 | DEBUG_PRINTLN(MQTT_SERVER); 126 | 127 | if (!wifiClient.connect(MQTT_SERVER, MQTT_PORT)) { 128 | DEBUG_PRINTLN(F("ERROR: Connection failed. Halting execution")); 129 | delay(1000); 130 | ESP.reset(); 131 | } 132 | 133 | if (wifiClient.verify(TLS_FINGERPRINT, MQTT_SERVER)) { 134 | DEBUG_PRINTLN(F("INFO: Connection secure")); 135 | } else { 136 | DEBUG_PRINTLN(F("ERROR: Connection insecure! Halting execution")); 137 | delay(1000); 138 | ESP.reset(); 139 | } 140 | } 141 | #endif 142 | 143 | /////////////////////////////////////////////////////////////////////////// 144 | // Utilities 145 | /////////////////////////////////////////////////////////////////////////// 146 | 147 | /* 148 | Helper function to subscribe to a MQTT topic 149 | */ 150 | 151 | void subscribeToMQTTTopic(const char* topic) { 152 | if (mqttClient.subscribe(topic)) { 153 | DEBUG_PRINT(F("INFO: Sending the MQTT subscribe succeeded for topic: ")); 154 | DEBUG_PRINTLN(topic); 155 | } else { 156 | DEBUG_PRINT(F("ERROR: Sending the MQTT subscribe failed for topic: ")); 157 | DEBUG_PRINTLN(topic); 158 | } 159 | } 160 | 161 | /* 162 | Helper function to publish to a MQTT topic with the given payload 163 | */ 164 | 165 | void publishToMQTT(const char* topic, const char* payload) { 166 | if (mqttClient.publish(topic, payload, true)) { 167 | DEBUG_PRINT(F("INFO: MQTT message publish succeeded. Topic: ")); 168 | DEBUG_PRINT(topic); 169 | DEBUG_PRINT(F(". Payload: ")); 170 | DEBUG_PRINTLN(payload); 171 | } else { 172 | DEBUG_PRINTLN(F("ERROR: MQTT message publish failed, either connection lost, or message too large")); 173 | } 174 | } 175 | 176 | #ifdef HOME_ASSISTANT_MQTT_ATTRIBUTES 177 | void publishAttributes(void) { 178 | StaticJsonDocument<512> root; 179 | root["BSSID"] = WiFi.BSSIDstr(); 180 | root["Chip ID"] = chipid; 181 | root["Hostname"] = MQTT_CLIENT_ID; 182 | root["IP Address"] = WiFi.localIP().toString(); 183 | root["LED Strip Type"] = arilux.getColorString(); 184 | root["MAC Address"] = WiFi.macAddress(); 185 | root["Model"] = DEVICE_MODEL; 186 | root["Remote Type"] = "None"; 187 | #if defined(IR_REMOTE) 188 | root["Remote Type"] = "Infrared (IR)"; 189 | #elif defined(RF_REMOTE) 190 | root["Remote Type"] = "Radio Frequency (RF)"; 191 | #endif 192 | root["RSSI"] = WiFi.RSSI(); 193 | root["SSID"] = WiFi.SSID(); 194 | root["Telnet Logging Enabled"] = false; 195 | #if defined(DEBUG_TELNET) 196 | root["Telnet Logging Enabled"] = true; 197 | #endif 198 | char outgoingJsonBuffer[512]; 199 | serializeJson(root, outgoingJsonBuffer); 200 | publishToMQTT(HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC, outgoingJsonBuffer); 201 | } 202 | #endif 203 | 204 | 205 | #ifdef HOME_ASSISTANT_MQTT_DISCOVERY 206 | void publishDiscovery(void) { 207 | StaticJsonDocument<512> root; 208 | root["name"] = friendlyName; 209 | root["availability_topic"] = ARILUX_MQTT_STATUS_TOPIC; 210 | 211 | #ifdef HOME_ASSISTANT_MQTT_ATTRIBUTES 212 | root["json_attributes_topic"] = HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC; 213 | #endif 214 | 215 | #ifdef JSON 216 | root["brightness"] = true; 217 | root["command_topic"] = ARILUX_MQTT_JSON_COMMAND_TOPIC; 218 | root["rgb"] = true; 219 | root["schema"] = "json"; 220 | root["state_topic"] = ARILUX_MQTT_JSON_STATE_TOPIC; 221 | #if defined(RGBW) || defined (RGBWW) 222 | root["white_value"] = true; 223 | #endif 224 | #else 225 | root["brightness_command_topic"] = ARILUX_MQTT_BRIGHTNESS_COMMAND_TOPIC; 226 | root["brightness_state_topic"] = ARILUX_MQTT_BRIGHTNESS_STATE_TOPIC; 227 | root["command_topic"] = ARILUX_MQTT_STATE_COMMAND_TOPIC; 228 | root["payload_off"] = MQTT_STATE_OFF_PAYLOAD; 229 | root["payload_on"] = MQTT_STATE_ON_PAYLOAD; 230 | root["rgb_command_topic"] = ARILUX_MQTT_COLOR_COMMAND_TOPIC; 231 | root["rgb_state_topic"] = ARILUX_MQTT_COLOR_STATE_TOPIC; 232 | root["state_topic"] = ARILUX_MQTT_STATE_STATE_TOPIC; 233 | #endif 234 | char outgoingJsonBuffer[1024]; 235 | serializeJson(root, outgoingJsonBuffer); 236 | publishToMQTT(HOME_ASSISTANT_MQTT_DISCOVERY_TOPIC, outgoingJsonBuffer); 237 | } 238 | #endif 239 | 240 | #ifndef JSON 241 | void publishStateChange(void) { 242 | publishToMQTT(ARILUX_MQTT_STATE_STATE_TOPIC, (arilux.getState() ? MQTT_STATE_ON_PAYLOAD : MQTT_STATE_OFF_PAYLOAD)); 243 | } 244 | 245 | void publishBrightnessChange(void) { 246 | snprintf(msgBuffer, sizeof(msgBuffer), "%d", arilux.getBrightness()); 247 | publishToMQTT(ARILUX_MQTT_BRIGHTNESS_STATE_TOPIC, msgBuffer); 248 | } 249 | 250 | void publishColorChange(void) { 251 | snprintf(msgBuffer, sizeof(msgBuffer), "%d,%d,%d", arilux.getRedValue(), arilux.getGreenValue(), arilux.getBlueValue()); 252 | publishToMQTT(ARILUX_MQTT_COLOR_STATE_TOPIC, msgBuffer); 253 | } 254 | 255 | #if defined(RGBW) || defined (RGBWW) 256 | void publishWhiteChange(void) { 257 | snprintf(msgBuffer, sizeof(msgBuffer), "%d,%d", arilux.getWhite1Value(), arilux.getWhite2Value()); 258 | publishToMQTT(ARILUX_MQTT_WHITE_STATE_TOPIC, msgBuffer); 259 | } 260 | #endif 261 | #endif 262 | 263 | /////////////////////////////////////////////////////////////////////////// 264 | // Effects 265 | /////////////////////////////////////////////////////////////////////////// 266 | 267 | // From https://www.arduino.cc/en/Tutorial/ColorCrossfader 268 | /* BELOW THIS LINE IS THE MATH -- YOU SHOULDN'T NEED TO CHANGE THIS FOR THE BASICS 269 | * 270 | * The program works like this: 271 | * Imagine a crossfade that moves the red LED from 0-10, 272 | * the green from 0-5, and the blue from 10 to 7, in 273 | * ten steps. 274 | * We'd want to count the 10 steps and increase or 275 | * decrease color values in evenly stepped increments. 276 | * Imagine a + indicates raising a value by 1, and a - 277 | * equals lowering it. Our 10 step fade would look like: 278 | * 279 | * 1 2 3 4 5 6 7 8 9 10 280 | * R + + + + + + + + + + 281 | * G + + + + + 282 | * B - - - 283 | * 284 | * The red rises from 0 to 10 in ten steps, the green from 285 | * 0-5 in 5 steps, and the blue falls from 10 to 7 in three steps. 286 | * 287 | * In the real program, the color percentages are converted to 288 | * 0-255 values, and there are 1020 steps (255*4). 289 | * 290 | * To figure out how big a step there should be between one up- or 291 | * down-tick of one of the LED values, we call calculateStep(), 292 | * which calculates the absolute gap between the start and end values, 293 | * and then divides that gap by 1020 to determine the size of the step 294 | * between adjustments in the value. 295 | */ 296 | int calculateStep(int prevValue, int endValue) { 297 | int step = endValue - prevValue; // What's the overall gap? 298 | if (step) { // If its non-zero, 299 | step = 1020/step; // divide by 1020 300 | } 301 | 302 | return step; 303 | } 304 | 305 | /* The next function is calculateVal. When the loop value, i, 306 | * reaches the step size appropriate for one of the 307 | * colors, it increases or decreases the value of that color by 1. 308 | * (R, G, and B are each calculated separately.) 309 | */ 310 | int calculateVal(int step, int val, int i) { 311 | if ((step) && i % step == 0) { // If step is non-zero and its time to change a value, 312 | if (step > 0) { // increment the value if step is positive... 313 | val += 1; 314 | } 315 | else if (step < 0) { // ...or decrement it if step is negative 316 | val -= 1; 317 | } 318 | } 319 | 320 | // Defensive driving: make sure val stays in the range 0-255 321 | if (val > 255) { 322 | val = 255; 323 | } 324 | else if (val < 0) { 325 | val = 0; 326 | } 327 | 328 | return val; 329 | } 330 | 331 | void flashSuccess(bool success) { 332 | flashLength = 5000; 333 | 334 | flashBrightness = 255; 335 | 336 | if(success) { 337 | flashRed = 0; 338 | flashGreen = 255; 339 | } else { 340 | flashRed = 255; 341 | flashGreen = 0; 342 | } 343 | flashBlue = 0; 344 | 345 | flashRed = map(flashRed, 0, 255, 0, flashBrightness); 346 | flashGreen = map(flashGreen, 0, 255, 0, flashBrightness); 347 | flashBlue = map(flashBlue, 0, 255, 0, flashBrightness); 348 | 349 | flash = true; 350 | startFlash = true; 351 | } 352 | 353 | void handleEffects(void) { 354 | if (flash) { 355 | if (startFlash) { 356 | startFlash = false; 357 | flashStartTime = millis(); 358 | arilux.setWhite(0, 0); 359 | } 360 | if ((millis() - flashStartTime) <= flashLength) { 361 | if ((millis() - flashStartTime) % 1000 <= 500) { 362 | arilux.setColor(flashRed, flashGreen, flashBlue); 363 | } else { 364 | arilux.setColor(0, 0, 0); 365 | // If you'd prefer the flashing to happen "on top of" 366 | // the current color, uncomment the next line. 367 | // arilux.setColor(realRed, realGreen, realBlue); 368 | } 369 | } else { 370 | flash = false; 371 | arilux.setColor(realRed, realGreen, realBlue); 372 | } 373 | } 374 | 375 | if (startFade) { 376 | // If we don't want to fade, skip it. 377 | if (transitionTime == 0) { 378 | arilux.setColor(realRed, realGreen, realBlue); 379 | 380 | redVal = realRed; 381 | grnVal = realGreen; 382 | bluVal = realBlue; 383 | 384 | startFade = false; 385 | } else { 386 | loopCount = 0; 387 | stepR = calculateStep(redVal, realRed); 388 | stepG = calculateStep(grnVal, realGreen); 389 | stepB = calculateStep(bluVal, realBlue); 390 | arilux.setFadeToColor(realRed, realGreen, realBlue); 391 | 392 | inFade = true; 393 | } 394 | } 395 | 396 | if (inFade) { 397 | startFade = false; 398 | unsigned long now = millis(); 399 | if (now - lastLoop > transitionTime) { 400 | if (loopCount <= 1020) { 401 | lastLoop = now; 402 | 403 | redVal = calculateVal(stepR, redVal, loopCount); 404 | grnVal = calculateVal(stepG, grnVal, loopCount); 405 | bluVal = calculateVal(stepB, bluVal, loopCount); 406 | 407 | arilux.setFadeColor(redVal, grnVal, bluVal); // Write current values to LED pins 408 | 409 | DEBUG_PRINT("Fade Loop count: "); 410 | DEBUG_PRINTLN(loopCount); 411 | loopCount++; 412 | } else { 413 | inFade = false; 414 | } 415 | } 416 | } 417 | } 418 | 419 | /////////////////////////////////////////////////////////////////////////// 420 | // MQTT 421 | /////////////////////////////////////////////////////////////////////////// 422 | /* 423 | Function called when a MQTT message arrived 424 | @param p_topic The topic of the MQTT message 425 | @param p_payload The payload of the MQTT message 426 | @param p_length The length of the payload 427 | */ 428 | void callback(char* p_topic, byte* p_payload, unsigned int p_length) { 429 | // Concatenate the payload into a string 430 | String payload; 431 | for (uint8_t i = 0; i < p_length; i++) { 432 | payload.concat((char)p_payload[i]); 433 | } 434 | 435 | // Handle the MQTT topic of the received message 436 | #ifdef JSON 437 | if (String(ARILUX_MQTT_JSON_COMMAND_TOPIC).equals(p_topic)) { 438 | StaticJsonDocument<512> doc; 439 | auto error = deserializeJson(doc, payload); 440 | if (error) { 441 | DEBUG_PRINT(F("parseObject() failed with code ")); 442 | DEBUG_PRINTLN(error.c_str()); 443 | return; 444 | } 445 | JsonObject root = doc.as(); 446 | 447 | if (root.containsKey("color")) { 448 | startFade = true; 449 | inFade = false; // Kill the current fade 450 | int red_color = root["color"]["r"]; 451 | int green_color = root["color"]["g"]; 452 | int blue_color = root["color"]["b"]; 453 | 454 | realRed = red_color; 455 | realGreen = green_color; 456 | realBlue = blue_color; 457 | } else { 458 | realRed = arilux.getRedValue(); 459 | realGreen = arilux.getGreenValue(); 460 | realBlue = arilux.getBlueValue(); 461 | } 462 | 463 | 464 | if (root.containsKey("flash")) { 465 | startFade = true; 466 | inFade = false; // Kill the current fade 467 | flashLength = (int)root["flash"] * 1000; 468 | 469 | if (root.containsKey("brightness")) { 470 | flashBrightness = root["brightness"]; 471 | } else { 472 | flashBrightness = arilux.getBrightness(); 473 | } 474 | 475 | if (root.containsKey("color")) { 476 | flashRed = root["color"]["r"]; 477 | flashGreen = root["color"]["g"]; 478 | flashBlue = root["color"]["b"]; 479 | } else { 480 | flashRed = arilux.getRedValue(); 481 | flashGreen = arilux.getGreenValue(); 482 | flashBlue = arilux.getBlueValue(); 483 | } 484 | 485 | flashRed = map(flashRed, 0, 255, 0, flashBrightness); 486 | flashGreen = map(flashGreen, 0, 255, 0, flashBrightness); 487 | flashBlue = map(flashBlue, 0, 255, 0, flashBrightness); 488 | 489 | flash = true; 490 | startFlash = true; 491 | } else { // Not flashing 492 | flash = false; 493 | if (root.containsKey("state")) { 494 | if (strcmp(root["state"], "ON") == 0) { 495 | arilux.turnOn(); 496 | } else if (strcmp(root["state"], "OFF") == 0) { 497 | startFade = false; 498 | startFlash = false; 499 | inFade = false; // Kill the current fade 500 | arilux.turnOff(); 501 | } 502 | } 503 | 504 | if (root.containsKey("transition")) { 505 | transitionTime = root["transition"]; 506 | } else { 507 | transitionTime = 0; 508 | } 509 | 510 | if (root.containsKey("brightness")) { 511 | int brightness = root["brightness"]; 512 | arilux.setBrightness(brightness); 513 | } 514 | 515 | if (root.containsKey("white_value")) { 516 | int white_value = root["white_value"]; 517 | arilux.setWhite(white_value, white_value); 518 | } 519 | } 520 | cmd = ARILUX_CMD_JSON; 521 | } 522 | #else 523 | if (String(ARILUX_MQTT_STATE_COMMAND_TOPIC).equals(p_topic)) { 524 | if (payload.equals(String(MQTT_STATE_ON_PAYLOAD))) { 525 | if (arilux.turnOn()) 526 | cmd = ARILUX_CMD_STATE_CHANGED; 527 | } else if (payload.equals(String(MQTT_STATE_OFF_PAYLOAD))) { 528 | if (arilux.turnOff()) 529 | cmd = ARILUX_CMD_STATE_CHANGED; 530 | } 531 | } else if (String(ARILUX_MQTT_BRIGHTNESS_COMMAND_TOPIC).equals(p_topic)) { 532 | if (arilux.setBrightness(payload.toInt())) 533 | cmd = ARILUX_CMD_BRIGHTNESS_CHANGED; 534 | } else if (String(ARILUX_MQTT_COLOR_COMMAND_TOPIC).equals(p_topic)) { 535 | // Get the position of the first and second commas 536 | int commaIndex = payload.indexOf(','); 537 | // Search for the next comma just after the first 538 | int secondCommaIndex = payload.indexOf(',', commaIndex + 1); 539 | String firstValue = payload.substring(0, commaIndex); 540 | String secondValue = payload.substring(commaIndex + 1, secondCommaIndex); 541 | String thirdValue = payload.substring(secondCommaIndex + 1); // To the end of the string 542 | int r = firstValue.toInt(); 543 | int g = secondValue.toInt(); 544 | int b = thirdValue.toInt(); 545 | 546 | if (arilux.setColor(r, g, b)) 547 | cmd = ARILUX_CMD_COLOR_CHANGED; 548 | } 549 | #if defined(RGBW) || defined (RGBWW) 550 | if (String(ARILUX_MQTT_WHITE_COMMAND_TOPIC).equals(p_topic)) { 551 | uint8_t firstIndex = payload.indexOf(','); 552 | if (arilux.setWhite(payload.substring(0, firstIndex).toInt(), payload.substring(firstIndex + 1).toInt())) 553 | cmd = ARILUX_CMD_WHITE_CHANGED; 554 | } 555 | #endif 556 | #endif 557 | } 558 | 559 | /* 560 | Function called to connect/reconnect to the MQTT broker 561 | */ 562 | 563 | volatile unsigned long lastmqttreconnect = 0; 564 | void connectMQTT(void) { 565 | if (!mqttClient.connected()) { 566 | if (lastmqttreconnect + 1000 < millis()) { 567 | if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS, ARILUX_MQTT_STATUS_TOPIC, 0, 1, "offline")) { 568 | DEBUG_PRINTLN(F("INFO: The client is successfully connected to the MQTT broker")); 569 | publishToMQTT(ARILUX_MQTT_STATUS_TOPIC, "online"); 570 | #ifdef HOME_ASSISTANT_MQTT_ATTRIBUTES 571 | publishAttributes(); 572 | #endif 573 | #ifdef HOME_ASSISTANT_MQTT_DISCOVERY 574 | publishDiscovery(); 575 | #endif 576 | flashSuccess(true); 577 | } else { 578 | DEBUG_PRINTLN(F("ERROR: The connection to the MQTT broker failed")); 579 | DEBUG_PRINT(F("Username: ")); 580 | DEBUG_PRINTLN(MQTT_USER); 581 | DEBUG_PRINT(F("Password: ")); 582 | DEBUG_PRINTLN(MQTT_PASS); 583 | DEBUG_PRINT(F("Broker: ")); 584 | DEBUG_PRINTLN(MQTT_SERVER); 585 | DEBUG_PRINT(F("Port: ")); 586 | DEBUG_PRINTLN(MQTT_PORT); 587 | flashSuccess(false); 588 | } 589 | 590 | #ifdef JSON 591 | subscribeToMQTTTopic(ARILUX_MQTT_JSON_COMMAND_TOPIC); 592 | #else 593 | subscribeToMQTTTopic(ARILUX_MQTT_STATE_COMMAND_TOPIC); 594 | subscribeToMQTTTopic(ARILUX_MQTT_BRIGHTNESS_COMMAND_TOPIC); 595 | subscribeToMQTTTopic(ARILUX_MQTT_COLOR_COMMAND_TOPIC); 596 | 597 | #if defined(RGBW) || defined (RGBWW) 598 | subscribeToMQTTTopic(ARILUX_MQTT_WHITE_COMMAND_TOPIC); 599 | #endif 600 | #endif 601 | 602 | lastmqttreconnect = millis(); 603 | } 604 | } 605 | } 606 | 607 | /////////////////////////////////////////////////////////////////////////// 608 | // TELNET 609 | /////////////////////////////////////////////////////////////////////////// 610 | /* 611 | Function called to handle Telnet clients 612 | https://www.youtube.com/watch?v=j9yW10OcahI 613 | */ 614 | #ifdef DEBUG_TELNET 615 | void handleTelnet(void) { 616 | if (telnetServer.hasClient()) { 617 | if (!telnetClient || !telnetClient.connected()) { 618 | if (telnetClient) { 619 | telnetClient.stop(); 620 | } 621 | telnetClient = telnetServer.available(); 622 | } else { 623 | telnetServer.available().stop(); 624 | } 625 | } 626 | } 627 | #endif 628 | 629 | /////////////////////////////////////////////////////////////////////////// 630 | // IR REMOTE 631 | /////////////////////////////////////////////////////////////////////////// 632 | /* 633 | Function called to handle received IR codes from the remote 634 | */ 635 | #ifdef IR_REMOTE 636 | void handleIRRemote(void) { 637 | decode_results results; 638 | 639 | if (irRecv.decode(&results)) { 640 | switch (results.value) { 641 | case ARILUX_IR_CODE_KEY_UP: 642 | if (arilux.increaseBrightness()) 643 | cmd = ARILUX_CMD_BRIGHTNESS_CHANGED; 644 | break; 645 | case ARILUX_IR_CODE_KEY_DOWN: 646 | if (arilux.decreaseBrightness()) 647 | cmd = ARILUX_CMD_BRIGHTNESS_CHANGED; 648 | break; 649 | case ARILUX_IR_CODE_KEY_OFF: 650 | if (arilux.turnOff()) 651 | cmd = ARILUX_CMD_STATE_CHANGED; 652 | break; 653 | case ARILUX_IR_CODE_KEY_ON: 654 | if (arilux.turnOn()) 655 | cmd = ARILUX_CMD_STATE_CHANGED; 656 | break; 657 | case ARILUX_IR_CODE_KEY_R: 658 | if (arilux.setColor(255, 0, 0)) 659 | cmd = ARILUX_CMD_COLOR_CHANGED; 660 | break; 661 | case ARILUX_IR_CODE_KEY_G: 662 | if (arilux.setColor(0, 255, 0)) 663 | cmd = ARILUX_CMD_COLOR_CHANGED; 664 | break; 665 | case ARILUX_IR_CODE_KEY_B: 666 | if (arilux.setColor(0, 0, 255)) 667 | cmd = ARILUX_CMD_COLOR_CHANGED; 668 | break; 669 | case ARILUX_IR_CODE_KEY_W: 670 | if (arilux.setColor(255, 255, 255)) 671 | cmd = ARILUX_CMD_COLOR_CHANGED; 672 | break; 673 | case ARILUX_IR_CODE_KEY_1: 674 | if (arilux.setColor(255, 51, 51)) 675 | cmd = ARILUX_CMD_COLOR_CHANGED; 676 | break; 677 | case ARILUX_IR_CODE_KEY_2: 678 | if (arilux.setColor(102, 204, 0)) 679 | cmd = ARILUX_CMD_COLOR_CHANGED; 680 | break; 681 | case ARILUX_IR_CODE_KEY_3: 682 | if (arilux.setColor(0, 102, 204)) 683 | cmd = ARILUX_CMD_COLOR_CHANGED; 684 | break; 685 | case ARILUX_IR_CODE_KEY_FLASH: 686 | // TODO 687 | DEBUG_PRINTLN(F("INFO: IR_CODE_KEY_FLASH")); 688 | break; 689 | case ARILUX_IR_CODE_KEY_4: 690 | if (arilux.setColor(255, 102, 102)) 691 | cmd = ARILUX_CMD_COLOR_CHANGED; 692 | break; 693 | case ARILUX_IR_CODE_KEY_5: 694 | if (arilux.setColor(0, 255, 255)) 695 | cmd = ARILUX_CMD_COLOR_CHANGED; 696 | break; 697 | case ARILUX_IR_CODE_KEY_6: 698 | if (arilux.setColor(153, 0, 153)) 699 | cmd = ARILUX_CMD_COLOR_CHANGED; 700 | break; 701 | case ARILUX_IR_CODE_KEY_STROBE: 702 | // TODO 703 | DEBUG_PRINTLN(F("INFO: IR_CODE_KEY_STROBE")); 704 | break; 705 | case ARILUX_IR_CODE_KEY_7: 706 | if (arilux.setColor(255, 255, 102)) 707 | cmd = ARILUX_CMD_COLOR_CHANGED; 708 | break; 709 | case ARILUX_IR_CODE_KEY_8: 710 | if (arilux.setColor(51, 153, 255)) 711 | cmd = ARILUX_CMD_COLOR_CHANGED; 712 | break; 713 | case ARILUX_IR_CODE_KEY_9: 714 | if (arilux.setColor(255, 0, 255)) 715 | cmd = ARILUX_CMD_COLOR_CHANGED; 716 | break; 717 | case ARILUX_IR_CODE_KEY_FADE: 718 | // TODO 719 | DEBUG_PRINTLN(F("INFO: IR_CODE_KEY_FADE")); 720 | break; 721 | case ARILUX_IR_CODE_KEY_10: 722 | if (arilux.setColor(255, 255, 0)) 723 | cmd = ARILUX_CMD_COLOR_CHANGED; 724 | break; 725 | case ARILUX_IR_CODE_KEY_11: 726 | if (arilux.setColor(0, 128, 255)) 727 | cmd = ARILUX_CMD_COLOR_CHANGED; 728 | break; 729 | case ARILUX_IR_CODE_KEY_12: 730 | if (arilux.setColor(255, 102, 178)) 731 | cmd = ARILUX_CMD_COLOR_CHANGED; 732 | break; 733 | case ARILUX_IR_CODE_KEY_SMOOTH: 734 | // TODO 735 | DEBUG_PRINTLN(F("INFO: IR_CODE_KEY_SMOOTH")); 736 | break; 737 | default: 738 | DEBUG_PRINT(F("ERROR: IR code not defined: ")); 739 | DEBUG_PRINTLN(uint64ToString(results.value, HEX)); 740 | break; 741 | } 742 | irRecv.resume(); 743 | } 744 | } 745 | #endif 746 | 747 | /////////////////////////////////////////////////////////////////////////// 748 | // RF REMOTE 749 | /////////////////////////////////////////////////////////////////////////// 750 | /* 751 | Function called to handle received RF codes from the remote 752 | */ 753 | #ifdef RF_REMOTE 754 | void handleRFRemote(void) { 755 | if (rcSwitch.available()) { 756 | int value = rcSwitch.getReceivedValue(); 757 | 758 | switch (value) { 759 | case ARILUX_RF_CODE_KEY_BRIGHT_PLUS: 760 | if (arilux.increaseBrightness()) 761 | cmd = ARILUX_CMD_BRIGHTNESS_CHANGED; 762 | break; 763 | case ARILUX_RF_CODE_KEY_BRIGHT_MINUS: 764 | if (arilux.decreaseBrightness()) 765 | cmd = ARILUX_CMD_BRIGHTNESS_CHANGED; 766 | break; 767 | case ARILUX_RF_CODE_KEY_OFF: 768 | if (arilux.turnOff()) 769 | cmd = ARILUX_CMD_STATE_CHANGED; 770 | break; 771 | case ARILUX_RF_CODE_KEY_ON: 772 | if (arilux.turnOn()) 773 | cmd = ARILUX_CMD_STATE_CHANGED; 774 | break; 775 | case ARILUX_RF_CODE_KEY_RED: 776 | if (arilux.setColor(255, 0, 0)) 777 | cmd = ARILUX_CMD_COLOR_CHANGED; 778 | break; 779 | case ARILUX_RF_CODE_KEY_GREEN: 780 | if (arilux.setColor(0, 255, 0)) 781 | cmd = ARILUX_CMD_COLOR_CHANGED; 782 | break; 783 | case ARILUX_RF_CODE_KEY_BLUE: 784 | if (arilux.setColor(0, 0, 255)) 785 | cmd = ARILUX_CMD_COLOR_CHANGED; 786 | break; 787 | case ARILUX_RF_CODE_KEY_WHITE: 788 | if (arilux.setColor(255, 255, 255)) 789 | cmd = ARILUX_CMD_COLOR_CHANGED; 790 | break; 791 | case ARILUX_RF_CODE_KEY_ORANGE: 792 | if (arilux.setColor(255, 165, 0)) 793 | cmd = ARILUX_CMD_COLOR_CHANGED; 794 | break; 795 | case ARILUX_RF_CODE_KEY_LTGRN: 796 | if (arilux.setColor(144, 238, 144)) 797 | cmd = ARILUX_CMD_COLOR_CHANGED; 798 | break; 799 | case ARILUX_RF_CODE_KEY_LTBLUE: 800 | if (arilux.setColor(173, 216, 230)) 801 | cmd = ARILUX_CMD_COLOR_CHANGED; 802 | break; 803 | case ARILUX_RF_CODE_KEY_AMBER: 804 | if (arilux.setColor(255, 194, 0)) 805 | cmd = ARILUX_CMD_COLOR_CHANGED; 806 | break; 807 | case ARILUX_RF_CODE_KEY_CYAN: 808 | if (arilux.setColor(0, 255, 255)) 809 | cmd = ARILUX_CMD_COLOR_CHANGED; 810 | break; 811 | case ARILUX_RF_CODE_KEY_PURPLE: 812 | if (arilux.setColor(128, 0, 128)) 813 | cmd = ARILUX_CMD_COLOR_CHANGED; 814 | break; 815 | case ARILUX_RF_CODE_KEY_YELLOW: 816 | if (arilux.setColor(255, 255, 0)) 817 | cmd = ARILUX_CMD_COLOR_CHANGED; 818 | break; 819 | case ARILUX_RF_CODE_KEY_PINK: 820 | if (arilux.setColor(255, 192, 203)) 821 | cmd = ARILUX_CMD_COLOR_CHANGED; 822 | break; 823 | case ARILUX_RF_CODE_KEY_TOGGLE: 824 | // TODO 825 | DEBUG_PRINTLN(F("INFO: ARILUX_RF_CODE_KEY_TOGGLE")); 826 | break; 827 | case ARILUX_RF_CODE_KEY_SPEED_PLUS: 828 | // TODO 829 | DEBUG_PRINTLN(F("INFO: ARILUX_RF_CODE_KEY_SPEED_PLUS")); 830 | break; 831 | case ARILUX_RF_CODE_KEY_MODE_PLUS: 832 | // TODO 833 | DEBUG_PRINTLN(F("INFO: ARILUX_RF_CODE_KEY_MODE_PLUS")); 834 | break; 835 | case ARILUX_RF_CODE_KEY_SPEED_MINUS: 836 | // TODO 837 | DEBUG_PRINTLN(F("INFO: ARILUX_RF_CODE_KEY_SPEED_MINUS")); 838 | break; 839 | case ARILUX_RF_CODE_KEY_MODE_MINUS: 840 | // TODO 841 | DEBUG_PRINTLN(F("INFO: ARILUX_RF_CODE_KEY_MODE_MINUS")); 842 | break; 843 | default: 844 | DEBUG_PRINTLN(F("ERROR: RF code not defined")); 845 | break; 846 | } 847 | rcSwitch.resetAvailable(); 848 | } 849 | } 850 | #endif 851 | 852 | /////////////////////////////////////////////////////////////////////////// 853 | // CMD 854 | /////////////////////////////////////////////////////////////////////////// 855 | /* 856 | Function called to handle commands due to changes 857 | */ 858 | void handleCMD(void) { 859 | #ifdef JSON 860 | if (cmd != ARILUX_CMD_NOT_DEFINED) { 861 | StaticJsonDocument<512> root; 862 | String stringState = arilux.getState() ? "ON" : "OFF"; 863 | root["state"] = stringState; 864 | root["brightness"] = arilux.getBrightness(); 865 | // root["transition"] = 866 | root["white_value"] = arilux.getWhite1Value(); 867 | JsonObject color = root.createNestedObject("color"); 868 | color["r"] = arilux.getRedValue(); 869 | color["g"] = arilux.getGreenValue(); 870 | color["b"] = arilux.getBlueValue(); 871 | char outgoingJsonBuffer[512]; 872 | serializeJson(root, outgoingJsonBuffer); 873 | publishToMQTT(ARILUX_MQTT_JSON_STATE_TOPIC, outgoingJsonBuffer); 874 | }; 875 | #else 876 | switch (cmd) { 877 | case ARILUX_CMD_NOT_DEFINED: 878 | break; 879 | case ARILUX_CMD_STATE_CHANGED: 880 | publishStateChange(); 881 | break; 882 | case ARILUX_CMD_BRIGHTNESS_CHANGED: 883 | publishBrightnessChange(); 884 | publishStateChange(); 885 | break; 886 | case ARILUX_CMD_COLOR_CHANGED: 887 | publishColorChange(); 888 | publishStateChange(); 889 | break; 890 | #if defined(RGBW) || defined (RGBWW) 891 | case ARILUX_CMD_WHITE_CHANGED: 892 | publishWhiteChange(); 893 | publishStateChange(); 894 | break; 895 | #endif 896 | default: 897 | break; 898 | } 899 | #endif 900 | cmd = ARILUX_CMD_NOT_DEFINED; 901 | } 902 | 903 | /////////////////////////////////////////////////////////////////////////// 904 | // WiFi 905 | /////////////////////////////////////////////////////////////////////////// 906 | /* 907 | Function called to setup the connection to the WiFi AP 908 | */ 909 | 910 | void setupWiFi() { 911 | delay(10); 912 | 913 | DEBUG_PRINT(F("INFO: Connecting to: ")); 914 | DEBUG_PRINTLN(WIFI_SSID); 915 | 916 | WiFi.mode(WIFI_STA); 917 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 918 | while (WiFi.status() != WL_CONNECTED) { 919 | delay(500); 920 | DEBUG_PRINT("."); 921 | } 922 | randomSeed(micros()); 923 | DEBUG_PRINTLN(); 924 | DEBUG_PRINTLN(F("INFO: WiFi connected")); 925 | DEBUG_PRINT(F("INFO: IP address: ")); 926 | DEBUG_PRINTLN(WiFi.localIP()); 927 | } 928 | 929 | /////////////////////////////////////////////////////////////////////////// 930 | // SETUP() AND LOOP() 931 | /////////////////////////////////////////////////////////////////////////// 932 | void setup() { 933 | Serial.begin(115200); 934 | delay(500); 935 | 936 | #ifdef DEBUG_TELNET 937 | // Start the Telnet server 938 | telnetServer.begin(); 939 | telnetServer.setNoDelay(true); 940 | #endif 941 | 942 | sprintf(chipid, "%08X", ESP.getChipId()); 943 | sprintf(MQTT_CLIENT_ID, HOST, chipid); 944 | sprintf(friendlyName, "Arilux %s %s LED Controller (%s)", DEVICE_MODEL, arilux.getColorString(), chipid); 945 | DEBUG_PRINT("Hostname:"); 946 | DEBUG_PRINTLN(MQTT_CLIENT_ID); 947 | WiFi.hostname(MQTT_CLIENT_ID); 948 | 949 | // Setup Wi-Fi 950 | setupWiFi(); 951 | 952 | // Init the Arilux LED controller 953 | if (arilux.init()) 954 | cmd = ARILUX_CMD_STATE_CHANGED; 955 | 956 | #ifdef IR_REMOTE 957 | // Start the IR receiver 958 | irRecv.enableIRIn(); 959 | #endif 960 | 961 | #ifdef RF_REMOTE 962 | // Start the RF receiver 963 | rcSwitch.enableReceive(ARILUX_RF_PIN); 964 | #endif 965 | 966 | #ifdef TLS 967 | // Check the fingerprint of CloudMQTT's SSL cert 968 | verifyFingerprint(); 969 | #endif 970 | 971 | sprintf(MQTT_TOPIC_PREFIX, MQTT_TOPIC_PREFIX_TEMPLATE, arilux.getColorString(), chipid); 972 | 973 | sprintf(ARILUX_MQTT_STATUS_TOPIC, MQTT_STATUS_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 974 | 975 | #ifdef HOME_ASSISTANT_MQTT_ATTRIBUTES 976 | sprintf(HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC, HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 977 | #endif 978 | 979 | #ifdef HOME_ASSISTANT_MQTT_DISCOVERY 980 | sprintf(HOME_ASSISTANT_MQTT_DISCOVERY_TOPIC, "%s/light/ARILUX_%s_%s_%s/config", HOME_ASSISTANT_MQTT_DISCOVERY_PREFIX, DEVICE_MODEL, arilux.getColorString(), chipid); 981 | #endif 982 | 983 | #ifdef JSON 984 | sprintf(ARILUX_MQTT_JSON_STATE_TOPIC, MQTT_JSON_STATE_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 985 | sprintf(ARILUX_MQTT_JSON_COMMAND_TOPIC, MQTT_JSON_COMMAND_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 986 | #else 987 | sprintf(ARILUX_MQTT_STATE_STATE_TOPIC, MQTT_STATE_STATE_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 988 | sprintf(ARILUX_MQTT_STATE_COMMAND_TOPIC, MQTT_STATE_COMMAND_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 989 | sprintf(ARILUX_MQTT_BRIGHTNESS_STATE_TOPIC, MQTT_BRIGHTNESS_STATE_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 990 | sprintf(ARILUX_MQTT_BRIGHTNESS_COMMAND_TOPIC, MQTT_BRIGHTNESS_COMMAND_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 991 | sprintf(ARILUX_MQTT_COLOR_STATE_TOPIC, MQTT_COLOR_STATE_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 992 | sprintf(ARILUX_MQTT_COLOR_COMMAND_TOPIC, MQTT_COLOR_COMMAND_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 993 | 994 | #if defined(RGBW) || defined (RGBWW) 995 | sprintf(ARILUX_MQTT_WHITE_STATE_TOPIC, MQTT_WHITE_STATE_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 996 | sprintf(ARILUX_MQTT_WHITE_COMMAND_TOPIC, MQTT_WHITE_COMMAND_TOPIC_TEMPLATE, MQTT_TOPIC_PREFIX); 997 | #endif 998 | #endif 999 | 1000 | mqttClient.setServer(MQTT_SERVER, MQTT_PORT); 1001 | mqttClient.setCallback(callback); 1002 | connectMQTT(); 1003 | 1004 | // Set hostname and start OTA 1005 | ArduinoOTA.setHostname(MQTT_CLIENT_ID); 1006 | ArduinoOTA.onStart([]() { 1007 | DEBUG_PRINTLN("OTA Beginning!"); 1008 | flashSuccess(true); 1009 | }); 1010 | ArduinoOTA.onError([](ota_error_t error) { 1011 | DEBUG_PRINT("ArduinoOTA Error["); 1012 | DEBUG_PRINT(error); 1013 | DEBUG_PRINT("]: "); 1014 | if (error == OTA_AUTH_ERROR) DEBUG_PRINTLN("Auth Failed"); 1015 | else if (error == OTA_BEGIN_ERROR) DEBUG_PRINTLN("Begin Failed"); 1016 | else if (error == OTA_CONNECT_ERROR) DEBUG_PRINTLN("Connect Failed"); 1017 | else if (error == OTA_RECEIVE_ERROR) DEBUG_PRINTLN("Receive Failed"); 1018 | else if (error == OTA_END_ERROR) DEBUG_PRINTLN("End Failed"); 1019 | }); 1020 | ArduinoOTA.begin(); 1021 | } 1022 | 1023 | void loop() { 1024 | #ifdef DEBUG_TELNET 1025 | // Handle Telnet connection for debugging 1026 | handleTelnet(); 1027 | #endif 1028 | 1029 | #ifdef IR_REMOTE 1030 | // Handle received IR codes from the remote 1031 | handleIRRemote(); 1032 | #endif 1033 | 1034 | #ifdef RF_REMOTE 1035 | // Handle received RF codes from the remote 1036 | handleRFRemote(); 1037 | #endif 1038 | 1039 | yield(); 1040 | connectMQTT(); 1041 | mqttClient.loop(); 1042 | handleEffects(); 1043 | yield(); 1044 | // Handle commands 1045 | handleCMD(); 1046 | yield(); 1047 | ArduinoOTA.handle(); 1048 | yield(); 1049 | } 1050 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mertenats/Arilux_AL-LC0X.svg?branch=master)](https://travis-ci.org/mertenats/Arilux_AL-LC0X) 2 | 3 | # Alternative firmware for Arilux LED controllers 4 | **THIS REPOSITORY IS NOT MAINTAINED ANYMORE, SEE [ESPHome](https://esphome.io/) AS AN ALTERNATIVE.** 5 | 6 | This is an alternative firmware for Arilux LED controllers which uses [MQTT] instead of the default "Magic Home"/"Flux LED" protocol which has numerous reliability problems. 7 | The LED controller is a cheap product available on sites like Banggood.com, Aliexpress, eBay and even Amazon which can be easily reprogrammed as it is based on the popular [ESP8266 Wi-Fi chip][esp8266]. 8 | The controllers are also known to sell under different manufacturer names such as "Firstd". If the product you bought looks similar to one of the Arilux controllers below, it most likely is. 9 | 10 | 11 | **WARNING: DUE TO A NEW PINOUT, THIS FIRMWARE SEEMS TO BE NOT MORE COMPATIBLE WITH THE NEWEST MODELS (PCB version > 1.4).** 12 | 13 | **ANOTHER FIRMWARE IS AVAILABLE [HERE](https://github.com/mertenats/Open-Home-Automation/tree/master/ha_mqtt_light_arilux), WHICH WRAPS THE [WS2812FX](https://github.com/kitesurfer1404/WS2812FX) LIBRARY AND OFFERS MANY EFFECTS** 14 | 15 | ![Arilux](images/Arilux.png) 16 | 17 | ## Features 18 | - Remote control over the MQTT protocol via individual topics or JSON 19 | - Supports transitions, flashing and effects in JSON mode 20 | - Remote control with the included IR control (uncomment `#define IR_REMOTE` in `config.h`) 21 | - Remote control with the included RF control (uncomment `#define RF_REMOTE` in `config.h`) 22 | - TLS support (uncomment `#define TLS` in `config.h` and change the fingerprint if not using CloudMQTT) 23 | - Debug printing over Telnet (uncomment `#define DEBUG_TELNET` in `config.h`) 24 | - ArduinoOTA support for over-the-air firmware updates 25 | - Native support for Home Assistant, including MQTT discovery 26 | 27 | ## Supported devices 28 | | Model | Color Support | Voltages | Remote | Price | Link | 29 | |-------|---------------|----------|--------|-------|---------------------------| 30 | | LC01 | RGB | 5-28V | None | ~$8 | [Banggood][LC01-banggood] | 31 | | LC02 | RGBW | 9-12V | None | ~$11 | [Banggood][LC02-banggood] | 32 | | LC03 | RGB | 5-28V | IR | ~$12 | [Banggood][LC03-banggood] | 33 | | LC04 | RGBW | 9-12V | IR | ~$13 | [Banggood][LC04-banggood] | 34 | | LC08 | RGBWW | 5-28V | None | ~$12 | [Banggood][LC08-banggood] | 35 | | LC09 | RGB | 5-28V | RF | ~$12 | [Banggood][LC09-banggood] | 36 | | LC10 | RGBW | 9-28V | RF | ~$14 | [Banggood][LC10-banggood] | 37 | | LC11 | RGBWW | 9-28V | RF | ~$15 | [Banggood][LC11-banggood] | 38 | 39 | ## Demonstration 40 | 41 | [![Arilux AL-LC03 + IR + MQTT + Home Assistant](images/Youtube.png)](https://www.youtube.com/watch?v=IKh0inaLvAU "Arilux AL-LC03 + IR + MQTT + Home Assistant") 42 | 43 | ## Flash the firmware 44 | Whichever flashing option you choose, ensure your Arduino IDE settings match the following: 45 | 46 | ### Configuration 47 | You must copy `config.example.h` to `config.h` and change settings to match your environment before flashing. 48 | 49 | ### Settings for the Arduino IDE 50 | 51 | | Parameter | Value | 52 | | ----------------|--------------------------| 53 | | Board | Generic ESP8266 Module | 54 | | Flash Mode | DIO | 55 | | Flash Frequency | 40 MHz | 56 | | Upload Using | Serial | 57 | | CPU Frequency | 80 MHz | 58 | | Flash Size | 1M (64K SPIFFS) | 59 | | Reset Method | ck | 60 | | Upload Speed | 115200 | 61 | | Port | COMX, /dev/ttyUSB0, etc. | 62 | 63 | *Note: If you own a board labeled 1.4 or the board isn't booting, use the DOUT mode instead of the DIO mode to flash the firmware* 64 | 65 | ### Option 1 66 | #### Schematic 67 | | Arilux | Left FTDI | Right FTDI | 68 | |--------|-------------------|------------| 69 | | VCC | VCC (set to 3.3V) | | 70 | | RX | | TX | 71 | | TX | | RX | 72 | | GPIO0 | GND | | 73 | | GND | | GND | 74 | 75 | Note: To enter in programming mode, you need to pull GPIO0 LOW while powering the board via the FTDI. It's not possible to reprogram the module without soldering the wire to the ESP8266 module. 76 | If you are unable or don't know how to solder try option 2 below which can be accomplished without soldering. 77 | 78 | The FTDI from the left gives power and it's connected to an USB charger (VCC, GND). The FTDI from the right is connected to the computer and is used to reprogram the ESP8266 (RX, TX, GND). 79 | 80 | ![ESP-12F Layout](images/ESP12-F_pinout3.jpg) 81 | 82 | ![Layout](images/Layout.JPG) 83 | 84 | ### Option 2 85 | Using the following image, connect RX, TX and GND of a single FTDI to the shown pins on the underside of the board. Plug in the wall power supply and flash using the above settings. 86 | It helps to have another person able to plug in the device and start the upload while you hold the pins. 87 | 88 | ![Option 2 Layout](images/option2.jpg) 89 | 90 | ## Updating 91 | OTA is enabled on this firmware. Assuming the device is plugged in you should find the device as a Port option in the Arduino IDE. Make sure you are using the settings listed above. 92 | 93 | ## Control 94 | ### IR 95 | The LED controller can be controlled with the IR remote included with the Arilux AL-LC03 and AL-LC04. The `Flash`, `Strobe`, `Fade` and `Smooth` functionalities are not yet implemented. 96 | 97 | ### RF 98 | The LED controller can be controlled with the RF remote included with the Arilux AL-LC09, AL-LC10 and AL-LC11. The `Mode+`, `Mode-`, `Speed+`, `Speed-` and `toggle` functionalities are not yet implemented. 99 | 100 | ### MQTT 101 | 102 | #### Control modes 103 | This firmware can work with MQTT in one of two ways. To cut down on firmware size only one mode can be enabled at a time. Whichever mode is not enabled will not be loaded to the board. 104 | 105 | 1. JSON mode. Only one topic will be published and subscribed to (as well as the Last Will and Testament topic). The payload for both will be/is expected to be a JSON object with all properties listed below in it. If a property is missing, the state of that property will change. 106 | JSON is great because it reduces roundtrips across the network. For example, if you wanted to turn the light on, set it to full brightness, and make it red, you would have to publish to the `state` topic, the `brightness` topic and the `color` topic. 107 | JSON also allows effects and transitions to be specified. No properties are required. **You must have the [ArduinoJson] library installed for this to work.** 108 | If you are have Home Assistant MQTT Discovery enabled, the `light.mqtt` platform will be loaded by Home Assistant with `schema` set to `json`. 109 | To enable JSON mode, uncomment `#define JSON` in `config.h`. 110 | 111 | ##### JSON properties 112 | | Name | Data Type | Example | Description | 113 | |---------------|------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 114 | | `brightness` | Integer, 0-255 | `255` | The brightness to set to | 115 | | `color` | Object/dictionary | `{}` | A dictionary with the below RGB values | 116 | | `color.r` | Integer, 0-255 | `255` | The red color to set to | 117 | | `color.g` | Integer, 0-255 | `255` | The green color to set to | 118 | | `color.b` | Integer, 0-255 | `255` | The blue color to set to | 119 | | `flash` | Integer, number of seconds to flash | `10` | If `true`, light will flash with either colors provided in payload or previously set colors. Integer is number of seconds to flash for. | 120 | | `state` | Boolean | `true` | The state of the lights, `true` = on, `false` = off | 121 | | `transition` | Integer, number of seconds to transition | `5` | If greater than `0`, light will transition from old values to new ones for the given number of seconds | 122 | | `white_value` | Integer, 0-255 | `255` | Controls the whiteness level of the lights. Only supported for RGBW and RGBWW. Only the first white level is set, there is no support for setting the second white level on RGBWW lights | 123 | 124 | 2. Individual topics mode. The firmware will publish and subscribe to at least 11 topics and expect specifically formatted payloads for each of them. The full list is below. 125 | 126 | ##### Individual topics 127 | 128 | ###### State 129 | 130 | | # | Topic | Payload | 131 | |------------|-----------------------------------|-----------| 132 | | State | `rgb(w/ww)//state/state` | `ON`/`OFF`| 133 | | Command | `rgb(w/ww)//state/set` | `ON`/`OFF`| 134 | 135 | ###### Brightness 136 | 137 | | # | Topic | Payload | 138 | |------------|----------------------------------------|-----------| 139 | | State | `rgb(w/ww)//brightness/state` | `0-255` | 140 | | Command | `rgb(w/ww)//brightness/set` | `0-255` | 141 | 142 | ###### Color 143 | 144 | | # | Topic | Payload | 145 | |------------|-----------------------------------|---------------------| 146 | | State | `rgb(w/ww)//color/state` | `0-255,0-255,0-255` | 147 | | Command | `rgb(w/ww)//color/set` | `0-255,0-255,0-255` | 148 | 149 | ###### White 150 | 151 | White is only supported for RGBW/RGBWW models (LC02, LC04, LC08, LC10, LC11). 152 | 153 | | # | Topic | Payload | 154 | |------------|----------------------------------------|-----------------| 155 | | State | `rgb(w/ww)//white/state` | `0-255,0-255` | 156 | | Command | `rgb(w/ww)//white/set` | `0-255,0-255` | 157 | 158 | #### Last Will and Testament 159 | 160 | The firmware will publish a [MQTT Last Will and Testament] at `rgb(w/ww)//status`. 161 | When the device successfully connects it will publish `alive` to that topic and when it disconnects `dead` will automatically be published. 162 | 163 | #### Discovery 164 | 165 | This firmware supports [Home Assistant's MQTT discovery functionality], added in 0.40. 166 | This allows for instant setup and use of your device without requiring any manual configuration in Home Assistant. 167 | If you are using the MQTT JSON mode, the `light.mqtt_json` platform will be loaded. Otherwise, the `light.mqtt` platform will load. `light.mqtt_json` is required for full functionality. 168 | There are a few one time steps that you need to take to get this working. 169 | 170 | 1. Install the [ArduinoJson] library. 171 | 2. Add `discovery: true` to your `mqtt` configuration in Home Assistant, if it isn't there already. 172 | 3. In your Ardunino libraries folder, find PubSubClient and open PubSubClient.h for editing. Change `MQTT_MAX_PACKET_SIZE` to 512. 173 | 4. Uncomment the `HOME_ASSISTANT_MQTT_DISCOVERY` and `HOME_ASSISTANT_MQTT_DISCOVERY_PREFIX` definitions in your `config.h` file. 174 | - You can change the discovery prefix (default is `homeassistant`) by changing `HOME_ASSISTANT_MQTT_DISCOVERY_PREFIX`. 175 | Make sure this matches your Home Assistant MQTT configuration. 176 | 5. Upload the firmware once more after making the previous changes. 177 | 178 | From now on your device will announce itself to Home Assistant with all of the proper configuration information. 179 | 180 | #### Attributes 181 | 182 | Installing the [ArduinoJSON] library and uncommenting `HOME_ASSISTANT_MQTT_ATTRIBUTES` in your `config.h` will cause device specific attributes to be published. These can be consumed by Home Assistant 183 | to improve the display of the light, as well as provide some useful debugging information. The attributes payload looks like this: 184 | 185 | ```json 186 | { 187 | "BSSID": "AA:BB:CC:DD:EE:FF", 188 | "Chip ID": "000AAB12", 189 | "Hostname": "ARILUX000AAB12", 190 | "IP Address": "192.168.1.2", 191 | "LED Strip Type": "RGB", 192 | "MAC Address": "GG:HH:II:JJ:KK:LL", 193 | "Model": "LC01", 194 | "Remote Type": "None", 195 | "RSSI": -68, 196 | "SSID": "my-funny-wifi-name", 197 | "Telnet Logging Enabled": true 198 | } 199 | ``` 200 | 201 | ### Configuration for Home Assistant 202 | configuration.yaml 203 | ```yaml 204 | mqtt: 205 | broker: 'm21.cloudmqtt.com' 206 | username: '[REDACTED]' 207 | password: '[REDACTED]' 208 | port: '[REDACTED]' 209 | discovery: true 210 | 211 | light: 212 | - platform: mqtt 213 | name: 'Arilux RGB Led Controller' 214 | availability_topic: 'rgb(w/ww)//status' 215 | brightness_command_topic: 'rgb(w/ww)//brightness/set' 216 | brightness_state_topic: 'rgb(w/ww)//brightness/state' 217 | command_topic: 'rgb(w/ww)//state/set' 218 | json_attributes_topic: 'rgb(w/ww)//attributes' 219 | rgb_command_topic: 'rgb(w/ww)//color/set' 220 | rgb_state_topic: 'rgb(w/ww)//color/state' 221 | state_topic: 'rgb(w/ww)//state/state' 222 | white_value_command_topic: 'rgb(w/ww)//white/set' 223 | white_value_state_topic: 'rgb(w/ww)//white/state' 224 | ``` 225 | 226 | ## Todo 227 | ### IR remote 228 | - Flash 229 | - Strobe 230 | - Fade 231 | - Smooth 232 | 233 | ### RF remote 234 | - Mode+ 235 | - Mode- 236 | - Speed+ 237 | - Speed- 238 | - Toggle 239 | 240 | ## Licence 241 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 242 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 243 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 244 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 245 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 246 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 247 | SOFTWARE. 248 | 249 | ## Home Assistant Community Discussion Forum 250 | For further information and to join the discussion for this firmware [check out this thread] on the Home Assistant Community Discussion Forum. 251 | 252 | ## Contributors 253 | - [@KmanOz]: Codes for the RF remote (Arilux AL-LC09) 254 | - [@DanGunvald]: RGBW/RGBWW support 255 | - [@robbiet480]: General cleanup and merging of RGBW/RGBWW code 256 | 257 | *If you like the content of this repo, please add a star! Thank you!* 258 | 259 | [ArduionJson]: https://github.com/bblanchon/ArduinoJson 260 | [@KmanOz]: https://github.com/KmanOz 261 | [@DanGunvald]: https://github.com/DanGunvald 262 | [@robbiet480]: https://github.com/robbiet480 263 | [MQTT Last Will and Testament]: http://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament 264 | [LC01-banggood]: http://www.banggood.com/ARILUX-AL-LC01-Super-Mini-LED-WIFI-Smart-RGB-Controller-For-RGB-LED-Strip-Light-DC-9-12V-p-1058603.html?rmmds=search 265 | [LC02-banggood]: http://www.banggood.com/ARILUX-AL-LC02-Super-Mini-LED-WIFI-APP-Controller-Dimmer-for-RGBW-LED-Strip-Light-DC-9-12V-p-1060222.html 266 | [LC03-banggood]: http://www.banggood.com/ARILUX-AL-LC03-Super-Mini-LED-WIFI-APP-Controller-Remote-Control-For-RGB-LED-Strip-DC-9-12V-p-1060223.html 267 | [LC04-banggood]: http://www.banggood.com/ARILUX-AL-LC04-Super-Mini-LED-WIFI-APP-Controller-Remote-Control-For-RGBW-LED-Strip-DC-9-12V-p-1060231.html 268 | [LC08-banggood]: http://www.banggood.com/ARILUX-AL-LC08-Super-Mini-LED-WIFI-APP-Controller-Dimmer-for-RGBWW-LED-Strip-Light-DC-5-28V-p-1081241.html 269 | [LC09-banggood]: http://www.banggood.com/ARILUX-AL-LC09-Super-Mini-LED-WIFI-APP-Controller-RF-Remote-Control-For-RGB-LED-Strip-DC9-28V-p-1081344.html 270 | [LC10-banggood]: http://www.banggood.com/ARILUX-AL-LC10-Super-Mini-LED-WIFI-APP-Controller-RF-Remote-Control-For-RGBW-LED-Strip-DC9-28V-p-1085111.html 271 | [LC11-banggood]: http://www.banggood.com/ARILUX-AL-LC11-Super-Mini-LED-WIFI-APP-Controller-RF-Remote-Control-For-RGBWW-LED-Strip-DC9-28V-p-1085112.html 272 | [esp8266]: https://en.wikipedia.org/wiki/ESP8266 273 | [Home Assistant's MQTT discovery functionality]: https://home-assistant.io/docs/mqtt/discovery/ 274 | [check out this thread]: https://community.home-assistant.io/t/alternative-firmware-for-arilux-al-lc03-for-use-with-mqtt-and-home-assistant-rgb-light-strip-controller/6328/16 275 | [MQTT]: http://mqtt.org/ 276 | -------------------------------------------------------------------------------- /config.example.h: -------------------------------------------------------------------------------- 1 | // Uncomment the block for your model 2 | // Begin LC01 block, uncomment starting below this line 3 | // #define RGB 4 | // #define DEVICE_MODEL "LC01" 5 | // End LC01 block, stop uncommenting above this line 6 | 7 | // Begin LC02 block, uncomment starting below this line 8 | // #define RGBW 9 | // #define DEVICE_MODEL "LC02" 10 | // End LC02 block, stop uncommenting above this line 11 | 12 | // Begin LC03 block, uncomment starting below this line 13 | // #define RGB 14 | // #define IR_REMOTE 15 | // #define DEVICE_MODEL "LC03" 16 | // End LC03 block, stop uncommenting above this line 17 | 18 | // Begin LC04 block, uncomment starting below this line 19 | // #define RGBW 20 | // #define IR_REMOTE 21 | // #define DEVICE_MODEL "LC04" 22 | // End LC04 block, stop uncommenting above this line 23 | 24 | // Begin LC08 block, uncomment starting below this line 25 | // #define RGBWW 26 | // #define DEVICE_MODEL "LC08" 27 | // End LC08 block, stop uncommenting above this line 28 | 29 | // Begin LC09 block, uncomment starting below this line 30 | // #define RGB 31 | // #define RF_REMOTE 32 | // #define DEVICE_MODEL "LC09" 33 | // End LC09 block, stop uncommenting above this line 34 | 35 | // Begin LC10 block, uncomment starting below this line 36 | // #define RGBW 37 | // #define RF_REMOTE 38 | // #define DEVICE_MODEL "LC10" 39 | // End LC10 block, stop uncommenting above this line 40 | 41 | // Begin LC11 block, uncomment starting below this line 42 | // #define RGBWW 43 | // #define RF_REMOTE 44 | // #define DEVICE_MODEL "LC11" 45 | // End LC11 block, stop uncommenting above this line 46 | 47 | // If you can't find your model above, you can use the defines below instead. 48 | 49 | // #define RGB 50 | // #define RGBW 51 | // #define RGBWW 52 | // #define IR_REMOTE 53 | // #define RF_REMOTE 54 | // #define DEVICE_MODEL "LC0X" 55 | 56 | // Wi-Fi 57 | #define WIFI_SSID "" 58 | #define WIFI_PASSWORD "" 59 | 60 | // MQTT server settings, leave username/password blank if no authentication required 61 | #define MQTT_SERVER "m21.cloudmqtt.com" 62 | #define MQTT_PORT 12345 63 | #define MQTT_USER "user" 64 | #define MQTT_PASS "pass" 65 | 66 | // MQTT topics 67 | // Leaving this as default will prefix all MQTT topics with RGB(W/WW)/ 68 | #define MQTT_TOPIC_PREFIX_TEMPLATE "%s/%s" 69 | 70 | // Last Will and Testament topic 71 | #define MQTT_STATUS_TOPIC_TEMPLATE "%s/status" 72 | 73 | // Enable Home Assistant MQTT discovery support. Requires ArduinoJSON library to be installed. 74 | // #define HOME_ASSISTANT_MQTT_DISCOVERY 75 | // #define HOME_ASSISTANT_MQTT_DISCOVERY_PREFIX "homeassistant" 76 | 77 | // Enable Home Assistant JSON Attributes support. Requires ArduinoJSON library to be installed. 78 | // #define HOME_ASSISTANT_MQTT_ATTRIBUTES 79 | // #define HOME_ASSISTANT_MQTT_ATTRIBUTES_TOPIC_TEMPLATE "%s/attributes" 80 | 81 | // Enable JSON. Requires ArduinoJSON library to be installed. 82 | // Home Assistant supports more features such as transitions, effects and flashing via JSON only. 83 | // #define JSON 84 | 85 | // Leave %s at the front if you wish to use the MQTT Topic Prefix configured above 86 | #ifdef JSON 87 | 88 | #define MQTT_JSON_STATE_TOPIC_TEMPLATE "%s/json/state" 89 | #define MQTT_JSON_COMMAND_TOPIC_TEMPLATE "%s/json/set" 90 | 91 | #else 92 | 93 | #define MQTT_STATE_STATE_TOPIC_TEMPLATE "%s/state/state" 94 | #define MQTT_STATE_COMMAND_TOPIC_TEMPLATE "%s/state/set" 95 | #define MQTT_BRIGHTNESS_STATE_TOPIC_TEMPLATE "%s/brightness/state" 96 | #define MQTT_BRIGHTNESS_COMMAND_TOPIC_TEMPLATE "%s/brightness/set" 97 | #define MQTT_COLOR_STATE_TOPIC_TEMPLATE "%s/color/state" 98 | #define MQTT_COLOR_COMMAND_TOPIC_TEMPLATE "%s/color/set" 99 | 100 | #if defined(RGBW) || defined (RGBWW) 101 | #define MQTT_WHITE_STATE_TOPIC_TEMPLATE "%s/white/state" 102 | #define MQTT_WHITE_COMMAND_TOPIC_TEMPLATE "%s/white/set" 103 | #endif 104 | 105 | // MQTT payloads 106 | #define MQTT_STATE_ON_PAYLOAD "ON" 107 | #define MQTT_STATE_OFF_PAYLOAD "OFF" 108 | 109 | #endif 110 | 111 | // Base hostname, used for the MQTT Client ID and OTA hostname 112 | #define HOST "ARILUX%s" 113 | 114 | // Enable console output via telnet 115 | // #define DEBUG_TELNET 116 | 117 | // TLS support, make sure to edit the fingerprint and the MQTT broker IP address if 118 | // you are not using CloudMQTT 119 | // #define TLS 120 | // #define TLS_FINGERPRINT "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07" 121 | -------------------------------------------------------------------------------- /images/Arilux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Arilux_AL-LC0X/d028b2b64cefec5b2adc5eb63778abe1ce2ff3d8/images/Arilux.png -------------------------------------------------------------------------------- /images/ESP12-F_pinout3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Arilux_AL-LC0X/d028b2b64cefec5b2adc5eb63778abe1ce2ff3d8/images/ESP12-F_pinout3.jpg -------------------------------------------------------------------------------- /images/Layout.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Arilux_AL-LC0X/d028b2b64cefec5b2adc5eb63778abe1ce2ff3d8/images/Layout.JPG -------------------------------------------------------------------------------- /images/Youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Arilux_AL-LC0X/d028b2b64cefec5b2adc5eb63778abe1ce2ff3d8/images/Youtube.png -------------------------------------------------------------------------------- /images/option2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamZorSec/Arilux_AL-LC0X/d028b2b64cefec5b2adc5eb63778abe1ce2ff3d8/images/option2.jpg -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "WRITING CONFIGURATION FILE" 3 | 4 | echo -e "#define DEVICE_MODEL \"Travis\"\n$(cat config.example.h)" > config.h 5 | 6 | echo "Wrote Travis device model to config.h" 7 | 8 | if [ -z "$RGB_TYPE" ]; then 9 | RGB_TYPE="RGB" 10 | fi 11 | 12 | echo -e "#define $RGB_TYPE\n$(cat config.h)" > config.h 13 | 14 | echo "Wrote #define $RGB_TYPE to config.h" 15 | 16 | if [ -n "$REMOTE_TYPE" ]; then 17 | echo -e "#define $REMOTE_TYPE\n$(cat config.h)" > config.h 18 | echo "Wrote #define $REMOTE_TYPE to config.h" 19 | fi 20 | 21 | if [ -n "$MQTT_MODE" ]; then 22 | echo -e "#define $MQTT_MODE\n$(cat config.h)" > config.h 23 | echo "Wrote #define $MQTT_MODE to config.h" 24 | fi 25 | 26 | if [ -n "$MQTT_DISCOVERY" ]; then 27 | echo -e "#define $MQTT_DISCOVERY\n$(cat config.h)" > config.h 28 | echo "Wrote #define $MQTT_DISCOVERY to config.h" 29 | fi 30 | 31 | echo "FINISHED WRITING CONFIGURATION FILE" 32 | --------------------------------------------------------------------------------