mirror of
https://github.com/Fishwaldo/open-zwave.git
synced 2025-07-12 16:08:41 +00:00
necessary. Add missing ioctls for Linux HID support. Remove these when a newer linux/hidraw.h is available. Add more defensive checks in device add/remove processing. Not all controllers fill in the node field. Provide group notifications on initial group discovery. Add Aeon Labs Home Energy Monitor device defintion as well as update device info for Everspring SM103. Make sure ACT Zir010 has default values specified in configuration for initial case.
549 lines
13 KiB
C
549 lines
13 KiB
C
/*******************************************************
|
|
HIDAPI - Multi-Platform library for
|
|
communication with HID devices.
|
|
|
|
Alan Ott
|
|
Signal 11 Software
|
|
|
|
8/22/2009
|
|
Linux Version - 6/2/2009
|
|
|
|
Copyright 2009, All Rights Reserved.
|
|
|
|
At the discretion of the user of this library,
|
|
this software may be licensed under the terms of the
|
|
GNU Public License v3, a BSD-Style license, or the
|
|
original HIDAPI license as outlined in the LICENSE.txt,
|
|
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
|
files located at the root of the source distribution.
|
|
These files may also be found in the public source
|
|
code repository located at:
|
|
http://github.com/signal11/hidapi .
|
|
********************************************************/
|
|
|
|
/* C */
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <locale.h>
|
|
#include <errno.h>
|
|
|
|
/* Unix */
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/utsname.h>
|
|
#include <fcntl.h>
|
|
|
|
/* Linux */
|
|
#include <linux/hidraw.h>
|
|
#include <linux/version.h>
|
|
#include <libudev.h>
|
|
|
|
#include "hidapi.h"
|
|
|
|
/*
|
|
* Added here until linux/hidraw.h has them. These definitions require a version
|
|
* 2.6.39 based kernel as well.
|
|
*/
|
|
#ifndef HIDIOCSFEATURE
|
|
#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
|
|
#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
|
|
#endif
|
|
|
|
struct hid_device_ {
|
|
int device_handle;
|
|
int blocking;
|
|
int uses_numbered_reports;
|
|
};
|
|
|
|
|
|
static __u32 kernel_version = 0;
|
|
|
|
hid_device *new_hid_device()
|
|
{
|
|
hid_device *dev = calloc(1, sizeof(hid_device));
|
|
dev->device_handle = -1;
|
|
dev->blocking = 1;
|
|
dev->uses_numbered_reports = 0;
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void register_error(hid_device *device, const char *op)
|
|
{
|
|
|
|
}
|
|
|
|
/* Get an attribute value from a udev_device and return it as a whar_t
|
|
string. The returned string must be freed with free() when done.*/
|
|
static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
|
|
{
|
|
const char *str;
|
|
wchar_t *ret = NULL;
|
|
str = udev_device_get_sysattr_value(dev, udev_name);
|
|
if (str) {
|
|
/* Convert the string from UTF-8 to wchar_t */
|
|
size_t wlen = mbstowcs(NULL, str, 0);
|
|
ret = calloc(wlen+1, sizeof(wchar_t));
|
|
mbstowcs(ret, str, wlen+1);
|
|
ret[wlen] = 0x0000;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* uses_numbered_reports() returns 1 if report_descriptor describes a device
|
|
which contains numbered reports. */
|
|
static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
|
|
int i = 0;
|
|
int size_code;
|
|
int data_len, key_size;
|
|
|
|
while (i < size) {
|
|
int key = report_descriptor[i];
|
|
|
|
/* Check for the Report ID key */
|
|
if (key == 0x85/*Report ID*/) {
|
|
/* This device has a Report ID, which means it uses
|
|
numbered reports. */
|
|
return 1;
|
|
}
|
|
|
|
//printf("key: %02hhx\n", key);
|
|
|
|
if ((key & 0xf0) == 0xf0) {
|
|
/* This is a Long Item. The next byte contains the
|
|
length of the data section (value) for this key.
|
|
See the HID specification, version 1.11, section
|
|
6.2.2.3, titled "Long Items." */
|
|
if (i+1 < size)
|
|
data_len = report_descriptor[i+1];
|
|
else
|
|
data_len = 0; /* malformed report */
|
|
key_size = 3;
|
|
}
|
|
else {
|
|
/* This is a Short Item. The bottom two bits of the
|
|
key contain the size code for the data section
|
|
(value) for this key. Refer to the HID
|
|
specification, version 1.11, section 6.2.2.2,
|
|
titled "Short Items." */
|
|
size_code = key & 0x3;
|
|
switch (size_code) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
data_len = size_code;
|
|
break;
|
|
case 3:
|
|
data_len = 4;
|
|
break;
|
|
default:
|
|
/* Can't ever happen since size_code is & 0x3 */
|
|
data_len = 0;
|
|
break;
|
|
};
|
|
key_size = 1;
|
|
}
|
|
|
|
/* Skip over this key and it's associated data */
|
|
i += data_len + key_size;
|
|
}
|
|
|
|
/* Didn't find a Report ID key. Device doesn't use numbered reports. */
|
|
return 0;
|
|
}
|
|
|
|
static int get_device_string(hid_device *dev, const char *key, wchar_t *string, size_t maxlen)
|
|
{
|
|
struct udev *udev;
|
|
struct udev_device *udev_dev, *parent;
|
|
struct stat s;
|
|
int ret = -1;
|
|
|
|
setlocale(LC_ALL,"");
|
|
|
|
/* Create the udev object */
|
|
udev = udev_new();
|
|
if (!udev) {
|
|
printf("Can't create udev\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Get the dev_t (major/minor numbers) from the file handle. */
|
|
fstat(dev->device_handle, &s);
|
|
/* Open a udev device from the dev_t. 'c' means character device. */
|
|
udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev);
|
|
if (udev_dev) {
|
|
const char *str;
|
|
/* Find the parent USB Device */
|
|
parent = udev_device_get_parent_with_subsystem_devtype(
|
|
udev_dev,
|
|
"usb",
|
|
"usb_device");
|
|
if (parent) {
|
|
str = udev_device_get_sysattr_value(parent, key);
|
|
if (str) {
|
|
/* Convert the string from UTF-8 to wchar_t */
|
|
ret = mbstowcs(string, str, maxlen);
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
end:
|
|
udev_device_unref(parent);
|
|
// udev_dev doesn't need unref'd. Not sure why, but
|
|
// it'll throw "double free" errors.
|
|
udev_unref(udev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
|
{
|
|
struct udev *udev;
|
|
struct udev_enumerate *enumerate;
|
|
struct udev_list_entry *devices, *dev_list_entry;
|
|
struct udev_device *dev;
|
|
|
|
struct hid_device_info *root = NULL; // return object
|
|
struct hid_device_info *cur_dev = NULL;
|
|
|
|
setlocale(LC_ALL,"");
|
|
|
|
/* Create the udev object */
|
|
udev = udev_new();
|
|
if (!udev) {
|
|
printf("Can't create udev\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Create a list of the devices in the 'hidraw' subsystem. */
|
|
enumerate = udev_enumerate_new(udev);
|
|
udev_enumerate_add_match_subsystem(enumerate, "hidraw");
|
|
udev_enumerate_scan_devices(enumerate);
|
|
devices = udev_enumerate_get_list_entry(enumerate);
|
|
/* For each item, see if it matches the vid/pid, and if so
|
|
create a udev_device record for it */
|
|
udev_list_entry_foreach(dev_list_entry, devices) {
|
|
const char *sysfs_path;
|
|
const char *dev_path;
|
|
const char *str;
|
|
unsigned short dev_vid;
|
|
unsigned short dev_pid;
|
|
|
|
/* Get the filename of the /sys entry for the device
|
|
and create a udev_device object (dev) representing it */
|
|
sysfs_path = udev_list_entry_get_name(dev_list_entry);
|
|
dev = udev_device_new_from_syspath(udev, sysfs_path);
|
|
dev_path = udev_device_get_devnode(dev);
|
|
|
|
/* The device pointed to by dev contains information about
|
|
the hidraw device. In order to get information about the
|
|
USB device, get the parent device with the
|
|
subsystem/devtype pair of "usb"/"usb_device". This will
|
|
be several levels up the tree, but the function will find
|
|
it.*/
|
|
dev = udev_device_get_parent_with_subsystem_devtype(
|
|
dev,
|
|
"usb",
|
|
"usb_device");
|
|
if (!dev) {
|
|
/* Unable to find parent usb device. */
|
|
goto next;
|
|
}
|
|
|
|
/* Get the VID/PID of the device */
|
|
str = udev_device_get_sysattr_value(dev,"idVendor");
|
|
dev_vid = (str)? strtol(str, NULL, 16): 0x0;
|
|
str = udev_device_get_sysattr_value(dev, "idProduct");
|
|
dev_pid = (str)? strtol(str, NULL, 16): 0x0;
|
|
|
|
/* Check the VID/PID against the arguments */
|
|
if ((vendor_id == 0x0 && product_id == 0x0) ||
|
|
(vendor_id == dev_vid && product_id == dev_pid)) {
|
|
struct hid_device_info *tmp;
|
|
size_t len;
|
|
|
|
/* VID/PID match. Create the record. */
|
|
tmp = malloc(sizeof(struct hid_device_info));
|
|
if (cur_dev) {
|
|
cur_dev->next = tmp;
|
|
}
|
|
else {
|
|
root = tmp;
|
|
}
|
|
cur_dev = tmp;
|
|
|
|
/* Fill out the record */
|
|
cur_dev->next = NULL;
|
|
str = dev_path;
|
|
if (str) {
|
|
len = strlen(str);
|
|
cur_dev->path = calloc(len+1, sizeof(char));
|
|
strncpy(cur_dev->path, str, len+1);
|
|
cur_dev->path[len] = '\0';
|
|
}
|
|
else
|
|
cur_dev->path = NULL;
|
|
|
|
/* Serial Number */
|
|
cur_dev->serial_number
|
|
= copy_udev_string(dev, "serial");
|
|
|
|
/* Manufacturer and Product strings */
|
|
cur_dev->manufacturer_string
|
|
= copy_udev_string(dev, "manufacturer");
|
|
cur_dev->product_string
|
|
= copy_udev_string(dev, "product");
|
|
|
|
/* VID/PID */
|
|
cur_dev->vendor_id = dev_vid;
|
|
cur_dev->product_id = dev_pid;
|
|
|
|
/* Release Number */
|
|
str = udev_device_get_sysattr_value(dev, "bcdDevice");
|
|
cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0;
|
|
|
|
/* Interface Number (Unsupported on Linux/hidraw) */
|
|
cur_dev->interface_number = -1;
|
|
|
|
}
|
|
else
|
|
goto next;
|
|
|
|
next:
|
|
udev_device_unref(dev);
|
|
}
|
|
/* Free the enumerator and udev objects. */
|
|
udev_enumerate_unref(enumerate);
|
|
udev_unref(udev);
|
|
|
|
return root;
|
|
}
|
|
|
|
void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
|
|
{
|
|
struct hid_device_info *d = devs;
|
|
while (d) {
|
|
struct hid_device_info *next = d->next;
|
|
free(d->path);
|
|
free(d->serial_number);
|
|
free(d->manufacturer_string);
|
|
free(d->product_string);
|
|
free(d);
|
|
d = next;
|
|
}
|
|
}
|
|
|
|
hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number)
|
|
{
|
|
struct hid_device_info *devs, *cur_dev;
|
|
const char *path_to_open = NULL;
|
|
hid_device *handle = NULL;
|
|
|
|
devs = hid_enumerate(vendor_id, product_id);
|
|
cur_dev = devs;
|
|
while (cur_dev) {
|
|
if (cur_dev->vendor_id == vendor_id &&
|
|
cur_dev->product_id == product_id) {
|
|
if (serial_number) {
|
|
if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
path_to_open = cur_dev->path;
|
|
break;
|
|
}
|
|
}
|
|
cur_dev = cur_dev->next;
|
|
}
|
|
|
|
if (path_to_open) {
|
|
/* Open the device */
|
|
handle = hid_open_path(path_to_open);
|
|
}
|
|
|
|
hid_free_enumeration(devs);
|
|
|
|
return handle;
|
|
}
|
|
|
|
hid_device * HID_API_EXPORT hid_open_path(const char *path)
|
|
{
|
|
hid_device *dev = NULL;
|
|
|
|
dev = new_hid_device();
|
|
|
|
if (kernel_version == 0) {
|
|
struct utsname name;
|
|
int major, minor, release;
|
|
int ret;
|
|
uname(&name);
|
|
ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release);
|
|
if (ret == 3) {
|
|
kernel_version = major << 16 | minor << 8 | release;
|
|
//printf("Kernel Version: %d\n", kernel_version);
|
|
}
|
|
else {
|
|
printf("Couldn't sscanf() version string %s\n", name.release);
|
|
}
|
|
}
|
|
|
|
// OPEN HERE //
|
|
dev->device_handle = open(path, O_RDWR);
|
|
|
|
// If we have a good handle, return it.
|
|
if (dev->device_handle > 0) {
|
|
|
|
/* Get the report descriptor */
|
|
int res, desc_size = 0;
|
|
struct hidraw_report_descriptor rpt_desc;
|
|
|
|
memset(&rpt_desc, 0x0, sizeof(rpt_desc));
|
|
|
|
/* Get Report Descriptor Size */
|
|
res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size);
|
|
if (res < 0)
|
|
perror("HIDIOCGRDESCSIZE");
|
|
|
|
|
|
/* Get Report Descriptor */
|
|
rpt_desc.size = desc_size;
|
|
res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc);
|
|
if (res < 0) {
|
|
perror("HIDIOCGRDESC");
|
|
} else {
|
|
/* Determine if this device uses numbered reports. */
|
|
dev->uses_numbered_reports =
|
|
uses_numbered_reports(rpt_desc.value,
|
|
rpt_desc.size);
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
else {
|
|
// Unable to open any devices.
|
|
free(dev);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
|
{
|
|
int bytes_written;
|
|
|
|
bytes_written = write(dev->device_handle, data, length);
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
int bytes_read;
|
|
|
|
bytes_read = read(dev->device_handle, data, length);
|
|
if (bytes_read < 0 && errno == EAGAIN)
|
|
bytes_read = 0;
|
|
|
|
if (bytes_read >= 0 &&
|
|
kernel_version < KERNEL_VERSION(2,6,34) &&
|
|
dev->uses_numbered_reports) {
|
|
/* Work around a kernel bug. Chop off the first byte. */
|
|
memmove(data, data+1, bytes_read);
|
|
bytes_read--;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
|
|
{
|
|
int flags, res;
|
|
|
|
flags = fcntl(dev->device_handle, F_GETFL, 0);
|
|
if (flags >= 0) {
|
|
if (nonblock)
|
|
res = fcntl(dev->device_handle, F_SETFL, flags | O_NONBLOCK);
|
|
else
|
|
res = fcntl(dev->device_handle, F_SETFL, flags & ~O_NONBLOCK);
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
else {
|
|
dev->blocking = !nonblock;
|
|
return 0; /* Success */
|
|
}
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
|
{
|
|
int res;
|
|
|
|
res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data);
|
|
if (res < 0)
|
|
perror("ioctl (SFEATURE)");
|
|
|
|
return res;
|
|
}
|
|
|
|
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
|
{
|
|
int res;
|
|
|
|
res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data);
|
|
if (res < 0)
|
|
perror("ioctl (GFEATURE)");
|
|
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
void HID_API_EXPORT hid_close(hid_device *dev)
|
|
{
|
|
if (!dev)
|
|
return;
|
|
close(dev->device_handle);
|
|
free(dev);
|
|
}
|
|
|
|
|
|
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
return get_device_string(dev, "manufacturer", string, maxlen);
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
return get_device_string(dev, "product", string, maxlen);
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
|
{
|
|
return get_device_string(dev, "serial", string, maxlen);
|
|
}
|
|
|
|
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
|
|
{
|
|
return NULL;
|
|
}
|