Skip to content

Commit c40c231

Browse files
committed
Support for Windows and Linux
Support for Windows and Linux
1 parent e16b21c commit c40c231

File tree

6 files changed

+231
-120
lines changed

6 files changed

+231
-120
lines changed

README.adoc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
= HID UPS Power Device Library for Arduino =
22

3-
This library allows an Arduino board with USB capabilities to act as a HID-compliant Power Device according to USB HID specifications. This library is useful if you want to build a smart UPS or other power device, which can report its state or perform power on/power off operations as necessary for your project.
3+
This library allows an Arduino board with USB capabilities to act as a HID-compliant UPS according to USB HID specifications.
4+
The library is useful if you want to build a smart UPS or other power device, which can report its state to the PC host
5+
or perform power on/power off operations as necessary for your project.
46

57
For more information about USB HID specs please read https://www.usb.org/sites/default/files/pdcv11.pdf
68

@@ -13,10 +15,16 @@ Setup is very simple. Just clone this repository to Arduino libraries, then uplo
1315
from the /examples folder. Once upload is completed successfully you will find HID Device Battery in
1416
your system.
1517

16-
== Supported Operating Systems ==
17-
Mac OSX 10.14.6 Mojave or newer
18+
== Additional setup step on Linux hosts ==
19+
Copy linux/98-upower-hid.rules file to the /etc/udev/rules.d/ folder , then reboot. This is required for
20+
Linux device manager (udev) to recognize the Arduino board as UPS.
1821

1922

23+
== Tested on Operating Systems ==
24+
Mac OSX 10.14.6 Mojave
25+
Ubuntu 18.04.05 LTS
26+
Windows 10
27+
2028
== License ==
2129

2230
Copyright (c) Alex Bratchik 2020. All right reserved.

examples/UPS/UPS.ino

Lines changed: 169 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,195 @@
11
#include <HIDPowerDevice.h>
22

3+
#define MINUPDATEINTERVAL 26
4+
5+
int iIntTimer=0;
6+
7+
8+
// String constants
9+
const char STRING_DEVICECHEMISTRY[] PROGMEM = "PbAc";
10+
const char STRING_OEMVENDOR[] PROGMEM = "MyCoolUPS";
11+
const char STRING_SERIAL[] PROGMEM = "UPS10";
12+
13+
const byte bDeviceChemistry = IDEVICECHEMISTRY;
14+
const byte bOEMVendor = IOEMVENDOR;
15+
16+
uint16_t iPresentStatus = 0, iPreviousStatus = 0;
17+
18+
byte bRechargable = 1;
19+
byte bCapacityMode = 2; // units are in %%
20+
21+
// Physical parameters
22+
const uint16_t iConfigVoltage = 1380;
23+
uint16_t iVoltage =1300, iPrevVoltage = 0;
24+
uint16_t iRunTimeToEmpty = 0, iPrevRunTimeToEmpty = 0;
25+
uint16_t iAvgTimeToFull = 7200;
26+
uint16_t iAvgTimeToEmpty = 7200;
27+
uint16_t iRemainTimeLimit = 600;
28+
int16_t iDelayBe4Reboot = -1;
29+
int16_t iDelayBe4ShutDown = -1;
30+
31+
byte iAudibleAlarmCtrl = 2; // 1 - Disabled, 2 - Enabled, 3 - Muted
32+
33+
34+
// Parameters for ACPI compliancy
35+
const byte iDesignCapacity = 100;
36+
byte iWarnCapacityLimit = 10; // warning at 10%
37+
byte iRemnCapacityLimit = 5; // low at 5%
38+
const byte bCapacityGranularity1 = 1;
39+
const byte bCapacityGranularity2 = 1;
40+
byte iFullChargeCapacity = 100;
41+
42+
byte iRemaining =0, iPrevRemaining=0;
43+
44+
int iRes=0;
45+
46+
347
void setup() {
4-
// put your setup code here, to run once:
48+
49+
Serial.begin(57600);
50+
551
PowerDevice.begin();
6-
pinMode(4, INPUT_PULLUP);
52+
53+
// Serial No is set in a special way as it forms Arduino port name
54+
PowerDevice.setSerial(STRING_SERIAL);
55+
56+
// Used for debugging purposes.
57+
PowerDevice.setOutput(Serial);
58+
59+
pinMode(4, INPUT_PULLUP); // ground this pin to simulate power failure.
60+
pinMode(5, OUTPUT); // output flushing 1 sec indicating that the arduino cycle is running.
61+
pinMode(10, OUTPUT); // output is on once commuication is lost with the host, otherwise off.
762

8-
Serial.begin(9600);
63+
64+
PowerDevice.setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
65+
66+
PowerDevice.setFeature(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
67+
PowerDevice.setFeature(HID_PD_AVERAGETIME2FULL, &iAvgTimeToFull, sizeof(iAvgTimeToFull));
68+
PowerDevice.setFeature(HID_PD_AVERAGETIME2EMPTY, &iAvgTimeToEmpty, sizeof(iAvgTimeToEmpty));
69+
PowerDevice.setFeature(HID_PD_REMAINTIMELIMIT, &iRemainTimeLimit, sizeof(iRemainTimeLimit));
70+
PowerDevice.setFeature(HID_PD_DELAYBE4REBOOT, &iDelayBe4Reboot, sizeof(iDelayBe4Reboot));
71+
PowerDevice.setFeature(HID_PD_DELAYBE4SHUTDOWN, &iDelayBe4ShutDown, sizeof(iDelayBe4ShutDown));
972

73+
PowerDevice.setFeature(HID_PD_RECHARGEABLE, &bRechargable, sizeof(bRechargable));
74+
PowerDevice.setFeature(HID_PD_CAPACITYMODE, &bCapacityMode, sizeof(bCapacityMode));
75+
PowerDevice.setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
76+
PowerDevice.setFeature(HID_PD_VOLTAGE, &iVoltage, sizeof(iVoltage));
77+
78+
PowerDevice.setStringFeature(HID_PD_IDEVICECHEMISTRY, &bDeviceChemistry, STRING_DEVICECHEMISTRY);
79+
PowerDevice.setStringFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, STRING_OEMVENDOR);
80+
81+
PowerDevice.setFeature(HID_PD_AUDIBLEALARMCTRL, &iAudibleAlarmCtrl, sizeof(iAudibleAlarmCtrl));
82+
83+
PowerDevice.setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));
84+
PowerDevice.setFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity));
85+
PowerDevice.setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
86+
PowerDevice.setFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit));
87+
PowerDevice.setFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit));
88+
PowerDevice.setFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1));
89+
PowerDevice.setFeature(HID_PD_CPCTYGRANULARITY2, &bCapacityGranularity2, sizeof(bCapacityGranularity2));
90+
1091
}
1192

1293
void loop() {
13-
//put your main code here, to run repeatedly:
94+
95+
96+
//*********** Measurements Unit ****************************
97+
bool bCharging = digitalRead(4);
98+
bool bACPresent = bCharging; // TODO - replace with sensor
99+
bool bDischarging = !bCharging; // TODO - replace with sensor
100+
int iA7 = analogRead(A7); // TODO - this is for debug only. Replace with charge estimation
14101

15-
delay(2000);
102+
iRemaining = (byte)(round((float)100*iA7/1024));
103+
iRunTimeToEmpty = (uint16_t)round((float)iAvgTimeToEmpty*iRemaining/100);
16104

17-
uint8_t raw[USB_EP_SIZE]={0};
18-
// Serial.println(USB_Available(HID_RX));
19-
if(Serial.available()) {
105+
// Charging
106+
if(bCharging)
107+
bitSet(iPresentStatus,PRESENTSTATUS_CHARGING);
108+
else
109+
bitClear(iPresentStatus,PRESENTSTATUS_CHARGING);
110+
if(bACPresent)
111+
bitSet(iPresentStatus,PRESENTSTATUS_ACPRESENT);
112+
else
113+
bitClear(iPresentStatus,PRESENTSTATUS_ACPRESENT);
114+
if(iRemaining == iFullChargeCapacity)
115+
bitSet(iPresentStatus,PRESENTSTATUS_FULLCHARGE);
116+
else
117+
bitClear(iPresentStatus,PRESENTSTATUS_FULLCHARGE);
20118

21-
int incomingByte = Serial.read();
22-
Serial.println(incomingByte, DEC);
23-
}
119+
// Discharging
120+
if(bDischarging) {
121+
bitSet(iPresentStatus,PRESENTSTATUS_DISCHARGING);
122+
// if(iRemaining < iRemnCapacityLimit) bitSet(iPresentStatus,PRESENTSTATUS_BELOWRCL);
24123

25-
if(USB_Available(HID_RX)) {
26-
Serial.print("USB_Available\t"); Serial.println(USB_Available(HID_RX));
124+
if(iRunTimeToEmpty < iRemainTimeLimit)
125+
bitSet(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
126+
else
127+
bitClear(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
128+
129+
}
130+
else {
131+
bitClear(iPresentStatus,PRESENTSTATUS_DISCHARGING);
132+
bitClear(iPresentStatus, PRESENTSTATUS_RTLEXPIRED);
27133
}
28134

29-
// return;
30-
31-
PowerDevice.sendByte(HID_PD_IPRODUCT, IPRODUCT);
32-
PowerDevice.sendByte(HID_PD_MANUFACTURER, IMANUFACTURER);
33-
PowerDevice.sendByte(HID_PD_SERIAL, ISERIAL);
135+
// Shutdown requested
136+
if(iDelayBe4ShutDown > 0 ) {
137+
bitSet(iPresentStatus, PRESENTSTATUS_SHUTDOWNREQ);
138+
Serial.println("shutdown requested");
139+
}
140+
else
141+
bitClear(iPresentStatus, PRESENTSTATUS_SHUTDOWNREQ);
142+
143+
// Shutdown imminent
144+
if((iPresentStatus & (1 << PRESENTSTATUS_SHUTDOWNREQ)) ||
145+
(iPresentStatus & (1 << PRESENTSTATUS_RTLEXPIRED))) {
146+
bitSet(iPresentStatus, PRESENTSTATUS_SHUTDOWNIMNT);
147+
Serial.println("shutdown imminent");
148+
}
149+
else
150+
bitClear(iPresentStatus, PRESENTSTATUS_SHUTDOWNIMNT);
151+
152+
34153

35-
PowerDevice.sendByte(HID_PD_RECHARGEABLE, 1); // should be 1 (Rechargable). Equivalent to "Battery Technology" in ACPI.
36-
PowerDevice.sendByte(HID_PD_CAPACITYMODE, 0);
37-
PowerDevice.sendInt32(HID_PD_FULLCHRGECAPACITY, 43200); //12 Ah (in Asec)
38-
PowerDevice.sendInt32(HID_PD_DESIGNCAPACITY, 43200); //12 Ah (in Asec)
39-
PowerDevice.sendInt16(HID_PD_CONFIGVOLTAGE, 13800); //13.8V
40-
PowerDevice.sendInt32(HID_PD_REMNCAPACITYLIMIT, 2160); //5% is set to threshold
41-
PowerDevice.sendByte(HID_PD_CPCTYGRANULARITY1, 100);
42-
PowerDevice.sendByte(HID_PD_CPCTYGRANULARITY2, 50);
43-
44-
byte bRemaining = 75;
154+
bitSet(iPresentStatus ,PRESENTSTATUS_BATTPRESENT);
155+
45156

46-
PowerDevice.sendByte(HID_PD_REMAININGCAPACITY, bRemaining);
47157

48-
int iPresentStatus = 0;
158+
//************ Delay ****************************************
159+
delay(1000);
160+
iIntTimer++;
161+
digitalWrite(5, HIGH); // turn the LED on (HIGH is the voltage level);
162+
delay(1000);
163+
iIntTimer++;
164+
digitalWrite(5, LOW); // turn the LED off;
165+
166+
//************ Check if we are still online ******************
167+
49168

50-
// Charging
51-
bool bCharging = digitalRead(4);
52-
if(bCharging) {
53-
bitSet(iPresentStatus,0);
54-
// Fully Charged
55-
if(bRemaining == 100) bitSet(iPresentStatus, 12);
56169

57-
}
58-
// Dischargig
59-
else {
60-
bitSet(iPresentStatus,1);
61-
// Fully Discharged
62-
if(bRemaining = 1) bitSet(iPresentStatus, 13);
63-
}
170+
//************ Bulk send or interrupt ***********************
64171

65-
// Need Replacement
66-
bitSet(iPresentStatus, 9);
67-
// Overload
68-
bitSet(iPresentStatus, 10);
172+
if((iPresentStatus != iPreviousStatus) || (iRemaining != iPrevRemaining) || (iRunTimeToEmpty != iPrevRunTimeToEmpty) || (iIntTimer>MINUPDATEINTERVAL) ) {
69173

174+
PowerDevice.sendReport(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
175+
if(bDischarging) PowerDevice.sendReport(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
176+
iRes = PowerDevice.sendReport(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
70177

71-
PowerDevice.sendInt16(HID_PD_PRESENTSTATUS, iPresentStatus);
72-
if(bCharging) {
73-
PowerDevice.sendInt16(HID_PD_CURRENT, 500);
74-
}
75-
else {
76-
PowerDevice.sendInt16(HID_PD_CURRENT, -500);
77-
PowerDevice.sendInt16(HID_PD_RUNTIMETOEMPTY, 3600);
178+
if(iRes <0 ) {
179+
digitalWrite(10, HIGH);
180+
}
181+
else
182+
digitalWrite(10, LOW);
183+
184+
iIntTimer = 0;
185+
iPreviousStatus = iPresentStatus;
186+
iPrevRemaining = iRemaining;
187+
iPrevRunTimeToEmpty = iRunTimeToEmpty;
78188
}
79-
80-
PowerDevice.sendInt16(HID_PD_VOLTAGE, 12000);
189+
190+
191+
Serial.println(iRemaining);
192+
Serial.println(iRunTimeToEmpty);
193+
Serial.println(iRes);
81194

82195
}

linux/98-upower-hid.rules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ATTRS{idVendor}=="2341", ENV{UPOWER_VENDOR}="Arduino"
2+
ATTRS{idVendor}=="2341", ATTRS{idProduct}=="8036", ENV{UPOWER_BATTERY_TYPE}="ups"
3+

src/HID/HID.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ int HID_::getInterface(uint8_t* interfaceCount)
3737
};
3838
return USB_SendControl(0, &hidInterface, sizeof(hidInterface));
3939
}
40-
// Send a USB descriptor string. The string is stored in PROGMEM as a
41-
// plain ASCII string but is sent out as UTF-16 with the correct 2-byte
42-
// prefix
40+
41+
// Since this function is not exposed in USBCore API, had to replicate here.
4342
static bool USB_SendStringDescriptor(const char* string_P, u8 string_len, uint8_t flags) {
4443

4544
u8 c[2] = {(u8)(2 + string_len * 2), 3};
@@ -63,6 +62,7 @@ int HID_::getDescriptor(USBSetup& setup)
6362

6463
u8 t = setup.wValueH;
6564

65+
// HID-specific strings
6666
if(USB_STRING_DESCRIPTOR_TYPE == t) {
6767

6868
// we place all strings in the 0xFF00-0xFFFE range

0 commit comments

Comments
 (0)