You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
17 KiB
529 lines
17 KiB
/* |
|
* Wemos d1 mini |
|
* Flash Size: 4M (1M SPIFFS) |
|
*/ |
|
|
|
#include <Arduino.h> |
|
#include <Homie.h> |
|
|
|
#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)) { //if brightness not reached |
|
brightness += brightness_change_per_loop; |
|
if ( ( old_brightness <= set_brightness && set_brightness <= brightness ) || ( old_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)) { //if temperature not reached |
|
temperature += temperature_change_per_loop; |
|
if ( ( old_temperature <= set_temperature && set_temperature <= temperature ) || ( old_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; |
|
} |
|
|
|
|