Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fvmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"flutter": "3.47.1"
"flutter": "3.41.9"
}
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,11 @@ server
/.kimi-code/
CONTEXT.md
tmp/script.md

# HarmonyOS port build/debug temp files
HANDOFF.md
hvigor-env-dump.json
lock_result.txt
node-procs.txt
proc_list.txt
ps-test.txt
29 changes: 7 additions & 22 deletions .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,20 @@
# This file should be version controlled and should not be manually edited.

version:
revision: "ad70ec4617166f1c38e5d2bfd388af71fda14f06"
channel: "stable"
revision: "2f31a8af0a8418dc593bb65c6dbca9405de74902"
channel: "[user-branch]"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: android
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: ios
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: linux
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: macos
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: web
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: windows
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
create_revision: 2f31a8af0a8418dc593bb65c6dbca9405de74902
base_revision: 2f31a8af0a8418dc593bb65c6dbca9405de74902
- platform: ohos
create_revision: 2f31a8af0a8418dc593bb65c6dbca9405de74902
base_revision: 2f31a8af0a8418dc593bb65c6dbca9405de74902

# User provided section

Expand Down
69 changes: 69 additions & 0 deletions harmonyos-port/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# OronBox → HarmonyOS (OpenHarmony NEXT) 移植方案

> 本文基于 `OronBox` 真实 `pubspec.yaml`(已逐项核对)与 `universal_ble` 的真实 Pigeon 契约撰写。
> 本目录 `harmonyos-port/` 是移植脚手架,需在装有 DevEco Studio + OHOS SDK + flutter ohos 的机器上收尾。

## 一、结论

- **UI 与业务逻辑(约 70% 代码)可直接复用**:Flutter-OH(CPF-Flutter)社区适配已成熟。
- **真正要动刀的只有 4 处平台强相关代码**:BLE 蓝牙、原生 C/C++ 库、后台同步、WebView 登录。
- 最小可用版(砍掉音频 WASM/原生库、mDNS)约 3~4 周;全功能约 5~8 周。

## 二、版本决策(关键)

- OronBox 用 fvm 锁 **Flutter 3.47.1**,但 `pubspec.yaml` 的 `environment.sdk: ^3.13.0`,约束宽松。
- 鸿蒙社区适配版 **CPF-Flutter**(`atomgit.com/CPF-Flutter/flutter_flutter`)已发布到 `oh-3.44.9-release`。
- **选用 `oh-3.44.9-release`**:离 3.47.1 仅差 3 个 minor,降级幅度远小于退回 3.35.7,依赖约束冲突最少。
- 需把 `OronBox/.fvmrc` 的 3.47.1 改到 3.44.9,并处理少量 Dart/插件 API 差异。

## 三、依赖兼容性矩阵(基于真实 pubspec)

| 依赖 | 版本 | ohos 现状 | 处理 |
|------|------|-----------|------|
| flutter / flutter_localizations | sdk | CPF-Flutter 提供 | 换 ohos flutter |
| go_router | ^17.3.0 | ✅ 纯 Dart | 直接复用 |
| flutter_riverpod | ^3.3.2 | ✅ 纯 Dart | 直接复用 |
| url_launcher | ^6.3.1 | ✅ CPF 已适配 | 换依赖 |
| shared_preferences | ^2.5.5 | ✅ 已适配 | 换依赖 |
| path_provider | ^2.1.6 | ✅ 已适配 | 换依赖 |
| protobuf / fixnum | ^6.0.0 / ^1.1.1 | ✅ 纯 Dart | 直接复用 |
| **universal_ble** | ^2.1.0 | ❌ 无 ohos | **fork 加 Pigeon C++ host(见下)** |
| **flutter_soloud** | ^4.0.12 | ❌ C++ 原生 | 首版砍掉音频,后续 NDK 移植 |
| **wasm_run_flutter** | ^0.1.0 | ❌ WASM runtime | 首版 stub,后续 NDK |
| **quickjs_engine** | ^0.1.5 | ❌ C 原生 | NDK 交叉编译(纯 C+CMake,难度中) |
| **mdns_dart** | ^2.2.2 | ❌ 原生 mDNS | 首版 stub,用 ohos 网络发现替代 |
| **desktop_drop** | ^0.8.0 | ❌ 桌面专用 | 条件导入守卫(仅 desktop 启用) |
| **window_manager** | ^0.5.2 | ❌ 桌面专用 | 条件导入守卫 |
| **tray_manager** | ^0.5.3 | ❌ 桌面专用 | 条件导入守卫 |
| oronbox_network | git zxor-org | ✅ 纯 Dart | 直接复用 |
| dio / file_picker / cross_file 等 | — | ⚠️ 部分需 ohos 适配 | 逐个核对 CPF 列表 |

## 四、四个攻坚点

1. **BLE(最核心)**:app 的 GATT 逻辑全在 `lib/src/core/services/ble_gatt_driver.dart`,只通过 `UniversalBle.*` 调用。
→ **fork `universal_ble` 加 `ohos` 平台**,用 Pigeon 生成 C++ host,在 C++ 里调 `@ohos.bluetooth.ble`。
→ 这样 `ble_gatt_driver.dart` 一行都不用改。真实契约见 `UNIVERSAL_BLE_PIGEON_CONTRACT.md`,C++ 骨架见 `ohos/universal_ble_ohos/cpp/`。
2. **原生 C/C++ 库**:`flutter_soloud`/`quickjs_engine`/`wasm_run` 需用 OHOS NDK(`--target=aarch64-linux-ohos`)交叉编译,经 NAPI 挂入 ohos 工程。首版建议砍掉音频与 WASM。
3. **后台定时同步**:鸿蒙后台管控严格,需用 `backgroundTaskManager` 长时任务(BLUETOOTH_INTERACTION)+ 代理提醒,纯轮询会被杀。模板见 `ohos/universal_ble_ohos/ets/BackgroundSync.ets`。
4. **WebView 登录**:小米 2FA 用 ArkWeb(`webview_flutter` 的 ohos 适配版)。模板见 `ohos/universal_ble_ohos/ets/XiaomiLogin.ets`。

## 五、建议执行顺序

空壳 ohos 工程 → 换通用插件 → BLE 真机连通一台手环 → 迁移原生库 → 后台任务。每步单独提交,方便回退。

## 六、许可证

OronBox 为 **AGPL-3.0**:你移植后的版本一旦分发(含网络服务)必须同样开源;对 `universal_ble` 的 fork 同理。

## 七、本目录文件清单

- `README.md` — 本文件(方案 + 依赖矩阵)
- `UNIVERSAL_BLE_PIGEON_CONTRACT.md` — BLE 平台通道真实契约
- `runbook.md` — DevEco 内逐步执行手册
- `pubspec.ohos.patch` — pubspec.yaml 需做的具体修改
- `ohos/entry/src/main/module.json5` — 权限与能力声明
- `ohos/entry/src/main/ets/entryability/EntryAbility.ets` — 平台通道注册入口
- `ohos/universal_ble_ohos/ets/UniversalBleOhosPlugin.ets` — NAPI 加载器
- `ohos/universal_ble_ohos/cpp/universal_ble_ohos_host.cpp` — Pigeon C++ host 骨架
- `ohos/universal_ble_ohos/ets/BackgroundSync.ets` — 后台同步模板
- `ohos/universal_ble_ohos/ets/XiaomiLogin.ets` — 小米登录模板
78 changes: 78 additions & 0 deletions harmonyos-port/UNIVERSAL_BLE_PIGEON_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# universal_ble — OpenHarmony 平台通道契约(必须逐条对齐)

> 来源:`Navideck/universal_ble`(OronBox 使用 `^2.1.0`)。
> 关键修正:它不是普通 `MethodChannel`,而是用 **Pigeon** 生成跨平台消息层。
> 通道名 = Pigeon `@HostApi(name: '...')` 的类名;必须用 `StandardMessageCodec`,不能手写 MethodChannel 字符串方法。

## 通道(HostApi = 原生端需实现;FlutterApi = Dart 端回调)

| 类名 | 类型 | 说明 |
|------|------|------|
| `UniversalBlePlatformChannel` | HostApi | 中心设备(Central),app 真正调用 |
| `UniversalBleCallbackChannel` | FlutterApi | 事件回调:扫描结果 / 连接状态 / 特征值变化 |
| `UniversalBlePeripheralChannel` | HostApi | 外设模式(app 不用,可留空实现) |
| `UniversalBleAndroidChannel` | HostApi | 仅 Android |

## UniversalBlePlatformChannel 方法签名(需在 C++ host 实现)

```dart
// 返回值/参数已简化,完整以 pigeon 定义文件为准
Future<BleAvailabilityState> getBluetoothAvailabilityState();
Future<bool> isBluetoothEnabled();
Future<void> startScan(ScanFilter filter, bool androidUseFineLocation);
Future<void> stopScan();
Future<void> connect(String deviceId);
Future<void> disconnect(String deviceId);
Future<ConnectionState> getConnectionState(String deviceId);
Future<List<Service>> discoverServices(String deviceId);
Future<Uint8List> readValue(String deviceId, String characteristicId, [String? descriptorId]);
Future<void> writeValue(String deviceId, String characteristicId, Uint8List value, WriteType writeType);
Future<void> setNotifiable(String deviceId, String characteristicId, BleInputProperty bleInputProperty);
Future<int> requestMtu(String deviceId, int mtu);
Future<void> pair(String deviceId);
Future<bool> isPaired(String deviceId);
Future<int> getRssi(String deviceId);
```

## UniversalBleCallbackChannel 回调(C++ 端需主动调用,把事件推回 Dart)

```dart
void onScanResult(ScanResult result);
void onConnectionChanged(String deviceId, ConnectionState connectionState);
void onValueChanged(String deviceId, String characteristicId, Uint8List value);
void onPairingStateChange(String deviceId, bool isPaired, String? error);
void onBluetoothAvailabilityChange(BleAvailabilityState state);
```

## 枚举 / 数据模型(节选)

- `BleAvailabilityState`:unknown / unavailable / turningOn / on / turningOff / off
- `ConnectionState`:unknown / connecting / connected / disconnecting / disconnected
- `WriteType`:withResponse / withoutResponse
- `BleInputProperty`:notification / indication
- `ScanResult`:`deviceId, name, rssi, manufacturerData, serviceData, ...`
- `Service` / `Characteristic`:`uuid, characteristics[], properties...`

## OpenHarmony 侧实现路径

1. **生成 C++ Pigeon 骨架**(在 fork 根目录):
```bash
flutter pub run pigeon \
--input pigeon/universal_ble.dart \
--cpp_header_out ohos/include \
--cpp_source_out ohos/src
```
2. 继承生成的 `UniversalBlePlatformChannel`,在每个方法体里调用 `@ohos.bluetooth.ble`
(`GattClientDevice`:`connect` / `discoverServices` / `readCharacteristicValue` /
`writeCharacteristicValue` / `setCharacteristicChangeNotification` / `setDeviceMtuSize` 等)。
3. 用 `UniversalBleCallbackChannel` 把扫描/连接/特征值事件推回 Dart。
4. OronBox 的 `pubspec.yaml` 把 `universal_ble` 指向这个 fork(git 依赖)。

## ohos 权限(module.json5 需声明)

- `ohos.permission.ACCESS_BLUETOOTH`
- `ohos.permission.USE_BLUETOOTH`
- `ohos.permission.LOCATION`(扫描需要)
- `ohos.permission.APPROXIMATELY_LOCATION`(API 12+)

> 注意:OpenHarmony 的 BLE 后台连接策略、扫描节流、MTU 协商与 Android 有差异,必须真机调试。
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// EntryAbility.ets — 应用入口,注册 universal_ble 的 NAPI 插件
// 参考实现:实际方法名/参数以你本地 CPF-Flutter 生成的 flutter_ohos 工程为准。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { universalBleOhosPlugin } from '../../../universal_ble_ohos/ets/UniversalBleOhosPlugin';

export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onCreate');
// 注册 BLE 平台通道(NAPI 加载 C++ Pigeon host)
universalBleOhosPlugin.register();
}

onDestroy(): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onDestroy');
}

onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'OronBox', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
}
});
}

onWindowStageDestroy(): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onWindowStageDestroy');
}

onForeground(): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onForeground');
}

onBackground(): void {
hilog.info(0x0000, 'OronBox', '%{public}s', 'Ability onBackground');
}
}
40 changes: 40 additions & 0 deletions harmonyos-port/ohos/entry/src/main/module.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
],
"requestPermissions": [
{ "name": "ohos.permission.ACCESS_BLUETOOTH", "reason": "$string:perm_ble", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.USE_BLUETOOTH", "reason": "$string:perm_ble", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.LOCATION", "reason": "$string:perm_loc", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.APPROXIMATELY_LOCATION", "reason": "$string:perm_loc", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.INTERNET", "reason": "$string:perm_net", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } },
{ "name": "ohos.permission.KEEP_BACKGROUND_RUNNING", "reason": "$string:perm_bg", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }
]
}
}
Loading