diff --git a/3rdParty/trick/examples/SIM_python_generic/RUN_test/generic_hc_config.py b/3rdParty/trick/examples/SIM_python_generic/RUN_test/generic_hc_config.py new file mode 100644 index 0000000..f1e2e8f --- /dev/null +++ b/3rdParty/trick/examples/SIM_python_generic/RUN_test/generic_hc_config.py @@ -0,0 +1,53 @@ +import sys +import traceback + +# Instantiate a Generic Joystick using its Vendor ID, Product ID and Interface ID +# Use IDF/apps/hidScanner to assist with getting this information. +# In this example, we are creating a mapping for a +# Thrustmaster AVA Base: https://eshop.thrustmaster.com/en_us/ava-base.html +# with an F16 grip: https://eshop.thrustmaster.com/en_us/viper-hotas-add-on-grip.html +try: + device = trick.HidGenericJoystick( 0x044F, 0x0415, 0 ) + device.thisown = 0 + self.addMasterDevice( device ) +except Exception as e: + print( "IDF init error:", traceback.format_exc() ) + sys.exit(1) + +# Create a SingleFlightController, including compositing X, Y, and Z from buttons +# use IDF/apps/decoder to assist with figuring out which buttons to use + +# this grip does not have a twist so we'll use some buttons +twist = trick.CompositeInput() +twist.thisown = 0 +twist.addInput( device.getButton(14) ) +twist.addInput( device.getButton(12), -1 ) + +x = trick.CompositeInput() +x.thisown = 0 +x.addInput( device.getButton(15) ) # the buttons vector is zero indexed +x.addInput( device.getButton(17), -1 ) + +y = trick.CompositeInput() +y.thisown = 0 +y.addInput( device.getButton(18) ) +y.addInput( device.getButton(16), -1 ) + +z = trick.CompositeInput() +z.thisown = 0 +z.addInput( device.getButton(7) ) +z.addInput( device.getButton(9), -1 ) + +controller = trick.SingleFlightController( + device.leftRightPivot, + device.forwardBackwardPivot, + twist, + x, y, z +) +controller.roll.setInverted( True ) +controller.pitch.setInverted( True ) +controller.thisown = 0 # don't forget this!! + + +# add controller to the InputDeviceManager +self.inputDeviceManager.controller = controller \ No newline at end of file diff --git a/3rdParty/trick/examples/SIM_python_generic/RUN_test/input.py b/3rdParty/trick/examples/SIM_python_generic/RUN_test/input.py new file mode 100644 index 0000000..add26d8 --- /dev/null +++ b/3rdParty/trick/examples/SIM_python_generic/RUN_test/input.py @@ -0,0 +1,18 @@ +trick.real_time_enable() +trick.exec_set_software_frame(0.1) + +# import IDF's modules +import idf.config + +# inherit from IDF's Configurator +class Configurator(idf.config.Configurator) : + + # this method is abstract in the base class, and so we must override it + def addMasterDevice(self, device): + + # Despite being abstract in the base class, this method is nevertheless implemented there. + # (It calls addInputDevice for you). Don't forget to call it! + super(Configurator, self).addMasterDevice(device) + +# Instantiate a Configurator, passing it the IdfInputDeviceManager instance from the S_define. +configurator = Configurator(example, configFile="RUN_test/generic_hc_config.py") diff --git a/3rdParty/trick/examples/SIM_python_generic/S_define b/3rdParty/trick/examples/SIM_python_generic/S_define new file mode 100644 index 0000000..4ca30df --- /dev/null +++ b/3rdParty/trick/examples/SIM_python_generic/S_define @@ -0,0 +1,99 @@ +#include "sim_objects/default_trick_sys.sm" +#include "idf/IdfInputDeviceManager.sm" + +##include +##include + +##include "sim_services/Executive/include/exec_proto.h" +##include "idf/SingleFlightController.hh" +##include "idf/Deadband.hh" + +/** + * Demonstrates the use of IDF within a Trick simulation, using the Python + * class idf.config.Configurator to add a device at run-time. See + * RUN_test/input.py. + */ +class Example : public IdfInputDeviceManager { + + public: + + /** + * While other examples have declared devices here, we want to leave the + * choice of device up to the user this time. In this example, our only + * concern is the Controller. + */ + + /** + * While devices represent physical hardware, a controller represents a + * virtual interface into a control system. This separation allows + * control system developers to program against a domain-specific interface + * independent of any particular device that might service that + * interface. It further ensures that only devices that have been + * specifically mapped to the interface may be assigned to service it. + */ + idf::SingleFlightController *controller; + + Example() : + /** + * Call the base class constructor, specifying the period at which the + * updateDevices() job will be called. You can optionally specify the + * phase and class for this job as well. See IdfInputDeviceManager.sm + * for details. + */ + IdfInputDeviceManager(0.1), + controller(NULL) { + /** + * Nothing to see here! + * All of the configuration is done in the input file. + */ + } + + /** + * If you want to perform some action after all devices have been updated, + * one possibility is overriding IdfInputDeviceManager's updateDevices() + * function. In this example, we just want to print the controller's + * normalized values to the console. Normalized values always fall in the + * range [-1, 1], with the extremes corresponing to the minimum and maximum + * raw values. 0 represents the "neutral" point. If you choose this method, + * be sure to call the base class version of the function or the devices + * will never be updated! + * + * Another option is to just declare another scheduled job and do your post- + * update logic there. This can be useful if you want the updateDevices() + * job and your post-update logic to run at different rates or have + * different phases or job classes. + */ + void updateDevices() { + + // Don't forget to call the base class version! + IdfInputDeviceManager::updateDevices(); + + /** + * The controller pointer is assigned via the input file (see Configurator.addMasterDevice). + * If no such assignment was made, then there is no physical device connected to the + * computer. In this case, IDF would launch the Virtual Hand Controller if the + * idf.config.Configurator was instantiated with support for the VHC. This sim does not + * do so (see SIM_virtual_hand_controller for an example that does), so we terminate. + */ + if (!controller) { + exec_terminate("S_define", "This sim requires a physical device to be connected."); + } + + bool blah = controller->isActive(); + + // Print the values. + std::cout << std::showpos << std::fixed << std::setprecision(2) + + << " Roll: " << controller->getRoll() + << " Pitch: " << controller->getPitch() + << " Yaw: " << controller->getYaw() + << " X: " << controller->getX() + << " Y: " << controller->getY() + << " Z: " << controller->getZ() + + << std::endl; + } + +}; + +Example example; diff --git a/3rdParty/trick/examples/SIM_python_generic/S_overrides.mk b/3rdParty/trick/examples/SIM_python_generic/S_overrides.mk new file mode 100644 index 0000000..0b8a3a9 --- /dev/null +++ b/3rdParty/trick/examples/SIM_python_generic/S_overrides.mk @@ -0,0 +1 @@ +include ../../makefiles/core.mk diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e7e4cc..fedfb6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,11 @@ set(SRC_IDF source/idf/EthernetWingMan.cpp source/idf/Extreme3dPro.cpp source/idf/FlightController.cpp + source/idf/GenericJoystick.cpp source/idf/Gravis.cpp + source/idf/HidDevice.cpp + source/idf/HidDecoder.cpp + source/idf/HidGenericJoystick.cpp source/idf/HagstromKEUSB36.cpp source/idf/IndustrialProducts2.cpp source/idf/IndustrialProducts3.cpp diff --git a/apps/decoder/.gitignore b/apps/decoder/.gitignore new file mode 100644 index 0000000..541ce64 --- /dev/null +++ b/apps/decoder/.gitignore @@ -0,0 +1 @@ +decoder diff --git a/apps/decoder/Makefile b/apps/decoder/Makefile new file mode 100644 index 0000000..d6b3240 --- /dev/null +++ b/apps/decoder/Makefile @@ -0,0 +1,18 @@ +IDF_HOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..) +IDF_CONFIG := $(IDF_HOME)/bin/idf-config +IDF_BUILD_DIR := $(IDF_HOME)/build +IDF_LIB := $(IDF_BUILD_DIR)/libidf.a + +CXXFLAGS += $(shell $(IDF_CONFIG) --cxxflags) +LDLIBS += $(shell $(IDF_CONFIG) --libs) + +decoder: $(IDF_LIB) + +$(IDF_LIB): | $(IDF_BUILD_DIR) + cd $| && cmake .. && $(MAKE) + +$(IDF_BUILD_DIR): + mkdir -p $@ + +clean: + @rm -rf decoder diff --git a/apps/decoder/decoder.cpp b/apps/decoder/decoder.cpp new file mode 100644 index 0000000..d9829bf --- /dev/null +++ b/apps/decoder/decoder.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hidapi/hidapi/hidapi.h" +#include "idf/HidDecoder.hh" + +static volatile bool keepReading = true; + +void interruptHandler(int unused){ + keepReading = false; +} + + +void printHidDescriptor(const std::vector report) +{ + printf("\nHID Report Descriptor (%lu bytes):\n ", report.size()); + for(size_t i=0; i < report.size(); ++i) { + printf("%02X ", report[i]); + if (i % 16 == 15) printf("\n "); + else if (i % 8 == 7) printf(" "); + } + printf("\n\n"); +} + +int main(int argc, char **args) { + + signal(SIGINT, interruptHandler); + + int result = hid_init(); + if (result < 0) { + printf("Failed to initialize HID library.\n"); + return -1; + } + + struct hid_device_info* enumerationHead = hid_enumerate(0, 0); + struct hid_device_info * deviceInfo; + size_t serialLength = std::wcslen(L"Serial #"); + size_t vendorLength = std::wcslen(L"Vendor"); + size_t pathLength = std::strlen("Path"); + for (deviceInfo = enumerationHead; deviceInfo; deviceInfo = deviceInfo->next) { + serialLength = std::max(serialLength, wcslen(deviceInfo->serial_number)); + vendorLength = std::max(vendorLength, wcslen(deviceInfo->manufacturer_string)); + pathLength = std::max(pathLength, strlen(deviceInfo->path)); + } + + printf("\nNOTE: If running as non-root, you must have udev rules in place allowing access to usb devices.\n\n"); + printf("Index %-*s Vendor ID Product ID %-*ls Interface # %-*ls Product\n", static_cast(pathLength), + "Path", static_cast(serialLength), L"Serial #", static_cast(vendorLength), L"Vendor"); + + int count = 0; + for (deviceInfo = enumerationHead; deviceInfo; deviceInfo = deviceInfo->next, ++count) { + printf("%5d %-*s 0x%04hX 0x%04hX %-*ls %11d %-*ls %ls\n", count, + static_cast(pathLength), deviceInfo->path, + deviceInfo->vendor_id, deviceInfo->product_id, + static_cast(serialLength), deviceInfo->serial_number, + deviceInfo->interface_number, + static_cast(vendorLength), deviceInfo->manufacturer_string, + deviceInfo->product_string); + } + + int selection = -1; + printf("\n"); + + while (selection < 0 || selection > count - 1) { + char buffer[1024]; + printf("Select a device to listen to: "); + fgets(buffer, sizeof(buffer), stdin); + sscanf(buffer, "%d", &selection); + } + + deviceInfo = enumerationHead; + for (count = 0; count < selection; ++count) { + deviceInfo = deviceInfo->next; + } + + hid_device* device = hid_open_path(deviceInfo->path); + if (!device) { + perror("Failed to open device"); + return -1; + } + + hid_free_enumeration(enumerationHead); + + hid_set_nonblocking(device, 1); + + unsigned char report[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + int descriptor_size = hid_get_report_descriptor(device, report, sizeof(report)); + + if (descriptor_size < 0) { + perror("Error reading HID Report Descriptor from device"); + return -1; + } + + std::vector descriptor(report, report + descriptor_size); + std::vector dataVect; + + printHidDescriptor(descriptor); + + idf::HidDecoder decoder; + idf::HidDecoded devDecoded = *decoder.parseDescriptor(descriptor); + + unsigned char readTest[devDecoded.maxReportLength]; + unsigned char data[devDecoded.maxReportLength]; + + decoder.printDecodedInfo(devDecoded); + + std::cout << "Press to start decoding..." << std::endl; + std::cin.ignore(); + + while (keepReading) { + int bytesRead = 0; + int prevBytesRead = 0; + do { + bytesRead = hid_read(device, readTest, sizeof(readTest)); + if (bytesRead > 0) { + memcpy(&data, &readTest, sizeof(data)); + prevBytesRead = bytesRead; + } + } while (bytesRead > 0); + + if (bytesRead < 0) { + perror("Error reading from device"); + if (errno == EINTR) keepReading = false; + else return -1; + } + else { + + dataVect.clear(); + dataVect.assign(data, data + prevBytesRead); + + // attempt to decode + + std::string btnStr = ""; + int btnStart = -1; + int btnCnt = 0; + int rows = 0; + + for (idf::HidReport r : devDecoded.reports) { + if (!r.has_report_byte || (r.has_report_byte && static_cast(data[0]) == r.id)) { + printf("Decode report (%d) values:\n", r.id); + ++rows; + for( idf::HidInput input : r.inputs) { + if (input.name != "Unknown") { + + // cluster buttons together for easier reading + // while keeping them in order + if (input.usage == idf::USAGE_BUTTON) { + + if (btnCnt == 0) { + btnStart = input.button_num; + } + else if (btnCnt == 4) btnStr.append(" "); + + u_int64_t tmp = decoder.extractValue(input, dataVect, false); + btnStr.append(tmp & 0x1 ? "1" : "0"); + ++btnCnt; + + if (btnCnt == 8) { + std::cout << " Buttons " << std::setw(3) << std::right << btnStart << "-" << std::left << std::setw(3) << btnStart+btnCnt-1 << " " << btnStr.c_str() << std::endl; + btnCnt = 0; + btnStr.clear(); + rows++; + } + } // everything else + else { + if (btnCnt) {std::cout << " Buttons " << std::setw(3) << std::right << btnStart << "-" << std::left << std::setw(3) << btnStart+btnCnt-1 << " " << btnStr.c_str() << std::endl; + btnCnt = 0; + btnStr.clear(); + rows++; + } + + u_int64_t tmp = decoder.extractValue(input, dataVect, true); + + if (decoder.isUsageKnown(input.usage)) rows++; + } + } + } + } + } + + // a short sleep prevents the cursor from visually jumping all over the place + usleep(100000); + if (!keepReading) break; + if (rows > 0) { // move the cursor up appropriate number of lines to visually update the read out + for (int i = 0; i < rows; ++i) { + std::cout << "\x1b[A"; + } + } + } + } + + hid_close(device); + + hid_exit(); + return 0; +} diff --git a/include/idf/GenericJoystick.hh b/include/idf/GenericJoystick.hh new file mode 100644 index 0000000..d4362e6 --- /dev/null +++ b/include/idf/GenericJoystick.hh @@ -0,0 +1,81 @@ +/* +PURPOSE: +LIBRARY DEPENDENCIES: ( +(idf/GenericJoystick.cpp) + +) +*/ + +/** + * @trick_parse{everything} + * @trick_link_dependency{idf/GenericJoystick.cpp} + */ + + +#ifndef GENERIC_JOYSTICk_HH +#define GENERIC_JOYSTICk_HH + +#include "idf/InputLayout.hh" +#include "idf/SingleInput.hh" + +namespace idf { + +/** + * @brief Generic joystick layout. Implementing device should guarantee at least: + * Roll axis, Pitch axis and Trigger button. Other Inputs are not guaranteed + * + * @author Philip Kunz + */ +class GenericJoystick : public virtual InputLayout { + +public: + + GenericJoystick(); + + virtual ~GenericJoystick() {}; + + // forward-backward pivoting + SingleInput forwardBackwardPivot; + + // left-right pivoting + SingleInput leftRightPivot; + + // twisting + SingleInput twist; + + // the trigger button + SingleInput trigger; + + // list of remaining buttons (if any) + std::vectorbuttons; + + // the slider + SingleInput slider; + + // Hat Directions + SingleInput HatNorth; + SingleInput HatNorthEast; + SingleInput HatEast; + SingleInput HatSouthEast; + SingleInput HatSouth; + SingleInput HatSouthWest; + SingleInput HatWest; + SingleInput HatNorthWest; + + /** + * @brief Get the Nth Button object + * + * @param number number of button (1-based indexing) + * @return SingleInput* pointer to the appropriate button or null + */ + const SingleInput* getButton(const int number); + +protected: + + virtual const std::vector& getConfigurables(); + +}; + +} // namespace idf + +#endif diff --git a/include/idf/HidDecoder.hh b/include/idf/HidDecoder.hh new file mode 100644 index 0000000..4673a27 --- /dev/null +++ b/include/idf/HidDecoder.hh @@ -0,0 +1,210 @@ +/* +PURPOSE: +LIBRARY DEPENDENCIES: ( +(idf/HidDecoder.cpp) +) +*/ + +/** + * @trick_parse{everything} + * @trick_link_dependency{idf/HidDecoder.cpp} + */ + +#ifndef HID_DECODER_HH +#define HID_DECODER_HH + +#include +#include +#include +#include +#include +#include "hidapi/hidapi/hidapi.h" + +namespace idf +{ + +struct HidInput +{ + u_int8_t usage; + std::string name; + int start_bit; + int end_bit; + int logical_min; + int logical_max; + int physical_min; + int physical_max; + int units; + int units_exp; + int button_num; +}; + +struct HidReport +{ + int id; + std::vector inputs; + int bytes_count; + bool has_report_byte; +}; + +struct HidDecoded +{ + std::string type; + std::vector reports; + int maxReportLength; +}; + +enum HidUsages { + USAGE_POINTER = 0x01, + USAGE_JOYSTICK = 0x04, + USAGE_GAMEPAD = 0x05, + USAGE_MULTIAXIS = 0x08, + USAGE_BUTTON = 0x09, + USAGE_X = 0x30, + USAGE_Y = 0x31, + USAGE_Z = 0x32, + USAGE_RX = 0x33, + USAGE_RY = 0x34, + USAGE_RZ = 0x35, + USAGE_SLIDER = 0x36, + USAGE_DIAL = 0x37, + USAGE_WHEEL = 0x38, + USAGE_HAT = 0x39, + USAGE_START = 0x3D, + USAGE_SELECT = 0x3E, +}; + +enum MainItemTag { + MAIN_INPUT = 0x8, + MAIN_OUTPUT = 0x9, + MAIN_COLLECTION = 0xA, + MAIN_FEATURE = 0xB, + MAIN_END_COLLECTION = 0xC, +}; + +enum GlobalItemTag { + GLOBAL_USAGE_PAGE = 0x0, + GLOBAL_LOGICAL_MINIMUM = 0x1, + GLOBAL_LOGICAL_MAXIMUM = 0x2, + GLOBAL_PHYSICAL_MINIMUM = 0x3, + GLOBAL_PHYSICAL_MAXIMUM = 0x4, + GLOBAL_UNIT_EXPONENT = 0x5, + GLOBAL_UNITS = 0x6, + GLOBAL_REPORT_SIZE = 0x7, + GLOBAL_REPORT_ID = 0x8, + GLOBAL_REPORT_COUNT = 0x9, + GLOBAL_PUSH = 0xA, + GLOBAL_POP = 0xB, +}; + +enum LocalItemTags { + LOCAL_USAGE = 0x0, + LOCAL_MINIMUM = 0x1, + LOCAL_MAXIMUM = 0x2, + LOCAL_DESIGNATOR_IDX = 0x3, + LOCAL_DESIGNATOR_MINIMUM = 0x4, + LOCAL_DESIGNATOR_MAXIMUM = 0x5, + LOCAL_STRING_IDX = 0x7, + LOCAL_STRING_MINIMUM = 0x8, + LOCAL_STRING_MAXIMUM = 0x9, + LOCAL_DELIMITER = 0xA, +}; + +class HidDecoder +{ + +public: + HidDecoder(); + + /** + * @brief parse a list of @a HidReports with @a HidInputs from an HID + * Report Descriptor. Only @a Input items are kept. @a Output and + * @a feature items are discarded + * + * @param descriptor binary HID report descriptor. + * @return HidDecoded struct with a list of HidReports and some metadata + */ + HidDecoded* parseDescriptor(const std::vector& descriptor); + + + /** + * @brief Print out decoded information to stdout + * + * @param decoded an @a HidDecoded struct + */ + static void printDecodedInfo(const HidDecoded decoded); + + + /** + * @brief extract the appropriate binary value from the data for a given HidInput + * + * @param input HidInput retrieved from parsing HID report descriptor + * @param data data that was read in from device + * @return u_int64_t raw binary data extracted from device. + */ + u_int64_t extractValue(const HidInput& input, const std::vector& data, const bool print = false) const; + + bool isUsageKnown(const uint usage) const; + std::string usageName(const uint usage) const; + +private: + void init(); + void decodeGlobalItem(const int tag_code, const int data, const std::vector& data_bytes); + void decodeLocalItem(const int tag_code, const int data); + void decodeMainItem(const int tag_code); + void createInputs(); + int convertDataToInt(const std::vector &data, const bool isSigned) const; + bool interpretSigned() const; + + struct HIDState { + uint report_size; + uint report_count; + int usage_page; + int usage_min; + int usage_max; + int logical_min; + int logical_max; + int physical_min; + int physical_max; + int units; + int units_exp; + }; + + int vendorId; + int productId; + + std::string device_type = "Unknown"; + std::vector reports; + std::vector inputs; + int bit_offset = 0; + int report_id = 0; + std::vector usage_list; + + HIDState state; + std::vector state_stack; + int next_button = 1; + + const std::map usage_names = { + {USAGE_POINTER, "Pointer"}, + {USAGE_JOYSTICK, "Joystick"}, + {USAGE_GAMEPAD, "Gamepad"}, + {USAGE_MULTIAXIS, "Multi-axis Controller"}, + {USAGE_BUTTON, "Button"}, + {USAGE_X, "X"}, + {USAGE_Y, "Y"}, + {USAGE_Z, "Z"}, + {USAGE_RX, "Rx"}, + {USAGE_RY, "Ry"}, + {USAGE_RZ, "Rz"}, + {USAGE_SLIDER, "Slider"}, + {USAGE_DIAL, "Dial"}, + {USAGE_WHEEL, "Wheel"}, + {USAGE_HAT, "Hat switch"}, + {USAGE_START, "Start"}, + {USAGE_SELECT, "Select"}, + }; + +}; // HidDecoder + +} // namespace + +#endif diff --git a/include/idf/HidDevice.hh b/include/idf/HidDevice.hh new file mode 100644 index 0000000..3055f19 --- /dev/null +++ b/include/idf/HidDevice.hh @@ -0,0 +1,79 @@ +/* PURPOSE: +LIBRARY DEPENDENCIES: ( +(idf/HidDevice.cpp) +) +*/ + +#ifndef IDF_HID_DEVICE_HH +#define IDF_HID_DEVICE_HH + +#include "idf/UsbDevice.hh" +#include "idf/HidDecoder.hh" + +namespace idf { + +/** + * @brief HidDevice is intended to help make IDF useful with generic + * gaming devices that do not yet have a specific implementation. + * ie joysticks, gamepads and "multi-axis" controllers + * @author Philip Kunz + * @date 2025-08-07 + */ +class HidDevice : public UsbDevice { + +public: + + /** + * @brief Find the specified device, read and decode its HID Report + * Descriptor, then instantiate an HidDevice based on the decoded + * input information + * + * @param vendor HID Vendor ID to find the connected device + * @param product HID product ID to find the connected device + * @param interface specific interface of the connected device. HID + * Scanner @link https://github.com/nasa/IDF/wiki/HID-Scanner can + * help identify the correct interface + */ + HidDevice(const int vendor, const int product, const int interface); + + HidDevice(const HidDecoded* decoded_in); + + virtual ~HidDevice() {}; + + /** + * @brief convenience method for displaying the Report Descriptor + * in hex + */ + void printHidDescriptor(); + + /** + * @brief convenience method for displaying the available reports and + * inputs + */ + void printDecodedHidInfo(); + + /** + * @brief open device with @a vendor and @a product, and parse the HID + * report descriptor. This method exists mainly to ease instantiation + * + * @param vendor USB Vendor ID + * @param product USB Product ID + * @return HIDDecodedDevice struct enumerating the available reports + */ + static HidDecoded* decodeDevice(const int vendor, const int product); + + +protected: + HidDecoder decoder; + + HidDecoded decoded; + + std::vector hidReportDescriptor; + + virtual std::vector getHidReportDescriptor(); + +}; + +} // namespace idf + +#endif diff --git a/include/idf/HidGenericJoystick.hh b/include/idf/HidGenericJoystick.hh new file mode 100644 index 0000000..c7d8773 --- /dev/null +++ b/include/idf/HidGenericJoystick.hh @@ -0,0 +1,64 @@ +/* +PURPOSE: +LIBRARY DEPENDENCIES: ( +(idf/HidGenericJoystick.cpp) + +) +*/ + +/** + * @trick_parse{everything} + * @trick_link_dependency{idf/HidGenericJoystick.cpp} + */ + + +#ifndef HID_GENERIC_JOYSTICK_HH +#define HID_GENERIC_JOYSTICK_HH + +#include "idf/HidDevice.hh" +#include "idf/GenericJoystick.hh" + +namespace idf { + +/** + * @brief Generic class to open any HID device, read the HID Report Descriptor + * and construct a usable controller with at least Roll axis, Pitch axis and + * Trigger button. Attempts will also be made to map, but no guarantee is provided: + * Throttle (Slider), Hat (8 directional) and a std::vector of buttons. + * + * @author Philip Kunz + */ +class HidGenericJoystick : public HidDevice, public virtual GenericJoystick { + + public: + + /** + * @brief Construct a Generic Joystick derived from HID Report Descriptor + * of the given device + * + * @param vendor HID Vendor ID to find the connected device + * @param product HID product ID to find the connected device + * @param interface specific interface of the connected device. HID + * Scanner @link https://github.com/nasa/IDF/wiki/HID-Scanner can + * help identify the correct interface + */ + HidGenericJoystick(const int vendor, const int product, const int interface); + + void decode(const std::vector& data); + + protected: + + /** + * @brief Flag indicating whether the report descriptor contained a Z axis. If so + use that for twist. Most joysticks will give the 'Rz' usage (0x35) for + the twist axis. But some may have additional inputs and often this + leads to using Z for twist. - This is NOT guaranteed, and may need to be + manually overridden. + */ + bool useZForTwist; + +}; + +} // namespace idf + +#endif diff --git a/include/idf/PythonInterface.hh b/include/idf/PythonInterface.hh index 98335d9..3e16779 100644 --- a/include/idf/PythonInterface.hh +++ b/include/idf/PythonInterface.hh @@ -73,6 +73,9 @@ #include "idf/UsbXBox.hh" #include "idf/UsbXBoxOne.hh" +// Generic HID Devices +#include "idf/HidGenericJoystick.hh" + // Ethernet Devices #include "idf/EthernetExtreme3dPro.hh" #include "idf/EthernetWingMan.hh" diff --git a/include/idf/SingleCameraController.hh b/include/idf/SingleCameraController.hh index 5407cdd..092660e 100644 --- a/include/idf/SingleCameraController.hh +++ b/include/idf/SingleCameraController.hh @@ -22,6 +22,7 @@ LIBRARY DEPENDENCIES: ( #include "idf/DacoThc.hh" #include "idf/DualShock.hh" #include "idf/Extreme3dPro.hh" +#include "idf/GenericJoystick.hh" #include "idf/Gravis.hh" #include "idf/IndustrialProducts.hh" #include "idf/IndustrialProducts2.hh" @@ -274,10 +275,21 @@ class SingleCameraController : public CameraController { * * @param virpil the inputs to use in the default mapping * - * @return a new Virpil Constellation Alhpa based lander controller + * @return a new Virpil Constellation Alhpa based camera controller */ static SingleCameraController* createInstance(const VirpilConstellationAlpha& virpil); + /** + * creates a new SingleCameraController mapped to @a HidGenericJoystick using appropriate + * defaults. Since this is a generic mapping without specific knowledge of the + * devices physical layout, it is merely a best guess according to common practices. Thus, + * some outputs may not be mapped to an real input. + * + * @param js the inputs to use in the default mapping + * + * @return a new Generic HID based camera controller + */ + static SingleCameraController* createInstance(const GenericJoystick& js); }; } diff --git a/include/idf/SingleFlightController.hh b/include/idf/SingleFlightController.hh index f258b41..89e1ab4 100644 --- a/include/idf/SingleFlightController.hh +++ b/include/idf/SingleFlightController.hh @@ -22,6 +22,7 @@ LIBRARY DEPENDENCIES: ( #include "idf/DacoThc.hh" #include "idf/DualShock.hh" #include "idf/Extreme3dPro.hh" +#include "idf/GenericJoystick.hh" #include "idf/Gravis.hh" #include "idf/IndustrialProducts.hh" #include "idf/IndustrialProducts2.hh" @@ -278,7 +279,7 @@ class SingleFlightController : public FlightController { * * @param dacoThc the inputs to use in the default mapping * - * @return a new DacoThc-based camera controller + * @return a new DacoThc-based flight controller */ static SingleFlightController* createInstance(const DacoThc& dacoThc); @@ -288,10 +289,22 @@ class SingleFlightController : public FlightController { * * @param virpil the inputs to use in the default mapping * - * @return a new Virpil Constellation Alhpa based lander controller + * @return a new Virpil Constellation Alhpa based flight controller */ static SingleFlightController* createInstance(const VirpilConstellationAlpha& virpil); + /** + * creates a new SingleFlightController mapped to @a HidGenericJoystick using appropriate + * defaults. Since this is a generic mapping without specific knowledge of the + * devices physical layout, it is merely a best guess according to common practices. Thus, + * some outputs may not be mapped to an real input. + * + * @param js the inputs to use in the default mapping + * + * @return a new Generic HID based flight controller + */ + static SingleFlightController* createInstance(const GenericJoystick& js); + }; } diff --git a/include/idf/SingleRoboticsController.hh b/include/idf/SingleRoboticsController.hh index 2fab376..23c0578 100644 --- a/include/idf/SingleRoboticsController.hh +++ b/include/idf/SingleRoboticsController.hh @@ -22,6 +22,7 @@ LIBRARY DEPENDENCIES: ( #include "idf/DacoThc.hh" #include "idf/DualShock.hh" #include "idf/Extreme3dPro.hh" +#include "idf/GenericJoystick.hh" #include "idf/Gravis.hh" #include "idf/Er7Orion.hh" #include "idf/IndustrialProducts.hh" @@ -352,10 +353,21 @@ class SingleRoboticsController : public RoboticsController { * * @param virpil the inputs to use in the default mapping * - * @return a new Virpil Constellation Alhpa based lander controller + * @return a new Virpil Constellation Alhpa based robotics controller */ static SingleRoboticsController* createInstance(const VirpilConstellationAlpha& virpil); + /** + * creates a new SingleRoboticsController mapped to @a HidGenericJoystick using appropriate + * defaults. Since this is a generic mapping without specific knowledge of the + * devices physical layout, it is merely a best guess according to common practices. Thus, + * some outputs may not be mapped to an real input. + * + * @param js the inputs to use in the default mapping + * + * @return a new Generic HID based robotics controller + */ + static SingleRoboticsController* createInstance(const GenericJoystick& js); }; } diff --git a/source/idf/GenericJoystick.cpp b/source/idf/GenericJoystick.cpp new file mode 100644 index 0000000..a2387de --- /dev/null +++ b/source/idf/GenericJoystick.cpp @@ -0,0 +1,45 @@ +#include "idf/GenericJoystick.hh" +#include + +namespace idf { + + +GenericJoystick::GenericJoystick() : + forwardBackwardPivot(0, 1023, 512), + leftRightPivot(0, 1023, 512), + twist(0, 1023, 512), + trigger(0,1), + slider(0, 1023, 512), + HatNorth(0,1), + HatNorthEast(0,1), + HatEast(0,1), + HatSouthEast(0,1), + HatSouth(0,1), + HatSouthWest(0,1), + HatWest(0,1), + HatNorthWest(0,1) { + buttons = {}; + } + +const SingleInput* GenericJoystick::getButton(const int number) { + try { + return buttons.at(number-1); + } + catch (const std::out_of_range& e) { + return NULL; + } +} + +const std::vector& GenericJoystick::getConfigurables() { + static std::vector inputs; + if (inputs.empty()) { + append(InputLayout::getConfigurables(), inputs); + inputs.push_back(Configurable(forwardBackwardPivot, "Forward/Backward Pivot", "forwardBackwardPivot")); + inputs.push_back(Configurable(leftRightPivot, "Left/Right Pivot", "leftRightPivot")); + inputs.push_back(Configurable(twist, "Twist", "twist")); + inputs.push_back(Configurable(slider, "Slider", "slider")); + } + return inputs; +} + +} // namespace idf diff --git a/source/idf/HidDecoder.cpp b/source/idf/HidDecoder.cpp new file mode 100644 index 0000000..7d41748 --- /dev/null +++ b/source/idf/HidDecoder.cpp @@ -0,0 +1,447 @@ +#include "idf/HidDecoder.hh" +#include +#include +#include +#include +#include + +namespace idf +{ + + +HidDecoder::HidDecoder() +{ + init(); +} + + +void HidDecoder::init() +{ + reports.clear(); + inputs.clear(); + bit_offset = 0; + report_id = 0; + usage_list.clear(); + state.report_size = 0; + state.report_count = 0; + state.usage_page = 0; + state.usage_min = 0; + state.usage_max = 0; + state.logical_min = 0; + state.logical_max = 0; + state.physical_min = 0; + state.physical_max = 0; + state.units = 0; + state.units_exp = 0; + device_type = "Unknown"; + state_stack.clear(); + next_button = 1; +} + + +HidDecoded* HidDecoder::parseDescriptor(const std::vector &descriptor) +{ + init(); + uint i = 0; + + while (i < descriptor.size()) { + unsigned char prefix = descriptor[i]; + int size_code = prefix & 0x03; + int type_code = (prefix >> 2) & 0x03; + int tag_code = (prefix >> 4) & 0x0F; + int data = 0; + std::vector data_bytes; + + int size = size_code; + if (size_code == 3) { + size = 4; + } + + if (size > 0) { + data_bytes = std::vector(descriptor.begin() + i + 1, descriptor.begin() + i + 1 + size); + data = 0; + for (int j = 0; j < size; ++j) { + data |= (data_bytes[j] << (j * 8)); + } + } + + if (type_code == 0) { // Main + decodeMainItem(tag_code); + } + else if (type_code == 1) { // Global + decodeGlobalItem(tag_code, data, data_bytes); + } + else if (type_code == 2) { // Local + decodeLocalItem(tag_code, data); + } + + i += 1 + size; + } + + // add final report to list + reports.push_back({ + report_id, + inputs, + bit_offset / 8, + report_id != 0 + }); + + int maxReport = -1; + for (HidReport rep : reports) { + if (rep.inputs.size() > 0) { + maxReport = std::max(maxReport, rep.bytes_count); + } + } + + HidDecoded* decoded = new HidDecoded(); + + decoded->type = device_type; + decoded->reports = reports; + decoded->maxReportLength = maxReport; + + return decoded; +} + + +void HidDecoder::decodeGlobalItem(const int tag_code, int data, const std::vector &data_bytes) +{ + switch(tag_code) { + case GLOBAL_USAGE_PAGE: + state.usage_page = data; + break; + + case GLOBAL_LOGICAL_MINIMUM: + state.logical_min = convertDataToInt(data_bytes, true); + break; + + case GLOBAL_LOGICAL_MAXIMUM: + state.logical_max = convertDataToInt(data_bytes, true); + break; + + case GLOBAL_PHYSICAL_MINIMUM: + state.physical_min = convertDataToInt(data_bytes, interpretSigned()); + break; + + case GLOBAL_PHYSICAL_MAXIMUM: + state.physical_max = convertDataToInt(data_bytes, interpretSigned()); + break; + + case GLOBAL_UNIT_EXPONENT: + state.units_exp = static_cast(data_bytes[0]); + break; + + case GLOBAL_UNITS: + state.units = data; + if (data == 0) + { + state.units = 0; + state.units_exp = 0; + state.physical_min = 0; + state.physical_max = 0; + } + break; + + case GLOBAL_REPORT_SIZE: + state.report_size = data; + break; + + case GLOBAL_REPORT_ID: + if (report_id != 0) + { // if not the 1st report, save inputs to a report + reports.push_back({report_id, + inputs, + bit_offset / 8, + true}); + inputs.clear(); + } + + bit_offset = 8; + report_id = data; + break; + + case GLOBAL_REPORT_COUNT: + state.report_count = data; + break; + + case GLOBAL_PUSH: + state_stack.push_back(state); + break; + + case GLOBAL_POP: + if (!state_stack.empty()) + { + HIDState tmp = state_stack.back(); + state.usage_page = tmp.usage_page; + state.usage_min = tmp.usage_min; + state.usage_max = tmp.usage_max; + state.report_size = tmp.report_size; + state.report_count = tmp.report_count; + state.logical_min = tmp.logical_min; + state.logical_max = tmp.logical_max; + state.physical_min = tmp.physical_min; + state.physical_max = tmp.physical_max; + state.units = tmp.units; + state.units_exp = tmp.units_exp; + state_stack.pop_back(); + } + break; + + default: + break; + } +} + + +void HidDecoder::decodeLocalItem(const int tag_code, const int data) +{ + switch(tag_code) { + case LOCAL_USAGE: + if (data < USAGE_X){ + if (device_type == "Unknown") { + device_type = usageName(data); + } + } + else { + usage_list.push_back(data); + } + break; + + case LOCAL_MINIMUM: + state.usage_min = data; + break; + + case LOCAL_MAXIMUM: + state.usage_max = data; + break; + + case LOCAL_DESIGNATOR_IDX: // not implemented + case LOCAL_DESIGNATOR_MINIMUM: // not implemented + case LOCAL_DESIGNATOR_MAXIMUM: // not implemented + case LOCAL_STRING_IDX: // not implemented + case LOCAL_STRING_MINIMUM: // not implemented + case LOCAL_STRING_MAXIMUM: // not implemented + case LOCAL_DELIMITER: // not implemented + default: break; // items not yet defined + } +} + + +void HidDecoder::decodeMainItem(const int tag_code) +{ + switch(tag_code) { + case MAIN_INPUT: + createInputs(); + break; + + case MAIN_OUTPUT: + case MAIN_FEATURE: // Outputs and Features are not supported + usage_list.clear(); + state.usage_min = 0; + state.usage_max = 0; + break; + + case MAIN_COLLECTION: // not implemented + case MAIN_END_COLLECTION: // not implemented + default: // items not yet defined + break; + } +} + +void HidDecoder::createInputs() +{ + std::vector expanded_usages; + + if (state.usage_min != 0 && state.usage_max != 0) + { + for (int j = state.usage_min; j <= state.usage_max; ++j) + { + expanded_usages.push_back(j); + } + } + else if (!usage_list.empty()) { + expanded_usages = usage_list; + } + + for (uint j = 0; j < state.report_count; ++j) { + std::string name; + u_int8_t usage = 0; + int btn_num = -1; + if (state.usage_page == 0x01 && j < expanded_usages.size()) { + usage = expanded_usages[j]; + name = usageName(usage); + } + else if (state.usage_page == USAGE_BUTTON && j < expanded_usages.size()) { + usage = USAGE_BUTTON; + btn_num = next_button++; + name = "Button " + std::to_string(btn_num); + } + else if (state.usage_page == 0x01 && expanded_usages.size() == 1) { + usage = expanded_usages[0]; + name = usageName(usage); + } + else { + name = "Padding"; + } + + int start_bit = bit_offset; + int end_bit = bit_offset + state.report_size - 1; + + int tmp_phys_min = state.physical_min; + int tmp_phys_max = state.physical_max; + + // when physical min/max are not set, default to logical + if (state.physical_min == 0) tmp_phys_min = state.logical_min; + if (state.physical_max == 0) tmp_phys_max = state.logical_max; + + inputs.push_back({ usage, + name, + start_bit, + end_bit, + state.logical_min, + state.logical_max, + tmp_phys_min, + tmp_phys_max, + state.units, + state.units_exp, + btn_num}); + + bit_offset += state.report_size; + } + + usage_list.clear(); + state.usage_min = 0; + state.usage_max = 0; +} + +bool HidDecoder::interpretSigned() const +{ + return state.logical_max < 0 || state.logical_min < 0; +} + + +int HidDecoder::convertDataToInt(const std::vector &data, const bool isSigned) const +{ + u_int32_t value = 0; + + for (uint i = 0; i < data.size(); ++i) { + value += (data[i] << 8*i); + } + + if (!isSigned) return value; + + switch (data.size()) { + case 1: + if (value >= pow(2,7)) return static_cast(value); + break; + case 2: + if (value >= pow(2,15)) return static_cast(value); + break; + case 4: + if (value >= pow(2,31)) return static_cast(value); + break; + } + return value; +} + + +void HidDecoder::printDecodedInfo(const HidDecoded decoded) +{ + std::ostringstream ss; + ss << "Device Type: " << decoded.type << "\n"; + for (HidReport report : decoded.reports) { + ss << "Report: " << report.id << " (" << report.bytes_count << " bytes)\n"; + if (report.id != 0) { + ss << " Report ID bits 0:7 value: " << report.id << "\n"; + } + for(HidInput input : report.inputs) { + ss << " " << std::setfill(' ') << std::setw(15) << std::left << input.name; + if (input.start_bit == input.end_bit) { + ss << " bit " << std::setw(5) << std::right << input.start_bit << " range: "; + ss << std::setw(5) << std::right << input.logical_min << ":"; + ss << std::left << input.logical_max << "\n"; + } + else { + ss << " bits " << std::setw(5) << std::right << input.start_bit << ":"; + ss << std::setw(5) << std::left << input.end_bit; + ss << " range: "; + ss << std::setw(5) << std::right << input.logical_min << ":"; + ss << std::setw(5) << std::left << input.logical_max << " "; + ss << std::setw(5) << std::right << input.physical_min << ":"; + ss << std::setw(5) << std::left << input.physical_max << "\n"; + } + } + ss << "\n"; + } + printf(ss.str().c_str()); +} + + +u_int64_t HidDecoder::extractValue(const HidInput& input, const std::vector& data, const bool print) const +{ + if (!isUsageKnown(input.usage)) return 0; + int startByte = input.start_bit / 8; + int startBit = input.start_bit % 8; + int endByte = input.end_bit / 8; + u_int64_t temp = 0; // while HID theoretically allows an item to be 255 bytes, it is + u_int64_t mask = 1; // very unlikely that a single HC input will need >16 bits + + mask = (mask << (input.end_bit - input.start_bit +1)) - 1; + + // stack bytes in reverse to get continuous bits + for(int i = endByte; i >= startByte; --i) { + temp = temp << 8; + temp |= data[i]; + } + + temp = (temp >> startBit) & mask; + + if (print) { + int endBit = input.end_bit % 8; + std::string paddedName = input.name; + std::string endBitsStr = std::to_string(endByte) + "[" + std::to_string(endBit) + "]"; + paddedName.append(14 - paddedName.length(), ' '); + endBitsStr.append(10 - endBitsStr.length(), ' '); + std::cout << " " << paddedName \ + << std::setw(4) << std::right << startByte << '[' << std::setw(0) << startBit << "]-" \ + << endBitsStr \ + << "mask(0x" << std::setw(4) << std::setfill('0') << std::hex << mask << ") = " \ + << std::setfill(' ') << std::setw(7) << std::dec << temp << std::endl; + + } + return temp; +} + +bool HidDecoder::isUsageKnown(const uint usage ) const +{ + switch(usage) { + case(USAGE_POINTER): + case(USAGE_JOYSTICK): + case(USAGE_GAMEPAD): + case(USAGE_MULTIAXIS): + case(USAGE_BUTTON): + case(USAGE_X): + case(USAGE_Y): + case(USAGE_Z): + case(USAGE_RX): + case(USAGE_RY): + case(USAGE_RZ): + case(USAGE_SLIDER): + case(USAGE_DIAL): + case(USAGE_WHEEL): + case(USAGE_HAT): + case(USAGE_START): + case(USAGE_SELECT): + return true; break; + default: + return false; break; + } +} + +std::string HidDecoder::usageName(const uint usage) const +{ + if (!isUsageKnown(usage)) return "Unknown"; + + return usage_names.at(usage); +} + +} // namespace diff --git a/source/idf/HidDevice.cpp b/source/idf/HidDevice.cpp new file mode 100644 index 0000000..5e95a60 --- /dev/null +++ b/source/idf/HidDevice.cpp @@ -0,0 +1,83 @@ +#include "idf/HidDevice.hh" +#include "idf/IOException.hh" +#include +#include +#include +#include +#include + +namespace idf { + + +HidDevice::HidDevice(const int vendor, const int product, const int interface) : + HidDevice(HidDevice::decodeDevice(vendor, product)) { + addIdentification(Identification(vendor, product, interface)); + } + + +HidDevice::HidDevice(const HidDecoded* decoded_in) : + UsbDevice("Generic " + decoded_in->type, decoded_in->maxReportLength), + decoded(*decoded_in) {} + + +HidDecoded* HidDevice::decodeDevice(const int vendor, const int product) { + std::ostringstream ss; + hid_device* hidDevice; + unsigned char buffer[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + HidDecoder decoder; + + if (!(hidDevice = hid_open(vendor, product, NULL))) { + ss << "unable to open device " << std::hex << vendor << ":" << product << " : "; + ss << strerror(errno) << std::endl; + throw IOException(ss.str()); + } + + int descSize = hid_get_report_descriptor(hidDevice, buffer, sizeof(buffer)); + + if (descSize < 0) { + ss << "unable to get HID report descriptor from " << std::hex << std::setw(4) << std::setfill('0') << vendor << ":" << product << " : "; + ss << strerror(errno) << std::endl; + hid_close(hidDevice); + throw IOException(ss.str()); + } + + std::vector descriptor(buffer, buffer + descSize); + HidDecoded* decDevice = decoder.parseDescriptor(descriptor); + + hid_close(hidDevice); + return decDevice; +} + + +std::vector HidDevice::getHidReportDescriptor() { + unsigned char buffer[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + int size = hid_get_report_descriptor(hidDevice, buffer, sizeof(buffer)); + + if (size < 0) { + perror("Unable to read HID Report Descriptor"); + return {}; + } + + std::vector report(buffer, buffer + size); + + return report; +} + + +void HidDevice::printHidDescriptor() { + std::vector report = getHidReportDescriptor(); + + printf("\nHID Report Descriptor (%lu bytes):\n ", report.size()); + for(size_t i=0; i < report.size(); ++i) { + printf("%02X ", report[i]); + if (i % 16 == 15) printf("\n "); + else if (i % 8 == 7) printf(" "); + } +} + + +void HidDevice::printDecodedHidInfo() { + decoder.printDecodedInfo(decoded); +} + +} //namespace idf diff --git a/source/idf/HidGenericJoystick.cpp b/source/idf/HidGenericJoystick.cpp new file mode 100644 index 0000000..35474d5 --- /dev/null +++ b/source/idf/HidGenericJoystick.cpp @@ -0,0 +1,91 @@ +#include "idf/HidGenericJoystick.hh" + +namespace idf { + + +HidGenericJoystick::HidGenericJoystick(const int vendor, const int product, const int interface) : + HidDevice(vendor, product, interface) { + // configure inputs and instantiate list of buttons + for( HidReport report : decoded.reports) { + for (HidInput input : report.inputs) { + switch(input.usage) { + case USAGE_BUTTON: + if (input.button_num == 1) + buttons.push_back(&trigger); + else + buttons.push_back(new SingleInput(0,1)); + break; + + case USAGE_X: + // legacy dictates that X is a pivot even though it is a liner axis + leftRightPivot.configure(input.logical_min, input.logical_max); + break; + + case USAGE_Y: + // legacy dictates that Y is a pivot even though it is a liner axis + forwardBackwardPivot.configure(input.logical_min, input.logical_max); + break; + + case USAGE_Z: + // Up to the manufacturer whether twist is Z or RZ, but the + // norm seems to be that if Z exists it is the twist axis + useZForTwist = true; + twist.configure(input.logical_min, input.logical_max); + break; + } + } + } + } + + +void HidGenericJoystick::decode(const std::vector& data) { + for (HidReport r : decoded.reports) { + if (!r.has_report_byte || (r.has_report_byte && static_cast(data[0]) == r.id)) { + for( HidInput input : r.inputs) { + u_int64_t value = decoder.extractValue(input, data); + switch (input.usage) { + case USAGE_BUTTON: + try { + buttons.at(input.button_num - 1)->setValue(value); + } + catch (std::out_of_range & e) {} + break; + + case USAGE_X: + leftRightPivot.setValue(value); + break; + + case USAGE_Y: + forwardBackwardPivot.setValue(value); + break; + + case USAGE_Z: + twist.setValue(value); + break; + + case USAGE_RZ: + if (!useZForTwist) twist.setValue(value); + break; + + case USAGE_SLIDER: + slider.setValue(value); + break; + + case USAGE_HAT: + int hat = value - input.logical_min; + HatNorth.setValue(hat == 0); + HatNorthEast.setValue(hat == 1); + HatEast.setValue(hat == 2); + HatSouthEast.setValue(hat == 3); + HatSouth.setValue(hat == 4); + HatSouthWest.setValue(hat == 5); + HatWest.setValue(hat == 6); + HatNorthWest.setValue(hat == 7); + break; + } + } + } + } +} + +} // namespace idf diff --git a/source/idf/SingleCameraController.cpp b/source/idf/SingleCameraController.cpp index d9dafe4..ec3c9a6 100644 --- a/source/idf/SingleCameraController.cpp +++ b/source/idf/SingleCameraController.cpp @@ -305,4 +305,25 @@ SingleCameraController* SingleCameraController::createInstance( return controller; } +SingleCameraController* SingleCameraController::createInstance(const GenericJoystick& js) { + + CompositeInput* zoom = new CompositeInput(); + zoom->addInput(*js.buttons.at(0)); + zoom->addInput(*js.buttons.at(1), -1); + + SingleCameraController *controller = + new SingleCameraController( + js.twist, + js.forwardBackwardPivot, + js.leftRightPivot, + *zoom + ); + + controller->pan.setInverted(true); + controller->tilt.setInverted(true); + controller->zoom.setInverted(true); + + return controller; +} + } diff --git a/source/idf/SingleFlightController.cpp b/source/idf/SingleFlightController.cpp index ed4c070..94eb321 100644 --- a/source/idf/SingleFlightController.cpp +++ b/source/idf/SingleFlightController.cpp @@ -389,4 +389,35 @@ SingleFlightController* SingleFlightController::createInstance(const VirpilConst return controller; } +SingleFlightController* SingleFlightController::createInstance(const GenericJoystick& js) { + SingleInput* dummyInput = new SingleInput(-1, 1); + + CompositeInput* x = new CompositeInput(); + x->addInput(js.HatEast); + x->addInput(js.HatWest, -1); + + CompositeInput* y = new CompositeInput(); + y->addInput(js.HatNorth); + y->addInput(js.HatSouth, -1); + + CompositeInput* z = new CompositeInput(); + z->addInput(*js.buttons.at(1)); + z->addInput(*js.buttons.at(2), -1); + + SingleFlightController *controller = + new SingleFlightController( + js.twist, + js.forwardBackwardPivot, + js.leftRightPivot, + *dummyInput, + *dummyInput, + *dummyInput + ); + + controller->pitch.setInverted(true); + controller->yaw.setInverted(true); + + return controller; +} + } diff --git a/source/idf/SingleRoboticsController.cpp b/source/idf/SingleRoboticsController.cpp index 6e566da..c148d23 100644 --- a/source/idf/SingleRoboticsController.cpp +++ b/source/idf/SingleRoboticsController.cpp @@ -537,4 +537,39 @@ SingleRoboticsController* SingleRoboticsController::createInstance(const VirpilC return controller; } +SingleRoboticsController* SingleRoboticsController::createInstance(const GenericJoystick& js) { + + CompositeInput* x = new CompositeInput(); + x->addInput(js.HatEast); + x->addInput(js.HatWest, -1); + + CompositeInput* y = new CompositeInput(); + y->addInput(js.HatNorth); + y->addInput(js.HatSouth, -1); + + CompositeInput* z = new CompositeInput(); + z->addInput(*js.buttons.at(1)); + z->addInput(*js.buttons.at(2), -1); + + SingleRoboticsController *controller = + new SingleRoboticsController( + js.twist, + js.forwardBackwardPivot, + js.leftRightPivot, + *x, + *y, + *z, + js.trigger, + js.slider + ); + + controller->pitch.setInverted(true); + controller->yaw.setInverted(true); + controller->x.setInverted(true); + controller->y.setInverted(true); + controller->z.setInverted(true); + + return controller; +} + } diff --git a/source/idf/UsbDevice.cpp b/source/idf/UsbDevice.cpp index 9b6aa3f..ce00fe2 100644 --- a/source/idf/UsbDevice.cpp +++ b/source/idf/UsbDevice.cpp @@ -9,9 +9,6 @@ #include #include -//deleteme -#include - namespace idf { int UsbDevice::instanceCount = 0;