#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 // maybe put semaphores in the struct? 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 sighandler(int) { log(LOG::WRN, "Signal", "got SIGINT/SIGTERM, exiting..."); sem::main.release(); sem::display.release(); } void hassThread(std::stop_token st, Sensors* s) { log(LOG::INF, "Thread", "Starting hass_thread"); 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) { log(LOG::INF, "Thread", "Starting sensors_thread, 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, "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) { log(LOG::INF, "Thread", "Starting display_thread"); // 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, 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 = pad(lcd.cc(1) + std::to_string(s->gpu.temp_hotspot - s->gpu.temp_edge) + lcd.cc(0), 4) + " " + 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, pad(s->td.date, 11, JUST::LEFT) + " " + s->td.time + lcd.bar(7, s->gpu.usage_core / 100.0)); 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, JUST::LEFT)); lcd.writeBuf(1, 7, pad(pBytes(s->ram.used, true), 6, JUST::LEFT)); lcd.writeBuf(1, 13, pad(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(), pad(pBytes(s->gpu.vram_used), 6)); lcd.writeBuf(1, 39 - gpu_temps.size(), gpu_temps + lcd.cc(0)); lcd.writeBuf(2, 0, pad(std::to_string((int)s->cpu.ptotal) + "W", 8, 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) + pBytes(s->net.upload)); lcd.writeBuf(2, 19, lcd.cc(4) + pBytes(s->net.download)); lcd.writeBuf(2, 26, pad(std::to_string(s->gpu.fan_speed) + "R", 5)); 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, 2) + "V", 7, JUST::LEFT)); lcd.writeBuf(3, 6, std::to_string(s->cpu.avg_freq) + "MHz"); lcd.writeBuf(3, 14, lcd.cc(2) + pad(pBytes(s->home.usage.free), 5)); 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(); } // goodbye msg lcd.writeBuf(2, 0, center("linuxSmartie closed", 40)); lcd.flushBuf(); } int main() { log(LOG::START, "LINUXSMARTIE", "============================= PROGRAM START ============================="); // to make logs clearer std::signal(SIGINT, sighandler); std::signal(SIGTERM, sighandler); Sensors* pSensors = new Sensors; log(LOG::DBG, "main", "main thread pid is {}, size of sensor struct is {} bytes", gettid(),sizeof(Sensors)); std::function 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 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; }