#include "GpioInput.h" #include #include #include #include #include #include #include #include #include #include #include namespace { volatile std::sig_atomic_t g_stopRequested = 0; struct TestOptions { GpioInputConfig gpio; int runSeconds = 30; int printIntervalMs = 500; }; void HandleSignal(int) { g_stopRequested = 1; } bool ParseInteger(const char* text, long long minimum, long long maximum, long long& value) { if (!text || *text == '\0') { return false; } char* end = nullptr; errno = 0; const long long parsed = std::strtoll(text, &end, 10); if (errno != 0 || end == text || *end != '\0' || parsed < minimum || parsed > maximum) { return false; } value = parsed; return true; } void PrintUsage(const char* program) { std::cout << "Usage: " << program << " [options]\n\n" << "Options:\n" << " --direction GPIO direction file\n" << " --value GPIO value file\n" << " --seconds Run duration (default: 30, 0 until Ctrl+C)\n" << " --active-high Physical high is active (default)\n" << " --active-low Physical low is active\n" << " --poll-ms Poll interval (default: 10)\n" << " --debounce-ms Debounce time (default: 50)\n" << " --reconnect-ms Reconnect interval (default: 1000)\n" << " --print-ms IO3 state print interval (default: 500)\n" << " -h, --help Show this help\n\n" << "Example:\n" << " " << program << " --seconds 30\n"; } bool ReadOptionValue(int argc, char* argv[], int& index, const std::string& option, const char*& value) { if (index + 1 >= argc) { std::cerr << "Missing value for " << option << '\n'; return false; } value = argv[++index]; return true; } bool ParseOptions(int argc, char* argv[], TestOptions& options) { for (int i = 1; i < argc; ++i) { const std::string option = argv[i]; const char* text = nullptr; long long value = 0; if (option == "-h" || option == "--help") { PrintUsage(argv[0]); return false; } if (option == "--direction") { if (!ReadOptionValue(argc, argv, i, option, text)) { return false; } options.gpio.directionPath = text; } else if (option == "--value") { if (!ReadOptionValue(argc, argv, i, option, text)) { return false; } options.gpio.valuePath = text; } else if (option == "--seconds") { if (!ReadOptionValue(argc, argv, i, option, text) || !ParseInteger(text, 0, 86400, value)) { std::cerr << "Invalid run duration\n"; return false; } options.runSeconds = static_cast(value); } else if (option == "--active-low") { options.gpio.activeLow = true; } else if (option == "--active-high") { options.gpio.activeLow = false; } else if (option == "--poll-ms") { if (!ReadOptionValue(argc, argv, i, option, text) || !ParseInteger(text, 1, 60000, value)) { std::cerr << "Invalid poll interval\n"; return false; } options.gpio.pollIntervalMs = static_cast(value); } else if (option == "--debounce-ms") { if (!ReadOptionValue(argc, argv, i, option, text) || !ParseInteger(text, 0, 60000, value)) { std::cerr << "Invalid debounce time\n"; return false; } options.gpio.debounceMs = static_cast(value); } else if (option == "--reconnect-ms") { if (!ReadOptionValue(argc, argv, i, option, text) || !ParseInteger(text, 100, 600000, value)) { std::cerr << "Invalid reconnect interval\n"; return false; } options.gpio.reconnectIntervalMs = static_cast(value); } else if (option == "--print-ms") { if (!ReadOptionValue(argc, argv, i, option, text) || !ParseInteger(text, 20, 60000, value)) { std::cerr << "Invalid state print interval\n"; return false; } options.printIntervalMs = static_cast(value); } else { std::cerr << "Unknown option: " << option << '\n'; return false; } } if (options.gpio.directionPath.empty()) { std::cerr << "GPIO direction path cannot be empty\n"; return false; } if (options.gpio.valuePath.empty()) { std::cerr << "GPIO value path cannot be empty\n"; return false; } return true; } } // namespace int main(int argc, char* argv[]) { #if !defined(__linux__) (void)argc; (void)argv; std::cerr << "GpioModuleTest only supports the Linux GPIO sysfs interface\n"; return 3; #else for (int i = 1; i < argc; ++i) { const std::string option = argv[i]; if (option == "-h" || option == "--help") { PrintUsage(argv[0]); return 0; } } TestOptions options; if (!ParseOptions(argc, argv, options)) { return 1; } std::signal(SIGINT, HandleSignal); std::signal(SIGTERM, HandleSignal); std::mutex outputMutex; std::atomic everAvailable{false}; std::atomic valueEventCount{0}; std::atomic lastLogicalValue{-1}; const auto startedAt = std::chrono::steady_clock::now(); const auto elapsedMilliseconds = [&startedAt]() { return std::chrono::duration_cast( std::chrono::steady_clock::now() - startedAt).count(); }; const auto printLine = [&outputMutex, &elapsedMilliseconds]( const char* category, const std::string& message) { std::lock_guard lock(outputMutex); const double seconds = static_cast(elapsedMilliseconds()) / 1000.0; std::cout << '[' << std::fixed << std::setprecision(3) << seconds << "s] " << category << ": " << message << std::endl; }; std::cout << "GpioModule hardware test\n" << " direction : " << options.gpio.directionPath << '\n' << " value : " << options.gpio.valuePath << '\n' << " polarity : " << (options.gpio.activeLow ? "active-low" : "active-high") << '\n' << " poll : " << options.gpio.pollIntervalMs << " ms\n" << " debounce : " << options.gpio.debounceMs << " ms\n" << " print : " << options.printIntervalMs << " ms\n" << " duration : " << (options.runSeconds == 0 ? std::string("until Ctrl+C") : std::to_string(options.runSeconds) + " s") << "\n\nThe emergency-stop loop is normally closed: I/O3 is connected to GND.\n" << "Closed/GND must be INACTIVE; pressed or disconnected must be ACTIVE.\n"; GpioInput input; const bool started = input.Start( options.gpio, [&printLine, &lastLogicalValue, &valueEventCount](bool active) { const int logicalValue = active ? 1 : 0; const int previous = lastLogicalValue.exchange(logicalValue); valueEventCount.fetch_add(1); std::string message = active ? "ACTIVE (logical 1)" : "INACTIVE (logical 0)"; if (previous < 0) { message += " [initial]"; } else if (previous == logicalValue) { message += " [reconnected]"; } else { message += " [changed]"; } printLine("VALUE", message); }, [&printLine, &everAvailable](bool available, const std::string& message) { if (available) { everAvailable.store(true); } printLine(available ? "READY" : "ERROR", message); }); if (!started) { std::cerr << "Failed to start GpioInput: " << input.LastError() << '\n'; return 2; } const auto deadline = startedAt + std::chrono::seconds(options.runSeconds); auto nextStatePrint = std::chrono::steady_clock::now(); while (!g_stopRequested && (options.runSeconds == 0 || std::chrono::steady_clock::now() < deadline)) { const auto now = std::chrono::steady_clock::now(); if (now >= nextStatePrint) { if (!input.IsAvailable()) { printLine("IO3", "UNAVAILABLE"); } else { printLine("IO3", input.IsActive() ? "ACTIVE (logical 1)" : "INACTIVE (logical 0)"); } nextStatePrint = now + std::chrono::milliseconds(options.printIntervalMs); } std::this_thread::sleep_for(std::chrono::milliseconds(20)); } input.Stop(); std::cout << "\nTest summary\n" << " GPIO available : " << (everAvailable.load() ? "yes" : "no") << '\n' << " value callbacks: " << valueEventCount.load() << '\n'; if (!everAvailable.load()) { std::cerr << "GPIO never became available. Last error: " << input.LastError() << '\n'; return 2; } return 0; #endif }