Introduction
This tip describes how to access the USB temperature sensor in a C# forms application by using the linux driver as starting point. Nowhere on the web is it described how to access the device in Windows, only for linux/python.
Background
The original linux/python driver can be found here: https://github.com/padelt/temper-python/blob/master/temperusb/temper.py and is used as referenced in this article. Also this document: http://www.usb.org/developers/hidpage/HID1_11.pdf contains detailed USB hid information.
Using the Code
Reverse engineering must be done to convert the driver mentioned in the background information, into a working Windows Forms application.
To have a quick start, the following framework is used: HidLibrary
(https://github.com/mikeobrien/HidLibrary/tree/master/src) this Library gives you a basic USB framework to access HID devices. I have tested various solutions (e.g. http://www.developerfusion.com/article/84338/making-usb-c-friendly/) but none of them was complete.
Now that a basic USB framework is available, we can start to reverse-engineer the linux driver.
The driver contains three major steps:
- Find the device
- Initialize the device
- Start polling the temperature
I will explain per step how to achieve this in a C# forms application by using the HidLibrary
.
Find the Device
To find the device, the HidLibrary
supplies the Enumerate
function which retrieves all the USB devices in the system.
HidDevice[] _deviceList = HidDevices.Enumerate().ToArray();
Next, we have to find the device. From the linux source can be read in line 18-20 that the PID
(Product ID) and VID
(Vendor ID) are respectively 0x7401 and 0x0C45. And bear in mind that one device can have multiple interfaces, also in this case.
List<HidDevice> _TemperInterfaces = _deviceList.Where
(x => x.Attributes.ProductHexId == "0x7401" & x.Attributes.VendorHexId == "0x0C45").ToList();
HidDevice Control = _TemperInterfaces.Find(x => x.DevicePath.Contains("mi_00"));
HidDevice Bulk = _TemperInterfaces.Find(x => x.DevicePath.Contains("mi_01"));
More information about control and bulk interfaces be found here: http://www.usb.org/developers/hidpage/HID1_11.pdf and the device can be analyzed using the usb view tool which can be found here: http://www.softpedia.com/get/System/System-Info/USBView.shtml.
Initialize the Device
To initialize the device, several parts of the source must be taken into account:
- Magic number in line 31-33, device specific initializers
- Two interfaces that were detected during detection (
Control
and Bulk
)
- Different way of accessing between linux (
device_control_transfer
) and windows (reports).
Let’s start with the difference in accessing the USB bus. Windows uses a default device driver to access HID devices by using reports. Within the HidLibrary
, 3 kinds of reports are available:
Inputreport
(input from device to host)
Outputreport
(output from host to device)
Featurereport
(configuration data in and out)
All 3 reports are available on the two interfaces, so to see exactly what to address and which report to use, the linux code must be analyzed like line 189:
device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0200,
wIndex=0x01, data_or_wLength=data, timeout=TIMEOUT)
By using the USB definition (PDF above) can be concluded that:
bmRequestType=0x21
bRequest=0x09 (Set_Report)
wValue=0x0200 (0x02= output report, 0x00 is Report ID 0)
wIndex=0x01 (interface 1)
This is an OutputReport
with ReportID
0x00 on interface 1 (which is the Bulk
interface).
Another example is line 147:
device.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0201, _
wIndex=0x00, data_or_wLength='\x01\x01', timeout=TIMEOUT)
bmRequestType=0x21
bRequest=0x09 (Set_Report)
wValue=0x0201 (0x02= output report, 0x01 is Report ID 1)
wIndex=0x00 (interface 0)
This is an OutputReport
with Report ID 0x01 on interface 0 (which is the Control interface). The data data_or_wLength='\x01\x01'
is an inline coded magic string which will be discussed later.
The examples above explain the use of the two different interfaces since wIndex
defines the interface which needs to be used and leaves us to the use of the magic strings in line 31-33:
'temp': '\x01\x80\x33\x01\x00\x00\x00\x00',
'ini1': '\x01\x82\x77\x01\x00\x00\x00\x00',
'ini2': '\x01\x86\xff\x01\x00\x00\x00\x00',
And basically the sequence in line 147 is also a magic string ('\x01\x01').
These magic strings (4) are used in a sequence to enable/activate the USB device and this sequence can be found in the source (lines 147-159) with the related ReportID
and interface based on the explanation above:
- Send
OutputReport
with data ='\x01\x01'
and report ID = 0x01
on the Control interface and catch the response but don’t do anything with the response.
- Send
OutputReport (temp)
with data = '\x01\x80\x33\x01\x00\x00\x00\x00'
and report ID = 0x00
on the Bulk
interface and catch the response but don’t do anything with the response.
- Send
OutputReport (ini1)
with data = '\x01\x82\x77\x01\x00\x00\x00\x00'
and report ID = 0x00
on the Bulk
interface and catch the response but don’t do anything with the response.
- Send
OutputReport (ini2)
with data = '\x01\x86\xff\x01\x00\x00\x00\x00'
and report ID = 0x00
on the Bulk
interface and catch the response but don’t do anything with the response.
And from now on by sending OutputReport (temp)
with data = '\x01\x80\x33\x01\x00\x00\x00\x00'
and report ID = 0x00
on the Bulk
interface, will result in a response which contains the temperature reading by the device.
After some fiddling, I found out that the second sequence (temp
) can be delayed after ini2
which clears out any garbage data in the device. So the final sequence looks like this: 1,3,4,3,3
and then the device is initialized and delivers clean data repeatedly.
Start polling the temperature
After initialization and starting to readback the data from the device, it still needs to be converted into a real temperature. The python source (Line 170-180) contains the code to convert the raw data into temperature (C and F). Device measurement errors must be fixed in the software with calibration data.
Within the received report, (Report.Data
) Byte 2 and 3 contains the measurement and must be combined to one integer:
int RawReading = (report.Data[3] & 0xFF) + (report.Data[2] << 8);
From line 172 can be concluded that the integer must be converted to a double
by this formula:
double temperatureCelsius = RawReading * (125.0 / 32000.0);
And to calibrate the device, this formula must be combined with a scale and offset like:
double temperatureCelsius = (Calibration_Scale*(RawReading * (125.0 / 32000.0))) + Calibration_Offset;
To convert the degree Celsius into Fahrenheit, the following formula is used:
double temperatureFahrenheit = temperatureCelsius * 1.8 + 32.0;
Points of Interest
I have learned that it’s possible to access HID devices without any specific driver, by using the HidLibrary
. And nowhere is it written clearly how to translate linux drivers into Windows drivers/application. Hopefully, this tip will help others to use the Temper USB device for Windows application. Besides the USB transmission also Manufacturer and product data can be retrieved. This data in unicoded in a byte array. The sources contain the solution for accessing this data.