用手机开机器人 — systemd 自启、网页摇杆与 launcher 服务¶
这一章讲什么: 上一章机器人已经能动了,但每开一次机要开两个 SSH 窗口、敲两行命令—— 这不是产品,是调试。本章把它变成「开机 → 绿灯亮 → 掏手机 → 开走」: ① 用 systemd 让电机服务开机自启,加一颗 LED 当「就绪」信号; ② 用 HTML/CSS/JavaScript 写一个手机网页应用,核心是自回中摇杆; ③ 给 Mosquitto 开 WebSocket 口,让浏览器里的网页直接连 MQTT; ④ 写一个 launcher 服务,网页上的按钮就能启动行为、关掉机器人。 读完你会有一个不再依赖 SSH 的完整遥控闭环。
1. 先看现象:SSH 不是给用户用的¶
书里先把痛点摆出来:手动驾驶很好玩,但登录、输命令启动、Ctrl+C 停止这套流程, 又慢又容易打错字1。买个蓝牙手柄也行,但作者指了指你口袋:「你手里的手机就是 一个无线触摸屏摇杆」——有 Wi-Fi、能开热点、能读手指动作1。
于是目标被定义成一句话:开机,等机器人说「我好了」,然后拿手机指它。 「说」这个动作用一颗 LED 完成——机器人没有屏幕,状态要用光来表达。 这个「无头设备的自我表达」问题,是本章前半的主线。
2. 顶层全景:四块新零件¶
① systemd unit 模板 + deploy_services.py —— 服务开机自启/崩溃自重启
② 就绪信号 电机服务点亮 HAT 上的 LED(绿=活着),退出时 atexit 熄灭
③ 网页应用 index.html 菜单 + manual_drive.html 摇杆 + mqtt_client.js 连接
④ launcher launcher_service.py:订阅 launcher/#,调 systemctl 启停行为、poweroff
图说:①② 让机器人「自己会开场」;③④ 让你「不用 SSH 也能收场」。
3. 核心原理之一:systemd——让程序开机自己起来¶
systemd 是 Raspberry Pi OS 用来自动启动程序的工具:管程序的生死、何时跑、
以哪个用户跑、在哪个目录启动、把日志收进 journal;systemctl 是操控它的命令2。
第 6 章装 Mosquitto 时已经用过它,本章把它用到自己的代码上。
每个被 systemd 管的程序有一份 unit file(单元文件),写在 deploy/service_template.j2:
[Unit]
Description={{ service_name }}
After=mosquitto.service
[Service]
Type=simple
Restart={{ restart }}
WorkingDirectory=/home/{{ pi_user }}/
ExecStart=/usr/local/bin/robotpython -u {{ command }}
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target
逐节拆开,每一行都有明确职责:
| 设置 | 干什么 | 为什么 |
|---|---|---|
After=mosquitto.service |
排在 Mosquitto 之后启动 | broker 先就绪,服务连得上 MQTT3 |
Restart={{ restart }} |
退出后怎么办 | 电机服务填 always(崩了自动拉起);行为填 no(只手动启动)3 |
WorkingDirectory |
程序的工作目录 | 代码都在用户主目录下 |
ExecStart |
启动命令 | 用 robotpython 跑;-u 让日志不缓冲、实时可见3 |
KillSignal=SIGINT |
停止时发什么信号 | 让 Python 的 atexit 能跑——停车、灭灯都靠它3 |
WantedBy=multi-user.target |
挂在哪个启动目标下 | 开机进入多用户模式时就启动3 |
模板里 {{ 双括号 }} 是 PyInfra 的占位符,部署时替换成真实服务名、命令、重启策略。
deploy/deploy_services.py 把「填模板 → 写 unit → 重载 systemd → 按需重启」封装成
一个 deploy_service() 函数,参数四样:服务名、命令、是否自启、代码有没有变4。
两个必学的运维命令(以后天天用):
systemctl status <服务名>—— 看死活;journalctl -u <服务名>—— 看完整日志,-f实时跟(第 4 章 pyinfra 排错那套 在这里升级成「服务级日志」)5。
4. 核心原理之二:LED 就绪灯——无头设备的「我好了」¶
服务能自启之后,新问题:你在房间另一头,怎么知道它好了?给它一盏灯。
改动只有两处:服务连上 broker 后点亮 HAT 的第一颗 LED 为绿色
(board.leds.set_rgb(0, 0, 255, 0)——RGB 三个分量,绿满、红蓝为零);
退出处理器从只停车扩展为「停车 + 清灯」6。
开机 → systemd 拉起电机服务 → 连上 broker → LED 变绿 = 「可以给我发消息了」
关机/崩溃 → atexit:停车、灭灯
图说:一颗灯,把「服务状态」这个本来要 SSH 才能看到的信息,变成房间任何角落可见。
部署、关机、拔掉墙电、只用电池再开机——绿灯亮,整条链路就自证成功了6。 这是嵌入式系统的经典模式:状态外化。机器人没有屏幕,灯、蜂鸣器、 later 网页仪表盘, 都是同一个思路的放大。
5. 核心原理之三:网页应用三件套——结构、样子、行为¶
手机端不装 App,用网页当界面——浏览器人人都有,跨平台零安装。 三件套分工书里给得很干净7:
| 技术 | 管什么 |
|---|---|
| HTML(超文本标记语言) | 页面的结构:文字、按钮、链接 |
| CSS(层叠样式表) | 样子:颜色、字体、位置、大小 |
| JS(JavaScript) | 行为:点击发生什么、和 MQTT 对话 |
HTML 的树。 页面由「开标签…闭标签」嵌套成树:<html> 包整页,<head> 装页面信息
(标题、样式链接),<body> 装看得见的内容(<h1> 大标题、<ul>/<li> 列表、
<a href=…> 链接)。浏览器开发者工具里能看到这棵树,它有个名字:DOM
(Document Object Model,文档对象模型)8。理解「页面 = 一棵可以编程的树」,
是本章所有 JavaScript 的前提。
CSS 的级联。 样式写在单独的 .css 文件里供多页共用;选择器按元素名匹配
(body {…} 作用于 body 及其所有后代——这就是「层叠 cascade」),
2em 表示「父元素字号的 2 倍」,随屏幕缩放9。两行关键 HTML 别忘了:
<link rel="stylesheet" href="styles.css"> 引样式;
<meta name="viewport" content="width=device-width, initial-scale=1"> 让手机按真实
宽度渲染而不是缩小成「桌面微缩版」10。
本地预览闭环:电脑上 python -m http.server --directory robot_control,
浏览器开 localhost:8000,改文件刷新即见——网页先在电脑上调好,再上机器人,
这个顺序省无数麻烦11。
6. 核心原理之四:摇杆——用别人的组件,读手指的位置¶
自回中摇杆不自造,用现成组件 bobboteck/JoyStick(GitHub),PyInfra 下载
joy.js 到 robot_control/libs/12。页面里三行关键:
<div id="joydiv" style="width:50vh;height:50vh;"></div>
<script type="module">
var joystick = new JoyStick('joydiv', {}, function(stickData) {
console.log(stickData.x, stickData.y);
});
</script>
<div id="joydiv">是给摇杆占的「坑」;50vh= 视口高度的 50%,摇杆因此占半屏, 任何手机上尺寸都对13;type="module"表示这段脚本是 ES 模块(能 import 别的文件——后面接 MQTT 必需);- 回调函数在摇杆被触摸或移动时触发,
stickData.x、stickData.y是相对中心的偏移, 范围 -100 到 10014。
调试姿势:桌面浏览器右键 → Inspect → Console,拖动摇杆,数值在控制台滚动15。
排错要点:404 查 libs/joy.js(libs 带 s);摇杆看不见查 style 里的尺寸16。
7. 核心原理之五:WebSocket——浏览器进 MQTT 的唯一通道¶
网页里的 JavaScript 有安全限制,不能直接开普通网络连接——连 MQTT 的唯一通道是
WebSocket:一种在浏览器和服务器之间保持长连接的协议17。
Mosquitto 默认不开它,配置 deploy/robot_mosquitto.conf:
注意两个细节:① 一旦给了配置文件,1883 要显式重新声明(书里明说了);
② 不开密码文件、不允许匿名连接,Mosquitto 就不启用 WebSocket——这两行是绑定的18。
配好后 systemctl status mosquitto 的日志里应看到
Loading config file /etc/mosquitto/conf.d/robot.conf19。
网页侧装 mqtt.js(ESM 版,import 用)20,再用一个 .env.js 存机器人地址和
MQTT 账号(主机名 / robot / robot)。这个文件含口令,绝不能进 Git——书里给了
.gitignore 操作,并再一次挂账:「这个应用没有任何安全机制,共享 Wi-Fi 上用要慎重,
第 18 章处理」21。
主线例子走完:手指到轮子的全链路¶
手指上推 stickData.y = -59(屏幕坐标,y 向下为正)
JS 换算 speed = y/100 = -0.59 → 取反约定后 = 前进 0.59
direction = x/100(左负右正)
合成两个轮速 [speed + direction, speed - direction]
例:推上略右 → [0.59+0.2, 0.59-0.2] = [0.79, 0.39] → 右缓弯
JSON 打包 JSON.stringify([...]) → "[0.79,0.39]"
WebSocket 发出 ws://learnrob3.local:9001 → broker → 电机服务
电机执行 set_motor_wheels:左 79%、右 39%
手指松开 摇杆自回中 → [0.0, 0.0]... 不对,服务 1 秒无消息自动停
图说:从皮肤到轮子,中间隔着 6 站,每一站都已经在前面章节建好了——这一章只是把它们接起来。
注意换算的两个设计:摇杆的 ±100 除以 100 变成电机认识的 ±1;「速度 ± 转向」合成两轮速
(speed + direction, speed - direction)——推满上 = 直行,斜推 = 差速弯,
逻辑两行程22。这套消息格式和第 6 章行为脚本用的完全一样——这正是 MQTT
服务层的回报:网页和 Python 行为说同一种语言。
8. 核心原理之六:launcher——让网页指挥 systemd¶
最后一跃:让网页能启动行为、关掉机器人。新服务 robot/launcher_service.py,
新武器一个——subprocess:Python 里启动别的程序的库23。四个消息处理:
| topic | 消息体 | 动作 |
|---|---|---|
launcher/start |
服务名 | systemctl start <服务名> |
launcher/stop |
服务名 | systemctl stop <服务名> |
launcher/poweroff |
空 | poweroff 关机 |
行为也注册成 systemd 服务,但自启参数设 False——它只在被点名时启动
(Restart=no + enabled=False 的组合)24。页面上一个 <button id="start">,
JavaScript 给按钮挂事件监听器(addEventListener):点击 → 发布
launcher/start + behavior_path.service → launcher 收到 → systemctl 拉起行为25。
关机页同样,只是按钮文案换成「Turn off」,并且单独一页——确认动作单独放一页,
误触就少一次26。
至此闭环完成。书里那句总结值得原样记住:
「现在你只需要在调试时才开 SSH 了。」27
顺带一提:你手动开车时会不自觉地修正跑偏——左轮快了就多推一点右—— 书里点破这是人的闭环控制,机器人自己的闭环(传感器反馈)要从下一章开始装28。
9. 作者的判断与证据¶
- 判断: 网页当 App,而不是原生 App 或手柄。理由:浏览器零安装、跨平台、 能直接长 MQTT;代价(无安全机制、WebSocket 配置麻烦)书里如实承认721。
- 判断: 自回中摇杆 + 「速度 ± 转向」的合成式,而不是左右两个推杆——单手操作友好, 且与两轮差速的消息格式天然对齐22。
- 判断: launcher 服务是「最小权限的遥控器」——它只暴露 systemctl 的三个动作, 而不是给网页一个 shell。书里没展开安全论证,但架构本身就是论证23。
- 事实层: unit 模板各行语义、WebSocket 9001 端口、Mosquitto 不启用 WS 的两个前置条件、
.local在 Android 上不被支持(要查 IP:ip addr show wlan0)——都可上机复核1829。
10. 边界与局限¶
- 安全近乎裸奔。 口令 robot/robot 明文、WebSocket 无加密(wss 没讲)、 网页应用无认证——书里两次主动挂账给第 18 章。家用 Wi-Fi 玩玩可以,公共网络不行。
- 网页控制的延迟没测。 摇杆到轮子的延迟受 Wi-Fi 质量影响,本书没有任何量化测量; 对「遥控车」够用,对高速行为不够。
- 前端教学是脚手架级别。 HTML/CSS/JS 三件套只够搭起这个应用;样式、布局、 组件化的深水区一律指向 Further reading30。
- 行为启动是「一次性」的。 launcher 能 start/stop,但没有「行为状态查询」—— 网页不知道行为跑没跑,这个洞到第 8 章的仪表盘才部分补上。
11. 可带走的¶
- systemd unit 五要素:After(依赖顺序)、Restart(always/no)、WorkingDirectory、 ExecStart、KillSignal=SIGINT(给 atexit 留活路)3。
- 看服务:
systemctl status看死活,journalctl -u看日志,-f实时跟5。 - 无头设备的状态要外化:一颗 LED = 服务的「我好了」;退出时 atexit 清灯6。
- 网页 = 结构(HTML 树/DOM)+ 样子(CSS 级联)+ 行为(JS 模块);先本地
python -m http.server调好再上机器人811。 - 浏览器连 MQTT 只有 WebSocket 一条路;Mosquitto 要显式开 9001, 且必须配密码文件 + 禁匿名才生效18。
- 摇杆 ±100 ÷ 100 → ±1;两轮速 = 速度 ± 转向——网页与行为共用同一套消息格式22。
.env.js进 .gitignore;这个应用没有安全机制,共享网络别用21。- launcher 模式:网页发消息 → 一个薄服务替它按 systemctl——只暴露需要的动作23。
- 危险动作(关机)单独一页 = 防误触26。
12. 原文地图¶
| 主题 | 原书位置 |
|---|---|
| 为什么用手机当手柄 | text/67-fm-technical-requirements.txt:28(搜「touchscreen joystick」) |
| systemd 与 unit 模板 | text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:34(搜「unit file」) · text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:50(搜「KillSignal=SIGINT」) |
| deploy_service 与状态命令 | text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:166(搜「systemctl status」) · text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:180(搜「journalctl」) |
| LED 就绪灯 | text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:213(搜「board.leds.set_rgb」) |
| 三件套与 DOM | text/69-fm-building-a-phone-app.txt:13(搜「web-page-based app」) · text/69-fm-building-a-phone-app.txt:88(搜「DOM (Document Object Model)」) |
| viewport 与 CSS 级联 | text/69-fm-building-a-phone-app.txt:129(搜「initial-scale=1」) |
| 本地预览与上架机器人 | text/69-fm-building-a-phone-app.txt:66(搜「http.server」) · text/69-fm-building-a-phone-app.txt:175(搜「web_server」) |
| 摇杆组件与 ±100 | text/69-fm-building-a-phone-app.txt:316(搜「stickData.x and stickData.y」) |
| WebSocket 配置 | text/70-fm-connecting-the-phone-app-to-mqtt.txt:40(搜「WebSockets are the only option」) · text/70-fm-connecting-the-phone-app-to-mqtt.txt:52(搜「listener 9001」) |
| mqtt.js 与 .env.js | text/70-fm-connecting-the-phone-app-to-mqtt.txt:132(搜「.env.js contains secrets」) · text/70-fm-connecting-the-phone-app-to-mqtt.txt:168(搜「no security mechanism」) |
| 摇杆到轮子的换算 | text/70-fm-connecting-the-phone-app-to-mqtt.txt:196(搜「speed + direction」) |
| Android 要 IP | text/70-fm-connecting-the-phone-app-to-mqtt.txt:224(搜「Android doesn't support」) |
| launcher 服务 | text/71-fm-adding-more-control-options-to-the-web-app.txt:58(搜「subprocess module is」) · text/71-fm-adding-more-control-options-to-the-web-app.txt:91(搜「auto_start is set to False」) |
| 关机页 | text/71-fm-adding-more-control-options-to-the-web-app.txt:155(搜「Turn off」) |
| 章节总结 | text/72-fm-summary.txt:5(搜「only need an SSH session when」) · text/72-fm-summary.txt:21(搜「correcting for veer」) |
-
出处:「Technical requirements」第 28 段(
text/67-fm-technical-requirements.txt:28,搜「touchscreen joystick」)。原文:SSH 敲命令启停又慢又易错;游戏手柄要花钱;口袋里的手机自带 Wi-Fi、热点、触摸屏——「把它当成一个无线触摸屏摇杆」。 ↩↩ -
出处:「Making services start when the Raspberry Pi starts」第 22 段(
text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:22,搜「automatically launch programs」)。原文 Note:systemd 用于自动启动程序;控制生命周期、查看状态;能控制何时跑、怎么跑、以哪个用户、在哪个目录启动,并把日志收为 journal;systemctl 是操控命令。 ↩ -
出处:「Making services start when the Raspberry Pi starts」第 34 段(
text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:34,搜「unit file」)与第 44 段(搜「KillSignal=SIGINT」)。原文:unit 文件按 [节] 组织;After=mosquitto.service 保证 broker 先起;Restart 用模板变量(always/no);WorkingDirectory 用用户主目录;ExecStart 用 robotpython -u 保证日志不缓冲;KillSignal=SIGINT 确保 atexit 代码能运行;WantedBy=multi-user.target 让开机启动。 ↩↩↩↩↩↩ -
出处:「Making services start when the Raspberry Pi starts」第 79 段(
text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:79,搜「deploy_service」)。原文:函数四参(服务名/命令/自启/变更标记);写 /etc/systemd/system/<名>.service;changed 时 daemon_reload 并重启;enabled=自启,running=立即启动。 ↩ -
出处:「Making services start when the Raspberry Pi starts」第 180 段(
text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:180,搜「journalctl」)。原文:journalctl -u <服务> 看完整日志,方向键滚动、Shift+G 到末尾、q 退出;-f 实时跟随,Ctrl+C 停止。 ↩↩ -
出处:「Making services start when the Raspberry Pi starts」第 213 段(
text/68-fm-making-services-start-when-the-raspberry-pi-star.txt:213,搜「board.leds.set_rgb」)。原文:exit_handler 停车 + leds.clear();连上 broker 后 set_rgb(0, 0, 255, 0) 点绿第一颗 LED;断电仅用电池重开,绿灯亮即自动运行成功。 ↩↩↩ -
出处:「Building a phone app」第 13 段(
text/69-fm-building-a-phone-app.txt:13,搜「web-page-based app」)。原文:建基于网页的 App,三工具——HTML 呈现结构(文字、输入、按钮、链接),CSS 管样式布局(颜色、字体、位置、大小),JS 写页面上的代码并对接 MQTT 服务。 ↩↩ -
出处:「Building a phone app」第 88 段(
text/69-fm-building-a-phone-app.txt:88,搜「DOM (Document Object Model)」)。原文:元素开闭标签嵌套成树;有子元素可展开;容器是枝、无子元素是叶;这棵树叫 DOM。 ↩↩ -
出处:「Building a phone app」第 99 段(
text/69-fm-building-a-phone-app.txt:99,搜「Cascading Style Sheets」)。原文:CSS 指定元素大小、颜色、字体;样式会级联到子元素;body 设字体全家生效;h1 用 2em(em = 父元素字号倍数)放大居中。 ↩ -
出处:「Building a phone app」第 129 段(
text/69-fm-building-a-phone-app.txt:129,搜「initial-scale=1」)。原文:link 标签引 styles.css;meta viewport 让手机视图与桌面同比例;手机上字太小多半是漏了这一行。 ↩ -
出处:「Building a phone app」第 66 段(
text/69-fm-building-a-phone-app.txt:66,搜「http.server」)。原文:python -m http.server --directory robot_control 本地起服务,浏览器开 0.0.0.0:8000;VS Code 可点链接直接开;在电脑上调样式极有用。 ↩↩ -
出处:「Building a phone app」第 255 段(
text/69-fm-building-a-phone-app.txt:255,搜「JoyStick」)。原文:用 bobboteck/JoyStick 组件(GitHub);PyInfra 建 libs 目录并下载 joy.js。 ↩ -
出处:「Building a phone app」第 299 段(
text/69-fm-building-a-phone-app.txt:299,搜「50vh」)。原文:div 是容器;50vh = 视口高度的 50%,摇杆因此占半屏;id=joydiv 供 JS 引用。 ↩ -
出处:「Building a phone app」第 316 段(
text/69-fm-building-a-phone-app.txt:316,搜「stickData.x and stickData.y」)。原文:回调在触摸/移动时触发;stickData.x/y 是相对中心的偏移,-100 到 100;type=module 才能 import。 ↩ -
出处:「Building a phone app」第 333 段(
text/69-fm-building-a-phone-app.txt:333,搜「Inspect」)。原文:右键 Inspect 开开发者面板,Console 里拖动摇杆可见数值滚动。 ↩ -
出处:「Connecting the phone app to MQTT」第 18 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:18,搜「404 errors」)。原文排错:404 查 libs/joy.js(注意 s);SSH 里 ls robot_control/libs;摇杆不可见查 style 属性;无输出查回调函数是否传给 JoyStick。 ↩ -
出处:「Connecting the phone app to MQTT」第 40 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:40,搜「WebSockets are the only option」)。原文:网页的 JS 不能直接连网络服务,WebSocket 让它和机器人保持长连接、收发消息;这是网页直连 MQTT 的唯一选项;Mosquitto 默认不启用。 ↩ -
出处:「Connecting the phone app to MQTT」第 52 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:52,搜「listener 9001」)。原文:加配置文件后必须显式重开 1883;WS 端口开在 9001;不设密码文件、不关匿名连接,Mosquitto 就不启用 WebSocket。 ↩↩↩ -
出处:「Connecting the phone app to MQTT」第 235 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:235,搜「Loading config file」)。原文:systemctl status mosquitto 应见 Active: active (running) 和 Loading config file /etc/mosquitto/conf.d/robot.conf;问题查 deploy/robot_mosquitto.conf 并重跑 pyinfra;内部日志 sudo tail /var/log/mosquitto/mosquitto.log。 ↩ -
出处:「Connecting the phone app to MQTT」第 113 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:113,搜「mqtt.esm.js」)。原文:下载 mqtt.js 的 ESM 变体(unpkg mqtt@5.7.0)以便 import。 ↩ -
出处:「Connecting the phone app to MQTT」第 168 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:168,搜「no security mechanism」)。原文 Note:.env.js 含口令,别进 Git(echo .env.js >> .gitignore);应用无安全机制/认证/密码,本书范围之外,共享 Wi-Fi 上要认真考虑,第 18 章处理。 ↩↩↩ -
出处:「Connecting the phone app to MQTT」第 196 段(
text/70-fm-connecting-the-phone-app-to-mqtt.txt:196,搜「speed + direction」)。原文:x、y 各除 100 得 -1..1;speed+direction 与 speed-direction 合成两轮速;JSON.stringify 打包;topic 与格式和第 6 章行为完全一致。 ↩↩↩ -
出处:「Adding more control options to the web app」第 58 段(
text/71-fm-adding-more-control-options-to-the-web-app.txt:58,搜「subprocess module is」)。原文:subprocess 是启动其他程序的 Python 库;launcher/start、stop 把消息体当服务名传给 systemctl;poweroff 忽略消息体。 ↩↩↩ -
出处:「Adding more control options to the web app」第 91 段(
text/71-fm-adding-more-control-options-to-the-web-app.txt:91,搜「auto_start is set to False」)。原文:行为也注册成服务,但 auto_start=False,只被 systemctl 点名时启动;Tip:拆成服务后,修改和排错可以瞄准系统的具体部分。 ↩ -
出处:「Adding more control options to the web app」第 124 段(
text/71-fm-adding-more-control-options-to-the-web-app.txt:124,搜「addEventListener」)。原文:button id=start;getElementById 找到按钮,addEventListener 监听点击,发布 launcher/start + behavior_path.service。 ↩ -
出处:「Adding more control options to the web app」第 155 段(
text/71-fm-adding-more-control-options-to-the-web-app.txt:155,搜「Turn off」)。原文:关机页复制行为页改几处;Turn off 按钮发 launcher/poweroff;危险动作单独一页,误触更少。 ↩↩ -
出处:「Summary」第 5 段(
text/72-fm-summary.txt:5,搜「only need an SSH session when」)。原文:「You now only need an SSH session when debugging!」。 ↩ -
出处:「Summary」第 21 段(
text/72-fm-summary.txt:21,搜「correcting for veer」)。原文:手动驾驶要练一阵;你会不自觉地用手修正跑偏(电机表现略不同)。 ↩ -
出处:「Serving HTML from the Raspberry Pi」第 192 段(
text/69-fm-building-a-phone-app.txt:192,搜「ip addr show wlan0」)。原文:Android 不认 .local,要查 IP——inet 后四个数即地址(如 192.168.0.137),换网络会变。 ↩ -
出处:「Challenges」第 7 段(
text/73-fm-challenges.txt:7,搜「w3schools」)。原文:样式改进指 w3schools CSS 教程(「深不见底的兔子洞」);安全挑战指 systemd EnvFile 方案,第 18 章细讲。 ↩