From 6cc2b2f3f55ef8a9c65c9b36d96143174a1118e6 Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sat, 11 Jan 2025 13:26:27 +0100 Subject: [PATCH 1/6] Change HIDPowerDevice_ to inherit from HID_ instead of aggregating it Preparation to better support multi-battery setups where a global HID singleton no longer makes sense. This also simplifies the HIDPowerDevice_ class by removing wrapper methods that just forwards the call to the underlying HID_ class. --- examples/UPS/UPS.ino | 46 +++++++++++++++++++++--------------------- src/HID/HID.cpp | 6 ------ src/HID/HID.h | 5 ----- src/HIDPowerDevice.cpp | 32 ++++++++--------------------- src/HIDPowerDevice.h | 11 +--------- 5 files changed, 32 insertions(+), 68 deletions(-) diff --git a/examples/UPS/UPS.ino b/examples/UPS/UPS.ino index e908b1b..9a02d43 100644 --- a/examples/UPS/UPS.ino +++ b/examples/UPS/UPS.ino @@ -65,36 +65,36 @@ void setup() { pinMode(COMMLOSTPIN, OUTPUT); // output is on once communication is lost with the host, otherwise off. - PowerDevice.setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus)); + 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_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.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_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)); + 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)); uint16_t year = 2024, month = 10, day = 12; iManufacturerDate = (year - 1980)*512 + month*32 + day; // from 4.2.6 Battery Settings in "Universal Serial Bus Usage Tables for HID Power Devices" - PowerDevice.setFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate)); + PowerDevice.SetFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate)); } void loop() { @@ -166,9 +166,9 @@ void loop() { 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)); + 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(COMMLOSTPIN, HIGH); diff --git a/src/HID/HID.cpp b/src/HID/HID.cpp index e9b969a..28b3555 100644 --- a/src/HID/HID.cpp +++ b/src/HID/HID.cpp @@ -21,12 +21,6 @@ #if defined(USBCON) -HID_& HID() -{ - static HID_ obj; - return obj; -} - int HID_::getInterface(uint8_t* interfaceCount) { *interfaceCount += 1; // uses 1 diff --git a/src/HID/HID.h b/src/HID/HID.h index bb55aa2..eb92f3d 100644 --- a/src/HID/HID.h +++ b/src/HID/HID.h @@ -157,11 +157,6 @@ class HID_ : public PluggableUSBModule }; -// Replacement for global singleton. -// This function prevents static-initialization-order-fiasco -// https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use -HID_& HID(); - #define D_HIDREPORT(length) { 9, 0x21, 0x01, 0x01, 0x21, 1, 0x22, lowByte(length), highByte(length) } #endif // USBCON diff --git a/src/HIDPowerDevice.cpp b/src/HIDPowerDevice.cpp index 5217e04..06909f2 100755 --- a/src/HIDPowerDevice.cpp +++ b/src/HIDPowerDevice.cpp @@ -224,51 +224,35 @@ static const uint8_t _hidReportDescriptor[] PROGMEM = { HIDPowerDevice_::HIDPowerDevice_(void) { static HIDSubDescriptor node(_hidReportDescriptor, sizeof (_hidReportDescriptor)); - HID().AppendDescriptor(&node); + AppendDescriptor(&node); } void HIDPowerDevice_::begin(void) { - HID().begin(); + HID_::begin(); // set string ID here - HID().SetFeature(HID_PD_IPRODUCT, &bProduct, sizeof(bProduct)); - HID().SetFeature(HID_PD_SERIAL, &bSerial, sizeof(bSerial)); - HID().SetFeature(HID_PD_MANUFACTURER, &bManufacturer, sizeof(bManufacturer)); + SetFeature(HID_PD_IPRODUCT, &bProduct, sizeof(bProduct)); + SetFeature(HID_PD_SERIAL, &bSerial, sizeof(bSerial)); + SetFeature(HID_PD_MANUFACTURER, &bManufacturer, sizeof(bManufacturer)); } -void HIDPowerDevice_::setOutput(Serial_& out) { - HID().setOutput(out); -} - -void HIDPowerDevice_::setSerial(const char* s) { - HID().setSerial(s); -} - void HIDPowerDevice_::end(void) { } int HIDPowerDevice_::sendDate(uint16_t id, uint16_t year, uint8_t month, uint8_t day) { uint16_t bval = (year - 1980)*512 + month * 32 + day; - return HID().SendReport(id, &bval, sizeof (bval)); -} - -int HIDPowerDevice_::sendReport(uint16_t id, const void* bval, int len) { - return HID().SendReport(id, bval, len); -} - -int HIDPowerDevice_::setFeature(uint16_t id, const void *data, int len) { - return HID().SetFeature(id, data, len); + return SendReport(id, &bval, sizeof (bval)); } int HIDPowerDevice_::setStringFeature(uint8_t id, const uint8_t* index, const char* data) { - int res = HID().SetFeature(id, index, 1); + int res = SetFeature(id, index, 1); if(res == 0) return 0; - res += HID().SetFeature(0xFF00 | *index , data, strlen_P(data)); + res += SetFeature(0xFF00 | *index , data, strlen_P(data)); return res; } diff --git a/src/HIDPowerDevice.h b/src/HIDPowerDevice.h index 888b860..b628e1a 100755 --- a/src/HIDPowerDevice.h +++ b/src/HIDPowerDevice.h @@ -102,7 +102,7 @@ static_assert(sizeof(PresentStatus) == sizeof(uint16_t)); -class HIDPowerDevice_ { +class HIDPowerDevice_ : public HID_ { private: @@ -114,20 +114,11 @@ class HIDPowerDevice_ { HIDPowerDevice_(void); void begin(void); - void setOutput(Serial_&); - - void setSerial(const char*); - - void end(void); int sendDate(uint16_t id, uint16_t year, uint8_t month, uint8_t day); - int sendReport(uint16_t id, const void* bval, int len); - - int setFeature(uint16_t id, const void* data, int len); int setStringFeature(uint8_t id, const uint8_t* index, const char* data); - }; extern HIDPowerDevice_ PowerDevice; From 070521cf0ce3ed4dfa8109166bdc09c2ff3a839f Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sat, 11 Jan 2025 11:52:16 +0100 Subject: [PATCH 2/6] Remove unused OUTPUT endpoint to simplify The HIDPowerDevice HID report descriptor only contains INPUT and FEATURE reports, and no OUTPUT reports. It therefore makes little sense to configure a USB input endpoint for INPUT reports. This also makes the implementation more similar to the upstream Arduino code on https://github.com/arduino/ArduinoCore-avr/blob/master/libraries/HID/src/HID.cpp --- src/HID/HID.cpp | 8 +++----- src/HID/HID.h | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/HID/HID.cpp b/src/HID/HID.cpp index e9b969a..2546e68 100644 --- a/src/HID/HID.cpp +++ b/src/HID/HID.cpp @@ -31,10 +31,9 @@ int HID_::getInterface(uint8_t* interfaceCount) { *interfaceCount += 1; // uses 1 HIDDescriptor hidInterface = { - D_INTERFACE(pluggedInterface, 2, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), + D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), D_HIDREPORT(descriptorSize), - D_ENDPOINT(USB_ENDPOINT_IN(HID_TX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x14), - D_ENDPOINT(USB_ENDPOINT_OUT(HID_RX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x0A) + D_ENDPOINT(USB_ENDPOINT_IN(HID_TX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x14) }; return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); } @@ -254,12 +253,11 @@ bool HID_::setup(USBSetup& setup) return false; } -HID_::HID_(void) : PluggableUSBModule(2, 1, epType), +HID_::HID_(void) : PluggableUSBModule(1, 1, epType), rootNode(NULL), descriptorSize(0), protocol(HID_REPORT_PROTOCOL), idle(1) { epType[0] = EP_TYPE_INTERRUPT_IN; - epType[1] = EP_TYPE_INTERRUPT_OUT; PluggableUSB().plug(this); } diff --git a/src/HID/HID.h b/src/HID/HID.h index bb55aa2..8af135f 100644 --- a/src/HID/HID.h +++ b/src/HID/HID.h @@ -87,7 +87,6 @@ typedef struct InterfaceDescriptor hid; HIDDescDescriptor desc; EndpointDescriptor in; - EndpointDescriptor out; //added } HIDDescriptor; class HIDReport { @@ -139,7 +138,7 @@ class HID_ : public PluggableUSBModule uint8_t getShortName(char* name) override; private: - uint8_t epType[2]; + uint8_t epType[1]; HIDSubDescriptor* rootNode; uint16_t descriptorSize; From 9febd9f43e7195b74414b5b71bb13c48c69f1639 Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sat, 11 Jan 2025 11:47:53 +0100 Subject: [PATCH 3/6] HID: Replace manual defines with pluggedEndpoint member Adopt coding style from https://github.com/arduino/ArduinoCore-avr/blob/master/libraries/HID/src/HID.cpp that uses "pluggedEndpoint" from the PluggableUSBModule base-class as endpoint number instead of a hardcoded preprocessor define. This allows removal of several defines in the header that are now unused. Benefits: * Closer alignment to the upstream Arduino sources. * Fewer hardcoded values, which will simplify future scaling to support multiple batteries in a USB composite device. --- src/HID/HID.cpp | 6 +++--- src/HID/HID.h | 8 -------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/HID/HID.cpp b/src/HID/HID.cpp index 2546e68..4eae49a 100644 --- a/src/HID/HID.cpp +++ b/src/HID/HID.cpp @@ -33,7 +33,7 @@ int HID_::getInterface(uint8_t* interfaceCount) HIDDescriptor hidInterface = { D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), D_HIDREPORT(descriptorSize), - D_ENDPOINT(USB_ENDPOINT_IN(HID_TX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x14) + D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x14) }; return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); } @@ -167,9 +167,9 @@ bool HID_::LockFeature(uint16_t id, bool lock) { int HID_::SendReport(uint16_t id, const void* data, int len) { - auto ret = USB_Send(HID_TX, &id, 1); + auto ret = USB_Send(pluggedEndpoint, &id, 1); if (ret < 0) return ret; - auto ret2 = USB_Send(HID_TX | TRANSFER_RELEASE, data, len); + auto ret2 = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, len); if (ret2 < 0) return ret2; return ret + ret2; } diff --git a/src/HID/HID.h b/src/HID/HID.h index 8af135f..ac3e53f 100644 --- a/src/HID/HID.h +++ b/src/HID/HID.h @@ -61,14 +61,6 @@ #define HID_REPORT_TYPE_OUTPUT 2 #define HID_REPORT_TYPE_FEATURE 3 -#define HID_INTERFACE (CDC_ACM_INTERFACE + CDC_INTERFACE_COUNT) // HID Interface -#define HID_FIRST_ENDPOINT (CDC_FIRST_ENDPOINT + CDC_ENPOINT_COUNT) -#define HID_ENDPOINT_INT (HID_FIRST_ENDPOINT) -#define HID_ENDPOINT_OUT (HID_FIRST_ENDPOINT+1) - -#define HID_TX HID_ENDPOINT_INT -#define HID_RX HID_ENDPOINT_OUT //++ EP HID_RX for ease of use with USB_Available & USB_Rec - typedef struct { uint8_t len; // 9 From 905999636abb090d30047eae5abf21b00c7b0d26 Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sun, 12 Jan 2025 18:31:49 +0100 Subject: [PATCH 4/6] TEST: Demonstrate how to expose multiple batteries All batteries are configured with the same parameters, so this is by no means a complete example. --- examples/UPS/UPS.ino | 69 ++++++++++++++++++++++-------------------- src/HIDPowerDevice.cpp | 2 +- src/HIDPowerDevice.h | 4 ++- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/examples/UPS/UPS.ino b/examples/UPS/UPS.ino index 9a02d43..42faf01 100644 --- a/examples/UPS/UPS.ino +++ b/examples/UPS/UPS.ino @@ -51,50 +51,53 @@ int iRes=0; void setup() { Serial.begin(57600); - - PowerDevice.begin(); + + for (int i = 0; i < BATTERY_COUNT; i++) { + PowerDevice[i].begin(); - // Serial No is set in a special way as it forms Arduino port name - PowerDevice.setSerial(STRING_SERIAL); + // Serial No is set in a special way as it forms Arduino port name + PowerDevice[i].setSerial(STRING_SERIAL); - // Used for debugging purposes. - PowerDevice.setOutput(Serial); + // Used for debugging purposes. + PowerDevice[i].setOutput(Serial); + } pinMode(CHGDCHPIN, INPUT_PULLUP); // ground this pin to simulate power failure. pinMode(RUNSTATUSPIN, OUTPUT); // output flushing 1 sec indicating that the arduino cycle is running. pinMode(COMMLOSTPIN, OUTPUT); // output is on once communication is lost with the host, otherwise off. - - PowerDevice.SetFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus)); + for (int i = 0; i < BATTERY_COUNT; i++) { + PowerDevice[i].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[i].SetFeature(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty)); + PowerDevice[i].SetFeature(HID_PD_AVERAGETIME2FULL, &iAvgTimeToFull, sizeof(iAvgTimeToFull)); + PowerDevice[i].SetFeature(HID_PD_AVERAGETIME2EMPTY, &iAvgTimeToEmpty, sizeof(iAvgTimeToEmpty)); + PowerDevice[i].SetFeature(HID_PD_REMAINTIMELIMIT, &iRemainTimeLimit, sizeof(iRemainTimeLimit)); + PowerDevice[i].SetFeature(HID_PD_DELAYBE4REBOOT, &iDelayBe4Reboot, sizeof(iDelayBe4Reboot)); + PowerDevice[i].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[i].SetFeature(HID_PD_RECHARGEABLE, &bRechargable, sizeof(bRechargable)); + PowerDevice[i].SetFeature(HID_PD_CAPACITYMODE, &bCapacityMode, sizeof(bCapacityMode)); + PowerDevice[i].SetFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage)); + PowerDevice[i].SetFeature(HID_PD_VOLTAGE, &iVoltage, sizeof(iVoltage)); - PowerDevice.setStringFeature(HID_PD_IDEVICECHEMISTRY, &bDeviceChemistry, STRING_DEVICECHEMISTRY); - PowerDevice.setStringFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, STRING_OEMVENDOR); + PowerDevice[i].setStringFeature(HID_PD_IDEVICECHEMISTRY, &bDeviceChemistry, STRING_DEVICECHEMISTRY); + PowerDevice[i].setStringFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, STRING_OEMVENDOR); - PowerDevice.SetFeature(HID_PD_AUDIBLEALARMCTRL, &iAudibleAlarmCtrl, sizeof(iAudibleAlarmCtrl)); + PowerDevice[i].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)); + PowerDevice[i].SetFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity)); + PowerDevice[i].SetFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity)); + PowerDevice[i].SetFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining)); + PowerDevice[i].SetFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit)); + PowerDevice[i].SetFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit)); + PowerDevice[i].SetFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1)); + PowerDevice[i].SetFeature(HID_PD_CPCTYGRANULARITY2, &bCapacityGranularity2, sizeof(bCapacityGranularity2)); uint16_t year = 2024, month = 10, day = 12; iManufacturerDate = (year - 1980)*512 + month*32 + day; // from 4.2.6 Battery Settings in "Universal Serial Bus Usage Tables for HID Power Devices" - PowerDevice.SetFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate)); + PowerDevice[i].SetFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate)); + } } void loop() { @@ -166,9 +169,11 @@ void loop() { 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)); + for (int i = 0; i < BATTERY_COUNT; i++) { + PowerDevice[i].SendReport(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining)); + if(bDischarging) PowerDevice[i].SendReport(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty)); + iRes = PowerDevice[i].SendReport(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus)); + } if(iRes <0 ) { digitalWrite(COMMLOSTPIN, HIGH); diff --git a/src/HIDPowerDevice.cpp b/src/HIDPowerDevice.cpp index 06909f2..d49ffa5 100755 --- a/src/HIDPowerDevice.cpp +++ b/src/HIDPowerDevice.cpp @@ -257,7 +257,7 @@ int HIDPowerDevice_::setStringFeature(uint8_t id, const uint8_t* index, const ch return res; } -HIDPowerDevice_ PowerDevice; +HIDPowerDevice_ PowerDevice[BATTERY_COUNT]; #endif diff --git a/src/HIDPowerDevice.h b/src/HIDPowerDevice.h index b628e1a..ef27c27 100755 --- a/src/HIDPowerDevice.h +++ b/src/HIDPowerDevice.h @@ -121,8 +121,10 @@ class HIDPowerDevice_ : public HID_ { int setStringFeature(uint8_t id, const uint8_t* index, const char* data); }; -extern HIDPowerDevice_ PowerDevice; +// as many batteries as supported by the HW +#define BATTERY_COUNT (USB_ENDPOINTS - CDC_FIRST_ENDPOINT - CDC_ENPOINT_COUNT) // 3 by default; 6 if defining CDC_DISABLED +extern HIDPowerDevice_ PowerDevice[BATTERY_COUNT]; #endif #endif From 224971af4cb42a90d6129adfb829c55f7c63bba0 Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sun, 19 Jan 2025 17:23:39 +0100 Subject: [PATCH 5/6] Make sample a bit more interesting with slightly different charge levels in each battery. Also, change bCapacityMode from 2(%) to 1(mWh) so that Windows reports each battery charge level correctly. --- examples/UPS/UPS.ino | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/UPS/UPS.ino b/examples/UPS/UPS.ino index 42faf01..12c739e 100644 --- a/examples/UPS/UPS.ino +++ b/examples/UPS/UPS.ino @@ -20,7 +20,7 @@ const byte bOEMVendor = IOEMVENDOR; PresentStatus iPresentStatus = {}, iPreviousStatus = {}; byte bRechargable = 1; -byte bCapacityMode = 2; // units are in %% +byte bCapacityMode = 1; // units are in mWh // Physical parameters const uint16_t iConfigVoltage = 1380; @@ -43,7 +43,7 @@ const byte bCapacityGranularity1 = 1; const byte bCapacityGranularity2 = 1; byte iFullChargeCapacity = 100; -byte iRemaining =0, iPrevRemaining=0; +byte iRemaining[BATTERY_COUNT] = {48, 50, 51, 49, 50, 52}, iPrevRemaining=0; int iRes=0; @@ -88,7 +88,7 @@ void setup() { PowerDevice[i].SetFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity)); PowerDevice[i].SetFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity)); - PowerDevice[i].SetFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining)); + PowerDevice[i].SetFeature(HID_PD_REMAININGCAPACITY, &iRemaining[i], sizeof(iRemaining[i])); PowerDevice[i].SetFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit)); PowerDevice[i].SetFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit)); PowerDevice[i].SetFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1)); @@ -104,18 +104,18 @@ void loop() { //*********** Measurements Unit **************************** - bool bCharging = digitalRead(CHGDCHPIN); + bool bCharging = false; digitalRead(CHGDCHPIN); bool bACPresent = bCharging; // TODO - replace with sensor bool bDischarging = !bCharging; // TODO - replace with sensor - int iBattSoc = analogRead(BATTSOCPIN); // TODO - this is for debug only. Replace with charge estimation + int iBattSoc = 400; //analogRead(BATTSOCPIN); // TODO - this is for debug only. Replace with charge estimation - iRemaining = (byte)(round((float)iFullChargeCapacity*iBattSoc/1024)); - iRunTimeToEmpty = (uint16_t)round((float)iAvgTimeToEmpty*iRemaining/iFullChargeCapacity); + //iRemaining = (byte)(round((float)iFullChargeCapacity*iBattSoc/1024)); + iRunTimeToEmpty = (uint16_t)round((float)iAvgTimeToEmpty*iRemaining[0]/iFullChargeCapacity); // Charging iPresentStatus.Charging = bCharging; iPresentStatus.ACPresent = bACPresent; - iPresentStatus.FullyCharged = (iRemaining == iFullChargeCapacity); + iPresentStatus.FullyCharged = (iRemaining[0] == iFullChargeCapacity); // Discharging if(bDischarging) { @@ -167,10 +167,10 @@ void loop() { //************ Bulk send or interrupt *********************** - if((iPresentStatus != iPreviousStatus) || (iRemaining != iPrevRemaining) || (iRunTimeToEmpty != iPrevRunTimeToEmpty) || (iIntTimer>MINUPDATEINTERVAL) ) { + if((iPresentStatus != iPreviousStatus) || (iRemaining[0] != iPrevRemaining) || (iRunTimeToEmpty != iPrevRunTimeToEmpty) || (iIntTimer>MINUPDATEINTERVAL) ) { for (int i = 0; i < BATTERY_COUNT; i++) { - PowerDevice[i].SendReport(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining)); + PowerDevice[i].SendReport(HID_PD_REMAININGCAPACITY, &iRemaining[0], sizeof(iRemaining[0])); if(bDischarging) PowerDevice[i].SendReport(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty)); iRes = PowerDevice[i].SendReport(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus)); } @@ -183,7 +183,7 @@ void loop() { iIntTimer = 0; iPreviousStatus = iPresentStatus; - iPrevRemaining = iRemaining; + iPrevRemaining = iRemaining[0]; iPrevRunTimeToEmpty = iRunTimeToEmpty; } From 5f22cb6e8e06258d7ecd77b00714add74c812bf9 Mon Sep 17 00:00:00 2001 From: Fredrik Orderud Date: Sun, 19 Jan 2025 17:25:11 +0100 Subject: [PATCH 6/6] Disable all Serial communication to make the build pass also if defining CDC_DISABLED. --- examples/UPS/UPS.ino | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/examples/UPS/UPS.ino b/examples/UPS/UPS.ino index 12c739e..bd8b8d1 100644 --- a/examples/UPS/UPS.ino +++ b/examples/UPS/UPS.ino @@ -49,17 +49,12 @@ int iRes=0; void setup() { - - Serial.begin(57600); for (int i = 0; i < BATTERY_COUNT; i++) { PowerDevice[i].begin(); // Serial No is set in a special way as it forms Arduino port name PowerDevice[i].setSerial(STRING_SERIAL); - - // Used for debugging purposes. - PowerDevice[i].setOutput(Serial); } pinMode(CHGDCHPIN, INPUT_PULLUP); // ground this pin to simulate power failure. @@ -133,7 +128,6 @@ void loop() { // Shutdown requested if(iDelayBe4ShutDown > 0 ) { iPresentStatus.ShutdownRequested = 1; - Serial.println("shutdown requested"); } else iPresentStatus.ShutdownRequested = 0; @@ -142,7 +136,6 @@ void loop() { if((iPresentStatus.ShutdownRequested) || (iPresentStatus.RemainingTimeLimitExpired)) { iPresentStatus.ShutdownImminent = 1; - Serial.println("shutdown imminent"); } else iPresentStatus.ShutdownImminent = 0; @@ -187,9 +180,4 @@ void loop() { iPrevRunTimeToEmpty = iRunTimeToEmpty; } - - Serial.println(iRemaining); - Serial.println(iRunTimeToEmpty); - Serial.println(iRes); - }