first attempt at ui
This commit is contained in:
1 parent
f8b978df15
commit
45c9f55473
6 files changed
+382
-1
No files matched your search
@@ -47,7 +47,7 @@ define Device/generic-mf68e
|
||||
CMDLINE := "earlycon console=ttyMSM0,115200 quiet loglevel=0 vt.global_cursor_default=0 vt.cur_default=0 root=/dev/mmcblk0p14 rootfstype=squashfs rootwait"
|
||||
DEVICE_PACKAGES := configs-mf68e wpad-basic-wolfssl rmtfs uci-usb-gadget \
|
||||
block-mount f2fs-tools prepare-rootfs-data \
|
||||
msm-firmware-dumper power-button-daemon kmod-fbtft-gc9107 display-monitor
|
||||
msm-firmware-dumper power-button-daemon kmod-fbtft-gc9107 display-monitor router-display
|
||||
endef
|
||||
TARGET_DEVICES += generic-mf68e
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=router-display
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
PKG_QRGEN_NAME:=QR-Code-generator
|
||||
PKG_QRGEN_VERSION:=v1.8.0
|
||||
PKG_QRGEN_SOURCE:=$(PKG_QRGEN_VERSION).tar.gz
|
||||
PKG_QRGEN_URL:=https://github.com/nayuki/QR-Code-generator/archive/refs/tags
|
||||
PKG_QRGEN_HASH:=skip
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/router-display
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Framebuffer router status display
|
||||
DEPENDS:=+libfreetype +dejavu-fonts-ttf-DejaVuSansMono
|
||||
endef
|
||||
|
||||
define Package/router-display/description
|
||||
Display router status on RGB565 framebuffer
|
||||
endef
|
||||
|
||||
define Download/qrcodegen
|
||||
URL:=$(PKG_QRGEN_URL)
|
||||
FILE:=qrcodegen-$(PKG_QRGEN_VERSION).tar.gz
|
||||
URL_FILE:=$(PKG_QRGEN_VERSION).tar.gz
|
||||
HASH:=$(PKG_QRGEN_HASH)
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
|
||||
# Copiar nuestro código
|
||||
$(CP) ./src/main.c $(PKG_BUILD_DIR)/
|
||||
|
||||
# Descargar y extraer qrcodegen
|
||||
$(eval $(call Download,qrcodegen))
|
||||
tar -xzf $(DL_DIR)/qrcodegen-$(PKG_QRGEN_VERSION).tar.gz -C $(PKG_BUILD_DIR)
|
||||
|
||||
# Copiar solo los archivos C necesarios
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_QRGEN_NAME)-*/c/qrcodegen.c $(PKG_BUILD_DIR)/
|
||||
$(CP) $(PKG_BUILD_DIR)/$(PKG_QRGEN_NAME)-*/c/qrcodegen.h $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) \
|
||||
-I$(STAGING_DIR)/usr/include/freetype2 \
|
||||
$(TARGET_LDFLAGS) \
|
||||
-o $(PKG_BUILD_DIR)/router-display \
|
||||
$(PKG_BUILD_DIR)/main.c \
|
||||
$(PKG_BUILD_DIR)/qrcodegen.c \
|
||||
-lfreetype -lm
|
||||
endef
|
||||
|
||||
define Package/router-display/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/router-display $(1)/usr/bin/
|
||||
|
||||
# $(INSTALL_DIR) $(1)/etc/init.d
|
||||
# $(INSTALL_BIN) ./files/router-display.init $(1)/etc/init.d/router-display
|
||||
|
||||
# $(INSTALL_DIR) $(1)/usr/sbin
|
||||
# $(INSTALL_BIN) ./files/update-display.sh $(1)/usr/sbin/update-display
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,router-display))
|
||||
@@ -0,0 +1,40 @@
|
||||
┌────────────────────────────┐
|
||||
│ MF68E │
|
||||
│ │
|
||||
│ ██ ██ ████ ██ ██ │
|
||||
│ ██ ██ ██ ██ ██ ██ │
|
||||
│ ██ ██ ██████ ██ ██ │
|
||||
│ ██ ██████ ██ │
|
||||
│ ████ ██ ████ │
|
||||
│ ██ ██ ██ ██ ██ ██ │
|
||||
│ ██ ██ ████ ██ ██ │
|
||||
│ │
|
||||
│ │
|
||||
│ Orange ES 4G │
|
||||
│ ██████████████████░░░░ 85% │
|
||||
└────────────────────────────┘
|
||||
|
||||
void draw_display() {
|
||||
// Hostname derecha
|
||||
draw_text("MF68E", 120, 2, FONT_10PX, ALIGN_RIGHT);
|
||||
|
||||
// QR centrado: (128 - 96) / 2 = 16px offset
|
||||
draw_qr_code(16, 16, 96); // x=16 para centrar
|
||||
|
||||
// Operador + Red
|
||||
draw_text("Orange ES", 2, 112, FONT_8PX, ALIGN_LEFT);
|
||||
draw_text("4G", 122, 112, FONT_8PX, ALIGN_RIGHT);
|
||||
|
||||
// Batería
|
||||
char bar[32];
|
||||
int percent = get_battery_percent();
|
||||
int filled = 18 * percent / 100;
|
||||
|
||||
for(int i = 0; i < filled; i++)
|
||||
bar[i] = 0x2588;
|
||||
for(int i = filled; i < 18; i++)
|
||||
bar[i] = 0x2591;
|
||||
|
||||
sprintf(bar + 18, " %d%%", percent);
|
||||
draw_text(bar, 0, 120, FONT_8PX);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "qrcodegen.h"
|
||||
|
||||
#define WIDTH 128
|
||||
#define HEIGHT 128
|
||||
#define RGB565(r,g,b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
|
||||
|
||||
#define COLOR_WHITE RGB565(255, 255, 255)
|
||||
#define COLOR_BLACK RGB565(0, 0, 0)
|
||||
|
||||
typedef struct {
|
||||
uint16_t data[WIDTH * HEIGHT];
|
||||
} Framebuffer;
|
||||
|
||||
typedef struct {
|
||||
int battery;
|
||||
char operator[32];
|
||||
char network_type[8];
|
||||
char ssid[64];
|
||||
char password[64];
|
||||
char hostname[32];
|
||||
} DisplayConfig;
|
||||
|
||||
void fb_init(Framebuffer *fb) {
|
||||
memset(fb->data, 0, sizeof(fb->data));
|
||||
}
|
||||
|
||||
void fb_put_pixel(Framebuffer *fb, int x, int y, uint16_t color) {
|
||||
if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
|
||||
fb->data[y * WIDTH + x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void fb_draw_rect(Framebuffer *fb, int x, int y, int w, int h, uint16_t color) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
fb_put_pixel(fb, x + i, y + j, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fb_draw_char(Framebuffer *fb, FT_Face face, char c, int x, int y, int size) {
|
||||
FT_Set_Pixel_Sizes(face, 0, size);
|
||||
|
||||
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
FT_Bitmap bitmap = slot->bitmap;
|
||||
|
||||
for (unsigned int row = 0; row < bitmap.rows; row++) {
|
||||
for (unsigned int col = 0; col < bitmap.width; col++) {
|
||||
int px = x + slot->bitmap_left + col;
|
||||
int py = y - slot->bitmap_top + row;
|
||||
|
||||
unsigned char gray = bitmap.buffer[row * bitmap.pitch + col];
|
||||
if (gray > 128) {
|
||||
fb_put_pixel(fb, px, py, COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fb_draw_text(Framebuffer *fb, FT_Face face, const char *text, int x, int y, int size) {
|
||||
int pen_x = x;
|
||||
|
||||
for (const char *p = text; *p; p++) {
|
||||
FT_Set_Pixel_Sizes(face, 0, size);
|
||||
|
||||
if (FT_Load_Char(face, *p, FT_LOAD_RENDER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
FT_Bitmap bitmap = slot->bitmap;
|
||||
|
||||
for (unsigned int row = 0; row < bitmap.rows; row++) {
|
||||
for (unsigned int col = 0; col < bitmap.width; col++) {
|
||||
int px = pen_x + slot->bitmap_left + col;
|
||||
int py = y - slot->bitmap_top + row;
|
||||
|
||||
unsigned char gray = bitmap.buffer[row * bitmap.pitch + col];
|
||||
if (gray > 128) {
|
||||
fb_put_pixel(fb, px, py, COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pen_x += slot->advance.x >> 6;
|
||||
}
|
||||
}
|
||||
|
||||
void fb_draw_qr(Framebuffer *fb, const char *text, int x, int y, int size) {
|
||||
uint8_t qr_data[qrcodegen_BUFFER_LEN_MAX];
|
||||
uint8_t temp_buffer[qrcodegen_BUFFER_LEN_MAX];
|
||||
|
||||
bool ok = qrcodegen_encodeText(text, temp_buffer, qr_data,
|
||||
qrcodegen_Ecc_LOW,
|
||||
qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MAX,
|
||||
qrcodegen_Mask_AUTO, true);
|
||||
|
||||
if (!ok) {
|
||||
fprintf(stderr, "QR generation failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int qr_size = qrcodegen_getSize(qr_data);
|
||||
int module_size = size / qr_size;
|
||||
|
||||
for (int row = 0; row < qr_size; row++) {
|
||||
for (int col = 0; col < qr_size; col++) {
|
||||
if (qrcodegen_getModule(qr_data, col, row)) {
|
||||
fb_draw_rect(fb,
|
||||
x + col * module_size,
|
||||
y + row * module_size,
|
||||
module_size, module_size,
|
||||
COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate_display(Framebuffer *fb, DisplayConfig *cfg, FT_Face face) {
|
||||
fb_init(fb);
|
||||
|
||||
// 1. Hostname arriba derecha
|
||||
int text_w = strlen(cfg->hostname) * 6;
|
||||
fb_draw_text(fb, face, cfg->hostname, WIDTH - text_w - 2, 10, 9);
|
||||
|
||||
// 2. QR WiFi centrado
|
||||
char wifi_qr[256];
|
||||
snprintf(wifi_qr, sizeof(wifi_qr),
|
||||
"WIFI:T:WPA;S:%s;P:%s;;", cfg->ssid, cfg->password);
|
||||
fb_draw_qr(fb, wifi_qr, 16, 16, 96);
|
||||
|
||||
// 3. Operador + tipo red
|
||||
fb_draw_text(fb, face, cfg->operator, 2, 118, 8);
|
||||
int net_w = strlen(cfg->network_type) * 5;
|
||||
fb_draw_text(fb, face, cfg->network_type, WIDTH - net_w - 2, 118, 8);
|
||||
|
||||
// 4. Barra batería
|
||||
char bar[64];
|
||||
int bar_chars = 18;
|
||||
int filled = (bar_chars * cfg->battery) / 100;
|
||||
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar[i] = 0x2588; // █
|
||||
}
|
||||
for (int i = filled; i < bar_chars; i++) {
|
||||
bar[i] = 0x2591; // ░
|
||||
}
|
||||
sprintf(bar + bar_chars, " %d%%", cfg->battery);
|
||||
|
||||
fb_draw_text(fb, face, bar, 0, 126, 8);
|
||||
}
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
fprintf(stderr, "Usage: %s [OPTIONS]\n", prog);
|
||||
fprintf(stderr, " -b NUM Battery (0-100)\n");
|
||||
fprintf(stderr, " -n NAME Operator name\n");
|
||||
fprintf(stderr, " -t TYPE Network type (4G, LTE)\n");
|
||||
fprintf(stderr, " -s SSID WiFi SSID\n");
|
||||
fprintf(stderr, " -p PASS WiFi password\n");
|
||||
fprintf(stderr, " -h HOST Hostname\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
DisplayConfig cfg = {
|
||||
.battery = 100,
|
||||
.operator = "Unknown",
|
||||
.network_type = "4G",
|
||||
.ssid = "WiFi",
|
||||
.password = "password",
|
||||
.hostname = "Router"
|
||||
};
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "b:n:t:s:p:h:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'b': cfg.battery = atoi(optarg); break;
|
||||
case 'n': strncpy(cfg.operator, optarg, 31); break;
|
||||
case 't': strncpy(cfg.network_type, optarg, 7); break;
|
||||
case 's': strncpy(cfg.ssid, optarg, 63); break;
|
||||
case 'p': strncpy(cfg.password, optarg, 63); break;
|
||||
case 'h': strncpy(cfg.hostname, optarg, 31); break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Init FreeType
|
||||
FT_Library ft;
|
||||
if (FT_Init_FreeType(&ft)) {
|
||||
fprintf(stderr, "FreeType init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
const char *fonts[] = {
|
||||
"/usr/share/fonts/dejavu/DejaVuSansMono.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
||||
NULL
|
||||
};
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; fonts[i]; i++) {
|
||||
if (FT_New_Face(ft, fonts[i], 0, &face) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
fprintf(stderr, "No font found\n");
|
||||
FT_Done_FreeType(ft);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Generate
|
||||
Framebuffer fb;
|
||||
generate_display(&fb, &cfg, face);
|
||||
|
||||
// Output raw RGB565
|
||||
fwrite(fb.data, sizeof(uint16_t), WIDTH * HEIGHT, stdout);
|
||||
|
||||
// Cleanup
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
procd_open_instance
|
||||
procd_set_param command /bin/sh -c "while true; do /usr/sbin/update-display; sleep 30; done"
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
BATTERY=$(cat /sys/class/power_supply/battery/capacity 2>/dev/null || echo 100)
|
||||
OPERATOR=$(uci get network.modem.operator 2>/dev/null || echo "Unknown")
|
||||
NETWORK="4G"
|
||||
SSID=$(uci get wireless.@wifi-iface[0].ssid 2>/dev/null || echo "WiFi")
|
||||
PASSWORD=$(uci get wireless.@wifi-iface[0].key 2>/dev/null || echo "")
|
||||
HOSTNAME=$(uci get system.@system[0].hostname 2>/dev/null || echo "Router")
|
||||
|
||||
/usr/bin/router-display \
|
||||
-b "$BATTERY" \
|
||||
-n "$OPERATOR" \
|
||||
-t "$NETWORK" \
|
||||
-s "$SSID" \
|
||||
-p "$PASSWORD" \
|
||||
-h "$HOSTNAME" > /dev/fb0
|
||||
Reference in new issue
Block a user