test tty writer
This commit is contained in:
91
main.cpp
91
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 <typename Fn>
|
||||
Fn try_load_symbol(ModuleHandle module, const char* name) {
|
||||
#ifdef _WIN32
|
||||
const auto addr = GetProcAddress(module, name);
|
||||
return reinterpret_cast<Fn>(addr);
|
||||
#else
|
||||
dlerror();
|
||||
void* addr = dlsym(module, name);
|
||||
(void) dlerror();
|
||||
return reinterpret_cast<Fn>(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<decltype(Free)>(x502_module, "X502_Free");
|
||||
Close = load_symbol<decltype(Close)>(x502_module, "X502_Close");
|
||||
GetErrorString = load_symbol<decltype(GetErrorString)>(x502_module, "X502_GetErrorString");
|
||||
GetDevInfo2 = load_symbol<decltype(GetDevInfo2)>(x502_module, "X502_GetDevInfo2");
|
||||
GetDevInfo = try_load_symbol<decltype(GetDevInfo)>(x502_module, "X502_GetDevInfo");
|
||||
GetDevInfo2 = try_load_symbol<decltype(GetDevInfo2)>(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<decltype(SetMode)>(x502_module, "X502_SetMode");
|
||||
StreamsStop = load_symbol<decltype(StreamsStop)>(x502_module, "X502_StreamsStop");
|
||||
StreamsDisable = load_symbol<decltype(StreamsDisable)>(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<uint32_t>(1U) << 13U) | (static_cast<uint32_t>(1U) << 17U);
|
||||
constexpr uint32_t kE502Digital1Mask = (static_cast<uint32_t>(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<int16_t>(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<int32_t>(payload);
|
||||
if ((payload & 0x00800000U) != 0U) {
|
||||
value |= static_cast<int32_t>(0xFF000000U);
|
||||
}
|
||||
} else {
|
||||
const uint32_t payload = word & 0x0000FFFFU;
|
||||
value = static_cast<int32_t>(payload);
|
||||
if ((payload & 0x00008000U) != 0U) {
|
||||
value |= static_cast<int32_t>(0xFFFF0000U);
|
||||
}
|
||||
}
|
||||
|
||||
raw_code = static_cast<double>(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<double>(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<uint32_t>(adc_raw_buffer.size());
|
||||
const int32_t raw_process_err = api.ProcessData(device.hnd,
|
||||
raw.data(),
|
||||
static_cast<uint32_t>(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();
|
||||
}
|
||||
|
||||
|
||||
@ -38,11 +38,17 @@ void TtyProtocolWriter::close_fd() noexcept {}
|
||||
|
||||
#else
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <optional>
|
||||
#include <pty.h>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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<std::string> read_link_target(const std::string& path) {
|
||||
std::array<char, PATH_MAX> 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<std::size_t>(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<char, PATH_MAX> 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
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user