本章使用 FreeRTOS 托管入口接入仪表媒体广告。SDK 处理已认证的接收流程;你提供存储、持久回执、解码与播放器。
接收与去重
每台设备在自己的持久存储中按广告 ID 去重,最多启动一次播放。完整接收、校验并持久保存回执后,APP 才记录接收成功。修改素材需新建广告 ID。
接收成功与播放成功不同。断电、离开当前认证会话、播放失败或播放中断均不触发重新发送或下次开机补播。广告只能在当前安全空闲会话中播放,仪表启动本身不会触发播放。
支持的仪表素材
| 用途 | 输入约束 |
|---|---|
| 仪表静态图 | ≤ 5 MiB;实际宽高不能超过终端上报能力 |
| 仪表 GIF | ≤ 10 MiB,≤ 10 秒,≤ 300 帧,累计解码像素 ≤ 1 亿,≤ 1920×1080 |
| 仪表 MP4 | H.264,≤ 50 MiB,≤ 15 秒,≤ 30fps,≤ 1920×1080;转码时移除音轨 |
设备可声明更小的容量与尺寸上限;仅声明已实现解码和播放的格式。
终端接入
调用 moto_terminal_config_init 后设置 config.advertising。端口类型为 moto/advertising.h 中的 moto_ad_port。
| 配置 | 填写要求 |
|---|---|
max_size / max_chunk |
素材/分片字节上限;协议最大 50 MiB / 1024 字节 |
mime_mask |
用 MOTO_AD_MIME_MASK(...) 声明已实现的格式 |
max_width / max_height |
解码原图的最大尺寸,不是缩放后的显示尺寸 |
| 8 个回调 | 全部实现;签名见下方示例,逐项返回值见“素材结构与返回值” |
回调在 SDK 任务执行,必须有界、不可阻塞,不得重入 SDK。哈希建议随写入增量计算;finish 只有验证和持久记录完成才返回成功。素材可使用 RAM 或 Flash,但接收回执必须跨重启、素材清理与解绑保留。存储满时返回错误,不得清理回执后继续接收旧广告。
启用广告需要完整的广告端口与设备云认证。
Wi-Fi 控制与数据
控制复用 Wi-Fi 主业务 TLS 会话;可选 bulk TLS 通道传输素材,失败后可在主业务通道按确认偏移继续。data.wifi.purpose=2 表示广告,data.wifi.task 为广告 UUID;OTA 使用 purpose=1。
配置 config.bulk 的 TCP 收发端口后设置 wifi_advertising=true。收到 purpose 2 事件后复用主业务热点、核对 IP/DHCP 并准备该任务的 TCP 监听,再调用 moto_terminal_bulk_ready;SDK 生成随机 PSK 并管理 TLS,同一接口也支持 OTA purpose 1。bulk 通道只能写入当前授权广告,状态、完成和播放仍走 Wi-Fi 主业务通道。断开、停止或会话切换时清理任务 TCP 与播放资源,保留主业务热点和发现监听。
广告传输和播放与导航、视频、仪表样式切换、OTA 互斥。safe_idle 应根据车辆和显示状态决定是否允许播放,不中断进行中的导航。
配置示例与回调签名
广告素材配置使用 moto_terminal_config.advertising;moto_terminal_advertising(t, out) 用于生成 BLE 发现广播。
以下示例仅启用 PNG。board_ad_* 由板级驱动实现;容量和尺寸按实际硬件填写。在 moto_terminal_config_init 后、moto_terminal_create 前调用 configure_advertising。
#include <moto_freertos/terminal.h>
#include <moto/advertising.h>
int board_ad_lookup(void *u, const uint8_t ad[16],
uint8_t hash[32], bool *consumed);
int board_ad_begin(void *u, const moto_ad_media *media);
int board_ad_write(void *u, uint32_t offset,
const uint8_t *bytes, size_t size);
int board_ad_finish(void *u, const moto_ad_media *media);
int board_ad_consume(void *u, const uint8_t ad[16]);
int board_ad_play(void *u, const moto_ad_media *media);
void board_ad_stop(void *u);
bool board_ad_safe_idle(void *u);
void configure_advertising(moto_terminal_config *c, void *board_context) {
c->advertising = (moto_ad_port){
.user = board_context,
.max_size = 256u * 1024u,
.max_chunk = 512,
.mime_mask = MOTO_AD_MIME_MASK(MOTO_AD_PNG),
.max_width = 800,
.max_height = 480,
.lookup = board_ad_lookup,
.begin = board_ad_begin,
.write = board_ad_write,
.finish = board_ad_finish,
.consume = board_ad_consume,
.play = board_ad_play,
.stop = board_ad_stop,
.safe_idle = board_ad_safe_idle,
};
c->wifi_advertising = false;
}
广告端口全零表示关闭。回调不完整或上限无效时,create 在 CONFIG 阶段返回 INVALID;可用 moto_ad_port_validate 预先检查端口。
advertising.user 独立于 port.user,创建时只复制指针,应存活到 SDK 销毁。media、ad、bytes 等输入只在回调期间有效;异步显示须复制元数据或字节,不能保存 SDK 借出的地址。
素材结构与返回值
moto_ad_media 为已经过协议校验的素材描述:
| 字段 | 类型与含义 |
|---|---|
ad[16] |
广告 UUID 的 16 个原始字节,是持久去重键;不是以 \0 结尾的字符串 |
task[16] |
本次传输任务 UUID 的原始字节;不能替代广告 ID 去重 |
sha256[32] |
整个素材文件的 SHA-256;驱动须实际核验,不能只复制成“校验成功” |
size |
uint32_t,完整文件字节数,范围 1 到端口 max_size |
mime |
uint8_t:1 PNG、2 JPEG、3 GIF、4 H.264 MP4;mime_mask 使用 1u << mime |
width / height |
uint16_t,素材原始像素尺寸,须在声明的解码上限内 |
duration_ms |
uint32_t,最长 15000 毫秒;0 时 SDK 使用 3000 毫秒停止计时;驱动可在素材自然结束时提前结束 |
端口 max_size 为 1–52428800 字节,max_chunk 为 1–1024 字节,宽高上限必须非零。上述协议上限不代表板端有相应内存、存储或解码性能。
| 回调 | 成功返回值 | 失败处理 |
|---|---|---|
lookup |
0 不存在;1 已接收,并填入 hash、consumed |
存储读取失败返回负错误码,不能按“不存在”处理 |
begin / write / finish / play |
MOTO_OK(0),本步实际完成后返回 |
返回负 moto_result,如 MOTO_IO、MOTO_FULL、MOTO_UNSUPPORTED |
consume |
0 本次首次消费;1 已消费 | 持久化失败返回负错误码 |
stop |
无返回值;可重复调用,无活动素材时也应安全 | 清理接收/播放及其专用资源,始终保留持久回执 |
safe_idle |
true 当前允许展示;false 拒绝开始或停止正在播放的广告 |
使用车辆和显示系统的真实状态 |
这里的 lookup/consume 返回 1 是各自的业务结果,不是异步等待。广告回调不支持返回 MOTO_AGAIN 后再调用 moto_terminal_complete 完成;也没有 MOTO_EVENT_AD_* 供应用事件循环接收广告内容。回调在 SDK 任务同步执行,必须有界,不能等待另一任务长时间擦写 Flash 或等待整段播放结束。write 应增量计算哈希,finish 完成核验及原子持久提交;耗时存储平台需先评估这一同步接口是否满足目标时延。
接收与播放顺序
lookup → begin → write → finish → consume → play → stop
成功接收与实际曝光分别处理。finish 后的记录必须能由 lookup 立即读回同一哈希;consume 在 play 之前提交。即使播放失败或这两步之间掉电,也不清除消费记录,不在下次连接或启动时补播。
write 的 offset 为连续接收偏移。finish 后保留回执;再次查询已接收素材时只返回记录,不重新播放。
广告数据通道接口
注册 config.bulk.send/close 并启用 wifi_advertising。板级负责热点、IP/DHCP、TCP 监听和有序字节收发,SDK 管理 TLS 认证与数据接收。
| 接口 | 用法和返回值 |
|---|---|
moto_terminal_bulk_ready(t, &e, &ap) |
保存的 purpose 1/2 WIFI 事件,AP/IP/TCP 监听实际就绪后提交;ap.port 为 TCP 端口。OK 仅表示入队,FULL 未提交,原参数重试 |
moto_terminal_bulk_connected(t, &e, &connection) |
TCP accept 后传监听器保存的原 WIFI 事件;OK 输出新的非零连接代次;BUSY 已有连接;STATE 尚未处理 ready 或事件已失效,关闭此次 socket |
moto_terminal_bulk_receive(t, connection, bytes, size) |
每次 1–512 字节 TLS 密文,OK 全部复制,FULL 完全未接收;保持顺序重试,不可跳过或重复 |
moto_terminal_bulk_disconnected(t, connection) |
TCP 断开,直接撤销该代次,不依赖接收队列空闲;旧代次 STATE |
config.bulk.send(user, connection, bytes, size) |
在 SDK 任务调用;复制实际接收字节并返回数量,0 暂忙,负数失败;不得阻塞 |
config.bulk.close(user, connection) |
在 SDK 清理时异步、幂等地关闭指定 TCP 代次;不阻塞等待,不关闭后来建立的连接 |
moto_terminal_complete(t, &e, MOTO_FAILED, reason) |
热点准备失败;不用此接口确认广告写入或播放 |
bulk.user 由板级持有至销毁;输入数据只借用至回调返回。bulk_rx_slots 默认 8,范围 1–128,仅启用 bulk 时分配。所有入口在任务调用,不用于 ISR。监听器必须复制原 WIFI 事件,不能用最新会话替换。
SDK 同时接受一条 bulk TCP 连接。断开、WIFI_STOP 或会话失效后清理连接;重新传输需使用新的 WIFI 事件完成就绪流程。异步失败通过错误事件和 get_status().error 查询。关闭终端前停止 BLE/TCP 输入和事件消费者。
从 bulk 回到主业务通道前,关闭 bulk 并查询设备已确认的接收偏移;不得同时向两条链路写入。
