we're getttng C to work
C deez nuts
This commit is contained in:
@@ -1,154 +0,0 @@
|
||||
#include "LCDDisplay.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <log.hpp>
|
||||
#include <string>
|
||||
|
||||
/* lcd size hardcoded to 4x40 idgaf */
|
||||
LCDDisplay::LCDDisplay(const std::string& dev, const uint32_t baud) {
|
||||
this->dev = dev;
|
||||
this->baud = baud;
|
||||
}
|
||||
|
||||
LCDDisplay::~LCDDisplay() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
int LCDDisplay::Init() {
|
||||
if (const int ret = this->open(); ret < 0) {
|
||||
this->close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct termios term{};
|
||||
// Setup the serial port 8 data bits, no parity, 1 stopbit, no flow control
|
||||
term.c_cflag = baud | CS8 | CSTOPB | CLOCAL | CREAD;
|
||||
term.c_iflag = 0;
|
||||
term.c_oflag = 0;
|
||||
term.c_lflag = 0;
|
||||
tcflush(lcd, TCIFLUSH);
|
||||
tcsetattr(lcd, TCSANOW, &term);
|
||||
|
||||
// init linebuf to spaces
|
||||
this->clearBuf();
|
||||
|
||||
// disable underline and block cursor
|
||||
constexpr uint8_t cmdbuf[] = {254, 75, 254, 84};
|
||||
::write(lcd, &cmdbuf, sizeof(cmdbuf));
|
||||
|
||||
// clear display and set cursor home
|
||||
this->clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LCDDisplay::open(const bool reopen) {
|
||||
applog(LOG::INF, "Opening serial port at \"{}\"", dev);
|
||||
if (lcd < 0 || reopen)
|
||||
lcd = ::open(dev.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); // ::open calls the global open() instead of LCDDisplay::open()
|
||||
if (lcd < 0) {
|
||||
applog(LOG::ERR, "Error opening serial port \"{}\"", dev); // err
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LCDDisplay::close() {
|
||||
if (lcd >= 0) {
|
||||
::close(lcd);
|
||||
applog(LOG::INF, "LCD port closed");
|
||||
}
|
||||
lcd = -1;
|
||||
}
|
||||
|
||||
void LCDDisplay::write(const uint8_t* cmdbuf, const uint8_t len) {
|
||||
if (::write(lcd, cmdbuf, len) != len) {
|
||||
applog(LOG::ERR, "failed to execute ::write, lost access to port?");
|
||||
this->open(true);
|
||||
}
|
||||
}
|
||||
|
||||
void LCDDisplay::clear() {
|
||||
constexpr uint8_t cmdbuf[] = {254, 88};
|
||||
this->write(cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
void LCDDisplay::setContrast(const uint8_t contrast) {
|
||||
applog(LOG::INF, "Set contrast to {}", contrast);
|
||||
const uint8_t cmdbuf[] = {254, 80, contrast};
|
||||
this->write(cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
void LCDDisplay::setBacklight(const bool enabled) {
|
||||
applog(LOG::INF, "Set backlight to {}", enabled);
|
||||
const uint8_t cmdbuf[] = {254, static_cast<uint8_t>(enabled ? 66 : 70), 255};
|
||||
this->write(cmdbuf, enabled ? 3 : 2);
|
||||
}
|
||||
|
||||
/* still doesnt work? */
|
||||
void LCDDisplay::setBacklight(const uint8_t brightness) {
|
||||
applog(LOG::INF, "Set backlight brightness to {}", brightness);
|
||||
const uint8_t cmdbuf[] = {254, static_cast<uint8_t>(brightness ? 66 : 70), 255, brightness}; // 3rd char is timer, 4th is brightness
|
||||
this->write(cmdbuf, brightness ? 4 : 2);
|
||||
}
|
||||
|
||||
void LCDDisplay::setCursor(const uint8_t row, const uint8_t col) {
|
||||
const uint8_t cmdbuf[] = {254, 71, static_cast<uint8_t>(col + 1), static_cast<uint8_t>(row + 1)};
|
||||
this->write(cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
/* reconsider so im not writing every time. prepend custom chars to the write buffer? */
|
||||
void LCDDisplay::setCustomChar(const uint8_t index, const std::array<uint8_t, 8> cchar) {
|
||||
uint8_t cmdbuf[] = {254, 78, index, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
std::memcpy(&cmdbuf[3], &cchar, sizeof(cchar));
|
||||
this->write(cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
/* immediately writes string to the lcd at current cursor pos */
|
||||
void LCDDisplay::writeStr(const std::string& str) {
|
||||
this->write(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
|
||||
}
|
||||
|
||||
/* immediately writes string to the lcd at given cursor pos */
|
||||
void LCDDisplay::writeStr(const uint8_t row, const uint8_t col, const std::string& str) {
|
||||
this->setCursor(row, col);
|
||||
this->write(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
|
||||
}
|
||||
|
||||
/* writes string to write buffer at calculated pos */
|
||||
void LCDDisplay::writeBuf(const uint8_t row, const uint8_t col, const std::string& str) {
|
||||
const size_t len = std::min(str.size(), writebuf.size() - col);
|
||||
uint8_t pos = 4 + col + (LCD_COLS * (row % 2)); // skip 4 bytes to leave room for set cursor command
|
||||
if (row >= 2) pos += 2 * LCD_COLS + 4; // if we're writing to disp2, skip another 4 bytes for set cursor command and 2*40 bytes of disp 1
|
||||
std::memcpy(&writebuf[pos], str.c_str(), len);
|
||||
}
|
||||
|
||||
/* initializes the write buffer to spaces */
|
||||
void LCDDisplay::clearBuf() {
|
||||
std::memset(writebuf.data(), ' ', writebuf.size());
|
||||
}
|
||||
|
||||
/* flushes writebuffer to lcd, hyperoptmized, flushes buffer and commands in a single write operation */
|
||||
void LCDDisplay::flushBuf() {
|
||||
constexpr uint8_t setDisp[] = {254, 71, 1, 1}; // set cursor to char 1, line 1
|
||||
std::memcpy(&writebuf[0], setDisp, 4);
|
||||
std::memcpy(&writebuf[2 * LCD_COLS + 4], setDisp, 4);
|
||||
writebuf[2 * LCD_COLS + 7] = 3; // overwrite set cursor c1 l1 to c1 l3
|
||||
this->write(reinterpret_cast<uint8_t*>(writebuf.data()), writebuf.size());
|
||||
}
|
||||
|
||||
// i have a whole flushbuf func to avoid too many writes, but im writing on every bar, need to rethink this.
|
||||
// we could have a separate pre-char buffer and put all the setCustomChar commands to that?
|
||||
char LCDDisplay::bar(const uint8_t char_id, const double value) {
|
||||
std::array<uint8_t, 8> b{};
|
||||
b.fill(0b01110);
|
||||
for (uint8_t i = 0; i < (1.0 - value) * 8; i++)
|
||||
b[i] = 0;
|
||||
this->setCustomChar(char_id, b);
|
||||
return cc(char_id);
|
||||
}
|
||||
209
src/app.cpp
209
src/app.cpp
@@ -1,209 +0,0 @@
|
||||
#include <LCDDisplay.hpp>
|
||||
#include <app.hpp>
|
||||
#include <chrono>
|
||||
#include <cpu.hpp>
|
||||
#include <csignal>
|
||||
#include <functional>
|
||||
#include <gpu.hpp>
|
||||
#include <hass.hpp>
|
||||
#include <log.hpp>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <string>
|
||||
#include <sys.hpp>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <utils.hpp>
|
||||
#include <vector>
|
||||
|
||||
Application::Application(Config config) {
|
||||
m_config = std::move(config);
|
||||
// set up signals
|
||||
sigemptyset(&m_control.sigset);
|
||||
sigaddset(&m_control.sigset, SIGINT);
|
||||
sigaddset(&m_control.sigset, SIGTERM);
|
||||
applog(LOG::START, "Application initialized");
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
auto level = LOG::START;
|
||||
if (m_ret < 0) level = LOG::WRN;
|
||||
applog(level, "Application destroyed");
|
||||
}
|
||||
|
||||
int Application::Run() {
|
||||
int sig;
|
||||
m_mainPid = getpid();
|
||||
pthread_sigmask(SIG_BLOCK, &m_control.sigset, nullptr);
|
||||
|
||||
{
|
||||
std::vector<std::jthread> threads;
|
||||
|
||||
std::function<void(std::stop_token)> funcs[] = {
|
||||
[this](const std::stop_token& st) { displayThread(st); },
|
||||
[this](const std::stop_token& st) { sensorsThread(st); },
|
||||
[this](const std::stop_token& st) { hassThread(st); },
|
||||
};
|
||||
|
||||
// start threads in order
|
||||
for (auto& f : funcs) {
|
||||
threads.emplace_back(f);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // start threads with delay so displayThread can quit the app if theres no lcd
|
||||
}
|
||||
|
||||
// waits for signal, the threads are stopped when we go out of scope
|
||||
sigwait(&m_control.sigset, &sig);
|
||||
applog(LOG::WRN, "got stop signal, shutting down...");
|
||||
}
|
||||
return m_ret;
|
||||
}
|
||||
|
||||
void Application::Crash(const int ret) {
|
||||
m_ret = ret;
|
||||
kill(m_mainPid, SIGINT);
|
||||
}
|
||||
|
||||
|
||||
void Application::hassThread(const std::stop_token& st) { //NOLINT
|
||||
applog(LOG::START, "Thread started");
|
||||
#ifdef ENABLE_HOME_ASSISTANT
|
||||
static double power;
|
||||
HomeAssistant::Config config;
|
||||
config.address = m_config.hass.url;
|
||||
config.entity = m_config.hass.entity;
|
||||
config.token = m_config.hass.token;
|
||||
HomeAssistant pc_power(config);
|
||||
|
||||
pc_power.connect();
|
||||
while (!st.stop_requested()) {
|
||||
pc_power.service();
|
||||
if (power != pc_power.state) {
|
||||
power = pc_power.state;
|
||||
{
|
||||
std::lock_guard lock(m_control.sensors);
|
||||
m_sensors.hass_state = power;
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
#else
|
||||
(void)st;
|
||||
applog(LOG::WRN, "feature disabled at compile time, probably no libwebsockets on host. exiting thread");
|
||||
{
|
||||
std::lock_guard lock(m_control.sensors);
|
||||
m_sensors.hass_state = -1.0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::sensorsThread(const std::stop_token& st) {
|
||||
applog(LOG::START, "Thread started, poll interval {} ms", m_config.lcd.refresh_interval_ms);
|
||||
int ret = cpu::detect(&m_sensors.cpu, m_config.cpu_pciaddr);
|
||||
ret += gpu::detect(&m_sensors.gpu, m_config.gpu_pciaddr);
|
||||
ret += sys::detect(&m_sensors.sys, m_config.sys_sensor);
|
||||
m_sensors.home.mountpoint = m_config.home_dir;
|
||||
if (ret < 0)
|
||||
applog(LOG::ERR, "Failed to detect some sensors");
|
||||
|
||||
while (!st.stop_requested()) {
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::lock_guard lock(m_control.sensors);
|
||||
|
||||
sys::getTimeDate(&m_sensors.td);
|
||||
getloadavg(m_sensors.loadavg, 3);
|
||||
sys::getRam(&m_sensors.ram);
|
||||
sys::getDisk(&m_sensors.home);
|
||||
sys::getNetwork(&m_sensors.net, m_config.sys_ifname);
|
||||
sys::updateSensors(&m_sensors.sys);
|
||||
cpu::update(&m_sensors.cpu);
|
||||
gpu::update(&m_sensors.gpu);
|
||||
}
|
||||
|
||||
// log(LOG::DBG, "took {} microseconds", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count());
|
||||
m_control.display.release(); // unlocks display loop
|
||||
std::this_thread::sleep_until(start + std::chrono::milliseconds(m_config.lcd.refresh_interval_ms));
|
||||
}
|
||||
applog(LOG::INF, "Thread stopped");
|
||||
}
|
||||
|
||||
void Application::displayThread(const std::stop_token& st) {
|
||||
std::stop_callback cb(st, [&] { m_control.display.release(); });
|
||||
applog(LOG::START, "Thread started");
|
||||
// init internal variables of LCDDisplay class
|
||||
LCDDisplay lcd(m_config.lcd.port);
|
||||
if (const int ret = lcd.Init(); ret < 0) {
|
||||
applog(LOG::ERR, "Failed to initialize LCDDisplay");
|
||||
this->Crash(ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// basic lcd setup
|
||||
lcd.setContrast(m_config.lcd.contrast);
|
||||
lcd.setBacklight(true);
|
||||
lcd.setCustomChar(0, {16, 6, 9, 8, 8, 9, 6, 0}); // celcius
|
||||
lcd.setCustomChar(1, {0, 0, 4, 10, 10, 17, 31, 0}); // delta
|
||||
lcd.setCustomChar(2, {0, 0, 8, 21, 21, 2, 0, 0}); // home (tilde)
|
||||
lcd.setCustomChar(3, {4, 14, 21, 4, 4, 4, 0}); // up arrow
|
||||
lcd.setCustomChar(4, {4, 4, 4, 21, 14, 4, 0}); // down arrow
|
||||
|
||||
// welcome msg
|
||||
lcd.writeBuf(2, 0, utils::center("Waiting for sensors...", 40));
|
||||
lcd.flushBuf();
|
||||
lcd.clearBuf();
|
||||
|
||||
// actual main display loop
|
||||
while (!st.stop_requested()) {
|
||||
m_control.display.try_acquire_for(std::chrono::milliseconds(static_cast<int>(m_config.lcd.refresh_interval_ms * 1.25))); // holds the loop until sensor data is ready, max 1.25x of the loop time
|
||||
if (st.stop_requested()) break; // needed, otherwise it wont quit correctly
|
||||
{
|
||||
std::lock_guard lock(m_control.sensors);
|
||||
|
||||
// prepare some tricky sensors
|
||||
std::string gpu_temps = std::to_string(m_sensors.gpu.temp_edge);
|
||||
if (m_sensors.gpu.temp_hotspot) {
|
||||
const auto delta = m_sensors.gpu.temp_hotspot - m_sensors.gpu.temp_edge;
|
||||
gpu_temps.append("/" + std::to_string(m_sensors.gpu.temp_hotspot));
|
||||
gpu_temps.insert(0, LCDDisplay::cc(1) + std::to_string(delta) + LCDDisplay::cc(0) + " ");
|
||||
}
|
||||
|
||||
uint16_t cpu_fan = 0;
|
||||
for (const auto& fan : m_sensors.sys.fan_speed)
|
||||
cpu_fan += fan;
|
||||
cpu_fan /= 2;
|
||||
|
||||
// write to display buffer
|
||||
lcd.writeBuf(0, 0, utils::pad(m_sensors.td.date, 11, utils::just::left) + " " + m_sensors.td.time + lcd.bar(7, m_sensors.gpu.usage_core / 100.0));
|
||||
lcd.writeBuf(0, 26, utils::pNum(m_sensors.loadavg[0], m_sensors.loadavg[0] > 10 ? 1 : 2));
|
||||
lcd.writeBuf(0, 31, utils::pNum(m_sensors.loadavg[1], m_sensors.loadavg[1] > 10 ? 1 : 2));
|
||||
lcd.writeBuf(0, 36, utils::pNum(m_sensors.loadavg[2], m_sensors.loadavg[2] > 10 ? 1 : 2));
|
||||
|
||||
lcd.writeBuf(1, 0, utils::pad(std::to_string(m_sensors.cpu.tctl) + "/" + std::to_string(m_sensors.cpu.tccd1) + LCDDisplay::cc(0), 8, utils::just::left));
|
||||
lcd.writeBuf(1, 7, utils::pad(utils::pBytes(m_sensors.ram.used, true), 6, utils::just::left));
|
||||
if (m_sensors.hass_state >= 0)
|
||||
lcd.writeBuf(1, 13, utils::pad(utils::pNum(m_sensors.hass_state) + "W", 4));
|
||||
lcd.writeBuf(1, 19, std::to_string(m_sensors.sys.t_sensor) + LCDDisplay::cc(0)); // t_sensor temp
|
||||
lcd.writeBuf(1, 32 - gpu_temps.size(), utils::pad(utils::pBytes(m_sensors.gpu.vram_used), 6));
|
||||
lcd.writeBuf(1, 39 - gpu_temps.size(), gpu_temps + LCDDisplay::cc(0));
|
||||
|
||||
lcd.writeBuf(2, 0, utils::pad(std::to_string(static_cast<int>(m_sensors.cpu.ptotal)) + "W", 8, utils::just::left));
|
||||
lcd.writeBuf(2, m_sensors.cpu.ptotal < 100 ? 4 : 5, std::to_string(cpu_fan) + "R"); // avg fan speed
|
||||
lcd.writeBuf(2, 12, LCDDisplay::cc(3) + utils::pBytes(m_sensors.net.upload));
|
||||
lcd.writeBuf(2, 19, LCDDisplay::cc(4) + utils::pBytes(m_sensors.net.download));
|
||||
lcd.writeBuf(2, 26, utils::pad(std::to_string(m_sensors.gpu.fan_speed) + "R", 5));
|
||||
lcd.writeBuf(2, 31, utils::pad(std::to_string(m_sensors.gpu.power_avg) + "/" + std::to_string(m_sensors.gpu.power_cap) + "W", 9));
|
||||
|
||||
lcd.writeBuf(3, 0, std::to_string(m_sensors.cpu.avg_freq) + "MHz");
|
||||
lcd.writeBuf(3, 14, utils::pad(LCDDisplay::cc(2) + utils::pBytes(m_sensors.home.usage.free), 6));
|
||||
lcd.writeBuf(3, 21, utils::pad(std::to_string(m_sensors.gpu.clock_core) + "/" + std::to_string(m_sensors.gpu.clock_mem) + "MHz ", 13));
|
||||
lcd.writeBuf(3, 34, utils::pad(utils::pNum(m_sensors.gpu.vcore, 3) + "V", 5));
|
||||
}
|
||||
|
||||
lcd.flushBuf();
|
||||
lcd.clearBuf();
|
||||
}
|
||||
|
||||
// goodbye msg
|
||||
lcd.writeBuf(2, 0, utils::center("Goodbye", 40));
|
||||
lcd.flushBuf();
|
||||
}
|
||||
72
src/cpu.cpp
72
src/cpu.cpp
@@ -1,72 +0,0 @@
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cpu.hpp>
|
||||
#include <cstdint>
|
||||
#include <log.hpp>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utils.hpp>
|
||||
|
||||
namespace cpu {
|
||||
|
||||
int detect(cpuSensors* target, const std::string& pci) {
|
||||
const std::string path = "/sys/bus/pci/devices/" + pci + "/hwmon";
|
||||
|
||||
try {
|
||||
target->hwmon = utils::getFirstDir(path);
|
||||
} catch (...) {
|
||||
applog(LOG::ERR, "NO CPU HWMON KERNEL MODULE DETECTED");
|
||||
return -1;
|
||||
}
|
||||
|
||||
target->module = utils::fileExists(target->hwmon + "/in1_input") ? zenpower : k10temp; // idk if this is reliable, we're trying to avoid just reading the name
|
||||
target->num_cores = std::thread::hardware_concurrency();
|
||||
applog(LOG::INF, "Detected {} cores, module enum #{} in path \"{}\"", target->num_cores, static_cast<uint8_t>(target->module), target->hwmon);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void update(cpuSensors* c) {
|
||||
static std::array<char, 32> buf;
|
||||
struct EnergyTs {
|
||||
uint64_t energy = 0;
|
||||
std::chrono::time_point<std::chrono::steady_clock> ts;
|
||||
};
|
||||
|
||||
static EnergyTs prev;
|
||||
EnergyTs curr;
|
||||
|
||||
// read power from intel-rapl? works on amd too, btop uses this trick, seems accurate
|
||||
// it tracks with "socket power" in hwinfo on windows, its actually the same thing i had in lcdsmartie
|
||||
curr.ts = std::chrono::steady_clock::now();
|
||||
utils::fastReadNum("/sys/class/powercap/intel-rapl:0/energy_uj", buf, curr.energy);
|
||||
c->ptotal = static_cast<float>(curr.energy - prev.energy) / static_cast<float>(std::chrono::duration_cast<std::chrono::microseconds>(curr.ts - prev.ts).count());
|
||||
prev = curr;
|
||||
|
||||
// lots of reads but still faster than reading /proc/cpuinfo
|
||||
c->avg_freq = 0;
|
||||
for (uint8_t i = 0; i < c->num_cores; i++) {
|
||||
uint32_t t = 0;
|
||||
utils::fastReadNum<1000>("/sys/devices/system/cpu/cpu" + std::to_string(i) + "/cpufreq/cpuinfo_avg_freq", buf, t);
|
||||
c->avg_freq += t;
|
||||
}
|
||||
c->avg_freq /= (c->num_cores > 1) ? c->num_cores : 1;
|
||||
|
||||
if (c->hwmon.empty())
|
||||
return;
|
||||
|
||||
// commented out unused values so its faster
|
||||
if (c->module == zenpower) {
|
||||
utils::fastReadNum<1000>(c->hwmon + "/temp1_input", buf, c->tdie);
|
||||
utils::fastReadNum<1000>(c->hwmon + "/temp2_input", buf, c->tctl);
|
||||
utils::fastReadNum<1000>(c->hwmon + "/temp3_input", buf, c->tccd1);
|
||||
// utils::fastReadNum<1000>(c->hwmon + "/in1_input", buf, c->vcore);
|
||||
// utils::fastReadNum<1000>(c->hwmon + "/in2_input", buf, c->vsoc);
|
||||
}
|
||||
|
||||
if (c->module == k10temp) {
|
||||
utils::fastReadNum<1000>(c->hwmon + "/temp1_input", buf, c->tctl);
|
||||
utils::fastReadNum<1000>(c->hwmon + "/temp3_input", buf, c->tccd1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
123
src/gpu.cpp
123
src/gpu.cpp
@@ -1,123 +0,0 @@
|
||||
#include <array>
|
||||
#include <gpu.hpp>
|
||||
#include <gpu_metrics.hpp>
|
||||
#include <log.hpp>
|
||||
#include <string>
|
||||
#include <utils.hpp>
|
||||
|
||||
/* maybe have a read pp_table function */
|
||||
/* would be neat to wrap it in a class but eh namespace is good enough */
|
||||
|
||||
namespace gpu {
|
||||
|
||||
const std::string rel_metrics_path = "/../../gpu_metrics"; // gpu_metrics path relative to hwmon path
|
||||
constexpr uint16_t max_metrics_length = 512; // length of the metrics buffer
|
||||
|
||||
/* takes pointer to a target gpu::sensors struct and updates its hwmon path and detects gpu_metrics */
|
||||
int detect(gpuSensors* target, const std::string& pci) {
|
||||
try {
|
||||
target->hwmon = utils::getFirstDir("/sys/bus/pci/devices/" + pci + "/hwmon");
|
||||
} catch (...) {
|
||||
applog(LOG::ERR, "Couldn't find GPU hwmon");
|
||||
return -1;
|
||||
}
|
||||
applog(LOG::INF, "Detected path \"{}\"", target->hwmon);
|
||||
|
||||
if (utils::fileExists(target->hwmon + rel_metrics_path)) {
|
||||
std::array<char, max_metrics_length> raw_metrics{};
|
||||
if (!utils::readFile(target->hwmon + rel_metrics_path, raw_metrics))
|
||||
return -1;
|
||||
const auto header = reinterpret_cast<metrics::metrics_table_header*>(raw_metrics.data());
|
||||
|
||||
applog(LOG::INF, "Found gpu_metrics_v{}_{}", header->format_revision, header->content_revision);
|
||||
target->hasGpuMetrics = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* some metrics are missing in gpu_metrics, we always fill them from fs, unused metrics are commented out */
|
||||
void fillMissingMetrics(gpuSensors* g) {
|
||||
static std::array<char, 32> buf;
|
||||
utils::fastReadNum<1000>(g->hwmon + "/in0_input", buf, g->vcore);
|
||||
// g->fan_pwm = std::stoi(readFile(g->hwmon + "/pwm1", 4)) * 100 / 256;
|
||||
utils::fastReadNum(g->hwmon + "/fan1_input", buf, g->fan_speed);
|
||||
utils::fastReadNum<1000000>(g->hwmon + "/power1_cap", buf, g->power_cap);
|
||||
// utils::fastReadNum(g->hwmon + "/power1_cap_max", g->power_cap_max) / 1000000;
|
||||
utils::fastReadNum(g->hwmon + "/../../mem_info_vram_used", buf, g->vram_used);
|
||||
// utils::fastReadNum(g->hwmon + "/../../mem_info_vram_total", g->vram_total);
|
||||
}
|
||||
|
||||
/* get sensors for pre-vega cards (rx480/580) */
|
||||
void updateFromFs(gpuSensors* g) {
|
||||
static std::array<char, 32> buf;
|
||||
utils::fastReadNum<1000000>(g->hwmon + "/freq1_input", buf, g->clock_core);
|
||||
utils::fastReadNum<1000000>(g->hwmon + "/freq2_input", buf, g->clock_mem);
|
||||
utils::fastReadNum<1000000>(g->hwmon + "/power1_input", buf, g->power_avg);
|
||||
utils::fastReadNum<1000>(g->hwmon + "/temp1_input", buf, g->temp_edge);
|
||||
utils::fastReadNum(g->hwmon + "/../../gpu_busy_percent", buf, g->usage_core);
|
||||
}
|
||||
|
||||
/* get sensors for vega and newer (rx5700xt, rx7800xt [*]) */
|
||||
void updateFromMetrics(gpuSensors* g) {
|
||||
std::array<char, max_metrics_length> raw_metrics{};
|
||||
if (!utils::readFile(g->hwmon + rel_metrics_path, raw_metrics)) return;
|
||||
|
||||
switch (const auto header = reinterpret_cast<metrics::metrics_table_header*>(raw_metrics.data()); header->format_revision * 10 + header->content_revision) {
|
||||
/* rx5700xt, rx7800xt */
|
||||
case 13: {
|
||||
const auto metrics = reinterpret_cast<metrics::gpu_metrics_v1_3*>(raw_metrics.data());
|
||||
g->usage_core = metrics->average_gfx_activity;
|
||||
g->usage_umc = metrics->average_umc_activity;
|
||||
g->usage_media = metrics->average_mm_activity;
|
||||
|
||||
g->temp_edge = metrics->temperature_edge;
|
||||
g->temp_hotspot = metrics->temperature_hotspot;
|
||||
g->temp_mem = metrics->temperature_mem;
|
||||
|
||||
g->temp_vrm_gfx = metrics->temperature_vrgfx;
|
||||
g->temp_vrm_soc = metrics->temperature_vrsoc;
|
||||
g->temp_vrm_mem = metrics->temperature_vrmem;
|
||||
g->power_avg = metrics->average_socket_power;
|
||||
g->fan_speed = metrics->current_fan_speed;
|
||||
|
||||
// hacks for rx7800xt
|
||||
// g->clock_core = metrics->average_vclk1_frequency; // why not gfxclock?
|
||||
// g->clock_mem = metrics->average_gfxclk_frequency; // why gfxclock? min mem clock seems to be 96mhz, this is probably including time sleeping
|
||||
|
||||
// hacks for rx5700xt
|
||||
g->clock_core = metrics->average_gfxclk_frequency;
|
||||
g->clock_mem = metrics->average_uclk_frequency * 2; // mem controller clock, actual mem is 2x that
|
||||
break;
|
||||
}
|
||||
|
||||
/* ryzen 5 5600g */
|
||||
case 22: {
|
||||
const auto metrics = reinterpret_cast<metrics::gpu_metrics_v2_2*>(raw_metrics.data());
|
||||
g->temp_edge = metrics->temperature_gfx / 100;
|
||||
break;
|
||||
}
|
||||
|
||||
/* werid hardware i probably dont like, maybe fallback to reading from fs? */
|
||||
default: {
|
||||
applog(LOG::WRN, "Unsupported gpu_metrics_v{}_{}", header->format_revision, header->content_revision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reads values from system and updates the target gpu::sensors struct */
|
||||
void update(gpuSensors* g) {
|
||||
const bool hwmon_valid = !g->hwmon.empty();
|
||||
if (hwmon_valid)
|
||||
fillMissingMetrics(g);
|
||||
|
||||
if (g->hasGpuMetrics)
|
||||
updateFromMetrics(g);
|
||||
else
|
||||
if (hwmon_valid)
|
||||
updateFromFs(g);
|
||||
|
||||
if (g->fan_speed > 9999) g->fan_speed = 9999; // sanity check fan speed
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
163
src/hass.cpp
163
src/hass.cpp
@@ -1,163 +0,0 @@
|
||||
#ifdef ENABLE_HOME_ASSISTANT
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <hass.hpp>
|
||||
#include <log.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
HomeAssistant::HomeAssistant(const Config& config) {
|
||||
m_config = config;
|
||||
}
|
||||
|
||||
HomeAssistant::~HomeAssistant() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void HomeAssistant::event_handler(lws* wsi, const lws_callback_reasons reason, char* in) {
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
|
||||
applog(LOG::ERR, "websocket conn error: {}", in ? in : "(null)");
|
||||
return;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE: {
|
||||
nlohmann::json json = nlohmann::json::parse(in);
|
||||
const std::string ha_type = json["type"].get<std::string>();
|
||||
|
||||
if (ha_type == "auth_required") {
|
||||
json.clear();
|
||||
json["type"] = "auth";
|
||||
json["access_token"] = m_config.token;
|
||||
tx = json.dump();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ha_type == "auth_ok") {
|
||||
applog(LOG::INF, "subscribing to entity \"{}\"", m_config.entity);
|
||||
json.clear();
|
||||
json["id"] = id++;
|
||||
json["type"] = "subscribe_entities";
|
||||
json["entity_ids"][0] = m_config.entity.c_str();
|
||||
tx = json.dump();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ha_type == "result") {
|
||||
if (json["success"].get<bool>() != true)
|
||||
applog(LOG::ERR, "api said success was \"false\" on message id {}", id - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ha_type == "event") {
|
||||
json = json["event"];
|
||||
if (json.contains("a"))
|
||||
state = std::stof(json["a"][m_config.entity]["s"].get<std::string>());
|
||||
if (json.contains("c"))
|
||||
state = std::stof(json["c"][m_config.entity]["+"]["s"].get<std::string>());
|
||||
break;
|
||||
}
|
||||
|
||||
applog(LOG::WRN, "unhandled event: {}", static_cast<char*>(in));
|
||||
break;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
|
||||
applog(LOG::INF, "Websocket connected");
|
||||
break;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_CLIENT_CLOSED: {
|
||||
applog(LOG::INF, "Websocket closed");
|
||||
return;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_CLIENT_WRITEABLE: {
|
||||
/* skip whole callback if there's no tx to send */
|
||||
if (!tx.empty()) {
|
||||
const size_t msgLen = LWS_PRE + tx.size();
|
||||
auto* buf = static_cast<unsigned char*>(malloc(msgLen));
|
||||
if (buf) {
|
||||
memcpy(&buf[LWS_PRE], tx.c_str(), tx.size());
|
||||
lws_write(wsi, &buf[LWS_PRE], tx.size(), LWS_WRITE_TEXT);
|
||||
tx = "";
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int HomeAssistant::callback(lws* wsi, const lws_callback_reasons reason, void* user, void* in, size_t) {
|
||||
auto* self = static_cast<HomeAssistant*>(user);
|
||||
self->event_handler(wsi, reason, static_cast<char*>(in));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HomeAssistant::connect() {
|
||||
lws_client_connect_info i{};
|
||||
lws_context_creation_info info{};
|
||||
|
||||
lws_set_log_level(LLL_ERR | LLL_WARN, lwsl_emit_syslog);
|
||||
|
||||
std::memset(&i, 0, sizeof(i));
|
||||
std::memset(&info, 0, sizeof info);
|
||||
|
||||
static constexpr lws_protocols protocols[] = {
|
||||
{"lws-minimal-client", callback, sizeof(void*), 0, 0, nullptr, 0},
|
||||
LWS_PROTOCOL_LIST_TERM};
|
||||
|
||||
static constexpr lws_extension extensions[] = {
|
||||
{"permessage-deflate",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"permessage-deflate"
|
||||
"; client_no_context_takeover"
|
||||
"; client_max_window_bits"},
|
||||
{nullptr, nullptr, nullptr}};
|
||||
|
||||
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
||||
info.port = CONTEXT_PORT_NO_LISTEN;
|
||||
info.protocols = protocols;
|
||||
info.fd_limit_per_thread = 1 + 1 + 1;
|
||||
info.extensions = extensions;
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (!context) {
|
||||
applog(LOG::ERR, "error creating context");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i.context = context;
|
||||
i.port = 443;
|
||||
i.address = m_config.address.c_str();
|
||||
i.path = "/api/websocket";
|
||||
i.host = i.address;
|
||||
i.origin = i.address;
|
||||
i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_PRIORITIZE_READS;
|
||||
i.protocol = nullptr;
|
||||
i.userdata = this;
|
||||
i.local_protocol_name = "lws-minimal-client";
|
||||
|
||||
websock = lws_client_connect_via_info(&i);
|
||||
if (!websock) {
|
||||
applog(LOG::ERR, "error creating lws client");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HomeAssistant::service(const int timeout_ms) const {
|
||||
lws_callback_on_writable(websock);
|
||||
lws_service(context, timeout_ms);
|
||||
}
|
||||
|
||||
void HomeAssistant::disconnect() const {
|
||||
lws_context_destroy(context);
|
||||
}
|
||||
|
||||
#endif
|
||||
133
src/lcd.c
Normal file
133
src/lcd.c
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "log.h"
|
||||
#include "lcd.h"
|
||||
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
int lcd_open(struct lcd_t* lcd, const bool reopen) {
|
||||
applog(LOG_INF, "Opening serial port at %s", lcd->port);
|
||||
if (lcd->fd <= 0 || reopen)
|
||||
lcd->fd = open(lcd->port, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
if (lcd->fd <= 0) {
|
||||
applog(LOG_ERR, "Error opening serial port %s", lcd->port); // err
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void lcd_close(struct lcd_t* lcd) {
|
||||
if (lcd->fd >= 0) {
|
||||
close(lcd->fd);
|
||||
applog(LOG_INF, "LCD port closed");
|
||||
}
|
||||
lcd->fd = -1;
|
||||
}
|
||||
|
||||
void lcd_write(struct lcd_t* lcd, const uint8_t* cmdbuf, const uint8_t len) {
|
||||
if (!write(lcd->fd, cmdbuf, len)) {
|
||||
applog(LOG_ERR, "failed to execute write(), lost access to port?");
|
||||
lcd_open(lcd, true);
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_clear(struct lcd_t* lcd) {
|
||||
constexpr uint8_t cmdbuf[] = {254, 88};
|
||||
lcd_write(lcd, cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
void lcd_set_contrast(struct lcd_t* lcd, const uint8_t contrast) {
|
||||
applog(LOG_INF, "Set contrast to %d", contrast);
|
||||
const uint8_t cmdbuf[] = {254, 80, contrast};
|
||||
lcd_write(lcd, cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
void lcd_set_backlight(struct lcd_t* lcd, const bool enabled) {
|
||||
applog(LOG_INF, "Set backlight to %s", enabled ? "true" : "false");
|
||||
const uint8_t cmdbuf[] = {254, (enabled ? 66 : 70), 255};
|
||||
lcd_write(lcd, cmdbuf, enabled ? 3 : 2);
|
||||
}
|
||||
|
||||
void lcd_set_cursor(struct lcd_t* lcd, const uint8_t row, const uint8_t col) {
|
||||
const uint8_t cmdbuf[] = {254, 71, (col + 1), (row + 1)};
|
||||
lcd_write(lcd, cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
/* reconsider so im not writing every time. prepend custom chars to the write buffer? */
|
||||
void lcd_set_custom_char(struct lcd_t* lcd, const uint8_t index, const lcd_glyph_t* glyph) {
|
||||
uint8_t cmdbuf[] = {254, 78, index, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
memcpy(&cmdbuf[3], &glyph, sizeof(glyph));
|
||||
lcd_write(lcd, cmdbuf, sizeof(cmdbuf));
|
||||
}
|
||||
|
||||
/* immediately writes string to the lcd at current cursor pos */
|
||||
void lcd_write_str(struct lcd_t* lcd, const char* str, const uint8_t len) {
|
||||
lcd_write(lcd, (const uint8_t*)str, len);
|
||||
}
|
||||
|
||||
void lcd_write_str_pos(struct lcd_t* lcd, const char* str, const uint8_t len, const uint8_t posX, const uint8_t posY) {
|
||||
lcd_set_cursor(lcd, posX, posY);
|
||||
lcd_write(lcd, (const uint8_t*)str, len);
|
||||
}
|
||||
|
||||
/* writes string to write buffer at calculated pos, we trust it to be null terminated */
|
||||
void lcd_write_buf(struct lcd_t* lcd, const uint8_t row, const uint8_t col, const char* str) {
|
||||
size_t len = 0;
|
||||
while (str[len] != '\0')
|
||||
len++;
|
||||
|
||||
len = MIN(len, sizeof(lcd->writebuf) - col);
|
||||
uint8_t pos = 4 + col + (LCD_COLS * (row % 2)); // skip 4 bytes to leave room for set cursor command
|
||||
if (row >= 2) pos += 2 * LCD_COLS + 4; // if we're writing to disp2, skip another 4 bytes for set cursor command and 2*40 bytes of disp 1
|
||||
memcpy(&lcd->writebuf[pos], str, len);
|
||||
}
|
||||
|
||||
|
||||
void lcd_clear_buf(struct lcd_t* lcd) {
|
||||
memset(lcd->writebuf, ' ', sizeof(lcd->writebuf));
|
||||
}
|
||||
|
||||
/* flushes writebuffer to lcd, hyperoptmized, flushes buffer and commands in a single write operation */
|
||||
void lcd_flush_buf(struct lcd_t* lcd) {
|
||||
constexpr uint8_t set_disp[] = {254, 71, 1, 1}; // set cursor to char 1, line 1
|
||||
memcpy(&lcd->writebuf[0], set_disp, 4);
|
||||
memcpy(&lcd->writebuf[2 * LCD_COLS + 4], set_disp, 4);
|
||||
lcd->writebuf[2 * LCD_COLS + 7] = 3; // overwrite set cursor c1 l1 to c1 l3
|
||||
lcd_write(lcd, (const uint8_t*)lcd->writebuf, sizeof(lcd->writebuf));
|
||||
}
|
||||
|
||||
// i have a whole flushbuf func to avoid too many writes, but im writing on every bar, need to rethink this.
|
||||
// we could have a separate pre-char buffer and put all the setCustomChar commands to that?
|
||||
char lcd_bar(struct lcd_t* lcd, const uint8_t char_id, const double value) {
|
||||
uint8_t b[8];
|
||||
memset(b, 0b01110, sizeof(b));
|
||||
for (uint8_t i = 0; i < (1.0 - value) * 8; i++)
|
||||
b[i] = 0;
|
||||
lcd_set_custom_char(lcd, char_id, &b);
|
||||
return cc(char_id);
|
||||
}
|
||||
|
||||
int lcd_init(struct lcd_t* lcd) {
|
||||
const int ret = lcd_open(lcd, false);
|
||||
if (ret < 0) {
|
||||
lcd_close(lcd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
applog(LOG_INF, "ret %d, fd %d", ret, lcd->fd);
|
||||
|
||||
struct termios term = {};
|
||||
term.c_cflag = lcd->baud | CS8 | CLOCAL | CREAD;
|
||||
|
||||
tcflush(lcd->fd, TCIFLUSH);
|
||||
tcsetattr(lcd->fd, TCSANOW, &term);
|
||||
|
||||
lcd_clear_buf(lcd);
|
||||
constexpr uint8_t cmdbuf[] = {254, 75, 254, 84};
|
||||
lcd_write(lcd, cmdbuf, sizeof(cmdbuf));
|
||||
|
||||
lcd_clear(lcd);
|
||||
return 0;
|
||||
}
|
||||
95
src/log.c
Normal file
95
src/log.c
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "log.h"
|
||||
|
||||
#ifdef APPLOG_ENABLED
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef LOG_FILE_LOCATION
|
||||
#include <zlib.h>
|
||||
static pthread_mutex_t fmtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
#endif
|
||||
|
||||
static pthread_mutex_t cmtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* Replicates pFname: strips return type and parameters from __PRETTY_FUNCTION__
|
||||
* "void myFunc(int x)" → "myFunc"
|
||||
* "int other_func(void)" → "other_func"
|
||||
*/
|
||||
static void fmt_funcname(const char *pretty, char *out, size_t outsz) {
|
||||
const char *start = pretty;
|
||||
|
||||
const char *space = strchr(pretty, ' ');
|
||||
if (space) start = space + 1;
|
||||
|
||||
const char *paren = strrchr(start, '(');
|
||||
size_t len = paren ? (size_t)(paren - start) : strlen(start);
|
||||
if (len >= outsz) len = outsz - 1;
|
||||
|
||||
memcpy(out, start, len);
|
||||
out[len] = '\0';
|
||||
}
|
||||
|
||||
void log_write(const LogLevel level, const char *func, const char *fmt, ...) {
|
||||
static const char *reset = "\033[0m";
|
||||
const char *color;
|
||||
|
||||
switch (level) {
|
||||
case LOG_INF: color = "\033[1;32m"; break; /* bold green */
|
||||
case LOG_WRN: color = "\033[1;33m"; break; /* bold yellow */
|
||||
case LOG_ERR: color = "\033[1;31m"; break; /* bold red */
|
||||
case LOG_DBG: color = ""; break;
|
||||
case LOG_START: color = "\033[1;3;36m"; break; /* bold italic cyan */
|
||||
default: color = ""; break;
|
||||
}
|
||||
|
||||
char funcname[128];
|
||||
fmt_funcname(func, funcname, sizeof(funcname));
|
||||
|
||||
/* ── console ─────────────────────────────────────────────────────── */
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
pthread_mutex_lock(&cmtx);
|
||||
fprintf(stderr, "[%s%s%s] ", color, funcname, reset);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputc('\n', stderr);
|
||||
pthread_mutex_unlock(&cmtx);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#ifdef LOG_FILE_LOCATION
|
||||
/* ── file ────────────────────────────────────────────────────────── */
|
||||
{
|
||||
char timebuf[32];
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
struct tm tm_info;
|
||||
localtime_r(&ts.tv_sec, &tm_info);
|
||||
strftime(timebuf, sizeof(timebuf), "%x %H:%M:%S", &tm_info);
|
||||
|
||||
char msgbuf[1024];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int offset = snprintf(msgbuf, sizeof(msgbuf),
|
||||
"%s.%03ld [%s%s%s] ",
|
||||
timebuf, ts.tv_nsec / 1000000L,
|
||||
color, funcname, reset);
|
||||
vsnprintf(msgbuf + offset, sizeof(msgbuf) - offset, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
pthread_mutex_lock(&fmtx);
|
||||
gzFile file = gzopen(LOG_FILE_LOCATION, "ab");
|
||||
if (file) {
|
||||
gzprintf(file, "%s\n", msgbuf);
|
||||
gzclose(file);
|
||||
}
|
||||
pthread_mutex_unlock(&fmtx);
|
||||
}
|
||||
#endif /* LOG_FILE_LOCATION */
|
||||
}
|
||||
|
||||
#endif /* APPLOG_ENABLED */
|
||||
115
src/main.c
Normal file
115
src/main.c
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include "log.h"
|
||||
#include <unistd.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include "lcd.h"
|
||||
|
||||
atomic_bool g_running; // if using c11, you need atomic_store() and atomic_load();
|
||||
|
||||
struct control_t {
|
||||
pthread_mutex_t sensors_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_cond_t sensors_ready = PTHREAD_COND_INITIALIZER,
|
||||
|
||||
} control;
|
||||
|
||||
struct sensors_t {
|
||||
// sys::TimeDate td;
|
||||
double loadavg[3];
|
||||
// sys::Memory ram;
|
||||
// sys::Disk home;
|
||||
// sys::Network net;
|
||||
// sys::sysSensors sys;
|
||||
// cpu::cpuSensors cpu;
|
||||
// gpu::gpuSensors gpu;
|
||||
double hass_state;
|
||||
};
|
||||
|
||||
static void *display_thread(void *arg) {
|
||||
applog(LOG_START, "display thread started");
|
||||
struct sensors_t* sensors = arg;
|
||||
sensors->hass_state = 1;
|
||||
|
||||
struct lcd_t lcd = {
|
||||
.port = "/dev/serial/by-id/usb-Raspberry_Pi_Pico_E6616407E37AA12F-if00",
|
||||
.baud = 0010002, // from termios-baud.h, B115200
|
||||
};
|
||||
|
||||
lcd_init(&lcd);
|
||||
|
||||
// basic lcd setup
|
||||
lcd_set_contrast(&lcd, 215);
|
||||
lcd_set_backlight(&lcd, true);
|
||||
lcd_set_custom_char(&lcd, 0, &(lcd_glyph_t){16, 6, 9, 8, 8, 9, 6, 0}); // celcius
|
||||
lcd_set_custom_char(&lcd, 1, &(lcd_glyph_t){0, 0, 4, 10, 10, 17, 31, 0}); // delta
|
||||
lcd_set_custom_char(&lcd, 2, &(lcd_glyph_t){0, 0, 8, 21, 21, 2, 0, 0}); // home (tilde)
|
||||
lcd_set_custom_char(&lcd, 3, &(lcd_glyph_t){4, 14, 21, 4, 4, 4, 0}); // up arrow
|
||||
lcd_set_custom_char(&lcd, 4, &(lcd_glyph_t){4, 4, 4, 21, 14, 4, 0}); // down arrow
|
||||
|
||||
lcd_write_buf(&lcd, 2, 0, "Waiting for sensors..."); // need to center this still
|
||||
lcd_flush_buf(&lcd);
|
||||
lcd_clear_buf(&lcd);
|
||||
|
||||
while (g_running) { // NOLINT its updated globally bud
|
||||
;
|
||||
}
|
||||
|
||||
lcd_close(&lcd);
|
||||
applog(LOG_WRN, "display thread stopped");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void *sensors_thread(void *arg) {
|
||||
applog(LOG_START, "sensors thread started");
|
||||
struct sensors_t* sensors = arg;
|
||||
(void)sensors;
|
||||
applog(LOG_WRN, "sensors thread stopped");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
g_running = true;
|
||||
applog(LOG_START, "linuxSmartie started");
|
||||
|
||||
/* signal setup */
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset);
|
||||
sigaddset(&sigset, SIGINT);
|
||||
sigaddset(&sigset, SIGTERM);
|
||||
sigaddset(&sigset, SIGQUIT);
|
||||
pthread_sigmask(SIG_BLOCK, &sigset, nullptr);
|
||||
struct signalfd_siginfo info;
|
||||
const int sfd = signalfd(-1, &sigset, SFD_CLOEXEC);
|
||||
|
||||
struct sensors_t *sensors = malloc(sizeof(struct sensors_t));
|
||||
|
||||
if (sensors == nullptr) {
|
||||
applog(LOG_ERR, "cant allocate mem for the sensor struct");
|
||||
free(sensors);
|
||||
return -1;
|
||||
}
|
||||
applog(LOG_INF, "sensor struct allocated, size is %lu bytes", sizeof(struct sensors_t));
|
||||
|
||||
applog(LOG_INF, "sensors is %.1f", sensors->hass_state);
|
||||
|
||||
pthread_t display_pthread;
|
||||
pthread_t sensors_pthread;
|
||||
pthread_create(&display_pthread, nullptr, display_thread, sensors);
|
||||
pthread_create(&sensors_pthread, nullptr, sensors_thread, sensors);
|
||||
|
||||
read(sfd, &info, sizeof(info)); // halts on this until SIGTERM
|
||||
g_running = false;
|
||||
close(sfd);
|
||||
applog(LOG_WRN, "got stop signal, shutting down...");
|
||||
|
||||
pthread_join(display_pthread, nullptr);
|
||||
pthread_join(sensors_pthread, nullptr);
|
||||
|
||||
applog(LOG_INF, "sensors is %.1f", sensors->hass_state);
|
||||
|
||||
free(sensors);
|
||||
applog(LOG_INF, "freed memory");
|
||||
return 0;
|
||||
}
|
||||
87
src/sys.cpp
87
src/sys.cpp
@@ -1,87 +0,0 @@
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <log.hpp>
|
||||
#include <string>
|
||||
#include <sys.hpp>
|
||||
#include <utils.hpp>
|
||||
|
||||
int getVal(const std::string_view target, std::string_view content) {
|
||||
int result = -1;
|
||||
if (const std::size_t start = content.find(target); start != std::string::npos) {
|
||||
content.remove_prefix(start + target.length());
|
||||
const std::size_t end = content.find(" kB");
|
||||
while (std::isspace(content.front()))
|
||||
content.remove_prefix(1);
|
||||
std::from_chars(content.data(), content.data() + end, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void sys::getTimeDate(TimeDate* td) {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto local = std::chrono::current_zone()->to_local(now);
|
||||
const std::chrono::year_month_day ymd{floor<std::chrono::days>(local)};
|
||||
const std::chrono::weekday wd{floor<std::chrono::days>(local)};
|
||||
td->time = std::format("{:%H:%M:%S}", floor<std::chrono::seconds>(local));
|
||||
td->date = std::format("{}, {} {}", wdays[wd.c_encoding()], ymd.day(), months[static_cast<unsigned>(ymd.month()) - 1]);
|
||||
}
|
||||
|
||||
void sys::getRam(Memory* mem) {
|
||||
static std::array<char, 128> buf; // file is longer but we only need first 128 chars
|
||||
utils::readFile("/proc/meminfo", buf);
|
||||
mem->free = getVal("MemAvailable:", buf.data());
|
||||
mem->total = getVal("MemTotal:", buf.data()); // shouldn't really update this on each loop
|
||||
mem->used = mem->total - mem->free;
|
||||
}
|
||||
|
||||
void sys::getDisk(Disk* disk) {
|
||||
struct statvfs diskData{};
|
||||
statvfs(disk->mountpoint.c_str(), &diskData);
|
||||
disk->usage.free = diskData.f_bfree * diskData.f_frsize;
|
||||
disk->usage.used = (diskData.f_blocks - diskData.f_bfree) * diskData.f_frsize;
|
||||
disk->usage.total = diskData.f_blocks * diskData.f_frsize;
|
||||
}
|
||||
|
||||
void sys::getNetwork(Network* net, const std::string& device) {
|
||||
struct Network {
|
||||
std::chrono::time_point<std::chrono::steady_clock> ts;
|
||||
uint64_t rx = 0;
|
||||
uint64_t tx = 0;
|
||||
};
|
||||
|
||||
static std::array<char, 32> buf;
|
||||
static Network prev;
|
||||
Network current;
|
||||
|
||||
current.ts = std::chrono::steady_clock::now();
|
||||
utils::fastReadNum("/sys/class/net/" + device + "/statistics/rx_bytes", buf, current.rx);
|
||||
utils::fastReadNum("/sys/class/net/" + device + "/statistics/tx_bytes", buf, current.tx);
|
||||
const double ts_diff = static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(current.ts - prev.ts).count()) / 1000000.0f;
|
||||
net->download = static_cast<uint32_t>(static_cast<double>(current.rx - prev.rx) / ts_diff);
|
||||
net->upload = static_cast<uint32_t>(static_cast<double>(current.tx - prev.tx) / ts_diff);
|
||||
prev = current;
|
||||
}
|
||||
|
||||
int sys::detect(sysSensors* s, const std::string& platform) {
|
||||
// we dont really need getFirstDir since its a static folder, but its useful for try/catch
|
||||
try {
|
||||
s->hwmon = utils::getFirstDir("/sys/devices/platform/" + platform + "/hwmon");
|
||||
} catch (...) {
|
||||
applog(LOG::ERR, "Can't open \"/sys/devices/platform/{}/hwmon\". Kernel params added? Module loaded?", platform);
|
||||
return -1;
|
||||
}
|
||||
applog(LOG::INF, "Found system sensors");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sys::updateSensors(sysSensors* s) {
|
||||
if (s->hwmon.empty())
|
||||
return;
|
||||
static std::array<char, 8> buf;
|
||||
utils::fastReadNum(s->hwmon + "/fan2_input", buf, s->fan_speed[0]);
|
||||
utils::fastReadNum(s->hwmon + "/fan7_input", buf, s->fan_speed[1]);
|
||||
utils::fastReadNum<1000>(s->hwmon + "/temp5_input", buf, s->t_sensor);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#include <utils.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace utils {
|
||||
|
||||
/* pads given string to length */
|
||||
std::string pad(std::string s, const uint8_t len, const just j) {
|
||||
if (len > s.size())
|
||||
s.insert(j == left ? s.end() : s.begin(), len - s.size(), fill_char);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* returns the string centered within given length */
|
||||
std::string center(std::string str, const uint8_t len) {
|
||||
const uint8_t padding = len - str.size();
|
||||
str.insert(str.begin(), padding / 2, fill_char);
|
||||
str.insert(str.end(), padding / 2, fill_char);
|
||||
return str;
|
||||
}
|
||||
|
||||
/* finds first dir in path and returns it. don't refactor, you don't understand it, its fast enough, and you only use it on init */
|
||||
std::string getFirstDir(const std::string_view& path) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path))
|
||||
return entry.path();
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
Reference in New Issue
Block a user