161 lines
5.7 KiB
C++
161 lines
5.7 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 */
|
|
|
|
namespace sem {
|
|
std::binary_semaphore display{0};
|
|
std::binary_semaphore main{0};
|
|
} // namespace sem
|
|
|
|
struct Sensors {
|
|
std::array<std::string, 2> td;
|
|
std::string loadavg;
|
|
mem_usage_t ram;
|
|
mem_usage_t home;
|
|
cpu::sensors cpu;
|
|
gpu::sensors gpu;
|
|
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...");
|
|
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);
|
|
// maybe make these take pointers too
|
|
s->td = getTimeDate();
|
|
s->loadavg = getAvgs();
|
|
s->ram = getRam();
|
|
s->home = getDisk("/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);
|
|
|
|
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);
|
|
|
|
Sensors* pSensors = new Sensors;
|
|
std::println("[main] size of sensor struct is {} bytes", 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);
|
|
|
|
sem::main.acquire();
|
|
}
|
|
|
|
delete pSensors;
|
|
return 0;
|
|
} |