#pragma once #include #include #include #include "board.hpp" #include "bootloader.hpp" #include "controller.hpp" #include "curve.hpp" #include "statistics.hpp" // The serial console: line-buffered commands over the hardware UART. // `help` lists everything; `monitor` streams until any key. namespace app { class terminal { static constexpr std::uint8_t line_max = 24; static inline char line[line_max]{}; static inline std::uint8_t at = 0; static inline bool monitoring = false; static inline std::uint64_t last_monitor = 0; static void prompt() { serial << "> "_P; } static void show() { serial << "temperature "_P << controller::temperature_quarters() / 4 << '.' << (controller::temperature_quarters() % 4) * 25 << " C, adc "_P << controller::last_adc() << ", fan "_P << controller::fan_percent() << " %, "_P; if (controller::automatic()) serial << "auto"_P; else serial << "manual"_P; serial << "\r\n"_P; } static void print_curve() { for (std::int8_t t = 15; t <= 60; t += 5) serial << t << " C -> "_P << curve::duty(t) << " %\r\n"_P; } static void print_uptime() { auto seconds = static_cast(uptime::millis() / 1000); serial << seconds / 86400 << "d "_P << (seconds / 3600) % 24 << "h "_P << (seconds / 60) % 60 << "m "_P << seconds % 60 << "s\r\n"_P; } static void print_statistics() { serial << "min "_P << statistics::min_temperature() << " C, max "_P << statistics::max_temperature() << " C, samples "_P << static_cast(statistics::total_samples()) << "\r\n"_P; } static void print_histogram() { auto highest = statistics::highest_bucket(); if (highest == 0) { serial << "empty\r\n"_P; return; } for (std::uint8_t t = statistics::min_temperature(); t <= statistics::max_temperature(); ++t) { serial << t << " C |"_P; auto width = static_cast((statistics::bucket(t) * 40) / highest); for (std::uint8_t i = 0; i < width; ++i) serial << '#'; serial << ' ' << statistics::bucket(t) << "\r\n"_P; } } static void help() { serial << "help show curve monitor uptime statistics histogram reset set <0-100> auto version bootloader\r\n"_P; } static void dispatch(std::string_view cmd) { if (cmd.empty()) { } else if (cmd == "help") { help(); } else if (cmd == "show") { show(); } else if (cmd == "curve") { print_curve(); } else if (cmd == "monitor") { monitoring = true; } else if (cmd == "uptime") { print_uptime(); } else if (cmd == "statistics") { print_statistics(); } else if (cmd == "histogram") { print_histogram(); } else if (cmd == "reset") { statistics::reset(); serial << "statistics cleared\r\n"_P; } else if (cmd == "auto") { controller::set_automatic(); serial << "auto\r\n"_P; } else if (cmd == "version") { serial << "fantemp on libavr\r\n"_P; } else if (cmd == "bootloader") { serial << "entering bootloader\r\n"_P; statistics::save(); bootloader::enter(); } else if (cmd.starts_with("set ")) { std::uint8_t percent = 0; bool valid = cmd.size() > 4; for (std::size_t i = 4; i < cmd.size(); ++i) { if (cmd[i] < '0' || cmd[i] > '9') { valid = false; break; } percent = static_cast(percent * 10 + (cmd[i] - '0')); } if (valid && percent <= 100) { controller::set_manual(percent); serial << "fan "_P << percent << " %\r\n"_P; } else { serial << "set 0..100\r\n"_P; } } else { serial << "? (help)\r\n"_P; } } public: static void init() { serial << "\r\nfantemp on libavr — help for commands\r\n"_P; prompt(); } static void poll() { if (monitoring) { if (uptime::millis() >= last_monitor + 1000) { show(); last_monitor = uptime::millis(); } if (serial_t::read()) { // any key stops monitoring = false; prompt(); } return; } while (auto in = serial_t::read()) { char c = static_cast(*in); if (c == '\r' || c == '\n') { serial << "\r\n"_P; dispatch(std::string_view{line, at}); at = 0; if (!monitoring) prompt(); } else if (c == 0x7f || c == 0x08) { if (at) { --at; serial << "\b \b"_P; } } else if (at < line_max && c >= ' ') { line[at++] = c; serial << c; // echo } } } }; } // namespace app