Files
linuxSmartie/main.cpp
2025-10-03 01:45:46 +02:00

168 lines
6.5 KiB
C++

// #include <array>
#include <chrono>
#include <csignal>
// #include <cstdint> // this is incorrect, we need to either use stdint.h, use std::uint8_t or put using std::uint_8
#include <functional>
#include <mutex>
#include <print>
#include <semaphore>
#include <stop_token>
#include <string>
#include <thread>
#include <vector>
// local includes
#include <LCDDisplay.hpp>
#include <cpu.hpp>
#include <gpu.hpp>
#include <hass.hpp>
#include <sys.hpp>
#include <utils.hpp>
#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::Memory home;
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 sighandler(int s) { // can i remove int s
std::println("[Signal] got SIGINT/SIGTERM, exiting...");
sem::main.release();
sem::display.release();
}
void hassThread(std::stop_token st, Sensors* s) {
// std::println("[Thread] Starting hass_thread, pid {}", gettid());
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
std::lock_guard<std::mutex> 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) {
std::println("[Thread] Starting sensors_thread, pid {}, poll interval {} ms", gettid(), LCD_INTERVAL_MS);
cpu::detectHwmon(&s->cpu, "0000:00:18.3");
gpu::detectHwmon(&s->gpu, "0000:0a:00.0");
while (!st.stop_requested()) {
auto start = std::chrono::steady_clock::now();
{
// this will unlock when program goes out of scope
std::lock_guard<std::mutex> lock(s->mtx);
sys::getTimeDate(&s->td);
getloadavg(s->avg, 3);
sys::getRam(&s->ram);
sys::getDisk(&s->home, "/home/drath");
cpu::update(&s->cpu);
gpu::update(&s->gpu);
}
// std::println("[Sensors] took {} microseconds", std::chrono::duration_cast<std::chrono::microseconds>(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) {
std::println("[Thread] Starting display_thread, pid {}", gettid());
LCDDisplay lcd(LCD_PORT);
lcd.setContrast(LCD_CONTRAST);
lcd.setBacklight(true);
lcd.setCustomChar(0, {16, 6, 9, 8, 8, 9, 6, 0}); // celcius
while (!st.stop_requested()) {
sem::display.acquire(); // holds the loop until sensor data is ready
{
std::lock_guard<std::mutex> lock(s->mtx);
std::string gpu_temps;
// gpu_temps += pad("d" + std::to_string(s->gpu.temp_hotspot - s->gpu.temp_edge), 3, true) + lcd.cc[0];
gpu_temps += std::to_string(s->gpu.temp_edge);
gpu_temps += (s->gpu.temp_hotspot) ? "/" + std::to_string(s->gpu.temp_hotspot) : "";
// gpu_temps += (s->gpu.temp_mem) ? "/" + std::to_string(s->gpu.temp_mem) : "";
lcd.writeBuf(0, 0, pad(s->td.date, 11, true) + " " + s->td.time + lcd.bar(1, s->gpu.usage_core / 100.0) /* + pad(s->loadavg, 17)*/);
lcd.writeBuf(0, 26, pNum(s->avg[0], s->avg[0] > 10 ? 1 : 2));
lcd.writeBuf(0, 31, pNum(s->avg[1], s->avg[1] > 10 ? 1 : 2));
lcd.writeBuf(0, 36, pNum(s->avg[2], s->avg[2] > 10 ? 1 : 2));
lcd.writeBuf(1, 0, pad(std::to_string(s->cpu.tctl) + "/" + std::to_string(s->cpu.tccd1) + lcd.cc[0], 8, true));
lcd.writeBuf(1, 8, pad(pBytes(s->ram.used, true) + "B", 7, true));
lcd.writeBuf(1, 20, pad(pBytes(s->gpu.vram_used) + "B", 7));
if (s->gpu.temp_hotspot) lcd.writeBuf(1, 28, "d" + std::to_string(s->gpu.temp_hotspot - s->gpu.temp_edge) + lcd.cc[0] + " ");
lcd.writeBuf(1, 32, pad(gpu_temps + lcd.cc[0], 8));
lcd.writeBuf(2, 0, pad(pNum(s->cpu.ptotal, 1) + "W", 8, true));
lcd.writeBuf(2, 8, "home:" + pad(pBytes(s->home.free), 5));
lcd.writeBuf(2, 20, pad(std::to_string(s->gpu.fan_pwm) + "%", 4));
lcd.writeBuf(2, 25, pad(std::to_string(s->gpu.fan_speed), 4) + "R");
lcd.writeBuf(2, 31, pad(std::to_string(s->gpu.power_avg) + "/" + std::to_string(s->gpu.power_cap) + "W", 9));
lcd.writeBuf(3, 0, pad(pNum(s->cpu.vcore, 3) + "V", 7, true));
lcd.writeBuf(3, 7, std::to_string(s->cpu.avg_freq) + "MHz");
lcd.writeBuf(3, 16, pad(pNum(s->hass_state) + "W", 4));
lcd.writeBuf(3, 21, pad(std::to_string(s->gpu.clock_core) + "/" + std::to_string(s->gpu.clock_mem) + "MHz ", 13));
lcd.writeBuf(3, 34, pad(pNum(s->gpu.vcore, 3) + "V", 5));
}
lcd.flushBuf();
}
lcd.clearBuf();
lcd.writeBuf(1, 10, "linuxSmartie closed");
lcd.flushBuf();
}
int main(int argc, char* argv[]) {
std::signal(SIGINT, sighandler);
std::signal(SIGTERM, sighandler);
Sensors* pSensors = new Sensors;
std::println("[main] size of sensor struct is {} bytes", sizeof(Sensors));
std::function<void(std::stop_token st, Sensors * s)> funcs[] = {displayThread, hassThread, sensorsThread}; // array of functions
/* what the fucking kind of code is this
this code below spins up display/sensors/homeassistant functions as threads and halts until sem::main is released in sighandler
then the program leaves the scope, but before that, it destroys the std::jthreads in the "threads" vector
this causes all threads to gracefully close and join back into main thread, so we can safely destroy pSensors without segfault
*/
{
std::vector<std::jthread> threads;
for (auto& f : funcs)
threads.emplace_back(f, pSensors); // params: function, stop_token (implied), pointer to sensor struct
sem::main.acquire();
}
delete pSensors;
return 0;
}