commit f76ca3daccb5437ecfd14094bf66b4d655c2fcd9 Author: Drath Date: Sat Apr 26 21:23:40 2025 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2153c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +linuxSmartie \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a3857dc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,75 @@ +{ + "files.associations": { + "*.bu": "yaml", + "*ign": "json", + "iostream": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "fstream": "cpp", + "codecvt": "cpp", + "ratio": "cpp", + "chrono": "cpp", + "semaphore": "cpp", + "stop_token": "cpp", + "thread": "cpp" + }, + "makefile.configureOnOpen": true, + "makefile.launchConfigurations": [ + { + "cwd": "/var/home/drath/lcdtest", + "binaryPath": "/var/home/drath/lcdtest/lcd", + "binaryArgs": [] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..61eeb8c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$gcc" + }, + { + "label": "clean", + "type": "shell", + "command": "make clean", + "group": { + "kind": "none", + "isDefaut": false + }, + "problemMatcher": "$gcc" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..741dd35 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +MAKEFLAGS := --jobs=$(shell nproc) +# MAKEFLAGS += --output-sync=target + +# compiling with C++26 +BUILD_OPTS = -std=c++2c -Ofast -fdiagnostics-color=always + +build: + g++ -c $(BUILD_OPTS) -o main.o main.cpp + g++ -c $(BUILD_OPTS) -o LCDDisplay.o -Iinclude include/LCDDisplay.cpp + g++ $(BUILD_OPTS) -o linuxSmartie LCDDisplay.o main.o + +clean: + rm linuxSmartie *.o \ No newline at end of file diff --git a/include/LCDDisplay.cpp b/include/LCDDisplay.cpp new file mode 100644 index 0000000..98ff09f --- /dev/null +++ b/include/LCDDisplay.cpp @@ -0,0 +1,80 @@ +#include "LCDDisplay.hpp" + +#include +#include +#include + +#include +#include + +/* lcd size hardcoded to 4x40 idgaf */ +LCDDisplay::LCDDisplay(std::string dev, uint32_t baud) { + std::cout << "[LCDDisplay] Opening LCD at " << dev << std::endl; + struct termios term; + int lcd = open(dev.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + + // handle errors + if (lcd < 0) { + std::cerr << "[LCDDisplay] Error opening serial port " << dev << std::endl; + exit(lcd); + } + + // Setup the serial port 8 data bits, no parity ,1 stopbit, no flow control + term.c_cflag = baud | CS8 | CSTOPB | CLOCAL | CREAD; + term.c_iflag = 0; + term.c_oflag = 0; + term.c_lflag = 0; + tcflush(lcd, TCIFLUSH); + tcsetattr(lcd, TCSANOW, &term); + _lcd = lcd; +} + +LCDDisplay::~LCDDisplay() { + std::cout << "[LCDDisplay] Closing LCD port" << std::endl; + close(_lcd); +} + +void LCDDisplay::clear() { + uint8_t cmdbuf[] = {254, 88}; + write(_lcd, cmdbuf, 2); +} + +void LCDDisplay::setContrast(uint8_t contrast) { + std::cout << "[LCDDisplay] Set contrast to " << std::to_string(contrast) << std::endl; + uint8_t cmdbuf[] = {254, 80, contrast}; + write(_lcd, cmdbuf, 3); +} + +void LCDDisplay::setCursor(uint8_t row, uint8_t col) { + uint8_t cmdbuf[] = {254, 71, (uint8_t)(col + 1), (uint8_t)(row + 1)}; + write(_lcd, cmdbuf, 4); +} + +void LCDDisplay::writeStr(std::string str) { + write(_lcd, str.c_str(), str.size()); +} + +void LCDDisplay::writeLines(std::array lines) { + for (auto& line : lines) { + if(line.size() > 40) { + std::cerr << "Line " << line << " too long! (" << line.size() << " chars)" << std::endl; + continue; + } + line.insert(line.end(), 40 - line.size(), ' '); + } + + setCursor(0, 0); + writeStr(lines[0] + lines[1]); + setCursor(2, 0); + writeStr(lines[2] + lines[3]); +} + +void LCDDisplay::writeSStream(std::stringstream& ss) { + std::string s = ss.str(); + if (s.size() != 160) + std::cerr << "[LCDDisplay] stringstream wrong (" + std::to_string((int)(160 - s.size())) + " chars too short)" << std::endl; + setCursor(0, 0); + writeStr(s.substr(0, 80)); + setCursor(2, 0); + writeStr(s.substr(80, 159)); +} \ No newline at end of file diff --git a/include/LCDDisplay.hpp b/include/LCDDisplay.hpp new file mode 100644 index 0000000..a4281e5 --- /dev/null +++ b/include/LCDDisplay.hpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +class LCDDisplay { + private: + uint8_t _lcd; + void writeStr(std::string str); + + public: + LCDDisplay(std::string dev, uint32_t baud); + ~LCDDisplay(); + + void clear(); + void setContrast(uint8_t contrast); + void setCursor(uint8_t row, uint8_t col); + void writeLines(std::array lines); + void writeSStream(std::stringstream& ss); +}; \ No newline at end of file diff --git a/include/linux-system-usage.hpp b/include/linux-system-usage.hpp new file mode 100644 index 0000000..03f26bf --- /dev/null +++ b/include/linux-system-usage.hpp @@ -0,0 +1,201 @@ +/* --------------------------------------------------------------------- + * Luiz Carlos doleron @ 2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero Public License for more details. + * ---------------------------------------------------------------------- + */ + + #ifndef _GET_CPU_USAGE_LINUX_ + #define _GET_CPU_USAGE_LINUX_ + + #include + #include + #include + #include + + namespace get_system_usage_linux + { + + struct CPU_stats + { + // see http://www.linuxhowtos.org/manpages/5/proc.htm + int user; + int nice; + int system; + int idle; + int iowait; + int irq; + int softirq; + int steal; + int guest; + int guest_nice; + + int get_total_idle() + const { + return idle + iowait; + } + + int get_total_active() + const { + return user + nice + system + irq + softirq + steal + guest + guest_nice; + } + + }; + + struct Memory_stats + { + int total_memory; + int available_memory; + int total_swap; + int free_swap; + + float get_memory_usage() { + const float result = static_cast(total_memory - available_memory) / total_memory; + return result; + } + + float get_swap_usage() { + const float result = static_cast(total_swap - free_swap) / total_swap; + return result; + } + + }; + + inline CPU_stats read_cpu_data() + { + CPU_stats result; + std::ifstream proc_stat("/proc/stat"); + + if (proc_stat.good()) + { + std::string line; + getline(proc_stat, line); + + unsigned int *stats_p = (unsigned int *)&result; + std::stringstream iss(line); + std::string cpu; + iss >> cpu; + while (iss >> *stats_p) + { + stats_p++; + }; + } + + proc_stat.close(); + + return result; + } + + inline int get_val(const std::string &target, const std::string &content) { + int result = -1; + std::size_t start = content.find(target); + if (start != std::string::npos) { + int begin = start + target.length(); + std::size_t end = content.find("kB", start); + std::string substr = content.substr(begin, end - begin); + result = std::stoi(substr); + } + return result; + } + + inline Memory_stats read_memory_data() + { + Memory_stats result; + std::ifstream proc_meminfo("/proc/meminfo"); + + if (proc_meminfo.good()) + { + std::string content((std::istreambuf_iterator(proc_meminfo)), + std::istreambuf_iterator()); + + result.total_memory = get_val("MemTotal:", content); + result.total_swap = get_val("SwapTotal:", content); + result.free_swap = get_val("SwapFree:", content); + result.available_memory = get_val("MemAvailable:", content); + + } + + proc_meminfo.close(); + + return result; + } + + inline float get_cpu_usage(const CPU_stats &first, const CPU_stats &second) { + const float active_time = static_cast(second.get_total_active() - first.get_total_active()); + const float idle_time = static_cast(second.get_total_idle() - first.get_total_idle()); + const float total_time = active_time + idle_time; + return active_time / total_time; + } + + inline float get_disk_usage(const std::string & disk) { + struct statvfs diskData; + + statvfs(disk.c_str(), &diskData); + + auto total = diskData.f_blocks; + auto free = diskData.f_bfree; + auto diff = total - free; + + float result = static_cast(diff) / total; + + return result; + } + + //see https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal/342023 + + inline int find_thermalzone_index() { + int result = 0; + bool stop = false; + // 20 must stop anyway + for (int i = 0; !stop && i < 20; ++i) { + std::ifstream thermal_file("/sys/class/thermal/thermal_zone" + std::to_string(i) + "/type"); + + if (thermal_file.good()) + { + std::string line; + getline(thermal_file, line); + + if (line.compare("x86_pkg_temp") == 0) { + result = i; + stop = true; + } + + } else { + stop = true; + } + + thermal_file.close(); + } + return result; + } + + inline int get_thermalzone_temperature(int thermal_index) { + int result = -1; + std::ifstream thermal_file("/sys/class/thermal/thermal_zone" + std::to_string(thermal_index) + "/temp"); + + if (thermal_file.good()) + { + std::string line; + getline(thermal_file, line); + + std::stringstream iss(line); + iss >> result; + } else { + throw std::invalid_argument(std::to_string(thermal_index) + " doesn't refer to a valid thermal zone."); + } + + thermal_file.close(); + + return result; + } + + } + + #endif \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ad22873 --- /dev/null +++ b/main.cpp @@ -0,0 +1,180 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/LCDDisplay.hpp" +#include "include/linux-system-usage.hpp" + +#define LCD_PORT "/dev/ttyACM0" +#define LCD_INTERVAL_MS 1000 +#define LCD_CONTRAST 225 + +struct gpuHwmon_t { + std::string fan_speed; + std::string core_clock; + std::string mem_clock; + std::string core_mv; + std::string power_avg; + std::string power_cap; + std::string power_max; + std::string fan_pwm; + std::string edge_temp; + std::string junction_temp; + std::string mem_temp; +}; + +struct mem_usage_t { + std::string free; + std::string used; + std::string total; +}; + +std::string pBytes(double i, bool isKbytes = false) { + std::stringstream ss; + std::array suffix = {'K', 'M', 'G', 'T', 'P', 'E', 'Z'}; + for (auto &s : suffix) { + if (s == 'K' && isKbytes) continue; + i /= 1024.0; + if (i < 1024) { + ss << std::fixed << std::setprecision(i > 100 ? 1 : 2) << i << s; + return ss.str(); + } + } + return "++++"; +} + +std::string pad(std::string s, uint8_t w, bool ljust = false) { + if (w > s.size()) s.insert(ljust ? s.end() : s.begin(), w - s.size(), ' '); + return s; +} + +std::string getAvgs() { + std::stringstream ss; + double avgs[3]; + getloadavg(avgs, 3); + ss << std::fixed << std::setprecision(2); + ss << avgs[0] << " " << avgs[1] << " " << avgs[2]; + return ss.str(); +} + +std::array getTimeDate() { + std::array months = {"sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paz", "lis", "gru"}; + std::array wdays = {"nie", "pon", "wto", "sro", "czw", "pia", "sob"}; + auto now = std::time(nullptr); + auto tm = *std::localtime(&now); + std::stringstream time, date; + time << std::setfill('0'); + time << std::setw(2) << tm.tm_hour << ":"; + time << std::setw(2) << tm.tm_min << ":"; + time << std::setw(2) << tm.tm_sec; + date << wdays[tm.tm_wday] << ", " << tm.tm_mday << " " << months[tm.tm_mon]; + return {time.str(), date.str()}; +} + +mem_usage_t getRam() { + mem_usage_t mem; + auto memory_data = get_system_usage_linux::read_memory_data(); + mem.free = pBytes(memory_data.available_memory, true); + mem.used = pBytes(memory_data.total_memory - memory_data.available_memory, true); + mem.total = pBytes(memory_data.total_memory, true); + return mem; +} + +mem_usage_t getDisk(std::string mount) { + mem_usage_t disk; + struct statvfs diskData; + statvfs(mount.c_str(), &diskData); + disk.free = pBytes(diskData.f_bfree * diskData.f_frsize); + disk.used = pBytes((diskData.f_blocks - diskData.f_bfree) * diskData.f_frsize); + disk.total = pBytes(diskData.f_blocks * diskData.f_frsize); + return disk; +} + +std::string readFile(std::string path, uint8_t trim = 0) { + std::string ret; + std::stringstream ss; + std::ifstream fs(path); + if (!fs.good()) { + std::cerr << "Error reading " << path << std::endl; + exit(1); + } + ss << fs.rdbuf(); + fs.close(); + ret = ss.str(); + return ret.substr(0, ret.size() - trim - 1); +} + +gpuHwmon_t getGpu(std::string hw) { + gpuHwmon_t gpu; + + gpu.fan_speed = readFile(hw + "/fan1_input"); + gpu.core_clock = readFile(hw + "/freq1_input", 6); + gpu.mem_clock = readFile(hw + "/freq2_input", 6); + gpu.core_mv = readFile(hw + "/in0_input"); + gpu.power_avg = readFile(hw + "/power1_average", 6); + gpu.power_cap = readFile(hw + "/power1_cap", 6); + gpu.power_max = readFile(hw + "/power1_cap_max", 6); + gpu.fan_pwm = readFile(hw + "/pwm1"); + gpu.edge_temp = readFile(hw + "/temp1_input", 3); + gpu.junction_temp = readFile(hw + "/temp2_input", 3); + gpu.mem_temp = readFile(hw + "/temp3_input", 3); + + return gpu; +} + +std::string getCpuTemp() { + return readFile("/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon3/temp1_input"); +} + +void collectAndDraw(LCDDisplay *lcd) { + auto td = getTimeDate(); + auto ram = getRam(); + auto anime = getDisk("/mnt/anime"); + auto home = getDisk("/var/home/drath"); + auto gpu = getGpu("/sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0/0000:09:00.0/0000:0a:00.0/hwmon/hwmon2"); + + std::array lines; + + lines[0] = td[1] + " " + td[0] + pad(getAvgs(), 20); + + lines[1] = pad(ram.used + "/" + ram.total, 13, true) + " "; + lines[1] += pad(gpu.fan_speed + "R", 5) + " "; + lines[1] += pad(gpu.power_avg + "/" + gpu.power_cap + "W", 8) + " "; + lines[1] += pad(gpu.edge_temp + "/" + gpu.junction_temp + "C", 8); + + lines[2] = "/home/drath:" + pad(home.free + " free of " + home.total, 28); + + lines[3] = "/mnt/anime: " + pad(anime.free + " free of " + anime.total, 28); + + lcd->writeLines(lines); +} + +int main(int argc, char *argv[]) { + LCDDisplay lcd(LCD_PORT, 0010002); + + if (argc > 1 && std::string(argv[1]) == "-d") { + std::cout << "[Chrono] Running in daemon mode, " << LCD_INTERVAL_MS << "ms interval" << std::endl; + lcd.setContrast(LCD_CONTRAST); + std::chrono::milliseconds interval(LCD_INTERVAL_MS); + auto start = std::chrono::steady_clock::now(); + while (true) { + collectAndDraw(&lcd); + std::this_thread::sleep_until(start + interval); + start += interval; + } + } else { + collectAndDraw(&lcd); + } + + return 0; +} \ No newline at end of file