Files
GfxCanvas/main.cpp
2026-04-09 21:16:01 -03:00

286 lines
9.3 KiB
C++

// 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <signal.h>
#include <unistd.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>
// ---------------------------------------------------------------------------
// 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<sockaddr_in*>(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<WifiScanResult*>(arg);
auto* gnlh = static_cast<genlmsghdr*>(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<const uint8_t*>(
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<WifiStationResult*>(arg);
auto* gnlh = static_cast<genlmsghdr*>(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<int8_t>(
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<int16_t>(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;
}