Asdoos/SonyGpsApp
GitHub: Asdoos/SonyGpsApp
一款通过 BLE 将手机 GPS 坐标无中介传输至索尼相机的 Android 工具,绕过官方应用实现隐蔽定位注入。
Stars: 0 | Forks: 0
# Sony GPS Link
[](https://github.com/Asdoos/SonyGpsApp/releases/latest)
[](https://github.com/Asdoos/SonyGpsApp/actions)
[](https://developer.android.com)
[](https://kotlinlang.org)
[](LICENSE)
一款通过蓝牙低功耗(BLE)将智能手机 GPS 坐标传输到索尼相机的 Android 应用程序——无需使用原始的 Sony Creators App。
**[⬇ Download latest APK](https://github.com/Asdoos/SonyGpsApp/releases/latest)**
## 功能概述
| 功能 | 说明 |
|---|---|
| BLE 相机发现 | 扫描索尼设备(制造商 ID 301) |
| GPS 传输 | WGS-84,每 5 秒发送一次,91 或 95 字节数据包 |
| APO 保持活跃 | 防止相机进入休眠模式,每 9 秒发送一次 |
| 自动重连 | 在意外断开后最多尝试 10 次重连 |
| 前台服务 | GPS + BLE 持续运行,即使应用关闭 |
## 支持的相机
所有支持索尼专有 BLE GPS 协议(`LocationInfoFromSmartPhone_1_0` / `_1_1`)的索尼相机,包括:
- Sony ZV-E1、ZV-E10、ZV-1 II
- Sony ILCE 系列(α7 IV、α7C、α7R V、α6700、...)
- Sony FX3、FX30
相机必须**通过 Android 系统蓝牙设置**与手机配对,才能在此应用中使用。
## 工作原理
### 1. 相机发现(BLE 扫描)
索尼相机的 BLE 广告中始终包含**制造商 ID 301**(`0x012D` = Sony Corporation, per Bluetooth SIG)。应用仅过滤此 ID:
```
ScanFilter: ManufacturerData(companyId = 301, data = [])
ScanMode: SCAN_MODE_LOW_LATENCY
```
发现的设备会以列表形式展示给用户,显示名称、MAC 地址和信号强度(RSSI)。
### 2. BLE GATT 协议(索尼专有)
索尼相机暴露一个专有 GATT 协议,包含两个相关服务:
#### 服务 1 — 相机控制(`CC00`)
```
UUID: 8000CC00-CC00-FFFF-FFFF-FFFFFFFFFFFF
```
| 特征(Prefix) | 类型 | 内容 |
|---|---|---|
| `0000CC02` | 写入 | 通用相机控制命令(包括 APO 避免) |
#### 服务 2 — GPS / 位置(`DD00`)
```
UUID: 8000DD00-DD00-FFFF-FFFF-FFFFFFFFFFFF
```
| 特征(Prefix) | 类型 | 内容 |
|---|---|---|
| `0000DD01` | 通知 | 相机 → 手机:传输禁用信号 `{3,1,2,0}` |
| `0000DD11` | 写入 | GPS 载荷(91 或 95 字节) |
| `0000DD21` | 读取 | 能力标志:字节[4] & `0x02` → 时区支持 |
| `0000DD30` | 写入 | 锁定:`{0x01}` = 获取,`{0x00}` = 释放 |
| `0000DD31` | 写入 | 位置传输:`{0x01}` = 开启,`{0x00}` = 关闭 |
| `0000DD32` | 读取 | 时间校正设置(信息性) |
| `0000DD33` | 读取 | 区域调整设置(信息性) |
### 3. 连接协议(握手序列)
在 GATT 连接和服务发现后,执行以下序列:
```
① enableNotify(DD01) — camera can abort transfer at any time
↓ CCCD descriptor written
② write {0x01} → DD30 — acquire exclusive lock
↓ write callback OK
③ write {0x01} → DD31 — enable GPS transfer on camera
↓ write callback OK
④ read DD32 — read time correction setting
↓ read callback
⑤ read DD33 — read area adjustment setting
↓ read callback
⑥ read DD21 — read capability flags
↓ read callback: byte[4] & 0x02 → timezoneSupport = true/false
⑦ → onReady(): start GPS updates + APO keepalive timer
```
所有 GATT 操作通过基于 `ArrayDeque` 的操作队列**串行化**执行,因为 BLE 仅允许一个并发操作。每个操作仅在前一个操作的回调收到后才开始。
### 4. GPS 数据包格式
来源:`BluetoothLeUtil.setLocationAndTime()` + `TransferringLocationInfoWithLockState.onLocationUpdated()`
GPS 坐标以**大端二进制数据包**形式写入特征 `DD11`。根据相机能力存在两种格式:
#### 格式 A — 91 字节(不支持时区)
#### 格式 B — 95 字节(支持时区,当 `DD21[4] & 0x02 != 0`)
**坐标编码:** `(double) 度数 × 1e7` → `toInt()` → `ByteBuffer.allocate(4).putInt(...)`
**时间戳:** UTC 时区,基于 `location.getTime()` 通过 `Calendar.getInstance(UTC)` 获取
**验证:** 超过 **10 秒** 旧的 GPS 定位将被丢弃(基于 `elapsedRealtimeNanos` 差值)
### 5. APO 保持活跃(自动断电避免)
相机在约 30 秒不活动(待机/休眠模式)后会断开 BLE 连接。
因此原始索尼应用每 **9 秒** 发送一次保持活跃命令:
```
Service: 8000CC00-CC00-FFFF-FFFF-FFFFFFFFFFFF
Characteristic: 0000CC02-...
Value: {0x03, 0x08, 0x10, 0x00}
Interval: 9,000 ms
```
保持活跃**独立于** GPS 更新运行,通过相同的操作队列,并在每次成功写入回调后重新调度。写入失败不会中止会话——下一次尝试将在固定间隔进行,因为单次失败是非关键性的。
### 6. 自动重连
在意外连接丢失(相机休眠尽管有保持活跃、超出范围等)时,应用自动尝试重连:
- **延迟:** 尝试之间间隔 4 秒
- **最大尝试次数:** 10 次
- **计数器重置:** 成功完成握手(`onReady`)时重置
- **不重连:** 如果用户手动点击“停止”(`userStopped` 标志)
### 7. 前台服务与能效
#### 为何需要前台服务?
| Android 机制 | 无服务时的影响 | 使用前台服务时 |
|---|---|---|
| Doze Light(屏幕关闭约 3 分钟) | GPS 回调被批处理/延迟 | 豁免 |
| Doze Deep(深度空闲) | `Handler.postDelayed` 冻结 → APO 保持活跃失效 | 豁免 |
| 内存压力 | 进程被杀死 | `START_STICKY`:系统自动重启 |
#### 服务类型声明(Android 14+ / API 34 必需)
```
android:foregroundServiceType="location|connectedDevice"
```
- `location` — 授予前台服务中的 GPS 访问权限
- `connectedDevice` — 授予前台服务中的 BLE 访问权限
#### 功耗估算(屏幕关闭时)
| 组件 | 平均电流 |
|---|---|
| GPS 芯片(`HIGH_ACCURACY`,5 秒间隔) | ~40–60 mA |
| BLE(连接 + ~6 次写入/10 秒) | ~2–4 mA |
| CPU(回调、数据包组装) | ~1–2 mA |
| APO 保持活跃(包含在 BLE 中) | ~0.1 mA |
| **应用增量总计** | **~45–65 mA** |
| **实际包含系统基线** | **~75–90 mA** |
→ **每小时约消耗 4–7% 电量**(在典型 4000 mAh 设备上)
→ 类似于 Strava 等后台运行 GPS 追踪应用
### 8. 架构
```
┌─────────────────────────────────┐
│ MainActivity │
│ ┌──────────────┐ │
│ │ BLE Scan │ (short-lived)│
│ └──────┬───────┘ │
│ │ device selected │
│ ┌──────▼───────────────────┐ │
│ │ startForegroundService │ │
│ │ bindService (Binder) │ │
│ └──────────────────────────┘ │
│ StatusListener (UI updates) │
└────────────┬────────────────────┘
│ Binder (LocalBinder)
┌────────────▼────────────────────┐
│ GpsForegroundService │ ← runs persistently in background
│ │
│ FusedLocationProviderClient │ GPS every 5 s
│ Handler (APO timer, reconnect) │
│ │
│ ┌──────────────────────────┐ │
│ │ SonyCameraGatt │ │
│ │ ┌──────────────────┐ │ │
│ │ │ ArrayDeque │ │ │ Serialised op-queue
│ │ │ (Op-Queue) │ │ │
│ │ └──────────────────┘ │ │
│ │ GATT Callback │ │
│ └──────────────────────────┘ │
│ │
│ Persistent Notification │ "Stop" action directly in notification
└─────────────────────────────────┘
│ BLE GATT
┌────────────▼────────────────────┐
│ Sony Camera │
│ Service CC00 (Control) │ ← APO keepalive {3,8,16,0} every 9 s
│ Service DD00 (GPS) │ ← GPS packet (91/95 B) every 5 s
└─────────────────────────────────┘
```
## 项目结构
```
SonyGpsApp/
├── app/src/main/
│ ├── java/com/example/sonygps/
│ │ ├── GpsForegroundService.kt Foreground service: GPS + BLE session management
│ │ ├── MainActivity.kt UI: BLE scan, camera selection, service binding
│ │ ├── SonyCameraGatt.kt BLE GATT client: handshake, op-queue, APO keepalive
│ │ └── SonyGpsPacket.kt GPS packet assembly (91/95 bytes, Sony format)
│ ├── res/
│ │ ├── layout/activity_main.xml
│ │ ├── drawable/ic_launcher_*.xml
│ │ ├── mipmap-anydpi-v26/
│ │ └── values/themes.xml, colors.xml
│ └── AndroidManifest.xml
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/gradle-wrapper.properties
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties android.useAndroidX=true
└── gradlew / gradlew.bat
```
## 构建与安装
### 要求
- Android Studio Hedgehog(2023.1.1)或更新版本
- Android SDK 34
- Kotlin 1.9.x
- 运行 Android 8.0+(API 26)并支持 BLE 的物理设备
### 步骤
1. 在 Android Studio 中打开 `SonyGpsApp/` 文件夹
2. 等待 Gradle 同步完成(所有依赖自动下载)
3. 通过 USB 连接设备
4. **运行**(`Shift+F10`)
## 权限
| 权限 | 用途 | 所需最低 API |
|---|---|---|
| `ACCESS_FINE_LOCATION | GPS 坐标 | API 1 |
| `ACCESS_COARSE_LOCATION` | 备用位置 | API 1 |
| `BLUETOOTH_SCAN` | BLE 扫描 | API 31 |
| `BLUETOOTH_CONNECT` | GATT 连接 | API 31 |
| `BLUETOOTH` + `BLUETOOTH_ADMIN` | BLE(旧版) | API ≤ 30 |
| `FOREGROUND_SERVICE` | 启动前台服务 | API 28 |
| `FOREGROUND_SERVICE_LOCATION` | 前台服务中的 GPS 访问 | API 34 |
| `FOREGROUND_SERVICE_CONNECTED_DEVICE` | 前台服务中的 BLE 访问 | API 34 |
| `POST_NOTIFICATIONS` | 显示服务通知 | API 33 |
## 已知限制
- **需要配对:** 相机必须通过 Android 系统蓝牙设置配对(不能通过本应用)。未实现 Sony 应用的配对协议。
- **仅支持 BLE GPS 协议:** 不支持 WiFi(PTP/IP,端口 15740)和 USB(PTP/MTP)。
- **无实时取景器:** 仅传输 GPS 数据,未实现相机控制和图像传输。
- **GPS 精度:** `PRIORITY_HIGH_ACCURACY` 使用硬件 GPS 芯片。在室内或信号弱条件下,超过 10 秒的旧定位将被自动丢弃。
## 资料来源与逆向工程
| 文件(从 Sony Creators App v3.3.1 反编译) | 发现 |
|---|---|
| `BluetoothLeUtil.java` | GPS 数据包编码(`setLocationAndTime`) |
| `TransferringLocationInfoWithLockState.java` | 握手序列、数据包结构、字节布局 |
| `ExecutingApoAvoidanceState.java` | APO 保持活跃命令 `{3,8,16,0}`,间隔 9,000 毫秒 |
| `BluetoothGattUtil.java` | UUID 常量、所有命令的字节常量 |
| `EnumCameraInfo.java` | 时区支持标志(格式 A 与格式 B) |
| `BluetoothLeUtil.startLeScanWithLowPower()` | BLE 扫描过滤器(制造商 ID 301) |
标签:Android, Android 8.0, APO保持活跃, BLE, DSL, GPS, IoT, Kotlin, Sony相机, WGS-84, 位置传输, 低延迟扫描, 制造商数据过滤, 前台服务, 后台服务, 开源, 无线传输, 物联, 相机控制, 索尼相机, 自动重连, 蓝牙, 蓝牙低功耗, 设备发现