/*Power Analyzer *22.08.2018 */ //Libraries #include #include #include #include #include #include #include #include #include #include //Library Config LiquidCrystal_I2C lcd(0x27,20,4); INA219 ina219; AsyncWebServer server(80); DNSServer dns; FtpServer ftpSrv; Scheduler task; Preferences nvs; //Konstanten #define R_SHUNT 0.001 #define V_SHUNT_MAX 0.060 #define V_BUS_MAX 26 #define I_MAX_EXPECTED 60 #define i2c_address 1 #define led_error_pin 25 #define led_run_pin 26 #define button1_pin 14 #define button2_pin 17 #define button3_pin 16 #define adc_battery_pin A0 //Funktionsdeklarationen void lcd_print(); void lcd_init(); void lcd_header(); void eeprom_save(); void i2c_receive(); void i2c_respond(); void readCurrent(); void read_bat(); void read_button(); void web_measuring_stop(AsyncWebServerRequest *request); void web_measuring_run(AsyncWebServerRequest *request); void web_measuring_reset(AsyncWebServerRequest *request); void web_get_values(AsyncWebServerRequest *request); void data_logging(); //Task Scheduler Task t1(500, TASK_FOREVER, &lcd_print); Task t2(1750, TASK_FOREVER, &lcd_header); Task t3(10000, TASK_FOREVER, &read_bat); Task t4(300, TASK_FOREVER, &read_button); Task t5(30000, TASK_FOREVER, &data_logging); //Globale Variablen float shuntvoltage_V = 0, shuntvoltage_mV = 0, busvoltage_V = 0, busvoltage_mV = 0, current_A = 0, current_mA = 0, power_W = 0, power_mW = 0; float busvoltage_V_max = 0, current_A_max = 0, power_W_max = 0, busvoltage_V_min = 0, current_A_min = 0, power_W_min = 0; float Ah = 0, mAh = 0, Wh = 0, mWh = 0; float battery_voltage, battery_average; int i_header = 0; bool button1, button2, button3, button3long; bool lcd_light = true, lcd_minmax = false, wifi_enabled = true, test_mode = false; bool measuring_run = false, measuring_init = false, reset_actual = false, reset_minmax = false, battery_low = false; unsigned long lastread = 0, tick, previousMillisReadData = 0, previousMillisDisplay = 0, data_timestamp = 0, button3timer = 0; //Custom LCD Characters byte grad[8] = { 0b01100, 0b10010, 0b10010, 0b01100, 0b00000, 0b00000, 0b00000, 0b00000 }; byte sollwert[8] = { 0b00000, 0b00000, 0b01111, 0b00011, 0b00101, 0b01001, 0b10000, 0b00000 }; byte batterie[8] = { 0b01110, 0b11011, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11111 }; void setup() { //Setup GPIO pinMode(led_error_pin, OUTPUT); pinMode(led_run_pin, OUTPUT); pinMode(button1_pin, INPUT_PULLUP); pinMode(button2_pin, INPUT_PULLUP); pinMode(button3_pin, INPUT_PULLUP); battery_average = analogRead(A0); //Setup Debug Serial Serial.begin(115200); Serial.println("Power Analyzer by Carsten Schmiemann (C) 2018"); //Setup Over The Air - Update ArduinoOTA .onStart([]() { //Disable Tasks t1.disable(); t2.disable(); t3.disable(); t4.disable(); t5.disable(); SPIFFS.end(); String type; lcd.clear(); lcd.setCursor(0,0); lcd.print("OTA Firmware Update"); lcd.setCursor(7,1); lcd.print("Init"); }) .onEnd([]() { lcd.setCursor(7,2); lcd.print("Done."); delay(5000); ESP.restart(); }) .onProgress([](unsigned int progress, unsigned int total) { lcd.setCursor(3,1); lcd.print(progress / (total / 100)); lcd.print("% Complete"); }) .onError([](ota_error_t error) { lcd.setCursor(3,3); digitalWrite(led_error_pin, HIGH); if (error == OTA_AUTH_ERROR) lcd.print("Auth Failed"); else if (error == OTA_BEGIN_ERROR) lcd.print("Begin Failed"); else if (error == OTA_CONNECT_ERROR) lcd.print("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) lcd.print("Receive Failed"); else if (error == OTA_END_ERROR) lcd.print("End Failed"); }); ArduinoOTA.setPort(1337); ArduinoOTA.setPassword("powerlyzer"); ArduinoOTA.begin(); //Init LCD, Custom Chars and test LEDs lcd.init(); lcd.backlight(); lcd.createChar(0, grad); lcd.createChar(1, sollwert); lcd.createChar(2, batterie); digitalWrite(led_error_pin, HIGH); digitalWrite(led_run_pin, HIGH); delay(1000); //Bootscreen lcd.clear(); lcd.setCursor(4,0); lcd.print("Power Analyzer"); lcd.setCursor(6,2); lcd.print("V0.31 Beta"); lcd.setCursor(7,3); lcd.print("CS,2018"); lcd.write(1); //End Test LEDs delay(5000); digitalWrite(led_error_pin, LOW); digitalWrite(led_run_pin, LOW); lcd.clear(); //Init Wi-Fi lcd.setCursor(0,0); if (digitalRead(button3_pin)) { lcd.print("Setup Wi-Fi..."); delay(300); AsyncWiFiManager wifiManager(&server,&dns); wifiManager.autoConnect("PowerAnalyzer WiFi"); lcd.setCursor(16,0); lcd.print("Done"); } else { lcd.print("Wi-Fi disabled"); wifi_enabled = false; } //Setup Over The Air - Update ArduinoOTA .onStart([]() { //Disable Tasks t1.disable(); t2.disable(); t3.disable(); t4.disable(); t5.disable(); SPIFFS.end(); String type; lcd.clear(); lcd.setCursor(0,0); lcd.print("OTA Firmware Update"); lcd.setCursor(7,1); lcd.print("Init"); }) .onEnd([]() { lcd.setCursor(7,2); lcd.print("Done."); delay(5000); ESP.restart(); }) .onProgress([](unsigned int progress, unsigned int total) { lcd.setCursor(3,1); lcd.print(progress / (total / 100)); lcd.print("% Complete"); }) .onError([](ota_error_t error) { lcd.setCursor(3,3); digitalWrite(led_error_pin, HIGH); if (error == OTA_AUTH_ERROR) lcd.print("Auth Failed"); else if (error == OTA_BEGIN_ERROR) lcd.print("Begin Failed"); else if (error == OTA_CONNECT_ERROR) lcd.print("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) lcd.print("Receive Failed"); else if (error == OTA_END_ERROR) lcd.print("End Failed"); }); if (wifi_enabled) { ArduinoOTA.setPort(1337); ArduinoOTA.setPassword("powerlyzer"); ArduinoOTA.begin(); } //Init ina219s lcd.setCursor(0,1); lcd.print("Init Sensors..."); if (digitalRead(button1_pin)) { ina219.begin(); ina219.configure(INA219::RANGE_16V, INA219::GAIN_2_80MV, INA219::ADC_16SAMP, INA219::ADC_16SAMP, INA219::CONT_SH_BUS); lastread = millis(); ina219.calibrate(R_SHUNT, V_SHUNT_MAX, V_BUS_MAX, I_MAX_EXPECTED); delay(300); lcd.setCursor(16,1); lcd.print("Done"); delay(200); } else { lcd.setCursor(16,1); lcd.print("Test"); test_mode = true; delay(1000); } //Init Task Scheduler lcd.setCursor(0,2); lcd.print("Init Threads..."); task.init(); task.addTask(t1); task.addTask(t2); task.addTask(t3); task.addTask(t4); task.addTask(t5); //Init Filesystem and Webserver SPIFFS.begin(); server.on("/meas/run", HTTP_GET, web_measuring_run); server.on("/meas/stop", HTTP_GET, web_measuring_stop); server.on("/meas/reset", HTTP_GET, web_measuring_reset); server.on("/meas/values", HTTP_GET, web_get_values); server.on("/datalog.csv", HTTP_ANY, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/datalog.csv"); }); server.serveStatic("/img", SPIFFS, "/img"); server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); server.begin(); //Init FTP Server ftpSrv.begin("power","analyzer"); delay(500); lcd.setCursor(16,2); lcd.print("Done"); delay(500); //Finished initilization lcd.setCursor(0,3); lcd.print("Done. Starting up..."); delay(1500); //Display IP address if (wifi_enabled) { lcd.clear(); lcd.setCursor(4,1); lcd.print("IP address"); lcd.setCursor(4,2); lcd.print(WiFi.localIP()); delay(1500); } //Load Default Screen lcd_init(); //Fetch stored values nvs.begin("usage", true); Ah = nvs.getFloat("Ah"); Wh = nvs.getFloat("Wh"); nvs.end(); //Enable Tasks t1.enable(); t2.enable(); t3.enable(); t4.enable(); t5.enable(); } void loop() { //Exec TaskScheduler task.execute(); //Service Handler ftpSrv.handleFTP(); ArduinoOTA.handle(); //High Precision Measuring unsigned long currentMillis = millis(); if ((unsigned long)(currentMillis - previousMillisReadData) >= 250) { previousMillisReadData = millis(); if (test_mode) { if (digitalRead(button1_pin)) { busvoltage_V += 1; power_W += 1; current_A += 1; Ah += 1; Wh += 1; } if (digitalRead(button2_pin)) { busvoltage_V -= 1; power_W -= 1; current_A -= 1; Ah -= 1; Wh -= 1; } } else { readCurrent(); } } } void lcd_print() { if (lcd_minmax) { lcd.setCursor(1,1); lcd.print(String(current_A_min) + String(" / ") + String(current_A_max) + String(" A")); lcd.setCursor(1,2); lcd.print(String(power_W_min) + String(" / ") + String(power_W_max) + String(" A")); lcd.setCursor(1,3); lcd.print(String(busvoltage_V_min) + String(" / ") + String(busvoltage_V_max) + String(" A")); } else { lcd.setCursor(0,1); if (current_A < 100 && current_A >= 10) {lcd.print(" ");} else if (current_A < 10 && current_A >= 0) {lcd.print(" ");}; if (current_A > -100 && current_A <= -10) {lcd.print(" ");} else if (current_A > -10 && current_A < 0) {lcd.print(" ");}; lcd.print(current_A); lcd.setCursor(0,2); if (power_W < 1000 && power_W > 100) {lcd.print(" ");} else if (power_W < 100 && power_W >= 10) {lcd.print(" ");} else if (power_W < 10) {lcd.print(" ");}; lcd.print(power_W); lcd.setCursor(0,3); if (busvoltage_V < 100 && busvoltage_V >= 10) {lcd.print(" ");} else if (busvoltage_V < 10) {lcd.print(" ");}; lcd.print(busvoltage_V); lcd.setCursor(11,1); if (Ah < 1000 && Ah > 100) {lcd.print(" ");} else if (Ah < 100 && Ah >= 10) {lcd.print(" ");} else if (Ah < 10) {lcd.print(" ");}; if (Ah > -100 && Ah <= -10) {lcd.print(" ");} else if (Ah > -10 && Ah < 0) {lcd.print(" ");}; lcd.print(Ah); lcd.setCursor(11,2); if (Wh < 1000 && Wh > 100) {lcd.print(" ");} else if (Wh < 100 && Wh >= 10) {lcd.print(" ");} else if (Wh < 10) {lcd.print(" ");}; lcd.print(Wh); lcd.setCursor(14,3); if (battery_voltage > 3.4) {lcd.print(battery_voltage);} else {lcd.print("LOW ");} } } void lcd_init() { lcd.clear(); lcd.setCursor(0,0); lcd.print("===--->Power<---==="); lcd.setCursor(7,1); lcd.print("A"); lcd.setCursor(7,2); lcd.print("W"); lcd.setCursor(7,3); lcd.print("V"); lcd.setCursor(18,1); lcd.print("Ah"); lcd.setCursor(18,2); lcd.print("Wh"); lcd.setCursor(13,3); lcd.write((uint8_t)2); lcd.setCursor(18,3); lcd.print("V"); delay(300); } void lcd_header() { if (measuring_run) { i_header++; if (i_header == 1) { lcd.setCursor(0,0); lcd.print("-===-->Power<--===-"); } if (i_header == 2) { lcd.setCursor(0,0); lcd.print("--===->Power<-===--"); } if (i_header == 3) { lcd.setCursor(0,0); lcd.print("---===>Power<===---"); } if (i_header == 4) { lcd.setCursor(0,0); lcd.print("=---==>Power<==---="); } if (i_header == 5) { lcd.setCursor(0,0); lcd.print("==---=>Power<=---=="); } if (i_header == 6) { lcd.setCursor(0,0); lcd.print("===--->Power<---==="); i_header = 0; } } //Flash Battery Low LED if (battery_low && digitalRead(led_error_pin)) { digitalWrite(led_error_pin, LOW); } else if (battery_low && digitalRead(led_error_pin) == false) { digitalWrite(led_error_pin, HIGH); } } void readCurrent() { uint32_t count = 0; unsigned long newtime; busvoltage_V = ina219.busVoltage(); while(!ina219.ready() && count < 500) { count++; delay(1); busvoltage_V = ina219.busVoltage(); busvoltage_mV = ina219.busVoltage() * 1000; } shuntvoltage_V = ina219.shuntVoltage() * 1000; shuntvoltage_mV = ina219.shuntVoltage(); current_A = ina219.shuntCurrent(); current_mA = current_A * 1000; power_W = ina219.busPower(); power_mW = power_W * 1000; newtime = millis(); tick = newtime - lastread; if (measuring_run) { Ah += (current_A * tick)/3600000.0; mAh = Ah * 1000; Wh += (power_W * tick)/3600000.0; mWh = Wh * 1000; if (busvoltage_V_max < busvoltage_V) { busvoltage_V_max = busvoltage_V; } if (current_A_max < current_A) { current_A_max = current_A; } if (power_W_max < power_W) { power_W_max = power_W; } if (busvoltage_V_min > busvoltage_V) { busvoltage_V_min = busvoltage_V; } if (current_A_min > current_A) { current_A_min = current_A; } if (power_W_min > power_W) { power_W_min = power_W; } } if (reset_actual) { Ah = 0; Wh = 0; data_timestamp = 0; reset_actual = false; busvoltage_V_max = 0; current_A_max = 0; power_W_max = 0; busvoltage_V_min = 0; current_A_min = 0; power_W_min = 0; } if (measuring_run && measuring_init == false) { busvoltage_V_max = 0; current_A_max = 0; power_W_max = 0; busvoltage_V_min = busvoltage_V; current_A_min = current_A; power_W_min = power_W; measuring_init = true; } if (measuring_run == false && measuring_init) { nvs.begin("usage", false); nvs.putFloat("Ah", Ah); nvs.putFloat("Wh", Wh); nvs.end(); measuring_init = false; } lastread = newtime; ina219.recalibrate(); ina219.reconfig(); } void read_bat() { battery_average += (analogRead(adc_battery_pin) - battery_average) * 0.3; battery_voltage = map(battery_average,0,3480,0,440)/100.0; if (battery_voltage < 3.3 && battery_low == false) { battery_low = true; lcd.noBacklight(); } if (battery_voltage > 3.6 && battery_low) { battery_low = false; digitalWrite(led_error_pin,LOW); lcd.backlight(); } } void read_button() { if (digitalRead(button1_pin)) { button1 = false; } else { button1 = true; } if (digitalRead(button2_pin)) { button2 = false; } else { button2 = true; } if (digitalRead(button3_pin)) { button3 = false; button3long = false; } else { button3 = true; button3timer = millis(); if (millis() - button3timer > 900) { button3long = true; } } if (button1 && test_mode == false) { reset_actual = true; } if (button2 && measuring_run && test_mode == false) { measuring_run = false; } else if (button2 && measuring_run == false) { measuring_run = true; } if (measuring_run) { digitalWrite(led_run_pin,true); } else { digitalWrite(led_run_pin,false); } if (button3 && button3long && lcd_light) { lcd_light = false; lcd.noBacklight(); } else if (button3 && button3long && lcd_light == false) { lcd_light = true; lcd.backlight(); } if (button3 && lcd_minmax == false) { lcd_minmax = true; } else if (button3 && lcd_minmax) { lcd_minmax = false; } } void web_measuring_stop(AsyncWebServerRequest *request) { measuring_run = false; request->send(200); } void web_measuring_run(AsyncWebServerRequest *request) { measuring_run = true; request->send(200); } void web_measuring_reset(AsyncWebServerRequest *request) { reset_actual = true; request->send(200); } void web_get_values(AsyncWebServerRequest *request) { String web_status; if (measuring_run) { web_status = "Running"; } else { web_status = "Stopped"; } request->send( 200, "application/json", "{\"voltage\":" + String(busvoltage_V) + ", \"current\":" + String(current_A) + ", \"power\":" + String(power_W) + ", \"Ah\":" + String(Ah) + ", \"Wh\":" + String(Wh) + ", \"battery\": " + String(battery_voltage) + ", \"run\":\"" + String(web_status) + "\", \"busvoltage_V_max\":" + String(busvoltage_V_max) + ", \"busvoltage_V_min\":" + String(busvoltage_V_min) + ", \"current_A_max\":" + String(current_A_max) + ", \"current_A_min\":" + String(current_A_min) + ", \"power_W_max\":" + String(power_W_max) + ", \"power_W_min\":" + String(power_W_min) + "}"); } void data_logging() { if (measuring_run && test_mode == false) { File datalog = SPIFFS.open("/datalog.csv", "a"); datalog.print(data_timestamp); datalog.print(','); datalog.print(busvoltage_V); datalog.print(','); datalog.print(current_A); datalog.print(','); datalog.print(power_W); datalog.print(','); datalog.print(Ah); datalog.print(','); datalog.println(Wh); datalog.close(); data_timestamp++; } }