photo-demo-ios

iOS 原生 Demo通过 BLE 连接相机硬件,接收其自动/手动拍摄的照片并显示。 上层数据帧遵循《相机传图协议 V1.02_20260707》——完整协议已沉淀在 PROTOCOL.md(含实测勘误),直接查询用。 BLE广播 Service 0xFF10(当前固件实际广播为字节序反的 10FF,已兼容)、蓝牙名 CmLXXXXXX、传图通道 = 服务 FF10 / 特征 FFF2WRITE+NOTIFY 双向)。

结构

photo-demo-ios/
├── PhotoProtocol/            # 纯 Foundation 协议层(可离线 swift test
│   └── Sources/PhotoProtocol/
│       ├── CameraProtocol.swift   # 常量、cmd 枚举、ack 码
│       ├── CameraFrame.swift      # 帧结构 / 编码 / 累加和校验 / 常用帧构造
│       └── FrameParser.swift      # 流式拆帧magic + data_len抗噪重同步
├── Sources/
│   ├── BLE/BleTypes.swift         # UUID(沿用旧固件)、状态、错误
│   ├── BLE/BleClient.swift        # CoreBluetooth 封装(零依赖)
│   ├── Transfer/PhotoTransferManager.swift  # 传图状态机 + ObservableObject
│   └── App/                       # SwiftUI 入口与单页 UI
├── project.yml                    # XcodeGen 工程定义
└── Info.plist

构建

# 1. 协议层单测(无需设备/模拟器)
cd PhotoProtocol && swift test

# 2. 生成 Xcode 工程并编译
cd .. && xcodegen generate
open photo-demo.xcodeproj    # 选真机运行BLE 无法在模拟器测)

协议帧格式

magic(2)=0xABCD | cmd(1) | packet_idx(2) | total_packet(2) | data_len(2) | ack_code(1) | reserve(1) | check_sum(1) | DATA(0~200)
  • 多字节字段 wire 上为大端check_sum = 前 11 头字节 + 全部 DATA 累加 & 0xFF。
  • cmd0x10/0x11/0x12 查数量0x200x23 请求传图(ACK/连续)0x300x33 图片数据0x40 完成0x50 对时。

交互流程ACK 模式)

查数量(0x10) → 请求(0x20) → 设备 ACK(有图) → 逐包 0x30 / 每包回 ACK → 0x40 完成 → 回 ACK(设备删图) → 自动请求下一张 → 直至设备返回「无图」。

⚠️ 卡点 / 待确认Blockers

状态标记:🔴 阻塞进行中的一步 · 🟡 影响运行正确性(不影响编译) · 🟢 已解决

# 卡点 状态 说明 / 当前处理
B1 飞书协议文档需登录WebFetch 抓不到 🟢 用户直接粘贴正文解决
B2 BLE 传输层用哪套 UUID 🟢 已确认:沿用旧 duooomi 固件(service 000002c4 / write 000002c5 / read 000002c6)
B3 真机能否扫到目标设备 🟢 已扫到设备
B3b 设备真实的 Service/写/通知特征 UUID 🟢 硬件日志确认:服务 FF10 → 特征 FFF2(WRITE+NOTIFY一特征双向)。App 默认已选 FFF2另可手动改选
B4 图片编码格式(JPEG/PNG/裸流) 🟡 现用 UIImage(data:) 自动识别CLI 落盘后按文件头判格式。待实传一张确认
B5 一次请求传一张还是全部 🟡 现按「每张 0x40 后自动再请求下一张」直到设备回非成功 ACK。若固件一次性发全部需调终止判定
B6 ack_code 错误码表 🟡 已知 0x80 成功 / 0xF8 忙(传图中) / 0xF6 无照片(0x23 实测);其余按「无更多照片/出错」结束会话
B7 写特征响应类型 🟢 已自动探测:支持带响应写用 withResponse否则退化 withoutResponse
B8 固件广播 UUID 字节序反:广播的是 10FF 而非 FF10 🟡 客户端已双向兼容(FF10/10FF 均识别)。待硬件修正
B9 固件带 data 回包 check_sum 错误(如 0x10 回包 F0协议算 3C) 🟢 固件已于 2026-07-22 修复(实测 219 包零校验错误)。客户端暂保持宽容模式观察,稳定后改回严格校验
B10 固件断开连接后不恢复广播,需重启设备才能再连(用户实测确认) 🔴 BLE 外设断开后应自动重新 advertising。影响重连体验待硬件修正
B11 按拍照键后 0x11 手动未上传仍=0照片未计入「手动」类 🟡 待确认按键拍照的归类逻辑CLI 已加降级:无手动图自动改拉自动图(0x21)
B12 连续模式丢包:一张图 219 包丢约 30 包,固件侧认为全部已发出(2026-07-22 实测) 🔴 判断为固件调 notify 过快,协议栈 TX 缓冲满被丢弃(notify 返回失败未检查)。建议固件检查发送返回值失败重发,或加大发包间隔。0x26 有 0x24 补包兜底0x23 无补包通道
B13 GATT UUID 变更2026-07-22 固件实测 服务 FF12 / 写 FF13 / 通知 FF14(文档写 FF10/FFF2) 🟡 App 按属性自动兜底选中,不受影响;文档与固件需统一口径
B14 Android 收完一张后卡「接收中 N/N 包」转圈 🟢 App bugAndroid GATT 单在途写限制「完成ACK+请求下一张」连发丢第二条。已加串行写队列修复(2026-07-22),同时修复 0x26 连发 0x24 补包只出第一条的隐患

📋 进度日志Changelog

2026-07-22 · 首次实传成功 + B9 修复确认 + Android 写队列修复

  • 首次真机实传成功(Android)3 张手动照片收到并可见缩略图;固件 check_sum 修复后 219 包零校验错误(B9 🟢)。
  • 新发现:连续模式一张图丢约 30 包而固件认为已发完(B12 🔴,疑似 notify 过快栈缓冲溢出)GATT 实测变为 FF12/FF13/FF14(B13)。
  • 修复 Android 卡转圈(B14)GATT 单在途写限制导致「完成ACK+请求下一张」连发丢第二条 → BleClient 加串行写队列;断开时清 SD 会话;发送失败自动收尾会话。
  • ⏭️ 固件改发送侧(查 notify 返回值/加间隔)后复测 0x23 丢包率;跑通 0x26+0x24 补包全流程。

2026-07-21 · 照片详情(EXIF) + 上传接口 + CLI 降级拉自动图

  • 照片详情页:点缩略图弹 sheet(修复 List 行内 NavigationLink 手势失效),大图 + 基本信息 + EXIF/TIFF/GPS 元数据分组展示。
  • 上传接口(仿 duooomi-ios-sdk demo)POST {apiHost}/api/auth/loomart/file/upload-s3(multipart file + x-api-key);详情页「上传照片」→ 返回 CDN URL 可复制。token 在 Sources/Upload/UploadSecrets.swift(gitignore模板入库)。
  • CLI查全三个数量0x23 被拒(0xF6 无照片)自动降级 0x21 拉自动图;落盘后打印 EXIF。
  • 实测新增:错误码 0xF6=无照片(B6)B10 固件断开后不恢复广播需重启(用户确认,🔴)B11 按键拍照未计入手动类。
  • CLI build / App BUILD SUCCEEDED。
  • ⏭️ 设备重启后跑 0x21/0x23 实传,确认图片格式(B4)与上传链路。

2026-07-21 · Mac 实测连通 + 指令全通 + 对齐最新文档

  • 新固件(广播 FF10、蓝牙名 CmLXXXXXX、SD 卡自动拍照、手动长按关机实测Mac CLI 扫到 CmL3D8C64 并连接成功。
  • 指令实测全通0x10=50 张自动、0x11=0 张手动未传、0x12=10 张手动总数、0x50 对时成功ack 均 0x80
  • 发现两个固件问题B8/B9广播 UUID 字节序反(10FF);带 data 回包 check_sum 错(疑似 struct 对齐填充混入求和)。客户端均已兼容。
  • 对齐最新文档:新增 0x13 删除SD卡所有照片App 加红色按钮0xF8=设备忙);FrameParser 支持宽容校验模式(告警不丢帧)。
  • App 补「拉取手动未上传照片(连续模式 0x23」按钮扫描识别兼容 10FF。
  • CLI 重构为集中测 0x23连续收 0x33 → 拼包 → 0x40 → 落盘 /tmp/manual_photo_N.<ext>(按文件头识别 JPEG/PNG/BMP带包序跳变告警与静默看门狗。
  • 协议层单测通过CLI build 通过App BUILD SUCCEEDED。
  • ⏭️ 手动拍几张后跑 0x23 实传确认图片格式B4与多张传输行为B5

2026-07-20 · 确认传图通道 FF10/FFF2 + 手动改选特征

  • 硬件端调试日志确认:相机协议通道 = 服务 FF10 / 特征 FFF2WRITE+NOTIFY一特征双向
  • App 默认自动选 FFF2 为写+通知GATT 列表每个特征加「设为写/设为通知」按钮,可手动改选(备选 FF13 写 / FF14 通知)。
  • BleClient 支持运行时切换写/通知特征并重建报告。
  • ⏭️ 真机连上点「查询数量」验证收发;通了即做拉图落地。

2026-07-20 · 自动取 UUID + 连接发指令一条龙

  • 设备实际用 FFFx 族 UUID非旧 000002c4改为不依赖固定 UUID:连上后遍历全部服务/特征,按属性自动挑写特征(Write/WriteNoResp)与通知特征(Notify/Indicate)。
  • 连接逻辑改为「只要连上就成功」,不再因找不到旧 UUID 失败,从而能看到 GATT。
  • App 内新增「GATT 服务/特征」展示区:列出所有 UUID + 属性,自动选中的写/通知打标签UUID 可长按复制;日志同步打印整棵 GATT 树。
  • 用户操作闭环:点设备→自动连+自动取值→点按钮发指令(0x10/0x50/0x20…)→日志看响应。
  • xcodebuild iphonesimulator BUILD SUCCEEDED。
  • ⏭️ 真机连上后截图 GATT确认自动识别的写/通知是否正确B3b

2026-07-20 · 扫描环节强化

  • 顶部实时显示蓝牙适配器状态(就绪/关闭/未授权),不再静默失败。
  • 蓝牙未就绪时点扫描,记下意图待 poweredOn 自动补开扫。
  • 扫全部设备(不按 service 过滤),避免目标设备未广播 service 被漏;广播了目标 service 的设备标 并置顶。
  • 优先用广播 localName;新增 RSSI 信号强度条AllowDuplicates 持续刷新)。
  • xcodebuild iphonesimulator BUILD SUCCEEDED。
  • ⏭️ 下一步:真机实测扫描,反馈 B3。

2026-07-20 · 项目搭建 + 协议层

  • 参照 duooomi-ios-sdk 的 demo 模式新建工程XcodeGen + 本地 SwiftPM 协议包 + 零依赖 CoreBluetooth 封装)。
  • 实现协议层:帧编解码 / 累加和校验 / 流式拆帧BLE 层复用旧 duooomi UUID传图状态机SwiftUI 单页 UI。
  • 完整链路:扫描→连接→对时(0x50)→查数量(0x10/11/12)→拉图(0x2023)→逐包接收(0x3033)+逐帧 ACK→0x40 完成回 ACK→自动拉下一张→拼包→显示。
  • 协议层 swift test 7/7 通过(测试向量取自文档示例校验和 0x88 / 0x0F
  • App xcodebuild iphonesimulator BUILD SUCCEEDED。

维护约定:每次迭代在「进度日志」置顶追加一条(日期·标题 + 要点 + 验证结果 + 下一步);卡点变化同步更新上表状态。

Description
娃娃相机 ios demo,前期测试用
Readme 167 KiB
Languages
Swift 100%