PowerAnalyzer/src/PowerAnalyzer.ino

651 lines
16 KiB
C++

/*Power Analyzer
*22.08.2018
*/
//Libraries
#include <Wire.h>
#include <TaskScheduler.h>
#include <LiquidCrystal_I2C.h>
#include <INA219.h>
#include <EEPROM.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h>
#include <SPIFFS.h>
#include <ESP8266FtpServer.h>
#include <ArduinoOTA.h>
//Library Config
LiquidCrystal_I2C lcd(0x27,20,4);
INA219 ina219;
AsyncWebServer server(80);
DNSServer dns;
FtpServer ftpSrv;
Scheduler task;
//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_lowbat_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);
//Task t3(60000, TASK_FOREVER, &eeprom_save);
//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 shuntvoltage_V_max = 0, current_A_max = 0, power_W_max = 0, shuntvoltage_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;
bool lcd_light = true, 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;
unsigned long tick;
unsigned long previousMillisReadData = 0;
unsigned long previousMillisDisplay = 0;
unsigned long data_timestamp = 0;
byte i2c_MasterCommand = 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_lowbat_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);
lcd.print("Done.");
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 and Custom Chars
lcd.init();
lcd.backlight();
lcd.createChar(0, grad);
lcd.createChar(1, sollwert);
lcd.createChar(2, batterie);
digitalWrite(led_lowbat_pin, 255);
digitalWrite(led_run_pin, 255);
delay(1000);
//Bootscreen
lcd.clear();
lcd.setCursor(4,0);
lcd.print("Power Analyzer");
lcd.setCursor(7,1);
//lcd.print("Tracker");
lcd.setCursor(6,2);
lcd.print("V0.29 Beta");
lcd.setCursor(7,3);
lcd.print("CS,2018");
lcd.write(1);
//End Test Output PWM
digitalWrite(led_lowbat_pin, 0);
digitalWrite(led_run_pin, 0);
delay(5000);
lcd.clear();
/*
//Load Setpoint from EEPROM
lcd.setCursor(0,0);
lcd.print("Loading Setup from");
lcd.setCursor(0,1);
lcd.print("EEPROM...");
EEPROM.get(0, ee_setpoint);
setpoint = ee_setpoint;
delay(800);
lcd.setCursor(16,1);
lcd.print("Done");
delay(200);
*/
//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);
lcd.print("Done.");
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(500);
}
//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();
ftpSrv.begin("power","analyzer");
delay(500);
lcd.setCursor(16,2);
lcd.print("Done");
delay(500);
lcd.setCursor(0,3);
lcd.print("Done. Starting up...");
delay(1500);
//Display IP address
if (wifi_enabled) {
lcd.clear();
lcd.setCursor(6,1);
lcd.print("IP address");
lcd.setCursor(6,2);
lcd.print(WiFi.localIP());
delay(1500);
}
//Load Default Screen
lcd_init();
//Enable Tasks
t1.enable();
t2.enable();
t3.enable();
t4.enable();
t5.enable();
}
void loop() {
//Exec TaskScheduler
task.execute();
ftpSrv.handleFTP();
ArduinoOTA.handle();
unsigned long currentMillis = millis();
if ((unsigned long)(currentMillis - previousMillisReadData) >= 250) {
previousMillisReadData = millis();
if (test_mode) {
if (digitalRead(button1_pin)) {
shuntvoltage_V += 1;
power_W += 1;
current_A += 1;
Ah += 1;
Wh += 1;
}
if (digitalRead(button2_pin)) {
shuntvoltage_V -= 1;
power_W -= 1;
current_A -= 1;
Ah -= 1;
Wh -= 1;
}
} else {
readCurrent();
}
}
}
void lcd_print() {
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(" ");};
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_lowbat_pin)) {
digitalWrite(led_lowbat_pin,LOW);
} else if (battery_low && digitalRead(led_lowbat_pin) == false) { digitalWrite(led_lowbat_pin,HIGH); }
}
/*void eeprom_save() {
if (ee_setpoint != setpoint) {
EEPROM.put(0, setpoint);
ee_setpoint = setpoint;
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(2,2);
lcd.print("EEProm written.");
}
}*/
void i2c_receive(int howMany){
i2c_MasterCommand = Wire.read();
}
void i2c_respond(){
int returnValue = 0;
switch(i2c_MasterCommand){
case 0: // No new command was received
Wire.write("Invalid Request");
break;
case 10:
returnValue = busvoltage_V;
break;
case 20:
returnValue = current_A;
break;
case 30:
returnValue = power_W;
}
byte buffer[2];
buffer[0] = returnValue >> 8;
buffer[1] = returnValue & 255;
Wire.write(buffer, 2);
i2c_MasterCommand = 0;
}
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 (shuntvoltage_V_max < shuntvoltage_V) {
shuntvoltage_V_max = shuntvoltage_V;
}
if (current_A_max < current_A) {
current_A_max = current_A;
}
if (power_W_max < power_W) {
power_W_max = power_W;
}
if (shuntvoltage_V_min > shuntvoltage_V) {
shuntvoltage_V_min = shuntvoltage_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;
shuntvoltage_V_max = 0;
current_A_max = 0;
power_W_max = 0;
shuntvoltage_V_min = 0;
current_A_min = 0;
power_W_min = 0;
}
if (measuring_run && measuring_init == false) {
shuntvoltage_V_max = 0;
current_A_max = 0;
power_W_max = 0;
shuntvoltage_V_min = shuntvoltage_V;
current_A_min = current_A;
power_W_min = power_W;
measuring_init = true;
}
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,4096,0,450)/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_lowbat_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;
}
else {
button3 = 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 && lcd_light) {
lcd_light = false;
lcd.noBacklight();
} else if (button3 && lcd_light == false) {
lcd_light = true;
lcd.backlight();
}
}
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) + "\", \"shuntvoltage_V_max\":" + String(shuntvoltage_V_max) + ", \"shuntvoltage_V_min\":" + String(shuntvoltage_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++;
}
}