diff --git a/README.adoc b/README.adoc index dc0a9ac..4e27fac 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,8 @@ = USB Host Library for Arduino = -The USBHost library allows an Arduino Due board to appear as a USB host, enabling it to communicate with peripherals like USB mice and keyboards. +The USBHost library allows an Arduino Due board to appear as a USB host, enabling it to communicate with peripherals like USB mice, keyboards and HTC Vive trackers (requires at least tracker firmware v.20). + +For an USBHost library supporting Vive tracker for Arduino SAMD boards see [here](https://github.com/matzman666/USBHost-samd). For more information about this library please visit us at http://www.arduino.cc/en/Reference/USBHost diff --git a/examples/ViveTracker/ViveTracker.ino b/examples/ViveTracker/ViveTracker.ino new file mode 100644 index 0000000..606558a --- /dev/null +++ b/examples/ViveTracker/ViveTracker.ino @@ -0,0 +1,36 @@ +/* + * Vive Tracker Example + * + * Shows how to send button + */ + +// Require vive tracker controller library +#include + +// Initialize USB Host Controller +USBHost usb; + +// Attach vive tracker controller to USB +ViveTrackerController tracker(usb); + + +void setup() { + Serial.begin(115200); + Serial.println("Program started"); + delay(200); +} + + +void loop() { + tracker.Task(); // Process usb and vive tracker controller tasks + if (tracker.isConnected()) { + // Send accessory state to vive tracker. + uint8_t buttons = VIVETRACKER_BUTTON_GRIP | VIVETRACKER_BUTTON_MENU; + int16_t padX = 1234; + int16_t padY = -333; + uint8_t trigger = 200; + uint16_t batteryLevel = 0; + tracker.setTrackerStatus(buttons, padX, padY, trigger, batteryLevel); + } + delay(300); +} diff --git a/library.properties b/library.properties index 755bffd..0d21c90 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=USBHost -version=1.0.5 +version=1.0.6 author=Arduino maintainer=Arduino sentence=Allows the communication with USB peripherals like mice, keyboards, and thumbdrives. diff --git a/src/Usb.cpp b/src/Usb.cpp index ce74b48..7b35f11 100644 --- a/src/Usb.cpp +++ b/src/Usb.cpp @@ -723,23 +723,28 @@ void USBHost::Task(void) { case UHD_STATE_ERROR: // Illegal state + //Serial.write("UHD_STATE_ERROR\n"); usb_task_state = USB_DETACHED_SUBSTATE_ILLEGAL; lowspeed = 0; break; case UHD_STATE_DISCONNECTED: + //Serial.write("UHD_STATE_DISCONNECTED\n"); // Disconnected state if ((usb_task_state & USB_STATE_MASK) != USB_STATE_DETACHED) { + //Serial.write("UHD_STATE_DISCONNECTED2\n"); usb_task_state = USB_DETACHED_SUBSTATE_INITIALIZE; lowspeed = 0; } break; case UHD_STATE_CONNECTED: + //Serial.write("UHD_STATE_CONNECTED\n"); // Attached state if ((usb_task_state & USB_STATE_MASK) == USB_STATE_DETACHED) { + //Serial.write("UHD_STATE_CONNECTED2\n"); delay = millis() + USB_SETTLE_DELAY; usb_task_state = USB_ATTACHED_SUBSTATE_SETTLE; //FIXME TODO: lowspeed = 0 ou 1; already done by hardware? diff --git a/src/ViveTrackerController.h b/src/ViveTrackerController.h new file mode 100644 index 0000000..cdc05b3 --- /dev/null +++ b/src/ViveTrackerController.h @@ -0,0 +1,189 @@ +/* + * ViveTrackerController.h + * + * Created on: 05.04.2017 + * Author: matzman + */ + +#ifndef VIVETRACKERCONTROLLER_H_ +#define VIVETRACKERCONTROLLER_H_ + +#include + + +/* Vive Tracker Button Bitmasks */ +#define VIVETRACKER_BUTTON_TRIGGER (1 << 0) +#define VIVETRACKER_BUTTON_GRIP (1 << 1) +#define VIVETRACKER_BUTTON_MENU (1 << 2) +#define VIVETRACKER_BUTTON_SYSTEM (1 << 3) +#define VIVETRACKER_BUTTON_PADTRIGGERED (1 << 4) +#define VIVETRACKER_BUTTON_PADTOUCHED (1 << 5) + +#define VIVETRACKER_LPF_DEFAULT 0 +#define VIVETRACKER_LPF_184HZ 0 +#define VIVETRACKER_LPF_5HZ 1 +#define VIVETRACKER_LPF_10HZ 2 +#define VIVETRACKER_LPF_20HZ 3 + + +/* + * The format of the USB HID feature reports is unfortunately not correct in HTC's documentation. + * Fortunately Peter S. Hollander was able to find out the correct format of the feature reports, + * and he was kind enough to share the details on his blog: + * http://www.talariavr.com/blog/vive-tracker-initial-documentation/ + */ + +/** + * \struct Vive Tracker Feature Report 0xB3 + */ +struct __attribute__((__packed__)) ViveTrackerFeatureReportB3 { + uint8_t address = 0xB3; + uint8_t payloadSize = 4; + uint8_t hostType; + uint8_t chargeEnable; + uint8_t osType; + uint8_t lpfConfig; + ViveTrackerFeatureReportB3(uint8_t hostType = 3, uint8_t chargeEnable = 0, uint8_t osType = 0, uint8_t lpfConfig = VIVETRACKER_LPF_DEFAULT) + : hostType(hostType), chargeEnable(chargeEnable), osType(osType), lpfConfig(lpfConfig) {} +}; + +/** + * \struct Vive Tracker Feature Report 0xB4 + */ +struct __attribute__((__packed__)) ViveTrackerFeatureReportB4 { + uint8_t address = 0xB4; + uint8_t payloadSize = 10; + uint8_t tagIndex = 0; + uint8_t buttons; + int16_t padX; + int16_t padY; + uint16_t trigger; + uint16_t batteryLevel; + ViveTrackerFeatureReportB4(uint8_t buttons = 0, int16_t padX = 0, int16_t padY = 0, uint16_t trigger = 0, uint16_t batteryLevel = 0) + : buttons(buttons), padX(padX), padY(padY), trigger(trigger), batteryLevel(batteryLevel) {} +} ; + +/** + * \class Customized HID Boot definition for Vive Tracker + */ +class ViveTrackerBoot : public HIDBoot { +public: + using HIDBoot::HIDBoot; + + virtual uint32_t Init(uint32_t parent, uint32_t port, uint32_t lowspeed) override { + auto retval = HIDBoot::Init(parent, port, lowspeed); + if (!retval) { + _isConnected = true; + } + return retval; + } + + virtual uint32_t Release() override { + auto retval = HIDBoot::Release(); + if (!retval) { + _isConnected = false; + } + return retval; + } + + virtual uint32_t Poll() override { + return 0; // The Vive Controller does not send any data + } + + bool isConnected() { + return _isConnected; + } + +private: + bool _isConnected = false; +}; + + +/** + * \class Vive Tracker Controller + * + * Represents the public API for the Vive Tracker + */ +class ViveTrackerController { +public: + ViveTrackerController(USBHost &usb, uint8_t hostType = 3, uint8_t chargeEnabled = 0, uint8_t osType = 0) + : usb(usb), hostTracker(&usb), _hostType(hostType), _chargeEnabled(chargeEnabled), _osType(osType) {} + + /** + * Returns whether a vive tracker is connected and initialized. + */ + bool isConnected() { + return hostTracker.isConnected() && _isInitialized; + } + + /** + * Sends the accessory state to the vive tracker. + * + * Regarding the trigger parameter: HTC's documentations states that a 16-bit value is used to represent the trigger. + * But the current firmware only uses an 8-bit value. + * + * \param buttons Bitmask representing the pressed buttons. + * \param padX X-axis of the trackpad (from -32768 to 32767) + * \param padY Y-axis of the trackpad (from -32768 to 32767) + * \param trigger Trigger state (from 0 to 255) + * \param batteryLevel Currently not used, should be set to 0. + * \param lowspeed USB device speed. + * + * \return 0 on success, error code otherwise. + */ + uint32_t setTrackerStatus(uint8_t buttons = 0, int16_t padX = 0, int16_t padY = 0, uint8_t trigger = 0, uint16_t batteryLevel = 0) { + if (isConnected()) { + ViveTrackerFeatureReportB4 report(buttons, padX, padY, trigger << 8, batteryLevel); + return hostTracker.SetReport(0, 2, 3, 0, sizeof(ViveTrackerFeatureReportB4), (uint8_t*)&report); + } + return -1; + } + + uint32_t setChargeEnabled(uint8_t enabled, bool sendUsbReport = true) { + _chargeEnabled = enabled; + if (sendUsbReport) { + return sendUsbReportB3(_hostType, _chargeEnabled, _osType, VIVETRACKER_LPF_DEFAULT); + } else { + return 0; + } + } + + uint32_t setLpfConfig(uint8_t lpfConfig) { + return sendUsbReportB3(_hostType, _chargeEnabled, _osType, lpfConfig); + } + + /** + * Process usb and vive tracker controller tasks. + * + * Should be repeatedly called in the main-loop. + */ + void Task() { + usb.Task(); + if (hostTracker.isConnected() && !_isInitialized && _initConnection() == 0) { + _isInitialized = true; + } else if (!hostTracker.isConnected()) { + _isInitialized = false; + } + } + +private: + USBHost &usb; + ViveTrackerBoot hostTracker; + bool _isInitialized = false; + uint8_t _hostType; + uint8_t _chargeEnabled; + uint8_t _osType; + + uint32_t _initConnection() { + return sendUsbReportB3(_hostType, _chargeEnabled, _osType, VIVETRACKER_LPF_DEFAULT); + } + + uint32_t sendUsbReportB3(uint8_t hostType, uint8_t chargeEnabled, uint8_t osType, uint8_t lpfConfig) { + ViveTrackerFeatureReportB3 report(hostType, chargeEnabled, osType, lpfConfig); + return hostTracker.SetReport(0, 2, 3, 0, sizeof(ViveTrackerFeatureReportB3), (uint8_t*)&report); + + } +}; + + +#endif /* VIVETRACKERCONTROLLER_H_ */ diff --git a/src/hid.h b/src/hid.h index 2191ddf..3b6ef2e 100644 --- a/src/hid.h +++ b/src/hid.h @@ -88,6 +88,7 @@ e-mail : support@circuitsathome.com #define HID_INTF 0x03 /* HID Interface Class SubClass Codes */ +#define HID_NONE_SUBCLASS 0x00 #define HID_BOOT_INTF_SUBCLASS 0x01 /* HID Interface Class Protocol Codes */ diff --git a/src/hidboot.h b/src/hidboot.h index 66f630f..98ee98e 100644 --- a/src/hidboot.h +++ b/src/hidboot.h @@ -173,7 +173,7 @@ class KeyboardReportParser : public HIDReportParser /** * \class HIDBoot definition. */ -template +template class HIDBoot : public HID { EpInfo epInfo[totalEndpoints]; @@ -206,11 +206,12 @@ class HIDBoot : public HID virtual void EndpointXtract(uint32_t conf, uint32_t iface, uint32_t alt, uint32_t proto, const USB_ENDPOINT_DESCRIPTOR *ep); }; + /** * \brief HIDBoot class constructor. */ -template -HIDBoot::HIDBoot(USBHost *p) : +template +HIDBoot::HIDBoot(USBHost *p) : HID(p), pRptParser(NULL), qNextPollTime(0), @@ -225,8 +226,8 @@ HIDBoot::HIDBoot(USBHost *p) : /** * \brief Initialize HIDBoot class. */ -template -void HIDBoot::Initialize() +template +void HIDBoot::Initialize() { for (uint32_t i = 0; i < totalEndpoints; ++i) { @@ -251,8 +252,8 @@ void HIDBoot::Initialize() * * \return 0 on success, error code otherwise. */ -template -uint32_t HIDBoot::Init(uint32_t parent, uint32_t port, uint32_t lowspeed) +template +uint32_t HIDBoot::Init(uint32_t parent, uint32_t port, uint32_t lowspeed) { const uint32_t constBufSize = sizeof(USB_DEVICE_DESCRIPTOR); @@ -360,7 +361,7 @@ uint32_t HIDBoot::Init(uint32_t parent, uint32_t port, uint32_t l { ConfigDescParser< USB_CLASS_HID, - HID_BOOT_INTF_SUBCLASS, + HID_SUBCLASS, BOOT_PROTOCOL, CP_MASK_COMPARE_ALL> confDescrParser(this); @@ -443,8 +444,8 @@ uint32_t HIDBoot::Init(uint32_t parent, uint32_t port, uint32_t l * \param proto Protocol version used. * \param pep Pointer to endpoint descriptor. */ -template -void HIDBoot::EndpointXtract(uint32_t conf, uint32_t iface, uint32_t alt, uint32_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) +template +void HIDBoot::EndpointXtract(uint32_t conf, uint32_t iface, uint32_t alt, uint32_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) { // If the first configuration satisfies, the others are not considered. if (bNumEP > 1 && conf != bConfNum) @@ -493,8 +494,8 @@ void HIDBoot::EndpointXtract(uint32_t conf, uint32_t iface, uint3 * * \return Always 0. */ -template -uint32_t HIDBoot::Release() +template +uint32_t HIDBoot::Release() { // Free allocated host pipes UHD_Pipe_Free(epInfo[epInterruptInIndex].hostPipeNum); @@ -519,8 +520,8 @@ uint32_t HIDBoot::Release() * * \return 0 on success, error code otherwise. */ -template -uint32_t HIDBoot::Poll() +template +uint32_t HIDBoot::Poll() { uint32_t rcode = 0;