diff --git a/main.cpp b/main.cpp index 6aeb63a..30e3e87 100644 --- a/main.cpp +++ b/main.cpp @@ -381,7 +381,7 @@ void print_help(const char* exe_name) { << " [internal_ref_hz:2000000]\n" << " [di1:zero|trace|ignore]\n" << " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n" - << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/dev/ttyADC_data]\n" + << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data]\n" << " [recv_block:32768] [stats_period_ms:1000] [live_update_period_ms:1000] [svg_history_packets:50] [start_wait_ms:10000]\n" << " [buffer_words:8388608] [step_words:32768]\n" << " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n" @@ -412,7 +412,7 @@ void print_help(const char* exe_name) { << " buffer_words:8388608 -> input stream buffer size in 32-bit words\n" << " step_words:32768 -> input stream transfer step in 32-bit words\n" << " live_html/live_json -> live graph files updated as packets arrive\n" - << " tty:/dev/ttyADC_data -> write binary step frames to a Linux/POSIX tty path\n" + << " tty:/tmp/ttyADC_data -> write binary step frames to a Linux/POSIX tty or PTY link path\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << "\n" << "Differential physical channel mapping:\n" @@ -747,6 +747,19 @@ Fn load_symbol(ModuleHandle module, const char* name) { #endif } +template +Fn try_load_symbol(ModuleHandle module, const char* name) { +#ifdef _WIN32 + const auto addr = GetProcAddress(module, name); + return reinterpret_cast(addr); +#else + dlerror(); + void* addr = dlsym(module, name); + (void) dlerror(); + return reinterpret_cast(addr); +#endif +} + struct Api { ModuleHandle x502_module = nullptr; ModuleHandle e502_module = nullptr; @@ -755,6 +768,7 @@ struct Api { decltype(&X502_Free) Free = nullptr; decltype(&X502_Close) Close = nullptr; decltype(&X502_GetErrorString) GetErrorString = nullptr; + decltype(&X502_GetDevInfo) GetDevInfo = nullptr; decltype(&X502_GetDevInfo2) GetDevInfo2 = nullptr; decltype(&X502_SetMode) SetMode = nullptr; decltype(&X502_StreamsStop) StreamsStop = nullptr; @@ -804,7 +818,11 @@ struct Api { Free = load_symbol(x502_module, "X502_Free"); Close = load_symbol(x502_module, "X502_Close"); GetErrorString = load_symbol(x502_module, "X502_GetErrorString"); - GetDevInfo2 = load_symbol(x502_module, "X502_GetDevInfo2"); + GetDevInfo = try_load_symbol(x502_module, "X502_GetDevInfo"); + GetDevInfo2 = try_load_symbol(x502_module, "X502_GetDevInfo2"); + if ((GetDevInfo == nullptr) && (GetDevInfo2 == nullptr)) { + fail("Neither X502_GetDevInfo nor X502_GetDevInfo2 is available in x502 API library"); + } SetMode = load_symbol(x502_module, "X502_SetMode"); StreamsStop = load_symbol(x502_module, "X502_StreamsStop"); StreamsDisable = load_symbol(x502_module, "X502_StreamsDisable"); @@ -859,6 +877,8 @@ void expect_ok(const Api& api, int32_t err, const std::string& what) { constexpr uint32_t kE502DiSyn2Mask = (static_cast(1U) << 13U) | (static_cast(1U) << 17U); constexpr uint32_t kE502Digital1Mask = (static_cast(1U) << 0U); +constexpr uint32_t kStreamInputAdcFlag = 0x80000000U; +constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U; using TickMs = uint64_t; @@ -1012,6 +1032,30 @@ int16_t pack_raw_code_to_int16(double avg_raw_code) { return static_cast(clamped); } +bool try_extract_raw_adc_code(uint32_t word, double& raw_code) { + if ((word & kStreamInputAdcFlag) == 0U) { + return false; + } + + int32_t value = 0; + if ((word & kStreamInputCalibratedAdcFlag) != 0U) { + const uint32_t payload = word & 0x00FFFFFFU; + value = static_cast(payload); + if ((payload & 0x00800000U) != 0U) { + value |= static_cast(0xFF000000U); + } + } else { + const uint32_t payload = word & 0x0000FFFFU; + value = static_cast(payload); + if ((payload & 0x00008000U) != 0U) { + value |= static_cast(0xFFFF0000U); + } + } + + raw_code = static_cast(value); + return true; +} + struct ConsoleCtrlGuard { bool installed = false; @@ -1113,7 +1157,11 @@ int run(const Config& cfg) { device.opened = true; t_x502_info info{}; - err = api.GetDevInfo2(device.hnd, &info, sizeof(info)); + if (api.GetDevInfo2 != nullptr) { + err = api.GetDevInfo2(device.hnd, &info, sizeof(info)); + } else { + err = api.GetDevInfo(device.hnd, &info); + } expect_ok(api, err, "Get device info"); print_device_info(info); @@ -1356,7 +1404,9 @@ int run(const Config& cfg) { return; } if (tty_step.next_step_index >= 0xFFFFU) { - fail("TTY protocol step index overflow within packet"); + std::cerr << "Warning: TTY protocol step index overflow, forcing tty packet restart\n"; + tty_step.reset_packet(); + tty_writer->emit_packet_start(); } const double ch1_avg = tty_step.sum_ch1 / static_cast(tty_step.count_ch1); @@ -1531,18 +1581,18 @@ int run(const Config& cfg) { uint32_t raw_adc_count = 0; if (tty_writer) { - raw_adc_count = static_cast(adc_raw_buffer.size()); - const int32_t raw_process_err = api.ProcessData(device.hnd, - raw.data(), - static_cast(recvd), - 0, - adc_raw_buffer.data(), - &raw_adc_count, - nullptr, - nullptr); - expect_ok(api, raw_process_err, "Process raw ADC data"); + for (std::size_t i = 0; i < recvd; ++i) { + double raw_code = 0.0; + if (!try_extract_raw_adc_code(raw[i], raw_code)) { + continue; + } + if (raw_adc_count >= adc_raw_buffer.size()) { + fail("Raw ADC parsing overflowed the temporary buffer"); + } + adc_raw_buffer[raw_adc_count++] = raw_code; + } if (raw_adc_count != adc_count) { - fail("Raw ADC processing returned a different sample count than voltage processing"); + fail("Raw ADC parsing returned a different sample count than voltage processing"); } } @@ -1624,7 +1674,14 @@ int run(const Config& cfg) { start_packet(); } - if (!packet_active && start_edge) { + if (packet_active && start_edge) { + finish_packet(PacketCloseReason::ExternalStopEdge); + if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { + stop_loop_requested = true; + continue; + } + start_packet(); + } else if (!packet_active && start_edge) { start_packet(); } diff --git a/tty_protocol_writer.cpp b/tty_protocol_writer.cpp index f7857c1..cfb464f 100644 --- a/tty_protocol_writer.cpp +++ b/tty_protocol_writer.cpp @@ -38,11 +38,17 @@ void TtyProtocolWriter::close_fd() noexcept {} #else +#include #include #include #include +#include +#include +#include #include +#include #include +#include #include namespace { @@ -53,31 +59,113 @@ std::string io_error(const std::string& action, const std::string& path) { return out.str(); } -} // namespace - -TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) { - fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY); - if (fd_ < 0) { - throw std::runtime_error(io_error("Cannot open tty output", path_)); +void set_fd_raw(int fd) { + struct termios tio {}; + if (::tcgetattr(fd, &tio) != 0) { + throw std::runtime_error(io_error("Cannot read tty attributes for", std::to_string(fd))); + } + ::cfmakeraw(&tio); + if (::tcsetattr(fd, TCSANOW, &tio) != 0) { + throw std::runtime_error(io_error("Cannot apply raw tty attributes to", std::to_string(fd))); } } +bool is_character_device_path(const std::string& path) { + struct stat st {}; + if (::stat(path.c_str(), &st) != 0) { + if (errno == ENOENT) { + return false; + } + throw std::runtime_error(io_error("Cannot stat tty output", path)); + } + return S_ISCHR(st.st_mode); +} + +std::optional read_link_target(const std::string& path) { + std::array buf {}; + const ssize_t len = ::readlink(path.c_str(), buf.data(), buf.size() - 1U); + if (len < 0) { + if (errno == EINVAL || errno == ENOENT) { + return std::nullopt; + } + throw std::runtime_error(io_error("Cannot read symlink", path)); + } + buf[static_cast(len)] = '\0'; + return std::string(buf.data()); +} + +} // namespace + +TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) { + if (is_character_device_path(path_)) { + fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY); + if (fd_ < 0) { + throw std::runtime_error(io_error("Cannot open tty output", path_)); + } + return; + } + + std::array slave_name {}; + if (::openpty(&fd_, &slave_fd_, slave_name.data(), nullptr, nullptr) != 0) { + throw std::runtime_error(io_error("Cannot create PTY bridge for", path_)); + } + try { + slave_path_ = slave_name.data(); + set_fd_raw(slave_fd_); + + struct stat st {}; + if (::lstat(path_.c_str(), &st) == 0) { + if (!S_ISLNK(st.st_mode) && !S_ISREG(st.st_mode)) { + throw std::runtime_error("Refusing to replace non-link path '" + path_ + "'"); + } + if (::unlink(path_.c_str()) != 0) { + throw std::runtime_error(io_error("Cannot remove existing tty link", path_)); + } + } else if (errno != ENOENT) { + throw std::runtime_error(io_error("Cannot inspect tty link path", path_)); + } + + if (::symlink(slave_path_.c_str(), path_.c_str()) != 0) { + throw std::runtime_error(io_error("Cannot create tty symlink", path_)); + } + } catch (...) { + close_slave_fd(); + close_fd(); + throw; + } + owns_link_ = true; +} + TtyProtocolWriter::~TtyProtocolWriter() { + remove_owned_link(); + close_slave_fd(); close_fd(); } TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept : path_(std::move(other.path_)), - fd_(other.fd_) { + fd_(other.fd_), + slave_fd_(other.slave_fd_), + slave_path_(std::move(other.slave_path_)), + owns_link_(other.owns_link_) { other.fd_ = -1; + other.slave_fd_ = -1; + other.owns_link_ = false; } TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept { if (this != &other) { + remove_owned_link(); + close_slave_fd(); close_fd(); path_ = std::move(other.path_); fd_ = other.fd_; + slave_fd_ = other.slave_fd_; + slave_path_ = std::move(other.slave_path_); + owns_link_ = other.owns_link_; other.fd_ = -1; + other.slave_fd_ = -1; + other.owns_link_ = false; } return *this; } @@ -125,4 +213,27 @@ void TtyProtocolWriter::close_fd() noexcept { } } +void TtyProtocolWriter::close_slave_fd() noexcept { + if (slave_fd_ >= 0) { + ::close(slave_fd_); + slave_fd_ = -1; + } +} + +void TtyProtocolWriter::remove_owned_link() noexcept { + if (!owns_link_ || path_.empty()) { + return; + } + + try { + const auto target = read_link_target(path_); + if (target && (*target == slave_path_)) { + ::unlink(path_.c_str()); + } + } catch (...) { + } + + owns_link_ = false; +} + #endif diff --git a/tty_protocol_writer.h b/tty_protocol_writer.h index 295e0c5..4f1a838 100644 --- a/tty_protocol_writer.h +++ b/tty_protocol_writer.h @@ -21,9 +21,14 @@ public: private: void write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const; void close_fd() noexcept; + void close_slave_fd() noexcept; + void remove_owned_link() noexcept; std::string path_; #ifndef _WIN32 int fd_ = -1; + int slave_fd_ = -1; + std::string slave_path_; + bool owns_link_ = false; #endif };