diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.cpp | 380 |
1 files changed, 316 insertions, 64 deletions
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 @@ | |||
| 1 | // Standard C++ | 1 | // Standard C++ |
| 2 | #include <array> | 2 | #include <array> |
| 3 | #include <iomanip> | 3 | #include <cstdlib> |
| 4 | #include <functional> | ||
| 4 | #include <iostream> | 5 | #include <iostream> |
| 6 | #include <map> | ||
| 7 | #include <span> | ||
| 5 | #include <string> | 8 | #include <string> |
| 6 | #include <thread> | ||
| 7 | 9 | ||
| 8 | // Asio | 10 | // Asio |
| 9 | #include <asio.hpp> | 11 | #include <asio.hpp> |
| 10 | 12 | ||
| 13 | #define SERIAL_PORT "/dev/cuaU0" | ||
| 14 | |||
| 11 | using namespace std; | 15 | using namespace std; |
| 12 | 16 | ||
| 17 | namespace { | ||
| 18 | |||
| 19 | bool | ||
| 20 | connect(asio::serial_port& port) | ||
| 21 | { | ||
| 22 | port.open(SERIAL_PORT); | ||
| 23 | if ( !port.is_open() ) { | ||
| 24 | return false; | ||
| 25 | } | ||
| 26 | |||
| 27 | port.set_option(asio::serial_port::baud_rate(57600)); | ||
| 28 | port.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); | ||
| 29 | port.set_option(asio::serial_port::character_size(8)); | ||
| 30 | port.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); | ||
| 31 | port.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); | ||
| 32 | |||
| 33 | return true; | ||
| 34 | } | ||
| 35 | |||
| 36 | std::vector<uint8_t> | ||
| 37 | read_until(asio::serial_port& port, asio::io_context& context, uint8_t delimiter, std::chrono::milliseconds timeout) | ||
| 38 | { | ||
| 39 | std::vector<uint8_t> result; | ||
| 40 | |||
| 41 | asio::steady_timer timer(context); | ||
| 42 | bool finished = false; | ||
| 43 | |||
| 44 | array<uint8_t, 1> byte{}; | ||
| 45 | |||
| 46 | std::function<void()> startRead; | ||
| 47 | |||
| 48 | startRead = [&]() { | ||
| 49 | port.async_read_some( | ||
| 50 | asio::buffer(byte), | ||
| 51 | [&](std::error_code error, std::size_t /* n */) { | ||
| 52 | if ( finished ) { | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | |||
| 56 | if ( error ) { | ||
| 57 | finished = true; | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | result.push_back(byte[0]); | ||
| 62 | |||
| 63 | if ( byte[0] == delimiter ) { | ||
| 64 | finished = true; | ||
| 65 | timer.cancel(); | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | startRead(); | ||
| 70 | }); | ||
| 71 | }; | ||
| 72 | |||
| 73 | timer.expires_after(timeout); | ||
| 74 | |||
| 75 | timer.async_wait( | ||
| 76 | [&](std::error_code error) { | ||
| 77 | if ( !error && !finished ) { | ||
| 78 | finished = true; | ||
| 79 | port.cancel(); | ||
| 80 | } | ||
| 81 | }); | ||
| 82 | |||
| 83 | startRead(); | ||
| 84 | |||
| 85 | while ( !finished ) { | ||
| 86 | context.run_one(); | ||
| 87 | } | ||
| 88 | |||
| 89 | context.restart(); | ||
| 90 | |||
| 91 | return result; | ||
| 92 | } | ||
| 93 | |||
| 94 | void | ||
| 95 | print_hex(string_view message, span<const unsigned char> data) | ||
| 96 | { | ||
| 97 | if ( data.empty() ) { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | cout << message; | ||
| 102 | for ( const auto& chr: data ) { | ||
| 103 | array<char, 4> hex{}; | ||
| 104 | ::snprintf(hex.data(), hex.size(), " %02X", chr); | ||
| 105 | cout << hex.data(); | ||
| 106 | } | ||
| 107 | cout << '\n'; | ||
| 108 | } | ||
| 109 | |||
| 110 | bool | ||
| 111 | sync(asio::serial_port& port, asio::io_context& context) | ||
| 112 | { | ||
| 113 | write(port, asio::buffer("<HEARTBEAT0>>")); | ||
| 114 | |||
| 115 | this_thread::sleep_for(100ms); | ||
| 116 | |||
| 117 | constexpr unsigned char delimiter = 0xAA; | ||
| 118 | constexpr auto timeout = 500ms; | ||
| 119 | |||
| 120 | write(port, asio::buffer("<GETDATETIME>>")); | ||
| 121 | auto data = read_until(port, context, delimiter, timeout); | ||
| 122 | |||
| 123 | print_hex("Data:", data); | ||
| 124 | |||
| 125 | return !data.empty() && data.back() == delimiter; | ||
| 126 | } | ||
| 127 | |||
| 128 | void | ||
| 129 | get_serial(asio::serial_port& port) | ||
| 130 | { | ||
| 131 | constexpr auto response_size = 7; | ||
| 132 | array<unsigned char, response_size> buffer{}; | ||
| 133 | |||
| 134 | write(port, asio::buffer("<GETSERIAL>>")); | ||
| 135 | asio::read(port, asio::buffer(buffer, buffer.size())); | ||
| 136 | |||
| 137 | print_hex("Serial:", buffer); | ||
| 138 | } | ||
| 139 | |||
| 140 | void | ||
| 141 | get_version(asio::serial_port& port) | ||
| 142 | { | ||
| 143 | constexpr auto response_size = 18; | ||
| 144 | array<char, response_size> buffer{}; | ||
| 145 | |||
| 146 | write(port, asio::buffer("<GETVER>>")); | ||
| 147 | asio::read(port, asio::buffer(buffer, buffer.size())); | ||
| 148 | |||
| 149 | cout << "Version: "; | ||
| 150 | cout.write(buffer.data(), buffer.size()); | ||
| 151 | cout << '\n'; | ||
| 152 | } | ||
| 153 | |||
| 154 | void | ||
| 155 | get_datetime(asio::serial_port& port) | ||
| 156 | { | ||
| 157 | constexpr auto response_size = 7; | ||
| 158 | array<unsigned char, response_size> buffer{}; | ||
| 159 | |||
| 160 | write(port, asio::buffer("<GETDATETIME>>")); | ||
| 161 | asio::read(port, asio::buffer(buffer, buffer.size())); | ||
| 162 | |||
| 163 | if ( buffer.back() != 0xAA ) { | ||
| 164 | print_hex("Ungültige Antwort:", buffer); | ||
| 165 | return; | ||
| 166 | } | ||
| 167 | |||
| 168 | unsigned int year = 2000 + buffer.at(0); | ||
| 169 | unsigned int month = buffer.at(1); | ||
| 170 | unsigned int day = buffer.at(2); | ||
| 171 | unsigned int hour = buffer.at(3); | ||
| 172 | unsigned int minute = buffer.at(4); | ||
| 173 | unsigned int second = buffer.at(5); | ||
| 174 | |||
| 175 | cout << "Datum: " | ||
| 176 | << year << '-' | ||
| 177 | << month << '-' | ||
| 178 | << day << ':' | ||
| 179 | << hour << '-' | ||
| 180 | << minute << '-' | ||
| 181 | << second << '\n'; | ||
| 182 | } | ||
| 183 | |||
| 13 | int | 184 | int |
| 14 | main() | 185 | run() |
| 15 | { | 186 | { |
| 16 | asio::io_context context; | 187 | using namespace asio; |
| 17 | 188 | ||
| 18 | try { | 189 | io_context context; |
| 19 | const auto baudrate = 57600; | 190 | serial_port port(context); |
| 20 | const auto databits = 8; | 191 | jthread thread; |
| 192 | |||
| 193 | enum class State : int8_t { | ||
| 194 | START, | ||
| 195 | CONNECT, | ||
| 196 | CONNECT_ERROR, | ||
| 197 | SYNC, | ||
| 198 | SYNC_ERROR, | ||
| 199 | SYNCED, | ||
| 200 | COMMAND_MODE, | ||
| 201 | INIT_HB, | ||
| 202 | WAIT_HB, | ||
| 203 | STOP_HB, | ||
| 204 | CLOSE, | ||
| 205 | QUIT, | ||
| 206 | }; | ||
| 21 | 207 | ||
| 22 | asio::serial_port port(context); | 208 | for ( State state = State::START; state != State::QUIT; ) { |
| 209 | using enum State; | ||
| 23 | 210 | ||
| 24 | auto send = [&](string_view cmd) { | 211 | switch ( state ) { |
| 25 | asio::write(port, asio::buffer(cmd)); | 212 | case START: |
| 26 | }; | 213 | state = CONNECT; |
| 214 | break; | ||
| 27 | 215 | ||
| 28 | port.open("/dev/cuaU0"); | 216 | case CONNECT: |
| 29 | if ( !port.is_open() ) { | 217 | if ( connect(port) ) { |
| 218 | state = SYNC; | ||
| 219 | } | ||
| 220 | else { | ||
| 221 | state = CONNECT_ERROR; | ||
| 222 | } | ||
| 223 | break; | ||
| 224 | |||
| 225 | case CONNECT_ERROR: | ||
| 30 | cerr << "Kann Port nicht öffnen...\n"; | 226 | cerr << "Kann Port nicht öffnen...\n"; |
| 31 | return EXIT_FAILURE; | 227 | state = QUIT; |
| 32 | } | 228 | break; |
| 33 | 229 | ||
| 34 | port.set_option(asio::serial_port::baud_rate(baudrate)); | 230 | case SYNC: |
| 35 | port.set_option(asio::serial_port::parity(asio::serial_port::parity::none)); | 231 | if ( sync(port, context) ) { |
| 36 | port.set_option(asio::serial_port::character_size(databits)); | 232 | state = SYNCED; |
| 37 | port.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one)); | 233 | } |
| 38 | port.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none)); | 234 | else { |
| 235 | state = SYNC_ERROR; | ||
| 236 | } | ||
| 237 | break; | ||
| 39 | 238 | ||
| 40 | send("<GETSERIAL>>"); | 239 | case SYNC_ERROR: |
| 240 | cerr << "Kann nicht synchronisieren\n"; | ||
| 241 | state = QUIT; | ||
| 242 | break; | ||
| 41 | 243 | ||
| 42 | jthread thread([&context, &port]() { | 244 | case SYNCED: |
| 43 | array<char, 1000> buffer{}; | 245 | cout << "sync ...\n"; |
| 246 | state = COMMAND_MODE; | ||
| 247 | break; | ||
| 44 | 248 | ||
| 45 | function<void()> readFromPort = [&]() { | 249 | case COMMAND_MODE: { |
| 46 | port.async_read_some(asio::buffer(buffer, buffer.size()), [&](std::error_code error, size_t length) { | 250 | static const map<string, function<void(serial_port&)>> callback{ |
| 47 | if ( !error ) { | 251 | { "ser?", get_serial }, |
| 48 | cout << "Daten: " << length << ": "; | 252 | { "ver?", get_version }, |
| 49 | for ( size_t i = 0; i != length; ++i ) { | 253 | { "date?", get_datetime }, |
| 50 | auto chr = static_cast<unsigned char>(buffer.at(i)); | 254 | { "poweron!", [](serial_port& port) { write(port, asio::buffer("<POWERON>>")); } }, |
| 255 | { "poweroff!", [](serial_port& port) { write(port, asio::buffer("<POWEROFF>>")); } }, | ||
| 256 | { "sync!", [&](serial_port& port) { sync(port, context); } }, | ||
| 257 | { "quit!", [&state](serial_port&) { state = CLOSE; } }, | ||
| 258 | { "hb!", [&state](serial_port&) { state = INIT_HB; } }, | ||
| 259 | }; | ||
| 51 | 260 | ||
| 52 | if ( isprint(chr) ) { | 261 | cout << "Command> "; |
| 53 | cout << static_cast<char>(chr); | ||
| 54 | } | ||
| 55 | else { | ||
| 56 | cout << '<' << hex << setw(2) << setfill('0') << static_cast<unsigned>(chr) << dec << '>'; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | cout << '\n'; | ||
| 60 | 262 | ||
| 61 | readFromPort(); | 263 | string line; |
| 264 | if ( getline(cin, line) ) { | ||
| 265 | const auto iter = callback.find(line); | ||
| 266 | if ( iter != callback.cend() ) { | ||
| 267 | iter->second(port); | ||
| 268 | } | ||
| 269 | else { | ||
| 270 | if ( !line.empty() ) { | ||
| 271 | cerr << "unbekanntes command: " << line << '\n'; | ||
| 62 | } | 272 | } |
| 63 | else { | 273 | } |
| 64 | if ( error != asio::error::operation_not_supported ) { | 274 | } |
| 275 | else { | ||
| 276 | state = CLOSE; | ||
| 277 | } | ||
| 278 | } break; | ||
| 279 | |||
| 280 | case INIT_HB: | ||
| 281 | asio::write(port, asio::buffer("<HEARTBEAT1>>")); | ||
| 282 | |||
| 283 | thread = jthread([&context, &port]() { | ||
| 284 | array<unsigned char, 1000> buffer{}; | ||
| 285 | |||
| 286 | function<void()> readFromPort = [&]() { | ||
| 287 | port.async_read_some(asio::buffer(buffer, buffer.size()), [&](std::error_code error, size_t length) { | ||
| 288 | if ( error && error != asio::error::operation_not_supported ) { | ||
| 65 | cerr << "Fehler: " << error.message() << '\n'; | 289 | cerr << "Fehler: " << error.message() << '\n'; |
| 290 | return; | ||
| 66 | } | 291 | } |
| 67 | } | ||
| 68 | }); | ||
| 69 | }; | ||
| 70 | 292 | ||
| 71 | readFromPort(); | 293 | print_hex("Daten:", { buffer.data(), length }); |
| 72 | context.run(); | ||
| 73 | }); | ||
| 74 | 294 | ||
| 75 | for ( string line; getline(cin, line); ) { | 295 | readFromPort(); |
| 76 | if ( line == "ser?" ) { | 296 | }); |
| 77 | send("<GETSERIAL>>"); | 297 | }; |
| 78 | } | 298 | |
| 79 | if ( line == "ver?" ) { | 299 | readFromPort(); |
| 80 | send("<GETVER>>"); | 300 | |
| 81 | } | 301 | context.run(); |
| 82 | if ( line == "date?" ) { | 302 | }); |
| 83 | send("<GETDATETIME>>"); | 303 | |
| 84 | } | 304 | state = WAIT_HB; |
| 85 | if ( line == "hb!" ) { | 305 | break; |
| 86 | send("<HEARTBEAT1>>"); | ||
| 87 | } | ||
| 88 | 306 | ||
| 89 | if ( line == "quit" ) { | 307 | case WAIT_HB: |
| 90 | break; | 308 | for ( string line; getline(cin, line); ) { |
| 309 | cout << "Heartbeat> "; | ||
| 310 | if ( line == "stop" ) { | ||
| 311 | break; | ||
| 312 | } | ||
| 313 | if ( !line.empty() ) { | ||
| 314 | cerr << "unbekanntes command: " << line << '\n'; | ||
| 315 | } | ||
| 91 | } | 316 | } |
| 317 | state = STOP_HB; | ||
| 318 | break; | ||
| 319 | |||
| 320 | case STOP_HB: | ||
| 321 | port.cancel(); | ||
| 322 | context.stop(); | ||
| 323 | thread.join(); | ||
| 324 | context.restart(); | ||
| 325 | |||
| 326 | state = SYNC; | ||
| 327 | break; | ||
| 328 | |||
| 329 | case CLOSE: | ||
| 330 | port.close(); | ||
| 331 | state = QUIT; | ||
| 332 | break; | ||
| 333 | |||
| 334 | case QUIT: | ||
| 335 | break; | ||
| 92 | } | 336 | } |
| 337 | } | ||
| 338 | |||
| 339 | return EXIT_SUCCESS; | ||
| 340 | } | ||
| 341 | |||
| 342 | } // namespace | ||
| 93 | 343 | ||
| 94 | port.cancel(); | 344 | int |
| 95 | context.stop(); | 345 | main() |
| 96 | port.close(); | 346 | { |
| 347 | try { | ||
| 348 | return run(); | ||
| 97 | } | 349 | } |
| 98 | catch ( asio::system_error& e ) { | 350 | catch ( asio::system_error& e ) { |
| 99 | cerr << e.what() << '\n'; | 351 | cerr << e.what() << '\n'; |
