// main.cpp — Info display for SSD1306 128x64 using gfx::LinuxFBDisplay. // // Build: see CMakeLists.txt // Run: ./gfx_info_display # defaults to /dev/fb1 // ./gfx_info_display /dev/fb0 # override #include "gfx_canvas.h" #include "gfx_font_5x7.h" #include "linux_fb_display.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // Configuration // --------------------------------------------------------------------------- constexpr int16_t SCREEN_W = 128; constexpr int16_t SCREEN_H = 64; // 16 yellow on top and 48 blue on bottom constexpr const char* ETH_IFACE = "eth0"; constexpr const char* WLAN_IFACE = "wlan0"; // --------------------------------------------------------------------------- // Network helper // --------------------------------------------------------------------------- static std::string get_ip(const char* iface_name) { struct ifaddrs* ifaddr = nullptr; if (getifaddrs(&ifaddr) == -1) return "err"; std::string result = "down"; for (auto* ifa = ifaddr; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) continue; if (ifa->ifa_addr->sa_family != AF_INET) continue; if (std::strcmp(ifa->ifa_name, iface_name) != 0) continue; char buf[INET_ADDRSTRLEN]; auto* sa = reinterpret_cast(ifa->ifa_addr); inet_ntop(AF_INET, &sa->sin_addr, buf, sizeof(buf)); result = buf; break; } freeifaddrs(ifaddr); return result; } // --------------------------------------------------------------------------- // Wi-Fi helper — SSID and RSSI via nl80211 / libnl-genl // --------------------------------------------------------------------------- struct WifiScanResult { char ssid[33]; uint8_t bssid[6]; bool have_ssid = false; bool have_bssid = false; }; struct WifiStationResult { int rssi = 0; bool have_rssi = false; }; static int scan_handler(struct nl_msg* msg, void* arg) { auto* result = static_cast(arg); auto* gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); struct nlattr* tb[NL80211_ATTR_MAX + 1]; nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr); if (!tb[NL80211_ATTR_BSS]) return NL_SKIP; struct nlattr* bss[NL80211_BSS_MAX + 1]; nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], nullptr); // Only process the BSS we are associated with. if (!bss[NL80211_BSS_STATUS]) return NL_SKIP; if (nla_get_u32(bss[NL80211_BSS_STATUS]) != NL80211_BSS_STATUS_ASSOCIATED) return NL_SKIP; if (bss[NL80211_BSS_BSSID]) { std::memcpy(result->bssid, nla_data(bss[NL80211_BSS_BSSID]), 6); result->have_bssid = true; } // Walk Information Elements to find SSID (IE id 0). if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) { const auto* ie = static_cast( nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); int ie_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]); while (ie_len >= 2) { if (ie[0] == 0 && ie[1] > 0 && ie[1] <= 32) { std::memcpy(result->ssid, ie + 2, ie[1]); result->ssid[ie[1]] = '\0'; result->have_ssid = true; break; } ie_len -= 2 + ie[1]; ie += 2 + ie[1]; } } return NL_SKIP; } static int station_handler(struct nl_msg* msg, void* arg) { auto* result = static_cast(arg); auto* gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); struct nlattr* tb[NL80211_ATTR_MAX + 1]; nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr); if (!tb[NL80211_ATTR_STA_INFO]) return NL_SKIP; struct nlattr* sta[NL80211_STA_INFO_MAX + 1]; nla_parse_nested(sta, NL80211_STA_INFO_MAX, tb[NL80211_ATTR_STA_INFO], nullptr); if (sta[NL80211_STA_INFO_SIGNAL]) { result->rssi = static_cast( nla_get_u8(sta[NL80211_STA_INFO_SIGNAL])); result->have_rssi = true; } return NL_SKIP; } static bool get_wifi_info(const char* iface, char* ssid_out, int* rssi_out) { struct nl_sock* sock = nl_socket_alloc(); if (!sock) return false; if (genl_connect(sock) < 0) { nl_socket_free(sock); return false; } int nl80211_id = genl_ctrl_resolve(sock, "nl80211"); if (nl80211_id < 0) { nl_socket_free(sock); return false; } unsigned int if_index = if_nametoindex(iface); if (!if_index) { nl_socket_free(sock); return false; } // --- GET_SCAN dump → find associated BSS, extract SSID --- WifiScanResult scan{}; nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, scan_handler, &scan); struct nl_msg* msg = nlmsg_alloc(); genlmsg_put(msg, 0, 0, nl80211_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0); nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); nl_send_auto(sock, msg); nlmsg_free(msg); nl_recvmsgs_default(sock); if (!scan.have_ssid) { nl_socket_free(sock); return false; } std::strncpy(ssid_out, scan.ssid, 32); ssid_out[32] = '\0'; // --- GET_STATION for the AP → extract RSSI --- WifiStationResult station{}; nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, station_handler, &station); msg = nlmsg_alloc(); genlmsg_put(msg, 0, 0, nl80211_id, 0, 0, NL80211_CMD_GET_STATION, 0); nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); nla_put(msg, NL80211_ATTR_MAC, 6, scan.bssid); nl_send_auto(sock, msg); nlmsg_free(msg); nl_recvmsgs_default(sock); if (station.have_rssi) *rssi_out = station.rssi; nl_socket_free(sock); return true; } // --------------------------------------------------------------------------- // Text centering helper // --------------------------------------------------------------------------- static void draw_centered(gfx::GfxCanvas& disp, int16_t y, const char* text) { int16_t bx, by; uint16_t bw, bh; disp.getTextBounds(text, 0, 0, bx, by, bw, bh); disp.drawString((SCREEN_W - static_cast(bw)) / 2, y, text, gfx::colors::white); } // --------------------------------------------------------------------------- // Signal handling // --------------------------------------------------------------------------- static volatile sig_atomic_t g_running = 1; static void signal_handler(int) { g_running = 0; } // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- int main(int argc, char* argv[]) { const char* fb_device = "/dev/fb1"; if (argc > 1) fb_device = argv[1]; gfx::LinuxFBDisplay display(SCREEN_W, SCREEN_H); if (!display.open(fb_device)) return EXIT_FAILURE; display.setFont(gfx::font_5x7); display.setTextColor(gfx::colors::white); display.setTextWrap(false); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); char line_buf[64]; while (g_running) { display.fillScreen(gfx::colors::black); // --- Date and time --- std::time_t now = std::time(nullptr); std::tm* tm = std::localtime(&now); std::snprintf(line_buf, sizeof(line_buf), "%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); //draw_centered(display, 0, line_buf); display.setCursor(0, 0); display.print(line_buf); // --- Separator --- // display.drawHLine(0, 16, SCREEN_W, gfx::colors::white); // --- Hostname --- gethostname(line_buf, sizeof(line_buf) - 1); display.setCursor(0, 8); display.print(line_buf); // --- Network info --- std::string eth_ip = get_ip(ETH_IFACE); std::string wlan_ip = get_ip(WLAN_IFACE); std::snprintf(line_buf, sizeof(line_buf), "eth0: %s", eth_ip.c_str()); display.setCursor(0, 16); display.print(line_buf); std::snprintf(line_buf, sizeof(line_buf), "wlan0: %s", wlan_ip.c_str()); display.setCursor(0, 24); display.print(line_buf); // --- Wi-Fi SSID / RSSI --- char ssid[33] = {}; int rssi = 0; if (get_wifi_info(WLAN_IFACE, ssid, &rssi)) { std::snprintf(line_buf, sizeof(line_buf), "SSID: %s", ssid); display.setCursor(0, 32); display.print(line_buf); std::snprintf(line_buf, sizeof(line_buf), "RSSI: %d dBm", rssi); display.setCursor(0, 40); display.print(line_buf); } display.update(); // --- Sleep aligned to next whole second --- struct timespec ts{}; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 1; ts.tv_nsec = 0; clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, nullptr); } display.fillScreen(gfx::colors::black); std::printf("Exiting.\n"); return EXIT_SUCCESS; }