SideSail
SideSail 是一个 GNOME Shell 50/50.1 侧边栏扩展,提供系统信息、性能历史、天气、小组件、剪贴板历史,以及基于 python-miio/miiocli 的小米/米家本地智能家居控制。
Github: CairBin/SideSail

功能
- 顶栏按钮打开/关闭侧边栏。
Info:系统、硬件、桌面环境信息。System:CPU、内存、网络、磁盘、电池、温度、进程列表。Weather:Open-Meteo 天气、天气动画、日出日落、日照进度。Widgets:快捷系统状态、性能历史图表、剪贴板历史、智能家居控制。- 主题支持跟随系统、强制暗色、强制亮色。
安装
先进入扩展目录:
bash
cd /home/owo/MyCode/rust/ubuntu_sidebar/sidebar
执行安装脚本:
bash
./install.sh
脚本会复制扩展到:
text
~/.local/share/gnome-shell/extensions/SideSail@top.cairbin
然后启用扩展:
bash
gnome-extensions enable SideSail@top.cairbin
查看状态:
bash
gnome-extensions info SideSail@top.cairbin
如果显示 Enabled: Yes 但 State: INACTIVE,通常需要重新加载 GNOME Shell:
- Wayland:注销后重新登录。
- X11:按
Alt + F2,输入r,回车。
打开设置
可以用 GNOME 扩展设置打开:
bash
gnome-extensions prefs SideSail@top.cairbin
设置里可以调整:
- 侧边栏左右位置。
- 顶栏按钮左右位置。
- 主题模式。
- 天气位置。
- 剪贴板历史保留条数,默认 8 条。
- 智能家居总开关、Python 命令和设备 JSON。
天气配置
默认经纬度都是 0 时,扩展会尝试用 IP 定位。更准确的方式是在设置里填写:
Location name:显示名称,例如Hangzhou。Latitude:纬度。Longitude:经度。
小组件配置
Widgets 分组里的 Clipboard history limit 用来控制剪贴板历史最多保留多少条。默认是 8。
这个限制同时作用于两处:
- 内存里只保留最近 N 条,旧内容会被丢弃。
Widgets页的剪贴板历史也只显示最近 N 条。
如果剪贴板条目太多导致界面溢出,建议保持默认 8,或者设置成更小的值。
智能家居依赖
智能家居控制通过本地 Python helper 调用 python-miio,GNOME Shell 本身不会直接加载 Python 库。
为了避免切换到陌生网络时误触设备控制或暴露设备 token,智能家居命令默认是关闭的。你需要在 Widgets 页的智能家居卡片点击 启用,或在扩展设置的 Smart Home -> Enable smart home commands 打开后,SideSail 才会读取状态或执行 miiocli 命令。关闭后,按钮和滑杆都不会调用 helper。
你需要安装支持modern device(MIoT)的python-miio,请不要用pipx install python-miio,而是:
sh
pipx install git+https://github.com/rytilahti/python-miio.git
更多信息请参考rytilahti/python-miio
除此之外,你需要手动修改click的版本,否则会导致miiocli的命令报错,详细参考python-miio issues#2048
对于使用pipx安装的你可以执行如下命令:
sh
pipx runpip miio install click==8.0.0 --force-reinstall
pipx inject miio click==8.0.0 --force
设备 JSON 放在哪里
打开扩展设置:
bash
gnome-extensions prefs SideSail@top.cairbin
在 Smart Home 分组里,把设备数组填到 Device JSON。
如果你使用 pipx install python-miio 安装依赖,Python command 通常不用改;SideSail 会自动尝试使用 pipx 里的 Python。只有自动识别失败时,才需要手动填写:
text
/home/owo/.local/share/pipx/venvs/python-miio/bin/python
genericmiot 最小示例
以下所有关于命令参数部分,请以python-miio和具体设备支持为准
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "这里填写32位token",
"kind": "genericmiot",
"status": "status",
"actions": [
{"name": "status", "label": "状态", "command": "status"},
{"name": "off", "label": "关闭", "command": "call 这里替换成你测试通过的动作"}
]
}
]
配置后打开侧边栏的 Widgets 页,智能家居卡片会按 actions 显示按钮。
这段配置的含义:
| 字段 | 怎么填 |
|---|---|
name |
显示在卡片上的设备名称,例如 书桌台灯 |
room |
房间或分组名,可选,例如 书桌、卧室 |
ip |
设备在局域网里的 IP,例如 192.168.1.20 |
token |
设备 token,通常是 32 位十六进制字符串 |
kind |
使用自定义 miiocli genericmiot 命令时固定写 genericmiot |
status |
刷新状态时执行的命令片段,常用 "status" |
actions |
前端要显示的按钮数组 |
actions[].name |
按钮内部 ID,只要同一设备内不重复即可 |
actions[].label |
按钮上显示的文字,例如 开启、关闭、切换 |
actions[].command |
点击按钮时执行的命令片段 |
controls |
前端要显示的滑杆数组,适合亮度、色温、风速等连续数值 |
controls[].min |
滑杆最小值 |
controls[].max |
滑杆最大值 |
controls[].step |
每次拖动变化的步进 |
controls[].unit |
显示单位,例如 %、K |
controls[].command |
松开滑杆时执行的命令片段,用 $value 表示当前值 |
command 只填写 miiocli genericmiot --ip <ip> --token <token> 后面的部分。例如你在终端能跑通:
bash
miiocli genericmiot --ip 192.168.1.20 --token 0123456789abcdef0123456789abcdef status
miiocli genericmiot --ip 192.168.1.20 --token 0123456789abcdef0123456789abcdef call light:toggle
那么 JSON 里就写:
json
{
"status": "status",
"actions": [
{"name": "status", "label": "状态", "command": "status"},
{"name": "toggle", "label": "切换", "command": "call light:toggle"}
]
}
如果命令里包含空格、引号或复杂参数,推荐把 command 写成数组,插件会按数组原样传给 miiocli:
json
{
"actions": [
{"name": "on", "label": "开启", "command": ["set", "light:on", "true"]},
{"name": "off", "label": "关闭", "command": ["set", "light:on", "false"]}
]
}
亮度、色温这类需要拖动调节的设备,可以加 controls。例如亮度范围是 1-100,色温范围是 2700-6500K:
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "genericmiot",
"status": "status",
"actions": [
{"name": "toggle", "label": "切换", "command": "call light:toggle"}
],
"controls": [
{
"name": "brightness",
"label": "亮度",
"min": 1,
"max": 100,
"step": 1,
"unit": "%",
"value": 60,
"command": ["set", "light:brightness", "$value"]
},
{
"name": "color_temp",
"label": "色温",
"min": 2700,
"max": 6500,
"step": 100,
"unit": "K",
"value": 4000,
"command": ["set", "light:color-temperature", "$value"]
}
]
}
]
$value 会在执行时替换成滑杆当前值。比如亮度拖到 42 时,上面的配置会执行:
bash
miiocli genericmiot --ip 192.168.1.20 --token 0123456789abcdef0123456789abcdef set light:brightness 42
注意:不同型号台灯的属性名不一定相同,light:brightness、light:color-temperature 只是示例。先在终端里用 miiocli genericmiot ... status 或 miiocli genericmiot ... --help 查清你的设备支持什么属性,再把能跑通的命令写进 command。
如果你的设备需要 raw_command set_properties,也可以把 $value 放在 JSON 参数字符串里。SideSail 会把字符串内部的 $value 替换成滑杆当前值:
json
{
"name": "color_temp",
"label": "色温",
"min": 2700,
"max": 6500,
"step": 100,
"unit": "K",
"value": 4000,
"command": [
"raw_command",
"set_properties",
"[{\"did\":\"你的设备did\",\"siid\":2,\"piid\":3,\"value\":$value}]"
]
}
上面会执行类似:
bash
miiocli genericmiot --ip 192.168.1.20 --token 0123456789abcdef0123456789abcdef raw_command set_properties '[{"did":"你的设备did","siid":2,"piid":3,"value":4200}]'
这里的 did 不建议留空。可以用 xiaomi-cloud-token-extractor 输出里的 id/did,或者先运行 miiocli genericmiot ... status 看设备返回里是否包含 did。
填完后设置会自动保存。若侧边栏没有立刻更新,可以点智能家居卡片右上角的 刷新,或者禁用再启用扩展。
如何获取设备 IP 和 token
你至少需要两个信息:
ip:设备在局域网里的 IP 地址。token:米家设备的本地控制 token,通常是 32 位十六进制字符串。
获取 IP 的常见方法:
- 在路由器后台查看已连接设备。
- 在米家 App 里查看设备网络信息。
- 用局域网扫描工具查找设备地址。
获取 token 的常见方法:
- 使用
python-miio/miiocli的云账号能力列出设备。 - 从米家 App 的本地数据库中导出。
- 使用支持读取米家 token 的第三方工具。
如果你已经安装了 python-miio,可以先查看本机是否有 miiocli:
bash
miiocli --help
不同版本的 python-miio 命令可能略有差异,请以 miiocli --help 输出为准。一般思路是登录小米账号后列出设备,找到目标设备的 IP 和 token。
如果你使用 xiaomi-cloud-token-extractor,它通常会输出 id、name、mac、token 等信息。插件真正需要的是 ip 和 token;id/name/mac 可以保留下来用于识别设备。拿到 token 后还要去路由器后台、米家 App 网络信息页,或用局域网扫描工具找到这个设备当前的局域网 IP。
示例:提取结果里有这些信息:
text
id: 123456789
name: 书桌台灯
mac: AA:BB:CC:DD:EE:FF
token: 0123456789abcdef0123456789abcdef
路由器里查到这个 MAC 对应的 IP 是 192.168.1.20,那么插件里的 JSON 写成:
json
[
{
"id": "123456789",
"name": "书桌台灯",
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miot",
"siid": 2,
"piid": 1
}
]
如果 extractor 输出里本来就有 ip 或 localip 字段,就直接把它填到 ip。
把提取结果转换成插件 JSON
仓库里提供了一个转换脚本 miioTokenToJson.py。新版脚本会按设备块解析,遇到新的 id/did 或空行/分隔线就结算上一台设备,避免把一台设备的 token 和另一台设备的 IP 配错。
把 xiaomi-cloud-token-extractor 的输出保存成文本文件,例如:
bash
xiaomi-cloud-token-extractor > tokens.txt
然后转换:
bash
python3 miioTokenToJson.py tokens.txt
也可以用管道:
bash
xiaomi-cloud-token-extractor | python3 miioTokenToJson.py
如果你准备用小米云端控制,可以直接生成云端 JSON:
bash
python3 miioTokenToJson.py --cloud --username '你的米家账号' --password '你的米家密码' --country cn tokens.txt
它会尽量识别这些字段:
text
id / did
name
model
mac
ip / localip / local_ip
token
country
输出示例:
json
[
{
"id": "123456789",
"name": "书桌台灯",
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miot",
"siid": 2,
"piid": 1
}
]
如果输出里没有 ip,需要你根据 mac 到路由器后台找到设备 IP,再手动补上 ip 字段。不要凭顺序手动把路由器里的 IP 批量贴给转换结果,最好逐台核对 name/mac/id,否则很容易设备错配。如果你准备用小米云端控制,则不需要 ip/token,改用下面的 kind: "cloud" 写法。
如何编写设备 JSON
Device JSON 必须是一个 JSON 数组。每个对象代表一个设备。
常用字段:
| 字段 | 必填 | 说明 |
|---|---|---|
name |
否 | 显示名称 |
room |
否 | 房间名 |
ip |
本地模式必填 | 设备局域网 IP |
token |
本地模式必填 | 设备 token |
kind |
否 | 推荐 genericmiot,也支持 miot、miio 或 cloud |
status |
genericmiot 可选 |
状态命令片段,例如 "status" 或 ["status"] |
actions |
genericmiot 常用 |
前端按钮列表,每个按钮可以绑定 command |
commands |
genericmiot 常用 |
命令字典,按钮的 name 可以引用这里的命令;没写 actions 时会按命令名自动生成按钮 |
controls |
genericmiot 常用 |
滑杆控件列表,适合亮度、色温等数值调节 |
miiocli |
否 | 手动指定 miiocli 路径,通常不用填 |
did |
云端模式必填 | 小米云端设备 ID,也可以用 id |
country |
云端模式常用 | 小米服务器区域,例如 cn、de、us,默认 cn |
cloud_username |
云端模式必填 | 小米账号 |
cloud_password |
云端模式必填 | 小米账号密码 |
icon |
否 | 图标名,默认 power |
siid |
MIOT 常用 | MIOT service id,默认 2 |
piid |
MIOT 常用 | MIOT property id,默认 1 |
status_method |
否 | miot_get、raw 或 none |
set_method |
否 | miot_set 或 raw |
value_type |
否 | bool、int、float、string,默认 bool |
on_value |
否 | 写入开启状态时使用的值 |
off_value |
否 | 写入关闭状态时使用的值 |
timeout |
否 | 本地请求超时秒数,默认 5 |
retries |
否 | 本地请求重试次数,默认 3 |
lazy_discover |
否 | 是否延迟发现设备,默认 true |
推荐:genericmiot 自定义按钮
现在更推荐使用 kind: "genericmiot"。插件不会猜你的设备模型,而是把 status 或 actions[].command 里的命令片段拼到下面这条命令后面执行:
bash
miiocli genericmiot --ip <ip> --token <token> 你的命令片段
配置步骤:
- 先在终端里用完整
miiocli genericmiot --ip ... --token ...命令测试。 - 能跑通后,只复制
--token <token>后面的命令片段。 - 查询状态的片段写到
status。 - 点击按钮要执行的片段写到
actions[].command。
完整示例:
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "genericmiot",
"status": "status",
"actions": [
{"name": "refresh", "label": "状态", "command": "status"},
{"name": "toggle", "label": "切换", "command": "call light:toggle"}
]
}
]
如果命令里有复杂参数,也可以用数组,避免引号被拆错:
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "genericmiot",
"status": ["status"],
"actions": [
{"name": "on", "label": "开启", "command": ["set", "light:on", "true"]},
{"name": "off", "label": "关闭", "command": ["set", "light:on", "false"]},
{"name": "toggle", "label": "切换", "command": ["call", "light:toggle"]}
]
}
]
也可以把命令集中写在 commands 里,按钮只引用 name:
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "genericmiot",
"commands": {
"status": "status",
"on": ["set", "light:on", "true"],
"off": ["set", "light:on", "false"]
},
"actions": [
{"name": "on", "label": "开启"},
{"name": "off", "label": "关闭"}
]
}
]
status 没写时,卡片会显示"已配置",不会把设备标成失败。按钮命令没写或写错时,点击对应按钮才会报错。
MIOT 开关设备示例
很多灯、插座、开关类 MIOT 设备的电源属性是 siid: 2、piid: 1:
json
[
{
"name": "卧室灯",
"room": "卧室",
"type": "灯",
"ip": "192.168.1.31",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miot",
"siid": 2,
"piid": 1,
"icon": "power"
}
]
如果设备的开关属性不是 2/1,需要查询该设备的 MIOT 说明或用调试工具确认正确的 siid/piid。
如果局域网偶尔报 No response from device,可以先把超时加长:
json
[
{
"name": "卧室灯",
"room": "卧室",
"ip": "192.168.1.31",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miot",
"siid": 2,
"piid": 1,
"timeout": 10,
"retries": 5
}
]
老 miIO raw 命令示例
有些旧设备不是 MIOT 属性模型,而是 get_prop、set_power 一类命令。可以这样写:
json
[
{
"name": "空气净化器",
"room": "客厅",
"ip": "192.168.1.32",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miio",
"status_method": "raw",
"status_command": "get_prop",
"status_params": ["power"],
"set_method": "raw",
"set_command": "set_power",
"set_params": ["$value"],
"on_value": "on",
"off_value": "off"
}
]
"$value" 会在执行时替换成 on_value 或 off_value。
小米云端控制示例
如果本地控制报 Unable to discover device,说明电脑无法和设备完成局域网 miIO 握手。这时可以改用小米服务器控制。云端模式不依赖设备局域网 IP,也不依赖 token,但需要小米账号、密码、设备 did/id 和服务器区域。
json
[
{
"name": "书桌台灯",
"room": "书桌",
"kind": "cloud",
"did": "123456789",
"country": "cn",
"cloud_username": "你的米家账号",
"cloud_password": "你的米家密码",
"siid": 2,
"piid": 1
}
]
说明:
did可以使用xiaomi-cloud-token-extractor输出里的id。- 国内米家账号通常用
country: "cn"。 - 欧洲区常见是
de,美国区常见是us。 - 云端控制需要联网,响应会比局域网本地控制慢一些。
- 账号密码会保存在本机 GSettings 配置里,不要提交到公开仓库。
多设备示例
json
[
{
"name": "书桌台灯",
"room": "书桌",
"ip": "192.168.1.20",
"token": "0123456789abcdef0123456789abcdef",
"kind": "miot",
"siid": 2,
"piid": 1
},
{
"name": "客厅净化器",
"room": "客厅",
"ip": "192.168.1.32",
"token": "abcdef0123456789abcdef0123456789",
"kind": "miio",
"status_method": "raw",
"status_command": "get_prop",
"status_params": ["power"],
"set_method": "raw",
"set_command": "set_power",
"set_params": ["$value"],
"on_value": "on",
"off_value": "off"
}
]
手动测试设备
推荐先直接用 miiocli genericmiot 测试设备:
bash
miiocli genericmiot --ip 192.168.1.20 --token 0123456789abcdef0123456789abcdef status
确认命令能跑通后,再运行 helper 测试同一份 JSON:
bash
python3 miioHelper.py status '{"name":"书桌台灯","ip":"192.168.1.20","token":"0123456789abcdef0123456789abcdef","kind":"genericmiot","status":"status","actions":[{"name":"toggle","label":"切换","command":"call light:toggle"}]}'
执行按钮动作:
bash
python3 miioHelper.py set '{"name":"书桌台灯","ip":"192.168.1.20","token":"0123456789abcdef0123456789abcdef","kind":"genericmiot","status":"status","actions":[{"name":"toggle","label":"切换","command":"call light:toggle"}]}' toggle
如果你仍然使用旧的 kind: "miot" 开关模式,也可以这样测试:
bash
python3 miioHelper.py set '{"name":"书桌台灯","ip":"192.168.1.20","token":"0123456789abcdef0123456789abcdef","kind":"miot","siid":2,"piid":1}' true
如果返回 {"ok": true, ...},说明配置基本可用。
常见问题
Enabled: Yes 但 State: INACTIVE:
重新登录,或在 X11 下 Alt + F2 输入 r 重启 GNOME Shell。
智能家居显示缺少 miio:
给 Python command 指向已经安装 python-miio 的 Python,例如虚拟环境里的 Python。
设备一直失败:
检查 IP、token、设备和电脑是否在同一局域网、设备是否支持本地 miIO/MIOT 控制,以及 siid/piid 或 raw 命令是否正确。
局域网 No response from device:
先确认电脑能 ping 到设备 IP:
bash
ping -c 3 192.168.1.31
再用 miiocli 直接测试:
bash
miiocli genericmiot --ip 192.168.1.31 --token 0123456789abcdef0123456789abcdef status
如果 ping 不通,通常是 IP 错了、设备离线、手机/电脑和设备不在同一个网络、访客 Wi-Fi 隔离、VLAN 隔离或路由器禁用了局域网互访。
如果 ping 能通但 miiocli 仍然无响应,常见原因是 token 不匹配、设备固件不开放本地 miIO、设备只允许云端控制,或者 UDP 54321 被网络策略挡住。可以先在 JSON 里加 "timeout": 10, "retries": 5,还不行就改用 kind: "cloud" 云端模式。
token 是否安全:
token 能控制你的设备,不要提交到公开仓库。建议只保存在本机 GSettings 配置里。