Files
linuxSmartie/main.cpp

160 lines
5.9 KiB
C++

#include <array>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <functional>
#include <mutex>
#include <print>
#include <semaphore>
#include <stop_token>
#include <string>
#include <thread>
#include <vector>
//
#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 */
std::binary_semaphore sDisp{0}; // halts display func until we have sensor data
std::binary_semaphore sMain{0}; // halts the main thread until SIGINT
struct Sensors {
std::array<std::string, 2> td;
std::string loadavg;
mem_usage_t ram;
mem_usage_t home;
gpu_t gpu;
cpu_t cpu;
float hass_state = 0.0f;
std::mutex mtx; // making sure the struct isnt accessed by multiple threads at once
};
void sighandler(int s) {
std::println("[Signal] got SIGINT/SIGTERM, exiting...");
sMain.release();
sDisp.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_hwmon_t cpu_hwmon = detectCpuHwmon("0000:00:18.3");
std::string gpu_hwmon = detectGpuHwmon("0000:08:00.0");
while (!st.stop_requested()) {
auto start = std::chrono::steady_clock::now();
{
std::lock_guard<std::mutex> lock(s->mtx); // will unlock when it goes out of scope, this is why this whole thing is in braces
s->td = getTimeDate();
s->loadavg = getAvgs();
s->ram = getRam();
s->home = getDisk("/home/drath");
s->cpu = getCpu(cpu_hwmon);
s->gpu = getGpu(gpu_hwmon, false);
}
// std::println("[Sensors] took {} microseconds", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count());
sDisp.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()) {
sDisp.acquire(); // holds the loop until sensor data is ready
{
std::lock_guard<std::mutex> lock(s->mtx);
auto gpu_temps = (s->gpu.hotspot_temp) ? "/" + std::to_string(s->gpu.hotspot_temp) : "";
gpu_temps += (s->gpu.mem_temp) ? "/" + std::to_string(s->gpu.mem_temp) : "";
lcd.writeBuf(0, 0, pad(s->td[1], 11, true) + " " + s->td[0] + lcd.bar(1, s->gpu.core_usage / 100.0) + pad(s->loadavg, 17));
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, 16, pad(pNum(s->hass_state) + "W", 4));
lcd.writeBuf(1, 22, pad(pBytes(s->gpu.vram_used) + "B", 7));
lcd.writeBuf(1, 29, pad(std::to_string(s->gpu.edge_temp) + gpu_temps + lcd.cc[0], 11));
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, 21, pad(std::to_string(s->gpu.core_clock) + "/" + std::to_string(s->gpu.mem_clock) + "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);
// std::println("[Thread] Main thread, pid {}", gettid());
// not worth using smart pointer, we dont need to fw pointer ownership
Sensors* pSensors = new Sensors;
std::println("sensors: {}", sizeof(Sensors));
std::vector<std::function<void(std::stop_token st, Sensors * s)>> funcs = {displayThread, hassThread, sensorsThread};
/* wtf is this?
this spins up funcs as threads and halts until sMain semaphore is released in sighandler
its scoped because when sMain is released, program waits until all the threads join back before leaving the scope
threads object has to be inside the scope too because destruction of std::jthread is what actually makes threads join.
*/
{
std::vector<std::jthread> threads;
for (auto& f : funcs)
threads.emplace_back(f, pSensors);
sMain.acquire();
}
delete pSensors;
std::println("Main thread exiting");
return 0;
}