Compare commits

..

14 Commits

1340 changed files with 379521 additions and 1209 deletions

View File

@@ -158,6 +158,22 @@ chmod +x "$INSTALL_DIR/bin/clawd.js"
info "clawd symlinked to /usr/local/bin/clawd"
# Install RK3588S LVGL demo
DEVICE_MODEL="$(tr -d '\0' </proc/device-tree/model 2>/dev/null || true)"
if echo "$DEVICE_MODEL" | grep -qi 'RK3588S'; then
DEMO_SRC="$INSTALL_DIR/lib/resource/3588s/demo"
DEMO_DST="/usr/bin/demo"
if [ -f "$DEMO_SRC" ]; then
info "RK3588S detected, installing LVGL demo to $DEMO_DST"
if [ -f "$DEMO_DST" ] && [ ! -f "${DEMO_DST}.clawd-bak" ]; then
cp "$DEMO_DST" "${DEMO_DST}.clawd-bak"
info "Backup created: ${DEMO_DST}.clawd-bak"
fi
install -m 0755 "$DEMO_SRC" "$DEMO_DST"
else
warn "RK3588S demo binary not found: $DEMO_SRC"
fi
fi
# Write default config files
mkdir -p "$CONFIG_DIR"
@@ -234,9 +250,7 @@ MemoryMax=256M
CPUQuota=50%
TasksMax=64
# Sandbox (allow writes to config, tmp, and network files)
ProtectSystem=full
ReadWritePaths=$CONFIG_DIR /tmp /etc/hosts /etc/hostname
# Sandbox disabled: clawd needs to write system/config files on some devices
# Logging
StandardOutput=journal

View File

@@ -20,7 +20,7 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execFileSync } = require('child_process');
const log = require('../logger');
// ── Constants (from reference script) ────────────────────────────────────────
@@ -45,11 +45,15 @@ function _buildClientVersion(version) {
// ── State-dir helpers (mirrors reference script) ─────────────────────────────
function _resolveStateDir() {
return (
(process.env.OPENCLAW_STATE_DIR || '').trim() ||
(process.env.CLAWDBOT_STATE_DIR || '').trim() ||
path.join(os.homedir(), '.openclaw')
);
return path.join('/home/sts', '.openclaw');
}
function _ensureStsOwnership(filePath) {
try {
execFileSync('chown', ['sts:sts', filePath], { stdio: 'ignore' });
} catch (err) {
log.warn('weixin', `chown sts:sts failed for ${filePath}: ${err.message}`);
}
}
function _resolveWeixinStateDir() { return path.join(_resolveStateDir(), 'openclaw-weixin'); }
@@ -81,7 +85,9 @@ function _registerAccountId(accountId) {
fs.mkdirSync(_resolveWeixinStateDir(), { recursive: true });
const existing = _listIndexedAccountIds();
if (existing.includes(accountId)) return;
fs.writeFileSync(_resolveAccountIndexPath(), JSON.stringify([...existing, accountId], null, 2), 'utf8');
const indexPath = _resolveAccountIndexPath();
fs.writeFileSync(indexPath, JSON.stringify([...existing, accountId], null, 2), 'utf8');
_ensureStsOwnership(indexPath);
}
function _loadAccount(accountId) {
@@ -113,6 +119,7 @@ function _saveAccount(accountId, update) {
};
const filePath = _resolveAccountPath(accountId);
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
_ensureStsOwnership(filePath);
try { fs.chmodSync(filePath, 0o600); } catch (_) {}
return filePath;
}
@@ -126,7 +133,9 @@ function _clearStaleAccountsForUserId(currentAccountId, userId) {
log.info('weixin', `removing stale account with same userId: ${id}`);
try { fs.unlinkSync(_resolveAccountPath(id)); } catch (_) {}
const existing = _listIndexedAccountIds();
fs.writeFileSync(_resolveAccountIndexPath(), JSON.stringify(existing.filter(x => x !== id), null, 2), 'utf8');
const indexPath = _resolveAccountIndexPath();
fs.writeFileSync(indexPath, JSON.stringify(existing.filter(x => x !== id), null, 2), 'utf8');
_ensureStsOwnership(indexPath);
}
}
}

View File

@@ -116,6 +116,9 @@ class ClawClient {
led.status.setApps();
}
// 启动即点亮 VFD不等 WS / 联网流程
led.display.showTime();
this._startSdNotify();
// RJ45 链路轮询OpenVFD play与 WS 无关,进程起来即开始
@@ -263,11 +266,12 @@ class ClawClient {
_connect() {
if (this._stopped) return;
// AP 模式 + 无网:不建立 WS5s 后重新检查(有线经 -I ping 仍通则建立,避免热点误挡 WS
if (this._provisionMgr && this._provisionMgr.isApMode() && !hasInternet() && !hasWiredInternetProbe()) {
// AP 模式下 hasInternet() 可能被热点本地网络 / NetworkManager limited 状态误判。
// 只有明确探测到有线口可访问公网时,才允许进入 WS 连接流程并显示 Conn。
if (this._provisionMgr && this._provisionMgr.isApMode() && !hasWiredInternetProbe()) {
led.display.showAP();
log.info('clawd', 'AP 模式无网络5s 后重新检查...');
this._backoff = 1_000; // 有时立即快速重连
log.info('clawd', 'AP 模式无有线网络5s 后重新检查...');
this._backoff = 1_000; // 有线恢复时立即快速重连
this._wsFailCount = 0; // 不计入失败
setTimeout(() => this._connect(), 5_000);
return;

View File

@@ -11,7 +11,7 @@ function loadImpl() {
if (forced) {
name = forced;
} else if (isRK3588()) {
name = 'noop';
name = 'rk3588-lvgl';
} else if (isRK3566()) {
name = 'rk3566';
} else {
@@ -23,6 +23,10 @@ function loadImpl() {
log.info('led', `LED/VFD backend → rk3566-openvfd (${model || 'unknown model'})`);
return require('./led/rk3566-openvfd');
}
if (name === 'rk3588-lvgl' || name === '3588' || name === 'rk3588') {
log.info('led', `LED/VFD backend → rk3588-lvgl (${model || 'unknown model'})`);
return require('./led/rk3588-lvgl');
}
if (name === 'noop' || name === 'none' || name === 'off') {
log.info('led', `LED/VFD backend → noop (${model || 'unknown model'})`);
return require('./led/noop');

81
lib/led/rk3588-lvgl.js Normal file
View File

@@ -0,0 +1,81 @@
'use strict';
const fs = require('fs');
const log = require('../logger');
const LVGL_CMD_FIFO = process.env.CLAWD_LVGL_CMD_FIFO || '/tmp/lvgl_cmd';
function writeLvglCommand(command) {
try {
const fd = fs.openSync(LVGL_CMD_FIFO, fs.constants.O_WRONLY | fs.constants.O_NONBLOCK);
fs.writeSync(fd, `${command}\n`);
fs.closeSync(fd);
return true;
} catch (e) {
log.warn('display', `lvgl cmd failed (${command}): ${e.message}`);
return false;
}
}
class BasicLed {
constructor(name) {
this.name = name;
this._current = null;
}
on() { this._current = 'on'; log.debug('led', `[rk3588-lvgl] ${this.name} on`); }
off() { this._current = 'off'; log.debug('led', `[rk3588-lvgl] ${this.name} off`); }
blink() { this._current = 'blink'; log.debug('led', `[rk3588-lvgl] ${this.name} blink`); }
destroy() { this._current = 'off'; log.debug('led', `[rk3588-lvgl] ${this.name} destroy`); }
}
class StatusLed {
setSetup() { log.debug('led', '[rk3588-lvgl] status setup'); }
setApps() { log.debug('led', '[rk3588-lvgl] status apps'); }
off() { log.debug('led', '[rk3588-lvgl] status off'); }
}
class Display {
showAP() {
if (writeLvglCommand('show_ap')) {
log.info('display', '显示屏 → AP闪烁');
}
}
showConn() {
if (writeLvglCommand('show_conn')) {
log.info('display', '显示屏 → Conn闪烁');
}
}
showErr0() {
if (writeLvglCommand('show_err0')) {
log.info('display', '显示屏 → Err0');
}
}
showTime() {
if (writeLvglCommand('show_time')) {
log.info('display', '显示屏 → 时间');
}
}
showPin(pin) {
const s = String(pin || '').padStart(4, '0').slice(-4);
if (writeLvglCommand(`show_pin:${s}`)) {
log.info('display', `显示屏 → PIN: ${s}(闪烁)`);
}
}
}
class LanLed {
start() { log.debug('led', '[rk3588-lvgl] LAN start ignored'); }
stop() { log.debug('led', '[rk3588-lvgl] LAN stop ignored'); }
}
const led = new BasicLed('wifi');
led.bt = new BasicLed('bt');
led.status = new StatusLed();
led.display = new Display();
led.lan = new LanLed();
module.exports = led;

View File

@@ -10,6 +10,7 @@ const AP_IP = '10.42.0.1';
const AP_PASSWORD = '12345678';
const AP_IFACE = process.env.CLAWD_WIFI_IFACE || '';
const CON_NAME = 'clawd-hotspot';
const AP_RETRY_TOKEN_FILE = '/run/clawd-ap-retry.token';
/** 产品 RJ45 在 sysfs 中的默认名;等价于检测 `cat /sys/class/net/end0/carrier` */
const DEFAULT_ETH_IFACE = 'end0';
@@ -92,28 +93,15 @@ function hasLanCableCarrier() {
return hasWiredCarrier();
}
function _tryPingInternet() {
function _tryPingDefaultInternet() {
try {
run('ping -c 1 -W 3 8.8.8.8');
return true;
} catch (_) {}
// 开热点时默认路由可能走 wlan无 -I 的 ping 会误判;指定有线口再试
const wired = getWiredIfaceWithCarrier();
if (wired) {
try {
run(`ping -c 1 -W 3 -I ${wired} 8.8.8.8`);
return true;
} catch (_) {}
}
return false;
}
/**
* 仅经有线口 ping 公网(不依赖默认路由)。
* AP 开启时 hasInternet() 易误判;维持 WS / 网络监视时用此兜底。
*/
function hasWiredInternetProbe() {
function _tryPingWiredInternet() {
const wired = getWiredIfaceWithCarrier();
if (!wired) return false;
try {
@@ -124,18 +112,31 @@ function hasWiredInternetProbe() {
}
/**
* 检测是否有互联网连接nmcli 连通性 + ping 兜底)
* 仅经有线口 ping 公网(不依赖默认路由)。
*/
function hasWiredInternetProbe() {
return _tryPingWiredInternet();
}
/**
* 检测是否有真实互联网连接。
* 注意NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
*/
function hasInternet() {
const wifiSta = isWifiStaConnected();
const wired = getWiredIfaceWithCarrier();
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 falsenmcli 有缓存,不可信)
if (!isWifiStaConnected() && !hasWiredCarrier()) return false;
if (!wifiSta && !wired) return false;
try {
const out = run('nmcli networking connectivity check').trim();
if (out === 'full' || out === 'limited') return true;
if (out === 'full') return true;
} catch (_) {}
return _tryPingInternet();
if (wifiSta) return _tryPingDefaultInternet();
if (wired) return _tryPingWiredInternet();
return false;
}
/**
@@ -265,6 +266,7 @@ function nmcliAsync(args, timeoutMs = 60000) {
* @returns {Promise<{ success: boolean, error?: string }>}
*/
async function connectWifi(ssid, password) {
cancelHotspotRadioRetry(`准备连接 WiFi: ${ssid}`);
const iface = getWifiIface();
log.info('network', `尝试连接 WiFi: ${ssid}ifname=${iface}`);
try {
@@ -272,14 +274,36 @@ async function connectWifi(ssid, password) {
await nmcliAsync(['connection', 'delete', ssid], 15000);
} catch (_) {}
try {
await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000);
} catch (_) {}
await _resetWifiRadioForSTA(iface);
const args = ['device', 'wifi', 'connect', ssid];
if (password) args.push('password', password);
args.push('ifname', iface);
await nmcliAsync(args, 120000);
if (password) {
// 显式创建 STA profile并固定为 WPA2-PSK only。
// RK3588/Broadcom DHD 对 NetworkManager 默认生成的 SAE/FT/WPA-PSK-SHA256 混合参数不稳定,
// 可能表现为一直 associating -> disconnected最后误报“需要密钥”。
await nmcliAsync([
'connection', 'add',
'type', 'wifi',
'ifname', iface,
'con-name', ssid,
'ssid', ssid,
], 15000);
await nmcliAsync([
'connection', 'modify', ssid,
// 连接成功前先禁止自动连接,避免失败恢复 AP 时 NM 又自动抢占 wlan0。
'connection.autoconnect', 'no',
'802-11-wireless-security.key-mgmt', 'wpa-psk',
'802-11-wireless-security.proto', 'rsn',
'802-11-wireless-security.pairwise', 'ccmp',
'802-11-wireless-security.group', 'ccmp',
'802-11-wireless-security.pmf', 'disable',
'802-11-wireless-security.psk', password,
], 15000);
await nmcliAsync(['connection', 'up', 'id', ssid, 'ifname', iface], 120000);
} else {
await nmcliAsync(['device', 'wifi', 'connect', ssid, 'ifname', iface], 120000);
}
await _ensureActiveWifiAutoconnect();
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
@@ -299,11 +323,201 @@ async function connectWifi(ssid, password) {
}
return { success: false, error: '超时:网卡未进入已连接状态' };
} catch (e) {
try { await nmcliAsync(['connection', 'modify', ssid, 'connection.autoconnect', 'no'], 8000); } catch (_) {}
log.error('network', `WiFi 连接失败: ${e.message}`);
return { success: false, error: e.message };
}
}
function _newHotspotRetryToken() {
const token = `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
try {
fs.writeFileSync(AP_RETRY_TOKEN_FILE, token, { mode: 0o600 });
} catch (e) {
log.warn('network', `写入 AP retry token 失败: ${e.message}`);
}
return token;
}
function cancelHotspotRadioRetry(reason = 'cancel') {
try {
fs.unlinkSync(AP_RETRY_TOKEN_FILE);
log.info('network', `已取消后台 AP retry: ${reason}`);
} catch (_) {}
}
async function _resetWifiRadioForSTA(iface, reason = '准备连接 STA 前重置 WiFi radio') {
log.warn('network', `${reason}: ${iface}`);
try { await nmcliAsync(['connection', 'down', CON_NAME], 8000); } catch (_) {}
try { await nmcliAsync(['connection', 'delete', CON_NAME], 8000); } catch (_) {}
try { await nmcliAsync(['device', 'disconnect', iface], 8000); } catch (_) {}
try {
await nmcliAsync(['radio', 'wifi', 'off'], 10000);
} catch (e) {
log.warn('network', `关闭 WiFi radio 失败: ${e.message}`);
}
await _delay(2500);
try {
await nmcliAsync(['radio', 'wifi', 'on'], 10000);
} catch (e) {
log.warn('network', `开启 WiFi radio 失败: ${e.message}`);
}
await _delay(5000);
try { await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000); } catch (_) {}
try { await nmcliAsync(['device', 'wifi', 'rescan', 'ifname', iface], 15000); } catch (_) {}
await _delay(1500);
}
function _resetWifiRadioForAP(iface, reason = '准备 AP 前重置 WiFi radio') {
log.warn('network', `${reason}: ${iface}`);
try { nmcliSync(['connection', 'down', CON_NAME], 8000); } catch (_) {}
try { nmcliSync(['connection', 'delete', CON_NAME], 8000); } catch (_) {}
try { nmcliSync(['device', 'disconnect', iface], 8000); } catch (_) {}
try {
nmcliSync(['radio', 'wifi', 'off'], 10000);
} catch (e) {
log.warn('network', `关闭 WiFi radio 失败: ${e.message}`);
}
sleep(2500);
try {
nmcliSync(['radio', 'wifi', 'on'], 10000);
} catch (e) {
log.warn('network', `开启 WiFi radio 失败: ${e.message}`);
}
sleep(5000);
try { nmcliSync(['device', 'set', iface, 'managed', 'yes'], 8000); } catch (_) {}
}
function _spawnHotspotRadioRetry(ssid, iface) {
const token = _newHotspotRetryToken();
const script = `
set -u
log() { logger -t clawd-ap-retry "$*"; }
check_token() {
if [ ! -f "$TOKEN_FILE" ] || [ "$(cat "$TOKEN_FILE" 2>/dev/null || true)" != "$TOKEN" ]; then
log "AP retry canceled"
exit 0
fi
}
log "AP retry started: ssid=$SSID iface=$IFACE"
check_token
nmcli connection down "$CON_NAME" >/dev/null 2>&1 || true
nmcli connection delete "$CON_NAME" >/dev/null 2>&1 || true
nmcli device disconnect "$IFACE" >/dev/null 2>&1 || true
check_token
nmcli radio wifi off >/dev/null 2>&1 || true
sleep 2.5
# If canceled while radio is off, always turn it back on before exiting.
nmcli radio wifi on >/dev/null 2>&1 || true
sleep 5
check_token
nmcli device set "$IFACE" managed yes >/dev/null 2>&1 || true
check_token
if ! nmcli connection add type wifi ifname "$IFACE" con-name "$CON_NAME" ssid "$SSID" >/dev/null 2>&1; then
log "AP retry failed: connection add failed"
exit 1
fi
args=(
connection modify "$CON_NAME"
connection.autoconnect no
802-11-wireless.mode ap
802-11-wireless.band bg
802-11-wireless.channel 1
802-11-wireless-security.key-mgmt wpa-psk
802-11-wireless-security.proto rsn
802-11-wireless-security.pairwise ccmp
802-11-wireless-security.group ccmp
802-11-wireless-security.pmf disable
ipv4.method shared
ipv4.addresses "$AP_IP/24"
ipv6.method ignore
)
if [ -n "\${AP_PASSWORD:-}" ]; then
args+=(802-11-wireless-security.psk "$AP_PASSWORD")
fi
check_token
if ! nmcli "\${args[@]}" >/dev/null 2>&1; then
log "AP retry failed: connection modify failed"
exit 1
fi
check_token
if nmcli connection up "$CON_NAME" >/dev/null 2>&1; then
if [ ! -f "$TOKEN_FILE" ] || [ "$(cat "$TOKEN_FILE" 2>/dev/null || true)" != "$TOKEN" ]; then
log "AP retry canceled after connection up; tearing hotspot down"
nmcli connection down "$CON_NAME" >/dev/null 2>&1 || true
nmcli connection delete "$CON_NAME" >/dev/null 2>&1 || true
exit 0
fi
log "AP retry success: $SSID"
rm -f "$TOKEN_FILE"
else
log "AP retry failed: connection up failed"
exit 1
fi
`;
const child = spawn('/bin/bash', ['-lc', script], {
detached: true,
stdio: 'ignore',
env: {
...process.env,
SSID: ssid,
IFACE: iface,
CON_NAME,
AP_IP,
AP_PASSWORD: AP_PASSWORD || '',
TOKEN_FILE: AP_RETRY_TOKEN_FILE,
TOKEN: token,
},
});
child.unref();
}
function _createHotspotProfile(ssid, iface) {
nmcliSync([
'connection', 'add',
'type', 'wifi',
'ifname', iface,
'con-name', CON_NAME,
'ssid', ssid,
], 15000);
const modifyArgs = [
'connection', 'modify', CON_NAME,
'connection.autoconnect', 'no',
'802-11-wireless.mode', 'ap',
'802-11-wireless.band', 'bg',
'802-11-wireless.channel', '1',
'802-11-wireless-security.key-mgmt', 'wpa-psk',
'802-11-wireless-security.proto', 'rsn',
'802-11-wireless-security.pairwise', 'ccmp',
'802-11-wireless-security.group', 'ccmp',
'802-11-wireless-security.pmf', 'disable',
'ipv4.method', 'shared',
'ipv4.addresses', `${AP_IP}/24`,
'ipv6.method', 'ignore',
];
if (AP_PASSWORD) {
modifyArgs.push('802-11-wireless-security.psk', AP_PASSWORD);
}
nmcliSync(modifyArgs, 15000);
}
function _activateHotspot(ssid, iface, timeoutMs = 8000) {
_createHotspotProfile(ssid, iface);
nmcliSync(['connection', 'up', CON_NAME], timeoutMs);
}
/**
* 启动 WiFi AP 热点
*/
@@ -313,29 +527,24 @@ function startAP(clawId) {
log.info('network', `启动 AP 热点: ${ssid} (${iface})`);
// 关闭已有热点
// 关闭已有热点,并在重新拉起 AP 前真正 power-cycle WiFi 芯片。
// RK3588/Broadcom DHD 在 LAN 断开后切 AP 时,单纯 ip link down/up 不一定清掉固件残留状态。
stopAP();
_resetWifiRadioForAP(iface, '准备 AP 前重置 WiFi radio');
try {
// nmcli 创建热点(开放网络)
const cmd = [
'nmcli device wifi hotspot',
`ifname ${iface}`,
`con-name ${CON_NAME}`,
`ssid "${ssid}"`,
'band bg',
];
// 如果需要密码
if (AP_PASSWORD) {
cmd.push(`password "${AP_PASSWORD}"`);
}
run(cmd.join(' '));
// 显式创建并激活热点,固定为 WPA2-PSK only。
// 避免 NetworkManager 自动生成 WPA-PSK-SHA256/SAE 混合配置,部分 3588 Broadcom DHD 驱动重启 AP 时会拒绝该 RSN 参数。
try {
nmcliSync(['connection', 'modify', CON_NAME, 'connection.autoconnect', 'no'], 8000);
} catch (_) {}
_activateHotspot(ssid, iface, 8000);
} catch (firstError) {
log.warn('network', `AP 启动未在短超时内完成,后台再次重置 WiFi radio 后重试;避免阻塞 watchdog: ${firstError.message}`);
_spawnHotspotRadioRetry(ssid, iface);
return { ssid, ip: AP_IP, iface, pending: true };
}
// 等待 AP 启动
sleep(2000);
sleep(1000);
log.info('network', `AP 已启动: ${ssid}, 网关 ${AP_IP}`);
return { ssid, ip: AP_IP, iface };
} catch (e) {
@@ -348,6 +557,7 @@ function startAP(clawId) {
* 关闭热点,恢复普通 WiFi 模式
*/
function stopAP() {
cancelHotspotRadioRetry('停止 AP');
try {
run(`nmcli connection down ${CON_NAME}`);
} catch (_) {}
@@ -454,6 +664,7 @@ async function _ensureActiveWifiAutoconnect() {
* clawd 只做调度真正的认证、DHCP、重连细节仍交给 NM。
*/
async function connectSavedWifiConnections() {
cancelHotspotRadioRetry('准备连接已保存 WiFi');
const iface = getWifiIface();
const profiles = listSavedWifiConnections();
if (profiles.length === 0) {
@@ -574,6 +785,7 @@ module.exports = {
connectWifi,
startAP,
stopAP,
cancelHotspotRadioRetry,
AP_IP,
getLocalIps,
getLocalNetworks,

View File

@@ -2,7 +2,7 @@
const EventEmitter = require('events');
const log = require('./logger');
const { hasInternet, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network');
const { hasInternet, hasWiredInternetProbe, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network');
const { DnsHijack } = require('./dns-hijack');
const { CaptiveServer } = require('./captive-server');
const led = require('./led');
@@ -258,7 +258,9 @@ class ProvisionManager extends EventEmitter {
}
if (this._state === 'ap') {
if (hasInternet()) {
// AP 模式下 hasInternet() 可能被热点本地网络 / NetworkManager limited 状态误判。
// 只有明确探测到有线口可访问公网时,才关闭配网 AP。
if (hasWiredInternetProbe()) {
log.info('provision', '检测到有线网络可用,关闭 AP');
this._stopAPServices();
this._state = 'wired';

View File

@@ -0,0 +1,39 @@
# RK3588S demo resource
This directory contains the LVGL demo binary deployed on RK3588S devices by `install.sh`.
## Files
- `demo` — prebuilt LVGL UI binary installed to `/usr/bin/demo` on RK3588S boards
## Source
Current binary source on the build machine:
- `/home/sts/share/小屏demo开发指南/lvgl源码/lv_port_linux_v2/lv_port_linux/build/bin/demo`
## Purpose
This demo provides the small-screen UI used on RK3588S devices, including FIFO-based control through:
- `/tmp/lvgl_cmd`
Supported commands expected by the current clawd RK3588 LVGL backend include:
- `show_text:AP`
- `show_text:Conn`
- `show_text:Err0`
- `show_text:<PIN>`
- `show_time`
## Install behavior
During `install.sh`, if `/proc/device-tree/model` matches `RK3588S`, clawd will:
1. Back up existing `/usr/bin/demo` to `/usr/bin/demo.clawd-bak` if not already backed up
2. Install this `demo` binary to `/usr/bin/demo`
## Notes
- The binary is hardware-specific and intended for RK3588S boards.
- Replacing the binary should be done together with verification of `/tmp/lvgl_cmd` behavior and screen rendering.

BIN
lib/resource/3588s/demo Executable file

Binary file not shown.

21
lib/resource/3588s/src/LICENSE Executable file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Littlev Graphics Library
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

105
lib/resource/3588s/src/Makefile Executable file
View File

@@ -0,0 +1,105 @@
ROOT_DIR := $(shell pwd)
LVGL_DIR_NAME ?= lvgl
CC ?= gcc
TARGET_NAME := demo
BUILD_DIR := build
OBJ_DIR := $(BUILD_DIR)/obj
BIN_DIR := $(BUILD_DIR)/bin
TARGET := $(BIN_DIR)/$(TARGET_NAME)
IMG_SRC_DIR := user/images
# 图片文件名固定为 logo后缀可以是 png / jpg / jpeg / bmp
LOGO_NAME := logo
LOGO_IMG_EXTS := png jpg jpeg bmp
LOGO_IMG_FILES := $(foreach ext,$(LOGO_IMG_EXTS),$(IMG_SRC_DIR)/$(LOGO_NAME).$(ext))
LOGO_IMG := $(firstword $(wildcard $(LOGO_IMG_FILES)))
LOGO_C := $(LOGO_NAME).c
LVGL_IMG_CONV := lv_img_conv
LVGL_IMG_CF := CF_TRUE_COLOR_ALPHA
APP_CSRCS := $(filter-out logo.c,$(wildcard *.c))
APP_CSRCS += $(shell if [ -d src ]; then find src -type f -name "*.c"; fi)
LVGL_CSRCS := $(shell if [ -d $(LVGL_DIR_NAME)/src ]; then find $(LVGL_DIR_NAME)/src -type f -name "*.c"; fi)
LV_DRIVERS_CSRCS := $(shell if [ -d lv_drivers ]; then find lv_drivers -type f -name "*.c"; fi)
CSRCS := $(APP_CSRCS)
CSRCS += $(LVGL_CSRCS)
CSRCS += $(LV_DRIVERS_CSRCS)
CSRCS += $(LOGO_C)
OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(CSRCS))
CFLAGS ?= -O3 -g0
CFLAGS += -Wall
CFLAGS += -I.
CFLAGS += -I$(LVGL_DIR_NAME)
CFLAGS += -I$(LVGL_DIR_NAME)/src
CFLAGS += -Ilv_drivers
CFLAGS += -Iinclude
LDFLAGS += -lm
.PHONY: all
all: $(TARGET)
$(BIN_DIR):
mkdir -p $(BIN_DIR)
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
$(LOGO_C): $(LOGO_IMG)
@if [ -z "$(LOGO_IMG)" ]; then \
echo "Error: 未找到图片文件"; \
echo "请将图片命名为以下任意一种格式,并放入 $(IMG_SRC_DIR) 目录:"; \
echo " logo.png"; \
echo " logo.jpg"; \
echo " logo.jpeg"; \
echo " logo.bmp"; \
exit 1; \
fi
@echo "Use image: $(LOGO_IMG)"
rm -f $(LOGO_C)
cp $(LOGO_IMG) ./$(notdir $(LOGO_IMG))
$(LVGL_IMG_CONV) $(notdir $(LOGO_IMG)) -f -c $(LVGL_IMG_CF)
rm -f ./$(notdir $(LOGO_IMG))
$(OBJ_DIR)/%.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(LOGO_C) $(OBJS) | $(BIN_DIR)
$(CC) $(OBJS) -o $(TARGET) $(LDFLAGS)
@echo "Build success: $(TARGET)"
.PHONY: clean
clean:
rm -rf $(OBJ_DIR)
rm -f $(TARGET)
.PHONY: imgclean
imgclean:
rm -f $(LOGO_C)
.PHONY: distclean
distclean:
rm -rf $(BUILD_DIR)
rm -f $(LOGO_C)
.PHONY: info
info:
@echo "TARGET = $(TARGET)"
@echo "IMG_SRC_DIR = $(IMG_SRC_DIR)"
@echo "LOGO_IMG = $(LOGO_IMG)"
@echo "LOGO_C = $(LOGO_C)"
@echo "Support image formats: $(LOGO_IMG_EXTS)"
@echo "CSRCS = $(CSRCS)"

View File

@@ -0,0 +1,8 @@
# LVGL for frame buffer device
LVGL configured to work with /dev/fb0 on Linux.
When cloning this repository, also make sure to download submodules (`git submodule update --init --recursive`) otherwise you will be missing key components.
Check out this blog post for a step by step tutorial:
https://blog.lvgl.io/2018-01-03/linux_fb

View File

@@ -0,0 +1,16 @@
#! /bin/sh
start() {
demo &
}
case "$1" in
start)
start
;;
*)
echo "Usage: $0 {start}"
exit 1
esac
exit $?

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More