Rewrite on libavr
Same controller: thermistor on ADC0 averaged over 1000 free-running conversions, 50 kHz fan PWM on OC0B, 115200 Bd console with the full command set, EEPROM temperature histogram, watchdog-reset path into the boot section. The Steinhart-Hart math and the libm log are gone — the Beta equation and the cubic fan curve are consteval-evaluated into flash tables; the firmware never does floating point. Byte-identical .text in both libavr modes. Legacy stays on master. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
173
src/terminal.hpp
Normal file
173
src/terminal.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include <libavr/libavr.hpp>
|
||||
|
||||
#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<std::uint32_t>(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<std::uint32_t>(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<std::uint8_t>((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<std::uint8_t>(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<char>(*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
|
||||
Reference in New Issue
Block a user