-
-
Save kOld/5d2c7f3ae3c810de0f3b00ebc06c6180 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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