// Standard C++ #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 run() { using namespace asio; 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, }; for ( State state = State::START; state != State::QUIT; ) { using enum State; switch ( state ) { case START: state = CONNECT; break; case CONNECT: if ( connect(port) ) { state = SYNC; } else { state = CONNECT_ERROR; } break; case CONNECT_ERROR: cerr << "Kann Port nicht öffnen...\n"; state = QUIT; break; case SYNC: if ( sync(port, context) ) { state = SYNCED; } else { state = SYNC_ERROR; } break; 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; } }, }; cout << "Command> "; 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'; } } } 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; 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 int main() { try { return run(); } catch ( asio::system_error& e ) { cerr << e.what() << '\n'; } }