first commit
This commit is contained in:
commit
46b505af15
|
|
@ -0,0 +1,120 @@
|
|||
//LiFePO4 4S battery soc and level calculation library
|
||||
|
||||
//todo:
|
||||
//- battery level will forced after 60-seconds if not passing battPercentageLevelThreshold (optional)
|
||||
|
||||
static uint8_t battPercentageLevelThreshold = 5; //threshold for switch discrete level 0-4
|
||||
static uint8_t battLastLevel = 0;
|
||||
static uint8_t battLastPercentageForLevel = 0;
|
||||
|
||||
//variables
|
||||
static float battVoltageFullChargeMinimal = 14.4; //used for longevity charge
|
||||
static float battVoltageFullChargeMaximal = 14.6; //3.60V - 3.65V * 4S = 14.4V - 14.6V (can be used for "topped-off" charge mode, )
|
||||
static float battVoltageFullRest = 13.6; //~3.40V - 3.50V * 4S = ~13.6V - 13.7V (can be used for "optimal" charge mode, preferred for longevity)
|
||||
static float battVoltageUpperZone = 13.3; //Also can be used for "Storage/Long-term Maintenance" charge mode, or after long usage without load ~13.2V - 13.3V (50-60% state of charge)
|
||||
static float battVoltageLowerZone = 12.9;
|
||||
//float battVoltageHalf = 13.1; //~3.25V - 3.28V * 4S = ~13.0V - 13.1V, known as nomimal
|
||||
static float battVoltageEmpty = 10.0; //~2.50V - 2.80V * 4S = ~10.0V - 11.2V
|
||||
static float battVoltageCritical = 12.0; //Default ~10% => 3.0V * 4S = 12V
|
||||
static float battVoltageForceShutdown = 11.2; //Default ~5% => 2.8V * 4S = 11.2V
|
||||
static float battVoltageResumeChargingDefault = 13.4; //Default ~90% = ~3.35V *4S = 13.4V
|
||||
|
||||
//error condition
|
||||
static float battOverVoltage = 15.3; //3.75V - 3.80V * 4S = 15V - 15.3V
|
||||
static float battVoltageDetected = 8;
|
||||
static float battVoltageNotDetected = 7;
|
||||
|
||||
static void resetBatteryLevel() {
|
||||
battLastLevel = 0;
|
||||
battLastPercentageForLevel = 0;
|
||||
}
|
||||
|
||||
static void setBatteryPercentage(uint8_t soc) {
|
||||
if (soc <= 0) {
|
||||
battLastLevel = 0;
|
||||
battLastPercentageForLevel = 0;
|
||||
return;
|
||||
} else if (soc >= 100) {
|
||||
battLastLevel = 4;
|
||||
battLastPercentageForLevel = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
if (soc > battLastPercentageForLevel) {
|
||||
//charging
|
||||
if (soc - battLastPercentageForLevel > battPercentageLevelThreshold) {
|
||||
battLastPercentageForLevel = soc;
|
||||
if (battLastPercentageForLevel > 75) {
|
||||
battLastLevel = 4;
|
||||
} else if (battLastPercentageForLevel > 50) {
|
||||
battLastLevel = 3;
|
||||
} else if (battLastPercentageForLevel > 25) {
|
||||
battLastLevel = 2;
|
||||
} else if (battLastPercentageForLevel > 10) {
|
||||
battLastLevel = 1;
|
||||
} else {
|
||||
battLastLevel = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//depleted or equal
|
||||
if (battLastPercentageForLevel - soc > battPercentageLevelThreshold) {
|
||||
battLastPercentageForLevel = soc;
|
||||
if (battLastPercentageForLevel < 10) {
|
||||
battLastLevel = 0;
|
||||
} else if (battLastPercentageForLevel < 25) {
|
||||
battLastLevel = 1;
|
||||
} else if (battLastPercentageForLevel < 50) {
|
||||
battLastLevel = 2;
|
||||
} else if (battLastPercentageForLevel < 75) {
|
||||
battLastLevel = 3;
|
||||
} else {
|
||||
battLastLevel = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t getBatteryPercentage(float voltage) {
|
||||
float soc = 0;
|
||||
if (voltage > battVoltageUpperZone) {
|
||||
soc = 90.0 + (10.0 * ((voltage - battVoltageUpperZone) / (battVoltageFullRest - battVoltageUpperZone)));
|
||||
if (soc > 100) soc = 100;
|
||||
} else if (voltage > battVoltageLowerZone && voltage <= battVoltageUpperZone) {
|
||||
soc = 20.0 + (70.0 * ((voltage - battVoltageLowerZone) / (battVoltageUpperZone - battVoltageLowerZone)));
|
||||
} else if (voltage > battVoltageEmpty) {
|
||||
soc = (20.0 * ((voltage - battVoltageEmpty) / (battVoltageLowerZone - battVoltageEmpty)));
|
||||
}
|
||||
setBatteryPercentage(soc);
|
||||
return (uint8_t) soc;
|
||||
}
|
||||
|
||||
static uint8_t getRawBatteryLevelFromPercentage(uint8_t soc) {
|
||||
if (soc <= 0) {
|
||||
return 0;
|
||||
} else if (soc >= 100) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (soc > 75) {
|
||||
return 4;
|
||||
} else if (soc > 50) {
|
||||
return 3;
|
||||
} else if (soc > 25) {
|
||||
return 2;
|
||||
} else if (soc > 5) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t getBatteryLevel() {
|
||||
return battLastLevel;
|
||||
}
|
||||
|
||||
static uint8_t getBatteryLevel(float voltage) {
|
||||
getBatteryPercentage(voltage);
|
||||
return battLastLevel;
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
static const unsigned char PROGMEM splash_logo_bitmap[512] = {
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xE0,0xFF,0xE7,0x01,0x80,0xEF,0x03,0xF0,0x39,0xE0,0xFF,0x07,0x00,0x00,
|
||||
0x00,0x00,0xF8,0xFF,0xEF,0x03,0x80,0xC7,0x07,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00,
|
||||
0x00,0x00,0xFC,0xFF,0xCF,0x03,0xC0,0x87,0x0F,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00,
|
||||
0x00,0x00,0xFC,0xFF,0xC7,0x07,0xC0,0x83,0x1F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00,
|
||||
0x00,0x00,0x3E,0x00,0x80,0x07,0xE0,0x03,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00,
|
||||
0x00,0x00,0x1E,0x00,0x80,0x0F,0xF0,0x01,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00,
|
||||
0x00,0x00,0x1E,0x00,0x00,0x0F,0xF0,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00,
|
||||
0x00,0x00,0xFE,0xFF,0x07,0x1F,0xF8,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00,
|
||||
0x00,0x00,0xFE,0xFF,0x07,0x1E,0x78,0x00,0xF0,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00,
|
||||
0x00,0x00,0xFE,0xFF,0x07,0x3E,0x7C,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00,
|
||||
0x00,0x00,0x1E,0x00,0x00,0x3C,0x3C,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00,
|
||||
0x00,0x00,0x1E,0x00,0x00,0x78,0x3E,0x00,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00,
|
||||
0x00,0x00,0x3E,0x00,0x00,0xF8,0x1E,0x00,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00,
|
||||
0x00,0x00,0xFC,0xFF,0x07,0xF0,0x1F,0x80,0x0F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00,
|
||||
0x00,0x00,0xFC,0xFF,0x07,0xF0,0x0F,0xC0,0x07,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00,
|
||||
0x00,0x00,0xF8,0xFF,0x0F,0xE0,0x07,0xE0,0x03,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00,
|
||||
0x00,0x00,0xF0,0xFF,0x07,0xC0,0x07,0xF0,0x01,0xF0,0x79,0xF0,0xFF,0x07,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xC0,0x9F,0x83,0x83,0xC1,0x3F,0xFF,0xC1,0x61,0xFE,0xFE,0x83,0x81,0xFC,0xF1,0x07,
|
||||
0xE0,0xDF,0xC3,0xC3,0xC3,0x7F,0xFF,0xE1,0x61,0xFF,0xFF,0x83,0xC1,0xFE,0xFB,0x07,
|
||||
0x60,0xC0,0xC7,0xC3,0xC7,0x60,0x18,0xE0,0x63,0x03,0x60,0x80,0xC1,0x0E,0x1B,0x00,
|
||||
0xE0,0xCF,0xC6,0x63,0xC6,0x60,0x18,0x60,0x67,0xFF,0x60,0x80,0xC1,0x0E,0xFB,0x03,
|
||||
0xE0,0xDF,0x6E,0x63,0xCC,0x7F,0x18,0x60,0x6E,0xFF,0x60,0x80,0xC1,0xFE,0xF3,0x0F,
|
||||
0x00,0xD8,0x6C,0xF3,0xCF,0x3F,0x18,0x60,0x7C,0x03,0x60,0x80,0xC1,0xFE,0x01,0x0C,
|
||||
0xE0,0xDF,0x3C,0xFB,0xDF,0x30,0x18,0x60,0x78,0xFF,0x60,0x80,0xFF,0x0E,0xF8,0x0F,
|
||||
0xE0,0xDF,0x38,0x1B,0xD8,0x60,0x18,0x60,0x70,0xFE,0x61,0x00,0xFF,0x0E,0xF8,0x07,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM splash_logo_bitmap_inverted[512] = {
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0x1F,0x00,0x18,0xFE,0x7F,0x10,0xFC,0x0F,0xC6,0x1F,0x00,0xF8,0xFF,0xFF,
|
||||
0xFF,0xFF,0x07,0x00,0x10,0xFC,0x7F,0x38,0xF8,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF,
|
||||
0xFF,0xFF,0x03,0x00,0x30,0xFC,0x3F,0x78,0xF0,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF,
|
||||
0xFF,0xFF,0x03,0x00,0x38,0xF8,0x3F,0x7C,0xE0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF,
|
||||
0xFF,0xFF,0xC1,0xFF,0x7F,0xF8,0x1F,0xFC,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE1,0xFF,0x7F,0xF0,0x0F,0xFE,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE1,0xFF,0xFF,0xF0,0x0F,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF,
|
||||
0xFF,0xFF,0x01,0x00,0xF8,0xE0,0x07,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF,
|
||||
0xFF,0xFF,0x01,0x00,0xF8,0xE1,0x87,0xFF,0x0F,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF,
|
||||
0xFF,0xFF,0x01,0x00,0xF8,0xC1,0x83,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE1,0xFF,0xFF,0xC3,0xC3,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF,
|
||||
0xFF,0xFF,0xE1,0xFF,0xFF,0x87,0xC1,0xFF,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF,
|
||||
0xFF,0xFF,0xC1,0xFF,0xFF,0x07,0xE1,0xFF,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF,
|
||||
0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xE0,0x7F,0xF0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF,
|
||||
0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xF0,0x3F,0xF8,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF,
|
||||
0xFF,0xFF,0x07,0x00,0xF0,0x1F,0xF8,0x1F,0xFC,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF,
|
||||
0xFF,0xFF,0x0F,0x00,0xF8,0x3F,0xF8,0x0F,0xFE,0x0F,0x86,0x0F,0x00,0xF8,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0x3F,0x60,0x7C,0x7C,0x3E,0xC0,0x00,0x3E,0x9E,0x01,0x01,0x7C,0x7E,0x03,0x0E,0xF8,
|
||||
0x1F,0x20,0x3C,0x3C,0x3C,0x80,0x00,0x1E,0x9E,0x00,0x00,0x7C,0x3E,0x01,0x04,0xF8,
|
||||
0x9F,0x3F,0x38,0x3C,0x38,0x9F,0xE7,0x1F,0x9C,0xFC,0x9F,0x7F,0x3E,0xF1,0xE4,0xFF,
|
||||
0x1F,0x30,0x39,0x9C,0x39,0x9F,0xE7,0x9F,0x98,0x00,0x9F,0x7F,0x3E,0xF1,0x04,0xFC,
|
||||
0x1F,0x20,0x91,0x9C,0x33,0x80,0xE7,0x9F,0x91,0x00,0x9F,0x7F,0x3E,0x01,0x0C,0xF0,
|
||||
0xFF,0x27,0x93,0x0C,0x30,0xC0,0xE7,0x9F,0x83,0xFC,0x9F,0x7F,0x3E,0x01,0xFE,0xF3,
|
||||
0x1F,0x20,0xC3,0x04,0x20,0xCF,0xE7,0x9F,0x87,0x00,0x9F,0x7F,0x00,0xF1,0x07,0xF0,
|
||||
0x1F,0x20,0xC7,0xE4,0x27,0x9F,0xE7,0x9F,0x8F,0x01,0x9E,0xFF,0x00,0xF1,0x07,0xF8,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM active_data_2x4_in_bitmap[4] = {
|
||||
0x02,
|
||||
0x02,
|
||||
0x03,
|
||||
0x02
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM active_data_2x4_out_bitmap[4] = {
|
||||
0x01,
|
||||
0x03,
|
||||
0x01,
|
||||
0x01
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM power_bitmap[32] = {
|
||||
0x80,0x01,
|
||||
0x80,0x01,
|
||||
0x90,0x09,
|
||||
0xB8,0x1D,
|
||||
0x9C,0x39,
|
||||
0x8E,0x71,
|
||||
0x86,0x61,
|
||||
0x86,0x61,
|
||||
0x86,0x61,
|
||||
0x06,0x60,
|
||||
0x06,0x60,
|
||||
0x0E,0x70,
|
||||
0x1C,0x38,
|
||||
0x78,0x1E,
|
||||
0xF0,0x0F,
|
||||
0xE0,0x07
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM wifi2_lv1_bitmap[32] = {
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x80,0x01,
|
||||
0xE0,0x07,
|
||||
0x70,0x0E,
|
||||
0x00,0x00,
|
||||
0x80,0x01,
|
||||
0x80,0x01,
|
||||
0x00,0x00,
|
||||
0x00,0x00
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM wifi2_lv2_bitmap[32] = {
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0xC0,0x03,
|
||||
0xF0,0x0F,
|
||||
0x3C,0x3C,
|
||||
0x8C,0x31,
|
||||
0xE0,0x07,
|
||||
0x70,0x0E,
|
||||
0x00,0x00,
|
||||
0x80,0x01,
|
||||
0x80,0x01,
|
||||
0x00,0x00,
|
||||
0x00,0x00
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM wifi2_lv3_bitmap[32] = {
|
||||
0x00,0x00,
|
||||
0x00,0x00,
|
||||
0xF0,0x0F,
|
||||
0xFC,0x3F,
|
||||
0x1E,0x78,
|
||||
0xC7,0xE3,
|
||||
0xF0,0x0F,
|
||||
0x3C,0x3C,
|
||||
0x8C,0x31,
|
||||
0xE0,0x07,
|
||||
0x70,0x0E,
|
||||
0x00,0x00,
|
||||
0x80,0x01,
|
||||
0x80,0x01,
|
||||
0x00,0x00,
|
||||
0x00,0x00
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM bolt_16x16_bitmap[32] = {
|
||||
0x00,0x00,
|
||||
0x00,0x01,
|
||||
0x00,0x01,
|
||||
0x80,0x01,
|
||||
0xC0,0x01,
|
||||
0xC0,0x01,
|
||||
0xE0,0x1F,
|
||||
0xE0,0x0F,
|
||||
0xF0,0x0F,
|
||||
0xF8,0x07,
|
||||
0x80,0x03,
|
||||
0x80,0x03,
|
||||
0x80,0x01,
|
||||
0x80,0x01,
|
||||
0x80,0x00,
|
||||
0x00,0x00
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM charging_7x8_bitmap[8] = {
|
||||
0x3E,
|
||||
0x41,
|
||||
0x5D,
|
||||
0x45,
|
||||
0x45,
|
||||
0x5D,
|
||||
0x41,
|
||||
0x3E
|
||||
};
|
||||
|
||||
|
||||
static const unsigned char PROGMEM charge_fast_7x8_bitmap[8] = {
|
||||
0x3E,
|
||||
0x7F,
|
||||
0x63,
|
||||
0x7B,
|
||||
0x7B,
|
||||
0x63,
|
||||
0x7F,
|
||||
0x3E
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM warn_16x16_bitmap[32] = {
|
||||
0x00,0x00,
|
||||
0x80,0x01,
|
||||
0xC0,0x03,
|
||||
0xC0,0x07,
|
||||
0xE0,0x07,
|
||||
0x70,0x0E,
|
||||
0x70,0x0E,
|
||||
0x78,0x1E,
|
||||
0x78,0x1E,
|
||||
0x7C,0x3E,
|
||||
0xFC,0x3F,
|
||||
0x7E,0x7E,
|
||||
0x7E,0x7E,
|
||||
0xFF,0xFF,
|
||||
0xFF,0xFF,
|
||||
0xFF,0xFF
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM error_16x16_bitmap[32] = {
|
||||
0xF0,0x0F,
|
||||
0xF8,0x1F,
|
||||
0xFC,0x3F,
|
||||
0x7E,0x7E,
|
||||
0x7F,0xFE,
|
||||
0x7F,0xFE,
|
||||
0x7F,0xFE,
|
||||
0x7F,0xFE,
|
||||
0x7F,0xFE,
|
||||
0x7F,0xFE,
|
||||
0xFF,0xFF,
|
||||
0x7F,0xFE,
|
||||
0x7E,0x7E,
|
||||
0xFC,0x3F,
|
||||
0xF8,0x1F,
|
||||
0xF0,0x0F
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM fatal_16x16_bitmap[32] = {
|
||||
0xF0,0x0F,
|
||||
0xF8,0x1F,
|
||||
0xFC,0x3F,
|
||||
0xFE,0x7F,
|
||||
0xDF,0xFB,
|
||||
0x8F,0xF1,
|
||||
0x1F,0xF8,
|
||||
0x3F,0xFC,
|
||||
0x3F,0xFC,
|
||||
0x1F,0xF8,
|
||||
0x8F,0xF1,
|
||||
0xDF,0xFB,
|
||||
0xFE,0x7F,
|
||||
0xFC,0x3F,
|
||||
0xF8,0x1F,
|
||||
0xF0,0x0F
|
||||
};
|
||||
|
||||
static const unsigned char PROGMEM timer_16x16_bitmap[32] = {
|
||||
0x80,0x01,
|
||||
0x80,0x19,
|
||||
0x00,0x38,
|
||||
0x08,0x70,
|
||||
0x10,0x60,
|
||||
0x73,0xC0,
|
||||
0xE3,0xC0,
|
||||
0xC3,0xC1,
|
||||
0x83,0xC1,
|
||||
0x03,0xC0,
|
||||
0x03,0xC0,
|
||||
0x06,0x60,
|
||||
0x06,0x60,
|
||||
0x1C,0x38,
|
||||
0xF8,0x1F,
|
||||
0xE0,0x07
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -0,0 +1,743 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UPS Dashboard</title>
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
margin-top: -20px;
|
||||
background-color: #121212; /* Dark background */
|
||||
color: #e0e0e0; /* Light text color */
|
||||
}
|
||||
hr {
|
||||
margin-bottom: 20px;
|
||||
border-color: #333; /* Darker border color */
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #ffffff; /* White text for headings */
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
button {
|
||||
background-color: #333; /* Dark button background */
|
||||
color: #e0e0e0; /* Light text color */
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #444; /* Slightly lighter on hover */
|
||||
}
|
||||
button.disabled {
|
||||
background-color: #0046a3; /* Gray color for disabled button */
|
||||
color: #a0a0a0;
|
||||
cursor: default;
|
||||
pointer-events: none; /* Prevents hover effect */
|
||||
transition: none; /* Removes transition effect on hover */
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
background-color: #333; /* Gray color for disabled button */
|
||||
color: #555;
|
||||
cursor: default;
|
||||
pointer-events: none; /* Prevents hover effect */
|
||||
transition: none; /* Removes transition effect on hover */
|
||||
}
|
||||
button[disabled]:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #e0e0e0; /* Light text color for labels */
|
||||
}
|
||||
|
||||
/* for styled on off using checkbox */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 24px;
|
||||
}
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .2s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .2s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
input:checked + .slider {
|
||||
background-color: #0078d7;
|
||||
}
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(22px);
|
||||
}
|
||||
/* Disabled state */
|
||||
input:disabled + .slider {
|
||||
background-color: #e6e6e6;
|
||||
cursor: default;
|
||||
pointer-events: none; /* Prevents hover effect */
|
||||
}
|
||||
input:disabled:checked + .slider {
|
||||
background-color: #a0d1f6;
|
||||
}
|
||||
input:disabled + .slider:before {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
/* Optional: Add focus style */
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #0078d7;
|
||||
}
|
||||
|
||||
/* Selec/dropdown style */
|
||||
select {
|
||||
/* width: calc(100% - 22px); */
|
||||
background-color: #333333;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
<label>Smart Network Controlled UPS V1.1</label>
|
||||
<!-- ⭕Hollow Red Circle⚫Black Circle⚪White Circle · 🟢Green Circle🔵Blue Circle🟡Yellow Circle🟠Orange Circle🟤Brown Circle 🔴Red Circle 🟣 Purple Circle -->
|
||||
<label id="connect-state-label" title="Unavailable"> ⚫</label>
|
||||
</h1>
|
||||
|
||||
<hr>
|
||||
<h1>Power Control:</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Auto ON</label>
|
||||
<!-- <button id="toggleAutoBtn"
|
||||
onclick="toggleAutoOn()"
|
||||
style="position: absolute;
|
||||
left: 140px;width:50px;">OFF</button> -->
|
||||
<select id="auto-mode" style="position: absolute; left: 150px;width: 150px;">
|
||||
<option value="0">Disabled</option>
|
||||
<option value="1">Line Powered</option>
|
||||
<option value="2">Persistent on Line</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- <div class="form-group">
|
||||
<label>State</label>
|
||||
<button id="switchBtn"
|
||||
onclick="toggleSwitch()"
|
||||
style="position: absolute;
|
||||
left: 140px;width:50px;">OFF</button>
|
||||
</div> -->
|
||||
|
||||
<div class="form-group">
|
||||
<label>Power</label>
|
||||
<label class="toggle-switch" style="position: absolute; left: 150px;">
|
||||
<input type="checkbox" id="switchToggle" disabled >
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Shutdown Cast</label>
|
||||
<select id="shutcast-mode" style="position: absolute; left: 150px;width: 150px;">
|
||||
<option value="0">Disabled</option>
|
||||
<option value="1">On Battery</option>
|
||||
<option value="2">≤75% Battery</option>
|
||||
<option value="3">≤50% Battery</option>
|
||||
<option value="4">≤25% Battery</option>
|
||||
<option value="5">Battery Critical</option>
|
||||
</select>
|
||||
<label id="shutdown-cast-label" style="position: absolute; left: 310px;width: 50px;"> </label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<div class="container">
|
||||
<label style="position: absolute; align-items: center;" title="RestAPI: htttp://<IP>/state?shutdown=<seconds>" >Shutdown</label>
|
||||
<label id="shutdown-countdown" style="position: absolute; left: 150px; width: 150px;">999</label>
|
||||
</div>
|
||||
|
||||
<div class="container" id="shutdown-form" style="position: absolute; left: 150px; align-items: center;">
|
||||
<input type="number" id="shut-timer-input" placeholder="0-999" value="120" min="10" max="999" step="1" style="position: absolute; left: 0px; width: 50px; align-items: center;" maxlength="3">
|
||||
<label style="position: absolute; left: 65px; width: 50px; align-items: center;">sec(s)</label>
|
||||
<button id="shutdownBtn"
|
||||
onclick="shutdownTriggered()"
|
||||
style="position: absolute;
|
||||
left: 120px;width:64px; align-items:center; padding: 0px 0px; height: 22px; margin: -1px -0px">START</button>
|
||||
</div>
|
||||
|
||||
<!-- <div class="container" id="shutdown-countdown" style="position: absolute; left: 150px; align-items: center;">
|
||||
<label style="position: absolute; left: 0px; width: 50px;"></label>
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h1>Charge Control:</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Charge Mode</label>
|
||||
<select id="charge-mode" style="position: absolute; left: 150px;width: 150px;">
|
||||
<option value="0">Always Charge</option>
|
||||
<option value="1">90-100%</option>
|
||||
<option value="2">85-100%</option>
|
||||
<option value="3">80-100%</option>
|
||||
<option value="4">90-95%</option>
|
||||
<option value="5">85-95%</option>
|
||||
<option value="6">80-95%</option>
|
||||
<option value="7">85-90%</option>
|
||||
<option value="8">80-90%</option>
|
||||
<option value="9">80-85%</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label title="Automatic Voltage Reducer">AVR</label>
|
||||
<label class="toggle-switch" style="position: absolute; left: 150px;">
|
||||
<input type="checkbox" id="switchToggleAutoReducer" disabled >
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label title="Overvoltage Protection">OVP</label>
|
||||
<label class="toggle-switch" style="position: absolute; left: 150px;">
|
||||
<input type="checkbox" id="switchToggleOVP" disabled >
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label title="Full Charge on Power Lost">FCoPL</label>
|
||||
<select id="fcopl-mode" style="position: absolute; left: 150px;width: 150px;">
|
||||
<option value="0">Disabled</option>
|
||||
<option value="1">Always</option>
|
||||
<option value="2">At Battery ≤95%</option>
|
||||
<option value="3">At Battery ≤90%</option>
|
||||
<option value="4">At Battery ≤85%</option>
|
||||
<option value="5">At Battery ≤80%</option>
|
||||
</select>
|
||||
<button id="resetFCoPLBtn"
|
||||
onclick="resetFCoPLTriggered()"
|
||||
style="position: absolute;
|
||||
left: 310px;width:64px; align-items:center; padding: 0px 0px; height: 22px; margin: -1px -0px">Reset</button>
|
||||
</div>
|
||||
|
||||
|
||||
<hr>
|
||||
<h1>Monitor:</h1>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<label id="status-label"
|
||||
style="position: absolute;
|
||||
left: 150px;width:400px;"
|
||||
>-</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Power Source</label>
|
||||
<label id="power-label"
|
||||
style="position: absolute;
|
||||
left: 150px;width:400px;"
|
||||
>-</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Battery Voltage</label>
|
||||
<label id="input-label"
|
||||
style="position: absolute;
|
||||
left: 150px;width:100px;"
|
||||
>-</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Battery Level</label>
|
||||
<label id="batt-label"
|
||||
style="position: absolute;
|
||||
left: 150px;width:400px;"
|
||||
>⬛⬛⬛⬛</label>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ⬛ 🟥 (red square), 🟦 (blue square), 🟩 (green square), 🟨 (yellow square), 🟧 (orange square), and 🟫 (brown square)
|
||||
// ◼️ Black Medium Square: A medium-sized black square.
|
||||
// ⬛ Black Large Square: A large black square.
|
||||
// ◾ Black Medium-Small Square: A smaller black square.
|
||||
// ▪️ Black Small Square: The smallest size of a black square.
|
||||
// 🔲 Black Square Button: A square version of the radio button emoji
|
||||
|
||||
var labelStatus = document.getElementById('status-label');
|
||||
|
||||
var labelPower = document.getElementById('power-label');
|
||||
var labelInput = document.getElementById('input-label');
|
||||
var labelBatt = document.getElementById('batt-label');
|
||||
|
||||
var dropdownAutoOnSelect = document.getElementById('auto-mode');
|
||||
var toggleSwitch = document.getElementById('switchToggle');
|
||||
|
||||
var shutTimerInput = document.getElementById('shut-timer-input');
|
||||
var shutdownForm = document.getElementById('shutdown-form');
|
||||
var shutTimer = document.getElementById('shutdown-countdown');
|
||||
|
||||
var dropdownShutLevelSelect = document.getElementById('shutcast-mode');
|
||||
var shutFlagLabel = document.getElementById('shutdown-cast-label');
|
||||
|
||||
var connectStateLabel = document.getElementById('connect-state-label');
|
||||
|
||||
var dropdownChargeModeSelect = document.getElementById('charge-mode');
|
||||
var dropdownFullChargeOnPowerLostModeSelect = document.getElementById('fcopl-mode');
|
||||
var btnResetFCOPL = document.getElementById('resetFCoPLBtn');
|
||||
var toggleAutomaticVoltageReducer = document.getElementById('switchToggleAutoReducer');
|
||||
var toggleOvervoltageProtection = document.getElementById('switchToggleOVP');
|
||||
|
||||
var lastConnectTick = 0;
|
||||
|
||||
var autoOnMode = 0;
|
||||
var stateTarget = false;
|
||||
var stateValue = false;
|
||||
var lineFrom = 0; //0 = None, 1 = AC, 2 = Battery
|
||||
|
||||
var onChange = false;
|
||||
var checkRefresh;
|
||||
|
||||
var battLevel = 0;
|
||||
var battMaxLevel = 0;
|
||||
var battPercentage = 0;
|
||||
var isChargingOrBlink = false;
|
||||
var chargeLevel = 0;
|
||||
var isBattCritical = false;
|
||||
var chargeTickTock = false;
|
||||
|
||||
var timedShutdown = false;
|
||||
var timedShutRemaining = 0;
|
||||
|
||||
var everReceiveStat = false;
|
||||
|
||||
var shutdownSuggestionLevel = 0;
|
||||
//var shutdownSuggestionLevelPrev = 0;
|
||||
var shutdownCastFlag = false;
|
||||
|
||||
var chargeStatus = 0;
|
||||
var chargingState = 0;
|
||||
var chargeMode = 0;
|
||||
var chargeAutoVoltageReducer = false;
|
||||
var chargeOvervoltageProtection = false;
|
||||
var fullChargeOnPowerLostMode = 0;
|
||||
var fullChargeOnPowerLostState = false;
|
||||
|
||||
var targetChargeMode = 0;
|
||||
var targetFCOPLMode = 0;
|
||||
var targetOvervoltageProtection = false;
|
||||
var targetAutomaticVoltageReducer = false;
|
||||
|
||||
function repeatChar(char, times) {
|
||||
return char.repeat(times);
|
||||
}
|
||||
|
||||
// Disable all elements in shutdown-form
|
||||
function disableShutdownForm() {
|
||||
const form = document.getElementById('shutdown-form');
|
||||
const elements = form.querySelectorAll('input, button, select, textarea');
|
||||
elements.forEach(element => {
|
||||
element.disabled = true;
|
||||
});
|
||||
//const btn = document.getElementById('shutdownBtn');
|
||||
//btn.disabled = true;
|
||||
}
|
||||
|
||||
// Enable all elements in shutdown-form
|
||||
function enableShutdownForm() {
|
||||
const form = document.getElementById('shutdown-form');
|
||||
const elements = form.querySelectorAll('input, button, select, textarea');
|
||||
elements.forEach(element => {
|
||||
element.disabled = false;
|
||||
});
|
||||
//const btn = document.getElementById('shutdownBtn');
|
||||
//btn.disabled = false;
|
||||
}
|
||||
|
||||
dropdownShutLevelSelect.addEventListener('change', function() {
|
||||
let targetShutdownLvMode = parseInt(this.value);
|
||||
let s = 'shutdown suggest cast: ' + targetShutdownLvMode;
|
||||
console.log(s);
|
||||
if (targetShutdownLvMode!==shutdownSuggestionLevel){
|
||||
this.disabled = true;
|
||||
SendState('shutlv', targetShutdownLvMode);
|
||||
}
|
||||
});
|
||||
|
||||
dropdownAutoOnSelect.addEventListener('change', function() {
|
||||
let targetAutoMode = parseInt(this.value);
|
||||
let s = 'auto on mode: ' + targetAutoMode;
|
||||
console.log(s);
|
||||
if (targetAutoMode!==autoOnMode){
|
||||
this.disabled = true;
|
||||
SendState('auto', targetAutoMode);
|
||||
}
|
||||
});
|
||||
|
||||
toggleSwitch.addEventListener('change', function() {
|
||||
let targetState = toggleSwitch.checked;
|
||||
let s = 'Toggle switched: ' + (targetState?'ON':'OFF');
|
||||
console.log(s);
|
||||
if (targetState!==stateValue){
|
||||
//Disabled Button
|
||||
toggleSwitch.disabled = true;
|
||||
SendState('on', targetState?'1':'0');
|
||||
}
|
||||
});
|
||||
|
||||
function shutdownTriggered(){
|
||||
//shutdownForm.style.display = 'none';
|
||||
let t = parseInt(shutTimerInput.value);
|
||||
console.log('Send timed shutdown: ' + t);
|
||||
if (stateValue){
|
||||
//url: htttp://<IP>/state?shutdown=<seconds>
|
||||
SendState('shutdown', t);
|
||||
}
|
||||
}
|
||||
|
||||
function ConnectionRefresh(){
|
||||
lastConnectTick = performance.now();
|
||||
connectStateLabel.textContent = ' 🟢';
|
||||
connectStateLabel.title = 'Connected';
|
||||
}
|
||||
|
||||
dropdownChargeModeSelect.addEventListener('change', function() {
|
||||
targetChargeMode = parseInt(this.value);
|
||||
let s = 'charge mode: ' + targetChargeMode;
|
||||
console.log(s);
|
||||
if (targetChargeMode!==chargeMode){
|
||||
this.disabled = true;
|
||||
SendState('cm', targetChargeMode);
|
||||
}
|
||||
});
|
||||
|
||||
dropdownFullChargeOnPowerLostModeSelect.addEventListener('change', function() {
|
||||
targetFCOPLMode = parseInt(this.value);
|
||||
let s = 'FCOPL mode: ' + targetFCOPLMode;
|
||||
console.log(s);
|
||||
if (targetFCOPLMode!==fullChargeOnPowerLostMode){
|
||||
this.disabled = true;
|
||||
SendState('coil', targetFCOPLMode);
|
||||
}
|
||||
});
|
||||
|
||||
function resetFCoPLTriggered(){
|
||||
btnResetFCOPL.disabled = true;
|
||||
if (fullChargeOnPowerLostState){
|
||||
SendState('rofc', 1);
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutomaticVoltageReducer.addEventListener('change', function() {
|
||||
targetAutomaticVoltageReducer = toggleAutomaticVoltageReducer.checked;
|
||||
let s = 'AVR switched: ' + (targetAutomaticVoltageReducer?'ON':'OFF');
|
||||
console.log(s);
|
||||
if (targetAutomaticVoltageReducer!==chargeAutoVoltageReducer){
|
||||
//Disabled Button
|
||||
toggleAutomaticVoltageReducer.disabled = true;
|
||||
SendState('car', targetAutomaticVoltageReducer?'1':'0');
|
||||
}
|
||||
});
|
||||
|
||||
toggleOvervoltageProtection.addEventListener('change', function() {
|
||||
targetOvervoltageProtection = toggleOvervoltageProtection.checked;
|
||||
let s = 'OVP switched: ' + (targetOvervoltageProtection?'ON':'OFF');
|
||||
console.log(s);
|
||||
if (targetOvervoltageProtection!==chargeOvervoltageProtection){
|
||||
//Disabled Button
|
||||
toggleOvervoltageProtection.disabled = true;
|
||||
SendState('covp', targetOvervoltageProtection?'1':'0');
|
||||
}
|
||||
});
|
||||
|
||||
function SendState(cmd, value) {
|
||||
// Clear timeout
|
||||
clearTimeout(checkRefresh);
|
||||
|
||||
if (cmd !== "") {
|
||||
onChange = true;
|
||||
const url = '/state?' + cmd + '=' + value;
|
||||
|
||||
console.log("Fetch URL:", url);
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error("Failed to update.");
|
||||
return response.text();
|
||||
})
|
||||
.then(data => {
|
||||
console.log(`${cmd} updated:`, data);
|
||||
onChange = false;
|
||||
if (parseInt(data.success)>0) ConnectionRefresh();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error:", error);
|
||||
onChange = false;
|
||||
});
|
||||
|
||||
} else {
|
||||
if (!onChange) {
|
||||
const url = '/state/';
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error("Failed to fetch data.");
|
||||
return response.json(); // Already a JSON object, no need for JSON.parse()
|
||||
})
|
||||
.then(data => {
|
||||
//console.log("Fetched Data:", data);
|
||||
if (parseInt(data.success)>0) ConnectionRefresh();
|
||||
if (onChange) return;
|
||||
//sync display
|
||||
if (data.success=="2"){
|
||||
autoOnMode = parseInt(data.auto);
|
||||
let targetAutoMode = parseInt(dropdownAutoOnSelect.value);
|
||||
if (targetAutoMode!=autoOnMode){
|
||||
dropdownAutoOnSelect.value = autoOnMode.toString();
|
||||
}
|
||||
if (dropdownAutoOnSelect.disabled){
|
||||
dropdownAutoOnSelect.disabled = false;
|
||||
}
|
||||
|
||||
//toggleAutoBtn.textContent = autoOn?'ON':'OFF';
|
||||
stateTarget = parseInt(data.on) > 0;
|
||||
stateValue = parseInt(data.state) > 0;
|
||||
if (stateTarget==stateValue){
|
||||
toggleSwitch.checked = stateTarget;
|
||||
toggleSwitch.disabled = false;
|
||||
}
|
||||
//switchBtn.textContent = stateOn?'ON':'OFF';
|
||||
|
||||
lineFrom = parseInt(data.line);
|
||||
isChargingOrBlink = parseInt(data.battBlink) > 0;
|
||||
chargeLevel = parseInt(data.chargeLv); //battery level at charges
|
||||
isBattCritical = parseInt(data.battCritical) > 0;
|
||||
|
||||
//Version 1.1
|
||||
battPercentage = parseInt(data.battP);
|
||||
chargeStatus = parseInt(data.chgError);
|
||||
chargingState = parseInt(data.charging);
|
||||
chargeMode = parseInt(data.chgMode);
|
||||
chargeAutoVoltageReducer = parseInt(data.chgReducer) > 0;
|
||||
chargeOvervoltageProtection = parseInt(data.chgOVP) > 0;
|
||||
fullChargeOnPowerLostMode = parseInt(data.chgFullTrig);
|
||||
fullChargeOnPowerLostState = parseInt(data.chgOFC) > 0;
|
||||
|
||||
//let s = lineFrom==0?'OFF':(lineFrom==1 || isChargingOrBlink)?'⚡️ AC':'🔋 Battery';
|
||||
let s = lineFrom==0?'OFF':(lineFrom==1 || isChargingOrBlink)?'⚡️ Line':'🔋 Battery';
|
||||
|
||||
if (chargingState>0){
|
||||
if (chargeStatus==0){ //normal state
|
||||
if (lineFrom==1 && chargingState==2) s += " (Charging)";
|
||||
if (lineFrom==1 && chargingState==1) s += " (Reduced Charging)";
|
||||
}else{
|
||||
//charging while error (retry test)
|
||||
if (lineFrom==1 && chargingState>0) s += " (Charging)";
|
||||
}
|
||||
}
|
||||
|
||||
if (lineFrom==2 && isBattCritical) s += " (Critical)";
|
||||
labelPower.textContent = s;
|
||||
labelInput.textContent = data.v + " V";
|
||||
battLevel = parseInt(data.batt);
|
||||
battMaxLevel = parseInt(data.battMax);
|
||||
|
||||
|
||||
//display V1.1
|
||||
//charge mode:
|
||||
if (targetChargeMode!=chargeMode){
|
||||
dropdownChargeModeSelect.value = chargeMode.toString();
|
||||
}
|
||||
if (dropdownChargeModeSelect.disabled){
|
||||
dropdownChargeModeSelect.disabled = false;
|
||||
}
|
||||
//fcopl:
|
||||
if (targetFCOPLMode!=fullChargeOnPowerLostMode){
|
||||
dropdownFullChargeOnPowerLostModeSelect.value = fullChargeOnPowerLostMode.toString();
|
||||
}
|
||||
if (dropdownFullChargeOnPowerLostModeSelect.disabled){
|
||||
dropdownFullChargeOnPowerLostModeSelect.disabled = false;
|
||||
}
|
||||
//cvopl reset button:
|
||||
if (fullChargeOnPowerLostState){
|
||||
if (btnResetFCOPL.disabled || btnResetFCOPL.style.display !== "inline-block"){
|
||||
btnResetFCOPL.disabled = false;
|
||||
btnResetFCOPL.style.display = "inline-block";
|
||||
}
|
||||
}else{
|
||||
if (!btnResetFCOPL.disabled || btnResetFCOPL.style.visibility !== "none"){
|
||||
btnResetFCOPL.disabled = true;
|
||||
btnResetFCOPL.style.display = "none";
|
||||
}
|
||||
}
|
||||
//AVR:
|
||||
//if (toggleAutomaticVoltageReducer.checked!=chargeAutoVoltageReducer){
|
||||
if (toggleAutomaticVoltageReducer.disabled){
|
||||
toggleAutomaticVoltageReducer.checked = chargeAutoVoltageReducer;
|
||||
toggleAutomaticVoltageReducer.disabled = false;
|
||||
}
|
||||
//}
|
||||
//OVP:
|
||||
if (toggleOvervoltageProtection.disabled){
|
||||
toggleOvervoltageProtection.checked = chargeOvervoltageProtection;
|
||||
toggleOvervoltageProtection.disabled = false;
|
||||
}
|
||||
|
||||
|
||||
if (stateTarget==stateValue){
|
||||
if (stateValue){
|
||||
enableShutdownForm();
|
||||
}else{
|
||||
disableShutdownForm();
|
||||
}
|
||||
}else{
|
||||
disableShutdownForm();
|
||||
}
|
||||
|
||||
timedShutdown = parseInt(data.timedShutdown) > 0;
|
||||
if (timedShutdown){
|
||||
timedShutRemaining = parseInt(data.shutdownRemain);
|
||||
if (timedShutRemaining==0){
|
||||
shutTimer.textContent = 'TURNING OFF...';
|
||||
}else{
|
||||
shutTimer.textContent = timedShutRemaining.toString();
|
||||
}
|
||||
shutdownForm.style.display = 'none';
|
||||
}else{
|
||||
timedShutRemaining = 0;
|
||||
shutTimer.textContent = '';
|
||||
shutdownForm.style.display = 'block';
|
||||
}
|
||||
|
||||
if (!everReceiveStat){
|
||||
shutTimerInput.value = parseInt(data.shutLastTimer);
|
||||
}
|
||||
everReceiveStat = true;
|
||||
|
||||
|
||||
//shutdown suggestion level
|
||||
let shutLv = parseInt(data.shutdownSuggestMode);
|
||||
if (shutdownSuggestionLevel!=shutLv){
|
||||
shutdownSuggestionLevel = shutLv;
|
||||
dropdownShutLevelSelect.value = shutLv.toString();
|
||||
}
|
||||
if (dropdownShutLevelSelect.disabled){
|
||||
dropdownShutLevelSelect.disabled = false;
|
||||
}
|
||||
|
||||
shutdownCastFlag = parseInt(data.shutdownSuggest) > 0;
|
||||
shutFlagLabel.textContent = shutdownCastFlag?'✔️':' ';
|
||||
|
||||
if (chargeStatus==0){
|
||||
labelStatus.textContent = "Normal";
|
||||
}else if (chargeStatus==1){
|
||||
labelStatus.textContent = "⚠️ Battery Not Detected" + (chargingState==0?"":" (Retry charging...)");
|
||||
}else if (chargeStatus==2){
|
||||
labelStatus.textContent = "⛔ Battery Shorted" + (chargingState==0?"":" (Retry charging..)");
|
||||
}else if (chargeStatus==4){
|
||||
labelStatus.textContent = "⚠️ Charge Overvoltage" + (chargingState==0?"":" (Retry charging...)");
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error:", error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//charging independent animation
|
||||
function BattDisplayRoutine(){
|
||||
|
||||
//connection state update when timeout
|
||||
if (performance.now() - lastConnectTick >= 3000){
|
||||
connectStateLabel.textContent = ' ⚫';
|
||||
connectStateLabel.title = 'Unavailable';
|
||||
}
|
||||
|
||||
if (!isChargingOrBlink){
|
||||
if (isBattCritical){
|
||||
chargeTickTock = !chargeTickTock;
|
||||
let bar = chargeTickTock?0:1;
|
||||
if (chargeTickTock){
|
||||
labelBatt.textContent = repeatChar('🟧', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
|
||||
}else{
|
||||
labelBatt.textContent = repeatChar('🟧', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
|
||||
}
|
||||
}else{
|
||||
chargeTickTock = false;
|
||||
if (battMaxLevel==0){
|
||||
labelBatt.textContent = '⬛⬛⬛⬛';//'Unknown';
|
||||
}else{
|
||||
labelBatt.textContent = repeatChar('🟩', battLevel) + repeatChar('⬛', battMaxLevel-battLevel) + " " + battPercentage + "%";
|
||||
}
|
||||
}
|
||||
}else{
|
||||
chargeTickTock = !chargeTickTock;
|
||||
let bar = chargeTickTock?Math.min(chargeLevel+1, battMaxLevel):Math.min(chargeLevel, battMaxLevel);
|
||||
if (chargeTickTock){
|
||||
labelBatt.textContent = repeatChar('🟩', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
|
||||
}else{
|
||||
labelBatt.textContent = repeatChar('🟩', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncState(){
|
||||
//checkOnlineState();
|
||||
if (onChange){
|
||||
clearTimeout(checkRefresh);
|
||||
return;
|
||||
}
|
||||
checkRefresh = setTimeout(function() {
|
||||
SendState("", 0);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
disableShutdownForm();
|
||||
|
||||
// Set an interval to sync display
|
||||
setInterval(syncState, 500);
|
||||
|
||||
SendState("", 0);
|
||||
syncState();
|
||||
|
||||
setInterval(BattDisplayRoutine, 1000);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// the setup function runs once when you press reset or power the board
|
||||
|
||||
#include "upsIO.h"
|
||||
#include "upsSvr.h"
|
||||
|
||||
bool printDebug = true;
|
||||
|
||||
bool inOk = false;
|
||||
int16_t adc = 0;
|
||||
float voltage = 0;
|
||||
uint32_t infoTick = 0;
|
||||
|
||||
void setup() {
|
||||
// initialize digital pin LED_BUILTIN as an output.
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
//ESP.wdtDisable();
|
||||
|
||||
//initUpsIo(true);
|
||||
initUpsIo();
|
||||
//initUpsIo(false, true);
|
||||
|
||||
upsServerInit();
|
||||
|
||||
//CharliplexDemoRoutine();
|
||||
}
|
||||
|
||||
// the loop function runs over and over again forever
|
||||
void loop() {
|
||||
if (printDebug && (millis()-infoTick>=500)){
|
||||
infoTick = millis();
|
||||
adc = analogRead(A0);//ioAdc;//analogRead(A0);//ioAdc;//
|
||||
//voltage = (16.0*((float)adc / 1024)) * 0.983; //use 1.5 MOhm series resistor
|
||||
voltage = (16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor
|
||||
Serial.println("VBatt: " + (String(voltage, 2)) + " V");
|
||||
|
||||
//adc = ioAdcFiltered;
|
||||
voltage = batteryVoltageUnfiltered;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor
|
||||
Serial.println("VBatt (Sub-Min): " + (String(voltage, 2)) + " V");
|
||||
|
||||
//adc = adcValueMin;
|
||||
voltage = batteryVoltage;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor
|
||||
Serial.print("VBatt (Min): " + (String(voltage, 2)) + " V");
|
||||
Serial.print(" (" + (String(batteryPercentage)) + "%)");
|
||||
Serial.println(", LV: " + (String(batteryLevel)));
|
||||
|
||||
|
||||
Serial.println("Charge Error: " + (String(chargeError)));
|
||||
Serial.println("Charge Switch: " + (String(isChargeActive)));
|
||||
Serial.println("Input Under Voltage: " + (String(inputUnderVoltage)));
|
||||
Serial.println("Input Voltage Exists: " + (String(inputVoltageKeepup)));
|
||||
|
||||
//inOk = digitalRead(pinInputSense);
|
||||
//Serial.println("Vin: " + (String)(inOk?"True":"False"));
|
||||
|
||||
//buttonState = !digitalRead(pinInputButton); //inverted
|
||||
Serial.println("Button: " + (String)(buttonState?"True":"False"));
|
||||
Serial.println("Button (short): " + (String)(buttonShortPress?"True":"False"));
|
||||
Serial.println("Button (long): " + (String)(buttonLongPress?"True":"False"));
|
||||
|
||||
Serial.println("Power OUT: " + (String)(powerOutputActive?"True":"False"));
|
||||
//digitalWrite(pinLed, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||
//digitalWrite(pinPowerOut, HIGH);
|
||||
|
||||
Serial.println("KEEP POWER: " + (String)(keepPowerState?"True":"False"));
|
||||
|
||||
Serial.println("Display: " + (String)(displayActive?"True":"False"));
|
||||
|
||||
Serial.println("RSSI: " + (String)(rssi));
|
||||
|
||||
Serial.println("Charge Allowed: " + (String)(chargeAllowed?"True":"False"));
|
||||
|
||||
//for persistent shutdown demo: ==============================================================
|
||||
// if (persistentShutdown){
|
||||
// Serial.println("Shutdown in: " + (String)(getPersistentShutdownTick()/1000000));
|
||||
// }else{
|
||||
// if (restartAutoPowerOn()) Serial.println("Automatic ON restarted");
|
||||
// setPersistentShutdown(10000000UL);
|
||||
// }
|
||||
//============================================================================================
|
||||
}
|
||||
|
||||
upsServerRoutine();
|
||||
|
||||
upsRoutineInterrupt();
|
||||
|
||||
//delay(2000); // wait for a second
|
||||
//digitalWrite(pinLed, LOW); // turn the LED off by making the voltage LOW
|
||||
//digitalWrite(pinPowerOut, LOW);
|
||||
|
||||
|
||||
//CharlieplexDemoRoutine();
|
||||
//UpsIoRoutine();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#include <FS.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
//shutdown suggestion level
|
||||
#define SHUTDOWN_SUGGESTION_DISABLED 0
|
||||
#define SHUTDOWN_SUGGESTION_ON_BATT 1
|
||||
#define SHUTDOWN_SUGGESTION_ON_BATT_75 2
|
||||
#define SHUTDOWN_SUGGESTION_ON_BATT_50 3
|
||||
#define SHUTDOWN_SUGGESTION_ON_BATT_25 4
|
||||
#define SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL 5
|
||||
static volatile uint8_t cfgShutdownSuggestion = SHUTDOWN_SUGGESTION_ON_BATT_25;
|
||||
|
||||
#define AUTO_POWER_OFF 0
|
||||
#define AUTO_POWER_ON 1
|
||||
#define AUTO_POWER_PERSIST 2
|
||||
static volatile uint8_t cfgAutoOn = AUTO_POWER_ON;//AUTO_POWER_PERSIST;//AUTO_POWER_ON;
|
||||
|
||||
#define SHUTDOWN_DURATION_DEFAULT 120
|
||||
static volatile uint16_t cfgShutdownInSeconds = SHUTDOWN_DURATION_DEFAULT;//default time in seconds for shutdown
|
||||
|
||||
//full charge trigger:
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_NONE 0
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS 1 //trigger full charge if below 100 percent
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95 2 //trigger full charge if below 95 percent
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90 3 //trigger full charge if below 90 percent
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85 4 //trigger full charge if below 85 percent
|
||||
#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80 5 //trigger full charge if below 80 percent
|
||||
static volatile uint8_t cfgInputLostTriggerChargeMode = INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90;
|
||||
|
||||
#define CHARGE_MODE_ALWAYS 0
|
||||
#define CHARGE_MODE_RANGE_90_100 1
|
||||
#define CHARGE_MODE_RANGE_85_100 2
|
||||
#define CHARGE_MODE_RANGE_80_100 3
|
||||
#define CHARGE_MODE_RANGE_90_95 4
|
||||
#define CHARGE_MODE_RANGE_85_95 5
|
||||
#define CHARGE_MODE_RANGE_80_95 6
|
||||
#define CHARGE_MODE_RANGE_85_90 7
|
||||
#define CHARGE_MODE_RANGE_80_90 8
|
||||
#define CHARGE_MODE_RANGE_80_85 9
|
||||
static volatile uint8_t cfgChargeMode = CHARGE_MODE_RANGE_85_90;
|
||||
|
||||
static volatile uint8_t cfgChargeOvervoltageProtection = 1;
|
||||
static volatile uint8_t cfgChargeAutomaticReducer = 1;
|
||||
|
||||
bool spiffsOk = false;
|
||||
|
||||
bool initConfigs(){
|
||||
//setup spiffs
|
||||
spiffsOk = SPIFFS.begin();
|
||||
if (!spiffsOk){
|
||||
Serial.println("SPIFFS ERROR!");
|
||||
}else{
|
||||
//LoadCfg();
|
||||
}
|
||||
return spiffsOk;
|
||||
}
|
||||
|
||||
void loadCfg(){
|
||||
if (!spiffsOk) return;
|
||||
// Open file for reading
|
||||
File file = SPIFFS.open("/cfg.json", "r");
|
||||
if (!file) {
|
||||
Serial.println("Failed to open config");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse file contents as JSON
|
||||
DynamicJsonDocument jsonDoc(255);
|
||||
DeserializationError error = deserializeJson(jsonDoc, file);
|
||||
file.close();
|
||||
|
||||
if (error) {
|
||||
Serial.println("Failed to parse file settings");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t tmpAutoOnMode = jsonDoc["auto"].as<int>();
|
||||
cfgAutoOn = tmpAutoOnMode;
|
||||
if (cfgAutoOn>AUTO_POWER_PERSIST) cfgAutoOn = AUTO_POWER_PERSIST;
|
||||
|
||||
uint8_t v = jsonDoc["shut"].as<int>();
|
||||
if (v>SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) v = SHUTDOWN_SUGGESTION_ON_BATT_25;
|
||||
cfgShutdownSuggestion = v;
|
||||
|
||||
v = jsonDoc["chgMode"].as<int>();
|
||||
if (v>CHARGE_MODE_RANGE_80_85) v = CHARGE_MODE_RANGE_90_100;
|
||||
cfgChargeMode = v;
|
||||
|
||||
v = jsonDoc["chgILTC"].as<int>();
|
||||
if (v>INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) v = INPUT_LOST_TRIGGER_CHARGE_NONE;
|
||||
cfgInputLostTriggerChargeMode = v;
|
||||
|
||||
v = jsonDoc["chgOVP"].as<int>();
|
||||
if (v>1) v = 1;
|
||||
cfgChargeOvervoltageProtection = v;
|
||||
|
||||
v = jsonDoc["chgAR"].as<int>();
|
||||
if (v>1) v = 1;
|
||||
cfgChargeAutomaticReducer = v;
|
||||
}
|
||||
|
||||
void saveCfg(){
|
||||
if (!spiffsOk) return;
|
||||
DynamicJsonDocument jsonDoc(255);
|
||||
jsonDoc["auto"] = cfgAutoOn;
|
||||
jsonDoc["shut"] = cfgShutdownSuggestion;
|
||||
jsonDoc["chgMode"] = cfgChargeMode;
|
||||
jsonDoc["chgILTC"] = cfgInputLostTriggerChargeMode;
|
||||
jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection;
|
||||
jsonDoc["chgAR"] = cfgChargeAutomaticReducer;
|
||||
File file = SPIFFS.open("/cfg.json", "w+");
|
||||
if (file) {
|
||||
uint32_t sz = serializeJson(jsonDoc, file);
|
||||
file.close();
|
||||
Serial.println("Configuration updated!");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
#include <Arduino.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "bitmap.h"
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 32 // 0.91 inch OLED is usually 128x32
|
||||
|
||||
// Custom Pins
|
||||
#define OLED_SCL 12 // MISO = D6 = OLED_SCL
|
||||
#define OLED_SDA 13 // MOSI = D7 = OLED_SDA
|
||||
#define UNUSED_PIN 14 // SCK = D5 (reserved when need reset pin)
|
||||
#define OLED_RESET -1 // Or 0 if display has a reset pin
|
||||
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ OLED_SCL, /* data=*/ OLED_SDA); // pin remapping with ESP8266 HW I2C
|
||||
|
||||
uint8_t displayContrast = 96;
|
||||
uint8_t battLevelNum = 4;
|
||||
uint8_t battBlockWidth = 15; uint8_t battBlockHeight = 14;
|
||||
uint8_t battBlockGap = 3;
|
||||
|
||||
//for demo
|
||||
uint8_t dispDemoIdx = 0;
|
||||
|
||||
void displaySplash(){
|
||||
u8g2.setContrast(255);
|
||||
//u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap);
|
||||
u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap_inverted);
|
||||
u8g2.sendBuffer();
|
||||
}
|
||||
|
||||
void drawBatteryBlocks(int8_t x, int8_t y, int8_t nFilled){
|
||||
int8_t xp = x+((battBlockWidth+battBlockGap)*battLevelNum);
|
||||
xp-=battBlockGap;
|
||||
int8_t yp = y;
|
||||
u8g2.setDrawColor(1);
|
||||
for (int8_t i=0; i<battLevelNum; i++){
|
||||
xp-=battBlockWidth;
|
||||
if (i<nFilled){
|
||||
u8g2.drawRBox(xp, yp, battBlockWidth, battBlockHeight, 2); //Draws a filled box with rounded
|
||||
}else{
|
||||
u8g2.drawRFrame(xp, yp, battBlockWidth, battBlockHeight, 2); //Draws an empty frame with rounded corners.
|
||||
}
|
||||
xp-=battBlockGap;
|
||||
}
|
||||
}
|
||||
|
||||
void drawBatteryCase(int8_t x, int8_t y, uint8_t style){
|
||||
//u8g2.drawLine(x, y, x+(battBlockWidth+battBlockGap)*battLevelNum), y);
|
||||
int8_t sideGap = 4;
|
||||
int8_t xpe = (x+((battBlockWidth+battBlockGap)*battLevelNum)) - battBlockGap;
|
||||
int8_t w = ((battBlockWidth+battBlockGap)*battLevelNum) - battBlockGap;
|
||||
u8g2.setDrawColor(1);
|
||||
//u8g2.drawLine(x-sideGap, y-sideGap, xpe+sideGap, y-sideGap);
|
||||
u8g2.drawRFrame(x-sideGap, y-sideGap, w+sideGap+sideGap, battBlockHeight+sideGap+sideGap, 3);
|
||||
//kepala batre
|
||||
int8_t headW = 5;
|
||||
int8_t headH = battBlockHeight-2;
|
||||
int8_t yc = y+(battBlockHeight/2);
|
||||
//u8g2.drawRFrame(x-(sideGap+headW), y+sideGap, headW, battBlockHeight, 3);
|
||||
if (style==0){
|
||||
u8g2.drawRFrame(x-(sideGap+headW), yc-(headH/2), headW+1, headH, 1);
|
||||
}else if (style==1){
|
||||
u8g2.drawRBox(x-(sideGap+headW), yc-(headH/2), headW+1, headH, 1);
|
||||
}else if (style==2 || style==3){
|
||||
u8g2.drawRFrame(x-(sideGap+headW), yc-(headH/2), headW+1, headH, 1);
|
||||
u8g2.setDrawColor(0);
|
||||
int8_t xp = (x-(sideGap+headW)) + headW;
|
||||
int8_t yp = yc-(headH/2);
|
||||
if (style==2) u8g2.drawLine(xp, yp+1, xp, yp+(headH-2)); //rigid
|
||||
if (style==3) u8g2.drawLine(xp, yp+0, xp, yp+(headH-1)); //curved or sleek
|
||||
}
|
||||
}
|
||||
|
||||
void drawBatteryPercentage(int8_t x, int8_t y, int8_t textWidth, int8_t percentage){
|
||||
//draw text right aligned
|
||||
String s = String(percentage) + "%";
|
||||
char char_array[6];
|
||||
s.toCharArray(char_array, s.length()+1);
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
||||
int8_t w = u8g2.getStrWidth(char_array); //note: current string width calculation is still not 100% perfect as the initial offset is part of the returned width.
|
||||
int8_t xp = x + (textWidth-w);
|
||||
u8g2.setDrawColor(1);
|
||||
u8g2.drawStr(xp,y,char_array);
|
||||
}
|
||||
|
||||
void drawActiveTransmission(int8_t x, int8_t y, bool inArrow, bool outArrow){
|
||||
u8g2.setDrawColor(1);
|
||||
if (inArrow) u8g2.drawXBMP(x, y, 2, 4, active_data_2x4_in_bitmap);
|
||||
if (outArrow) u8g2.drawXBMP(x+3, y, 2, 4, active_data_2x4_out_bitmap);
|
||||
}
|
||||
|
||||
void drawWifi(int8_t x, int8_t y, uint8_t level){
|
||||
//power 0 = not drawn
|
||||
u8g2.setDrawColor(1);
|
||||
if (level==1) u8g2.drawXBMP(x, y, 16, 16, wifi2_lv1_bitmap);
|
||||
if (level==2) u8g2.drawXBMP(x, y, 16, 16, wifi2_lv2_bitmap);
|
||||
if (level>2) u8g2.drawXBMP(x, y, 16, 16, wifi2_lv3_bitmap);
|
||||
}
|
||||
|
||||
void drawWifiAndTransmission(int8_t x, int8_t y, uint8_t level, bool inArrow, bool outArrow){
|
||||
//power 0 = not drawn
|
||||
drawWifi(x, y+1, level);
|
||||
drawActiveTransmission(x+11, y+12, inArrow, outArrow);
|
||||
}
|
||||
|
||||
void drawPower(int8_t x, int8_t y, bool timed){
|
||||
u8g2.setDrawColor(1);
|
||||
if (timed){
|
||||
u8g2.drawXBMP(x, y, 16, 16, timer_16x16_bitmap);
|
||||
}else{
|
||||
u8g2.drawXBMP(x, y, 16, 16, power_bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
void drawBolt(int8_t x, int8_t y){
|
||||
u8g2.setDrawColor(1);
|
||||
u8g2.drawXBMP(x, y, 16, 16, bolt_16x16_bitmap);
|
||||
}
|
||||
|
||||
void drawCharging(int8_t x, int8_t y, bool reduced){
|
||||
u8g2.setDrawColor(1);
|
||||
if (reduced){
|
||||
u8g2.drawXBMP(x, y, 7, 8, charging_7x8_bitmap);
|
||||
}else{
|
||||
u8g2.drawXBMP(x, y, 7, 8, charge_fast_7x8_bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
void drawWarning(int8_t x, int8_t y, uint8_t mode){
|
||||
u8g2.setDrawColor(1);
|
||||
if (mode==0){
|
||||
u8g2.drawXBMP(x, y, 16, 16, warn_16x16_bitmap);
|
||||
}else if (mode==1){
|
||||
u8g2.drawXBMP(x, y, 16, 16, error_16x16_bitmap);
|
||||
}else if (mode==2){
|
||||
u8g2.drawXBMP(x, y, 16, 16, fatal_16x16_bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
void initDisplay(bool showSplash){
|
||||
pinMode(UNUSED_PIN, INPUT);
|
||||
u8g2.begin();
|
||||
u8g2.setContrast(displayContrast);
|
||||
if (showSplash){
|
||||
displaySplash();
|
||||
}else{
|
||||
u8g2.setContrast(displayContrast);
|
||||
u8g2.clearBuffer();
|
||||
u8g2.sendBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void clearDisplay(){
|
||||
u8g2.clearBuffer();
|
||||
}
|
||||
|
||||
void sendDisplay(){
|
||||
u8g2.sendBuffer();
|
||||
}
|
||||
|
||||
void drawString(int8_t x, int8_t y, String s){
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||
char char_array[32];
|
||||
s.toCharArray(char_array, s.length()+1);
|
||||
char_array[s.length()] = 0;
|
||||
u8g2.setDrawColor(1);
|
||||
u8g2.drawStr(x, y, char_array);
|
||||
}
|
||||
|
||||
uint8_t stringWidth(String s){
|
||||
char char_array[32];
|
||||
s.toCharArray(char_array, s.length()+1);
|
||||
char_array[s.length()] = 0;
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
||||
return u8g2.getStrWidth(char_array);
|
||||
}
|
||||
|
||||
uint8_t stringCenterX(String s){
|
||||
char char_array[32];
|
||||
s.toCharArray(char_array, s.length()+1);
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
||||
uint8_t w = u8g2.getStrWidth(char_array);
|
||||
return (SCREEN_WIDTH-w)/2;
|
||||
}
|
||||
|
||||
uint8_t stringRightSideX(String s){
|
||||
char char_array[32];
|
||||
s.toCharArray(char_array, s.length()+1);
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
||||
uint8_t w = u8g2.getStrWidth(char_array);
|
||||
return (SCREEN_WIDTH-w);
|
||||
}
|
||||
|
||||
void displayDemoRoutine1(){
|
||||
u8g2.setContrast(displayContrast);
|
||||
u8g2.clearBuffer();
|
||||
//drawBatteryBlock(5,5, true);
|
||||
drawBatteryBlocks(55, 4, dispDemoIdx);
|
||||
drawBatteryCase(55, 4, 2);
|
||||
|
||||
//u8g2.setDrawColor(1);
|
||||
//u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
|
||||
//u8g2.drawStr(100,32,"100%");
|
||||
drawBatteryPercentage(100,32,28, 25*dispDemoIdx);
|
||||
|
||||
if (dispDemoIdx<=3) drawPower(0,0, dispDemoIdx>=2);
|
||||
if (dispDemoIdx==1 || dispDemoIdx==3) drawBolt(20,0);
|
||||
|
||||
//drawWifi(0, 16, dispDemoIdx);
|
||||
//drawActiveTransmission(11,28, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false);
|
||||
|
||||
drawWifiAndTransmission(0, 16, dispDemoIdx, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false);
|
||||
|
||||
drawCharging(88, 24, dispDemoIdx==4);
|
||||
|
||||
drawWarning(20,16, dispDemoIdx);
|
||||
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||
if (dispDemoIdx==0) u8g2.drawStr(39, 32, "OVER.");
|
||||
if (dispDemoIdx==1) u8g2.drawStr(39, 32, "N0 BAT.");
|
||||
if (dispDemoIdx==2) u8g2.drawStr(39, 32, "SHORT");
|
||||
|
||||
dispDemoIdx++;
|
||||
if (dispDemoIdx > 4) dispDemoIdx = 0;
|
||||
u8g2.sendBuffer();
|
||||
}
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
//todo:
|
||||
// - more accurate TX/RX indication (for security concern), e.g. favicon is served static, thus not display TX/RX arrow.
|
||||
// - Login and apikey access for next version
|
||||
|
||||
#include "wifi.h"
|
||||
#include "upsCfg.h"
|
||||
|
||||
#ifndef UPSIO_H
|
||||
#include "upsIo.h"
|
||||
#endif
|
||||
|
||||
//#include <AsyncTCP.h> //for esp32
|
||||
#include <ESPAsyncTCP.h> //for esp8266
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
int8_t wifiStatePrev = 99;
|
||||
|
||||
bool configChanged = false;
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
void applyConfigs(){
|
||||
autoPowerOn = (cfgAutoOn >= AUTO_POWER_ON); //auto power on
|
||||
|
||||
//cfgChargeMode: charge mode:
|
||||
switch (cfgChargeMode){
|
||||
case CHARGE_MODE_ALWAYS:{
|
||||
chargeAlwaysActive = true;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_90_100:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 100;
|
||||
chargePercentageForResume = 90;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_85_100:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 100;
|
||||
chargePercentageForResume = 85;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_80_100:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 100;
|
||||
chargePercentageForResume = 80;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_90_95:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = false;
|
||||
chargePercentageForStop = 95;
|
||||
chargePercentageForResume = 90;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_85_95:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 95;
|
||||
chargePercentageForResume = 85;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_80_95:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 95;
|
||||
chargePercentageForResume = 80;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_85_90:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 90;
|
||||
chargePercentageForResume = 85;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_80_90:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 90;
|
||||
chargePercentageForResume = 80;
|
||||
}
|
||||
break;
|
||||
case CHARGE_MODE_RANGE_80_85:{
|
||||
chargeAlwaysActive = false;
|
||||
chargeToFull = true;
|
||||
chargePercentageForStop = 85;
|
||||
chargePercentageForResume = 80;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//cfgInputLostTriggerChargeMode:
|
||||
switch (cfgInputLostTriggerChargeMode){
|
||||
case INPUT_LOST_TRIGGER_CHARGE_NONE:{
|
||||
chargeAfterInputLost = false;
|
||||
}
|
||||
break;
|
||||
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS:{
|
||||
chargeAfterInputLost = true;
|
||||
fullChargeAfterInputLost = true;
|
||||
}
|
||||
break;
|
||||
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95:{
|
||||
chargeAfterInputLost = true;
|
||||
fullChargeAfterInputLost = false;
|
||||
triggerFullChargeAfterInputLostAtPercentage = 95;
|
||||
}
|
||||
break;
|
||||
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90:{
|
||||
chargeAfterInputLost = true;
|
||||
fullChargeAfterInputLost = false;
|
||||
triggerFullChargeAfterInputLostAtPercentage = 90;
|
||||
}
|
||||
break;
|
||||
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85:{
|
||||
chargeAfterInputLost = true;
|
||||
fullChargeAfterInputLost = false;
|
||||
triggerFullChargeAfterInputLostAtPercentage = 85;
|
||||
}
|
||||
break;
|
||||
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80:{
|
||||
chargeAfterInputLost = true;
|
||||
fullChargeAfterInputLost = false;
|
||||
triggerFullChargeAfterInputLostAtPercentage = 80;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//cfgChargeOvervoltageProtection:
|
||||
chargeOvervoltageProtection = (cfgChargeOvervoltageProtection > 0);
|
||||
//cfgChargeAutomaticReducer:
|
||||
chargeReduceAtBatteryFull = (cfgChargeAutomaticReducer > 0);
|
||||
}
|
||||
|
||||
bool getShutdownSuggestionFlag(){
|
||||
if (cfgShutdownSuggestion==SHUTDOWN_SUGGESTION_DISABLED) return false;
|
||||
if (!startupReady) return false;
|
||||
switch(cfgShutdownSuggestion){
|
||||
case SHUTDOWN_SUGGESTION_ON_BATT:{
|
||||
return powerOutputActive && !inputVoltageKeepup;
|
||||
}
|
||||
break;
|
||||
case SHUTDOWN_SUGGESTION_ON_BATT_75:{
|
||||
return powerOutputActive && !inputVoltageKeepup && (batteryLevel<=3);
|
||||
}
|
||||
break;
|
||||
case SHUTDOWN_SUGGESTION_ON_BATT_50:{
|
||||
return powerOutputActive && !inputVoltageKeepup && (batteryLevel<=2);
|
||||
}
|
||||
break;
|
||||
case SHUTDOWN_SUGGESTION_ON_BATT_25:{
|
||||
return powerOutputActive && !inputVoltageKeepup && (batteryLevel<=1);
|
||||
}
|
||||
break;
|
||||
case SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL:{
|
||||
return powerOutputActive && !inputVoltageKeepup && (batteryLevel<=1) && isUpsBatteryCritical();
|
||||
}
|
||||
break;
|
||||
default:{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleGetState(AsyncWebServerRequest *request) {
|
||||
setNetworkIndicatorInTransmission(true, false);
|
||||
bool isRefresh = true;
|
||||
String v = "";
|
||||
uint8_t i = 0;
|
||||
|
||||
if (request->hasParam("on")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("on")->value();
|
||||
bool tgtState = v != "0";
|
||||
Serial.print("Switch request: ");
|
||||
Serial.println(tgtState);
|
||||
if (tgtState != powerOutputActive) {
|
||||
setOutputPowerState(tgtState);
|
||||
Serial.println("Switching...");
|
||||
}
|
||||
}
|
||||
|
||||
if (request->hasParam("auto")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("auto")->value();
|
||||
uint8_t tmpAutoOnMode = v.toInt();
|
||||
if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= AUTO_POWER_PERSIST) ) {
|
||||
cfgAutoOn = tmpAutoOnMode;
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (request->hasParam("shutdown")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("shutdown")->value();
|
||||
uint16_t tSec = v.toInt();
|
||||
if (tSec > 999) tSec = 999;
|
||||
uint32_t nanoT = ((uint32_t)tSec) * 1000000UL;
|
||||
if (nanoT < 999999) nanoT = 999999;
|
||||
bool ok = setPersistentShutdown(nanoT);
|
||||
if ((cfgShutdownInSeconds != tSec && ok)){
|
||||
cfgShutdownInSeconds = tSec;
|
||||
configChanged = true;
|
||||
}
|
||||
Serial.print("Timed Shutdown request: "); Serial.println(tSec);
|
||||
//Serial.println(ok);
|
||||
}
|
||||
|
||||
if (request->hasParam("shutlv")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("shutlv")->value();
|
||||
int16_t lv = v.toInt();
|
||||
if (lv >= SHUTDOWN_SUGGESTION_DISABLED && lv <= SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) {
|
||||
if (lv != cfgShutdownSuggestion) {
|
||||
cfgShutdownSuggestion = lv;
|
||||
configChanged = true;
|
||||
Serial.print("Shutdown level changed: ");
|
||||
Serial.println(lv);
|
||||
}
|
||||
} else {
|
||||
Serial.print("Invalid shutdown level: ");
|
||||
Serial.println(v);
|
||||
}
|
||||
}
|
||||
|
||||
//battery management params:
|
||||
//cfgChargeMode (charge mode):
|
||||
if (request->hasParam("cm")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("cm")->value();
|
||||
i = v.toInt();
|
||||
if (i<=CHARGE_MODE_RANGE_80_85){
|
||||
cfgChargeMode = i;
|
||||
configChanged = true;
|
||||
Serial.print("CM: "); Serial.println(i);
|
||||
}
|
||||
}
|
||||
|
||||
//cfgInputLostTriggerChargeMode (charge on input lost):
|
||||
if (request->hasParam("coil")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("coil")->value();
|
||||
i = v.toInt();
|
||||
if (i<=INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80){
|
||||
cfgInputLostTriggerChargeMode = i;
|
||||
configChanged = true;
|
||||
Serial.print("COIL: "); Serial.println(i);
|
||||
}
|
||||
}
|
||||
|
||||
//cfgChargeOvervoltageProtection (charge overvoltage protection):
|
||||
if (request->hasParam("covp")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("covp")->value();
|
||||
i = v.toInt();
|
||||
if (i>1) i = 1;
|
||||
if (i!=cfgChargeOvervoltageProtection){
|
||||
cfgChargeOvervoltageProtection = i;
|
||||
configChanged = true;
|
||||
Serial.print("COVP: "); Serial.println(i);
|
||||
}
|
||||
}
|
||||
//reset "On-Going Full Charge" triggered after input lost (reset ongoing full charge):
|
||||
if (request->hasParam("rofc")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("rofc")->value();
|
||||
i = v.toInt();
|
||||
if (i>0 && needFullChargeOnce) {
|
||||
resetOnGoingFullChargeAfterLineLost();
|
||||
Serial.print("ROFC: "); Serial.println(i);
|
||||
}
|
||||
}
|
||||
|
||||
//cfgChargeAutomaticReducer (charge automatic reducer)
|
||||
if (request->hasParam("car")) {
|
||||
isRefresh = false;
|
||||
v = request->getParam("car")->value();
|
||||
i = v.toInt();
|
||||
if (i>1) i = 1;
|
||||
if (i!=cfgChargeAutomaticReducer){
|
||||
cfgChargeAutomaticReducer = i;
|
||||
configChanged = true;
|
||||
Serial.print("CAR: "); Serial.println(i);
|
||||
}
|
||||
}
|
||||
|
||||
DynamicJsonDocument jsonDoc(1024);
|
||||
String jsonResponse;
|
||||
//String stateOn = isUpsOn()?"1":"0";
|
||||
//String autoOn = String(cfgAutoOn);
|
||||
//String voltageValue = String(batteryVoltage, 2);
|
||||
//2 battery, 1 Input line or AC, 0 OFF
|
||||
//String lineActive = isUpsPoweredFromBattery()?"2":isInputLinePowered()?"1":isUpsOn()?"2":"0";
|
||||
uint8_t lineActiveState = isUpsPoweredFromBattery()?2:isInputLinePowered()?1:isUpsOn()?2:0;
|
||||
//String batt = String(batteryLevel);
|
||||
//String battMax = "4";
|
||||
// String battBlink = IsUpsChargingOrBlink()?"1":"0";
|
||||
// String chargeLv = IsUpsChargingOrBlink()?(String(UpsBattMinimalLevelUnderChargingOrBlink())):(String(UpsBatteryLevel()));
|
||||
// String battCritical = IsUpsBatteryCritical()?"1":"0";
|
||||
// String isTimedShut = timedShutdownFlag?"1":"0";
|
||||
// String onShutRemaining = String(GetShutdownCountdown());
|
||||
// String shutdownSuggest = GetShutdownSuggestionFlag()?"1":"0";
|
||||
// String shutdownSuggestMode = String(cfgShutdownSuggestion);
|
||||
// String shutdownLastPeriod = String(timeShutdownLastPeriod);
|
||||
|
||||
//json vars:
|
||||
jsonDoc["success"] = isRefresh ? 2 : 1;
|
||||
jsonDoc["on"] = powerOutputActive?1:0;;
|
||||
jsonDoc["state"] = isUpsOn()?1:0;
|
||||
jsonDoc["v"] = String(batteryVoltage, 2);
|
||||
jsonDoc["batt"] = batteryLevel;
|
||||
jsonDoc["battP"] = batteryPercentage;
|
||||
jsonDoc["line"] = lineActiveState;
|
||||
|
||||
jsonDoc["auto"] = cfgAutoOn;
|
||||
jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink()?1:0;
|
||||
jsonDoc["battCritical"] = isUpsBatteryCritical()?1:0;
|
||||
jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); //real level
|
||||
jsonDoc["batt"] = batteryLevel; //staged level
|
||||
jsonDoc["battMax"] = 4;
|
||||
jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive)?1:0;
|
||||
jsonDoc["shutdownRemain"] = getPersistentShutdownTick()/1000000;
|
||||
jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion;
|
||||
jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag()?1:0; //internal flag raised if cfgShutdownSuggestion fullfill the condition
|
||||
jsonDoc["shutLastTimer"] = cfgShutdownInSeconds;
|
||||
//charge configuration:
|
||||
jsonDoc["chgError"] = chargeError; //error state
|
||||
jsonDoc["charging"] = getChargingState(); //on charging state
|
||||
jsonDoc["chgMode"] = cfgChargeMode;
|
||||
jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; //over voltage protection
|
||||
jsonDoc["chgReducer"] = cfgChargeAutomaticReducer;
|
||||
jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode;
|
||||
jsonDoc["chgOFC"] = needFullChargeOnce?1:0; //when input lost and "Ongoing Full Charge" flag triggered
|
||||
|
||||
serializeJson(jsonDoc, jsonResponse);
|
||||
request->send(200, "application/json", jsonResponse);
|
||||
|
||||
setNetworkIndicatorInTransmission(true, true);
|
||||
}
|
||||
|
||||
void onRootRequest(AsyncWebServerRequest *request) {
|
||||
setNetworkIndicatorInTransmission(true, false);
|
||||
Serial.println("onRootRequest");
|
||||
bool isOK = false;
|
||||
if (spiffsOk) {
|
||||
if (SPIFFS.exists("/index.html")) {
|
||||
isOK = true;
|
||||
request->send(SPIFFS, "/index.html", "text/html");
|
||||
setNetworkIndicatorInTransmission(true, true);
|
||||
}
|
||||
}
|
||||
if (!isOK) {
|
||||
request->send(404, "text/text", "404: Not Found");
|
||||
setNetworkIndicatorInTransmission(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void upsServerInit() {
|
||||
|
||||
bool b = initConfigs();
|
||||
if (b){
|
||||
loadCfg();
|
||||
applyConfigs();
|
||||
}
|
||||
|
||||
//transfer configuration to ups library:
|
||||
if (cfgAutoOn >= AUTO_POWER_ON) {
|
||||
autoPowerOn = true;
|
||||
restartAutoPowerOn();
|
||||
} else {
|
||||
autoPowerOn = false;
|
||||
}
|
||||
|
||||
//for favicon
|
||||
server.serveStatic("/favicon.ico", SPIFFS, "/favicon.ico");
|
||||
server.serveStatic("*/favicon.ico", SPIFFS, "/favicon.ico");
|
||||
|
||||
//for RESTapi
|
||||
server.on("/state", HTTP_GET, handleGetState);
|
||||
|
||||
//root
|
||||
server.on("/", onRootRequest);
|
||||
server.on("/*", onRootRequest);
|
||||
//Redirect all not-found requests to the root
|
||||
server.onNotFound(onRootRequest);
|
||||
|
||||
server.begin();
|
||||
Serial.println("Server is running!");
|
||||
}
|
||||
|
||||
void upsServerRoutine() {
|
||||
WiFiManagerRoutine();
|
||||
|
||||
//updating wifi state:
|
||||
if (wifiCurrentState != wifiStatePrev) {
|
||||
wifiStatePrev = wifiCurrentState;
|
||||
setNetworkState(wifiStatePrev);
|
||||
}
|
||||
|
||||
//update signal:
|
||||
indicatorNetworkSignalLevel = wifiSignalLevel;
|
||||
|
||||
//manage auto on mode:
|
||||
if (cfgAutoOn == AUTO_POWER_PERSIST) {
|
||||
if (inputVoltageKeepup && inputVoltageOK) {
|
||||
if (restartAutoPowerOn()) Serial.println("Automatic ON restarted");
|
||||
}
|
||||
}
|
||||
|
||||
//if button long pressed -------------------------------------------
|
||||
if (buttonLongPressIgnore && buttonLongPress){
|
||||
buttonLongPressIgnore = true;
|
||||
Serial.println("Long Pressed Button Action is Reserved!");
|
||||
//(not used)
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
|
||||
//saving configuration:
|
||||
if (configChanged) {
|
||||
configChanged = false;
|
||||
saveCfg();
|
||||
applyConfigs();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
|
||||
bool useStaticIP = true;
|
||||
#ifndef STASSID
|
||||
#define STASSID "EVXIO-HUB"
|
||||
#define STAPSK "Hub-1122"
|
||||
#endif
|
||||
|
||||
const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
String staticIP = "192.168.0.100";
|
||||
String staticGateway = "192.168.0.1";
|
||||
String staticSubnet = "255.255.255.0";
|
||||
String staticDNS = "192.168.0.1";
|
||||
String mdnsPrefix = "SMART-UPS"; //for fast access like http://smart-ups.local
|
||||
|
||||
static volatile bool wifiStarted = false;
|
||||
static volatile uint32_t wifiNextCheckTick = 0;
|
||||
static volatile bool wifiConnected = false;
|
||||
static volatile bool wifiConnecting = false;
|
||||
static volatile uint8_t wifiCurrentState = 0;
|
||||
static volatile bool setWifiConfig = false;
|
||||
|
||||
static volatile uint8_t wifiSignalLevel = 0; //0=none, 1=low, 2=medium, 2=high
|
||||
|
||||
long rssi = -999;
|
||||
|
||||
void WiFiManagerRoutine() {
|
||||
int8_t wifiState = WiFi.status();
|
||||
bool isWifiConnected = wifiState == WL_CONNECTED;
|
||||
if (isWifiConnected) wifiConnecting = false;
|
||||
if (wifiConnected != isWifiConnected){
|
||||
setWifiConfig = true; //reset config on reconnect
|
||||
wifiConnected = isWifiConnected;
|
||||
if (wifiConnected) wifiCurrentState = 2; //connected
|
||||
}
|
||||
|
||||
//wifi signal update
|
||||
rssi = WiFi.RSSI();
|
||||
if (rssi <= -80){
|
||||
wifiSignalLevel = 1;
|
||||
}else if (rssi > -80 && rssi <= -70){
|
||||
wifiSignalLevel = 2;
|
||||
}else if (rssi > -70 && rssi <= -60){
|
||||
wifiSignalLevel = 3;
|
||||
}else{
|
||||
wifiSignalLevel = 3;
|
||||
}
|
||||
|
||||
if (!isWifiConnected){
|
||||
if (wifiState==WL_NO_SSID_AVAIL || wifiState==WL_CONNECT_FAILED || wifiState==WL_CONNECTION_LOST) {
|
||||
wifiConnecting = false;
|
||||
wifiCurrentState=0;
|
||||
wifiSignalLevel = 0;
|
||||
}
|
||||
|
||||
if (!wifiStarted || (millis() - wifiNextCheckTick) >= 30000UL){ //check each 30seconds
|
||||
wifiStarted = true;
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
WiFi.setOutputPower(20.5);//set to max dbm for reliability (for highest RF power output, supply current ~ 80mA)
|
||||
//WiFi.setOutputPower(0);//(for lowest RF power output, supply current ~ 70mA)
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
wifiNextCheckTick = millis(); //next 30 seconds check again
|
||||
wifiConnecting = true;
|
||||
wifiCurrentState = 1; //connecting
|
||||
}
|
||||
}else{
|
||||
if (!wifiStarted){
|
||||
wifiStarted = true;
|
||||
setWifiConfig = true;
|
||||
}
|
||||
if (setWifiConfig){
|
||||
//set connection on first time connected
|
||||
setWifiConfig = false;
|
||||
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.persistent(true);
|
||||
|
||||
if (useStaticIP){
|
||||
IPAddress ip, gateway, subnet, dns;
|
||||
bool b = ip.fromString(staticIP);
|
||||
b = gateway.fromString(staticGateway);
|
||||
b = subnet.fromString(staticSubnet);
|
||||
b = dns.fromString(staticDNS);
|
||||
WiFi.config(ip, gateway, subnet, dns);
|
||||
}
|
||||
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
if (MDNS.begin(mdnsPrefix)) {
|
||||
Serial.print("MDNS responder started as ");
|
||||
Serial.print(mdnsPrefix);
|
||||
Serial.println(".local");
|
||||
}
|
||||
}
|
||||
|
||||
MDNS.update();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue