API Access on Linux
Our current USBC-TKEY-based setup for measurement of power consumption uses a Ubuntu 20.04 machine for tracking the parameters on the VBUS line. Since the USBC-TKEY has a command line interface accessed over the serial port, we had developed a Python script to record a CSV file with the required data. In order to experiment with the APIs provided by ChargerLAB for the KM003C, a SanDisk Professional PRO G40 Thunderbolt 3 portable SSD was connected to a Thunderbolt 3 port of a Quartz Canyon NUC with the KM003C acting as a man-in-the-middle (additional details in a later section discussing the KM003C in action). The HID port of the KM003C was connected to the Ubuntu machine using a Type-C to Type-A cable.
The API document makes a note of the KM003C presenting itself with three bidirectional interfaces. As only Windows support is claimed, it is no surprise that only the HID endpoints get exposed via the usbhid driver. The WinUSB endpoints belong to a vendor specific class, and the virtual serial port appears as a CDC data interface.
In exploring the KM003C as an alternative to the USBC-TKEY, we wanted to retain the Python script infrastructure (because it tied in well with the other components of our direct-attached storage evaluation infrastructure). It turned out that Python's libusb module could act as a WinUSB alternative with its ability to access the endpoints under the Vendor Specific Class. This module requires the end user to be aware of the raw data that needs to be sent across the interface, and the format in which data is returned on the other endpoint.
The relevant modules to import in Python3 for libusb support are usb.core, usb.util, and usb.control. Since the vendor and product ID of the KM003C are known from the output of the usbdevices command, they are used directly to open the device for access through the script. Reliable scripting needs to ensure that the device is successfully found. For demonstration purposes, the variable contents are displayed, as shown above.
While the libusb module aims to be cross-platform, we did find that the steps below to detach the kernel driver did not work on Windows. libusb needs exclusive access to all the interfaces over which communication is expected to take place. For demonstration purposes, the code extract above cycles through a hard-coded number of interfaces (4), but it is possible to obtain that number from other functions in the module. The extract checks if an interface has a kernel driver attached to it, and detaches it in that case. It then proceeds to claim the interface for access through the script's session.
In order to retrieve the power consumption data from the device, a command needs to be transferred via the EP 1 OUT endpoint (with endpoint address 0x01). As ChargerLAB mentions, we can also use the EP 5 OUT and EP 3 OUT ones at addresses 0x5 and 0x3, but the response needs to be monitored at the corresponding EP IN endpoint. The command itself is in the form of a 32-bit word, and its encoding is detailed in the API document. In the script extract below, the 32-bit word is 0x0C000200. It corresponds to a GET_DATA command for the ADC data with a command ID of 0x0. I found during the course of experimentation that the ID in [23:16] could be varied from 0x0 to 0xFF without any loss in functionality. Since the command remains the same throughout the reading loop (we attempt to read out the power data 20 times with a gap of 100ms between each read), the command word is initialized outside the loop.
The first step in the loop is to obtain a timestamp for the command written out to endpoint address 0x1. This write is followed by the read to endpoint address 0x81 (the EP IN corresponding to the EP OUT used in the write). The read is blocking, i.e, it waits for the data to be sent back by the device and there is a default timeout associated with this command. The received data is then parsed out into appropriate components (instantaneous VBUS voltage and current, and the averaged VBUS voltage and current) based on the struct specified in the API document.
The HID demonstration code (supplied as a Visual Studio 2019 project) tagged along with the API document read out the averaged voltage and current. The documentation did not specify the averaging duration or the sampling rate. In the initial trials, the code did not read out the instantaneous values, and I was left frustrated as to why the averaged value was changing only once every second instead of getting updated for every read out. I spent quite a bit of time trying out the same code with different endpoints, and even different OSes. I even went to the extent of using Wireshark along with usbpcap in an attempt to trace the commands sent and data received by ChargeLAB's closed-source Windows software. It was only after careful analysis of the packet capture did I realize that the average VBUS voltage and currents were changing only once ever second even in the traffic triggered by the software. The instantaneous data of interest actually turned out to be the first two words in the returned ADC data struct. The time taken for the read process and display of the CSV data is computed and the code segment is left idle before starting the next loop iteration 100ms later. Each line in the output data stream shows the timestamp, instantaneous voltage, instantaneous current, and instantaneous computed power, followed by the averaged versions of all three. Given the details of the averaging process, it is possible that the computed averaged power consumption can also be interpreted as the energy consumed by the sink (device, in this case) since the last change in that parameter.
As a final step in the script, a cleanup is performed so that all open accesses to the device via libusb are flushed out, and the device is made accessible to other programs. The script can then be terminated safely.
ncG1vNJzZmivp6x7orrAp5utnZOde6S7zGiqoaenZH55hZNtZq6rkph6sbvWnqlmpZWpsrO1zaBksKGknXq1tMRmmqGZopyys7jAm2SkpWBlgKR5wGaeqKeXobJuwNaipaShlWKurcDEq6WarJmrsnB%2F