fantemp/fantemp/terminal.hpp

195 lines
4.8 KiB
C++

#pragma once
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#include "flash/flash.hpp"
#include "controller.hpp"
GF(ENDL, "\r\n");
GF(HELP_CMD, "help");
GF(SHOW_CMD, "show");
GF(CURVE_CMD, "curve");
GF(MONITOR_CMD, "monitor");
GF(BOOTLOADER_CMD, "bootloader");
constexpr auto BACKSPACE = uint8_t{0x7f};
constexpr auto CTRL_C = uint8_t{0x03};
template <class Uart>
class Terminal {
public:
static void init()
{
m_serial.init();
m_serial << ENDL << F("FanTemp Control Panel") << ENDL << ENDL << F("$ ");
}
static void callback()
{
uint8_t inputByte;
bool handleInput = false;
while (m_serial.rxByte(inputByte)) {
if (isprint(inputByte) || inputByte == CTRL_C) {
m_inputBuffer[m_inputSize++] = inputByte;
// Handle Ctrl + C
if (inputByte == CTRL_C) {
m_serial << F("^C") << ENDL;
handleInput = true;
break;
}
// Echo
else {
m_serial << static_cast<char>(inputByte);
}
}
// Handle backspace
if (inputByte == BACKSPACE && m_inputSize > 0) {
m_serial << F("\b \b");
--m_inputSize;
}
// Handle line terminator
else if (inputByte == '\r' || inputByte == '\n') {
// Consume possible second line terminator
if (m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
m_serial.rxByte(inputByte);
}
m_serial << ENDL;
handleInput = true;
break;
}
if (m_inputSize >= INPUT_BUFFER_SIZE) {
m_serial << ENDL << F("WARNING: Terminal input buffer overflow!") << ENDL;
handleInput = true;
break;
}
}
if (handleInput) {
parseInput();
m_inputSize = 0;
m_serial << F("$ ");
}
if (m_state == State::MONITOR)
showState();
}
private:
static constexpr auto INPUT_BUFFER_SIZE = 128;
enum class State {
NONE,
MONITOR,
};
static Uart m_serial;
static char m_inputBuffer[INPUT_BUFFER_SIZE];
static uint16_t m_inputSize;
static State m_state;
static void parseInput()
{
if (m_inputSize) {
if (m_inputBuffer[m_inputSize - 1] == CTRL_C) {
handleCtrlC();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(HELP_CMD), m_inputSize) == 0) {
printHelp();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(SHOW_CMD), m_inputSize) == 0) {
showState();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(CURVE_CMD), m_inputSize) == 0) {
printCurve();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(MONITOR_CMD), m_inputSize) == 0) {
m_state = State::MONITOR;
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(BOOTLOADER_CMD), m_inputSize) == 0) {
handleBootloader();
} else {
printUnknown();
}
}
}
static void handleCtrlC()
{
m_serial << F("Abort!") << ENDL;
m_state = State::NONE;
}
static void printHelp()
{
m_serial << F("FanTemp command overview: ") << ENDL;
m_serial << HELP_CMD << F(" .......: print this help message") << ENDL;
m_serial << SHOW_CMD << F(" .......: shows current temperature and fan speed") << ENDL;
m_serial << CURVE_CMD << F(" ......: shows the curve used to map temperature to fan speed") << ENDL;
m_serial << MONITOR_CMD << F(" ....: loops the show command until Ctrl + C is pressed") << ENDL;
m_serial << BOOTLOADER_CMD << F(" .: enters the bootloader after 10 seconds") << ENDL;
}
static void showState()
{
char floatBuffer[16];
sprintf(floatBuffer, "%.2f", Controller::m_adcSample);
m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_resistance);
m_serial << F("Resistance ..: ") << floatBuffer << F(" Ohm") << ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_temperature);
m_serial << F("Temperature .: ") << floatBuffer << F(" C") << ENDL;
m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%") << ENDL;
}
static void printCurve()
{
for (uint8_t i = 10; i <= 60; ++i) {
m_serial << i << F(" C = ");
m_serial.template txNumber<uint8_t, 10, 3, ' '>(Controller::mapTemperature(i));
m_serial << F("%\t");
for (uint8_t s = 0; s < Controller::mapTemperature(i); ++s) {
m_serial << "#";
}
m_serial << ENDL;
}
}
static void handleBootloader()
{
for (int8_t i = 10; i >= 0; --i) {
m_serial << i << ENDL;
_delay_ms(1000);
}
m_serial << F("Entering bootloader...") << ENDL;
m_serial.flushTx();
_delay_ms(1000);
Bootloader::enter();
}
static void printUnknown()
{
m_serial << F("Unknown command \"");
for (uint16_t i = 0; i < m_inputSize; ++i)
m_serial << static_cast<char>(m_inputBuffer[i]);
m_serial << F("\"") << ENDL;
}
};
template <class Uart>
char Terminal<Uart>::m_inputBuffer[INPUT_BUFFER_SIZE];
template <class Uart>
uint16_t Terminal<Uart>::m_inputSize = 0;
template <class Uart>
typename Terminal<Uart>::State Terminal<Uart>::m_state = State::NONE;