From 0dc78be629cea86fcf7a2bfb809203f34a0913f2 Mon Sep 17 00:00:00 2001 From: Drath Date: Sun, 28 Dec 2025 22:01:37 +0100 Subject: [PATCH] move out everything out of main into a separate file --- include/app.hpp | 28 ++++++++ main.cpp | 183 +----------------------------------------------- src/app.cpp | 179 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 181 deletions(-) create mode 100644 include/app.hpp create mode 100644 src/app.cpp diff --git a/include/app.hpp b/include/app.hpp new file mode 100644 index 0000000..8a13f75 --- /dev/null +++ b/include/app.hpp @@ -0,0 +1,28 @@ +#ifndef APP_HPP +#define APP_HPP + +#include +#include +#include +#include + +namespace app { + +struct sensors { + sys::TimeDate td; + double avg[3] = {0.0f}; + sys::Memory ram; + sys::Disk home; + sys::Network net; + sys::sysSensors sys; + cpu::cpuSensors cpu; + gpu::gpuSensors gpu; + float hass_state = 0.0f; + std::mutex mtx; // making sure the struct isnt accessed by multiple threads at once +}; + +void start(void); + +} // namespace app + +#endif \ No newline at end of file diff --git a/main.cpp b/main.cpp index c671691..3b9e8e7 100644 --- a/main.cpp +++ b/main.cpp @@ -1,186 +1,7 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// local includes -#include -#include -#include -#include -#include -#include -#include - -#define LCD_PORT "/dev/serial/by-id/usb-Raspberry_Pi_Pico_E6616407E37AA12F-if00" // tis a symlink, can also be just /dev/ttyACM0 -#define LCD_INTERVAL_MS 500 -#define LCD_CONTRAST 215 - -/* these need to be global since we're talking to them from sighandler */ - -namespace sem { -std::binary_semaphore display{0}; -std::binary_semaphore main{0}; -} // namespace sem - -struct Sensors { - sys::TimeDate td; - double avg[3] = {0.0f}; - sys::Memory ram; - sys::Disk home; - sys::Network net; - sys::sysSensors sys; - cpu::cpuSensors cpu; - gpu::gpuSensors gpu; - float hass_state = 0.0f; - std::mutex mtx; // making sure the struct isnt accessed by multiple threads at once -}; - -void signalHandler(int) { - applog(LOG::WRN, "got SIGINT/SIGTERM, exiting..."); - sem::main.release(); - sem::display.release(); -} - -void hassThread(std::stop_token st, Sensors* s) { - applog(LOG::START, "Thread started"); - HomeAssistant pc_power("home.drath.win", "sensor.gniazdko_komputer_moc"); - - pc_power.connect(); - while (!st.stop_requested()) { - pc_power.service(); - { - // locks mutex for the lifetime of the scope, unlocks when it goes out of scope - // mutex can only be locked in one place, so when its locked here, other threads need to wait until its unlocked before they can lock it again - std::lock_guard lock(s->mtx); - s->hass_state = pc_power.state; - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } -} - -void sensorsThread(std::stop_token st, Sensors* s) { - applog(LOG::START, "Thread started, poll interval {} ms", LCD_INTERVAL_MS); - cpu::detectHwmon(&s->cpu, "0000:00:18.3"); - gpu::detectHwmon(&s->gpu, "0000:0b:00.0"); - sys::detectHwmon(&s->sys, "nct6775.656"); - s->home.mountpoint = "/home/drath"; - - while (!st.stop_requested()) { - auto start = std::chrono::steady_clock::now(); - { - std::lock_guard lock(s->mtx); - - sys::getTimeDate(&s->td); - getloadavg(s->avg, 3); - sys::getRam(&s->ram); - sys::getDisk(&s->home); - sys::getNetwork(&s->net, "enp4s0f0"); - sys::updateSensors(&s->sys); - cpu::update(&s->cpu); - gpu::update(&s->gpu); - } - - // log(LOG::DBG, "took {} microseconds", std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); - sem::display.release(); // unlocks display loop - std::this_thread::sleep_until(start + std::chrono::milliseconds(LCD_INTERVAL_MS)); - } -} - -void displayThread(std::stop_token st, Sensors* s) { - applog(LOG::START, "Thread started"); - // init internal variables of LCDDisplay class - LCDDisplay lcd(LCD_PORT); - - // basic lcd setup - lcd.setContrast(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()) { - sem::display.acquire(); // holds the loop until sensor data is ready - { - std::lock_guard lock(s->mtx); - - // prepare some tricky sensors - std::string gpu_temps = std::to_string(s->gpu.temp_edge); - if (s->gpu.temp_hotspot) - gpu_temps = utils::pad(lcd.cc(1) + std::to_string(s->gpu.temp_hotspot - s->gpu.temp_edge) + lcd.cc(0), 4) + " " + utils::pad(gpu_temps + "/" + std::to_string(s->gpu.temp_hotspot), 5); - - uint16_t cpu_fan = 0; - for (auto& fan : s->sys.fan_speed) - cpu_fan += fan; - cpu_fan /= 2; - - // write to display buffer - lcd.writeBuf(0, 0, utils::pad(s->td.date, 11, utils::just::left) + " " + s->td.time + lcd.bar(7, s->gpu.usage_core / 100.0)); - lcd.writeBuf(0, 26, utils::pNum(s->avg[0], s->avg[0] > 10 ? 1 : 2)); - lcd.writeBuf(0, 31, utils::pNum(s->avg[1], s->avg[1] > 10 ? 1 : 2)); - lcd.writeBuf(0, 36, utils::pNum(s->avg[2], s->avg[2] > 10 ? 1 : 2)); - - lcd.writeBuf(1, 0, utils::pad(std::to_string(s->cpu.tctl) + "/" + std::to_string(s->cpu.tccd1) + lcd.cc(0), 8, utils::just::left)); - lcd.writeBuf(1, 7, utils::pad(utils::pBytes(s->ram.used, true), 6, utils::just::left)); - lcd.writeBuf(1, 13, utils::pad(utils::pNum(s->hass_state) + "W", 4)); - lcd.writeBuf(1, 19, std::to_string(s->sys.t_sensor) + lcd.cc(0)); // t_sensor temp - lcd.writeBuf(1, 32 - gpu_temps.size(), utils::pad(utils::pBytes(s->gpu.vram_used), 6)); - lcd.writeBuf(1, 39 - gpu_temps.size(), gpu_temps + lcd.cc(0)); - - lcd.writeBuf(2, 0, utils::pad(std::to_string((int)s->cpu.ptotal) + "W", 8, utils::just::left)); - lcd.writeBuf(2, s->cpu.ptotal < 100 ? 4 : 5, std::to_string(cpu_fan) + "R"); // avg fan speed - lcd.writeBuf(2, 12, lcd.cc(3) + utils::pBytes(s->net.upload)); - lcd.writeBuf(2, 19, lcd.cc(4) + utils::pBytes(s->net.download)); - lcd.writeBuf(2, 26, utils::pad(std::to_string(s->gpu.fan_speed) + "R", 5)); - lcd.writeBuf(2, 31, utils::pad(std::to_string(s->gpu.power_avg) + "/" + std::to_string(s->gpu.power_cap) + "W", 9)); - - lcd.writeBuf(3, 0, utils::pad(utils::pNum(s->cpu.vcore, 2) + "V", 7, utils::just::left)); - lcd.writeBuf(3, 6, std::to_string(s->cpu.avg_freq) + "MHz"); - lcd.writeBuf(3, 14, utils::pad(lcd.cc(2) + utils::pBytes(s->home.usage.free), 6)); - lcd.writeBuf(3, 21, utils::pad(std::to_string(s->gpu.clock_core) + "/" + std::to_string(s->gpu.clock_mem) + "MHz ", 13)); - lcd.writeBuf(3, 34, utils::pad(utils::pNum(s->gpu.vcore, 3) + "V", 5)); - } - - lcd.flushBuf(); - lcd.clearBuf(); - } - - // goodbye msg - lcd.writeBuf(2, 0, utils::center("linuxSmartie closed", 40)); - lcd.flushBuf(); -} +#include int main() { - applog(LOG::START, "Application started", __FILE__); // to make logs clearer - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); - - { - std::unique_ptr pSensors = std::make_unique(); - std::vector threads; - std::function funcs[] = {displayThread, sensorsThread, hassThread}; - - for (auto& f : funcs) { - threads.emplace_back(f, pSensors.get()); // params: function, stop_token (implied), pointer to sensor struct - std::this_thread::sleep_for(std::chrono::milliseconds(10)); // start threads with delay so displayThread can quit the app if theres no lcd - } - - sem::main.acquire(); - } - + app::start(); return 0; } diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..1f7cae9 --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,179 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace app { + +const std::string lcd_port = "/dev/serial/by-id/usb-Raspberry_Pi_Pico_E6616407E37AA12F-if00"; // tis a symlink, can also be just /dev/ttyACM0 +const uint16_t lcd_interval_ms = 500; +const uint8_t lcd_contrast = 215; + +const std::string cpu_pciaddr = "0000:00:18.3"; +const std::string gpu_pciaddr = "0000:0b:00.0"; +const std::string sys_sensor = "nct6775.656"; +const std::string sys_ifname = "enp4s0f0"; +const std::string home_dir = "/home/drath"; + +namespace semaphores { +std::binary_semaphore display{0}; +std::binary_semaphore main{0}; +} // namespace semaphores + +void signalHandler(int) { + applog(LOG::WRN, "got SIGINT/SIGTERM, exiting..."); + app::semaphores::main.release(); + app::semaphores::display.release(); +} + +void hassThread(std::stop_token st, app::sensors* s) { + applog(LOG::START, "Thread started"); + HomeAssistant pc_power("home.drath.win", "sensor.gniazdko_komputer_moc"); + + pc_power.connect(); + while (!st.stop_requested()) { + pc_power.service(); + { + // locks mutex for the lifetime of the scope, unlocks when it goes out of scope + // mutex can only be locked in one place, so when its locked here, other threads need to wait until its unlocked before they can lock it again + std::lock_guard lock(s->mtx); + s->hass_state = pc_power.state; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +void sensorsThread(std::stop_token st, app::sensors* s) { + applog(LOG::START, "Thread started, poll interval {} ms", lcd_interval_ms); + cpu::detectHwmon(&s->cpu, cpu_pciaddr); + gpu::detectHwmon(&s->gpu, gpu_pciaddr); + sys::detectHwmon(&s->sys, sys_sensor); + s->home.mountpoint = home_dir; + + while (!st.stop_requested()) { + auto start = std::chrono::steady_clock::now(); + { + std::lock_guard lock(s->mtx); + + sys::getTimeDate(&s->td); + getloadavg(s->avg, 3); + sys::getRam(&s->ram); + sys::getDisk(&s->home); + sys::getNetwork(&s->net, sys_ifname); + sys::updateSensors(&s->sys); + cpu::update(&s->cpu); + gpu::update(&s->gpu); + } + + // log(LOG::DBG, "took {} microseconds", std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); + app::semaphores::display.release(); // unlocks display loop + std::this_thread::sleep_until(start + std::chrono::milliseconds(lcd_interval_ms)); + } +} + +void displayThread(std::stop_token st, app::sensors* s) { + applog(LOG::START, "Thread started"); + // init internal variables of LCDDisplay class + LCDDisplay lcd(lcd_port); + + // basic lcd setup + lcd.setContrast(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()) { + app::semaphores::display.acquire(); // holds the loop until sensor data is ready + { + std::lock_guard lock(s->mtx); + + // prepare some tricky sensors + std::string gpu_temps = std::to_string(s->gpu.temp_edge); + if (s->gpu.temp_hotspot) + gpu_temps = utils::pad(lcd.cc(1) + std::to_string(s->gpu.temp_hotspot - s->gpu.temp_edge) + lcd.cc(0), 4) + " " + utils::pad(gpu_temps + "/" + std::to_string(s->gpu.temp_hotspot), 5); + + uint16_t cpu_fan = 0; + for (auto& fan : s->sys.fan_speed) + cpu_fan += fan; + cpu_fan /= 2; + + // write to display buffer + lcd.writeBuf(0, 0, utils::pad(s->td.date, 11, utils::just::left) + " " + s->td.time + lcd.bar(7, s->gpu.usage_core / 100.0)); + lcd.writeBuf(0, 26, utils::pNum(s->avg[0], s->avg[0] > 10 ? 1 : 2)); + lcd.writeBuf(0, 31, utils::pNum(s->avg[1], s->avg[1] > 10 ? 1 : 2)); + lcd.writeBuf(0, 36, utils::pNum(s->avg[2], s->avg[2] > 10 ? 1 : 2)); + + lcd.writeBuf(1, 0, utils::pad(std::to_string(s->cpu.tctl) + "/" + std::to_string(s->cpu.tccd1) + lcd.cc(0), 8, utils::just::left)); + lcd.writeBuf(1, 7, utils::pad(utils::pBytes(s->ram.used, true), 6, utils::just::left)); + lcd.writeBuf(1, 13, utils::pad(utils::pNum(s->hass_state) + "W", 4)); + lcd.writeBuf(1, 19, std::to_string(s->sys.t_sensor) + lcd.cc(0)); // t_sensor temp + lcd.writeBuf(1, 32 - gpu_temps.size(), utils::pad(utils::pBytes(s->gpu.vram_used), 6)); + lcd.writeBuf(1, 39 - gpu_temps.size(), gpu_temps + lcd.cc(0)); + + lcd.writeBuf(2, 0, utils::pad(std::to_string((int)s->cpu.ptotal) + "W", 8, utils::just::left)); + lcd.writeBuf(2, s->cpu.ptotal < 100 ? 4 : 5, std::to_string(cpu_fan) + "R"); // avg fan speed + lcd.writeBuf(2, 12, lcd.cc(3) + utils::pBytes(s->net.upload)); + lcd.writeBuf(2, 19, lcd.cc(4) + utils::pBytes(s->net.download)); + lcd.writeBuf(2, 26, utils::pad(std::to_string(s->gpu.fan_speed) + "R", 5)); + lcd.writeBuf(2, 31, utils::pad(std::to_string(s->gpu.power_avg) + "/" + std::to_string(s->gpu.power_cap) + "W", 9)); + + lcd.writeBuf(3, 0, utils::pad(utils::pNum(s->cpu.vcore, 2) + "V", 7, utils::just::left)); + lcd.writeBuf(3, 6, std::to_string(s->cpu.avg_freq) + "MHz"); + lcd.writeBuf(3, 14, utils::pad(lcd.cc(2) + utils::pBytes(s->home.usage.free), 6)); + lcd.writeBuf(3, 21, utils::pad(std::to_string(s->gpu.clock_core) + "/" + std::to_string(s->gpu.clock_mem) + "MHz ", 13)); + lcd.writeBuf(3, 34, utils::pad(utils::pNum(s->gpu.vcore, 3) + "V", 5)); + } + + lcd.flushBuf(); + lcd.clearBuf(); + } + + // goodbye msg + lcd.writeBuf(2, 0, utils::center("Goodbye", 40)); + lcd.flushBuf(); +} + +void start(void) { + applog(LOG::START, "Application started"); + std::signal(SIGINT, app::signalHandler); + std::signal(SIGTERM, app::signalHandler); + + { + std::unique_ptr pSensors = std::make_unique(); + std::vector threads; + std::function funcs[] = {app::displayThread, app::sensorsThread, app::hassThread}; + + for (auto& f : funcs) { + threads.emplace_back(f, pSensors.get()); // params: function, stop_token (implied), pointer to sensor struct + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // start threads with delay so displayThread can quit the app if theres no lcd + } + + app::semaphores::main.acquire(); + } +} + +} // namespace app \ No newline at end of file