#include #include #include #include #include #include #include #include "config.h" //********* Config block *********// // ch1, ch2, ch3, ch4 uint8_t pins[LIGHTS_COUNT] = { 14, 12, 15, 13 }; IPAddress strip_ip(192, 168, 0, 26); // choose an unique IP Adress IPAddress gateway_ip(192, 168, 0, 1); // Router IP IPAddress subnet_mask(255, 255, 255, 0); IPAddress dns(192, 168, 0, 1); //********************************// #define LIGHT_VERSION 2.1 #define LAST_STATE_STARTUP_LIGHT_LAST_STATE 0 #define LAST_STATE_STARTUP_LIGHT_ON_STATE 1 #define LAST_STATE_STARTUP_LIGHT_OFF_STATE 2 #define LIGHT_STATE_ON 1 #define LIGHT_STATE_OFF 0 #define TIMING_CONTROL_ENABLED 1 #define TIMING_CONTROL_DISABLED 0 #define SCENE_RELEAX 0 #define SCENE_BRIGHT 1 #define SCENE_NIGHTLY 2 // 10 bit PWM #define PWM_OFF 0 // 0V #define PWM_MIN 0 // 0V - minimum light amount (~1%) #define PWM_MAX 1023 // 24V - maximum light amount (100%) #define BRI_TO_PWM_FACTOR 4.012 // 24V-15V = 24V range //********************************// uint8_t scene; uint8_t tc_enabled; bool light_state[LIGHTS_COUNT]; bool in_transition; int default_transitiontime = 4; // 4 seconds int transitiontime[LIGHTS_COUNT]; int bri[LIGHTS_COUNT]; uint16_t current_pwm[LIGHTS_COUNT]; float step_level[LIGHTS_COUNT]; float current_bri[LIGHTS_COUNT]; byte mac[6]; ESP8266WebServer server(80); ESP8266HTTPUpdateServer httpUpdateServer; uint32_t last_lightengine_activity = 0; //********************************// void apply_scene(uint8_t new_scene, uint8_t light) { if (new_scene == SCENE_RELEAX) { bri[light] = 144; } else if (new_scene == SCENE_BRIGHT) { bri[light] = 254; } else if (new_scene == SCENE_NIGHTLY) { bri[0] = 25; bri[1] = 0; bri[2] = 0; bri[3] = 0; } } //********************************// void process_lightdata(uint8_t light, float tt) { if (light_state[light]) { step_level[light] = (bri[light] - current_bri[light]) / tt; } else { step_level[light] = current_bri[light] / tt; } } //********************************// void lightEngine() { if (millis() < (last_lightengine_activity + TIME_LIGHTENGINE_INTERVAL_MS)) { // abort processing, the transition setting is a delay of seconds return; } last_lightengine_activity = millis(); for (int i = 0; i < LIGHTS_COUNT; i++) { if (light_state[i]) { if (bri[i] != current_bri[i]) { in_transition = true; current_bri[i] += step_level[i] / BRI_MOD_STEPS_PER_SEC; if ((step_level[i] > 0.0 && current_bri[i] > bri[i]) || (step_level[i] < 0.0 && current_bri[i] < bri[i])) { current_bri[i] = bri[i]; //Serial.println("Reached target bri[" + (String)i + "] = " + (String)bri[i]); } uint16_t tmp_pwm = calcPWM(current_bri[i]); current_pwm[i] = tmp_pwm; //Serial.println("lon: pin" + (String)i + " = PWM(" + (String)tmp_pwm + ")"); analogWrite(pins[i], tmp_pwm); } } else { if (current_bri[i] != 0) { in_transition = true; current_bri[i] -= step_level[i] / BRI_MOD_STEPS_PER_SEC; if (current_bri[i] < 0) { current_bri[i] = 0; //Serial.println("Reached target bri[" + (String)i + "] = " + (String)bri[i]); } uint16_t tmp_pwm = calcPWM(current_bri[i]); current_pwm[i] = tmp_pwm; //Serial.println("loff: pin" + (String)i + " = PWM(" + (String)tmp_pwm + ")"); analogWrite(pins[i], tmp_pwm); } } } // for loop end if (in_transition) { delay(6); in_transition = false; } } //********************************// uint16_t calcPWM(float tbri) { uint16_t tmp_pwm = PWM_OFF; if (tbri > 0.0) { tmp_pwm = PWM_MIN + (int)(tbri * BRI_TO_PWM_FACTOR); } if (tmp_pwm > PWM_MAX) { tmp_pwm = PWM_MAX; } return tmp_pwm; } //********************************// void read_eeprom_config() { for (uint8_t light = 0; light < LIGHTS_COUNT; light++) { apply_scene(EEPROM.read(EEPROM_SCENE_ADDRESS), light); step_level[light] = bri[light] / 150.0; if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) == LAST_STATE_STARTUP_LIGHT_LAST_STATE || (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) == LAST_STATE_STARTUP_LIGHT_ON_STATE && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_ON)) { light_state[light] = true; // set the light state to on } Serial.println("light[" + (String)light + "] = " + (String)light_state[light]); } uint8_t tmp = EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS); if (tmp == TIMING_CONTROL_DISABLED) { tc_enabled = TIMING_CONTROL_DISABLED; } else if (tmp == TIMING_CONTROL_ENABLED) { tc_enabled = TIMING_CONTROL_ENABLED; } else { EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_DISABLED); EEPROM.commit(); tc_enabled = TIMING_CONTROL_DISABLED; } Serial.println("tc_enabled = " + (String)tc_enabled); if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) == 255) { // set the default value on uninitialized EEPROM EEPROM.write(EEPROM_LAST_STATE_STARTUP_ADDRESS, 0); EEPROM.commit(); } #ifdef USE_STATIC_IP if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) == 255) { EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, 0); EEPROM.commit(); } #else if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) == 255) { EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, 1); EEPROM.commit(); } #endif } //********************************// void setup() { EEPROM.begin(256); Serial.begin(SERIAL_BAUD_RATE); if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) == 0) { WiFi.config(strip_ip, gateway_ip, subnet_mask, dns); } read_eeprom_config(); for (int j = 0; j < 200; j++) { lightEngine(); } WiFi.mode(WIFI_STA); WiFiManager wifiManager; wifiManager.setConfigPortalTimeout(120); wifiManager.autoConnect(light_name); IPAddress myIP = WiFi.localIP(); Serial.print("IP: "); Serial.println(myIP); if (!light_state[0]) { // Show that we are connected analogWrite(pins[0], 100); delay(500); analogWrite(pins[0], 0); } WiFi.macAddress(mac); pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH httpUpdateServer.setup(&server); // start http server init_webserver(); tc_init(); server.begin(); } // end of setup //********************************// void loop() { server.handleClient(); lightEngine(); if (tc_enabled == TIMING_CONTROL_ENABLED) { //Serial.println("tc_enabled = " + (String)tc_enabled); tc_update_loop(); } } //********************************// void handleNotFound() { String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i = 0; i < server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } //********************************// void init_webserver() { #ifndef DISABLE_WEB_CONTROL server.on("/state", HTTP_PUT, []() { // HTTP PUT request used to set a new light state DynamicJsonDocument root(1024); DeserializationError error = deserializeJson(root, server.arg("plain")); if (error) { server.send(404, "text/plain", "FAIL. " + server.arg("plain")); } else { for (JsonPair state : root.as()) { const char* key = state.key().c_str(); int light = atoi(key) - 1; JsonObject values = state.value(); uint8_t tmp = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS); if (values.containsKey("on")) { if (values["on"]) { light_state[light] = true; if (tmp == LAST_STATE_STARTUP_LIGHT_LAST_STATE && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_OFF) { EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_ON); } } else { light_state[light] = false; if (tmp == LAST_STATE_STARTUP_LIGHT_LAST_STATE && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_ON) { EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_OFF); } } } if (values.containsKey("bri")) { bri[light] = values["bri"]; } if (values.containsKey("bri_inc")) { bri[light] += (int)values["bri_inc"]; if (bri[light] > 255) bri[light] = 255; else if (bri[light] < 1) bri[light] = 1; } if (values.containsKey("transitiontime")) { default_transitiontime = values["transitiontime"]; if (tc_enabled == TIMING_CONTROL_DISABLED) { for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++) { // set the default transition time for all lights process_lightdata(i, default_transitiontime); } } } } String output; serializeJson(root, output); server.send(200, "text/plain", output); } }); server.on("/state", HTTP_GET, []() { // HTTP GET request used to fetch current light state uint8_t light = server.arg("light").toInt() - 1; DynamicJsonDocument root(1024); root["on"] = light_state[light]; root["bri"] = bri[light]; root["curbri"] = (int)current_bri[light]; root["curpwm"] = current_pwm[light]; String output; serializeJson(root, output); server.send(200, "text/plain", output); }); server.on("/detect", []() { // HTTP GET request used to discover the light type char macString[32] = { 0 }; sprintf(macString, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); DynamicJsonDocument root(1024); root["name"] = light_name; root["lights"] = LIGHTS_COUNT; root["protocol"] = "native_multi"; root["modelid"] = "LWB010"; root["type"] = "dimmable_light"; root["mac"] = String(macString); root["version"] = LIGHT_VERSION; String output; serializeJson(root, output); server.send(200, "text/plain", output); }); server.on("/tc_data_blocks", []() { String output = tc_getJsonData(); server.send(200, "application/json", output); }); #endif // DISABLE_WEB_CONTROL server.on("/", []() { #ifndef DISABLE_WEB_CONTROL if (server.hasArg("transition")) { default_transitiontime = server.arg("transition").toFloat(); if (tc_enabled == TIMING_CONTROL_DISABLED) { for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++) { // set the default transition time for all lights process_lightdata(i, default_transitiontime); } } } // startup behavior switch handling if (server.hasArg("startup")) { int startup = server.arg("startup").toInt(); if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) != startup) { EEPROM.write(EEPROM_LAST_STATE_STARTUP_ADDRESS, startup); for (uint8_t i = 0; i < LIGHTS_COUNT; i++) { uint8_t tmp = (light_state[i] == true ? LIGHT_STATE_ON : LIGHT_STATE_OFF); if (EEPROM.read(EEPROM_LAST_STATE_ADDRESS + i) != tmp) { EEPROM.write(EEPROM_LAST_STATE_ADDRESS + i, tmp); } } EEPROM.commit(); Serial.print("Startup behavior set to "); Serial.println(EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS)); } } #endif // DISABLE_WEB_CONTROL // timing controller switch handling if (server.hasArg("tc")) { if (server.arg("tc") == "true") { if (tc_enabled == TIMING_CONTROL_DISABLED) { if (EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS) != TIMING_CONTROL_ENABLED) { tc_enabled = TIMING_CONTROL_ENABLED; EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_ENABLED); EEPROM.commit(); Serial.print("Timing control = "); Serial.println(EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS)); tc_update_main(); // call the main update function to read data and set the light states } } } else { // tc is set to false or something else if (tc_enabled == TIMING_CONTROL_ENABLED) { tc_enabled = TIMING_CONTROL_DISABLED; if (EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS) != TIMING_CONTROL_DISABLED) { EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_DISABLED); EEPROM.commit(); Serial.print("Timing control = "); Serial.println(EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS)); for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++) { // set the default transition time for all lights process_lightdata(i, default_transitiontime); } } } } } #ifndef DISABLE_WEB_CONTROL // scene switch handling if (server.hasArg("scene")) { scene = server.arg("scene").toInt(); if (EEPROM.read(EEPROM_SCENE_ADDRESS) != scene) { EEPROM.write(EEPROM_SCENE_ADDRESS, scene); EEPROM.commit(); Serial.print("Scene set to "); Serial.println(EEPROM.read(EEPROM_SCENE_ADDRESS)); } } if (server.hasArg("dip")) { uint8_t tmp = EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS); uint8_t tmp2 = (server.arg("dip") == "true" ? 1 : 0); if (tmp != tmp2) { EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, tmp2); EEPROM.commit(); Serial.print("Set dynamic IP to "); Serial.println(EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS)); } } // process the received data for every light for (int light = 0; light < LIGHTS_COUNT; light++) { if (server.hasArg("bri" + (String)light)) { bri[light] = (int)server.arg("bri" + (String)light).toInt(); Serial.print("Brightness "); Serial.print(light); Serial.print(" set to "); Serial.println(bri[light]); } if (server.hasArg("on" + (String)light)) { uint8_t tmp = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS); if (server.arg("on" + (String)light) == "true" && light_state[light] == false) { light_state[light] = true; if (tmp == 0 && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == 0) { EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_ON); } Serial.print("Light "); Serial.print(light); Serial.print(" state set to "); Serial.println(light_state[light]); } else if (server.arg("on" + (String)light) == "false" && light_state[light] == true) { light_state[light] = false; if (tmp == 0 && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == 1) { EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_OFF); } Serial.print("Light "); Serial.print(light); Serial.print(" state set to "); Serial.println(light_state[light]); } EEPROM.commit(); } else { // light is off if (tc_enabled == TIMING_CONTROL_DISABLED) { process_lightdata(light, default_transitiontime); } } // start alerting for every light if (server.hasArg("alert")) { if (light_state[light]) { current_bri[light] = 0; } else { current_bri[light] = 255; } } } // process all lights #endif // DISABLE_WEB_CONTROL if (server.hasArg("resettc")) { // reqrite the tc config and reboot tc_write_default(); ESP.reset(); } if (server.hasArg("reset")) { ESP.reset(); } // Generate HTML page String http_content = ""; http_content += ""; http_content += ""; http_content += ""; http_content += ""; http_content += ""; //http_content += ""; // Reload the page every 15 seconds automatically http_content += "Light Setup"; http_content += ""; http_content += ""; http_content += ""; http_content += ""; http_content += ""; http_content += ""; http_content += "
"; http_content += "

" + (String)light_name + "

"; http_content += "
"; http_content += "alert   reset   reset timing control data   update"; http_content += ""; http_content += "
"; http_content += "
"; http_content += ""; #ifndef DISABLE_WEB_CONTROL http_content += "
"; // Light control for (uint8 light_num = 0; light_num < LIGHTS_COUNT; light_num++) { // on/off buttons http_content += "

Light " + (String)(light_num + 1) + "

"; http_content += "
"; http_content += ""; http_content += "ON"; http_content += "OFF"; http_content += "
"; // slider for brightness http_content += "
"; http_content += ""; http_content += ""; http_content += " " + (String)(int)(bri[light_num] * 100.0 / 255.0) + "%"; http_content += "
"; http_content += ""; http_content += " %"; http_content += ""; http_content += "
"; } // startup state and scene for all of the lights http_content += "
"; http_content += "
"; http_content += "

"; http_content += "

Config

"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; // scene http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; // timing control button http_content += "
"; http_content += ""; int tc_val = EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS); http_content += "ON"; http_content += "OFF"; http_content += "
"; http_content += "
"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; // Wifi settings http_content += "
"; http_content += "

Wifi

"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; // Network settings uint8_t dip = EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS); http_content += "
"; http_content += "

Network

"; http_content += "
"; http_content += ""; http_content += "ON"; http_content += "OFF"; http_content += "
"; // ip config if (dip == 0) { http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; http_content += "
"; http_content += ""; http_content += ""; http_content += "
"; } // The save button http_content += "
"; http_content += ""; http_content += "
"; http_content += ""; #endif // DISABLE_WEB_CONTROL http_content += "
"; http_content += ""; http_content += ""; http_content += ""; server.send(200, "text/html", http_content); }); server.on("/reset", []() { // trigger manual reset server.send(200, "text/html", "reset"); delay(1000); ESP.restart(); }); server.onNotFound(handleNotFound); }