From 48e22c4f6fb7b46976e6fba745760316aeb22e11 Mon Sep 17 00:00:00 2001 From: Thomas Schmucker Date: Mon, 3 Aug 2026 23:15:06 +0200 Subject: Zustandsmaschine steuert jetzt den Ablauf und schaltet zwischen sync/async um --- src/app.cpp | 388 +++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 320 insertions(+), 68 deletions(-) (limited to 'src/app.cpp') diff --git a/src/app.cpp b/src/app.cpp index c6ec00a..fb7659d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,99 +1,351 @@ // Standard C++ #include -#include +#include +#include #include +#include +#include #include -#include // Asio #include +#define SERIAL_PORT "/dev/cuaU0" + using namespace std; +namespace { + +bool +connect(asio::serial_port& port) +{ + port.open(SERIAL_PORT); + if ( !port.is_open() ) { + return false; + } + + port.set_option(asio::serial_port::baud_rate(57600)); + port.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); + port.set_option(asio::serial_port::character_size(8)); + port.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); + port.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); + + return true; +} + +std::vector +read_until(asio::serial_port& port, asio::io_context& context, uint8_t delimiter, std::chrono::milliseconds timeout) +{ + std::vector result; + + asio::steady_timer timer(context); + bool finished = false; + + array byte{}; + + std::function startRead; + + startRead = [&]() { + port.async_read_some( + asio::buffer(byte), + [&](std::error_code error, std::size_t /* n */) { + if ( finished ) { + return; + } + + if ( error ) { + finished = true; + return; + } + + result.push_back(byte[0]); + + if ( byte[0] == delimiter ) { + finished = true; + timer.cancel(); + return; + } + + startRead(); + }); + }; + + timer.expires_after(timeout); + + timer.async_wait( + [&](std::error_code error) { + if ( !error && !finished ) { + finished = true; + port.cancel(); + } + }); + + startRead(); + + while ( !finished ) { + context.run_one(); + } + + context.restart(); + + return result; +} + +void +print_hex(string_view message, span data) +{ + if ( data.empty() ) { + return; + } + + cout << message; + for ( const auto& chr: data ) { + array hex{}; + ::snprintf(hex.data(), hex.size(), " %02X", chr); + cout << hex.data(); + } + cout << '\n'; +} + +bool +sync(asio::serial_port& port, asio::io_context& context) +{ + write(port, asio::buffer(">")); + + this_thread::sleep_for(100ms); + + constexpr unsigned char delimiter = 0xAA; + constexpr auto timeout = 500ms; + + write(port, asio::buffer(">")); + auto data = read_until(port, context, delimiter, timeout); + + print_hex("Data:", data); + + return !data.empty() && data.back() == delimiter; +} + +void +get_serial(asio::serial_port& port) +{ + constexpr auto response_size = 7; + array buffer{}; + + write(port, asio::buffer(">")); + asio::read(port, asio::buffer(buffer, buffer.size())); + + print_hex("Serial:", buffer); +} + +void +get_version(asio::serial_port& port) +{ + constexpr auto response_size = 18; + array buffer{}; + + write(port, asio::buffer(">")); + asio::read(port, asio::buffer(buffer, buffer.size())); + + cout << "Version: "; + cout.write(buffer.data(), buffer.size()); + cout << '\n'; +} + +void +get_datetime(asio::serial_port& port) +{ + constexpr auto response_size = 7; + array buffer{}; + + write(port, asio::buffer(">")); + asio::read(port, asio::buffer(buffer, buffer.size())); + + if ( buffer.back() != 0xAA ) { + print_hex("Ungültige Antwort:", buffer); + return; + } + + unsigned int year = 2000 + buffer.at(0); + unsigned int month = buffer.at(1); + unsigned int day = buffer.at(2); + unsigned int hour = buffer.at(3); + unsigned int minute = buffer.at(4); + unsigned int second = buffer.at(5); + + cout << "Datum: " + << year << '-' + << month << '-' + << day << ':' + << hour << '-' + << minute << '-' + << second << '\n'; +} + int -main() +run() { - asio::io_context context; + using namespace asio; - try { - const auto baudrate = 57600; - const auto databits = 8; + io_context context; + serial_port port(context); + jthread thread; + + enum class State : int8_t { + START, + CONNECT, + CONNECT_ERROR, + SYNC, + SYNC_ERROR, + SYNCED, + COMMAND_MODE, + INIT_HB, + WAIT_HB, + STOP_HB, + CLOSE, + QUIT, + }; - asio::serial_port port(context); + for ( State state = State::START; state != State::QUIT; ) { + using enum State; - auto send = [&](string_view cmd) { - asio::write(port, asio::buffer(cmd)); - }; + switch ( state ) { + case START: + state = CONNECT; + break; + + case CONNECT: + if ( connect(port) ) { + state = SYNC; + } + else { + state = CONNECT_ERROR; + } + break; - port.open("/dev/cuaU0"); - if ( !port.is_open() ) { + case CONNECT_ERROR: cerr << "Kann Port nicht öffnen...\n"; - return EXIT_FAILURE; - } + state = QUIT; + break; - port.set_option(asio::serial_port::baud_rate(baudrate)); - port.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); - port.set_option(asio::serial_port::character_size(databits)); - port.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); - port.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); - - send(">"); - - jthread thread([&context, &port]() { - array buffer{}; - - function readFromPort = [&]() { - port.async_read_some(asio::buffer(buffer, buffer.size()), [&](std::error_code error, size_t length) { - if ( !error ) { - cout << "Daten: " << length << ": "; - for ( size_t i = 0; i != length; ++i ) { - auto chr = static_cast(buffer.at(i)); - - if ( isprint(chr) ) { - cout << static_cast(chr); - } - else { - cout << '<' << hex << setw(2) << setfill('0') << static_cast(chr) << dec << '>'; - } - } - cout << '\n'; + case SYNC: + if ( sync(port, context) ) { + state = SYNCED; + } + else { + state = SYNC_ERROR; + } + break; - readFromPort(); - } - else { - if ( error != asio::error::operation_not_supported ) { - cerr << "Fehler: " << error.message() << '\n'; - } - } - }); + case SYNC_ERROR: + cerr << "Kann nicht synchronisieren\n"; + state = QUIT; + break; + + case SYNCED: + cout << "sync ...\n"; + state = COMMAND_MODE; + break; + + case COMMAND_MODE: { + static const map> callback{ + { "ser?", get_serial }, + { "ver?", get_version }, + { "date?", get_datetime }, + { "poweron!", [](serial_port& port) { write(port, asio::buffer(">")); } }, + { "poweroff!", [](serial_port& port) { write(port, asio::buffer(">")); } }, + { "sync!", [&](serial_port& port) { sync(port, context); } }, + { "quit!", [&state](serial_port&) { state = CLOSE; } }, + { "hb!", [&state](serial_port&) { state = INIT_HB; } }, }; - readFromPort(); - context.run(); - }); + cout << "Command> "; - for ( string line; getline(cin, line); ) { - if ( line == "ser?" ) { - send(">"); - } - if ( line == "ver?" ) { - send(">"); - } - if ( line == "date?" ) { - send(">"); + string line; + if ( getline(cin, line) ) { + const auto iter = callback.find(line); + if ( iter != callback.cend() ) { + iter->second(port); + } + else { + if ( !line.empty() ) { + cerr << "unbekanntes command: " << line << '\n'; + } + } } - if ( line == "hb!" ) { - send(">"); + else { + state = CLOSE; } + } break; + + case INIT_HB: + asio::write(port, asio::buffer(">")); + + thread = jthread([&context, &port]() { + array buffer{}; + + function readFromPort = [&]() { + port.async_read_some(asio::buffer(buffer, buffer.size()), [&](std::error_code error, size_t length) { + if ( error && error != asio::error::operation_not_supported ) { + cerr << "Fehler: " << error.message() << '\n'; + return; + } + + print_hex("Daten:", { buffer.data(), length }); + + readFromPort(); + }); + }; + + readFromPort(); + + context.run(); + }); + + state = WAIT_HB; + break; - if ( line == "quit" ) { - break; + case WAIT_HB: + for ( string line; getline(cin, line); ) { + cout << "Heartbeat> "; + if ( line == "stop" ) { + break; + } + if ( !line.empty() ) { + cerr << "unbekanntes command: " << line << '\n'; + } } + state = STOP_HB; + break; + + case STOP_HB: + port.cancel(); + context.stop(); + thread.join(); + context.restart(); + + state = SYNC; + break; + + case CLOSE: + port.close(); + state = QUIT; + break; + + case QUIT: + break; } + } + + return EXIT_SUCCESS; +} + +} // namespace - port.cancel(); - context.stop(); - port.close(); +int +main() +{ + try { + return run(); } catch ( asio::system_error& e ) { cerr << e.what() << '\n'; -- cgit v1.3