/* * Userspace Daemon to send data to Pi Pico CPU LED Act Meter * Author: Ben @ LostGeek.NET * Date: 03/02/2026 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_CPUS 32 #define NUM_LEDS 12 #define UPDATE_US 33300 // 33.3ms static uint64_t prev_idle[MAX_CPUS]; static uint64_t prev_total[MAX_CPUS]; static uint8_t prev_leds[NUM_LEDS] = {0}; /* ANSI colors */ #define ANSI_RESET "\033[0m" #define ANSI_BLACK "\033[30m" #define ANSI_YELLOW "\033[33m" #define ANSI_CLEAR "\033[H\033[J" // ----------------- Efficient CPU load reader ----------------- static int read_cpu_load(double *loads, int cpus) { int fd = open("/proc/stat", O_RDONLY | O_CLOEXEC); if (fd < 0) return -1; char buf[4096]; ssize_t n = read(fd, buf, sizeof(buf)-1); close(fd); if (n <= 0) return -1; buf[n] = '\0'; char *line = buf; int cpu = 0; while (cpu < cpus) { char *next = strchr(line, '\n'); if (!next) break; *next = 0; if (strncmp(line, "cpu", 3) == 0 && line[3] != ' ') { uint64_t user, nice, system, idle; if (sscanf(line, "cpu%*d %lu %lu %lu %lu", &user, &nice, &system, &idle) == 4) { uint64_t total = user + nice + system + idle; uint64_t totald = total - prev_total[cpu]; uint64_t idled = idle - prev_idle[cpu]; prev_total[cpu] = total; prev_idle[cpu] = idle; loads[cpu] = totald ? (double)(totald - idled) / totald : 0.0; cpu++; } } line = next + 1; } return 0; } // ----------------- Efficient Pico finder ----------------- //static int find_pico(char *path, size_t len) //{ // for (int i = 0; i < 8; i++) { // snprintf(path, len, "/dev/ttyACM%d", i); // if (access(path, W_OK) == 0) // return 0; // } // return -1; //} static int find_pico(char *path, size_t len) { // Hard-coded to /dev/ttyACM1 snprintf(path, len, "/dev/ttyACM2"); // Check if it’s writable if (access(path, W_OK) == 0) return 0; return -1; // Pico not found } // ----------------- Minimal debug output ----------------- static void draw_debug(double *loads, int cpus) { static char buffer[1024]; char *ptr = buffer; ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), ANSI_CLEAR "CPU activity (yellow = active)\n\n"); for (int i = 0; i < cpus; i++) { if (loads[i] > 0.10) ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), ANSI_YELLOW "O "); else ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), ANSI_BLACK "O "); } ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), ANSI_RESET "\n"); fwrite(buffer, 1, ptr - buffer, stdout); fflush(stdout); } // ----------------- Main ----------------- int main(int argc, char **argv) { int debug = (argc > 1 && strcmp(argv[1], "-d") == 0); int cpus = get_nprocs(); if (cpus > MAX_CPUS) cpus = MAX_CPUS; double loads[MAX_CPUS] = {0}; int fd = -1; char serial_path[64]; if (!debug) { if (find_pico(serial_path, sizeof(serial_path)) != 0) { fprintf(stderr, "No Pico found (/dev/ttyACM*)\n"); return 1; } fd = open(serial_path, O_WRONLY | O_NOCTTY | O_SYNC | O_NONBLOCK); if (fd < 0) { perror("open serial"); return 1; } usleep(1000000); // Pico reset delay } struct timespec ts; while (1) { if (read_cpu_load(loads, cpus) == 0) { if (debug) { draw_debug(loads, cpus); } else { uint8_t leds[NUM_LEDS] = {0}; bool changed = false; for (int i = 0; i < NUM_LEDS && i < cpus; i++) { leds[i] = (uint8_t)(loads[i] * 255.0); if (leds[i] != prev_leds[i]) changed = true; } if (changed) { write(fd, leds, NUM_LEDS); memcpy(prev_leds, leds, NUM_LEDS); } } } // Sleep precisely ts.tv_sec = 0; ts.tv_nsec = UPDATE_US * 1000; nanosleep(&ts, NULL); } if (fd >= 0) close(fd); return 0; }