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