From 65a0f63f5d244044014c198896db74c6250c1f9a Mon Sep 17 00:00:00 2001 From: F4JNT Date: Mon, 12 Jan 2026 13:31:48 +0100 Subject: [PATCH 1/5] Added CM108 PTT support --- 50-cm108-ptt.rules | 1 + Makefile | 3 +- cm108_ptt.hh | 60 ++++ hidapi.h | 691 +++++++++++++++++++++++++++++++++++++++++++++ kiss_tnc.cc | 31 +- kiss_tnc.hh | 6 +- tnc_ui.hh | 40 ++- 7 files changed, 821 insertions(+), 11 deletions(-) create mode 100644 50-cm108-ptt.rules create mode 100644 cm108_ptt.hh create mode 100644 hidapi.h diff --git a/50-cm108-ptt.rules b/50-cm108-ptt.rules new file mode 100644 index 0000000..b7fd429 --- /dev/null +++ b/50-cm108-ptt.rules @@ -0,0 +1 @@ +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0660", TAG+="uaccess" diff --git a/Makefile b/Makefile index 1a002ce..5bc025e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ CC = gcc CXXFLAGS = -std=c++17 -O3 -march=native -Wall -Wextra -LDFLAGS = -lpthread -ltinfo -lncurses -ldl -lm +LDFLAGS = -lpthread -ltinfo -lncurses -ldl -lm -lhidapi-hidraw # dependencies AICODIX_DSP ?= ../dsp @@ -34,6 +34,7 @@ clean: install: $(TARGET) install -m 755 $(TARGET) /usr/local/bin/ + $(shell cp 50-cm108-ptt.rules /etc/udev/rules.d/) # Debug build debug: CXXFLAGS = -std=c++17 -g -O0 -Wall -Wextra -DDEBUG diff --git a/cm108_ptt.hh b/cm108_ptt.hh new file mode 100644 index 0000000..ea37015 --- /dev/null +++ b/cm108_ptt.hh @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "hidapi.h" + +class CM108PTT { +public: + CM108PTT() = default; + + ~CM108PTT() { + close(); + } + + bool open(const int gpio){ + res_ = hid_init(); + gpio_ = gpio; + handle_ = hid_open(0x0D8C, 0x013C, NULL); + if (!handle_) { + std::cerr << "Failed to open CM108 PTT via USB" << std::endl; + hid_exit(); + return false; + } + return true; + } + + void close(){ + hid_close(handle_); + res_ = hid_exit(); + } + + void set_ptt(bool on){ + if (!handle_) return; + + unsigned char buf[5]; + buf[0] = 0x00; + buf[1] = 0x00; + + if (on){ + buf[2] = cm108_on_[gpio_-1]; + buf[3] = cm108_on_[gpio_-1]; + } else { + buf[2] = 0x00; + buf[3] = 0x00; + } + + res_ = hid_write(handle_, buf, 5); + } + +private: + int res_; + int gpio_; //#PTT control pin GPIOX , where X should be 1,2,3,4 - GPIO3 on most devices + const int cm108_on_[4] = {0x01, 0x02, 0x04, 0x08}; + hid_device *handle_; +}; \ No newline at end of file diff --git a/hidapi.h b/hidapi.h new file mode 100644 index 0000000..cbc3107 --- /dev/null +++ b/hidapi.h @@ -0,0 +1,691 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2023, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General 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: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +/* #480: this is to be refactored properly for v1.0 */ +#ifdef _WIN32 + #ifndef HID_API_NO_EXPORT_DEFINE + #define HID_API_EXPORT __declspec(dllexport) + #endif +#endif +#ifndef HID_API_EXPORT + #define HID_API_EXPORT /**< API export macro */ +#endif +/* To be removed in v1.0 */ +#define HID_API_CALL /**< API call macro */ + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +/** @brief Static/compile-time major version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MAJOR 0 +/** @brief Static/compile-time minor version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MINOR 16 +/** @brief Static/compile-time patch version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_PATCH 0 + +/* Helper macros */ +#define HID_API_AS_STR_IMPL(x) #x +#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) +#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) + +/** @brief Coverts a version as Major/Minor/Patch into a number: + <8 bit major><16 bit minor><8 bit patch>. + + This macro was added in version 0.12.0. + + Convenient function to be used for compile-time checks, like: + @code{.c} + #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + @endcode + + @ingroup API +*/ +#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) + +/** @brief Static/compile-time version of the library. + + This macro was added in version 0.12.0. + + @see @ref HID_API_MAKE_VERSION. + + @ingroup API +*/ +#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Static/compile-time string version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Maximum expected HID Report descriptor size in bytes. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API +*/ +#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 + +#ifdef __cplusplus +extern "C" { +#endif + /** A structure to hold the version numbers. */ + struct hid_api_version { + int major; /**< major version number */ + int minor; /**< minor version number */ + int patch; /**< patch version number */ + }; + + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** @brief HID underlying bus types. + + @ingroup API + */ + typedef enum { + /** Unknown bus type */ + HID_API_BUS_UNKNOWN = 0x00, + + /** USB bus + Specifications: + https://usb.org/hid */ + HID_API_BUS_USB = 0x01, + + /** Bluetooth or Bluetooth LE bus + Specifications: + https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ + https://www.bluetooth.com/specifications/specs/hid-service-1-0/ + https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ + HID_API_BUS_BLUETOOTH = 0x02, + + /** I2C bus + Specifications: + https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ + HID_API_BUS_I2C = 0x03, + + /** SPI bus + Specifications: + https://www.microsoft.com/download/details.aspx?id=103325 */ + HID_API_BUS_SPI = 0x04, + + /** Virtual device + E.g.: https://elixir.bootlin.com/linux/v4.0/source/include/uapi/linux/input.h#L955 + + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + */ + HID_API_BUS_VIRTUAL = 0x05, + } hid_bus_type; + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage; + /** The USB interface which this logical device + represents. + + Valid only if the device is a USB HID device. + Set to -1 in all other cases. + */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + + /** Underlying bus type + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + */ + hid_bus_type bus_type; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(NULL) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device_info, containing information about the HID devices + attached to the system, + or NULL in the case of failure or if no HID devices present in the system. + Call hid_error(NULL) to get the failure reason. + + @note The returned value by this function must to be freed by calling hid_free_enumeration(), + when not needed anymore. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first interrupt OUT + endpoint, if one exists. If it does not the behaviour is as + @ref hid_send_output_report + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. + Call hid_read_error(dev) to get the failure reason. + If no packet was available to be read within + the timeout period, this function returns 0. + + @note This function doesn't change the buffer returned by the hid_error(dev). + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. + Call hid_read_error(dev) to get the failure reason. + If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + + @note This function doesn't change the buffer returned by the hid_error(dev). + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Get a string describing the last error which occurred during hid_read/hid_read_timeout. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL for a valid @ref dev. + If there was no error in the last call to hid_read/hid_read_error - + the returned string clearly indicates that. + + Strings returned from hid_read_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + + @ingroup API + @param dev A device handle. Shall never be NULL. + + @returns + A string describing the hid_read/hid_read_timeout error (if any). + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Send a Output report to the device. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + Output reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_output_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_output_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + This function sets the return value of hid_error(). + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + + @see @ref hid_write + */ + int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); + + /** @brief Get a input report from a HID device. + + Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The struct #hid_device_info from a HID device. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a report descriptor from a HID device. + + Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) + + User has to provide a preallocated buffer where descriptor will be copied to. + The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buf The buffer to copy descriptor into. + @param buf_size The size of the buffer in bytes. + + @returns + This function returns non-negative number of bytes actually copied, or -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); + + /** @brief Get a string describing the last error which occurred. + + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL. + If there was no error in the last function call - + the returned string clearly indicates that. + + Any HIDAPI function that can explicitly indicate an execution failure + (e.g. by an error code, or by returning NULL) - may set the error string, + to be returned by this function. + + Strings returned from hid_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + Global error string may remain allocated at most until hid_exit() is called. + + @ingroup API + @param dev A device handle returned from hid_open(), + or NULL to get the last non-device-specific error + (e.g. for errors in hid_open() or hid_enumerate()). + + @returns + A string describing the last error (if any). + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); + + /** @brief Get a runtime version of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated struct, that contains version. + */ + HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); + + + /** @brief Get a runtime version string of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated string, that contains version string. + */ + HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/kiss_tnc.cc b/kiss_tnc.cc index 411065f..fb03056 100644 --- a/kiss_tnc.cc +++ b/kiss_tnc.cc @@ -30,6 +30,7 @@ #include "miniaudio_audio.hh" #include "rigctl_ptt.hh" #include "serial_ptt.hh" +#include "cm108_ptt.hh" #include "modem.hh" #ifdef WITH_UI @@ -184,6 +185,9 @@ public: config_.com_invert_rts)) { std::cerr << "Could not open COM port: " << serial_ptt_->last_error() << std::endl; } + } else if (config_.ptt_type == PTTType::CM108) { + cm108_ptt_ = std::make_unique(); + cm108_ptt_->open(config_.cm108_gpio); } else { dummy_ptt_ = std::make_unique(); dummy_ptt_->connect(); @@ -246,6 +250,9 @@ public: std::cerr << "PTT: COM " << config_.com_port << " (" << PTT_LINE_OPTIONS[config_.com_ptt_line] << ")" << std::endl; break; + case PTTType::CM108: + std::cerr << "PTT: CM108 (GPIO" << config_.cm108_gpio << ")"; + break; } // Start threads @@ -585,14 +592,14 @@ private: if (g_ui_state) g_ui_state->ptt_on = false; #endif } else { - // RIGCTL, COM, or NONE mode + // RIGCTL, COM, CM108 or NONE mode total_tx_duration += (config_.tx_delay_ms + config_.ptt_tail_ms) / 1000.0f; ui_log("TX: " + std::to_string(samples.size()) + " samples, " + std::to_string(duration) + " seconds"); - // PTT on (for RIGCTL or COM mode) - if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM) { + // PTT on (for RIGCTL, COM or CM108 mode) + if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM || config_.ptt_type == PTTType::CM108) { set_ptt(true); std::this_thread::sleep_for(std::chrono::milliseconds(config_.ptt_delay_ms)); } @@ -612,7 +619,7 @@ private: audio_->drain_playback(); // PTT off - if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM) { + if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM || config_.ptt_type == PTTType::CM108) { std::this_thread::sleep_for(std::chrono::milliseconds(config_.ptt_tail_ms)); set_ptt(false); } @@ -746,6 +753,8 @@ private: } else { serial_ptt_->ptt_off(); } + } else if (cm108_ptt_) { + cm108_ptt_->set_ptt(on); } else if (dummy_ptt_) { dummy_ptt_->set_ptt(on); } @@ -799,6 +808,7 @@ private: std::unique_ptr audio_; std::unique_ptr rigctl_; std::unique_ptr serial_ptt_; + std::unique_ptr cm108_ptt_; std::unique_ptr dummy_ptt_; int server_fd_ = -1; @@ -927,6 +937,7 @@ void print_help(const char* prog) { << " --vox-freq HZ VOX tone frequency (default: 1200)\n" << " --vox-lead MS VOX lead time in ms (default: 150)\n" << " --vox-tail MS VOX tail time in ms (default: 100)\n" + << " --cm108-gpio CM108 GPIO pin for PTT (default: 3)\n" << " --ptt-delay MS PTT delay before TX (default: 50)\n" << " --ptt-tail MS PTT tail after TX (default: 50)\n" << "\nCSMA options:\n" @@ -1011,8 +1022,9 @@ int main(int argc, char** argv) { if (ptt_type == "none") config.ptt_type = PTTType::NONE; else if (ptt_type == "rigctl") config.ptt_type = PTTType::RIGCTL; else if (ptt_type == "vox") config.ptt_type = PTTType::VOX; + else if (ptt_type == "cm108") config.ptt_type = PTTType::CM108; else { - std::cerr << "Unknown PTT type: " << ptt_type << " (use none, rigctl, or vox)\n"; + std::cerr << "Unknown PTT type: " << ptt_type << " (use none, rigctl, cm108 or vox)\n"; return 1; } } else if (arg == "--vox-freq" && i + 1 < argc) { @@ -1021,6 +1033,8 @@ int main(int argc, char** argv) { config.vox_lead_ms = std::atoi(argv[++i]); } else if (arg == "--vox-tail" && i + 1 < argc) { config.vox_tail_ms = std::atoi(argv[++i]); + } else if (arg == "--cm108-gpio" && i + 1 < argc) { + config.cm108_gpio = std::atoi(argv[++i]); } else if (arg == "--ptt-delay" && i + 1 < argc) { config.ptt_delay_ms = std::atoi(argv[++i]); } else if (arg == "--ptt-tail" && i + 1 < argc) { @@ -1112,6 +1126,8 @@ int main(int argc, char** argv) { config.com_invert_dtr = ui_state.com_invert_dtr; config.com_invert_rts = ui_state.com_invert_rts; + // CM108 PTT settings + config.cm108_gpio = ui_state.cm108_gpio; // Network settings config.port = ui_state.port; @@ -1186,7 +1202,7 @@ int main(int argc, char** argv) { ui_state.vox_tone_freq = config.vox_tone_freq; ui_state.vox_lead_ms = config.vox_lead_ms; ui_state.vox_tail_ms = config.vox_tail_ms; - + ui_state.cm108_gpio = config.cm108_gpio; @@ -1268,6 +1284,9 @@ int main(int argc, char** argv) { new_config.com_ptt_line = state.com_ptt_line; new_config.com_invert_dtr = state.com_invert_dtr; new_config.com_invert_rts = state.com_invert_rts; + + //CM108 PTT setings + new_config.cm108_gpio = state.cm108_gpio; tnc.update_config(new_config); }; diff --git a/kiss_tnc.hh b/kiss_tnc.hh index 8e02af1..a07a0bb 100644 --- a/kiss_tnc.hh +++ b/kiss_tnc.hh @@ -36,7 +36,8 @@ enum class PTTType { NONE = 0, RIGCTL = 1, VOX = 2, - COM = 3 + COM = 3, + CM108 = 4 }; struct TNCConfig { @@ -73,6 +74,9 @@ struct TNCConfig { int com_ptt_line = 1; // 0=DTR, 1=RTS, 2=BOTH bool com_invert_dtr = false; bool com_invert_rts = false; + + // CM108 PTT settings + int cm108_gpio = 3; // PTT timing int ptt_delay_ms = 50; // Delay after PTT before TX diff --git a/tnc_ui.hh b/tnc_ui.hh index 0e25256..96631ad 100644 --- a/tnc_ui.hh +++ b/tnc_ui.hh @@ -35,7 +35,7 @@ const std::vector CODE_RATE_OPTIONS = { }; const std::vector PTT_TYPE_OPTIONS = { - "NONE", "RIGCTL", "VOX", "COM" + "NONE", "RIGCTL", "VOX", "COM", "CM108" }; const std::vector PTT_LINE_OPTIONS = { @@ -68,7 +68,7 @@ struct TNCUIState { int port = 8001; // PTT - int ptt_type_index = 1; // 0=NONE, 1=RIGCTL, 2=VOX + int ptt_type_index = 1; // 0=NONE, 1=RIGCTL, 2=VOX 3=SERIAL 4=CM108 // Rigctl settings (PTT type 1) std::string rigctl_host = "localhost"; @@ -87,6 +87,8 @@ struct TNCUIState { bool com_invert_dtr = false; bool com_invert_rts = false; + // CM108 PTT settings (PTT type 4) + int cm108_gpio = 3; //GPIO pin to use for PTT, default 3 int mtu_bytes = 0; int bitrate_bps = 0; @@ -683,6 +685,7 @@ private: FIELD_COM_PORT, FIELD_COM_LINE, FIELD_COM_INVERT, + FIELD_CM108_GPIO, FIELD_NET_PORT, FIELD_PRESET, FIELD_COUNT @@ -835,6 +838,10 @@ private: edit_text_field(FIELD_COM_PORT); + } else if (current_field_ == FIELD_CM108_GPIO) { + + + edit_text_field(FIELD_CM108_GPIO); } else if (current_field_ == FIELD_AUDIO_INPUT) { @@ -1045,6 +1052,9 @@ private: } else if (field == FIELD_COM_PORT) { row = 20; max_len = 20; + } else if (field == FIELD_CM108_GPIO) { + row = 20; + max_len = 1; } else if (field == FIELD_NET_PORT) { if (state_.ptt_type_index == 2) { //2 extra rows row = 24; @@ -1095,7 +1105,16 @@ private: apply_settings(); } } catch (...) {} + } else if (field == FIELD_CM108_GPIO) { + try { + int gpio = std::stoi(buf); + if (gpio >= 1 && gpio <= 4) { + state_.cm108_gpio = gpio; + apply_settings(); + } + } catch (...) {} } + } nodelay(stdscr, TRUE); @@ -1149,7 +1168,7 @@ private: case FIELD_AUDIO_OUTPUT: break; case FIELD_PTT_TYPE: - state_.ptt_type_index = (state_.ptt_type_index + delta + 4) % 4; + state_.ptt_type_index = (state_.ptt_type_index + delta + 5) % 5; break; case FIELD_VOX_FREQ: state_.vox_tone_freq += delta * 100; @@ -2022,6 +2041,11 @@ private: if (field == FIELD_COM_INVERT) return row; row++; } + // CM108 field, only when CM108 selected as ptt + if (state_.ptt_type_index == 4) { + if (field == FIELD_CM108_GPIO) return row; + row++; + } row++; // NETWORK section row++; // header @@ -2252,6 +2276,16 @@ private: } row++; } + + if (state_.ptt_type_index == 4) { // CM108 + dy = visible_y(row); + if (dy >= 0) { + char cm108_gpio_buf[32]; + snprintf(cm108_gpio_buf, sizeof(cm108_gpio_buf), "%d", state_.cm108_gpio); + draw_field(dy, c1, c2, "GPIO Pin", FIELD_CM108_GPIO, cm108_gpio_buf, true); + } + row++; + } row++; // Network section From 13c102e7cd38eb61937d97f49820f83dcda1c470 Mon Sep 17 00:00:00 2001 From: F4JNT Date: Mon, 12 Jan 2026 13:47:37 +0100 Subject: [PATCH 2/5] Updated README.md for new libhidadpi dependency From 842f9391a4b39288152ab5336631d899b9693df3 Mon Sep 17 00:00:00 2001 From: zenith Date: Thu, 15 Jan 2026 14:09:30 -0500 Subject: [PATCH 3/5] Make CM108 support conditional on hidapi availability, cleanup --- 50-cm108-ptt.rules | 1 - Makefile | 34 ++++++++++++++++++++-- cm108_ptt.hh | 19 ++++++++----- kiss_tnc.cc | 71 ++++++++++++++++++++++++++++++++++++---------- kiss_tnc.hh | 50 ++++++++++++++------------------ tnc_ui.hh | 63 +++++++++++++++++++++++++++++----------- 6 files changed, 167 insertions(+), 71 deletions(-) delete mode 100644 50-cm108-ptt.rules diff --git a/50-cm108-ptt.rules b/50-cm108-ptt.rules deleted file mode 100644 index b7fd429..0000000 --- a/50-cm108-ptt.rules +++ /dev/null @@ -1 +0,0 @@ -SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0660", TAG+="uaccess" diff --git a/Makefile b/Makefile index 5bc025e..b10328f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ CC = gcc CXXFLAGS = -std=c++17 -O3 -march=native -Wall -Wextra -LDFLAGS = -lpthread -ltinfo -lncurses -ldl -lm -lhidapi-hidraw +LDFLAGS = -lpthread -ltinfo -lncurses -ldl -lm # dependencies AICODIX_DSP ?= ../dsp @@ -19,6 +19,20 @@ OBJS = miniaudio.o # defualt to build with UI, headless operations through --headless UI_FLAGS = -DWITH_UI +# Optional CM108 PTT support requires libhidapi-dev +HIDAPI_CFLAGS := $(shell pkg-config --cflags hidapi-hidraw 2>/dev/null || pkg-config --cflags hidapi-libusb 2>/dev/null || pkg-config --cflags hidapi 2>/dev/null) +HIDAPI_LIBS := $(shell pkg-config --libs hidapi-hidraw 2>/dev/null || pkg-config --libs hidapi-libusb 2>/dev/null || pkg-config --libs hidapi 2>/dev/null) + +ifneq ($(HIDAPI_LIBS),) + $(info CM108 PTT support: enabled (found hidapi)) + CM108_FLAGS = -DWITH_CM108 + CXXFLAGS += $(HIDAPI_CFLAGS) + LDFLAGS += $(HIDAPI_LIBS) +else + $(info CM108 PTT support: disabled (install libhidapi-dev to enable)) + CM108_FLAGS = +endif + .PHONY: all clean install debug help all: $(TARGET) @@ -27,14 +41,25 @@ miniaudio.o: miniaudio.c miniaudio.h $(CC) -c -O2 -o $@ miniaudio.c $(TARGET): $(SRCS) $(HDRS) $(OBJS) - $(CXX) $(CXXFLAGS) $(UI_FLAGS) $(INCLUDES) -o $@ $(SRCS) $(OBJS) $(LDFLAGS) + $(CXX) $(CXXFLAGS) $(UI_FLAGS) $(CM108_FLAGS) $(INCLUDES) -o $@ $(SRCS) $(OBJS) $(LDFLAGS) +ifneq ($(HIDAPI_LIBS),) + @echo "" + @echo "CM108 PTT support enabled. To allow non-root access, install udev rules:" + @echo " sudo cp misc/50-cm108-ptt.rules /etc/udev/rules.d/" + @echo " sudo udevadm control --reload-rules" +endif clean: rm -f $(TARGET) $(OBJS) install: $(TARGET) install -m 755 $(TARGET) /usr/local/bin/ - $(shell cp 50-cm108-ptt.rules /etc/udev/rules.d/) +ifneq ($(HIDAPI_LIBS),) + @if [ -f misc/50-cm108-ptt.rules ]; then \ + cp misc/50-cm108-ptt.rules /etc/udev/rules.d/ 2>/dev/null || \ + echo "Note: Run 'sudo cp misc/50-cm108-ptt.rules /etc/udev/rules.d/' for CM108 udev rules"; \ + fi +endif # Debug build debug: CXXFLAGS = -std=c++17 -g -O0 -Wall -Wextra -DDEBUG @@ -55,6 +80,9 @@ help: @echo " AICODIX_CODE - Path to aicodix/code (default: ../code)" @echo " MODEM_SRC - Path to modem source (default: ../modem)" @echo "" + @echo "Optional features:" + @echo " CM108 PTT - Requires libhidapi-dev (auto-detected)" + @echo "" @echo "Example:" @echo " make AICODIX_DSP=~/aicodix/dsp AICODIX_CODE=~/aicodix/code" @echo "" diff --git a/cm108_ptt.hh b/cm108_ptt.hh index ea37015..9c65a0b 100644 --- a/cm108_ptt.hh +++ b/cm108_ptt.hh @@ -6,8 +6,9 @@ #include #include #include +#include -#include "hidapi.h" +#include class CM108PTT { public: @@ -30,8 +31,11 @@ public: } void close(){ - hid_close(handle_); - res_ = hid_exit(); + if (handle_) { + hid_close(handle_); + handle_ = nullptr; + } + hid_exit(); } void set_ptt(bool on){ @@ -48,13 +52,14 @@ public: buf[2] = 0x00; buf[3] = 0x00; } + buf[4] = 0x00; res_ = hid_write(handle_, buf, 5); } private: - int res_; - int gpio_; //#PTT control pin GPIOX , where X should be 1,2,3,4 - GPIO3 on most devices + int res_ = 0; + int gpio_ = 3; // PTT control pin GPIOX, where X should be 1,2,3,4 - GPIO3 on most devices const int cm108_on_[4] = {0x01, 0x02, 0x04, 0x08}; - hid_device *handle_; -}; \ No newline at end of file + hid_device *handle_ = nullptr; +}; diff --git a/kiss_tnc.cc b/kiss_tnc.cc index fb03056..a8b6ea9 100644 --- a/kiss_tnc.cc +++ b/kiss_tnc.cc @@ -30,7 +30,9 @@ #include "miniaudio_audio.hh" #include "rigctl_ptt.hh" #include "serial_ptt.hh" +#ifdef WITH_CM108 #include "cm108_ptt.hh" +#endif #include "modem.hh" #ifdef WITH_UI @@ -185,9 +187,11 @@ public: config_.com_invert_rts)) { std::cerr << "Could not open COM port: " << serial_ptt_->last_error() << std::endl; } +#ifdef WITH_CM108 } else if (config_.ptt_type == PTTType::CM108) { cm108_ptt_ = std::make_unique(); - cm108_ptt_->open(config_.cm108_gpio); + cm108_ptt_->open(config_.cm108_gpio); +#endif } else { dummy_ptt_ = std::make_unique(); dummy_ptt_->connect(); @@ -250,9 +254,11 @@ public: std::cerr << "PTT: COM " << config_.com_port << " (" << PTT_LINE_OPTIONS[config_.com_ptt_line] << ")" << std::endl; break; +#ifdef WITH_CM108 case PTTType::CM108: - std::cerr << "PTT: CM108 (GPIO" << config_.cm108_gpio << ")"; + std::cerr << "PTT: CM108 (GPIO" << config_.cm108_gpio << ")" << std::endl; break; +#endif } // Start threads @@ -592,14 +598,18 @@ private: if (g_ui_state) g_ui_state->ptt_on = false; #endif } else { - // RIGCTL, COM, CM108 or NONE mode + // RIGCTL, COM, or NONE mode total_tx_duration += (config_.tx_delay_ms + config_.ptt_tail_ms) / 1000.0f; ui_log("TX: " + std::to_string(samples.size()) + " samples, " + std::to_string(duration) + " seconds"); - // PTT on (for RIGCTL, COM or CM108 mode) - if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM || config_.ptt_type == PTTType::CM108) { + // PTT on (for RIGCTL or COM mode) + if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM +#ifdef WITH_CM108 + || config_.ptt_type == PTTType::CM108 +#endif + ) { set_ptt(true); std::this_thread::sleep_for(std::chrono::milliseconds(config_.ptt_delay_ms)); } @@ -619,7 +629,11 @@ private: audio_->drain_playback(); // PTT off - if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM || config_.ptt_type == PTTType::CM108) { + if (config_.ptt_type == PTTType::RIGCTL || config_.ptt_type == PTTType::COM +#ifdef WITH_CM108 + || config_.ptt_type == PTTType::CM108 +#endif + ) { std::this_thread::sleep_for(std::chrono::milliseconds(config_.ptt_tail_ms)); set_ptt(false); } @@ -753,8 +767,10 @@ private: } else { serial_ptt_->ptt_off(); } +#ifdef WITH_CM108 } else if (cm108_ptt_) { cm108_ptt_->set_ptt(on); +#endif } else if (dummy_ptt_) { dummy_ptt_->set_ptt(on); } @@ -808,7 +824,9 @@ private: std::unique_ptr audio_; std::unique_ptr rigctl_; std::unique_ptr serial_ptt_; +#ifdef WITH_CM108 std::unique_ptr cm108_ptt_; +#endif std::unique_ptr dummy_ptt_; int server_fd_ = -1; @@ -932,12 +950,20 @@ void print_help(const char* prog) { << " --short Use short frames\n" << " --normal Use normal frames (default)\n" << "\nPTT options:\n" - << " --ptt TYPE PTT type: none, rigctl, vox (default: rigctl)\n" + << " --ptt TYPE PTT type: none, rigctl, vox, com" +#ifdef WITH_CM108 + << ", cm108" +#endif + << " (default: rigctl)\n" << " --rigctl HOST:PORT Rigctl address (default: localhost:4532)\n" + << " --com-port PORT Serial port for COM PTT (default: /dev/ttyUSB0)\n" + << " --com-line LINE COM PTT line: dtr, rts, both (default: rts)\n" << " --vox-freq HZ VOX tone frequency (default: 1200)\n" << " --vox-lead MS VOX lead time in ms (default: 150)\n" << " --vox-tail MS VOX tail time in ms (default: 100)\n" - << " --cm108-gpio CM108 GPIO pin for PTT (default: 3)\n" +#ifdef WITH_CM108 + << " --cm108-gpio N CM108 GPIO pin for PTT (default: 3)\n" +#endif << " --ptt-delay MS PTT delay before TX (default: 50)\n" << " --ptt-tail MS PTT tail after TX (default: 50)\n" << "\nCSMA options:\n" @@ -1017,14 +1043,32 @@ int main(int argc, char** argv) { } else { config.rigctl_host = hostport; } + } else if (arg == "--com-port" && i + 1 < argc) { + config.com_port = argv[++i]; + } else if (arg == "--com-line" && i + 1 < argc) { + std::string line = argv[++i]; + if (line == "dtr") config.com_ptt_line = 0; + else if (line == "rts") config.com_ptt_line = 1; + else if (line == "both") config.com_ptt_line = 2; + else { + std::cerr << "Unknown COM PTT line: " << line << " (use dtr, rts, or both)\n"; + return 1; + } } else if (arg == "--ptt" && i + 1 < argc) { std::string ptt_type = argv[++i]; if (ptt_type == "none") config.ptt_type = PTTType::NONE; else if (ptt_type == "rigctl") config.ptt_type = PTTType::RIGCTL; else if (ptt_type == "vox") config.ptt_type = PTTType::VOX; + else if (ptt_type == "com") config.ptt_type = PTTType::COM; +#ifdef WITH_CM108 else if (ptt_type == "cm108") config.ptt_type = PTTType::CM108; +#endif else { - std::cerr << "Unknown PTT type: " << ptt_type << " (use none, rigctl, cm108 or vox)\n"; + std::cerr << "Unknown PTT type: " << ptt_type << " (use none, rigctl, vox, com" +#ifdef WITH_CM108 + << ", cm108" +#endif + << ")\n"; return 1; } } else if (arg == "--vox-freq" && i + 1 < argc) { @@ -1033,8 +1077,10 @@ int main(int argc, char** argv) { config.vox_lead_ms = std::atoi(argv[++i]); } else if (arg == "--vox-tail" && i + 1 < argc) { config.vox_tail_ms = std::atoi(argv[++i]); +#ifdef WITH_CM108 } else if (arg == "--cm108-gpio" && i + 1 < argc) { config.cm108_gpio = std::atoi(argv[++i]); +#endif } else if (arg == "--ptt-delay" && i + 1 < argc) { config.ptt_delay_ms = std::atoi(argv[++i]); } else if (arg == "--ptt-tail" && i + 1 < argc) { @@ -1126,8 +1172,6 @@ int main(int argc, char** argv) { config.com_invert_dtr = ui_state.com_invert_dtr; config.com_invert_rts = ui_state.com_invert_rts; - // CM108 PTT settings - config.cm108_gpio = ui_state.cm108_gpio; // Network settings config.port = ui_state.port; @@ -1202,7 +1246,7 @@ int main(int argc, char** argv) { ui_state.vox_tone_freq = config.vox_tone_freq; ui_state.vox_lead_ms = config.vox_lead_ms; ui_state.vox_tail_ms = config.vox_tail_ms; - ui_state.cm108_gpio = config.cm108_gpio; + @@ -1284,9 +1328,6 @@ int main(int argc, char** argv) { new_config.com_ptt_line = state.com_ptt_line; new_config.com_invert_dtr = state.com_invert_dtr; new_config.com_invert_rts = state.com_invert_rts; - - //CM108 PTT setings - new_config.cm108_gpio = state.cm108_gpio; tnc.update_config(new_config); }; diff --git a/kiss_tnc.hh b/kiss_tnc.hh index a07a0bb..945646a 100644 --- a/kiss_tnc.hh +++ b/kiss_tnc.hh @@ -37,7 +37,9 @@ enum class PTTType { RIGCTL = 1, VOX = 2, COM = 3, +#ifdef WITH_CM108 CM108 = 4 +#endif }; struct TNCConfig { @@ -75,8 +77,10 @@ struct TNCConfig { bool com_invert_dtr = false; bool com_invert_rts = false; +#ifdef WITH_CM108 // CM108 PTT settings int cm108_gpio = 3; +#endif // PTT timing int ptt_delay_ms = 50; // Delay after PTT before TX @@ -254,44 +258,32 @@ inline std::string packet_visualize(const uint8_t* data, size_t len, bool is_tx, uint8_t flags = data[4]; oss << " │ FRAG HDR [5 bytes] Magic: 0xF3 │\n"; - oss << " │ Packet ID: " << std::setw(5) << pkt_id; - oss << " Seq: " << std::setw(3) << (int)seq; - oss << " Flags: "; - - std::string flag_str; - if (flags & 0x02) flag_str += "FIRST "; - if (flags & 0x01) flag_str += "MORE"; - if (flag_str.empty()) flag_str = "LAST"; - oss << std::left << std::setw(12) << flag_str << std::right << " │\n"; - + oss << " │ Packet ID: 0x" << std::hex << std::setfill('0') << std::setw(4) << pkt_id << std::dec; + oss << " Seq: " << std::setw(3) << (int)seq; + oss << " Flags: "; + if (flags & 0x02) oss << "FIRST "; + if (flags & 0x01) oss << "MORE"; + if (!(flags & 0x03)) oss << "LAST"; + oss << std::string(20, ' ') << "│\n"; offset = 5; - oss << " ├─────────────────────────────────────────────────────────────┤\n"; } - size_t payload_len = len - offset; - oss << " │ PAYLOAD [" << payload_len << " bytes]"; - oss << std::string(49 - std::to_string(payload_len).length(), ' ') << "│\n"; - - size_t preview_len = std::min(payload_len, (size_t)32); - if (preview_len > 0) { + if (offset < len) { + oss << " ├─────────────────────────────────────────────────────────────┤\n"; + size_t payload_len = len - offset; + oss << " │ PAYLOAD [" << payload_len << " bytes]"; + oss << std::string(49 - std::to_string(payload_len).length(), ' ') << "│\n"; + + size_t preview_len = std::min(payload_len, (size_t)24); oss << " │ "; for (size_t i = 0; i < preview_len; i++) { oss << std::hex << std::setfill('0') << std::setw(2) << (int)data[offset + i]; if (i < preview_len - 1) oss << " "; } - if (payload_len > 32) oss << "..."; - size_t used = preview_len * 3 - 1 + (payload_len > 32 ? 3 : 0); + if (payload_len > 24) oss << " ..."; + oss << std::dec; + size_t used = preview_len * 3 - 1 + (payload_len > 24 ? 4 : 0); if (used < 57) oss << std::string(57 - used, ' '); - oss << std::dec << " │\n"; - - oss << " │ "; - for (size_t i = 0; i < preview_len; i++) { - char c = data[offset + i]; - oss << (c >= 32 && c < 127 ? c : '.'); - } - if (payload_len > 32) oss << "..."; - size_t ascii_used = preview_len + (payload_len > 32 ? 3 : 0); - if (ascii_used < 57) oss << std::string(57 - ascii_used, ' '); oss << " │\n"; } diff --git a/tnc_ui.hh b/tnc_ui.hh index 96631ad..3ec96e4 100644 --- a/tnc_ui.hh +++ b/tnc_ui.hh @@ -35,7 +35,10 @@ const std::vector CODE_RATE_OPTIONS = { }; const std::vector PTT_TYPE_OPTIONS = { - "NONE", "RIGCTL", "VOX", "COM", "CM108" + "NONE", "RIGCTL", "VOX", "COM" +#ifdef WITH_CM108 + , "CM108" +#endif }; const std::vector PTT_LINE_OPTIONS = { @@ -68,7 +71,7 @@ struct TNCUIState { int port = 8001; // PTT - int ptt_type_index = 1; // 0=NONE, 1=RIGCTL, 2=VOX 3=SERIAL 4=CM108 + int ptt_type_index = 1; // 0=NONE, 1=RIGCTL, 2=VOX // Rigctl settings (PTT type 1) std::string rigctl_host = "localhost"; @@ -87,8 +90,10 @@ struct TNCUIState { bool com_invert_dtr = false; bool com_invert_rts = false; +#ifdef WITH_CM108 // CM108 PTT settings (PTT type 4) - int cm108_gpio = 3; //GPIO pin to use for PTT, default 3 + int cm108_gpio = 3; // GPIO pin to use for PTT, default 3 +#endif int mtu_bytes = 0; int bitrate_bps = 0; @@ -370,6 +375,10 @@ struct TNCUIState { fprintf(f, "com_ptt_line=%d\n", com_ptt_line); fprintf(f, "com_invert_dtr=%d\n", com_invert_dtr ? 1 : 0); fprintf(f, "com_invert_rts=%d\n", com_invert_rts ? 1 : 0); +#ifdef WITH_CM108 + fprintf(f, "# CM108 PTT\n"); + fprintf(f, "cm108_gpio=%d\n", cm108_gpio); +#endif fprintf(f, "# Network\n"); fprintf(f, "port=%d\n", port); fprintf(f, "# Utils\n"); @@ -416,6 +425,9 @@ struct TNCUIState { else if (strcmp(key, "com_ptt_line") == 0) com_ptt_line = atoi(value); else if (strcmp(key, "com_invert_dtr") == 0) com_invert_dtr = atoi(value) != 0; else if (strcmp(key, "com_invert_rts") == 0) com_invert_rts = atoi(value) != 0; +#ifdef WITH_CM108 + else if (strcmp(key, "cm108_gpio") == 0) cm108_gpio = atoi(value); +#endif else if (strcmp(key, "port") == 0) port = atoi(value); else if (strcmp(key, "random_data_size") == 0) random_data_size = atoi(value); } @@ -685,7 +697,9 @@ private: FIELD_COM_PORT, FIELD_COM_LINE, FIELD_COM_INVERT, +#ifdef WITH_CM108 FIELD_CM108_GPIO, +#endif FIELD_NET_PORT, FIELD_PRESET, FIELD_COUNT @@ -838,10 +852,10 @@ private: edit_text_field(FIELD_COM_PORT); +#ifdef WITH_CM108 } else if (current_field_ == FIELD_CM108_GPIO) { - - edit_text_field(FIELD_CM108_GPIO); +#endif } else if (current_field_ == FIELD_AUDIO_INPUT) { @@ -1052,9 +1066,11 @@ private: } else if (field == FIELD_COM_PORT) { row = 20; max_len = 20; +#ifdef WITH_CM108 } else if (field == FIELD_CM108_GPIO) { row = 20; max_len = 1; +#endif } else if (field == FIELD_NET_PORT) { if (state_.ptt_type_index == 2) { //2 extra rows row = 24; @@ -1096,6 +1112,16 @@ private: state_.com_port = buf; state_.add_log("(!) COM port changed, restart required"); apply_settings(); +#ifdef WITH_CM108 + } else if (field == FIELD_CM108_GPIO) { + try { + int gpio = std::stoi(buf); + if (gpio >= 1 && gpio <= 4) { + state_.cm108_gpio = gpio; + apply_settings(); + } + } catch (...) {} +#endif } else if (field == FIELD_NET_PORT) { try { int port = std::stoi(buf); @@ -1105,16 +1131,7 @@ private: apply_settings(); } } catch (...) {} - } else if (field == FIELD_CM108_GPIO) { - try { - int gpio = std::stoi(buf); - if (gpio >= 1 && gpio <= 4) { - state_.cm108_gpio = gpio; - apply_settings(); - } - } catch (...) {} } - } nodelay(stdscr, TRUE); @@ -1133,6 +1150,13 @@ private: return true; } } +#ifdef WITH_CM108 + if (state_.ptt_type_index != 4) { // not CM108 + if (field == FIELD_CM108_GPIO) { + return true; + } + } +#endif return false; } @@ -1168,7 +1192,11 @@ private: case FIELD_AUDIO_OUTPUT: break; case FIELD_PTT_TYPE: +#ifdef WITH_CM108 state_.ptt_type_index = (state_.ptt_type_index + delta + 5) % 5; +#else + state_.ptt_type_index = (state_.ptt_type_index + delta + 4) % 4; +#endif break; case FIELD_VOX_FREQ: state_.vox_tone_freq += delta * 100; @@ -2041,11 +2069,13 @@ private: if (field == FIELD_COM_INVERT) return row; row++; } - // CM108 field, only when CM108 selected as ptt +#ifdef WITH_CM108 + // CM108 field, only when CM108 selected as PTT if (state_.ptt_type_index == 4) { if (field == FIELD_CM108_GPIO) return row; row++; } +#endif row++; // NETWORK section row++; // header @@ -2276,7 +2306,7 @@ private: } row++; } - +#ifdef WITH_CM108 if (state_.ptt_type_index == 4) { // CM108 dy = visible_y(row); if (dy >= 0) { @@ -2286,6 +2316,7 @@ private: } row++; } +#endif row++; // Network section From 68b692e0f33301fc3e54d27ac273f5c8e2743831 Mon Sep 17 00:00:00 2001 From: zenith Date: Thu, 15 Jan 2026 14:11:56 -0500 Subject: [PATCH 4/5] Make CM108 support conditional on hidapi availability, cleanup --- misc/50-cm108-ptt.rules | 1 + 1 file changed, 1 insertion(+) create mode 100644 misc/50-cm108-ptt.rules diff --git a/misc/50-cm108-ptt.rules b/misc/50-cm108-ptt.rules new file mode 100644 index 0000000..b7fd429 --- /dev/null +++ b/misc/50-cm108-ptt.rules @@ -0,0 +1 @@ +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", MODE="0660", TAG+="uaccess" From 2e28783c2e246442b1df787845405d3f56b3335c Mon Sep 17 00:00:00 2001 From: zenith Date: Thu, 15 Jan 2026 14:34:10 -0500 Subject: [PATCH 5/5] Cleanup, update README --- README.md | 17 ++ hidapi.h | 691 ------------------------------------------------------ 2 files changed, 17 insertions(+), 691 deletions(-) delete mode 100644 hidapi.h diff --git a/README.md b/README.md index 2a75c23..bcd786a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,17 @@ MODEM73 is a [KISS](https://en.wikipedia.org/wiki/KISS_(amateur_radio_protocol)) sudo apt install git build-essential libncurses-dev g++ ``` +#### Optional Addons + + +##### CM108 USB PTT Support + +CM108-based USB audio interfaces have GPIO pins that can be used for PTT control. To enable CM108 support, install libhidapi-dev before building. The Makefile will auto-detect it and enable the feature. +``` +# Debian/Ubuntu/Pi - install before building +sudo apt install libhidapi-dev +``` +---- 2. Clone aiocdix DSP libraries and build. @@ -64,6 +75,7 @@ There are currently four PTT options: - Rigctl - VOX - Serial +- CM108 ``` @@ -97,6 +109,11 @@ while running `rigctld` ./modem73 --ptt com --com-port /dev/ttyUSB0 --com-line rts ``` +``` +# CM108 USB audio interface PTT (GPIO3 is default) +./modem73 --ptt cm108 --cm108-gpio 3 +``` + ## Updating diff --git a/hidapi.h b/hidapi.h deleted file mode 100644 index cbc3107..0000000 --- a/hidapi.h +++ /dev/null @@ -1,691 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - libusb/hidapi Team - - Copyright 2023, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General 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: - https://github.com/libusb/hidapi . -********************************************************/ - -/** @file - * @defgroup API hidapi API - */ - -#ifndef HIDAPI_H__ -#define HIDAPI_H__ - -#include - -/* #480: this is to be refactored properly for v1.0 */ -#ifdef _WIN32 - #ifndef HID_API_NO_EXPORT_DEFINE - #define HID_API_EXPORT __declspec(dllexport) - #endif -#endif -#ifndef HID_API_EXPORT - #define HID_API_EXPORT /**< API export macro */ -#endif -/* To be removed in v1.0 */ -#define HID_API_CALL /**< API call macro */ - -#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ - -/** @brief Static/compile-time major version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_MAJOR 0 -/** @brief Static/compile-time minor version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_MINOR 16 -/** @brief Static/compile-time patch version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_PATCH 0 - -/* Helper macros */ -#define HID_API_AS_STR_IMPL(x) #x -#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) -#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) - -/** @brief Coverts a version as Major/Minor/Patch into a number: - <8 bit major><16 bit minor><8 bit patch>. - - This macro was added in version 0.12.0. - - Convenient function to be used for compile-time checks, like: - @code{.c} - #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) - @endcode - - @ingroup API -*/ -#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) - -/** @brief Static/compile-time version of the library. - - This macro was added in version 0.12.0. - - @see @ref HID_API_MAKE_VERSION. - - @ingroup API -*/ -#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) - -/** @brief Static/compile-time string version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) - -/** @brief Maximum expected HID Report descriptor size in bytes. - - Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) - - @ingroup API -*/ -#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 - -#ifdef __cplusplus -extern "C" { -#endif - /** A structure to hold the version numbers. */ - struct hid_api_version { - int major; /**< major version number */ - int minor; /**< minor version number */ - int patch; /**< patch version number */ - }; - - struct hid_device_; - typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ - - /** @brief HID underlying bus types. - - @ingroup API - */ - typedef enum { - /** Unknown bus type */ - HID_API_BUS_UNKNOWN = 0x00, - - /** USB bus - Specifications: - https://usb.org/hid */ - HID_API_BUS_USB = 0x01, - - /** Bluetooth or Bluetooth LE bus - Specifications: - https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ - https://www.bluetooth.com/specifications/specs/hid-service-1-0/ - https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ - HID_API_BUS_BLUETOOTH = 0x02, - - /** I2C bus - Specifications: - https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ - HID_API_BUS_I2C = 0x03, - - /** SPI bus - Specifications: - https://www.microsoft.com/download/details.aspx?id=103325 */ - HID_API_BUS_SPI = 0x04, - - /** Virtual device - E.g.: https://elixir.bootlin.com/linux/v4.0/source/include/uapi/linux/input.h#L955 - - Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) - */ - HID_API_BUS_VIRTUAL = 0x05, - } hid_bus_type; - - /** hidapi info structure */ - struct hid_device_info { - /** Platform-specific device path */ - char *path; - /** Device Vendor ID */ - unsigned short vendor_id; - /** Device Product ID */ - unsigned short product_id; - /** Serial Number */ - wchar_t *serial_number; - /** Device Release Number in binary-coded decimal, - also known as Device Version Number */ - unsigned short release_number; - /** Manufacturer String */ - wchar_t *manufacturer_string; - /** Product string */ - wchar_t *product_string; - /** Usage Page for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage_page; - /** Usage for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage; - /** The USB interface which this logical device - represents. - - Valid only if the device is a USB HID device. - Set to -1 in all other cases. - */ - int interface_number; - - /** Pointer to the next device */ - struct hid_device_info *next; - - /** Underlying bus type - Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) - */ - hid_bus_type bus_type; - }; - - - /** @brief Initialize the HIDAPI library. - - This function initializes the HIDAPI library. Calling it is not - strictly necessary, as it will be called automatically by - hid_enumerate() and any of the hid_open_*() functions if it is - needed. This function should be called at the beginning of - execution however, if there is a chance of HIDAPI handles - being opened by different threads simultaneously. - - @ingroup API - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(NULL) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_init(void); - - /** @brief Finalize the HIDAPI library. - - This function frees all of the static data associated with - HIDAPI. It should be called at the end of execution to avoid - memory leaks. - - @ingroup API - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_exit(void); - - /** @brief Enumerate the HID Devices. - - This function returns a linked list of all the HID devices - attached to the system which match vendor_id and product_id. - If @p vendor_id is set to 0 then any vendor matches. - If @p product_id is set to 0 then any product matches. - If @p vendor_id and @p product_id are both set to 0, then - all HID devices will be returned. - - @ingroup API - @param vendor_id The Vendor ID (VID) of the types of device - to open. - @param product_id The Product ID (PID) of the types of - device to open. - - @returns - This function returns a pointer to a linked list of type - struct #hid_device_info, containing information about the HID devices - attached to the system, - or NULL in the case of failure or if no HID devices present in the system. - Call hid_error(NULL) to get the failure reason. - - @note The returned value by this function must to be freed by calling hid_free_enumeration(), - when not needed anymore. - */ - struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); - - /** @brief Free an enumeration Linked List - - This function frees a linked list created by hid_enumerate(). - - @ingroup API - @param devs Pointer to a list of struct_device returned from - hid_enumerate(). - */ - void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); - - /** @brief Open a HID device using a Vendor ID (VID), Product ID - (PID) and optionally a serial number. - - If @p serial_number is NULL, the first device with the - specified VID and PID is opened. - - @ingroup API - @param vendor_id The Vendor ID (VID) of the device to open. - @param product_id The Product ID (PID) of the device to open. - @param serial_number The Serial Number of the device to open - (Optionally NULL). - - @returns - This function returns a pointer to a #hid_device object on - success or NULL on failure. - Call hid_error(NULL) to get the failure reason. - - @note The returned object must be freed by calling hid_close(), - when not needed anymore. - */ - HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); - - /** @brief Open a HID device by its path name. - - The path name be determined by calling hid_enumerate(), or a - platform-specific path name can be used (eg: /dev/hidraw0 on - Linux). - - @ingroup API - @param path The path name of the device to open - - @returns - This function returns a pointer to a #hid_device object on - success or NULL on failure. - Call hid_error(NULL) to get the failure reason. - - @note The returned object must be freed by calling hid_close(), - when not needed anymore. - */ - HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); - - /** @brief Write an Output report to a HID device. - - The first byte of @p data[] must contain the Report ID. For - devices which only support a single report, this must be set - to 0x0. The remaining bytes contain the report data. Since - the Report ID is mandatory, calls to hid_write() will always - contain one more byte than the report contains. For example, - if a hid report is 16 bytes long, 17 bytes must be passed to - hid_write(), the Report ID (or 0x0, for devices with a - single report), followed by the report data (16 bytes). In - this example, the length passed in would be 17. - - hid_write() will send the data on the first interrupt OUT - endpoint, if one exists. If it does not the behaviour is as - @ref hid_send_output_report - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data The data to send, including the report number as - the first byte. - @param length The length in bytes of the data to send. - - @returns - This function returns the actual number of bytes written and - -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); - - /** @brief Read an Input report from a HID device with timeout. - - Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will - contain the Report number if the device uses numbered reports. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into. - @param length The number of bytes to read. For devices with - multiple reports, make sure to read an extra byte for - the report number. - @param milliseconds timeout in milliseconds or -1 for blocking wait. - - @returns - This function returns the actual number of bytes read and - -1 on error. - Call hid_read_error(dev) to get the failure reason. - If no packet was available to be read within - the timeout period, this function returns 0. - - @note This function doesn't change the buffer returned by the hid_error(dev). - */ - int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); - - /** @brief Read an Input report from a HID device. - - Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will - contain the Report number if the device uses numbered reports. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into. - @param length The number of bytes to read. For devices with - multiple reports, make sure to read an extra byte for - the report number. - - @returns - This function returns the actual number of bytes read and - -1 on error. - Call hid_read_error(dev) to get the failure reason. - If no packet was available to be read and - the handle is in non-blocking mode, this function returns 0. - - @note This function doesn't change the buffer returned by the hid_error(dev). - */ - int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Get a string describing the last error which occurred during hid_read/hid_read_timeout. - - Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) - - This function is intended for logging/debugging purposes. - - This function guarantees to never return NULL for a valid @ref dev. - If there was no error in the last call to hid_read/hid_read_error - - the returned string clearly indicates that. - - Strings returned from hid_read_error() must not be freed by the user, - i.e. owned by HIDAPI library. - Device-specific error string may remain allocated at most until hid_close() is called. - - @ingroup API - @param dev A device handle. Shall never be NULL. - - @returns - A string describing the hid_read/hid_read_timeout error (if any). - */ - HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev); - - /** @brief Set the device handle to be non-blocking. - - In non-blocking mode calls to hid_read() will return - immediately with a value of 0 if there is no data to be - read. In blocking mode, hid_read() will wait (block) until - there is data to read before returning. - - Nonblocking can be turned on and off at any time. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param nonblock enable or not the nonblocking reads - - 1 to enable nonblocking - - 0 to disable nonblocking. - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); - - /** @brief Send a Feature report to the device. - - Feature reports are sent over the Control endpoint as a - Set_Report transfer. The first byte of @p data[] must - contain the Report ID. For devices which only support a - single report, this must be set to 0x0. The remaining bytes - contain the report data. Since the Report ID is mandatory, - calls to hid_send_feature_report() will always contain one - more byte than the report contains. For example, if a hid - report is 16 bytes long, 17 bytes must be passed to - hid_send_feature_report(): the Report ID (or 0x0, for - devices which do not use numbered reports), followed by the - report data (16 bytes). In this example, the length passed - in would be 17. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data The data to send, including the report number as - the first byte. - @param length The length in bytes of the data to send, including - the report number. - - @returns - This function returns the actual number of bytes written and - -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); - - /** @brief Get a feature report from a HID device. - - Set the first byte of @p data[] to the Report ID of the - report to be read. Make sure to allow space for this - extra byte in @p data[]. Upon return, the first byte will - still contain the Report ID, and the report data will - start in data[1]. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into, including - the Report ID. Set the first byte of @p data[] to the - Report ID of the report to be read, or set it to zero - if your device does not use numbered reports. - @param length The number of bytes to read, including an - extra byte for the report ID. The buffer can be longer - than the actual report. - - @returns - This function returns the number of bytes read plus - one for the report ID (which is still in the first - byte), or -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Send a Output report to the device. - - Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) - - Output reports are sent over the Control endpoint as a - Set_Report transfer. The first byte of @p data[] must - contain the Report ID. For devices which only support a - single report, this must be set to 0x0. The remaining bytes - contain the report data. Since the Report ID is mandatory, - calls to hid_send_output_report() will always contain one - more byte than the report contains. For example, if a hid - report is 16 bytes long, 17 bytes must be passed to - hid_send_output_report(): the Report ID (or 0x0, for - devices which do not use numbered reports), followed by the - report data (16 bytes). In this example, the length passed - in would be 17. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data The data to send, including the report number as - the first byte. - @param length The length in bytes of the data to send, including - the report number. - - @returns - This function returns the actual number of bytes written and - -1 on error. - - @see @ref hid_write - */ - int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); - - /** @brief Get a input report from a HID device. - - Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) - - Set the first byte of @p data[] to the Report ID of the - report to be read. Make sure to allow space for this - extra byte in @p data[]. Upon return, the first byte will - still contain the Report ID, and the report data will - start in data[1]. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into, including - the Report ID. Set the first byte of @p data[] to the - Report ID of the report to be read, or set it to zero - if your device does not use numbered reports. - @param length The number of bytes to read, including an - extra byte for the report ID. The buffer can be longer - than the actual report. - - @returns - This function returns the number of bytes read plus - one for the report ID (which is still in the first - byte), or -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Close a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - */ - void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); - - /** @brief Get The Manufacturer String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Product String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Serial Number String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The struct #hid_device_info from a HID device. - - Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) - - @ingroup API - @param dev A device handle returned from hid_open(). - - @returns - This function returns a pointer to the struct #hid_device_info - for this hid_device, or NULL in the case of failure. - Call hid_error(dev) to get the failure reason. - This struct is valid until the device is closed with hid_close(). - - @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. - */ - struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); - - /** @brief Get a string from a HID device, based on its string index. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string_index The index of the string to get. - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - Call hid_error(dev) to get the failure reason. - */ - int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); - - /** @brief Get a report descriptor from a HID device. - - Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) - - User has to provide a preallocated buffer where descriptor will be copied to. - The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param buf The buffer to copy descriptor into. - @param buf_size The size of the buffer in bytes. - - @returns - This function returns non-negative number of bytes actually copied, or -1 on error. - */ - int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); - - /** @brief Get a string describing the last error which occurred. - - This function is intended for logging/debugging purposes. - - This function guarantees to never return NULL. - If there was no error in the last function call - - the returned string clearly indicates that. - - Any HIDAPI function that can explicitly indicate an execution failure - (e.g. by an error code, or by returning NULL) - may set the error string, - to be returned by this function. - - Strings returned from hid_error() must not be freed by the user, - i.e. owned by HIDAPI library. - Device-specific error string may remain allocated at most until hid_close() is called. - Global error string may remain allocated at most until hid_exit() is called. - - @ingroup API - @param dev A device handle returned from hid_open(), - or NULL to get the last non-device-specific error - (e.g. for errors in hid_open() or hid_enumerate()). - - @returns - A string describing the last error (if any). - */ - HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); - - /** @brief Get a runtime version of the library. - - This function is thread-safe. - - @ingroup API - - @returns - Pointer to statically allocated struct, that contains version. - */ - HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); - - - /** @brief Get a runtime version string of the library. - - This function is thread-safe. - - @ingroup API - - @returns - Pointer to statically allocated string, that contains version string. - */ - HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); - -#ifdef __cplusplus -} -#endif - -#endif