/* LIGHT (EXPERIMENTAL) Copyright (C) 2016-2017 by Xose Pérez Copyright (C) 2017 by François Déchery ------------------------------------------------------------------------------------------ Features : - ONLY RGB strips are supported (No RGBW or RGBWW) - IR remote supported (but not mandatory) -- moved to ir.ino - HSV (intuitive) WEB controls - MQTT & API "color_rgb" + "color_hsv" + "brightness" parameters - Uses the (amazing) Fastled library for fast and natural Color & Brightness - Several Animation Modes and Speed (controlled from Remote + Web + MQTT + API) ------------------------------------------------------------------------------------------ Not currently Implemented : - Saving/Restoring Settings - HomeAssistant ------------------------------------------------------------------------------------------ */ #ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR #include // #### Defined ########################################################################## #define ANIM_SPEED_STEP 20 #define ANIM1_SPEED 350 // flash ON Variable #define ANIM1_PAUSE 200 // flash OFF fixed #define ANIM2_SPEED 550 // strobe OFF variable #define ANIM2_PAUSE 150 // storbe ON fixed #define ANIM3_SPEED 100 // fade speed #define ANIM4_SPEED 700 // smooth speed #define ANIM5_SPEED 200 // party speed #define LED_DURATION 70 // Status led ON duration // #### Variables ######################################################################## // variables declarations ############################################################### CHSV _cur_color = CHSV(0,255,255); CHSV _cur_anim_color = CHSV(0,0,0); byte _cur_status = 0 ; byte _cur_anim_mode = 0 ; byte _cur_anim_step = 0; boolean _cur_anim_dir = true; unsigned long _cur_anim_speed = 1000; unsigned long _anim_last_update = millis(); unsigned long _last_status_led_time = 0; // ####################################################################################### // #### PRIVATE ########################################################################## // ####################################################################################### // --------------------------------------------------------------------------------------- void _updateStatusLed(){ if(millis() > _last_status_led_time + LED_DURATION ){ digitalWrite(LED1_PIN, LOW); } } // --------------------------------------------------------------------------------------- void _flashStatusLed(){ digitalWrite(LED1_PIN, HIGH); _last_status_led_time=millis(); } // --------------------------------------------------------------------------------------- void _buttonPower(boolean on){ _flashStatusLed(); DEBUG_MSG_P(PSTR("BUT Power: ")); _cur_anim_mode=0; if(on){ _cur_status=1; DEBUG_MSG_P(PSTR("ON : ")); _setLedsHSV(_cur_color); } else{ _cur_status=0; DEBUG_MSG_P(PSTR("OFF : ")); _setLedsHSV(CHSV {0,0,0}); } } // --------------------------------------------------------------------------------------- void _buttonAnimMode(byte val){ _flashStatusLed(); DEBUG_MSG_P(PSTR("BUT Anim Mode: %d\n"),val); _processAnimation(val, true, true); } // --------------------------------------------------------------------------------------- void _setAnimMode(byte val){ DEBUG_MSG_P(PSTR("[LIGHT] Set AnimMode: %d\n"),val); _processAnimation(val, true, false); } // --------------------------------------------------------------------------------------- void _buttonBrightness(boolean up){ DEBUG_MSG_P(PSTR("BUT Brightness: ")); if(up){ if(_cur_anim_mode==0){ DEBUG_MSG_P(PSTR("UP : ")); _buttonColorHSV(_cur_color, 1 ); } else{ DEBUG_MSG_P(PSTR("FASTER\n")); _buttonChangeSpeed( - ANIM_SPEED_STEP ); } } else{ if(_cur_anim_mode==0){ DEBUG_MSG_P(PSTR("DOWN : ")); _buttonColorHSV(_cur_color, -1 ); } else{ DEBUG_MSG_P(PSTR("SLOWER\n")); _buttonChangeSpeed( ANIM_SPEED_STEP ); } } } // --------------------------------------------------------------------------------------- void _buttonColorHSV(CHSV color, int offset){ _flashStatusLed(); DEBUG_MSG_P(PSTR("[LIGHT] Set Color to : ")); //DEBUG_MSG_P(PSTR("(from HSV=%d,%d,%d) "), color.h, color.s, color.v); color=_dimHSV(color,offset); //DEBUG_MSG_P(PSTR("(to HSV=%d,%d,%d) "), color.h, color.s, color.v); _setLedsHSV(color); if(color.v ==0 ){ _cur_status=0; } else{ _cur_status=1; } _cur_color=color; _romSaveCurrentColor(); } // --------------------------------------------------------------------------------------- void _buttonColorRVB(CRGB color, int offset){ _buttonColorHSV(_rgbToHsv(color), offset); } // --------------------------------------------------------------------------------------- void _buttonChangeSpeed(int offset){ _flashStatusLed(); if(offset !=0){ _cur_anim_speed=_cur_anim_speed + offset ; } } // --------------------------------------------------------------------------------------- CHSV _dimHSV(CHSV color, int offset){ offset=offset*10; int bright=color.v + offset; if(offset ==0){ return color; } else if(bright < 1){ bright=1; // no off } else if(bright > 255){ bright=255; } color.v=bright; return color; } // --------------------------------------------------------------------------------------- void _setBrightnessMapped(byte val){ DEBUG_MSG_P(PSTR("[LIGHT] Set from 0-100 Brightness : %u => "), val); val =map(val,0,100,0,255); _setBrightness(val); } // --------------------------------------------------------------------------------------- void _setBrightness(byte val){ DEBUG_MSG_P(PSTR("[LIGHT] Set Brightness to : %u\n"), val); _cur_color.v = val; _cur_anim_color.v = val; if(val==0){ _cur_status=0; } _buttonColorHSV(_cur_color,0); } // --------------------------------------------------------------------------------------- void _setAnimSpeed(unsigned long speed){ if(speed !=0){ _cur_anim_speed=speed ; } } // --------------------------------------------------------------------------------------- void _setLedsRGB(CRGB rgb){ analogWrite(LIGHT_CH1_PIN, rgb.r); analogWrite(LIGHT_CH2_PIN, rgb.g); analogWrite(LIGHT_CH3_PIN, rgb.b); if(_cur_anim_mode == 0){ DEBUG_MSG_P(PSTR("RGB=%3u,%3u,%3u\n"), rgb.r, rgb.g, rgb.b); } } // --------------------------------------------------------------------------------------- void _setLedsHSV(CHSV hsv){ if(_cur_anim_mode == 0){ DEBUG_MSG_P(PSTR("HSV=%3u,%3u,%3u - "), hsv.h, hsv.s, hsv.v); } _setLedsRGB( CHSV(hsv) ); } // --------------------------------------------------------------------------------------- void _confirmFlash(){ _setLedsRGB(CRGB::Black); delay(70); _setLedsRGB(CRGB::White); delay(70); _setLedsRGB(CRGB::Black); delay(70); _setLedsRGB(CRGB::White); delay(70); _setLedsRGB(CRGB::Black); delay(70); } // --------------------------------------------------------------------------------------- void _processAnimation(byte mode, boolean init, boolean is_button){ if(init){ if(_cur_anim_mode == mode && is_button){ _confirmFlash(); DEBUG_MSG_P(PSTR("[ANIM_%d] Stopped !!!\n"), mode); _cur_anim_mode=0; _cur_status=0; return; } else if(mode == 0){ DEBUG_MSG_P(PSTR("[ANIM_%d] Stopped !!!\n"), mode); _cur_anim_mode=0; _cur_status=0; return; } else{ if(is_button){ _confirmFlash(); } DEBUG_MSG_P(PSTR("[ANIM_%d] Started !!!\n"), mode); } } if(mode==1){ _anim1(init); } else if(mode==2){ _anim2(init); } else if(mode==3){ _anim3(init); } else if(mode==4){ _anim4(init); } else if(mode==5){ _anim5(init); } else{ //invalid mode } } // --------------------------------------------------------------------------------------- // anim1 : flash void _anim1(boolean init){ if(init){ _cur_anim_mode = 1; _cur_anim_speed = ANIM1_SPEED; _cur_status = 1; _cur_anim_step = 1; } unsigned long now= millis(); if(_cur_anim_step==1 && now > (_anim_last_update + _cur_anim_speed) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV(_cur_color); _cur_anim_step=0; _anim_last_update = now; } else if(_cur_anim_step==0 && now > (_anim_last_update + ANIM1_PAUSE) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV(CHSV {0,0,0}); _cur_anim_step=1; _anim_last_update = now; } } // --------------------------------------------------------------------------------------- // anim2 : strobe void _anim2(boolean init){ if(init){ _cur_anim_mode = 2; _cur_anim_speed = ANIM2_SPEED; _cur_status = 1; _cur_anim_step = 1; } unsigned long now= millis(); if(_cur_anim_step==1 && now > (_anim_last_update + ANIM2_PAUSE) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV(_cur_color); _cur_anim_step=0; _anim_last_update = now; } else if(_cur_anim_step==0 && now > (_anim_last_update + _cur_anim_speed) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV(CHSV {0,0,0}); _cur_anim_step=1; _anim_last_update = now; } } // --------------------------------------------------------------------------------------- // anim3 : fade void _anim3(boolean init){ if(init){ _cur_anim_mode = 3; _cur_anim_speed = ANIM3_SPEED; _cur_status = 1; _cur_anim_step = _cur_color.v; } unsigned long now= millis(); if( now > (_anim_last_update + _cur_anim_speed) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV( CHSV(_cur_color.h, _cur_color.s, dim8_lin(_cur_anim_step)) ); if(_cur_anim_dir){ if(_cur_anim_step == 255){ _cur_anim_dir=false; } else{ _cur_anim_step++; } } else{ if(_cur_anim_step == 1){ _cur_anim_dir=true; } else{ _cur_anim_step--; } } _anim_last_update = now; } } // --------------------------------------------------------------------------------------- // anim4 : smooth void _anim4(boolean init){ if(init){ _cur_anim_mode = 4; _cur_anim_speed = ANIM4_SPEED; _cur_status = 1; _cur_anim_step = 0; _cur_anim_color = _cur_color; _cur_anim_color.v = 255; } unsigned long now= millis(); if( now > (_anim_last_update + _cur_anim_speed) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _cur_anim_color.h=_cur_anim_step; _cur_anim_color.s=255; _setLedsHSV( CHSV(_cur_anim_step,255,255) ); _cur_anim_step++; _anim_last_update = now; if(_cur_anim_step > 255){ _cur_anim_step=0; } } } // --------------------------------------------------------------------------------------- // anim5 : party void _anim5(boolean init){ if(init){ _cur_anim_mode = 5; _cur_anim_speed = ANIM5_SPEED; _cur_status = 1; _cur_anim_step = 0; _cur_anim_color = _cur_color; _cur_anim_color.s= 255; _cur_anim_color.v= 255; } unsigned long now= millis(); if(_cur_anim_step == 1 && now > (_anim_last_update + _cur_anim_speed) ){ DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _cur_anim_color.h = random(0,255); _setLedsHSV(_cur_anim_color); _cur_anim_step = 0; _anim_last_update = now; } else if(_cur_anim_step==0 && now > (_anim_last_update + _cur_anim_speed + 15) ){ //DEBUG_MSG_P(PSTR("[ANIM_%d] Update : "), _cur_anim_mode); _setLedsHSV(CHSV {0,0,0}); _cur_anim_step = 1; _anim_last_update = now; } } // --------------------------------------------------------------------------------------- void _loopUpdateAnimation(){ _processAnimation(_cur_anim_mode, false, false); } // --------------------------------------------------------------------------------------- CHSV _rgbToHsv(CRGB rgb){ /* if(rgb.r <=1 && rgb.g <=1 && rgb.b <=1){ DEBUG_MSG_P(PSTR(" {Rounding RGB to black} ")); return CHSV {0,0,0}; } */ return rgb2hsv_approximate(rgb); } // --------------------------------------------------------------------------------------- void _romSaveCurrentColor(){ //save it to 1,2,3 positions //but dont stress eeprom if not needed /* CHSV rom =_romLoadColor(); if(_cur_color.h != rom.h){gw.saveState(1,_cur_color.h);} if(_cur_color.s != rom.s){gw.saveState(2,_cur_color.s);} if(_cur_color.v != rom.v){gw.saveState(3,_cur_color.v);} */ } // --------------------------------------------------------------------------------------- CHSV _romLoadColor(){ //load from 1,2,3 positions CHSV color; /* color.h=gw.loadState(1); color.s=gw.loadState(2); color.v=gw.loadState(3); */ return color; } // ---------------------------------------------- CRGB _longToRgb(unsigned long rgb){ CRGB out; out.r = rgb >> 16; out.g = rgb >> 8 & 0xFF; out.b = rgb & 0xFF; return out; } // --------------------------------------------------------------------------------------- CHSV _longToHsv(unsigned long hsv){ CHSV out; out.h = hsv >> 16; out.s = hsv >> 8 & 0xFF; out.v = hsv & 0xFF; return out; } // --------------------------------------------------------------------------------------- unsigned long _rgbToLong(CRGB in){ return (((long)in.r & 0xFF) << 16) + (((long)in.g & 0xFF) << 8) + ((long)in.b & 0xFF); } // --------------------------------------------------------------------------------------- unsigned long _hsvToLong(CHSV in){ return (((long)in.h & 0xFF) << 16) + (((long)in.s & 0xFF) << 8) + ((long)in.v & 0xFF); } // --------------------------------------------------------------------------------------- CRGB _charToRgb(const char * rgb) { char * p = (char *) rgb; // if color begins with a # then assume HEX RGB if (p[0] == '#') { ++p; } return _longToRgb( strtoul(p, NULL, 16) ); } // --------------------------------------------------------------------------------------- CHSV _charToHsv(const char * rgb) { char * p = (char *) rgb; // if color begins with a # then assume HEX RGB if (p[0] == '#') { ++p; } return _longToHsv( strtoul(p, NULL, 16) ); } // --------------------------------------------------------------------------------------- boolean _charColorIsValid(const char * rgb){ char * p = (char *) rgb; if (strlen(p) == 6 || strlen(p) == 7 ){ return true; } return false; } // --------------------------------------------------------------------------------------- void _CurrentColorToRGB(char * buffer) { snprintf_P(buffer, 8, PSTR("%06X"), _rgbToLong( CHSV(_cur_color))); } // --------------------------------------------------------------------------------------- void _CurrentColorToHSV(char * buffer) { snprintf_P(buffer, 8, PSTR("%06X"), _hsvToLong( _cur_color)); } // --------------------------------------------------------------------------------------- void _lightColorRestore() { /* for (unsigned int i=0; i < _channels.size(); i++) { _channels[i].value = getSetting("ch", i, 0).toInt(); } _brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); lightUpdate(false, false); */ } // --------------------------------------------------------------------------------------- void _lightAPISetup() { #if WEB_SUPPORT // API entry points (protected with apikey) if (lightHasColor()) { apiRegister(MQTT_TOPIC_COLOR_RGB, MQTT_TOPIC_COLOR_RGB, [](char * buffer, size_t len) { _CurrentColorToRGB(buffer); }, [](const char * payload) { _SetLightColorRGB(payload); lightUpdate(true, true); } ); apiRegister(MQTT_TOPIC_COLOR_HSV, MQTT_TOPIC_COLOR_HSV, [](char * buffer, size_t len) { _CurrentColorToHSV(buffer); }, [](const char * payload) { _SetLightColorHSV(payload); lightUpdate(true, true); } ); apiRegister(MQTT_TOPIC_BRIGHTNESS, MQTT_TOPIC_BRIGHTNESS, [](char * buffer, size_t len) { snprintf_P(buffer, len, PSTR("%d"), _cur_color.v); }, [](const char * payload) { _setBrightness(atoi(payload)); lightUpdate(true, true); } ); apiRegister(MQTT_TOPIC_ANIM_MODE, MQTT_TOPIC_ANIM_MODE, [](char * buffer, size_t len) { snprintf_P(buffer, len, PSTR("%d"), _cur_anim_mode); }, [](const char * payload) { _setAnimMode(atoi(payload)); lightUpdate(true, true); } ); apiRegister(MQTT_TOPIC_ANIM_SPEED, MQTT_TOPIC_ANIM_SPEED, [](char * buffer, size_t len) { snprintf_P(buffer, len, PSTR("%d"), _cur_anim_speed); }, [](const char * payload) { _setAnimSpeed(atoi(payload)); lightUpdate(true, true); } ); } /* for (unsigned int id=0; id "), value); current_rvb.r=value; _buttonColorRVB(current_rvb,0); } else if(id == 1 ){ DEBUG_MSG_P(PSTR("GREEN to : %d => "), value); current_rvb.g=value; _buttonColorRVB(current_rvb,0); } else if(id == 2 ){ DEBUG_MSG_P(PSTR("BLUE to : %d => "), value); current_rvb.b=value; _buttonColorRVB(current_rvb,0); } else{ DEBUG_MSG_P(PSTR(" [ERROR] SET lightChannel %s To %d \n"), id, value); } } // --------------------------------------------------------------------------------------- // Get Brightness unsigned int lightBrightness() { return _cur_color.v; } // --------------------------------------------------------------------------------------- // Set Brightness void lightBrightness(unsigned int b) { b=constrain(b, 0, 255); DEBUG_MSG_P(PSTR("[WEB|API] Set Brightness to : %d\n"), b); _cur_color.v=b; _setLedsHSV(_cur_color); //set status if(b > 0){ _cur_status=1; } else{ _cur_status=0; } } // --------------------------------------------------------------------------------------- // Get Color String lightColor() { char rgb[8]; snprintf_P(rgb, 8, PSTR("#%06X"), _rgbToLong( CHSV(_cur_color))); return String(rgb); //return String(""); } String lightColorH(){ return String(_cur_color.h); } String lightColorS(){ return String(_cur_color.s); } String lightColorV(){ return String(_cur_color.v); } // --------------------------------------------------------------------------------------- // Set Color void lightColor(const char * color) { //used only from settings _SetLightColorRGB(color); } void _SetLightColorRGB(const char * color) { //used only from settings DEBUG_MSG_P(PSTR("[WEB|API] Set (#RGB) Color to : ")); if( _charColorIsValid(color) ){ DEBUG_MSG_P(PSTR("%s \n"), color); _buttonColorRVB(_charToRgb(color), 0); } else{ DEBUG_MSG_P(PSTR(" Canceled ('%s' is invalid) !\n"), color); } } void _SetLightColorHSV(const char * color) { DEBUG_MSG_P(PSTR("[WEB|API] Set (#HSV) Color to : ")); if( _charColorIsValid(color) ){ DEBUG_MSG_P(PSTR("%s \n"), color); _buttonColorHSV(_charToHsv(color), 0); } else{ DEBUG_MSG_P(PSTR(" Canceled ('%s' is invalid) !\n"), color); } } void setLightColor (const char * h, const char * s, const char * v){ DEBUG_MSG_P(PSTR("[WEB|API] Set Color from (%s,%s,%s) "), h, s, v); CHSV color; color.h=strtoul(h, NULL, 10); color.s=strtoul(s, NULL, 10); color.v=strtoul(v, NULL, 10); DEBUG_MSG_P(PSTR("to (%d,%d,%d) "), color.h, color.s, color.v); _buttonColorRVB(color, 0); } // --------------------------------------------------------------------------------------- bool lightHasColor() { // bool useColor = getSetting("useColor", LIGHT_USE_COLOR).toInt() == 1; // return useColor && (_channels.size() > 2); //if(_cur_status || _cur_anim_mode){return 1;} return true; } // --------------------------------------------------------------------------------------- // Get State bool lightState() { //DEBUG_MSG_P(PSTR("[->LIGHT] _cur_status is : %d \n"),_cur_status); return _cur_status; } // --------------------------------------------------------------------------------------- // Set State void lightState(bool state){ DEBUG_MSG_P(PSTR("[WEB|API] Set Relay to : %u => "), state); //if(state != _cur_status){ _buttonPower(state); //} } // --------------------------------------------------------------------------------------- String lightAnimMode(){ return String(_cur_anim_mode); } void lightAnimMode(const char * val){ DEBUG_MSG_P(PSTR("[WEB|API] Set AnimMode to %s\n"), val); _setAnimMode(strtoul(val, NULL, 10)); } // --------------------------------------------------------------------------------------- String lightAnimSpeed(){ return String(_cur_anim_speed); } void lightAnimSpeed(const char * val){ DEBUG_MSG_P(PSTR("[WEB|API] Set AnimSpeed to %s \n"), val); _setAnimSpeed(strtoul(val, NULL, 10)); } // --------------------------------------------------------------------------------------- // MQTT // --------------------------------------------------------------------------------------- void _lightMQTTCallback(unsigned int type, const char * topic, const char * payload) { if (type == MQTT_CONNECT_EVENT) { if (lightHasColor()) { mqttSubscribe(MQTT_TOPIC_BRIGHTNESS); mqttSubscribe(MQTT_TOPIC_COLOR_RGB); mqttSubscribe(MQTT_TOPIC_COLOR_HSV); mqttSubscribe(MQTT_TOPIC_ANIM_MODE); mqttSubscribe(MQTT_TOPIC_ANIM_SPEED); } //char buffer[strlen(MQTT_TOPIC_CHANNEL) + 3]; //snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_CHANNEL); //mqttSubscribe(buffer); } if (type == MQTT_MESSAGE_EVENT) { // Match topic String t = mqttSubtopic((char *) topic); // Color RGB if (t.equals(MQTT_TOPIC_COLOR_RGB)) { _SetLightColorRGB(payload); lightUpdate(true, mqttForward()); } // Color HSV if (t.equals(MQTT_TOPIC_COLOR_HSV)) { _SetLightColorHSV(payload); lightUpdate(true, mqttForward()); } // ANIM Mode if (t.equals(MQTT_TOPIC_ANIM_MODE)) { _setAnimMode(atoi(payload)); lightUpdate(true, mqttForward()); } // ANIM Speed if (t.equals(MQTT_TOPIC_ANIM_SPEED)) { _setAnimSpeed(atoi(payload)); lightUpdate(true, mqttForward()); } // Brightness if (t.equals(MQTT_TOPIC_BRIGHTNESS)) { _setBrightness (constrain(atoi(payload), 0, LIGHT_MAX_BRIGHTNESS)); lightUpdate(true, mqttForward()); } /* // Channel if (t.startsWith(MQTT_TOPIC_CHANNEL)) { unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt(); if (channelID >= _channels.size()) { DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID); return; } lightChannel(channelID, atoi(payload)); lightUpdate(true, mqttForward()); } */ } } #endif // LIGHT_IR_PIN