本文档对应 AiMonitor 当前实现,描述设备内置 HTTP 服务提供的全部接口。
| 项目 | 说明 |
|---|---|
| 地址 | http://<设备局域网 IP>:<自动选择的端口> |
| 传输协议 | HTTP/1.1 明文传输 |
| HTTP 端口 | 从 10241 起使用首个可用端口 |
| JSON 编码 | UTF-8 |
| 最大请求头 | 16 KiB |
| 最大请求体 | 8 MiB |
| 连接读取超时 | 5 秒 |
| 同时处理的连接 | 最多 4 个,另排队 16 个 |
| 图片上传并发 | 1(其余上传排队) |
| 支持的图片 | JPEG、PNG、GIF |
| 鉴权 | 无 |
设备 IP、端口和健康检查地址可在应用的“设置”页面查看。服务没有身份验证,建议仅在可信局域网中使用,不要直接暴露到公网。
下文示例假设 10241 未被占用;发生冲突时,请将示例端口替换为设置页或发现结果返回的实际端口。
请求队列已满时,服务返回 503 Service Unavailable,客户端应采用带抖动的退避策略稍后重试。图片上传会串行处理,以限制大请求正文带来的瞬时内存占用;普通查询和宫格更新仍可并发执行。
服务启动后会通过 mDNS/DNS-SD 发布 _aimonitor._tcp.。客户端可以通过
Android NSD、Apple Bonjour 或其他 DNS-SD 客户端自动发现设备地址和端口。
服务的 TXT 记录包含:
| 属性 | 说明 |
|---|---|
id |
首次启动生成并持久化的设备 UUID |
name |
用户在设置页配置的设备名称 |
apiVersion |
HTTP API 版本 |
path |
设备信息接口,当前为 /api/device |
局域网或路由器禁用组播时,mDNS 可能不可用,此时仍可使用设置页显示的 IP 和端口连接。
客户端还可以使用 UDP 广播发现。向局域网广播地址的 UDP 8080 端口发送
UTF-8 文本 AIMONITOR_DISCOVER_V1。设备只响应完全匹配的探测消息,并向
客户端数据报的来源 IP 和来源端口单播回复:
{"id":"17e13bf7-98e7-4c3c-b66e-76d21eb44554","name":"客厅监控屏","port":10241,"apiVersion":"3"}UDP 回复固定只包含 id、name、port 和字符串类型的 apiVersion。
客户端应使用收到回复的数据报来源地址作为设备 IP,再通过回复中的 port
连接 HTTP API。
所有响应都包含以下 CORS 头:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type| 方法 | 路径 | 用途 |
|---|---|---|
OPTIONS |
任意路径 | CORS 预检 |
GET |
/health |
检查服务状态 |
GET |
/api/device |
获取设备身份和能力信息 |
GET |
/api/config |
获取宫格和端口配置 |
POST |
/api/images |
上传图片 |
GET |
/api/images |
扫描并返回全部缓存图片 |
GET |
/api/images/{filename} |
获取一张原始图片 |
DELETE |
/api/images/{filename} |
删除一张图片 |
POST |
/api/slots/{slot} |
更新宫格 |
DELETE |
/api/slots/{slot} |
清空宫格 |
POST |
/api/clients/{clientId}/heartbeat |
为控制端占用的宫格续租 |
推荐的业务调用顺序:
- 调用
POST /api/images上传图片。 - 从响应中取得 UUID 文件名。
- 将稳定的控制端
clientId和图片文件名调用POST /api/slots/{slot}。 - 每 30 秒调用
POST /api/clients/{clientId}/heartbeat。 - 不再使用图片时,调用
DELETE /api/images/{filename}。
除“获取原始图片”接口外,响应正文均为 JSON:
Content-Type: application/json; charset=utf-8错误响应统一使用 error 字段:
{
"error": "错误说明"
}带正文的请求必须发送正确的 Content-Length。当前服务不支持 HTTP chunked request body。
上传成功后,服务根据图片实际格式生成文件名:
<UUID>.<extension>
示例:
7604efdb-28c8-4aa2-b8b1-d95762cc396f.png
宫格接口只接收文件名,不再接收 Base64。文件名只允许字母、数字、点、下划线和连字符,且扩展名必须是 jpg、jpeg、png 或 gif。
服务对任意路径的 OPTIONS 请求返回成功。
响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8{
"status": "ok"
}检查 HTTP 服务是否可访问。
成功响应:
HTTP/1.1 200 OK{
"status": "ok"
}示例:
curl http://192.168.1.100:10241/health发现服务后,获取设备的稳定标识、显示名称、型号和接口能力。
成功响应:
{
"id": "17e13bf7-98e7-4c3c-b66e-76d21eb44554",
"name": "客厅监控屏",
"manufacturer": "Google",
"model": "Pixel Tablet",
"androidVersion": "16",
"appVersion": "1.1.1",
"apiVersion": 3,
"port": 10241,
"rows": 3,
"columns": 5,
"capabilities": ["images", "slots", "heartbeats"]
}客户端应使用 id 识别和记忆设备,使用 name 向用户展示。mDNS 实例名称
可能因局域网内重名而被系统修改,不应作为稳定标识。
获取当前宫格行数、列数和监听端口。
成功响应:
HTTP/1.1 200 OK{
"rows": 3,
"columns": 5,
"port": 10241
}| 字段 | 类型 | 说明 |
|---|---|---|
rows |
Integer | 当前宫格行数,范围 1–5 |
columns |
Integer | 当前宫格列数,范围 1–5 |
port |
Integer | HTTP 服务实际监听端口(从 10241 起自动选择) |
上传 JPEG、PNG 或 GIF。服务通过文件签名识别实际格式,不依赖原文件名或客户端声明的 MIME 类型。
推荐使用 multipart/form-data,文件字段名为 file:
curl -F "file=@avatar.png" \
http://192.168.1.100:10241/api/images等价 HTTP 请求:
POST /api/images HTTP/1.1
Host: 192.168.1.100:10241
Content-Type: multipart/form-data; boundary=BOUNDARY
Content-Length: ...
--BOUNDARY
Content-Disposition: form-data; name="file"; filename="avatar.png"
Content-Type: image/png
<图片二进制>
--BOUNDARY--也可以直接将图片作为请求正文:
curl --data-binary "@avatar.png" \
-H "Content-Type: image/png" \
http://192.168.1.100:10241/api/imagesHTTP/1.1 200 OK{
"filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}| 字段 | 类型 | 说明 |
|---|---|---|
filename |
String | 服务生成的 UUID 图片文件名 |
| 状态码 | 响应 | 原因 |
|---|---|---|
400 |
{"error":"an image file is required"} |
请求正文为空、multipart 缺少文件或 boundary 无效 |
400 |
{"error":"image must be a valid JPG, PNG, or GIF file"} |
文件签名不是受支持的图片,或图片无法写入缓存 |
413 |
{"error":"request body too large"} |
完整请求体超过 8 MiB |
扫描图片缓存目录,并分页返回有效图片的元数据。结果按文件名升序排列,列表接口不会读取或 Base64 编码图片正文。
查询参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
offset |
0 |
从第几条记录开始,必须大于等于 0 |
limit |
50 |
每页记录数,范围 1~100 |
成功响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8{
"images": [
{
"filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png",
"mimeType": "image/png",
"size": 123456,
"url": "/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}
],
"offset": 0,
"limit": 50,
"total": 1,
"hasMore": false
}没有图片时:
{
"images": [],
"offset": 0,
"limit": 50,
"total": 0,
"hasMore": false
}| 字段 | 类型 | 说明 |
|---|---|---|
images |
Array | 图片列表 |
images[].filename |
String | UUID 图片文件名 |
images[].mimeType |
String | image/jpeg、image/png 或 image/gif |
images[].size |
Number | 图片文件字节数 |
images[].url |
String | 按需获取原图的相对 URL |
offset |
Number | 当前页起始位置 |
limit |
Number | 当前页大小 |
total |
Number | 有效图片总数 |
hasMore |
Boolean | 是否还有下一页 |
需要显示图片时,客户端应使用 images[].url 按需加载原图。
按文件名获取原始图片字节。
示例:
curl -o downloaded.png \
http://192.168.1.100:10241/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png成功响应:
HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: inline; filename="7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
Content-Length: ...
<图片二进制>错误响应:
HTTP/1.1 404 Not Found{
"error": "image not found"
}当文件不存在、文件名不合法、扩展名不受支持或缓存文件不是有效图片时,均返回 404。
从缓存中删除图片。
示例:
curl -X DELETE \
http://192.168.1.100:10241/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png成功响应:
HTTP/1.1 200 OK{
"status": "deleted",
"filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}如果某个宫格正在引用该图片,图片删除后宫格的用户名、AI 名称和内容仍会保留,图片区域立即切换为默认占位图。
文件不存在或文件名不合法时:
HTTP/1.1 404 Not Found{
"error": "image not found"
}更新一个宫格的完整内容。slot 使用从 1 开始的编号。
当前内部最多有 25 个宫格,因此合法范围为 1–25。页面实际显示数量由 rows × columns 决定;更新暂时不可见的合法宫格仍会返回成功。
请求:
POST /api/slots/1 HTTP/1.1
Host: 192.168.1.100:10241
Content-Type: application/json
Content-Length: ...
{
"clientId": "550e8400-e29b-41d4-a716-446655440000",
"username": "小明",
"aiName": "CodeX",
"content": "运行正常",
"image": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
clientId |
String | 是 | 控制端稳定唯一 ID;1-128 位字母、数字、- 或 _ |
username |
String | 是 | 用户名称;首尾空白会被移除,处理后不能为空 |
aiName |
String | 是 | AI 名称;首尾空白会被移除,处理后不能为空 |
content |
String | 否 | 展示内容;缺省或 null 时使用空字符串 |
image |
String | 是 | 上传接口返回的图片文件名,不是 Base64 |
成功响应:
HTTP/1.1 200 OK{
"status": "updated",
"slot": 1
}只要 image 是格式合法的图片文件名,即使缓存中找不到对应文件,宫格更新仍返回 200。应用会保留该文件名,并在图片区域显示默认占位图。
这也适用于图片被系统清理或随后通过删除接口移除的情况。
| 状态码 | 响应 | 原因 |
|---|---|---|
400 |
{"error":"invalid JSON"} |
请求正文不是有效 JSON |
400 |
{"error":"clientId is required"} |
clientId 缺失或格式无效 |
400 |
{"error":"username is required"} |
username 缺失、不是字符串或处理后为空 |
400 |
{"error":"aiName is required"} |
aiName 缺失、不是字符串或处理后为空 |
400 |
{"error":"image is required"} |
image 缺失、不是字符串或处理后为空 |
400 |
{"error":"image must be a valid uploaded filename"} |
图片文件名包含非法字符或扩展名不受支持 |
404 |
{"error":"slot must be between 1 and 25"} |
slot 超出合法范围 |
清空指定宫格中的用户名、AI 名称、内容、图片引用和更新时间。该操作不会删除图片缓存文件。
示例:
curl -X DELETE http://192.168.1.100:10241/api/slots/1成功响应:
HTTP/1.1 200 OK{
"status": "cleared",
"slot": 1
}宫格编号超出范围:
HTTP/1.1 404 Not Found{
"error": "slot must be between 1 and 25"
}刷新控制端租约,不需要请求正文。clientId 必须与槽位更新中的字段一致,控制端
应每 30 秒调用一次。设备每 30 秒检查租约;连续 2 分钟未收到心跳时,会把该
clientId 拥有的全部槽位重置为空,效果等同于逐个调用 DELETE,不影响其他客户端。
curl -X POST \
http://192.168.1.100:10241/api/clients/550e8400-e29b-41d4-a716-446655440000/heartbeat成功响应:
{"status":"alive","clientId":"550e8400-e29b-41d4-a716-446655440000"}| 状态码 | 响应 | 说明 |
|---|---|---|
400 |
{"error":"invalid HTTP headers"} |
请求头不完整、超过 16 KiB 或连接提前结束 |
400 |
{"error":"invalid content length"} |
Content-Length 为负数 |
404 |
{"error":"not found"} |
路径或 HTTP 方法没有对应接口 |
413 |
{"error":"request body too large"} |
请求体超过 8 MiB |
以下示例使用 jq 从上传响应中读取文件名:
BASE_URL="http://192.168.1.100:10241"
CLIENT_ID="550e8400-e29b-41d4-a716-446655440000"
FILENAME="$(
curl -sS -F "file=@avatar.png" "$BASE_URL/api/images" |
jq -r '.filename'
)"
curl -sS \
-H "Content-Type: application/json" \
--data "{
\"clientId\":\"$CLIENT_ID\",
\"username\":\"小明\",
\"aiName\":\"CodeX\",
\"content\":\"运行正常\",
\"image\":\"$FILENAME\"
}" \
"$BASE_URL/api/slots/1"
curl -sS -X POST \
"$BASE_URL/api/clients/$CLIENT_ID/heartbeat"
curl -sS "$BASE_URL/api/images" | jq
curl -sS -o downloaded-image \
"$BASE_URL/api/images/$FILENAME"
curl -sS -X DELETE \
"$BASE_URL/api/images/$FILENAME"
curl -sS -X DELETE \
"$BASE_URL/api/slots/1"- 图片保存在 Android 应用的
cacheDir/monitor_images目录。 - 上传图片在应用进程重启后仍然存在。
- Android 系统可能在存储空间不足时清理缓存。
- 卸载应用或清除应用数据会删除缓存图片。
- 宫格内容当前保存在进程内存中,应用进程重启后会恢复为空宫格。
- 控制端心跳租约保存在进程内存中;2 分钟未续租会自动清空该控制端的宫格。
- 行数和列数保存在应用偏好设置中,应用重启后仍然保留。
- 删除宫格不会删除图片;删除图片也不会清空宫格文字。
- 没有鉴权、TLS、速率限制或访问日志接口。
- 不支持分块请求体。
- 不支持局部更新宫格;每次更新都必须提供
username、aiName和image。 - 不提供读取单个宫格或列出全部宫格的接口。
- 不支持 WebP、SVG、BMP、AVIF 等其他图片格式。