This is a simple utility to track UPS status.
This commit is contained in:
@@ -35,6 +35,7 @@ endif ()
|
||||
# Dependencies
|
||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
|
||||
include (IconUtils)
|
||||
include (CTest)
|
||||
|
||||
find_package (PkgConfig REQUIRED)
|
||||
pkg_check_modules (libusb libusb-1.0)
|
||||
@@ -95,6 +96,20 @@ if (WITH_HIDAPI)
|
||||
target_include_directories (eizoctl PUBLIC ${hidapi_INCLUDE_DIRS})
|
||||
target_link_directories (eizoctl PUBLIC ${hidapi_LIBRARY_DIRS})
|
||||
target_link_libraries (eizoctl ${hidapi_LIBRARIES})
|
||||
|
||||
list (APPEND targets upscat)
|
||||
add_executable (upscat upscat.c)
|
||||
target_include_directories (upscat PUBLIC ${hidapi_INCLUDE_DIRS})
|
||||
target_link_directories (upscat PUBLIC ${hidapi_LIBRARY_DIRS})
|
||||
target_link_libraries (upscat ${hidapi_LIBRARIES})
|
||||
endif ()
|
||||
if (WITH_HIDAPI AND BUILD_TESTING)
|
||||
add_executable (test-upscat $<TARGET_PROPERTY:upscat,SOURCES>)
|
||||
set_target_properties (test-upscat PROPERTIES COMPILE_DEFINITIONS TESTING)
|
||||
target_link_libraries (test-upscat $<TARGET_PROPERTY:upscat,LINK_LIBRARIES>)
|
||||
add_test (NAME test-upscat
|
||||
COMMAND test-upscat upscat-eaton3p.bin upscat-cyberpower.bin
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
|
||||
endif ()
|
||||
if (WITH_HIDAPI AND WIN32)
|
||||
list (APPEND targets_gui eizoctltray)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2013, 2024 - 2025, Přemysl Eric Janouch <p@janouch.name>
|
||||
Copyright (c) 2013, 2024 - 2026, Přemysl Eric Janouch <p@janouch.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
Unreleased
|
||||
|
||||
* Added upscat for streaming status from UPS devices
|
||||
|
||||
|
||||
1.2.0 (2026-01-04)
|
||||
|
||||
* eizoctl: added a --quiet option to suppress information and/or errors
|
||||
|
||||
+22
@@ -102,6 +102,28 @@ Make sure to let the Windows Razer Synapse tool upgrade the firmware to the
|
||||
newest version before running the program. There might be some issues otherwise
|
||||
due to protocol changes, although I don't really deem it very probable.
|
||||
|
||||
upscat
|
||||
~~~~~~
|
||||
_upscat_ streams the basic status of UPS devices using the USB HID protocol,
|
||||
in CSV format. You'll get the power status, remaining capacity, run time,
|
||||
and load percentage. It's been verified with Eaton 3S/3P series and CyberPower
|
||||
BR1200ELCD, both working quite differently.
|
||||
|
||||
This program is meant to be a simple replacement for NUT:
|
||||
|
||||
```sh
|
||||
upscat | awk -F, '
|
||||
BEGIN {
|
||||
getline
|
||||
for (i = 1; i <= NF; i++) csv[$i] = i
|
||||
}
|
||||
!$csv["AC Present"] && $csv["Remaining Capacity"] < 50 {
|
||||
print "On battery with " $csv["Remaining Capacity"] "% left, suspending"
|
||||
system("systemctl suspend")
|
||||
exit
|
||||
}'
|
||||
```
|
||||
|
||||
Packages
|
||||
--------
|
||||
Regular releases are sporadic. git master should be stable enough.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/bin/sh -e
|
||||
# upscat-gen-usb.sh: convert tables from the USB HID specification to C code
|
||||
#
|
||||
# Copyright (c) 2026, Přemysl Eric Janouch <p@janouch.name>
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
if [ ! -e hut1_7.pdf ]
|
||||
then curl -LO https://www.usb.org/sites/default/files/hut1_7.pdf
|
||||
fi
|
||||
|
||||
# Our program doesn't need all of them, so we're filtering them here.
|
||||
process() (qpdf --show-attachment=HidUsageTables.json hut1_7.pdf \
|
||||
| jq -r '.UsagePages[] | select(.Name | IN("Battery System", "Power")) |
|
||||
'"$1"' | @tsv' | sort -n -k 1,1 -k 2,2 | awk -F'\t' '
|
||||
function ident(s) { gsub(/[^[:alnum:]]/, "_", s); return s } '"{ $2 }")
|
||||
|
||||
pages='[.Id, .Name]'
|
||||
usages='. as $page | $page.UsageIds[] |
|
||||
[$page.Id, .Id, $page.Name, (.Name | gsub("\\\\_"; "_")),
|
||||
(.Kinds | join("/"))]'
|
||||
generated='. as $page | .UsageIdGenerator | select(.) |
|
||||
[$page.Id, .StartUsageId, .EndUsageId, $page.Name, .NamePrefix,
|
||||
(.Kinds | join("/"))]'
|
||||
|
||||
cat <<EOF >upscat-usb.h
|
||||
// Code generated by $0. DO NOT EDIT.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum {
|
||||
USB_HID_COLLECTION_PHYSICAL = 0x00,
|
||||
USB_HID_COLLECTION_APPLICATION = 0x01,
|
||||
USB_HID_COLLECTION_LOGICAL = 0x02,
|
||||
USB_HID_COLLECTION_REPORT = 0x03,
|
||||
USB_HID_COLLECTION_NAMED_ARRAY = 0x04,
|
||||
USB_HID_COLLECTION_USAGE_SWITCH = 0x05,
|
||||
USB_HID_COLLECTION_USAGE_MODIFIER = 0x06,
|
||||
};
|
||||
|
||||
enum {
|
||||
$(process "$pages" '
|
||||
printf("\t/// %s\n", $2)
|
||||
printf("\tUSB_HID_USAGE_PAGE__%s = 0x%04x,\n", toupper(ident($2)), $1)
|
||||
')
|
||||
};
|
||||
|
||||
static const struct usb_hid_usage_page_info {
|
||||
uint16_t usage_page;
|
||||
const char *name;
|
||||
} usb_hid_usage_pages[] = {
|
||||
$(process "$pages" 'printf("\t{0x%04x, \"%s\"},\n", $1, $2)')
|
||||
};
|
||||
|
||||
$(process "$usages" '
|
||||
printf("/// %s: %s (%s)\n", $3, $4, $5)
|
||||
printf("#define USB_HID_USAGE__%s__%s 0x%04x%04x\n",
|
||||
toupper(ident($3)), toupper(ident($4)), $1, $2)
|
||||
')
|
||||
|
||||
static const struct usb_hid_usage_info {
|
||||
uint32_t usage;
|
||||
const char *name;
|
||||
} usb_hid_usages[] = {
|
||||
$(process "$usages" 'printf("\t{0x%04x%04x, \"%s\"},\n", $1, $2, $4)')
|
||||
};
|
||||
|
||||
static const struct usb_hid_usage_range_info {
|
||||
uint32_t usage_start;
|
||||
uint32_t usage_end;
|
||||
const char *name_prefix;
|
||||
} usb_hid_usages_ranged[] = {
|
||||
$(process "$generated" '
|
||||
printf("\t// %s (%s)\n", $4, $6)
|
||||
printf("\t{0x%04x%04x, 0x%04x%04x, \"%s\"},\n", $1, $2, $1, $3, $5)
|
||||
')
|
||||
};
|
||||
|
||||
static int
|
||||
usb_hid_usage_pages_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint16_t *key = a;
|
||||
const struct usb_hid_usage_page_info *entry = b;
|
||||
return (*key > entry->usage_page) - (*key < entry->usage_page);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_hid_usages_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint32_t *key = a;
|
||||
const struct usb_hid_usage_info *entry = b;
|
||||
return (*key > entry->usage) - (*key < entry->usage);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_hid_usages_ranged_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint32_t *key = a;
|
||||
const struct usb_hid_usage_range_info *entry = b;
|
||||
return (*key > entry->usage_end) - (*key < entry->usage_start);
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_page_to_string(uint16_t usage_page)
|
||||
{
|
||||
const struct usb_hid_usage_page_info *entry = bsearch(
|
||||
&usage_page, usb_hid_usage_pages,
|
||||
sizeof usb_hid_usage_pages / sizeof usb_hid_usage_pages[0],
|
||||
sizeof usb_hid_usage_pages[0], usb_hid_usage_pages_cmp);
|
||||
if (entry)
|
||||
return entry->name;
|
||||
|
||||
static char buffer[128];
|
||||
snprintf(buffer, sizeof buffer, "%04x", usage_page);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_to_string(uint32_t usage)
|
||||
{
|
||||
static char buffer[128];
|
||||
|
||||
const struct usb_hid_usage_info *info = bsearch(
|
||||
&usage, usb_hid_usages,
|
||||
sizeof usb_hid_usages / sizeof usb_hid_usages[0],
|
||||
sizeof usb_hid_usages[0], usb_hid_usages_cmp);
|
||||
if (info)
|
||||
return info->name;
|
||||
|
||||
const struct usb_hid_usage_range_info *range = bsearch(
|
||||
&usage, usb_hid_usages_ranged,
|
||||
sizeof usb_hid_usages_ranged / sizeof usb_hid_usages_ranged[0],
|
||||
sizeof usb_hid_usages_ranged[0], usb_hid_usages_ranged_cmp);
|
||||
if (range) {
|
||||
snprintf(buffer, sizeof buffer,
|
||||
"%s %u", range->name_prefix, (unsigned) (usage & 0xffff));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof buffer, "%04x", (unsigned) (usage & 0xffff));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_to_string_full(uint32_t usage)
|
||||
{
|
||||
static char buffer[128];
|
||||
snprintf(buffer, sizeof buffer, "%s: %s",
|
||||
usb_hid_usage_page_to_string(usage >> 16),
|
||||
usb_hid_usage_to_string(usage));
|
||||
return buffer;
|
||||
}
|
||||
EOF
|
||||
+624
@@ -0,0 +1,624 @@
|
||||
// Code generated by ./upscat-gen-usb.sh. DO NOT EDIT.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum {
|
||||
USB_HID_COLLECTION_PHYSICAL = 0x00,
|
||||
USB_HID_COLLECTION_APPLICATION = 0x01,
|
||||
USB_HID_COLLECTION_LOGICAL = 0x02,
|
||||
USB_HID_COLLECTION_REPORT = 0x03,
|
||||
USB_HID_COLLECTION_NAMED_ARRAY = 0x04,
|
||||
USB_HID_COLLECTION_USAGE_SWITCH = 0x05,
|
||||
USB_HID_COLLECTION_USAGE_MODIFIER = 0x06,
|
||||
};
|
||||
|
||||
enum {
|
||||
/// Power
|
||||
USB_HID_USAGE_PAGE__POWER = 0x0084,
|
||||
/// Battery System
|
||||
USB_HID_USAGE_PAGE__BATTERY_SYSTEM = 0x0085,
|
||||
};
|
||||
|
||||
static const struct usb_hid_usage_page_info {
|
||||
uint16_t usage_page;
|
||||
const char *name;
|
||||
} usb_hid_usage_pages[] = {
|
||||
{0x0084, "Power"},
|
||||
{0x0085, "Battery System"},
|
||||
};
|
||||
|
||||
/// Power: iName (SV)
|
||||
#define USB_HID_USAGE__POWER__INAME 0x00840001
|
||||
/// Power: Present Status (CL)
|
||||
#define USB_HID_USAGE__POWER__PRESENT_STATUS 0x00840002
|
||||
/// Power: Changed Status (CL)
|
||||
#define USB_HID_USAGE__POWER__CHANGED_STATUS 0x00840003
|
||||
/// Power: UPS (CA)
|
||||
#define USB_HID_USAGE__POWER__UPS 0x00840004
|
||||
/// Power: Power Supply (CA)
|
||||
#define USB_HID_USAGE__POWER__POWER_SUPPLY 0x00840005
|
||||
/// Power: Battery System (CP)
|
||||
#define USB_HID_USAGE__POWER__BATTERY_SYSTEM 0x00840010
|
||||
/// Power: Battery System Id (SV)
|
||||
#define USB_HID_USAGE__POWER__BATTERY_SYSTEM_ID 0x00840011
|
||||
/// Power: Battery (CP)
|
||||
#define USB_HID_USAGE__POWER__BATTERY 0x00840012
|
||||
/// Power: Battery Id (SV)
|
||||
#define USB_HID_USAGE__POWER__BATTERY_ID 0x00840013
|
||||
/// Power: Charger (CP)
|
||||
#define USB_HID_USAGE__POWER__CHARGER 0x00840014
|
||||
/// Power: Charger Id (SV)
|
||||
#define USB_HID_USAGE__POWER__CHARGER_ID 0x00840015
|
||||
/// Power: Power Converter (CP)
|
||||
#define USB_HID_USAGE__POWER__POWER_CONVERTER 0x00840016
|
||||
/// Power: Power Converter Id (SV)
|
||||
#define USB_HID_USAGE__POWER__POWER_CONVERTER_ID 0x00840017
|
||||
/// Power: Outlet System (CP)
|
||||
#define USB_HID_USAGE__POWER__OUTLET_SYSTEM 0x00840018
|
||||
/// Power: Outlet System Id (SV)
|
||||
#define USB_HID_USAGE__POWER__OUTLET_SYSTEM_ID 0x00840019
|
||||
/// Power: Input (CP)
|
||||
#define USB_HID_USAGE__POWER__INPUT 0x0084001a
|
||||
/// Power: Input Id (SV)
|
||||
#define USB_HID_USAGE__POWER__INPUT_ID 0x0084001b
|
||||
/// Power: Output (CP)
|
||||
#define USB_HID_USAGE__POWER__OUTPUT 0x0084001c
|
||||
/// Power: Output Id (SV)
|
||||
#define USB_HID_USAGE__POWER__OUTPUT_ID 0x0084001d
|
||||
/// Power: Flow (CP)
|
||||
#define USB_HID_USAGE__POWER__FLOW 0x0084001e
|
||||
/// Power: Flow Id (SV)
|
||||
#define USB_HID_USAGE__POWER__FLOW_ID 0x0084001f
|
||||
/// Power: Outlet (CP)
|
||||
#define USB_HID_USAGE__POWER__OUTLET 0x00840020
|
||||
/// Power: Outlet Id (SV)
|
||||
#define USB_HID_USAGE__POWER__OUTLET_ID 0x00840021
|
||||
/// Power: Gang (CL/CP)
|
||||
#define USB_HID_USAGE__POWER__GANG 0x00840022
|
||||
/// Power: Gang Id (SV)
|
||||
#define USB_HID_USAGE__POWER__GANG_ID 0x00840023
|
||||
/// Power: Power Summary (CL/CP)
|
||||
#define USB_HID_USAGE__POWER__POWER_SUMMARY 0x00840024
|
||||
/// Power: Power Summary Id (SV)
|
||||
#define USB_HID_USAGE__POWER__POWER_SUMMARY_ID 0x00840025
|
||||
/// Power: Voltage (DV)
|
||||
#define USB_HID_USAGE__POWER__VOLTAGE 0x00840030
|
||||
/// Power: Current (DV)
|
||||
#define USB_HID_USAGE__POWER__CURRENT 0x00840031
|
||||
/// Power: Frequency (DV)
|
||||
#define USB_HID_USAGE__POWER__FREQUENCY 0x00840032
|
||||
/// Power: Apparent Power (DV)
|
||||
#define USB_HID_USAGE__POWER__APPARENT_POWER 0x00840033
|
||||
/// Power: Active Power (DV)
|
||||
#define USB_HID_USAGE__POWER__ACTIVE_POWER 0x00840034
|
||||
/// Power: Percent Load (DV)
|
||||
#define USB_HID_USAGE__POWER__PERCENT_LOAD 0x00840035
|
||||
/// Power: Temperature (DV)
|
||||
#define USB_HID_USAGE__POWER__TEMPERATURE 0x00840036
|
||||
/// Power: Humidity (DV)
|
||||
#define USB_HID_USAGE__POWER__HUMIDITY 0x00840037
|
||||
/// Power: Bad Count (DV)
|
||||
#define USB_HID_USAGE__POWER__BAD_COUNT 0x00840038
|
||||
/// Power: Config Voltage (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_VOLTAGE 0x00840040
|
||||
/// Power: Config Current (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_CURRENT 0x00840041
|
||||
/// Power: Config Frequency (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_FREQUENCY 0x00840042
|
||||
/// Power: Config Apparent Power (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_APPARENT_POWER 0x00840043
|
||||
/// Power: Config Active Power (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_ACTIVE_POWER 0x00840044
|
||||
/// Power: Config Percent Load (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_PERCENT_LOAD 0x00840045
|
||||
/// Power: Config Temperature (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_TEMPERATURE 0x00840046
|
||||
/// Power: Config Humidity (SV/DV)
|
||||
#define USB_HID_USAGE__POWER__CONFIG_HUMIDITY 0x00840047
|
||||
/// Power: Switch On Control (DV)
|
||||
#define USB_HID_USAGE__POWER__SWITCH_ON_CONTROL 0x00840050
|
||||
/// Power: Switch Off Control (DV)
|
||||
#define USB_HID_USAGE__POWER__SWITCH_OFF_CONTROL 0x00840051
|
||||
/// Power: Toggle Control (DV)
|
||||
#define USB_HID_USAGE__POWER__TOGGLE_CONTROL 0x00840052
|
||||
/// Power: Low Voltage Transfer (DV)
|
||||
#define USB_HID_USAGE__POWER__LOW_VOLTAGE_TRANSFER 0x00840053
|
||||
/// Power: High Voltage Transfer (DV)
|
||||
#define USB_HID_USAGE__POWER__HIGH_VOLTAGE_TRANSFER 0x00840054
|
||||
/// Power: Delay Before Reboot (DV)
|
||||
#define USB_HID_USAGE__POWER__DELAY_BEFORE_REBOOT 0x00840055
|
||||
/// Power: Delay Before Startup (DV)
|
||||
#define USB_HID_USAGE__POWER__DELAY_BEFORE_STARTUP 0x00840056
|
||||
/// Power: Delay Before Shutdown (DV)
|
||||
#define USB_HID_USAGE__POWER__DELAY_BEFORE_SHUTDOWN 0x00840057
|
||||
/// Power: Test (DV)
|
||||
#define USB_HID_USAGE__POWER__TEST 0x00840058
|
||||
/// Power: Module Reset (DV)
|
||||
#define USB_HID_USAGE__POWER__MODULE_RESET 0x00840059
|
||||
/// Power: Audible Alarm Control (DV)
|
||||
#define USB_HID_USAGE__POWER__AUDIBLE_ALARM_CONTROL 0x0084005a
|
||||
/// Power: Present (DF)
|
||||
#define USB_HID_USAGE__POWER__PRESENT 0x00840060
|
||||
/// Power: Good (DF)
|
||||
#define USB_HID_USAGE__POWER__GOOD 0x00840061
|
||||
/// Power: Internal Failure (DF)
|
||||
#define USB_HID_USAGE__POWER__INTERNAL_FAILURE 0x00840062
|
||||
/// Power: Voltag Out Of Range (DF)
|
||||
#define USB_HID_USAGE__POWER__VOLTAG_OUT_OF_RANGE 0x00840063
|
||||
/// Power: Frequency Out Of Range (DF)
|
||||
#define USB_HID_USAGE__POWER__FREQUENCY_OUT_OF_RANGE 0x00840064
|
||||
/// Power: Overload (DF)
|
||||
#define USB_HID_USAGE__POWER__OVERLOAD 0x00840065
|
||||
/// Power: Over Charged (DF)
|
||||
#define USB_HID_USAGE__POWER__OVER_CHARGED 0x00840066
|
||||
/// Power: Over Temperature (DF)
|
||||
#define USB_HID_USAGE__POWER__OVER_TEMPERATURE 0x00840067
|
||||
/// Power: Shutdown Requested (DF)
|
||||
#define USB_HID_USAGE__POWER__SHUTDOWN_REQUESTED 0x00840068
|
||||
/// Power: Shutdown Imminent (DF)
|
||||
#define USB_HID_USAGE__POWER__SHUTDOWN_IMMINENT 0x00840069
|
||||
/// Power: Switch On/Off (DF)
|
||||
#define USB_HID_USAGE__POWER__SWITCH_ON_OFF 0x0084006b
|
||||
/// Power: Switchable (DF)
|
||||
#define USB_HID_USAGE__POWER__SWITCHABLE 0x0084006c
|
||||
/// Power: Used (DF)
|
||||
#define USB_HID_USAGE__POWER__USED 0x0084006d
|
||||
/// Power: Boost (DF)
|
||||
#define USB_HID_USAGE__POWER__BOOST 0x0084006e
|
||||
/// Power: Buck (DF)
|
||||
#define USB_HID_USAGE__POWER__BUCK 0x0084006f
|
||||
/// Power: Initialized (DF)
|
||||
#define USB_HID_USAGE__POWER__INITIALIZED 0x00840070
|
||||
/// Power: Tested (DF)
|
||||
#define USB_HID_USAGE__POWER__TESTED 0x00840071
|
||||
/// Power: Awaiting Power (DF)
|
||||
#define USB_HID_USAGE__POWER__AWAITING_POWER 0x00840072
|
||||
/// Power: Communication Lost (DF)
|
||||
#define USB_HID_USAGE__POWER__COMMUNICATION_LOST 0x00840073
|
||||
/// Power: iManufacturer (SV)
|
||||
#define USB_HID_USAGE__POWER__IMANUFACTURER 0x008400fd
|
||||
/// Power: iProduct (SV)
|
||||
#define USB_HID_USAGE__POWER__IPRODUCT 0x008400fe
|
||||
/// Power: iSerialNumber (SV)
|
||||
#define USB_HID_USAGE__POWER__ISERIALNUMBER 0x008400ff
|
||||
/// Battery System: Smart Battery Battery Mode (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_BATTERY_MODE 0x00850001
|
||||
/// Battery System: Smart Battery Battery Status (NAry)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_BATTERY_STATUS 0x00850002
|
||||
/// Battery System: Smart Battery Alarm Warning (NAry)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_ALARM_WARNING 0x00850003
|
||||
/// Battery System: Smart Battery Charger Mode (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_MODE 0x00850004
|
||||
/// Battery System: Smart Battery Charger Status (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_STATUS 0x00850005
|
||||
/// Battery System: Smart Battery Charger Spec Info (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_SPEC_INFO 0x00850006
|
||||
/// Battery System: Smart Battery Selector State (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_STATE 0x00850007
|
||||
/// Battery System: Smart Battery Selector Presets (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_PRESETS 0x00850008
|
||||
/// Battery System: Smart Battery Selector Info (CL)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_INFO 0x00850009
|
||||
/// Battery System: Optional Mfg Function 1 (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_1 0x00850010
|
||||
/// Battery System: Optional Mfg Function 2 (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_2 0x00850011
|
||||
/// Battery System: Optional Mfg Function 3 (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_3 0x00850012
|
||||
/// Battery System: Optional Mfg Function 4 (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_4 0x00850013
|
||||
/// Battery System: Optional Mfg Function 5 (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_5 0x00850014
|
||||
/// Battery System: Connection To SM Bus (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CONNECTION_TO_SM_BUS 0x00850015
|
||||
/// Battery System: Output Connection (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OUTPUT_CONNECTION 0x00850016
|
||||
/// Battery System: Charger Connection (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_CONNECTION 0x00850017
|
||||
/// Battery System: Battery Insertion (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_INSERTION 0x00850018
|
||||
/// Battery System: Use Next (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__USE_NEXT 0x00850019
|
||||
/// Battery System: OK To Use (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__OK_TO_USE 0x0085001a
|
||||
/// Battery System: Battery Supported (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_SUPPORTED 0x0085001b
|
||||
/// Battery System: Selector Revision (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SELECTOR_REVISION 0x0085001c
|
||||
/// Battery System: Charging Indicator (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGING_INDICATOR 0x0085001d
|
||||
/// Battery System: Manufacturer Access (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURER_ACCESS 0x00850028
|
||||
/// Battery System: Remaining Capacity Limit (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_CAPACITY_LIMIT 0x00850029
|
||||
/// Battery System: Remaining Time Limit (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_TIME_LIMIT 0x0085002a
|
||||
/// Battery System: At Rate (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE 0x0085002b
|
||||
/// Battery System: Capacity Mode (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_MODE 0x0085002c
|
||||
/// Battery System: Broadcast To Charger (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BROADCAST_TO_CHARGER 0x0085002d
|
||||
/// Battery System: Primary Battery (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__PRIMARY_BATTERY 0x0085002e
|
||||
/// Battery System: Charge Controller (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGE_CONTROLLER 0x0085002f
|
||||
/// Battery System: Terminate Charge (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__TERMINATE_CHARGE 0x00850040
|
||||
/// Battery System: Terminate Discharge (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__TERMINATE_DISCHARGE 0x00850041
|
||||
/// Battery System: Below Remaining Capacity Limit (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BELOW_REMAINING_CAPACITY_LIMIT 0x00850042
|
||||
/// Battery System: Remaining Time Limit Expired (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_TIME_LIMIT_EXPIRED 0x00850043
|
||||
/// Battery System: Charging (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGING 0x00850044
|
||||
/// Battery System: Discharging (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__DISCHARGING 0x00850045
|
||||
/// Battery System: Fully Charged (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__FULLY_CHARGED 0x00850046
|
||||
/// Battery System: Fully Discharged (Sel)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__FULLY_DISCHARGED 0x00850047
|
||||
/// Battery System: Conditioning Flag (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CONDITIONING_FLAG 0x00850048
|
||||
/// Battery System: At Rate OK (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_OK 0x00850049
|
||||
/// Battery System: Smart Battery Error Code (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_ERROR_CODE 0x0085004a
|
||||
/// Battery System: Need Replacement (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__NEED_REPLACEMENT 0x0085004b
|
||||
/// Battery System: At Rate Time To Full (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_TIME_TO_FULL 0x00850060
|
||||
/// Battery System: At Rate Time To Empty (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_TIME_TO_EMPTY 0x00850061
|
||||
/// Battery System: Average Current (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_CURRENT 0x00850062
|
||||
/// Battery System: Max Error (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__MAX_ERROR 0x00850063
|
||||
/// Battery System: Relative State Of Charge (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__RELATIVE_STATE_OF_CHARGE 0x00850064
|
||||
/// Battery System: Absolute State Of Charge (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__ABSOLUTE_STATE_OF_CHARGE 0x00850065
|
||||
/// Battery System: Remaining Capacity (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_CAPACITY 0x00850066
|
||||
/// Battery System: Full Charge Capacity (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__FULL_CHARGE_CAPACITY 0x00850067
|
||||
/// Battery System: Run Time To Empty (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__RUN_TIME_TO_EMPTY 0x00850068
|
||||
/// Battery System: Average Time To Empty (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_TIME_TO_EMPTY 0x00850069
|
||||
/// Battery System: Average Time To Full (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_TIME_TO_FULL 0x0085006a
|
||||
/// Battery System: Cycle Count (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CYCLE_COUNT 0x0085006b
|
||||
/// Battery System: Battery Pack Model Level (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_PACK_MODEL_LEVEL 0x00850080
|
||||
/// Battery System: Internal Charge Controller (SF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__INTERNAL_CHARGE_CONTROLLER 0x00850081
|
||||
/// Battery System: Primary Battery Support (SF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__PRIMARY_BATTERY_SUPPORT 0x00850082
|
||||
/// Battery System: Design Capacity (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__DESIGN_CAPACITY 0x00850083
|
||||
/// Battery System: Specification Info (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SPECIFICATION_INFO 0x00850084
|
||||
/// Battery System: Manufacture Date (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURE_DATE 0x00850085
|
||||
/// Battery System: Serial Number (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__SERIAL_NUMBER 0x00850086
|
||||
/// Battery System: iManufacturer Name (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__IMANUFACTURER_NAME 0x00850087
|
||||
/// Battery System: iDevice Name (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__IDEVICE_NAME 0x00850088
|
||||
/// Battery System: iDevice Chemistry (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__IDEVICE_CHEMISTRY 0x00850089
|
||||
/// Battery System: Manufacturer Data (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURER_DATA 0x0085008a
|
||||
/// Battery System: Rechargable (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__RECHARGABLE 0x0085008b
|
||||
/// Battery System: Warning Capacity Limit (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__WARNING_CAPACITY_LIMIT 0x0085008c
|
||||
/// Battery System: Capacity Granularity 1 (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_GRANULARITY_1 0x0085008d
|
||||
/// Battery System: Capacity Granularity 2 (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_GRANULARITY_2 0x0085008e
|
||||
/// Battery System: iOEM Information (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__IOEM_INFORMATION 0x0085008f
|
||||
/// Battery System: Inhibit Charge (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__INHIBIT_CHARGE 0x008500c0
|
||||
/// Battery System: Enable Polling (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__ENABLE_POLLING 0x008500c1
|
||||
/// Battery System: Reset To Zero (DF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__RESET_TO_ZERO 0x008500c2
|
||||
/// Battery System: AC Present (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__AC_PRESENT 0x008500d0
|
||||
/// Battery System: Battery Present (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_PRESENT 0x008500d1
|
||||
/// Battery System: Power Fail (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__POWER_FAIL 0x008500d2
|
||||
/// Battery System: Alarm Inhibited (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__ALARM_INHIBITED 0x008500d3
|
||||
/// Battery System: Thermistor Under Range (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_UNDER_RANGE 0x008500d4
|
||||
/// Battery System: Thermistor Hot (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_HOT 0x008500d5
|
||||
/// Battery System: Thermistor Cold (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_COLD 0x008500d6
|
||||
/// Battery System: Thermistor Over Range (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_OVER_RANGE 0x008500d7
|
||||
/// Battery System: Voltage Out Of Range (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__VOLTAGE_OUT_OF_RANGE 0x008500d8
|
||||
/// Battery System: Current Out Of Range (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CURRENT_OUT_OF_RANGE 0x008500d9
|
||||
/// Battery System: Current Not Regulated (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CURRENT_NOT_REGULATED 0x008500da
|
||||
/// Battery System: Voltage Not Regulated (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__VOLTAGE_NOT_REGULATED 0x008500db
|
||||
/// Battery System: Master Mode (DV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__MASTER_MODE 0x008500dc
|
||||
/// Battery System: Charger Selector Support (SF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_SELECTOR_SUPPORT 0x008500f0
|
||||
/// Battery System: Charger Spec (SV)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_SPEC 0x008500f1
|
||||
/// Battery System: Level 2 (SF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__LEVEL_2 0x008500f2
|
||||
/// Battery System: Level 3 (SF)
|
||||
#define USB_HID_USAGE__BATTERY_SYSTEM__LEVEL_3 0x008500f3
|
||||
|
||||
static const struct usb_hid_usage_info {
|
||||
uint32_t usage;
|
||||
const char *name;
|
||||
} usb_hid_usages[] = {
|
||||
{0x00840001, "iName"},
|
||||
{0x00840002, "Present Status"},
|
||||
{0x00840003, "Changed Status"},
|
||||
{0x00840004, "UPS"},
|
||||
{0x00840005, "Power Supply"},
|
||||
{0x00840010, "Battery System"},
|
||||
{0x00840011, "Battery System Id"},
|
||||
{0x00840012, "Battery"},
|
||||
{0x00840013, "Battery Id"},
|
||||
{0x00840014, "Charger"},
|
||||
{0x00840015, "Charger Id"},
|
||||
{0x00840016, "Power Converter"},
|
||||
{0x00840017, "Power Converter Id"},
|
||||
{0x00840018, "Outlet System"},
|
||||
{0x00840019, "Outlet System Id"},
|
||||
{0x0084001a, "Input"},
|
||||
{0x0084001b, "Input Id"},
|
||||
{0x0084001c, "Output"},
|
||||
{0x0084001d, "Output Id"},
|
||||
{0x0084001e, "Flow"},
|
||||
{0x0084001f, "Flow Id"},
|
||||
{0x00840020, "Outlet"},
|
||||
{0x00840021, "Outlet Id"},
|
||||
{0x00840022, "Gang"},
|
||||
{0x00840023, "Gang Id"},
|
||||
{0x00840024, "Power Summary"},
|
||||
{0x00840025, "Power Summary Id"},
|
||||
{0x00840030, "Voltage"},
|
||||
{0x00840031, "Current"},
|
||||
{0x00840032, "Frequency"},
|
||||
{0x00840033, "Apparent Power"},
|
||||
{0x00840034, "Active Power"},
|
||||
{0x00840035, "Percent Load"},
|
||||
{0x00840036, "Temperature"},
|
||||
{0x00840037, "Humidity"},
|
||||
{0x00840038, "Bad Count"},
|
||||
{0x00840040, "Config Voltage"},
|
||||
{0x00840041, "Config Current"},
|
||||
{0x00840042, "Config Frequency"},
|
||||
{0x00840043, "Config Apparent Power"},
|
||||
{0x00840044, "Config Active Power"},
|
||||
{0x00840045, "Config Percent Load"},
|
||||
{0x00840046, "Config Temperature"},
|
||||
{0x00840047, "Config Humidity"},
|
||||
{0x00840050, "Switch On Control"},
|
||||
{0x00840051, "Switch Off Control"},
|
||||
{0x00840052, "Toggle Control"},
|
||||
{0x00840053, "Low Voltage Transfer"},
|
||||
{0x00840054, "High Voltage Transfer"},
|
||||
{0x00840055, "Delay Before Reboot"},
|
||||
{0x00840056, "Delay Before Startup"},
|
||||
{0x00840057, "Delay Before Shutdown"},
|
||||
{0x00840058, "Test"},
|
||||
{0x00840059, "Module Reset"},
|
||||
{0x0084005a, "Audible Alarm Control"},
|
||||
{0x00840060, "Present"},
|
||||
{0x00840061, "Good"},
|
||||
{0x00840062, "Internal Failure"},
|
||||
{0x00840063, "Voltag Out Of Range"},
|
||||
{0x00840064, "Frequency Out Of Range"},
|
||||
{0x00840065, "Overload"},
|
||||
{0x00840066, "Over Charged"},
|
||||
{0x00840067, "Over Temperature"},
|
||||
{0x00840068, "Shutdown Requested"},
|
||||
{0x00840069, "Shutdown Imminent"},
|
||||
{0x0084006b, "Switch On/Off"},
|
||||
{0x0084006c, "Switchable"},
|
||||
{0x0084006d, "Used"},
|
||||
{0x0084006e, "Boost"},
|
||||
{0x0084006f, "Buck"},
|
||||
{0x00840070, "Initialized"},
|
||||
{0x00840071, "Tested"},
|
||||
{0x00840072, "Awaiting Power"},
|
||||
{0x00840073, "Communication Lost"},
|
||||
{0x008400fd, "iManufacturer"},
|
||||
{0x008400fe, "iProduct"},
|
||||
{0x008400ff, "iSerialNumber"},
|
||||
{0x00850001, "Smart Battery Battery Mode"},
|
||||
{0x00850002, "Smart Battery Battery Status"},
|
||||
{0x00850003, "Smart Battery Alarm Warning"},
|
||||
{0x00850004, "Smart Battery Charger Mode"},
|
||||
{0x00850005, "Smart Battery Charger Status"},
|
||||
{0x00850006, "Smart Battery Charger Spec Info"},
|
||||
{0x00850007, "Smart Battery Selector State"},
|
||||
{0x00850008, "Smart Battery Selector Presets"},
|
||||
{0x00850009, "Smart Battery Selector Info"},
|
||||
{0x00850010, "Optional Mfg Function 1"},
|
||||
{0x00850011, "Optional Mfg Function 2"},
|
||||
{0x00850012, "Optional Mfg Function 3"},
|
||||
{0x00850013, "Optional Mfg Function 4"},
|
||||
{0x00850014, "Optional Mfg Function 5"},
|
||||
{0x00850015, "Connection To SM Bus"},
|
||||
{0x00850016, "Output Connection"},
|
||||
{0x00850017, "Charger Connection"},
|
||||
{0x00850018, "Battery Insertion"},
|
||||
{0x00850019, "Use Next"},
|
||||
{0x0085001a, "OK To Use"},
|
||||
{0x0085001b, "Battery Supported"},
|
||||
{0x0085001c, "Selector Revision"},
|
||||
{0x0085001d, "Charging Indicator"},
|
||||
{0x00850028, "Manufacturer Access"},
|
||||
{0x00850029, "Remaining Capacity Limit"},
|
||||
{0x0085002a, "Remaining Time Limit"},
|
||||
{0x0085002b, "At Rate"},
|
||||
{0x0085002c, "Capacity Mode"},
|
||||
{0x0085002d, "Broadcast To Charger"},
|
||||
{0x0085002e, "Primary Battery"},
|
||||
{0x0085002f, "Charge Controller"},
|
||||
{0x00850040, "Terminate Charge"},
|
||||
{0x00850041, "Terminate Discharge"},
|
||||
{0x00850042, "Below Remaining Capacity Limit"},
|
||||
{0x00850043, "Remaining Time Limit Expired"},
|
||||
{0x00850044, "Charging"},
|
||||
{0x00850045, "Discharging"},
|
||||
{0x00850046, "Fully Charged"},
|
||||
{0x00850047, "Fully Discharged"},
|
||||
{0x00850048, "Conditioning Flag"},
|
||||
{0x00850049, "At Rate OK"},
|
||||
{0x0085004a, "Smart Battery Error Code"},
|
||||
{0x0085004b, "Need Replacement"},
|
||||
{0x00850060, "At Rate Time To Full"},
|
||||
{0x00850061, "At Rate Time To Empty"},
|
||||
{0x00850062, "Average Current"},
|
||||
{0x00850063, "Max Error"},
|
||||
{0x00850064, "Relative State Of Charge"},
|
||||
{0x00850065, "Absolute State Of Charge"},
|
||||
{0x00850066, "Remaining Capacity"},
|
||||
{0x00850067, "Full Charge Capacity"},
|
||||
{0x00850068, "Run Time To Empty"},
|
||||
{0x00850069, "Average Time To Empty"},
|
||||
{0x0085006a, "Average Time To Full"},
|
||||
{0x0085006b, "Cycle Count"},
|
||||
{0x00850080, "Battery Pack Model Level"},
|
||||
{0x00850081, "Internal Charge Controller"},
|
||||
{0x00850082, "Primary Battery Support"},
|
||||
{0x00850083, "Design Capacity"},
|
||||
{0x00850084, "Specification Info"},
|
||||
{0x00850085, "Manufacture Date"},
|
||||
{0x00850086, "Serial Number"},
|
||||
{0x00850087, "iManufacturer Name"},
|
||||
{0x00850088, "iDevice Name"},
|
||||
{0x00850089, "iDevice Chemistry"},
|
||||
{0x0085008a, "Manufacturer Data"},
|
||||
{0x0085008b, "Rechargable"},
|
||||
{0x0085008c, "Warning Capacity Limit"},
|
||||
{0x0085008d, "Capacity Granularity 1"},
|
||||
{0x0085008e, "Capacity Granularity 2"},
|
||||
{0x0085008f, "iOEM Information"},
|
||||
{0x008500c0, "Inhibit Charge"},
|
||||
{0x008500c1, "Enable Polling"},
|
||||
{0x008500c2, "Reset To Zero"},
|
||||
{0x008500d0, "AC Present"},
|
||||
{0x008500d1, "Battery Present"},
|
||||
{0x008500d2, "Power Fail"},
|
||||
{0x008500d3, "Alarm Inhibited"},
|
||||
{0x008500d4, "Thermistor Under Range"},
|
||||
{0x008500d5, "Thermistor Hot"},
|
||||
{0x008500d6, "Thermistor Cold"},
|
||||
{0x008500d7, "Thermistor Over Range"},
|
||||
{0x008500d8, "Voltage Out Of Range"},
|
||||
{0x008500d9, "Current Out Of Range"},
|
||||
{0x008500da, "Current Not Regulated"},
|
||||
{0x008500db, "Voltage Not Regulated"},
|
||||
{0x008500dc, "Master Mode"},
|
||||
{0x008500f0, "Charger Selector Support"},
|
||||
{0x008500f1, "Charger Spec"},
|
||||
{0x008500f2, "Level 2"},
|
||||
{0x008500f3, "Level 3"},
|
||||
};
|
||||
|
||||
static const struct usb_hid_usage_range_info {
|
||||
uint32_t usage_start;
|
||||
uint32_t usage_end;
|
||||
const char *name_prefix;
|
||||
} usb_hid_usages_ranged[] = {
|
||||
|
||||
};
|
||||
|
||||
static int
|
||||
usb_hid_usage_pages_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint16_t *key = a;
|
||||
const struct usb_hid_usage_page_info *entry = b;
|
||||
return (*key > entry->usage_page) - (*key < entry->usage_page);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_hid_usages_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint32_t *key = a;
|
||||
const struct usb_hid_usage_info *entry = b;
|
||||
return (*key > entry->usage) - (*key < entry->usage);
|
||||
}
|
||||
|
||||
static int
|
||||
usb_hid_usages_ranged_cmp(const void *a, const void *b)
|
||||
{
|
||||
const uint32_t *key = a;
|
||||
const struct usb_hid_usage_range_info *entry = b;
|
||||
return (*key > entry->usage_end) - (*key < entry->usage_start);
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_page_to_string(uint16_t usage_page)
|
||||
{
|
||||
const struct usb_hid_usage_page_info *entry = bsearch(
|
||||
&usage_page, usb_hid_usage_pages,
|
||||
sizeof usb_hid_usage_pages / sizeof usb_hid_usage_pages[0],
|
||||
sizeof usb_hid_usage_pages[0], usb_hid_usage_pages_cmp);
|
||||
if (entry)
|
||||
return entry->name;
|
||||
|
||||
static char buffer[128];
|
||||
snprintf(buffer, sizeof buffer, "%04x", usage_page);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_to_string(uint32_t usage)
|
||||
{
|
||||
static char buffer[128];
|
||||
|
||||
const struct usb_hid_usage_info *info = bsearch(
|
||||
&usage, usb_hid_usages,
|
||||
sizeof usb_hid_usages / sizeof usb_hid_usages[0],
|
||||
sizeof usb_hid_usages[0], usb_hid_usages_cmp);
|
||||
if (info)
|
||||
return info->name;
|
||||
|
||||
const struct usb_hid_usage_range_info *range = bsearch(
|
||||
&usage, usb_hid_usages_ranged,
|
||||
sizeof usb_hid_usages_ranged / sizeof usb_hid_usages_ranged[0],
|
||||
sizeof usb_hid_usages_ranged[0], usb_hid_usages_ranged_cmp);
|
||||
if (range) {
|
||||
snprintf(buffer, sizeof buffer,
|
||||
"%s %u", range->name_prefix, (unsigned) (usage & 0xffff));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof buffer, "%04x", (unsigned) (usage & 0xffff));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const char *
|
||||
usb_hid_usage_to_string_full(uint32_t usage)
|
||||
{
|
||||
static char buffer[128];
|
||||
snprintf(buffer, sizeof buffer, "%s: %s",
|
||||
usb_hid_usage_page_to_string(usage >> 16),
|
||||
usb_hid_usage_to_string(usage));
|
||||
return buffer;
|
||||
}
|
||||
@@ -0,0 +1,992 @@
|
||||
/*
|
||||
* upscat.c: USB HID UPS status streaming utility
|
||||
*
|
||||
* This program stays independent of the liberty library
|
||||
* in order to build on Windows.
|
||||
*
|
||||
* Copyright (c) 2026, Přemysl Eric Janouch <p@janouch.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// On Windows, vswprintf() interprets %s in the width of the format string,
|
||||
// and %hs is not really compliant with any standard:
|
||||
// https://devblogs.microsoft.com/oldnewthing/20190830-00/?p=102823
|
||||
#ifdef _WIN32
|
||||
#define __USE_MINGW_ANSI_STDIO
|
||||
#endif
|
||||
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <getopt.h>
|
||||
#include <hidapi.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <sysinfoapi.h> // GetTickCount
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <time.h> // clock_gettime
|
||||
#endif
|
||||
|
||||
#include "upscat-usb.h"
|
||||
|
||||
#include "config.h"
|
||||
#undef PROGRAM_NAME
|
||||
#define PROGRAM_NAME "upscat"
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define hid_init hidapi_hid_init
|
||||
#endif
|
||||
|
||||
#if defined __MINGW_GNU_PRINTF
|
||||
#define ATTRIBUTE_PRINTF(x, y) __MINGW_GNU_PRINTF((x), (y))
|
||||
#elif defined __GNUC__
|
||||
#define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y)))
|
||||
#else
|
||||
#define ATTRIBUTE_PRINTF(x, y)
|
||||
#endif
|
||||
|
||||
#define countof(array) (sizeof array / sizeof array[0])
|
||||
|
||||
static int64_t
|
||||
get_timestamp_ms(void)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return GetTickCount64();
|
||||
#else
|
||||
struct timespec tp;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &tp) < 0) {
|
||||
fprintf(stderr, "error: clock_gettime: %s", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return (int64_t) tp.tv_sec * 1000 + (int64_t) tp.tv_nsec / 1000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// --- USB HID report descriptor parser ----------------------------------------
|
||||
// This parser is simplified for simple readouts.
|
||||
// hidpar.pdf provides guidelines for better conforming implementations.
|
||||
|
||||
enum {
|
||||
// This is one byte.
|
||||
// USB HID 6.2.2.7: Report ID zero is reserved and should not be used.
|
||||
PARSER_REPORT_LIMIT = 256,
|
||||
// There is no explicit limit on the number of usages.
|
||||
PARSER_USAGES_LIMIT = 64,
|
||||
};
|
||||
|
||||
enum {
|
||||
PARSER_ITEM_TYPE_MAIN = 0,
|
||||
PARSER_ITEM_TYPE_GLOBAL,
|
||||
PARSER_ITEM_TYPE_LOCAL,
|
||||
PARSER_ITEM_TYPE_RESERVED,
|
||||
|
||||
PARSER_ITEM_TAG_LONG = 0xf,
|
||||
|
||||
PARSER_ITEM_TAG_MAIN_INPUT = 0x8,
|
||||
PARSER_ITEM_TAG_MAIN_OUTPUT = 0x9,
|
||||
PARSER_ITEM_TAG_MAIN_FEATURE = 0xb,
|
||||
PARSER_ITEM_TAG_MAIN_COLLECTION = 0xa,
|
||||
PARSER_ITEM_TAG_MAIN_END_COLLECTION = 0xc,
|
||||
|
||||
PARSER_ITEM_TAG_GLOBAL_USAGE_PAGE = 0x0,
|
||||
PARSER_ITEM_TAG_GLOBAL_LOGICAL_MINIMUM = 0x1,
|
||||
PARSER_ITEM_TAG_GLOBAL_LOGICAL_MAXIMUM = 0x2,
|
||||
PARSER_ITEM_TAG_GLOBAL_PHYSICAL_MINIMUM = 0x3,
|
||||
PARSER_ITEM_TAG_GLOBAL_PHYSICAL_MAXIMUM = 0x4,
|
||||
PARSER_ITEM_TAG_GLOBAL_UNIT_EXPONENT = 0x5,
|
||||
PARSER_ITEM_TAG_GLOBAL_UNIT = 0x6,
|
||||
PARSER_ITEM_TAG_GLOBAL_REPORT_SIZE = 0x7,
|
||||
PARSER_ITEM_TAG_GLOBAL_REPORT_ID = 0x8,
|
||||
PARSER_ITEM_TAG_GLOBAL_REPORT_COUNT = 0x9,
|
||||
PARSER_ITEM_TAG_GLOBAL_PUSH = 0xa,
|
||||
PARSER_ITEM_TAG_GLOBAL_POP = 0xb,
|
||||
|
||||
PARSER_ITEM_TAG_LOCAL_USAGE = 0x0,
|
||||
PARSER_ITEM_TAG_LOCAL_USAGE_MINIMUM = 0x1,
|
||||
PARSER_ITEM_TAG_LOCAL_USAGE_MAXIMUM = 0x2,
|
||||
PARSER_ITEM_TAG_LOCAL_DESIGNATOR_INDEX = 0x3,
|
||||
PARSER_ITEM_TAG_LOCAL_DESIGNATOR_MINIMUM = 0x4,
|
||||
PARSER_ITEM_TAG_LOCAL_DESIGNATOR_MAXIMUM = 0x5,
|
||||
PARSER_ITEM_TAG_LOCAL_STRING_INDEX = 0x7,
|
||||
PARSER_ITEM_TAG_LOCAL_STRING_MINIMUM = 0x8,
|
||||
PARSER_ITEM_TAG_LOCAL_STRING_MAXIMUM = 0x9,
|
||||
PARSER_ITEM_TAG_LOCAL_DELIMITER = 0xa,
|
||||
};
|
||||
|
||||
// Technically, this is for data-type main items, describing fields.
|
||||
struct parser_field {
|
||||
// USB HID 6.2.2.9: "Reports can be padded to byte-align fields by [...]
|
||||
// not declaring a usage for the main item."
|
||||
// USB HID 6.2.2.8: If there are fewer Usages than controls, the last Usage
|
||||
// also applies to the remaining controls.
|
||||
uint32_t usages[PARSER_USAGES_LIMIT];
|
||||
size_t usages_len;
|
||||
int32_t logical_minimum;
|
||||
int32_t logical_maximum;
|
||||
uint32_t report_size;
|
||||
uint32_t report_count;
|
||||
unsigned array__variable : 1;
|
||||
unsigned absolute__relative : 1;
|
||||
};
|
||||
|
||||
struct parser_report {
|
||||
uint32_t report_id;
|
||||
// USB HID 8.4: "reports may not span more than one top-level collection."
|
||||
uint32_t application_usage;
|
||||
|
||||
// There is no explicit limit on the number of fields per report.
|
||||
struct parser_field fields[16];
|
||||
size_t fields_len;
|
||||
};
|
||||
|
||||
struct parser {
|
||||
// There is no explicit limit on the depth of collections.
|
||||
// USB HID 6.2.2.6: "a Usage item tag must be associated
|
||||
// with any collection" however many devices simply do not care.
|
||||
uint32_t collections[16];
|
||||
size_t collections_len;
|
||||
struct parser_state_global {
|
||||
uint32_t usage_page;
|
||||
int32_t logical_minimum; // \_ If neither is negative,
|
||||
int32_t logical_maximum; // / the report field is unsigned.
|
||||
int32_t physical_minimum; // \ These actually
|
||||
int32_t physical_maximum; // > start as UNDEFINED,
|
||||
int32_t unit_exponent; // / which we can't express.
|
||||
int32_t unit;
|
||||
uint32_t report_size;
|
||||
uint32_t report_id;
|
||||
uint32_t report_count;
|
||||
} global;
|
||||
struct parser_state_local {
|
||||
uint32_t usages[PARSER_USAGES_LIMIT];
|
||||
size_t usages_len;
|
||||
uint32_t usage_minimum;
|
||||
uint32_t designator_index;
|
||||
uint32_t designator_minimum;
|
||||
uint32_t designator_maximum;
|
||||
uint32_t string_index;
|
||||
uint32_t string_minimum;
|
||||
uint32_t string_maximum;
|
||||
uint32_t delimiter;
|
||||
} local;
|
||||
struct parser_report input[PARSER_REPORT_LIMIT];
|
||||
struct parser_report feature[PARSER_REPORT_LIMIT];
|
||||
};
|
||||
|
||||
static const char *
|
||||
parse_item_set(struct parser *parser, uint32_t flags, bool feature)
|
||||
{
|
||||
struct parser_state_global *global = &parser->global;
|
||||
if (!global->report_id)
|
||||
return "missing Report ID";
|
||||
if (global->report_id >= PARSER_REPORT_LIMIT)
|
||||
return "Report ID is too high";
|
||||
|
||||
struct parser_report *report = feature
|
||||
? &parser->feature[global->report_id]
|
||||
: &parser->input[global->report_id];
|
||||
if (report->fields_len == countof(report->fields))
|
||||
return "too many fields per Report";
|
||||
|
||||
struct parser_state_local *local = &parser->local;
|
||||
struct parser_field *field = &report->fields[report->fields_len++];
|
||||
|
||||
if (local->usages_len) {
|
||||
uint32_t last = local->usages[local->usages_len - 1];
|
||||
// Not bothering to error out on hitting the compile-time limit.
|
||||
while (local->usages_len < global->report_count
|
||||
&& local->usages_len < PARSER_USAGES_LIMIT)
|
||||
local->usages[local->usages_len++] = last;
|
||||
}
|
||||
|
||||
report->report_id = global->report_id;
|
||||
report->application_usage = parser->collections[0];
|
||||
memcpy(field->usages, local->usages, sizeof local->usages);
|
||||
field->usages_len = local->usages_len;
|
||||
field->logical_minimum = global->logical_minimum;
|
||||
field->logical_maximum = global->logical_maximum;
|
||||
field->report_size = global->report_size;
|
||||
field->report_count = global->report_count;
|
||||
field->array__variable = (flags >> 1) & 1;
|
||||
field->absolute__relative = (flags >> 2) & 1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
parse_item(
|
||||
struct parser *parser, uint8_t type, uint8_t tag, int32_t s, uint32_t u)
|
||||
{
|
||||
switch (type) {
|
||||
case PARSER_ITEM_TYPE_MAIN: {
|
||||
const char *err = NULL;
|
||||
switch (tag) {
|
||||
break; case PARSER_ITEM_TAG_MAIN_INPUT:
|
||||
err = parse_item_set(parser, u, false);
|
||||
break; case PARSER_ITEM_TAG_MAIN_OUTPUT:
|
||||
// We don't care about these.
|
||||
break; case PARSER_ITEM_TAG_MAIN_FEATURE:
|
||||
err = parse_item_set(parser, u, true);
|
||||
break; case PARSER_ITEM_TAG_MAIN_COLLECTION:
|
||||
if (parser->local.usages_len > 1)
|
||||
return "expecting Collections to have at most one usage";
|
||||
if (!parser->collections_len && u != USB_HID_COLLECTION_APPLICATION)
|
||||
return "top-level Collections must be Application";
|
||||
|
||||
if (parser->collections_len == countof(parser->collections))
|
||||
return "Collections nested too deep";
|
||||
parser->collections[parser->collections_len++] =
|
||||
parser->local.usages_len ? parser->local.usages[0] : 0;
|
||||
break; case PARSER_ITEM_TAG_MAIN_END_COLLECTION:
|
||||
if (!parser->collections_len)
|
||||
return "no Collection to end";
|
||||
parser->collections_len--;
|
||||
break; default:
|
||||
return "unsupported Main item tag";
|
||||
}
|
||||
|
||||
parser->local = (struct parser_state_local) {};
|
||||
return err;
|
||||
}
|
||||
case PARSER_ITEM_TYPE_GLOBAL:
|
||||
switch (tag) {
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_USAGE_PAGE:
|
||||
parser->global.usage_page = u;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_LOGICAL_MINIMUM:
|
||||
parser->global.logical_minimum = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_LOGICAL_MAXIMUM:
|
||||
parser->global.logical_maximum = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_PHYSICAL_MINIMUM:
|
||||
parser->global.physical_minimum = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_PHYSICAL_MAXIMUM:
|
||||
parser->global.physical_maximum = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_UNIT_EXPONENT:
|
||||
parser->global.unit_exponent = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_UNIT:
|
||||
parser->global.unit = s;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_REPORT_SIZE:
|
||||
if (u > 32)
|
||||
return "Report Size too large";
|
||||
parser->global.report_size = u;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_REPORT_ID:
|
||||
parser->global.report_id = u;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_REPORT_COUNT:
|
||||
parser->global.report_count = u;
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_PUSH:
|
||||
return "state pushing is not supported";
|
||||
break; case PARSER_ITEM_TAG_GLOBAL_POP:
|
||||
return "state pushing is not supported";
|
||||
break; default:
|
||||
return "unsupported Global item tag";
|
||||
}
|
||||
break;
|
||||
case PARSER_ITEM_TYPE_LOCAL:
|
||||
switch (tag) {
|
||||
break; case PARSER_ITEM_TAG_LOCAL_USAGE:
|
||||
if (parser->local.usages_len == countof(parser->local.usages))
|
||||
return "too many Usages per field";
|
||||
|
||||
// USB HUT 3.1: Usage ID 0 should always be reserved.
|
||||
if (!u)
|
||||
return "zero Usage";
|
||||
|
||||
// We should really decide by the data length instead.
|
||||
if (u < 0x10000)
|
||||
u = parser->global.usage_page << 16 | u;
|
||||
|
||||
parser->local.usages[parser->local.usages_len++] = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_USAGE_MINIMUM:
|
||||
parser->local.usage_minimum = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_USAGE_MAXIMUM:
|
||||
// This adds to usages from parser->local.usage_minimum through u.
|
||||
return "usage ranges are not supported";
|
||||
break; case PARSER_ITEM_TAG_LOCAL_DESIGNATOR_INDEX:
|
||||
parser->local.designator_index = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_DESIGNATOR_MINIMUM:
|
||||
parser->local.designator_minimum = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_DESIGNATOR_MAXIMUM:
|
||||
parser->local.designator_maximum = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_STRING_INDEX:
|
||||
parser->local.string_index = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_STRING_MINIMUM:
|
||||
parser->local.string_minimum = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_STRING_MAXIMUM:
|
||||
parser->local.string_maximum = u;
|
||||
break; case PARSER_ITEM_TAG_LOCAL_DELIMITER:
|
||||
parser->local.delimiter = u;
|
||||
return "delimiters are not supported";
|
||||
break; default:
|
||||
return "unsupported Local item tag";
|
||||
}
|
||||
break;
|
||||
case PARSER_ITEM_TYPE_RESERVED:
|
||||
// Completely unnecessary.
|
||||
return "long/reserved items are not supported";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
parse_descriptor(struct parser *parser, const uint8_t *descriptor, size_t len)
|
||||
{
|
||||
// USB HID 5.2 Report Descriptors
|
||||
const uint8_t *p = descriptor, *end = p + len;
|
||||
while (p != end) {
|
||||
// USB HID 5.3 Generic Item Format
|
||||
// USB HID 6.2.2.1 Items Types and Tags
|
||||
uint8_t prefix = *p++,
|
||||
size = prefix & 0x3,
|
||||
type = (prefix >> 2) & 0x3,
|
||||
tag = prefix >> 4;
|
||||
|
||||
size += size == 3;
|
||||
if (p + size > end)
|
||||
return "item overflow";
|
||||
|
||||
uint32_t uvalue = 0;
|
||||
int32_t svalue = 0;
|
||||
switch (size) {
|
||||
break; case 0:
|
||||
break; case 1:
|
||||
uvalue = p[0];
|
||||
svalue = (int8_t) p[0];
|
||||
break; case 2:
|
||||
uvalue = p[0] | p[1] << 8;
|
||||
svalue = (int16_t) (p[0] | p[1] << 8);
|
||||
break; case 4:
|
||||
uvalue = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
|
||||
svalue = (int32_t) uvalue;
|
||||
}
|
||||
|
||||
p += size;
|
||||
const char *err = parse_item(parser, type, tag, svalue, uvalue);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Report reader -----------------------------------------------------------
|
||||
|
||||
struct report_parser {
|
||||
const uint8_t *data; ///< Start of data from the report
|
||||
size_t len; ///< Length of report data
|
||||
const struct parser_report *report; ///< Report definition
|
||||
|
||||
unsigned field; ///< Current field iterator
|
||||
unsigned subfield; ///< report_count iterator
|
||||
unsigned bit_position; ///< Current bit position within data
|
||||
};
|
||||
|
||||
static struct report_parser
|
||||
report_parser_make(
|
||||
const struct parser_report *report, const uint8_t *data, size_t len)
|
||||
{
|
||||
return (struct report_parser) {.data = data, .len = len, .report = report};
|
||||
}
|
||||
|
||||
static int64_t
|
||||
report_parser_extract(struct report_parser *rp, unsigned nbits, bool is_signed)
|
||||
{
|
||||
// XXX: This algorithm seems unnecessarily random-access.
|
||||
// USB HID 5.8: the little-endian aspect actually needs no special handling.
|
||||
unsigned byte_offset = rp->bit_position / 8,
|
||||
shift = rp->bit_position % 8,
|
||||
nbytes = (shift + nbits + 7) / 8;
|
||||
|
||||
uint64_t raw = 0;
|
||||
for (unsigned i = 0; i < nbytes; i++) {
|
||||
uint8_t byte = 0;
|
||||
if (byte_offset + i < rp->len)
|
||||
byte = rp->data[byte_offset + i];
|
||||
raw |= (uint64_t) byte << (8 * i);
|
||||
}
|
||||
rp->bit_position += nbits;
|
||||
|
||||
raw >>= shift;
|
||||
if (nbits < 64)
|
||||
raw &= (1ULL << nbits) - 1;
|
||||
|
||||
// USB HID 5.8: fields are signed (2's complement)
|
||||
// unless both Logical Minimum and Logical Maximum are non-negative.
|
||||
// Do sign extension.
|
||||
if (is_signed && nbits > 0 && nbits < 64 && (raw & 1ULL << (nbits - 1)))
|
||||
raw |= ~((1ULL << nbits) - 1);
|
||||
return (int64_t) raw;
|
||||
}
|
||||
|
||||
static bool
|
||||
report_parser_parse(struct report_parser *rp, uint32_t *usage, int64_t *value)
|
||||
{
|
||||
while (rp->field < rp->report->fields_len) {
|
||||
const struct parser_field *f = &rp->report->fields[rp->field];
|
||||
if (!f->report_count) {
|
||||
rp->field++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t u = 0;
|
||||
if (rp->subfield < f->usages_len)
|
||||
u = f->usages[rp->subfield];
|
||||
int64_t v = report_parser_extract(
|
||||
rp, f->report_size, f->logical_minimum < 0);
|
||||
|
||||
if (++rp->subfield == f->report_count) {
|
||||
rp->field++;
|
||||
rp->subfield = 0;
|
||||
}
|
||||
|
||||
// Cannot make use of these.
|
||||
if (f->array__variable == 0 || f->absolute__relative == 1)
|
||||
continue;
|
||||
|
||||
*usage = u;
|
||||
*value = v;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- UPS abstraction ---------------------------------------------------------
|
||||
|
||||
struct ups_usage_output {
|
||||
uint32_t usage; ///< Extended usage, including page
|
||||
int64_t value; ///< Value
|
||||
};
|
||||
|
||||
static int
|
||||
ups_usage_output_cmp(const void *a, const void *b)
|
||||
{
|
||||
const struct ups_usage_output *aa = (const struct ups_usage_output *) a;
|
||||
const struct ups_usage_output *bb = (const struct ups_usage_output *) b;
|
||||
return (aa->usage > bb->usage) - (aa->usage < bb->usage);
|
||||
}
|
||||
|
||||
struct ups {
|
||||
hid_device *dev; ///< HID device handle
|
||||
const struct hid_device_info *info; ///< HID device information
|
||||
struct parser parser; ///< Parsed USB HID report descriptor
|
||||
|
||||
bool useful_reports[PARSER_REPORT_LIMIT];
|
||||
struct ups_usage_output fields[256];
|
||||
size_t fields_len;
|
||||
|
||||
// As a theme, we spend memory in order to limit code and dependencies.
|
||||
char error[1024];
|
||||
};
|
||||
|
||||
static int
|
||||
ups_printid(const struct ups *u, char *buffer, size_t len)
|
||||
{
|
||||
if (!*u->info->manufacturer_string)
|
||||
return snprintf(buffer, len, "%s(%04x:%04x): ",
|
||||
u->info->path, u->info->vendor_id, u->info->product_id);
|
||||
else if (!wcsncmp(u->info->manufacturer_string, u->info->product_string,
|
||||
wcslen(u->info->manufacturer_string)))
|
||||
return snprintf(buffer, len, "%s(%ls): ",
|
||||
u->info->path, u->info->product_string);
|
||||
else
|
||||
return snprintf(buffer, len, "%s(%ls %ls): ", u->info->path,
|
||||
u->info->manufacturer_string, u->info->product_string);
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_failf(struct ups *u, const char *format, ...)
|
||||
ATTRIBUTE_PRINTF(2, 3);
|
||||
|
||||
static bool
|
||||
ups_failf(struct ups *u, const char *format, ...)
|
||||
{
|
||||
int len = ups_printid(u, u->error, sizeof u->error);
|
||||
if (len >= 0) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
(void) vsnprintf(u->error + len, sizeof u->error - len, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
ups_find_feature_report_with_usage(struct ups *u, uint32_t usage)
|
||||
{
|
||||
for (size_t i = 0; i < PARSER_REPORT_LIMIT; i++) {
|
||||
struct parser_report *r = &u->parser.feature[i];
|
||||
if (!r->report_id || r->application_usage != USB_HID_USAGE__POWER__UPS)
|
||||
continue;
|
||||
|
||||
for (size_t k = 0; k < r->fields_len; k++) {
|
||||
struct parser_field *f = &r->fields[k];
|
||||
|
||||
// Cannot make use of these.
|
||||
if (f->array__variable == 0 || f->absolute__relative == 1)
|
||||
continue;
|
||||
|
||||
for (size_t u = 0; u < f->usages_len; u++)
|
||||
if (f->usages[u] == usage)
|
||||
return r->report_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const uint32_t ups_required_usages[] = {
|
||||
USB_HID_USAGE__BATTERY_SYSTEM__AC_PRESENT,
|
||||
USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_CAPACITY,
|
||||
USB_HID_USAGE__BATTERY_SYSTEM__RUN_TIME_TO_EMPTY,
|
||||
USB_HID_USAGE__POWER__PERCENT_LOAD,
|
||||
0
|
||||
};
|
||||
|
||||
static bool
|
||||
ups_is_required_usage(uint32_t usage)
|
||||
{
|
||||
for (size_t i = 0; ups_required_usages[i]; i++)
|
||||
if (ups_required_usages[i] == usage)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_is_compatible(struct ups *u)
|
||||
{
|
||||
for (size_t i = 0; ups_required_usages[i]; i++) {
|
||||
unsigned report_id =
|
||||
ups_find_feature_report_with_usage(u, ups_required_usages[i]);
|
||||
if (!report_id)
|
||||
return ups_failf(u, "usage not found: %s",
|
||||
usb_hid_usage_to_string_full(ups_required_usages[i]));
|
||||
u->useful_reports[report_id] = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_open(struct ups *u, const struct hid_device_info *info)
|
||||
{
|
||||
#if 0
|
||||
// On some systems, we get one Application collection per logical device.
|
||||
// However, on systems where this is not the case, if there are
|
||||
// more such collections, these fields only cover the first one of them.
|
||||
// We'll use the parsed descriptor to figure out compatibility.
|
||||
uint32_t usage = info->usage_page << 16 | info->usage;
|
||||
if (usage && usage != USB_HID_USAGE__POWER__UPS)
|
||||
return ups_failf(u, "unexpected HID usage");
|
||||
#endif
|
||||
|
||||
u->info = info;
|
||||
hid_device *dev = hid_open_path(info->path);
|
||||
if (!dev) {
|
||||
ups_failf(u, "%ls", hid_error(NULL));
|
||||
goto out1;
|
||||
}
|
||||
|
||||
u->dev = dev;
|
||||
u->info = hid_get_device_info(dev);
|
||||
if (!u->info) {
|
||||
ups_failf(u, "%ls", hid_error(NULL));
|
||||
goto out2;
|
||||
}
|
||||
|
||||
uint8_t descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE] = {};
|
||||
int len = hid_get_report_descriptor(dev, descriptor, sizeof descriptor);
|
||||
if (len < 0) {
|
||||
ups_failf(u, "failed to read report descriptor");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
const char *err = parse_descriptor(&u->parser, descriptor, len);
|
||||
if (err) {
|
||||
ups_failf(u, "failed to parse report descriptor: %s", err);
|
||||
goto out2;
|
||||
}
|
||||
|
||||
if (ups_is_compatible(u))
|
||||
return true;
|
||||
|
||||
out2:
|
||||
hid_close(dev);
|
||||
out1:
|
||||
u->dev = NULL;
|
||||
u->info = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
ups_close(struct ups *u)
|
||||
{
|
||||
if (u->dev)
|
||||
hid_close(u->dev);
|
||||
|
||||
*u = (struct ups) {};
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static size_t
|
||||
ups_report_len(const struct parser_report *r)
|
||||
{
|
||||
size_t total_bits = 0;
|
||||
for (size_t i = 0; i < r->fields_len; i++)
|
||||
total_bits += r->fields[i].report_size * r->fields[i].report_count;
|
||||
return (total_bits + 7) / 8;
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_parse_report_field(
|
||||
struct ups *u, const struct ups_usage_output field, bool filter)
|
||||
{
|
||||
// Update values of the same usage.
|
||||
//
|
||||
// This is not ideal, because, e.g., Voltage can figure in multiple
|
||||
// Power page physical collections: Power Summary, Input, Output.
|
||||
//
|
||||
// It also causes spurious change reports.
|
||||
for (size_t i = 0; i < u->fields_len; i++) {
|
||||
if (u->fields[i].usage == field.usage) {
|
||||
bool changed = u->fields[i].value != field.value;
|
||||
u->fields[i].value = field.value;
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
if (filter && !ups_is_required_usage(field.usage))
|
||||
return false;
|
||||
if (u->fields_len == countof(u->fields)) {
|
||||
fprintf(stderr, "warning: too many fields\n");
|
||||
return false;
|
||||
}
|
||||
u->fields[u->fields_len++] = field;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_parse_report(struct ups *u, const struct parser_report *r,
|
||||
const uint8_t *data, size_t len, bool filter)
|
||||
{
|
||||
struct report_parser rp = report_parser_make(r, data, len);
|
||||
struct ups_usage_output field = {};
|
||||
bool changed = false;
|
||||
while (report_parser_parse(&rp, &field.usage, &field.value))
|
||||
if (ups_parse_report_field(u, field, filter))
|
||||
changed = true;
|
||||
|
||||
qsort(u->fields, u->fields_len, sizeof u->fields[0], ups_usage_output_cmp);
|
||||
return changed;
|
||||
}
|
||||
|
||||
static void
|
||||
ups_dump(const struct ups *u)
|
||||
{
|
||||
printf("%s,%ls,%ls,%ls", u->info->path, u->info->manufacturer_string,
|
||||
u->info->product_string, u->info->serial_number);
|
||||
for (size_t i = 0; i < u->fields_len; i++)
|
||||
if (u->fields[i].usage)
|
||||
printf(",%" PRId64, u->fields[i].value);
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/// Retrieve all reports which contain the data we're interested in.
|
||||
static bool
|
||||
ups_rescan(struct ups *u, int verbose, bool *changed)
|
||||
{
|
||||
for (unsigned i = 0; i < countof(u->useful_reports); i++) {
|
||||
if (verbose < 2 && !u->useful_reports[i])
|
||||
continue;
|
||||
|
||||
const struct parser_report *r = &u->parser.feature[i];
|
||||
if (!r->report_id)
|
||||
continue;
|
||||
|
||||
size_t len = 1 + ups_report_len(r);
|
||||
uint8_t buf[1024] = {i};
|
||||
if (hid_get_feature_report(u->dev, buf, len) < 0) {
|
||||
ups_failf(u, "Get_Feature failed: %ls", hid_error(u->dev));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ups_parse_report(u, r, buf + 1, len - 1, !verbose))
|
||||
*changed = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ups_watch(struct ups *u, int interval, int verbose)
|
||||
{
|
||||
int until_rescan = interval;
|
||||
|
||||
uint8_t buf[1024] = {};
|
||||
int res = 0;
|
||||
while (true) {
|
||||
int64_t stamp = get_timestamp_ms();
|
||||
if ((res = hid_read_timeout(
|
||||
u->dev, buf, sizeof buf, until_rescan)) < 0)
|
||||
return ups_failf(u, "watch: %ls", hid_read_error(u->dev));
|
||||
if (interval >= 0) {
|
||||
if ((until_rescan -= get_timestamp_ms() - stamp) < 0)
|
||||
until_rescan = 0;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
// Non-negative intervals allow a timeout:
|
||||
// do a full rescan, and prime the next interval.
|
||||
until_rescan = interval;
|
||||
|
||||
bool changed = false;
|
||||
if (!ups_rescan(u, verbose, &changed))
|
||||
return false;
|
||||
if (changed)
|
||||
ups_dump(u);
|
||||
} else {
|
||||
const struct parser_report *r = &u->parser.input[buf[0]];
|
||||
if (r->report_id && ups_parse_report(u, r, buf + 1, res - 1, true))
|
||||
ups_dump(u);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Tests -------------------------------------------------------------------
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
static bool
|
||||
test_parse_descriptor_file(const char *path)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
perror(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[65535] = {};
|
||||
size_t len = fread(data, 1, sizeof data, fp);
|
||||
if (ferror(fp)) {
|
||||
perror(path);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
struct ups u = {.info = &(struct hid_device_info) {
|
||||
.path = "",
|
||||
.manufacturer_string = L"Test",
|
||||
.product_string = L"Test",
|
||||
}};
|
||||
const char *err = parse_descriptor(&u.parser, data, len);
|
||||
if (err) {
|
||||
fprintf(stderr, "%s: failed to parse report descriptor: %s\n",
|
||||
path, err);
|
||||
return false;
|
||||
}
|
||||
if (!ups_is_compatible(&u)) {
|
||||
fprintf(stderr, "%s: incompatible: %s\n", path, u.error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
// The most complex part of this program is the report descriptor parser,
|
||||
// so that's what we'll test. All passed files are expected to be valid
|
||||
// and useful descriptors of a UPS-class device.
|
||||
int status = 0;
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (!test_parse_descriptor_file(argv[i]))
|
||||
status = 1;
|
||||
return status;
|
||||
}
|
||||
|
||||
#define main main_shadowed
|
||||
#endif // TESTING
|
||||
|
||||
// --- Main --------------------------------------------------------------------
|
||||
|
||||
static const char *
|
||||
run(struct ups *devices, size_t len, bool watch, int interval, int verbose)
|
||||
{
|
||||
if (!len)
|
||||
return "no devices found";
|
||||
|
||||
// The CSV format is inconvenient, as verbose output would be heterogenous.
|
||||
// We could realistically switch to JSON, as we only need to produce it,
|
||||
// not parse it, and that isn't too hard.
|
||||
if (len > 1 && verbose) {
|
||||
fprintf(stderr,
|
||||
"warning: multiple devices, disabling verbose output\n");
|
||||
verbose = 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
struct ups *u = &devices[i];
|
||||
bool changed = false;
|
||||
if (!ups_rescan(u, verbose, &changed))
|
||||
return u->error;
|
||||
|
||||
// In non-verbose mode, the order must be made the same for all fields.
|
||||
if (u == devices) {
|
||||
printf("path,manufacturer,product,serial");
|
||||
for (size_t i = 0; i < u->fields_len; i++)
|
||||
if (u->fields[i].usage)
|
||||
printf(",%s", usb_hid_usage_to_string(u->fields[i].usage));
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
ups_dump(u);
|
||||
}
|
||||
if (watch) {
|
||||
// TODO(p): We could support multiple devices, but it's tricky reading
|
||||
// from all of them at once. Options:
|
||||
// - Run hid_read_timeout() for all of them in a sequence.
|
||||
// But any lags are in principle undesired.
|
||||
// - Run this from multiple threads. This is a bit more code.
|
||||
if (len > 1)
|
||||
fprintf(stderr, "warning: multiple devices, watching the first\n");
|
||||
|
||||
struct ups *u = devices;
|
||||
if (!ups_watch(u, interval, verbose))
|
||||
return u->error;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *usage = "Usage: %s OPTION...\n\n"
|
||||
" -l, --list List all recognised UPS devices.\n"
|
||||
" -n, --nowatch Do not watch for changes.\n"
|
||||
" -i, --interval Polling interval in milliseconds, negative to disable.\n"
|
||||
" -v, --verbose Output a bit more information.\n"
|
||||
" -h, --help Display this help and exit.\n"
|
||||
" -V, --version Output version information and exit.\n";
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
const char *name = argv[0];
|
||||
static struct option opts[] = {
|
||||
{"list", no_argument, NULL, 'l'},
|
||||
{"nowatch", no_argument, NULL, 'n'},
|
||||
{"interval", required_argument, NULL, 'i'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"verbose", no_argument, NULL, 'v'},
|
||||
{"version", no_argument, NULL, 'V'},
|
||||
{}
|
||||
};
|
||||
|
||||
// Some devices' Input reports simply never happen.
|
||||
// Eaton UPS Companion scans everything in 3-second intervals.
|
||||
//
|
||||
// On the other hand, CyberPower's continuous reporting makes our rescans
|
||||
// let the interrupt queue fill up, after which we may pick up slightly
|
||||
// older data from there. It creates an opportunity for flip-flopping.
|
||||
long interval = 5000;
|
||||
|
||||
bool list = false, watch = true;
|
||||
int verbose = 0;
|
||||
int c = 0;
|
||||
while ((c = getopt_long(argc, argv, "lni:vhV", opts, NULL)) != -1)
|
||||
switch (c) {
|
||||
case 'l':
|
||||
list = true;
|
||||
break;
|
||||
case 'n':
|
||||
watch = false;
|
||||
break;
|
||||
case 'i':
|
||||
if ((interval = strtol(optarg, NULL, 10)) < 0)
|
||||
interval = -1;
|
||||
else if (interval > INT_MAX)
|
||||
interval = INT_MAX;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'h':
|
||||
printf(usage, name);
|
||||
return 0;
|
||||
case 'V':
|
||||
printf(PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
||||
return 0;
|
||||
default:
|
||||
fprintf(stderr, "Unknown option\n");
|
||||
fprintf(stderr, usage, name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc != 0) {
|
||||
fprintf(stderr, usage, name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is safe to call repeatedly, it just might reset the locale.
|
||||
// It's actually not needed, so we might even leave it out.
|
||||
if (hid_init()) {
|
||||
fprintf(stderr, "error: %ls\n", hid_error(NULL));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not many of these structures would fit on the stack.
|
||||
static struct ups devices[16] = {};
|
||||
size_t devices_len = 0;
|
||||
|
||||
struct hid_device_info *devs = hid_enumerate(0, 0), *p = devs;
|
||||
for (; p; p = p->next) {
|
||||
if (devices_len == countof(devices)) {
|
||||
fprintf(stderr, "warning: too many devices, skipping the rest");
|
||||
break;
|
||||
}
|
||||
|
||||
struct ups *u = &devices[devices_len];
|
||||
if (ups_open(u, p))
|
||||
devices_len++;
|
||||
else if (verbose)
|
||||
fprintf(stderr, "error: %s\n", u->error);
|
||||
}
|
||||
hid_free_enumeration(devs);
|
||||
|
||||
const char *err = NULL;
|
||||
if (list) {
|
||||
for (size_t i = 0; i < devices_len; i++) {
|
||||
struct ups *u = &devices[i];
|
||||
printf("path,manufacturer,product,serial\n%s,%ls,%ls,%ls\n",
|
||||
u->info->path, u->info->manufacturer_string,
|
||||
u->info->product_string, u->info->serial_number);
|
||||
}
|
||||
} else if ((err = run(devices, devices_len, watch, interval, verbose))) {
|
||||
fprintf(stderr, "error: %s\n", err);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < devices_len; i++)
|
||||
ups_close(&devices[i]);
|
||||
return err != NULL;
|
||||
}
|
||||
Reference in New Issue
Block a user