mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-01-09 20:46:07 +01:00
390 lines
12 KiB
C++
390 lines
12 KiB
C++
/* @file pjrc_rawhid_mac.cpp
|
|
* @addtogroup GCSPlugins GCS Plugins
|
|
* @{
|
|
* @addtogroup RawHIDPlugin Raw HID Plugin
|
|
* @{
|
|
* @brief Impliments a HID USB connection to the flight hardware as a QIODevice
|
|
*****************************************************************************/
|
|
|
|
/* Simple Raw HID functions for Linux - for use with Teensy RawHID example
|
|
* http://www.pjrc.com/teensy/rawhid.html
|
|
* Copyright (c) 2009 PJRC.COM, LLC
|
|
*
|
|
* rawhid_open - open 1 or more devices
|
|
* rawhid_recv - receive a packet
|
|
* rawhid_send - send a packet
|
|
* rawhid_close - close a device
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above description, website URL and copyright notice and this permission
|
|
* notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* Version 1.0: Initial Release
|
|
*/
|
|
|
|
|
|
#include "pjrc_rawhid.h"
|
|
|
|
#include <unistd.h>
|
|
#include <QString>
|
|
#include <QThread>
|
|
#include <QTimer>
|
|
#include <QCoreApplication>
|
|
|
|
struct timeout_info {
|
|
CFRunLoopRef loopRef;
|
|
bool timed_out;
|
|
};
|
|
|
|
pjrc_rawhid::pjrc_rawhid() :
|
|
device_open(false), hid_manager(NULL), buffer_count(0), unplugged(false)
|
|
{
|
|
m_writeMutex = new QMutex();
|
|
m_readMutex = new QMutex();
|
|
}
|
|
|
|
pjrc_rawhid::~pjrc_rawhid()
|
|
{
|
|
if (device_open) {
|
|
close(0);
|
|
}
|
|
|
|
if (m_writeMutex) {
|
|
delete m_writeMutex;
|
|
m_writeMutex = NULL;
|
|
}
|
|
|
|
if (m_readMutex) {
|
|
delete m_readMutex;
|
|
m_readMutex = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief open - open 1 or more devices
|
|
* @param[in] max maximum number of devices to open
|
|
* @param[in] vid Vendor ID, or -1 if any
|
|
* @param[in] pid Product ID, or -1 if any
|
|
* @param[in] usage_page top level usage page, or -1 if any
|
|
* @param[in] usage top level usage number, or -1 if any
|
|
* @returns actual number of devices opened
|
|
*/
|
|
int pjrc_rawhid::open(int max, int vid, int pid, int usage_page, int usage)
|
|
{
|
|
CFMutableDictionaryRef dict;
|
|
CFNumberRef num;
|
|
IOReturn ret;
|
|
|
|
Q_ASSERT(hid_manager == NULL);
|
|
Q_ASSERT(device_open == false);
|
|
|
|
attach_count = 0;
|
|
|
|
// Start the HID Manager
|
|
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
|
if (hid_manager == NULL || CFGetTypeID(hid_manager) != IOHIDManagerGetTypeID()) {
|
|
if (hid_manager) CFRelease(hid_manager);
|
|
return 0;
|
|
}
|
|
|
|
if (vid > 0 || pid > 0 || usage_page > 0 || usage > 0) {
|
|
// Tell the HID Manager what type of devices we want
|
|
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
if (!dict) return 0;
|
|
if (vid > 0) {
|
|
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vid);
|
|
CFDictionarySetValue(dict, CFSTR(kIOHIDVendorIDKey), num);
|
|
CFRelease(num);
|
|
}
|
|
if (pid > 0) {
|
|
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);
|
|
CFDictionarySetValue(dict, CFSTR(kIOHIDProductIDKey), num);
|
|
CFRelease(num);
|
|
}
|
|
if (usage_page > 0) {
|
|
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_page);
|
|
CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsagePageKey), num);
|
|
CFRelease(num);
|
|
}
|
|
if (usage > 0) {
|
|
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
|
|
CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsageKey), num);
|
|
CFRelease(num);
|
|
}
|
|
IOHIDManagerSetDeviceMatching(hid_manager, dict);
|
|
CFRelease(dict);
|
|
} else {
|
|
IOHIDManagerSetDeviceMatching(hid_manager, NULL);
|
|
}
|
|
|
|
// Set the run loop reference before configuring the attach callback
|
|
the_correct_runloop = CFRunLoopGetCurrent();
|
|
|
|
// set up a callbacks for device attach & detach
|
|
IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetCurrent(),
|
|
kCFRunLoopDefaultMode);
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, pjrc_rawhid::attach_callback, this);
|
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, pjrc_rawhid::dettach_callback, this);
|
|
ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
|
|
if (ret != kIOReturnSuccess) {
|
|
IOHIDManagerUnscheduleFromRunLoop(hid_manager,
|
|
CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
|
CFRelease(hid_manager);
|
|
return 0;
|
|
}
|
|
|
|
// let it do the callback for all devices
|
|
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) ;
|
|
|
|
// count up how many were added by the callback
|
|
return attach_count;
|
|
}
|
|
|
|
/**
|
|
* @brief receive - receive a packet
|
|
* @param[in] num device to receive from (unused now)
|
|
* @param[in] buf buffer to receive packet
|
|
* @param[in] len buffer's size
|
|
* @param[in] timeout = time to wait, in milliseconds
|
|
* @returns number of bytes received, or -1 on error
|
|
*/
|
|
int pjrc_rawhid::receive(int, void *buf, int len, int timeout)
|
|
{
|
|
QMutexLocker locker(m_readMutex);
|
|
Q_UNUSED(locker);
|
|
|
|
if (!device_open)
|
|
return -1;
|
|
|
|
// Pass information to the callback to stop this run loop and signal if a timeout occurred
|
|
struct timeout_info info;
|
|
info.loopRef = CFRunLoopGetCurrent();;
|
|
info.timed_out = false;
|
|
CFRunLoopTimerContext context;
|
|
memset(&context, 0, sizeof(context));
|
|
context.info = &info;
|
|
|
|
// Set up the timer for the timeout
|
|
CFRunLoopTimerRef timer;
|
|
timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + (double)timeout / 1000.0, 0, 0, 0, timeout_callback, &context);
|
|
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
|
|
|
received_runloop = CFRunLoopGetCurrent();
|
|
|
|
// Run the CFRunLoop until either a timeout or data is available
|
|
while(1) {
|
|
if (buffer_count != 0) {
|
|
if (len > buffer_count) len = buffer_count;
|
|
memcpy(buf, buffer, len);
|
|
buffer_count = 0;
|
|
break;
|
|
} else if (info.timed_out) {
|
|
len = 0;
|
|
break;
|
|
}
|
|
CFRunLoopRun(); // Wait for data
|
|
}
|
|
|
|
CFRunLoopTimerInvalidate(timer);
|
|
CFRelease(timer);
|
|
|
|
received_runloop = NULL;
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper class that will workaround the fact
|
|
* that the HID send is broken on OSX
|
|
*/
|
|
class Sender : public QThread
|
|
{
|
|
public:
|
|
Sender(IOHIDDeviceRef d, uint8_t * b, int l) :
|
|
dev(d), buf(b), len(l), result(-1) { }
|
|
|
|
void run() {
|
|
ret = IOHIDDeviceSetReport(dev, kIOHIDReportTypeOutput, 2, buf, len);
|
|
result = (ret == kIOReturnSuccess) ? len : -1;
|
|
}
|
|
|
|
int result;
|
|
IOReturn ret;
|
|
private:
|
|
IOHIDDeviceRef dev;
|
|
uint8_t * buf;
|
|
int len;
|
|
};
|
|
|
|
/**
|
|
* @brief send - send a packet
|
|
* @param[in] num device to transmit to (zero based)
|
|
* @param[in] buf buffer containing packet to send
|
|
* @param[in] len number of bytes to transmit
|
|
* @param[in] timeout = time to wait, in milliseconds
|
|
* @returns number of bytes sent, or -1 on error
|
|
*/
|
|
int pjrc_rawhid::send(int, void *buf, int len, int timeout)
|
|
{
|
|
// This lock ensures that when closing we don't do it until the
|
|
// write has terminated (and then the device_open flag is set to false)
|
|
QMutexLocker locker(m_writeMutex);
|
|
Q_UNUSED(locker);
|
|
|
|
if(!device_open || unplugged) {
|
|
return -1;
|
|
}
|
|
|
|
uint8_t *report_buf = (uint8_t *) malloc(len);
|
|
memcpy(&report_buf[0], buf,len);
|
|
|
|
QEventLoop el;
|
|
Sender sender(dev, report_buf, len);
|
|
connect(&sender, SIGNAL(finished()), &el, SLOT(quit()));
|
|
sender.start();
|
|
QTimer::singleShot(timeout, &el, SLOT(quit()));
|
|
el.exec();
|
|
|
|
return sender.result;
|
|
}
|
|
|
|
//! Get the serial number for a HID device
|
|
QString pjrc_rawhid::getserial(int num) {
|
|
QMutexLocker locker(m_readMutex);
|
|
Q_UNUSED(locker);
|
|
|
|
if (!device_open || unplugged)
|
|
return "";
|
|
|
|
CFTypeRef serialnum = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDSerialNumberKey));
|
|
if(serialnum && CFGetTypeID(serialnum) == CFStringGetTypeID())
|
|
{
|
|
//Note: I'm not sure it will always succeed if encoded as MacRoman but that
|
|
//is a superset of UTF8 so I think this is fine
|
|
CFStringRef str = (CFStringRef)serialnum;
|
|
const char * buf = CFStringGetCStringPtr(str, kCFStringEncodingMacRoman);
|
|
return QString(buf);
|
|
}
|
|
|
|
return QString("Error");
|
|
}
|
|
|
|
//! Close the HID device
|
|
void pjrc_rawhid::close(int)
|
|
{
|
|
// Make sure any pending locks are done
|
|
QMutexLocker lock(m_writeMutex);
|
|
|
|
if (device_open) {
|
|
device_open = false;
|
|
CFRunLoopStop(the_correct_runloop);
|
|
|
|
if (!unplugged) {
|
|
IOHIDDeviceUnscheduleFromRunLoop(dev, the_correct_runloop, kCFRunLoopDefaultMode);
|
|
IOHIDDeviceRegisterInputReportCallback(dev, buffer, sizeof(buffer), NULL, NULL);
|
|
IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
|
|
}
|
|
|
|
IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, NULL, NULL);
|
|
IOHIDManagerClose(hid_manager, 0);
|
|
|
|
dev = NULL;
|
|
hid_manager = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief input Called to add input data to the buffer
|
|
* @param[in] id Report id
|
|
* @param[in] data The data buffer
|
|
* @param[in] len The report length
|
|
*/
|
|
void pjrc_rawhid::input(uint8_t *data, CFIndex len)
|
|
{
|
|
if (!device_open)
|
|
return;
|
|
|
|
if (len > BUFFER_SIZE) len = BUFFER_SIZE;
|
|
// Note: packet preprocessing done in OS independent code
|
|
memcpy(buffer, &data[0], len);
|
|
buffer_count = len;
|
|
|
|
if (received_runloop)
|
|
CFRunLoopStop(received_runloop);
|
|
}
|
|
|
|
//! Callback for the HID driver on an input report
|
|
void pjrc_rawhid::input_callback(void *c, IOReturn ret, void *sender, IOHIDReportType type, uint32_t id, uint8_t *data, CFIndex len)
|
|
{
|
|
if (ret != kIOReturnSuccess || len < 1) return;
|
|
|
|
pjrc_rawhid *context = (pjrc_rawhid *) c;
|
|
context->input(data, len);
|
|
}
|
|
|
|
//! Timeout used for the
|
|
void pjrc_rawhid::timeout_callback(CFRunLoopTimerRef, void *i)
|
|
{
|
|
struct timeout_info *info = (struct timeout_info *) i;
|
|
info->timed_out = true;
|
|
CFRunLoopStop(info->loopRef);
|
|
}
|
|
|
|
//! Called on a dettach event
|
|
void pjrc_rawhid::dettach(IOHIDDeviceRef d)
|
|
{
|
|
unplugged = true;
|
|
if (d == dev)
|
|
emit deviceUnplugged(0);
|
|
}
|
|
|
|
//! Called from the USB system and forwarded to the instance (context)
|
|
void pjrc_rawhid::dettach_callback(void *context, IOReturn, void *, IOHIDDeviceRef dev)
|
|
{
|
|
pjrc_rawhid *p = (pjrc_rawhid*) context;
|
|
p->dettach(dev);
|
|
}
|
|
|
|
/**
|
|
* @brief Called by the USB system
|
|
* @param dev The device that was attached
|
|
*/
|
|
void pjrc_rawhid::attach(IOHIDDeviceRef d)
|
|
{
|
|
// Store the device handle
|
|
dev = d;
|
|
|
|
if (IOHIDDeviceOpen(dev, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return;
|
|
// Disconnect the attach callback since we don't want to automatically reconnect
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, NULL, NULL);
|
|
IOHIDDeviceScheduleWithRunLoop(dev, the_correct_runloop, kCFRunLoopDefaultMode);
|
|
IOHIDDeviceRegisterInputReportCallback(dev, buffer, sizeof(buffer), pjrc_rawhid::input_callback, this);
|
|
|
|
attach_count++;
|
|
device_open = true;
|
|
unplugged = false;
|
|
}
|
|
|
|
//! Called from the USB system and forwarded to the instance (context)
|
|
void pjrc_rawhid::attach_callback(void *context, IOReturn r, void *hid_mgr, IOHIDDeviceRef dev)
|
|
{
|
|
pjrc_rawhid *p = (pjrc_rawhid*) context;
|
|
p->attach(dev);
|
|
}
|
|
|