#include #include #include #include #include #include #include #include #include #include #include #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]; 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) { 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 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 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::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 lock(s->mtx); auto 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, 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.temp_edge) + 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.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::vector> 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 threads; for (auto& f : funcs) threads.emplace_back(f, pSensors); sem::main.acquire(); } delete pSensors; return 0; }