From 01ad58fcb8fbadfb6bea442c981321498e26c4a3 Mon Sep 17 00:00:00 2001 From: Francisco Gil Date: Thu, 9 Apr 2026 07:26:34 +0200 Subject: [PATCH] sysinfo: add QR output and display backends Add QR code generation to sysinfo using U-Boot serial and MAC address information, with support for both standard and legacy payload formats. When requested with --qr_display, show the QR code using the most suitable backend for the running system https://onedigi.atlassian.net/browse/DEL-9281 Signed-off-by: Francisco Gil --- meta-digi-dey/recipes-digi/sysinfo/sysinfo.bb | 4 +- .../recipes-digi/sysinfo/sysinfo/sysinfo | 278 ++++++++++++++++++ .../qrencode/qrencode_%.bbappend | 3 + 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 meta-digi-dey/recipes-graphics/qrencode/qrencode_%.bbappend diff --git a/meta-digi-dey/recipes-digi/sysinfo/sysinfo.bb b/meta-digi-dey/recipes-digi/sysinfo/sysinfo.bb index bc089b009..75c2b9e52 100644 --- a/meta-digi-dey/recipes-digi/sysinfo/sysinfo.bb +++ b/meta-digi-dey/recipes-digi/sysinfo/sysinfo.bb @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2022, Digi International Inc. +# Copyright (C) 2016-2026, Digi International Inc. SUMMARY = "Digi's system info utility" SECTION = "base" @@ -9,6 +9,8 @@ SRC_URI = "file://sysinfo" S = "${WORKDIR}" +RDEPENDS:${PN} += "qrencode" + do_install() { install -d ${D}${bindir} install -m 0755 sysinfo ${D}${bindir} diff --git a/meta-digi-dey/recipes-digi/sysinfo/sysinfo/sysinfo b/meta-digi-dey/recipes-digi/sysinfo/sysinfo/sysinfo index bfaafd9c1..1a4de05cd 100755 --- a/meta-digi-dey/recipes-digi/sysinfo/sysinfo/sysinfo +++ b/meta-digi-dey/recipes-digi/sysinfo/sysinfo/sysinfo @@ -16,6 +16,253 @@ #=============================================================================== FW_PRINTENV="$(which fw_printenv)" +QRENCODE="$(which qrencode)" +WESTON_IMAGE="$(which weston-image)" +FBSPLASH="$(which fbsplash)" +QR_PREFIX="50000000-01;A" +QR_TEMP_FILES="" +QR_VIEWER_PID="" + +usage() { + echo "Usage:" + echo " sysinfo" + echo " sysinfo --qr_display" +} + +add_temp_file() { + QR_TEMP_FILES="${QR_TEMP_FILES} $1" +} + +cleanup_temp_files() { + for f in ${QR_TEMP_FILES}; do + [ -n "${f}" ] && rm -f "${f}" + done +} + +# Compose the compact payload encoded in the QR from U-Boot environment data. +get_qr_payload() { + if [ -x "${FW_PRINTENV}" ]; then + QR_SERIAL="$("${FW_PRINTENV}" -n "serial#" 2>/dev/null)" + QR_MACADDR="$("${FW_PRINTENV}" -n "ethaddr" 2>/dev/null)" + fi + + if [ -z "${QR_SERIAL}" ]; then + echo "Error: unable to read 'serial#' from fw_printenv" >&2 + return 1 + fi + + if [ -z "${QR_MACADDR}" ]; then + echo "Error: unable to read 'ethaddr' from fw_printenv" >&2 + return 1 + fi + + printf "%s;%s;MAC%s\n" "${QR_PREFIX}" "${QR_SERIAL}" "$(echo "${QR_MACADDR}" | tr -d ':')" +} + +get_framebuffer_device() { + if [ -e "/dev/fb0" ]; then + echo "/dev/fb0" + return 0 + fi + + ls /dev/fb* 2>/dev/null | grep -m 1 '^/dev/fb' +} + +get_framebuffer_size() { + FB_DEVICE="$1" + FB_NAME="$(basename "${FB_DEVICE}")" + FB_SIZE_PATH="/sys/class/graphics/${FB_NAME}/virtual_size" + + if [ -r "${FB_SIZE_PATH}" ]; then + tr ',' ' ' < "${FB_SIZE_PATH}" + return 0 + fi + + return 1 +} + +is_weston_running() { + pidof weston >/dev/null 2>&1 +} + +wait_for_keypress() { + if [ ! -t 0 ]; then + return 0 + fi + + echo "Press any key to close the QR code" + old_stty="$(stty -g 2>/dev/null)" || return 0 + stty -echo -icanon min 1 time 0 2>/dev/null || return 0 + dd bs=1 count=1 >/dev/null 2>&1 + stty "${old_stty}" 2>/dev/null || true +} + +print_qr() { + if [ ! -x "${QRENCODE}" ]; then + echo "Error: qrencode is not installed" >&2 + return 1 + fi + + QR_PAYLOAD="$(get_qr_payload)" || return 1 + echo "Payload: ${QR_PAYLOAD}" + printf "%s" "${QR_PAYLOAD}" | "${QRENCODE}" -t ANSIUTF8 + echo "" + echo "This QR code is used to provision the device in Digi Device Cloud." +} + +# PNG output is used by the Wayland/Weston path. +generate_qr_png() { + QR_PAYLOAD="$(get_qr_payload)" || return 1 + QR_PNG_PATH="/tmp/sysinfo-qr.png" + + "${QRENCODE}" -o "${QR_PNG_PATH}" -s 8 -m 2 "${QR_PAYLOAD}" || return 1 + echo "${QR_PNG_PATH}" +} + +# fbsplash only accepts raw PPM images, so convert qrencode ASCII output into +# a binary P6 image and generate a small ini file to center it on screen. +generate_qr_ppm() { + FB_DEVICE="$1" + QR_PAYLOAD="$(get_qr_payload)" || return 1 + QR_ASCII_PATH="/tmp/sysinfo-qr.txt" + QR_MATRIX_PATH="/tmp/sysinfo-qr.matrix" + QR_PPM_PATH="/tmp/sysinfo-qr.ppm" + QR_INI_PATH="/tmp/sysinfo-qr.ini" + FB_SIZE="$(get_framebuffer_size "${FB_DEVICE}")" + add_temp_file "${QR_ASCII_PATH}" + add_temp_file "${QR_MATRIX_PATH}" + add_temp_file "${QR_PPM_PATH}" + add_temp_file "${QR_INI_PATH}" + + if [ -n "${FB_SIZE}" ]; then + FB_WIDTH="$(echo "${FB_SIZE}" | awk '{print $1}')" + FB_HEIGHT="$(echo "${FB_SIZE}" | awk '{print $2}')" + fi + + "${QRENCODE}" -t ASCII -o "${QR_ASCII_PATH}" "${QR_PAYLOAD}" || return 1 + + awk ' + BEGIN { + height = 0 + width = 0 + } + { + line = $0 + if (length(line) > width) { + width = length(line) + } + lines[++height] = line + } + END { + if (height == 0) { + exit 1 + } + # qrencode ASCII uses double-width horizontal cells. Collapse each pair + # into a single logical QR module encoded as 1/0. + module_width = int((width + 1) / 2) + print module_width, height + for (y = 1; y <= height; y++) { + line = lines[y] + while (length(line) < module_width * 2) { + line = line " " + } + row = "" + for (x = 1; x <= module_width * 2; x += 2) { + pair = substr(line, x, 2) + row = row (index(pair, "#") ? "1" : "0") + } + print row + } + } + ' "${QR_ASCII_PATH}" > "${QR_MATRIX_PATH}" || return 1 + + IFS=' ' read -r QR_WIDTH QR_HEIGHT < "${QR_MATRIX_PATH}" || return 1 + QR_SCALE=4 + IMG_WIDTH=$((QR_WIDTH * QR_SCALE)) + IMG_HEIGHT=$((QR_HEIGHT * QR_SCALE)) + + IMG_LEFT=0 + IMG_TOP=0 + if [ -n "${FB_WIDTH}" ] && [ "${FB_WIDTH}" -gt "${IMG_WIDTH}" ] 2>/dev/null; then + IMG_LEFT=$(((FB_WIDTH - IMG_WIDTH) / 2)) + fi + if [ -n "${FB_HEIGHT}" ] && [ "${FB_HEIGHT}" -gt "${IMG_HEIGHT}" ] 2>/dev/null; then + IMG_TOP=$(((FB_HEIGHT - IMG_HEIGHT) / 2)) + fi + + { + echo "BAR_LEFT=0" + echo "BAR_TOP=0" + echo "BAR_WIDTH=0" + echo "BAR_HEIGHT=0" + echo "BAR_R=0" + echo "BAR_G=0" + echo "BAR_B=0" + echo "IMG_LEFT=${IMG_LEFT}" + echo "IMG_TOP=${IMG_TOP}" + } > "${QR_INI_PATH}" || return 1 + + printf 'P6\n%s %s\n255\n' "${IMG_WIDTH}" "${IMG_HEIGHT}" > "${QR_PPM_PATH}" || return 1 + + awk -v scale="${QR_SCALE}" -v width="${QR_WIDTH}" ' + NR == 1 { next } + { + row = $0 + # Expand each logical QR module into a scale x scale pixel block. + for (repeat_y = 0; repeat_y < scale; repeat_y++) { + for (i = 1; i <= width; i++) { + pixel = substr(row, i, 1) + color = (pixel == "1") ? 0 : 255 + for (repeat_x = 0; repeat_x < scale; repeat_x++) { + printf "%c%c%c", color, color, color + } + } + } + } + ' "${QR_MATRIX_PATH}" >> "${QR_PPM_PATH}" || return 1 + + echo "${QR_PPM_PATH}" +} + +# On graphical images, rely on Weston to render the PNG in a proper window. +display_qr_wayland() { + if [ -x "${WESTON_IMAGE}" ] && [ -n "${WAYLAND_DISPLAY}" ] && is_weston_running; then + QR_PNG_PATH="$(generate_qr_png)" || return 1 + "${WESTON_IMAGE}" "${QR_PNG_PATH}" >/dev/null 2>/dev/null & + QR_VIEWER_PID=$! + return 0 + fi + + return 1 +} + +# Select the most suitable display backend for the current runtime. +display_qr() { + FB_DEVICE="$(get_framebuffer_device)" + + if [ -n "${SYSINFO_QR_DISPLAY_HOOK}" ] && [ -x "${SYSINFO_QR_DISPLAY_HOOK}" ]; then + QR_PNG_PATH="$(generate_qr_png)" || return 1 + "${SYSINFO_QR_DISPLAY_HOOK}" "${QR_PNG_PATH}" + return $? + fi + + display_qr_wayland && return 0 + + if [ -x "${FBSPLASH}" ] && [ -n "${FB_DEVICE}" ]; then + QR_PPM_PATH="$(generate_qr_ppm "${FB_DEVICE}")" || return 1 + QR_INI_PATH="/tmp/sysinfo-qr.ini" + echo "Framebuffer device: ${FB_DEVICE}" + "${FBSPLASH}" -s "${QR_PPM_PATH}" -d "${FB_DEVICE}" -i "${QR_INI_PATH}" -c + return $? + fi + + echo "Error: no display backend available" >&2 + echo "Detected framebuffer: ${FB_DEVICE:-[NONE]}" >&2 + echo "Set SYSINFO_QR_DISPLAY_HOOK or use Weston/fbsplash-capable images" >&2 + return 1 +} + +trap cleanup_temp_files EXIT INT TERM make_report(){ echo "--------------------------------------" @@ -336,6 +583,28 @@ fi DATE="$(date "+%Y%m%d%H%M%S")" REPORT_PATH="/tmp/sysinfo-${DEY_VERSION}-${BOARD_SN}-${DATE}" +DISPLAY_QR="0" + +while [ $# -gt 0 ]; do + case "$1" in + --qr_display) + DISPLAY_QR="1" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: unknown argument '$1'" >&2 + usage >&2 + exit 1 + ;; + esac +done + +print_qr || exit $? + ( echo "-------------------------------------" echo "- -" @@ -368,3 +637,12 @@ tar -zhcf "${REPORT_PATH}.tar.gz" -C $(dirname ${REPORT_PATH}) $(basename "${REP echo "Report generated in ${REPORT_PATH}.tar.gz" rm -rf "${REPORT_PATH}.txt" + +if [ "${DISPLAY_QR}" = "1" ]; then + display_qr || exit $? + wait_for_keypress + if [ -n "${QR_VIEWER_PID}" ]; then + kill "${QR_VIEWER_PID}" >/dev/null 2>&1 || true + wait "${QR_VIEWER_PID}" 2>/dev/null || true + fi +fi diff --git a/meta-digi-dey/recipes-graphics/qrencode/qrencode_%.bbappend b/meta-digi-dey/recipes-graphics/qrencode/qrencode_%.bbappend new file mode 100644 index 000000000..fac03e315 --- /dev/null +++ b/meta-digi-dey/recipes-graphics/qrencode/qrencode_%.bbappend @@ -0,0 +1,3 @@ +# Copyright (C) 2026, Digi International Inc. + +PACKAGECONFIG += "tools"