9. 云端上传与设备状态监控
本章面向持有云账号与密钥的管理员。现场采集人员不需要阅读,可直接跳过。
设备可以通过 Wi-Fi 把数据直接送到你自己的对象存储(阿里云 OSS、腾讯云 COS、AWS S3、Google Cloud Storage、Azure Blob;本页以阿里云 OSS 为例)。两条通道相互独立、共用同一套凭据,可以只开其中一条:
| 通道 | 上传内容 | 典型用途 |
|---|---|---|
| 录像上传 | 录制完成的 .mcap 文件 | 免插拔取数:设备连上可上网的 Wi-Fi 就自动回传(录制期间暂停),上传成功的录像会从 SD 卡删除 |
| 状态上报 | 一小份设备健康信息 + 一张低分辨率相机图像 | 远程确认设备是否在线、画面是否对、温度与剩余空间 |
凭据保存在设备与本手机的 App 中,数据由设备(用手机录制、或把录像复制到手机时由手机)直连你的存储桶,不经过我们的任何服务器。
你需要配置什么(总览)
一共 5 项,全部在你自己的阿里云账号里完成,只做一次。用第一章的配置脚本可以一次全做好;想手动配置就按「在哪一节」逐项来。
| 要配置的 | 作用 | 在哪一节 |
|---|---|---|
| 1. 一个私有 Bucket | 存放录像和状态文件 | 一 · 第一步 |
| 2. 一个上传密钥(RAM 用户 + 授权) | 设备上传;App 的「测试」和「已上传到云端」列表 | 一 · 第二、三步 |
| 3. 一个只读密钥(RAM 用户 + 授权) | 状态看板读取状态文件 | 三 · 第二步 (用看板才需要) |
| 4. 一条跨域规则(CORS) | 让看板能在浏览器里读取存储桶 | 三 · 第一步 (用看板才需要) |
| 5. 一条生命周期规则 | 状态文件到期自动删除,避免长期累积 | 四 · 费用与保留期限 |
配好之后,需要填进 App「云端上传」页的一共 7 项(其中「区域」会自动从端点读取):
| App 中的字段 | 取值示例 | 从哪来 |
|---|---|---|
| 端点 URL | https://oss-cn-hangzhou.aliyuncs.com | 桶所在地域的地域节点 |
| 区域 | cn-hangzhou | 端点中 oss- 后面那段(App 自动从端点读取) |
| 存储桶 | my-recording-bucket | 第 1 项创建的桶名 |
| AccessKey ID | LTAI…… | 第 2 项的 AccessKey ID |
| AccessKey Secret | (只显示一次) | 第 2 项的 AccessKey Secret |
| 前缀(可选) | recordings/ | 你自己定;要与授权策略里的前缀一致 |
| 状态前缀 | status/ | 保持默认即可 |
只想取录像、不用状态看板的话,第 3、4 项可以跳过,只配 1、2 两项。
一、在阿里云 OSS 上准备存储
只需做一次。已有可用的 Bucket 与密钥时可跳到「二」。
更快的做法:用配置脚本一次配好
下面这些步骤(建桶、建密钥、授权、跨域、保留期限)我们做成了一个脚本,你只需要输入地域和桶名,其余全部自动完成。跑完它还会用刚建好的密钥实测一遍:App 的「测试」能不能通过、录像能不能上传、「已上传到云端」列表能不能列出、看板能不能读到状态。
向我们索取 oss_setup.py,我们会直接发给你。它只用 Python 3.10 以上的标准库,不需要安装任何依赖,也不需要阿里云命令行工具:
python oss_setup.py --region cn-hangzhou --bucket my-recording-bucket
运行前请准备一份有权限创建 Bucket 和 RAM 用户的管理员 AccessKey;脚本只在本次运行中使用它,不会存盘。跑完后它会直接打印要填进 App 的每一项,以及看板专用的只读密钥。
脚本可以重复运行:已存在的桶、用户和策略不会被破坏,你自己已有的生命周期规则也会原样保留。想先看看它会做什么、但不真的执行,加上 --dry-run。
如果你更愿意在控制台手动配置,或者只想给已有的桶补一下权限,按下面三步操作。
第一步:创建 Bucket
- 登录阿里云 → 对象存储 OSS → 「Bucket 列表」→ 创建 Bucket。
- 名称自定(例 my-recording-bucket);地域就近选择(例「华东 1(杭州)」)。
- 读写权限选 私有。不要选公共读,否则任何人都能下载你的数据。
- 其余保持默认,创建完成。
创建后,把下面 3 个值记下来,稍后要填进 App:
| App 中的字段 | 取值示例(杭州) | 在哪看 |
|---|---|---|
| 端点 URL | https://oss-cn-hangzhou.aliyuncs.com | Bucket 概览 → 「外网访问」的 Endpoint,前面加 https:// |
| 区域 | cn-hangzhou | Endpoint 中 oss- 后面那段(App 会自动从端点读取) |
| 存储桶 | my-recording-bucket | 你刚创建的名称 |
端点 URL 要填「地域节点」,不要填「Bucket 域名」。控制台上这两个长得很像:地域节点是 https://oss-cn-hangzhou.aliyuncs.com,而 Bucket 域名里已经带上了桶名(my-recording-bucket.oss-cn-hangzhou.aliyuncs.com)。设备会自动把桶名加在最前面,所以填后者会让桶名出现两次,域名解析不出来。
另外两种也用不了:带 -internal 的内网端点只能在阿里云内部访问,设备连不上;绑定的自定义域名(CNAME)不受支持,请用 oss-<地域>.aliyuncs.com 这种标准端点。
第二步:创建一个专用密钥(RAM 用户)
不要使用主账号的 AccessKey。设备是放在外面的硬件,一旦丢失,主账号密钥等于整个云账号失守。请专门建一个只能访问这一个 Bucket 的子账号,具体权限在第三步收紧。
- 阿里云控制台 → 访问控制 RAM → 「用户」→ 创建用户。
- 登录名称自定(例 ego-device);访问方式勾选 OpenAPI 调用访问(不需要控制台密码)。
- 创建后立即保存 AccessKey ID 与 AccessKey Secret。Secret 只显示一次,关掉页面就看不到了。
第三步:给这个用户授权
RAM → 「权限策略」→ 创建策略 → 选「脚本编辑」,粘贴下面内容(把 my-recording-bucket 换成你的桶名),保存后回到「用户」→ 该用户 → 新增授权 → 选中这条自定义策略。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:PutObject"],
"Resource": ["acs:oss:*:*:my-recording-bucket/*"]
},
{
"Effect": "Allow",
"Action": ["oss:ListObjects"],
"Resource": ["acs:oss:*:*:my-recording-bucket"],
"Condition": {
"StringLike": { "oss:Prefix": ["recordings/*", "recordings/"] }
}
}
]
}
各项权限分别对应什么功能:
| 权限 | 它让什么功能可用 |
|---|---|
| oss:PutObject | 设备上传录像和状态文件。用手机直接录制、或从设备复制到手机的录像,App 也用它上传。 |
| oss:ListObjects(限录像前缀) | App 里的「测试」按钮(列出录像前缀下的一个文件,确认桶与密钥可用)与「录像」页底部的「已上传到云端」列表。 |
| oss:ListObjects(不再需要) | 旧版文档要求的权限,现在的「测试」已不使用;已经授权的可以保留,也可以移除。 |
设备本身只需要「写」。oss:ListObjects 不是上传要用的:它让 App 的「测试」按钮与「已上传到云端」列表可用。少了它设备照样能上传,但「测试」会失败、也看不到这个列表。
两项都不包含读取录像内容,也不包含删除。所以设备即使丢失,别人也无法用它下载或破坏你已有的数据。
策略里的 recordings/ 要和 App 中填的前缀保持一致;改了前缀,这里也要跟着改。前缀留空(录像直接放在桶根目录)时,把整段 Condition 删掉即可。
如果你用的是 AWS S3:同样两件事,换成 S3 的写法——上传密钥给 s3:PutObject(上传)和 s3:ListBucket(App 的「测试」与「已上传到云端」列表);看板密钥给 s3:GetObject + s3:ListBucket,同样限制在状态前缀下。端点填 https://s3.区域.amazonaws.com。腾讯云 COS、Google Cloud Storage、Azure Blob 同理(写入 + 列出录像前缀),在 App 中选择对应服务商后,字段名称会按该云的叫法显示。本页的配置脚本只支持阿里云 OSS,其他云请在各自控制台手动配置。
二、在 App 中配置
App 中进入「设备设置 → 云端上传」。页面分三段:上半段是存储位置(两条通道共用),下面两段分别是两条通道各自的目录前缀和开关。
存储位置
- 服务商:从 Aliyun OSS / Tencent COS / AWS S3 / Google Cloud Storage / Azure Blob 中选择,会自动填入端点模板(阿里云默认为 oss-cn-beijing,请改成桶所在的地域)。
- 按第一步记下的值填 端点 URL / 存储桶(「区域」会自动从端点读取)。
- 填入第二步的 AccessKey ID 与 AccessKey Secret(选 AWS S3 时显示为「访问密钥 ID」「私有访问密钥」)。
录像上传
- 前缀(可选):录像存放的目录,例 recordings/。留空则放在桶根目录。
- 通过 Wi-Fi 自动上传:打开后,设备在未录制且连上可上网的 Wi-Fi 时,按录制时间从早到晚依次回传已完成的录像(录制期间暂停,停止后继续;EGO1GS 机身过热时也会暂停,降温后自动恢复),上传成功的录像会从 SD 卡删除;关闭则录像一直保留在 SD 卡上。
状态上报
- 状态前缀:状态数据存放的目录,默认 status/。与录像前缀分开,便于单独设置保留期限和访问权限。
- 发送状态上报:默认开启——保存凭据且设备连上可上网的 Wi-Fi 后,每 5 分钟上报一次健康信息(EGO1GS 机身过热时暂停,降温后自动恢复);不需要可关闭。
最后点 测试 验证凭据是否可用,再点 保存。手机通过设备热点连接时无法测试(页面顶部会提示),此时可直接保存,设备连上可上网的 Wi-Fi 后自动生效。
两个开关拨动后立即生效;两个前缀输入框需要按「保存」后才生效。私有密钥(阿里云为 AccessKey Secret)出于安全不会回显,显示「已保存——重新输入以更改」表示设备里已有密钥;只想改前缀时不必重新输入密钥。
三、设备状态监控看板
状态上报开启时(默认开启),每台设备会定期往 状态前缀 下写一个很小的 JSON 文件,路径形如 status/YYYY-MM-DD/HH/分秒-设备ID.json(UTC 时间)。我们提供一个单文件网页看板读取这些文件,把整批设备显示在一页上。
一份状态上报包含什么
| 设备与固件 | 设备编号(GILABS-XXXXXXXX)、型号、固件版本、已开机时长 |
| 录制 | 是否正在录制、当前会话名 |
| 存储 | SD 卡剩余百分比、是否未插卡 |
| 温度 | 主控温度、各相机温度 |
| 网络 | Wi-Fi 状态、所连 SSID、IP |
| 上传 | 待上传 / 进行中 / 失败数量 |
| 画面 | 一张 640×360 的相机截图 |
关于画面的隐私默认:设备默认只在录制时才附带截图;空闲时只上报健康数据,看板上显示为「idle」而没有画面。这是头戴设备的保守默认——需要空闲也拍照请联系我们,这应当是一个明确的决定,而不是顺手打开的选项。
如果你用了第一章的配置脚本,本章的第一步(跨域 CORS)和第二步(只读密钥)都已经自动配好了,可以直接跳到第三步:打开看板。
第一步:给 Bucket 配置跨域访问(CORS)
看板是在你的浏览器里直接读取存储桶的。浏览器有一条安全规则:网页要访问另一个域名的数据,必须先由那个域名同意——这条规则叫 CORS。所以需要在存储桶上加一条规则(只做一次)。
控制台路径:Bucket → 权限管理 → 跨域设置 → 创建规则。或用命令行:
cat > cors.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>null</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
<ExposeHeader>Date</ExposeHeader>
</CORSRule>
</CORSConfiguration>
EOF
aliyun oss cors --method put oss://my-recording-bucket cors.xml
为什么来源是 null:直接双击打开的本地网页,浏览器上报的来源就是字符串 null。这条规则不会让别人看到你的数据——每个请求仍然要用密钥签名,桶依然是私有的;CORS 只决定「哪个网页可以读到返回结果」,真正把门的是密钥。如果以后把看板放到固定网址上,把 null 换成那个网址会更严格。
第二步:为看板准备一个「只读」密钥
不要把设备上传用的密钥填进浏览器。按第一章的方法再建一个 RAM 用户,只给它读状态目录的权限。
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:GetObject"],
"Resource": ["acs:oss:*:*:my-recording-bucket/status/*"]
},
{
"Effect": "Allow",
"Action": ["oss:ListObjects"],
"Resource": ["acs:oss:*:*:my-recording-bucket"],
"Condition": { "StringLike": { "oss:Prefix": ["status/*", "status/"] } }
}
]
}
这两项分别是:oss:GetObject 让看板读到状态文件本身,oss:ListObjects 让它列出有哪些状态文件。两项都被限制在状态前缀之内,所以这个密钥读不到录像,也不能写入或删除任何东西——可以放心填进浏览器。
如果你把状态前缀改成了别的目录,把上面两处 status/ 一起改掉。
第三步:打开看板
- 取得看板文件 index.html(位于公开仓库 visio-schema 的 tools/fleet-status/ 目录,也可由我们直接提供)。看板目前支持阿里云 OSS 与 AWS S3。
- 双击打开即可,不需要安装任何东西,也不需要联网服务。
- 在设置面板填入:端点 URL、区域、存储桶、状态前缀(默认 status/)、以及第二步的只读密钥。
- 点「Test」验证,再点「Connect」。凭据保存在这台电脑的浏览器里,文件本身不含任何密钥,可以放心分享给同事。
看板怎么读
- 顶部计数:共多少台、live(正常)/ late(迟到)/ dark(失联)各多少。
- 卡片:缩略图、左上角 REC(正在录制)或 IDLE(空闲)、右上角距上次上报的时间;下方是设备编号与温度 / 剩余空间 / Wi-Fi。
- 左侧色条与红色数字:绿=正常,黄=迟到,红=失联或告警。触发告警的情况包括主控温度过高、SD 卡剩余空间不足、有上传失败、设备时钟未同步。
- 迟到的判断是相对每台设备自己的上报间隔:5 分钟上报一次和 1 分钟上报一次的设备,不会用同一个绝对时间来衡量。
- 视图:Grid(卡片墙)/ Table(表格,设备多时更好用)/ Wall(大图墙,适合挂在显示器上)。
已经上报过、但最近失联的设备会继续显示为 dark,不会从列表里消失——设备掉线恰恰是最需要看到的情况。
四、常见问题
| 现象 | 原因与处理 |
|---|---|
| App 里点「测试」提示 Access denied(HTTP 403) | 密钥缺少 oss:ListObjects,或策略中的前缀与 App 里填的「前缀」不一致;也可能是密钥、区域或端点填错。用第一章的配置脚本可以一次排除这一类问题。 |
| App 里「测试」按钮不可用 | 手机正通过设备热点连接(页面顶部有提示)。请让设备连上可上网的 Wi-Fi,再通过同一 Wi-Fi 或 USB 连接设备后测试;也可直接「保存」,设备联网后自动生效。 |
| 提示域名无法解析 / 找不到服务器 | 端点填成了 Bucket 域名(里面已经含桶名),或用了 -internal 内网端点、自定义域名。见一·第一步下方的提示。 |
| 「测试」通过,但录像一直没上传 | 密钥缺少 oss:PutObject——「测试」只验证能否列出录像前缀,不验证能否写入。也请确认「通过 Wi-Fi 自动上传」已打开、设备已连上可上网的 Wi-Fi,且当前没有在录制(录制期间上传暂停)。EGO1GS 机身过热时也会暂停上传,降温后自动继续。 |
| App「录像」页看不到「已上传到云端」列表 | 密钥缺少 oss:ListObjects,或 App 里填的前缀与策略 Condition 中的前缀不一致。 |
| 看板提示网络 / CORS 错误 | 存储桶还没配跨域规则(见三·第一步),或规则里没有包含 null 这个来源。 |
| 看板提示 403 | 密钥、区域或桶名不对;也可能填的是只能写的上传密钥——看板需要读权限。 |
| 看板一台设备都没有 | 状态前缀与 App 里填的不一致;或设备还没上报(默认 5 分钟一次)。 |
| 卡片只有数据、没有画面 | 正常。默认只在录制时拍照,空闲显示 idle。 |
| 设备一直显示 dark | 设备断电、不在 Wi-Fi 覆盖内、云端上传没有配置凭据,或「发送状态上报」已关闭。EGO1GS 机身过热时会暂停上报,期间可能短暂显示为 late / dark,降温后恢复。 |
费用与保留期限
状态上报的文件很小(几百字节到几十 KB)。按默认 5 分钟一次计算,每台设备每天约 288 个文件,费用以对象存储的请求次数为主,量级很低。但文件数量会一直累积,所以请给状态前缀单独设一条生命周期规则:录像是要长期保存的,状态数据不是。
控制台路径:Bucket → 数据管理 → 生命周期 → 创建规则,按下面三项填:
| 设置项 | 填什么 |
|---|---|
| 应用范围 / 前缀 | status/(与 App 里的状态前缀一致) |
| 规则状态 | 启用 |
| 过期删除 | 距最后修改 30 天后删除(按需调整) |
范围一定要限定在状态前缀上。如果规则的前缀留空,它会作用于整个桶,把你的录像一起删掉。
深入了解
AWS S3 与其它服务商
AWS S3 的设备密钥策略如下,替换你的桶名与前缀:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DeviceUploads",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-recording-bucket/*"
},
{
"Sid": "ListRecordingsPrefix",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-recording-bucket",
"Condition": {
"StringLike": { "s3:prefix": ["recordings/*", "recordings/"] }
}
}
]
}
看板用的只读密钥给 s3:GetObject(<bucket>/status/*)加限制在 status/ 前缀的 s3:ListBucket。两把密钥分开,设备的密钥读不到状态目录,看板的密钥写不了任何东西。
| 服务商 | 端点(示例地域) |
|---|---|
| AWS S3 | https://s3.us-east-1.amazonaws.com |
| 阿里云 OSS | https://oss-cn-hangzhou.aliyuncs.com |
设备根据端点的主机名选择请求签名方式,没有服务商开关可选错;这也意味着 OSS 桶前面的自定义 CNAME 域名会被按 AWS S3 的方式签名而被桶拒绝。请填标准的 oss-<地域>.aliyuncs.com
或 s3.<地域>.amazonaws.com 主机。
腾讯云 COS(固件 1.1.0 起)、Google Cloud Storage 与 Azure Blob(EGO1GS 固件 1.1.3 起、EGO1 固件 1.1.6)同样支持:在 App 的服务商里选择,字段会按该服务商自己的叫法显示(App 1.1.3 起)。它们的权限原则相同:设备只需写入,加上让「测试」与列表可用的录像前缀的列表权限;看板只需状态前缀下的读取。
上传如何工作
- 一段录制在 SD 卡上完成并封口。
- 设备按录制时间从早到晚排队,在未录制且连上可上网的 Wi-Fi 时上传;录制中或 EGO1GS 过热时暂停,停止录制或降温后恢复。
- 成功后删除本地副本,为下一次会话腾出卡空间。
规划时值得知道的几点:
- 上传需要你的 Wi-Fi,不是设备热点。 热点没有互联网;设备处于热点下时上传暂停、稍后恢复,什么也不丢。
- 只上传已完成的录制。 分片只有关闭并带有 MCAP 文件尾标记后才可上传,正在写的分片会被跳过。进行中的采集绝不会传一半。
- 长录制会变成多个对象。 采集按 256 MB 分片写入,每片独立上传,但录制期间暂停上传,停止录制后继续。
- 队列在重启与固件升级后仍然保留。 它存在升级不会替换的那部分文件系统里,中断的上传会从断点继续。
- 每片单次请求上传。 没有分段上传,这也是 256 MB 分片大小的原因。
- 不需要、也不会请求删除权限。 系统中没有任何东西会删除你桶里的内容。
- 一次录制的所有分片都传完后,设备会写入完成标记
session.json(固件 1.1.0 起),便于确认一次录制是否已完整上传。 - 阿里云 OSS 上设备在每次 PUT 时带禁止覆盖头,重复上传会被拒绝而不是悄悄替换;AWS S3 忽略该头,但键的唯一性同样保证上传不会覆盖早先的录制。
失败会以逐渐拉长的间隔重试——从约一秒到五分钟——12 次后该分片停止重试并显示为失败。保存修正后的凭据会重置失败项,立即重试。
文件落在哪里
对象按设备与会话排在你的前缀下:
<prefix>/<device-serial>/<session>-<start-time>/<part>.mcap
例如:
recordings/26887a068c4e93f0/session_00042-1784017895/ego_0000.mcap
recordings/26887a068c4e93f0/session_00042-1784017895/ego_0001.mcap
<device-serial>是 16 位设备序列号,App 的「设备信息」中也会显示。<session>-<start-time>是会话计数加其起始 Unix 时间戳。时间戳保证键唯一:换卡或格式化后会话计数会重新开始,没有它两段不同的录制可能撞到同一个键。- 录制前设置的任务、地点、采集者、备注写在 MCAP 内部,所以单个对象同时携带数据与标签;SD 卡上会话目录旁的
session.json也会在全部分片传完后作为完成标记写入桶中。
状态上报落在一棵单独的、按时间分区的树里——status/<date>/<hour>/<time>-<serial>.json——这也是它们需要自己的前缀与自己的生命周期规则的原因。
在 App 中监控
设备页的上传队列显示三个计数:待上传、进行中、失败。录像列表里每个会话带一个徽标;多分片的会话显示其中最差的状态,一片卡住也看得见:上传中(进行中)、已上传(已在你的桶里确认)、上传失败(重试用尽,检查目的地)。App 还能列出桶里已有的内容(「云端录像」)——那是手机在直接访问你的桶,手机处于设备热点上时没有互联网,列表加载不出来,连到普通 Wi-Fi 即可。
排查速查
设备会把服务端自己的错误码转成具体的提示而不是笼统的失败,所以请读「测试」按钮说了什么——AccessDenied、SignatureDoesNotMatch 与 InvalidAccessKeyId
都是 HTTP 403,但含义完全不同。
| 现象 | 含义与处理 |
|---|---|
| 「测试」失败:签名被拒绝 | 私有密钥或端点错了——不是桶策略。完整重输密钥;粘贴时带了尾部空格是最常见原因。检查用的是服务商的真实端点而不是 CNAME。 |
| 「测试」失败:访问密钥不识别 | 访问密钥 ID 错了或密钥已被删除。重输 ID。 |
| 「测试」失败:访问被拒绝 | 先查 oss:ListObjects 是否授权;其次是区域或端点填错,或前缀与策略 Condition 中的前缀不一致(含结尾斜杠)。 |
| 「测试」失败:端点重定向 | 通常是桶在另一个地域。带签名的请求不能跟随重定向,请把端点指向错误信息中指出的地域。 |
| 「测试」失败:找不到桶 | 桶名错了,或根本没创建。 |
| 「测试」不可用 | 你在设备热点上——没有互联网。照样保存,设备连上 Wi-Fi 后再测。 |
| 什么都不上传,队列显示待上传 | 设备不在能上网的网络上。看 设备信息 → Wi-Fi;状态为 AP fallback 时它在自身热点上,上传按设计暂停。 |
| 全部显示上传失败 | 几乎总是凭据或权限。修正并重新保存——保存修正后的目的地会自动重置失败项以便重试。 |
| 队列一直增长、从不清空 | 上行带宽跟不上录制速度。把持续上传带宽与默认码率下的约 16 Mbps 比较。 |
| 上传正常但卡还是满了 | 上传后删除才是释放卡空间的动作。已上传但删不掉的分片会在队列的最后错误中报告。 |
| 一个会话卡在 Repairing | 它被写入中途的断电撕裂了。一分钟没有新字节后设备把该片视为已结束,就地修复再上传。断电前先停止录制可避免。 |
| 录像能上传,但桶列表为空 | 写入正常而列表不正常。密钥缺少录像前缀上的列表权限——两项权限各自独立地失败。 |
设备时钟偏差
设备没有 NTP 客户端。它的时钟来自开机时的 RTC,手机连接时会被校正。S3 与 OSS 都会拒绝签名时间与服务器相差超过 15 分钟的请求,所以时钟不对的设备原则上可能什么都传不了。设备自己会处理:桶的每个响应都带服务器时间,设备从第一次被拒绝的响应中学到真实时间,之后签名正确。实际表现是冷启动且 RTC 失效后的第一次请求失败、重试成功,你不需要做任何事。
这个自校正只用于请求签名,有意不写回系统时钟。从未被手机连接过的设备可以正常上传,却写着 1970 年的录制时间戳。如果 MCAP 文件里的时间戳对你重要,部署后请用 App 连接每台设备一次。
强制门户网络
设备只用网络名和密码加入 Wi-Fi。它无法通过强制门户——需要点击同意、输房间号或网页登录的访客 Wi-Fi。设备会关联并拿到 IP,App 里看起来已连接,但每次上传都因门户拦截而失败。请使用不需要门户的网络:带预共享密钥的 WPA2/WPA3 网络、专用 IoT SSID 或手机热点。站点只有门户 Wi-Fi 时,改用 MicroSD 卡或 USB。
码率与上行带宽
默认每相机 8 Mbps 时,一台录制中的设备产生约 16 Mbps 的视频。若开着自动上传近乎连续地录制,站点的上行带宽必须持续高于此值,否则队列会随录制一直增长、卡也跟着填满。要么给整批设备的上行留出余量,要么调低码率(见 4. 码率、存储与上行带宽),要么计划让队列在下班后清空。
上传在录制期间暂停:估算带宽时还需预留足够的停录时段,让队列完成上传;只比较瞬时码率并不足够。