在机器狗上写代码前,先搞清楚 Luwu-OS 到底是怎么跑起来的。本章讲清它的整体分层、硬件组成、启动顺序、进程间通信与目录结构——理解了这些,你改代码时才知道该改哪里、改了会影响到谁。
本章包含:
小节 | 内容 | 难度 |
|---|---|---|
2.1 整体架构 | 三层架构、9 项外设与“单点独占”硬件设计(含 2.1.1 硬件规格表、2.1.2 官方四层分层) | ★★☆ |
2.2 启动流程 | 从开机到主界面就绪经历了哪些 systemd 服务 | ★★☆ |
2.3 进程间通信(IPC) | FIFO、D-Bus、MQTT,以及外部启动应用的调试入口 | ★★☆ |
2.4 按键系统 | A/B/C/D 四键如何从内核映射到应用 | ★★☆ |
2.5 目录结构 |
| ★☆☆ |
2.6 常见问题排查 | 黑屏 / 应用起不来 / 按键无响应等 | ★☆☆ |
🧭 阅读路径:只想快速改代码的,可跳过 2.2/2.4 先看 2.5 目录结构;要自己新增功能或应用的,建议 2.1 → 2.3 → 2.5。
前置条件:
完成第一章开发环境搭建,已能用 SSH 登录机器狗;
会基本的 Linux 命令(
ls、cat、cd等)。
2.1 整体架构
┌──────────────────────────────────────────────────────┐
│ 用户层 │
│ ┌────────────────────────────────────────────────┐ │
│ │ Qt 5.15 Launcher (C++) │ │
│ │ GalleryView │ StatusBar │ KeyFilter │ │
│ └──────────────────┬─────────────────────────────┘ │
│ │ FIFO pipes │
│ ┌──────────────────┴─────────────────────────────┐ │
│ │ PySide6 Applications (Python 3) │ │
│ │ AI │ Coding │ Demos │ Settings │ ... 23 apps │ │
│ └────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────┤
│ 系统层 (Linux Kernel) │
│ FBTFT │ gpio-keys │ ALSA dmix │ pwm-fan │
│ Picamera2 │ BlueZ │ UART │ GPIO │
└──────────────────────────────────────────────────────┘
核心设计要点: - Qt C++ 启动器管理主界面和应用生命周期 - Python PySide6 应用以独立进程运行,通过 FIFO 管道通信 - 无 X11/Wayland,直接渲染到 Linux 帧缓冲(240×320 SPI LCD,通过 udev 规则映射为 /dev/fb-spi) - 单个应用崩溃不影响其他应用
2.1.1 硬件规格
Luwu-OS 的硬件由若干外设组成,每个外设均有唯一主人(内核驱动或系统服务),应用只能通过标准接口访问:
# | 组件 | 接口 | 驱动 |
|---|---|---|---|
1 | SPI LCD(ST7789V,240×320) | SPI0 + GPIO 27/25/0 |
|
2 | 摄像头(OV5647) | CSI | Picamera2(各应用自行管理生命周期) |
3 | 4 颗实体按键(A/B/C/D) | GPIO 17/22/23/24 |
|
4 | 蓝牙(Cypress) | mini-UART / PL011 |
|
5 | 音频 + 麦克风(WM8960) | I2S + I2C | ALSA |
6 | 机器狗串口 | ttyAMA0 / UART5 |
|
7 | 舵机 / IMU / 电池 | UART → MCU |
|
8 | 散热风扇(4 线 PWM) | GPIO PWM + TACH |
|
9 | 电源 + 电池 | VC 电压监控 + UART |
|
设计原则:每个硬件外设有且只有一个主人(内核驱动或系统服务);应用通过标准接口访问硬件,绝不互相争抢裸设备。
2.1.2 架构分层(官方定义)
层级 | 技术栈 | 职责 |
|---|---|---|
启动器 | Qt 5.15 C++(EGLFS / LinuxFB) | 进程生命周期管理、按键路由、状态栏 |
应用层 | PySide6 Python(LinuxFB) | 功能应用,渲染到帧缓冲 |
硬件抽象 | 内核驱动 + systemd 服务 | 单点独占硬件访问,经标准 Linux 接口暴露 |
进程通信 | FIFO( | 跨进程数据交换 |
2.2 启动流程
电源接通 → Kernel 加载(gpio-keys/FBTFT/ALSA/PWM)
→ systemd 启动
→ luwu-hw-autoconf(硬件检测,切换新/老硬件 config.txt)
→ luwu-splash(启动画面)
→ luwu-launcher(主界面就绪)
→ luwu-portal-api(启动 Portal API Flask 服务)
提示:硬件型号由
configs/detect_device.py串口实时探测(优先级:串口探测 >configs/device.ini手动覆盖 > unknown),launcher 据此过滤不适用的示例程序。
这个过程由几个 systemd 服务串联,每个服务负责一件明确的事,可用 systemctl 查看与管理:
systemd 服务 | 作用 | 常用命令 |
|---|---|---|
| 开机启动画面 |
|
| 主界面(Qt 启动器),最核心 |
|
| 硬件自动识别(CM4 / 老硬件 config.txt 切换) |
|
| Portal API 服务( |
|
💡 改完代码后一般只需重启对应的应用(见 2.3 的 run.fifo),不必重启整机;只有动了内核配置 / 系统服务才需要
sudo reboot。
2.3 进程间通信(IPC)
FIFO 路径 | 方向 | 用途 |
|---|---|---|
| Launcher → App | 转发按键事件(子应用运行期间) |
| Launcher → Preload | 通知预加载进程( |
| 外部进程 → Launcher | 外部写入脚本路径,由 launcher 走 |
Launcher 启动时会拉起 apps/demo_page/preload_app.py 预加载进程(提前加载 PySide6 等重依赖,加速示例页启动),并通过 /tmp/luwu_preload.fifo 通知其预加载;子应用退出后该进程也会被重启,用于兜底重绘防黑屏。
/tmp/luwu_run.fifo 是外部触发应用的调试入口,无需经过主界面菜单:
# 相对 LUWU_ROOT 路径
echo "apps/foo/main.py" > /tmp/luwu_run.fifo
# 绝对路径
echo "/opt/luwu-os/apps/foo/main.py" > /tmp/luwu_run.fifo
其他通信方式:MQTT(多机群组通信)、UDP 广播(无外网时的群组通信)。
如何用 run.fifo 调试单个应用:这是开发时最常用的一条调试路径——让 launcher 走完整的启动流程(自动套用主题、注册按键路由、接入状态栏),而不是裸 python3 main.py 只跑窗口。启动后 launcher 会把应用全屏显示到 /dev/fb-spi,你就能在实体屏上看到真实的界面效果:
# ① 让 launcher 启动你的应用(相对 LUWU_ROOT 路径即可)
echo "apps/my_new_app/main.py" > /tmp/luwu_run.fifo
# ② 查看应用是否被正确拉起(看最近的日志)
sudo journalctl -u luwu-launcher -n 50
💡 裸跑
python3 main.py与走 fifo 的差别:裸跑不会应用 launcher 的主题 / 按键路由,部分应用在无 launcher 上下文时可能报错或显示异常。开发时建议先裸跑看代码逻辑,再用 fifo 验证完整效果。
2.4 按键系统
gpio-keys 内核驱动管理物理按键:
物理按键 | GPIO | Linux Key | Qt::Key |
|---|---|---|---|
A (左上) | GPIO17 | KEY_LEFT (105) | Qt::Key_Left |
B (右上) | GPIO22 | KEY_RIGHT (106) | Qt::Key_Right |
C (左下) | GPIO23 | KEY_BACK (158) | Qt::Key_Back |
D (右下) | GPIO24 | KEY_ENTER (28) | Qt::Key_Enter |
Launcher 的 KeyFilter 拦截按键 → 写入 /tmp/luwu_keys.fifo → 子应用通过 QSocketNotifier 读取。
在应用内处理按键,直接用 keyPressEvent(PySide6 会把按键事件派发给当前聚焦的控件):
from PySide6.QtCore import Qt
from PySide6.QtGui import QKeyEvent
class MyPage(AppFrame):
def keyPressEvent(self, ev: QKeyEvent):
if ev.key() == Qt.Key.Key_Back: # 对应右下角 C 键(val:149)
self.close()
elif ev.key() == Qt.Key.Key_Enter: # 对应 D 键(val:28)
self.doSomething()
else:
super().keyPressEvent(ev)
⚠️ 用
Qt.Key_Back/Qt.Key_Enter而不是硬编码按键值,是为兼容不同机型的按键映射(见 2.4 的物理键→QT 键对照)。
2.5 目录结构
/opt/luwu-os/
├── apps/ # 23 个应用 (ai/ai_chat_pro/ball_catch/ball_track/
│ # bluetooth_gamepad/coding/demo_page/face_follow/gamepad/
│ # gesture/group_perform/hotspot/joystick/network/perform/
│ # person_follow/portal/radar/rc_mode/rl_demo/settings/
│ # sound_locate/voice_chat)
├── launcher/ # Qt5 C++ 启动器 (main.cpp/galleryview/demogridview/
│ # statusbar/keyfilter/devicetable/i18n)
├── configs/ # 系统配置 (install.sh/luwu-keys.dts/detect_device.py/
│ # hardware_autoconf.py/language.ini/systemd services/
│ # asound.conf/udev rules/luwu-undervolt-monitor.py)
├── libs/ # 共享库 (xgolib/xgoedu-luwuos/theme/ui/gamepad_config/
│ # ydlidar_sdk/i18n.py/ball_catch_core.py)
├── model/ # AI 模型 ONNX (人脸/手掌/手部/人体/姿态/情绪/性别年龄/
│ # YOLO/语音唤醒词(hi_luka, xiaolu_classmate 等)/中文字体 msyh.ttc)
├── assets/ # 资源 (expressions/images/music)
├── scripts/ # 工具脚本 (check_update.py/edu_test.py/snap.py)
└── docs/ # 文档
目录结构说明:
launcher/下galleryview.cpp的CARDS数组定义主界面 5 张卡片(无线网络 / 图形化编程 / AI交互 / 示例程序 / 系统设置);demogridview.cpp定义「示例程序」页内的 9 个演示程序;devicetable.h是机型注册表(新增机型只需加一行)。configs/language.ini是全局语言配置(内容仅cn或en一行)。model/中的mp_*.py是 MediaPipe ONNX 模型对应的 Python 包装类(MPPalmDet/MPHandPose/MPPersonDet/MPPose)。
2.6 常见问题排查
现象 | 可能原因 | 处理 |
|---|---|---|
开机后主界面黑屏 / 无画面 | SPI LCD 未正确绑定 | 确认 |
主界面能看到,但分辨率不对 / 撕裂 | 帧缓冲尺寸与 LCD 不匹配 | 查 |
某个应用起不来, | launcher 未运行,或路径不对 | 确认 |
按键无响应 | 按键设备树 / udev 未加载 |
|
改完代码不生效 | 没重启对应应用 | 用 2.3 的 run.fifo 重启该应用;改系统服务才需 reboot |
机器狗对运动指令无反应 | 串口被占用 / 未识别机型 | 见第四章 xgolib 的 4.1.9 常见报错 |
