Skip to content

Experimental libwinhid backend for usbhid-ups (Windows only)#3335

Open
MysteryDove wants to merge 9 commits intonetworkupstools:masterfrom
MysteryDove:experimental_libwinhid
Open

Experimental libwinhid backend for usbhid-ups (Windows only)#3335
MysteryDove wants to merge 9 commits intonetworkupstools:masterfrom
MysteryDove:experimental_libwinhid

Conversation

@MysteryDove
Copy link
Contributor

@MysteryDove MysteryDove commented Mar 3, 2026

Proof of concept stage

While working on my own STM32 project to simulate USB HID UPS, I've made a python script to read windows HID API and it works as expected. So I'm looking for some ways to port that so I can use nut on windows without any additional drivers.

I'm working on an experimental, native Windows HID backend (libwinhid) for the usbhid-ups driver to try avoid the libusb's problem of fetching raw bytes from Windows system claimed HID UPS device. So that no extra driver like WinUSB or Zadig is needed.

For enumeration I'm using setupapi.dll

For HID device:

  • HidD_GetPreparsedData and HidP_GetCaps to read collection mappings.
  • HidD_GetFeature / HidD_SetFeature to handle feature reports.

The current implementation is kinda messy, takes Windows capabilities to reconstruct a descriptor, route back to Parse_ReportDesc() to parse that, And continue the remaining logic as untouched, but few functions are introduced here to deal with improper HID descriptor Parsing. The code is not cleaned and completly tested yet, but it's working now with apchid subdriver with correct data reading now.

To use current updated code, pollonly and experimentalhid(newly added) is required:

[usbups]

    experimentalhid

    pollonly

    driver = usbhid-ups

    port = auto

Looking for test result and idea how to improve this to make the code works better

@AppVeyorBot
Copy link

@jimklimov
Copy link
Member

This looks promising, thanks! The indentation is a bit jumpy when I looked at it in GitHub WebUI (tabs vs spaces?) and the two aliases for exp(e)rimentalhid look odd (IMHO one (use)winhid would be more to the point).

Notably, does the resulting program need linking with libusb{0,1}.c if you use a different backend? Is the LibUSB "proper" still used here?

Also, I wonder if the methods you've added to usbhid-ups.c driver sources make more sense in e.g. libhid.c so other USB-capable drivers could take advantage of them eventually?

@MysteryDove
Copy link
Contributor Author

This looks promising, thanks! The indentation is a bit jumpy when I looked at it in GitHub WebUI (tabs vs spaces?) and the two aliases for exp(e)rimentalhid look odd (IMHO one (use)winhid would be more to the point).

Notably, does the resulting program need linking with libusb{0,1}.c if you use a different backend? Is the LibUSB "proper" still used here?

Also, I wonder if the methods you've added to usbhid-ups.c driver sources make more sense in e.g. libhid.c so other USB-capable drivers could take advantage of them eventually?

I have make few rounds of refactoring of this patch, beginning with using IOCTL_HID_GET_REPORT_DESCRIPTOR and other IOCTL functions but get no luck with same result and behavior like current libusb. Some of the code is not cleaned or reused with current code, also I'm working on diffenrent devices so the config of IDE might affect indentation. During testing of the code the linking is still remained, will check that later.

Thanks for the advice and I will make the code cleaner.

@MysteryDove
Copy link
Contributor Author

MysteryDove commented Mar 3, 2026

For the second part, my goal is make it acting like a replacement of libusb under windows to provide alternative of transporting data besides libusb for HID devices ONLY (But it has drawback of not getting any raw data from USB interface), I think current code should already be capable for testing with other USB-capable drivers (I'm assuming all USB-capable drivers are a sub-driver of usbhid-ups since I'm not having full context of the NUT project, and also windows hid api obviously works straighfoward with usbhid-ups)

@jimklimov
Copy link
Member

assuming all USB-capable drivers are a sub-driver of usbhid-ups

Well, actually yes, for USB HID that should be the case. Other USB cases include numerous Megatec Qx protocol handlers, mostly under nutdrv_qx umbrella now, or Modbus USB. But if your effort is specifically about HID, those drivers are probably not much interested indeed.

Skimming through sources, I see that drivers/apc_modbus.c includes "libhid.h" and mge_shut_SOURCES include libhid.c but not quickly sure further to what end.

@MysteryDove
Copy link
Contributor Author

@jimklimov Fixed for typo and indent now, Under assist of Claude Opus 4.6, A major refactor is also done

I've walk through mge_shut and apc_modbus, the former one seems like a usb over serial things to use libhid for parsing only, and the latter one uses libhid to extract report id for finding modbus data payload report. So I think the libwinhid is not applicable for them.

For newly added libwinhid, it is runtime independent and it won't change any original libusb behavior if experimentalhid not present under ups.conf, I believe it is not a breaking change.

@MysteryDove MysteryDove marked this pull request as ready for review March 4, 2026 11:14
@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@MysteryDove
Copy link
Contributor Author

For those who interested in this patch and want help testing, here's a build Windows x64 release: https://github.com/MysteryDove/nut/releases/tag/v2.8.4-exphid01

@jimklimov jimklimov added the AI For good or bad, machine tools are upon us. Humans are still the responsible ones. label Mar 6, 2026
…inhid

Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
…rt for usbhid-ups [networkupstools#3335]

Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
@jimklimov
Copy link
Member

@MysteryDove : just in case, to confirm: while the driver may now link against some libusb{0,1} and winhid backends simultaneously, this just provides a run-time choice and libusb code path/methods are never called if winhid is chosen?

To make the PR more merge-ready, I've added commits with a NEWS entry and with renaming of the config flag from experimentalhid to winhid, so it is more to the point in end-user builds. Please be sure to not force-push over these :)

@MysteryDove
Copy link
Contributor Author

MysteryDove commented Mar 8, 2026

@MysteryDove : just in case, to confirm: while the driver may now link against some libusb{0,1} and winhid backends simultaneously, this just provides a run-time choice and libusb code path/methods are never called if winhid is chosen?

To make the PR more merge-ready, I've added commits with a NEWS entry and with renaming of the config flag from experimentalhid to winhid, so it is more to the point in end-user builds. Please be sure to not force-push over these :)

For the first part, the simultaneous linkage just gives a runtime backend choice, maybe some shared warning/debug message/error handle is reused, like this warn_if_bad_usb_port_filename is reaching to usb-common.c

	upsdebugx(2, "Initializing an USB-connected UPS with backend %s " \
		"(NUT subdriver name='%s' ver='%s')",
		transport_backend ? transport_backend : "(unknown)",
		comm_driver->name, comm_driver->version );

	warn_if_bad_usb_port_filename(device_path);

Winhid backend also uses LIBUSB_ERROR_* for now but that's for compatibility:

static int winhid_map_winerr_to_libusb(const DWORD err)
{
	switch (err) {
	case ERROR_SUCCESS:
		return 0;
	case ERROR_ACCESS_DENIED:
		return LIBUSB_ERROR_ACCESS;
	case ERROR_SHARING_VIOLATION:
		return LIBUSB_ERROR_BUSY;
	case ERROR_NOT_ENOUGH_MEMORY:
	case ERROR_OUTOFMEMORY:
		return LIBUSB_ERROR_NO_MEM;
	case ERROR_FILE_NOT_FOUND:
	case ERROR_PATH_NOT_FOUND:
		return LIBUSB_ERROR_NOT_FOUND;
	case ERROR_DEVICE_NOT_CONNECTED:
	case ERROR_INVALID_HANDLE:
		return LIBUSB_ERROR_NO_DEVICE;
	case ERROR_SEM_TIMEOUT:
		return LIBUSB_ERROR_TIMEOUT;
	case ERROR_INVALID_FUNCTION:
		return LIBUSB_ERROR_NOT_SUPPORTED;
	default:
		return LIBUSB_ERROR_IO;
	}
}

the libwinhid shares usb_communication_subdriver_t ABI in nut_libusb.h, but the tricky part is:

  • nut_libusb.h includes usb-common.h
  • usb-common.h hard fails if no libusb is configured
#if (!WITH_LIBUSB_1_0) && (!WITH_LIBUSB_0_1)
#error "configure script error: Neither WITH_LIBUSB_1_0 nor WITH_LIBUSB_0_1 is set"
#endif
  • And then usb-common.h includes libusb.h
/* Select version-specific libusb header file and define a sort of
 * "Compatibility layer" between libusb 0.1 and 1.0
 */
#if WITH_LIBUSB_1_0
# include <libusb.h>

I'm suspecting changing current code with backend-neutral transport ABI/typedefs/device handle type might introduce duplicated code and have impact or risk here. Since I'm not familiar with build system and makefile, I'd rather keep current situation.

@jimklimov
Copy link
Member

Aha, ok, thanks for clarification! Maybe later some configure.ac/m4 snippet can take care of detecting winhid support in build environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI For good or bad, machine tools are upon us. Humans are still the responsible ones. enhancement USB Windows

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants