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