tischlicht/controllerHomie/controllerHomie.ino

504 lines
16 KiB
C++

/*
* Wemos d1 mini
* Flash Size: 4M (1M SPIFFS)
*/
#include <Homie.h>
#define FW_NAME "tischlicht"
#define FW_VERSION "1.0.3"
//#define DEBUG //turns on continuous serial debug printing
/*
* To Update configuration (wifi credentials) from data/homie/config.json:
* Connect to serial. On ESP-12E connect flash jumper
* Apply Power to ESP
* Optional: upload sketch
* Tools - Sketch Data Upload
* Remove jumper
* /
//http://homieiot.github.io/homie-esp8266/docs/develop/configuration/json-configuration-file/
//curl -X PUT http://homie.config/config -d @config.json --header "Content-Type: application/json"
/*Example data/homie/config.json
{
"name": "pringleslight",
"device_id": "pringleslight",
"wifi": {
"ssid": "CTDO-IoT",
"password": "12345678"
},
"mqtt": {
"host": "raum.ctdo.de",
"port": 1883,
"auth": false
},
"ota": {
"enabled": false
}
}
*/
HomieNode lightNode("light", "light");
//(pin x) nunbering CCW starting with rst
#define LED_WW 14 //D5 = GPIO14 (pin5)
#define LED_CW 12 //D6 = GPIO12 (pin6)
#define BTN_A 13 //D7 = GPIO13 (pin 7)
#define BTN_B 15 //D8 = GPIO15 (pin 10)
/*
* VCC (pin 8)
* GND (pin 9)
*/
#define PWM_MAX 1023 //10 bit dac
#define PWM_FREQUENCY 500 //default: 1000 Hz
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 1
#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
#define BRIGHTNESSCURVE 1.4
float brightness=set_brightness;
float brightness_change_per_loop=0; //will be calculated by Handler
#define TEMPERATURE_MIN 2760 //temperature of warm white leds
#define TEMPERATURE_MAX 5640//temperature of cold white leds
float set_temperature=(TEMPERATURE_MAX+TEMPERATURE_MIN)/2;
float temperature=set_temperature;
float temperature_change_per_loop=0; //will be calculated by Handler
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;
uint8_t btnBstate=0; //for button state machine
long btnBtime=0;
#define BTNHOLDTIME 1000
boolean holdDirection_brightness=false;
boolean holdDirection_temperature=false;
#define HOLDBRIGHTNESSCHANGE_PER_LOOP 0.01 //depends on BUTTONUPDATETIME. BUTTONUPDATETIME/1000/HOLDBRIGHTNESSCHANGE_PER_LOOP=seconds to change a full cycle(0 to 1)
#define HOLDTEMPERATURECHANGE_PER_LOOP 10.0 // (TEMPERATURE_MAX-TEMPERATURE_MIN)*BUTTONUPDATETIME/1000/HOLDBRIGHTNESSCHANGE_PER_LOOP=seconds to change a full cycle (min to max)
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;
float known_set_temperature=set_temperature+1;
void setup() {
Serial.begin(115200);
Serial.println("Hello");
pinMode(LED_WW, OUTPUT);
pinMode(LED_CW, OUTPUT);
analogWriteFreq(PWM_FREQUENCY);
analogWrite(LED_CW, PWM_MAX); //high = off
analogWrite(LED_WW, PWM_MAX); //high = off
pinMode(BTN_A, INPUT);
pinMode(BTN_B, INPUT);
Homie_setFirmware(FW_NAME, FW_VERSION);
Homie_setBrand(FW_NAME);
Homie.setLoopFunction(loopHandler);
lightNode.advertise("brightness").settable(brightnessHandler);
lightNode.advertise("temperature").settable(temperatureHandler);
lightNode.advertise("fadetime").settable(fadetimeHandler);
lightNode.advertise("enable").settable(enableHandler);
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 ####
// #### 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 ####
//Button handling
if (flag_btnApress || flag_btnBpress){ //short press either button
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) {
}
// 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) {
}
}
}
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
}
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
}
//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
if (brightness != set_brightness || temperature != set_temperature || flag_updatePWM) { //if target not reached
flag_updatePWM=false; // reset flag
//calculate pwm values
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
analogWrite(LED_WW, PWM_MAX-pwmWW); //full pwm is led off
analogWrite(LED_CW, PWM_MAX-pwmCW); //full pwm is led off
/*
if (enable) {
analogWrite(LED_WW, PWM_MAX-pwmWW); //full pwm is led off
analogWrite(LED_CW, PWM_MAX-pwmCW); //full pwm is led off
}else{
analogWrite(LED_WW, PWM_MAX); //light off
analogWrite(LED_CW, PWM_MAX); //light off
}*/
}
}
//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;
}
if (known_set_temperature!=set_temperature) {
lightNode.setProperty("temperature").send(String(set_temperature));
known_set_temperature=set_temperature;
}
#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();
}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 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();
}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;
}
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;
}
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;
}