A Delphi translation of USB HID relay DLL calls Last updated 14/08/2023
A little while ago I had a requirement to control switching mains power using Delphi. I quickly found these USB HID relay boards, quite inexpensively, on Ebay. A USB HID device sounded very promising, no drivers or COM port access required, straight plug and play. Sounds easy, but of course it wasn’t. I purchased a USB HID library for Delphi. The device showed up in enumeration, showed the structure of its “feature report” okay, but could I set the value of that report? No, absolutely not. The device showed that the report could not be written, not good considering those reports are there to be written!
Some research found a GitHub project with the C++ code, based on reverse engineering the vendor’s software, to build a Windows DLL, also a pre-compiled DLL available on this page. I would strongly advise placing the DLL in the same folder as your executable, in case some other app overwrites with a custom version in the shared folder. I don’t pretend any great knowledge of C++ but, going through the .c and .h files, the job didn’t look too difficult and so it proved. I should emphasise that this has only been tested with a two relay board, as shown above, using Delphi 11.3 running on Windows 11. Download A zip archive containing a demo project and the translation unit can be downloaded here. The required DLL is not included and should be downloaded from the GitHub page. The list of records and functions together with some notes on usage. There is only one, very simple, record: type p_usb_relay_device_info = ^t_usb_relay_device_info; t_usb_relay_device_info = record idstr: pansichar; end; function usb_relay_init(): integer; As the name implies this initialises the DLL and must be called first. Returns 0 on success. function usb_relay_exit(): integer; Called last to ensure all DLL static memory is freed. Returns void. function usb_relay_device_enumerate(): p_usb_relay_device_info; Call immediately after initialisation. Necessary to obtain the idstr (serial number) of the board. Should return a list of devices, but only enumerates relay boards and, as I only have one, not tested with multiple boards connected. function usb_relay_device_next_dev(udi: p_usb_relay_device_info): p_usb_relay_device_info; Returns 0 on failure or a pointer to the next device. As far as I can tell this works okay, no errors, but I have no way to test. function usb_relay_device_free_enumerate(udi: p_usb_relay_device_info): integer; Call before usb_relay_exit() to free enumeration list memory. Returns void; function usb_relay_device_lib_version(): integer; Returns an integer representing the DLL version. function usb_relay_device_open_with_serial_number(serial_number: pansichar; len: integer): intptr; Call this with the idstr member of a p_usb_relay_device_info record and its length. Opens a board for communications and returns a pointer to it. function usb_relay_device_close(hhandle: intptr): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number(). Terminates communication with a board. N.B. Does not affect the state of the relays on the board. function usb_relay_device_open_all_relay_channel(hhandle: intptr): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number(). Switches all relays on. Returns 0 on success. function usb_relay_device_close_all_relay_channel(hhandle: intptr): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number(). Switches all relays off. Returns 0 on success. function usb_relay_device_open_one_relay_channel(hhandle: intptr; index: integer): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number() and the index of the relay. Index is 1 based. Switches relay[index] on. Returns 0 on success. function usb_relay_device_close_one_relay_channel(hhandle: intptr; index: integer): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number() and the index of the relay. Index is 1 based. Switches relay[index] off. Returns 0 on success. function usb_relay_device_get_status(hhandle: intptr; var status: cardinal): integer; Call this with the pointer returned by usb_relay_device_open_with_serial_number() and a var. Returns 0 on success and puts an unsigned integer value into the status argument, being the sum of a byte representing up to eight channels with bits on = 1 and off = 0. With two channels this means: 0 = both channels off - bits 0 and 1 not set. 1 = channel 1 only on - bit 0 set bit 1 not set. 2 = channel 2 only on - bit 0 not set bit 1 set. 3 = both channels on - bits 0 and 1 set. function usb_relay_device_get_status_bitmap(hhandle: intptr): byte; Call this with the pointer returned by usb_relay_device_open_with_serial_number(); Returns a byte as described above. function usb_relay_device_get_id_string(udi: p_usb_relay_device_info): pansichar; I can only think this more useful in C++ than Delphi. Returns the pansichar extracted from the p_usb_relay_device_info record passed as the only argument.