/* * Wemos d1 mini * Flash Size: 4M (1M SPIFFS) */ #include #include #define FW_NAME "esplight" #define FW_VERSION "1.0.0" bool enableHandler(const HomieRange& range, const String& value); bool fadetimeHandler(const HomieRange& range, const String& value); bool brightnessHandler(const HomieRange& range, const String& value); #ifdef DUALCOLOR bool temperatureHandler(const HomieRange& range, const String& value); #endif void loopHandler(); float mapFloat(float x, float in_min, float in_max, float out_min, float out_max); //#define DEBUG //turns on continuous serial debug printing HomieNode lightNode("light", "Light", "light"); //paramters: topic, $name, $type boolean enable=false; float enable_fadevalue=0; //0=off, 1=on float enable_fadevalue_change_per_loop=0.01; //fixed value. For manual calculatoin: enable_fadevalue_change_per_loop=_difference/fadetime*UPDATETIME; float set_brightness=2; //0 to 2. 1 is maximum brightness with full color range still possible. 2 is full brightness regardless of possible color temp #define BRIGHTNESS_MIN 0.0 #define BRIGHTNESS_MAX 2.0 //if temperature is in between both strips brightness of 2 means both are at full power. otherwise brightness will be clipped float brightness=set_brightness; float brightness_change_per_loop=0; //will be calculated by Handler #ifdef DUALCOLOR float set_temperature=(TEMPERATURE_MAX+TEMPERATURE_MIN)/2; float temperature=set_temperature; float temperature_change_per_loop=0; //will be calculated by Handler #endif uint16_t fadetime=0; //0=instant. value is time in milliseconds #define FADETIME_MIN 0 #define FADETIME_MAX 60000 long last_updatetime=0; #define UPDATETIME 10 //after how many ms pwm values will be updated //Button stuff #define BUTTONUPDATETIME 20 long last_buttonupdatetime=0; uint8_t btnAstate=0; //for button state machine long btnAtime=0; #ifdef DUALCOLOR uint8_t btnBstate=0; //for button state machine long btnBtime=0; #endif #define BTNHOLDTIME 1000 boolean holdDirection_brightness=false; #ifdef DUALCOLOR boolean holdDirection_temperature=false; #endif #define HOLDBRIGHTNESSCHANGE_PER_LOOP 0.01 //depends on BUTTONUPDATETIME. BUTTONUPDATETIME/1000/HOLDBRIGHTNESSCHANGE_PER_LOOP=seconds to change a full cycle(0 to 1) #ifdef DUALCOLOR #define HOLDTEMPERATURECHANGE_PER_LOOP 10.0 // (TEMPERATURE_MAX-TEMPERATURE_MIN)*BUTTONUPDATETIME/1000/HOLDBRIGHTNESSCHANGE_PER_LOOP=seconds to change a full cycle (min to max) #endif boolean flag_updatePWM=false; //if manually set brightness or temperature, set this flag //Debug long last_debugupdatetime=0; #define DEBUGUPDATETIME 500 //To check if values changed (for mqtt response) boolean known_enable=!enable; //start with differend known values, actual values will be send first float known_set_brightness=set_brightness+1; #ifdef DUALCOLOR float known_set_temperature=set_temperature+1; #endif void setup() { Serial.begin(115200); Serial.println("Hello"); analogWriteRange(PWM_MAX); analogWriteFreq(PWM_FREQUENCY); #ifdef DUALCOLOR pinMode(LED_WW, OUTPUT); pinMode(LED_CW, OUTPUT); analogWrite(LED_CW, PWM_MAX); //high = off analogWrite(LED_WW, PWM_MAX); //high = off #else pinMode(LED_PWM, OUTPUT); analogWrite(LED_PWM, PWM_MAX); //high = off #endif #ifdef DUALCOLOR pinMode(BTN_A, INPUT); pinMode(BTN_B, INPUT); #else pinMode(BTN_A, INPUT); #endif Homie_setFirmware(FW_NAME, FW_VERSION); Homie_setBrand(FW_NAME); Homie.setLoopFunction(loopHandler); lightNode.advertise("brightness").settable(brightnessHandler); lightNode.advertise("fadetime").settable(fadetimeHandler); lightNode.advertise("enable").settable(enableHandler); #ifdef DUALCOLOR lightNode.advertise("temperature").settable(temperatureHandler); #endif Homie.setup(); } void loop() { Homie.loop(); } void loopHandler() { long loopmillis=millis(); if (loopmillis >= last_buttonupdatetime+BUTTONUPDATETIME ) { last_buttonupdatetime = loopmillis; // #### Button A #### boolean flag_btnApress=false; //short press on release boolean flag_btnAholdstart=false; //long press on start boolean flag_btnAhold=false; //long press after long press time boolean flag_btnAholdrelease=false; //long press on release if (digitalRead(BTN_A)) { //Button State Machine switch (btnAstate) { case 0: //was not pressed btnAstate=1; btnAtime=loopmillis; //start timer break; case 1: //was pressed last time checked if (loopmillis>btnAtime+BTNHOLDTIME) { btnAstate=2; flag_btnAholdstart=true; } break; case 2: //button hold time reached flag_btnAhold=true; break; } }else { if (btnAstate==1) { //short press btnAstate=0; //reset state flag_btnApress=true; }else if(btnAstate==2) { //long press released flag_btnAholdrelease=true; btnAstate=0; //reset state } } // #### END Button A Check #### #ifdef DUALCOLOR // #### Button B #### boolean flag_btnBpress=false; //short press on release boolean flag_btnBholdstart=false; //long press on start boolean flag_btnBhold=false; //long press after long press time boolean flag_btnBholdrelease=false; //long press on release if (digitalRead(BTN_B)) { //Button State Machine switch (btnBstate) { case 0: //was not pressed btnBstate=1; btnBtime=loopmillis; //start timer break; case 1: //was pressed last time checked if (loopmillis>btnBtime+BTNHOLDTIME) { btnBstate=2; flag_btnBholdstart=true; } break; case 2: //button hold time reached flag_btnBhold=true; break; } }else { if (btnBstate==1) { //short press btnBstate=0; //reset state flag_btnBpress=true; }else if(btnBstate==2) { //long press released flag_btnBholdrelease=true; btnBstate=0; //reset state } } // #### END Button B Check #### #endif //Button handling #ifdef DUALCOLOR if (flag_btnApress || flag_btnBpress){ //short press either button #else if (flag_btnApress){ //short press button #endif enable = !enable; //switch on/off flag_updatePWM=true; //update pwm values } if (!enable && flag_btnAholdstart ) { //in not enabled and brightness button held down enable=true; //enable light set_brightness=0; //reset brightness brightness=set_brightness; //immediately holdDirection_brightness=true; //increase brightness } else if (enable) { //only change values if enabled // Button A Longpress Handling if (flag_btnAholdstart) { /* //Change only hold direction at extremes if (set_brightness>=BRIGHTNESS_MAX) { //if hold started with brightness at one extreme holdDirection_brightness=false; //direction decrease } if (set_brightness<=BRIGHTNESS_MIN) { //if hold started with brightness at one extreme holdDirection_brightness=true; //direction increase } */ holdDirection_brightness=!holdDirection_brightness; //change direction everytime } if (flag_btnAhold) { //brightness if (holdDirection_brightness) { set_brightness += HOLDBRIGHTNESSCHANGE_PER_LOOP; }else{ set_brightness -= HOLDBRIGHTNESSCHANGE_PER_LOOP; } set_brightness = constrain(set_brightness, BRIGHTNESS_MIN, BRIGHTNESS_MAX); brightness=set_brightness; //change immediately flag_updatePWM=true; //update pwm values } if (flag_btnAholdrelease) { } #ifdef DUALCOLOR // Button B Longpress Handling if (flag_btnBholdstart) { /* //Change only hold direction at extremes if (set_temperature>=TEMPERATURE_MAX) { //if hold started with brightness at one extreme holdDirection_temperature=false; //direction decrease } if (set_temperature<=TEMPERATURE_MIN) { //if hold started with brightness at one extreme holdDirection_temperature=true; //direction increase } */ holdDirection_temperature=!holdDirection_temperature; //change direction everytime } if (flag_btnBhold) { //brightness if (holdDirection_temperature) { set_temperature += HOLDTEMPERATURECHANGE_PER_LOOP; }else{ set_temperature -= HOLDTEMPERATURECHANGE_PER_LOOP; } set_temperature = constrain(set_temperature, TEMPERATURE_MIN, TEMPERATURE_MAX); temperature=set_temperature; //change immediately flag_updatePWM=true; //update pwm values } if (flag_btnBholdrelease) { } #endif } } if (loopmillis >= last_updatetime+UPDATETIME ) { last_updatetime = loopmillis; float old_brightness = brightness; //store last brightness if ( (brightness_change_per_loop<0 && brightness>set_brightness) || (brightness_change_per_loop>0 && brightness= set_brightness && set_brightness >= brightness ) ) { //overshot set value brightness = set_brightness; } flag_updatePWM=true; //force update } #ifdef DUALCOLOR float old_temperature = temperature; //store last temperature if ( (temperature_change_per_loop<0 && temperature>set_temperature) || (temperature_change_per_loop>0 && temperature= set_temperature && set_temperature >= temperature ) ) { //overshot set value temperature = set_temperature; } flag_updatePWM=true; //force update } #endif //Sleep if ( (!enable && enable_fadevalue>0) || (enable && enable_fadevalue<1) ) { //not fully turned off or on if (!enable) { //turn off enable_fadevalue-=enable_fadevalue_change_per_loop; }else{ //turn on enable_fadevalue+=enable_fadevalue_change_per_loop; } if (enable_fadevalue>1) { enable_fadevalue=1; } //limit if (enable_fadevalue<0) { enable_fadevalue=0; } //limit flag_updatePWM=true; //force update } //calculate and update pwm #ifdef DUALCOLOR if (brightness != set_brightness || temperature != set_temperature || flag_updatePWM) { //if target not reached #else if (brightness != set_brightness || flag_updatePWM) { //if target not reached #endif flag_updatePWM=false; // reset flag //calculate pwm values #ifdef DUALCOLOR uint16_t pwmCW; uint16_t pwmWW; float temp=mapFloat(temperature, TEMPERATURE_MIN, TEMPERATURE_MAX, 0.0,1.0); //0=warmwhite, 1=coldwhite pwmCW=pow((brightness*enable_fadevalue)/2.0, BRIGHTNESSCURVE) *2.0 *temp *PWM_MAX; //calculate brightness for led stripe, scale to 0-1, ^2, rescale to 0-2, scale for pwm pwmWW=pow((brightness*enable_fadevalue)/2.0, BRIGHTNESSCURVE) *2.0 *(1-temp) *PWM_MAX; if (pwmCW>PWM_MAX) { pwmCW=PWM_MAX; } //limit if (pwmWW>PWM_MAX) { pwmWW=PWM_MAX; } //limit Serial.print("brightness"); Serial.println(brightness); Serial.print("CW="); Serial.print(pwmCW); Serial.print(", WW="); Serial.println(pwmWW); analogWrite(LED_WW, PWM_MAX-pwmWW); //full pwm is led off analogWrite(LED_CW, PWM_MAX-pwmCW); //full pwm is led off #else uint16_t pwm; pwm=pow((brightness*enable_fadevalue), BRIGHTNESSCURVE) *1.0 *PWM_MAX; //calculate brightness for led stripe, scale to 0-1, ^2, rescale to 0-2, scale for pwm #ifdef PWM_MINDIMMED if (pwm>0){ //if light should be dimmed at minimum setting pwm=map(pwm,0,PWM_MAX,PWM_MINDIMMED,PWM_MAX); //lowest dimming setting is 0.33. rescale to fit } #endif if (pwm>PWM_MAX) { pwm=PWM_MAX; } //limit analogWrite(LED_PWM, PWM_MAX-pwm); //full pwm is led off #endif } } //send new values back to broker if (known_enable!=enable) { lightNode.setProperty("enable").send(enable ? "true" : "false"); known_enable=enable; } if (known_set_brightness!=set_brightness) { lightNode.setProperty("brightness").send(String(set_brightness)); known_set_brightness=set_brightness; } #ifdef DUALCOLOR if (known_set_temperature!=set_temperature) { lightNode.setProperty("temperature").send(String(set_temperature)); known_set_temperature=set_temperature; } #endif #ifdef DEBUG if (loopmillis >= last_debugupdatetime+DEBUGUPDATETIME ) { last_debugupdatetime = loopmillis; /* if (!enable) { Serial.print("not enable. "); } Serial.print("bright="); Serial.print(brightness); Serial.print(" set="); Serial.print(set_brightness); Serial.print("| temp="); Serial.print(temperature); Serial.print(" set="); Serial.print(set_temperature); Serial.print(" change="); Serial.print(brightness_change_per_loop); Serial.print(" enable_fadevalue="); Serial.println(enable_fadevalue); */ uint16_t pwmCW; uint16_t pwmWW; float temp=mapFloat(temperature, TEMPERATURE_MIN, TEMPERATURE_MAX, 0.0,1.0); //0=warmwhite, 1=coldwhite pwmCW=pow((brightness*enable_fadevalue)*temp/2.0, 2) *2.0 *PWM_MAX; //calculate brightness for led stripe, scale to 0-1, ^2, rescale to 0-2, scale for pwm pwmWW=pow((brightness*enable_fadevalue)*(1-temp)/2.0, 2) *2.0 *PWM_MAX; if (pwmCW>PWM_MAX) { pwmCW=PWM_MAX; } //limit if (pwmWW>PWM_MAX) { pwmWW=PWM_MAX; } //limit if (enable) { Serial.print("bright="); Serial.print(brightness); Serial.print(PWM_MAX-pwmWW); Serial.print(", "); Serial.println(PWM_MAX-pwmCW); }else{ Serial.print("off"); Serial.print(", "); Serial.println("off"); } } #endif } bool brightnessHandler(const HomieRange& range, const String& value) { if (range.isRange) { return false; //if range is given but index is not in allowed range } Homie.getLogger() << "brightness " << ": " << value << endl; //lightNode.setProperty("brightness").send(value); //done in main loop if (value.toFloat() >= BRIGHTNESS_MIN && value.toFloat() <= BRIGHTNESS_MAX) { set_brightness=value.toFloat(); if (!enable) { //if light was off set final brightness immediately (fade on is done by enable fade) brightness=set_brightness; } //enable light when brightness set enable=true; flag_updatePWM=true; //force update }else { Homie.getLogger() << "Value outside range" << endl; return false; } float _difference=set_brightness-brightness; if (fadetime>0) { brightness_change_per_loop = _difference/fadetime*UPDATETIME; } else { //special case for instant change brightness_change_per_loop = _difference; } return true; } bool fadetimeHandler(const HomieRange& range, const String& value) { //fadetime for temperature and brightness in milliseconds if (range.isRange) { return false; //if range is given but index is not in allowed range } Homie.getLogger() << "fadetime " << ": " << value << endl; lightNode.setProperty("fadetime").send(value); if (value.toInt() >= FADETIME_MIN && value.toInt() <= FADETIME_MAX) { fadetime=value.toInt(); }else { Homie.getLogger() << "Value outside range" << endl; return false; } return true; } #ifdef DUALCOLOR bool temperatureHandler(const HomieRange& range, const String& value) { if (range.isRange) { return false; //if range is given but index is not in allowed range } Homie.getLogger() << "temperature " << ": " << value << endl; //lightNode.setProperty("temperature").send(value); //done in main loop if (value.toFloat() >= TEMPERATURE_MIN && value.toFloat() <= TEMPERATURE_MAX) { set_temperature=value.toFloat(); if (!enable) { //if light is off set final temperature immediately temperature=set_temperature; } }else { Homie.getLogger() << "Value outside range" << endl; return false; } float _difference=set_temperature-temperature; if (fadetime>0) { temperature_change_per_loop = _difference/fadetime*UPDATETIME; } else { //special case for instant change temperature_change_per_loop = _difference; } return true; } #endif bool enableHandler(const HomieRange& range, const String& value) { //change on off if (range.isRange) { return false; //if range is given but index is not in allowed range } Homie.getLogger() << "enable " << ": " << value << endl; //lightNode.setProperty("enable").send(value); //done in main loop if (value=="false") { enable=false; } else if(value=="true") { enable=true; } else { Homie.getLogger() << "Value outside range" << endl; return false; } flag_updatePWM=true; //force update return true; } float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }