Skip to content

Instantly share code, notes, and snippets.

@kOld
Last active September 13, 2022 04:31
Show Gist options
  • Save kOld/5d2c7f3ae3c810de0f3b00ebc06c6180 to your computer and use it in GitHub Desktop.
Save kOld/5d2c7f3ae3c810de0f3b00ebc06c6180 to your computer and use it in GitHub Desktop.
#include <HIDPowerDevice.h>
#include <ArduinoJson.h>
#define MINUPDATEINTERVAL 26
int iIntTimer=0;
// String constants
const char STRING_DEVICECHEMISTRY[] PROGMEM = "PbAc";
const char STRING_OEMVENDOR[] PROGMEM = "Eaton 3S Mini UPS";
const char STRING_SERIAL[] PROGMEM = "UPS10";
const byte bDeviceChemistry = IDEVICECHEMISTRY;
const byte bOEMVendor = IOEMVENDOR;
uint16_t iPresentStatus = 0, iPreviousStatus = 0;
byte bRechargable = 1;
byte bCapacityMode = 2; // units are in %%
// Physical parameters
const uint16_t iConfigVoltage = 1380;
uint16_t iVoltage =1300, iPrevVoltage = 0;
uint16_t iRunTimeToEmpty = 0, iPrevRunTimeToEmpty = 0;
uint16_t iAvgTimeToFull = 7200;
uint16_t iAvgTimeToEmpty = 7200;
uint16_t iRemainTimeLimit = 600;
int16_t iDelayBe4Reboot = -1;
int16_t iDelayBe4ShutDown = -1;
byte iAudibleAlarmCtrl = 2; // 1 - Disabled, 2 - Enabled, 3 - Muted
// Parameters for ACPI compliancy
const byte iDesignCapacity = 100;
byte iWarnCapacityLimit = 10; // warning at 10%
byte iRemnCapacityLimit = 5; // low at 5%
const byte bCapacityGranularity1 = 1;
const byte bCapacityGranularity2 = 1;
byte iFullChargeCapacity = 100;
byte iRemaining =0, iPrevRemaining=0;
int iRes=0;
/* Eaton Mini UPS */
#define leds 4
enum OutputFormat { RAW, LEDS, STATE };
OutputFormat outFormat = STATE;
int onOffEdge = 350;
int outputEveryMillis = 5000;
int minBlinksForFast = 7;
unsigned long valueSum[leds] = {0, 0, 0, 0};
unsigned int valueCur[leds] = {0, 0, 0, 0};
unsigned int valueLastChange[leds] = {0, 0, 0, 0};
unsigned short changesCntr[leds] = {0, 0, 0, 0};
unsigned int loopCntr = 0;
unsigned long stopTime = millis() + outputEveryMillis;
unsigned long upsStopTime = millis() + outputEveryMillis;
bool raw = false;
String prevOut = "";
String on = "on";
String off = "off";
String slow = "slow";
String fast = "fast";
String a = "A";
void setup() {
Serial.begin(9600);
PowerDevice.begin();
// Serial No is set in a special way as it forms Arduino port name
PowerDevice.setSerial(STRING_SERIAL);
// Used for debugging purposes.
PowerDevice.setOutput(Serial);
// pinMode(4, INPUT_PULLUP); // ground this pin to simulate power failure.
// pinMode(5, OUTPUT); // output flushing 1 sec indicating that the arduino cycle is running.
// pinMode(10, OUTPUT); // output is on once commuication is lost with the host, otherwise off.
PowerDevice.setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
PowerDevice.setFeature(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
PowerDevice.setFeature(HID_PD_AVERAGETIME2FULL, &iAvgTimeToFull, sizeof(iAvgTimeToFull));
PowerDevice.setFeature(HID_PD_AVERAGETIME2EMPTY, &iAvgTimeToEmpty, sizeof(iAvgTimeToEmpty));
PowerDevice.setFeature(HID_PD_REMAINTIMELIMIT, &iRemainTimeLimit, sizeof(iRemainTimeLimit));
PowerDevice.setFeature(HID_PD_DELAYBE4REBOOT, &iDelayBe4Reboot, sizeof(iDelayBe4Reboot));
PowerDevice.setFeature(HID_PD_DELAYBE4SHUTDOWN, &iDelayBe4ShutDown, sizeof(iDelayBe4ShutDown));
PowerDevice.setFeature(HID_PD_RECHARGEABLE, &bRechargable, sizeof(bRechargable));
PowerDevice.setFeature(HID_PD_CAPACITYMODE, &bCapacityMode, sizeof(bCapacityMode));
PowerDevice.setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
PowerDevice.setFeature(HID_PD_VOLTAGE, &iVoltage, sizeof(iVoltage));
PowerDevice.setStringFeature(HID_PD_IDEVICECHEMISTRY, &bDeviceChemistry, STRING_DEVICECHEMISTRY);
PowerDevice.setStringFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, STRING_OEMVENDOR);
PowerDevice.setFeature(HID_PD_AUDIBLEALARMCTRL, &iAudibleAlarmCtrl, sizeof(iAudibleAlarmCtrl));
PowerDevice.setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));
PowerDevice.setFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity));
PowerDevice.setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
PowerDevice.setFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit));
PowerDevice.setFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit));
PowerDevice.setFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1));
PowerDevice.setFeature(HID_PD_CPCTYGRANULARITY2, &bCapacityGranularity2, sizeof(bCapacityGranularity2));
}
bool bCharging = true;
bool bACPresent = bCharging; // TODO - replace with sensor
bool bDischarging = !bCharging; // TODO - replace with sensor
void loop() {
if (millis() >= upsStopTime) {
iRunTimeToEmpty = (uint16_t)round((float)iAvgTimeToEmpty*iRemaining/100);
// Charging
if(bCharging)
bitSet(iPresentStatus,PRESENTSTATUS_CHARGING);
else
bitClear(iPresentStatus,PRESENTSTATUS_CHARGING);
if(bACPresent)
bitSet(iPresentStatus,PRESENTSTATUS_ACPRESENT);
else
bitClear(iPresentStatus,PRESENTSTATUS_ACPRESENT);
if(iRemaining == iFullChargeCapacity)
bitSet(iPresentStatus,PRESENTSTATUS_FULLCHARGE);
else
bitClear(iPresentStatus,PRESENTSTATUS_FULLCHARGE);
// Discharging
if(bDischarging) {
bitSet(iPresentStatus,PRESENTSTATUS_DISCHARGING);
// if(iRemaining < iRemnCapacityLimit) bitSet(iPresentStatus,PRESENTSTATUS_BELOWRCL);
if(iRunTimeToEmpty < iRemainTimeLimit)
bitSet(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
else
bitClear(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
}
else {
bitClear(iPresentStatus,PRESENTSTATUS_DISCHARGING);
bitClear(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
}
// Shutdown requested
if(iDelayBe4ShutDown > 0 ) {
bitSet(iPresentStatus, PRESENTSTATUS_SHUTDOWNREQ);
Serial.println("shutdown requested");
}
else
bitClear(iPresentStatus, PRESENTSTATUS_SHUTDOWNREQ);
// Shutdown imminent
if((iPresentStatus & (1 << PRESENTSTATUS_SHUTDOWNREQ)) ||
(iPresentStatus & (1 << PRESENTSTATUS_RTLEXPIRED))) {
bitSet(iPresentStatus, PRESENTSTATUS_SHUTDOWNIMNT);
Serial.println("shutdown imminent");
}
else
bitClear(iPresentStatus, PRESENTSTATUS_SHUTDOWNIMNT);
bitSet(iPresentStatus ,PRESENTSTATUS_BATTPRESENT);
//************ Bulk send or interrupt ***********************
if((iPresentStatus != iPreviousStatus) || (iRemaining != iPrevRemaining) || (iRunTimeToEmpty != iPrevRunTimeToEmpty) || (iIntTimer>MINUPDATEINTERVAL) ) {
PowerDevice.sendReport(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
if(bDischarging) PowerDevice.sendReport(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
iRes = PowerDevice.sendReport(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
if(iRes <0 ) {
//digitalWrite(10, HIGH);
} else {
// digitalWrite(10, LOW);
}
iIntTimer = 0;
iPreviousStatus = iPresentStatus;
iPrevRemaining = iRemaining;
iPrevRunTimeToEmpty = iRunTimeToEmpty;
}
if(outFormat==RAW){
Serial.println(iRemaining);
Serial.println(iRunTimeToEmpty);
Serial.println(iRes);
}
upsStopTime = millis() + 2000;
}
/* Eaton Mini UPS */
if (millis() >= stopTime) {
switch (outFormat) {
case RAW:
doRawOutput();
break;
case LEDS:
doDiscreteOutput();
break;
case STATE:
doStateOutput();
break;
}
dataReset();
}
for (int i = 0; i < leds; i++) {
valueCur[i] = analogRead(i);
valueSum[i] += valueCur[i];
if (valueLastChange[i] > onOffEdge != valueCur[i] > onOffEdge) {
changesCntr[i]++;
valueLastChange[i] = valueCur[i];
}
}
loopCntr++;
}
void doRawOutput() {
Serial.print("{\"A0\":");
Serial.print(valueSum[0] / loopCntr);
Serial.print(",\"A1\":");
Serial.print(valueSum[1] / loopCntr);
Serial.print(",\"A2\":");
Serial.print(valueSum[2] / loopCntr);
Serial.print(",\"A3\":");
Serial.print(valueSum[3] / loopCntr);
Serial.println("}");
}
void doDiscreteOutput() {
StaticJsonDocument<100> doc;
for (int i = 0; i < leds; i++) {
if (changesCntr[i] < 2) {
// we are on or off
doc[a + i] = valueSum[i] / loopCntr > onOffEdge ? on : off;
} else {
//we are blinking
doc[a + i] = changesCntr[i] < minBlinksForFast ? slow : fast;
}
}
String out = "";
serializeJson(doc, out);
output(out);
}
void doStateOutput() {
String states[leds];
for (int i = 0; i < leds; i++) {
if (changesCntr[i] < 2) {
// we are on or off
states[i] = valueSum[i] / loopCntr > onOffEdge ? on : off;
} else {
//we are blinking
states[i] = changesCntr[i] < minBlinksForFast ? slow : fast;
}
}
// defaulting to error but hoping this will be overwritten
String state = "error";
short value = 0;
short onCntr = 0;
boolean blinkingLeds = false;
short id = 0;
for (short i = 0; i < leds; i++) {
if (states[i] == on) {
onCntr++;
id = i;
} else if (!blinkingLeds && (states[i] == slow || states[i] == fast)) {
blinkingLeds = true;
}
}
// if a single LED is on we are on mains
if (onCntr == 1 && !blinkingLeds) {
state = "normal";
bDischarging = false;
bACPresent = true;
switch (id) {
case 0:
value = 9;
break;
case 1:
value = 12;
break;
case 2:
value = 15;
break;
case 3:
value = 19;
break;
}
} else if (states[0] == fast && states[1] == off && states[2] == off && states[3] == off) {
//we are discharging <5%
state = "discharging";
value = 3;
bDischarging = true;
iRemaining = 3;
} else if (states[0] == slow && states[1] == off && states[2] == off && states[3] == off) {
//we are discharging >5 <10%
state = "discharging";
value = 8;
bDischarging = true;
iRemaining = 8;
} else if (states[0] == on && states[1] == slow && states[2] == off && states[3] == off) {
//we are discharging >25 <50%
state = "discharging";
value = 38;
bDischarging = true;
iRemaining = 38;
} else if (states[0] == on && states[1] == on && states[2] == slow && states[3] == off) {
//we are discharging >50 <75%
state = "discharging";
value = 63;
bDischarging = true;
iRemaining = 63;
} else if (states[0] == on && states[1] == on && states[2] == on && states[3] == slow) {
//we are discharging >75 <100%
state = "discharging";
value = 88;
bDischarging = true;
iRemaining = 88;
} else if (states[0] == on && states[1] == on && states[2] == on && states[3] == on) {
//we are discharging 100%
state = "discharging";
value = 100;
bDischarging = true;
iRemaining = 100;
}
StaticJsonDocument<100> doc;
doc["state"] = state;
doc["value"] = value;
String out = "";
serializeJson(doc, out);
output(out);
}
void dataReset() {
for (int i = 0; i < leds; i++) {
valueSum[i] = 0;
changesCntr[i] = 0;
}
stopTime = millis() + outputEveryMillis;
loopCntr = 0;
}
void output(String out) {
if (out.equals(prevOut)) {
Serial.println(out);
} else {
prevOut = out;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment