第一部分:核心知识点
在动手之前,我们必须明确我们将要使用的技术"武器"及其在OpenHarmony 6.0中的具体实现。
技术领域 | 核心知识点 | OpenHarmony 6.0 关键API/模块 | 实战应用 |
---|---|---|---|
分布式组网 | 设备发现、认证与状态管理,构建"超级终端"的基础。 | @ohos.distributedDevice.DeviceManager |
地面站发现空中的无人机,并建立可信连接。 |
分布式数据 | 跨设备数据对象的实时同步,实现状态共享。 | @ohos.data.distributedDataObject |
领航无人机发布飞行任务,跟随无人机实时接收并执行。 |
端侧AI推理 | 模型加载、输入预处理、推理执行、输出后处理。 | @kit.MindSporeLiteKit |
无人机机载摄像头实时捕捉画面,进行障碍物(如:人、车)AI识别。 |
数据标准化 | 将设备能力抽象为统一的数据服务,实现跨设备访问。 | @ohos.app.ability.DataAbility , @ohos.data.dataAbility.DataAbilityHelper |
无人机将飞行日志、电池状态等数据发布为标准服务,地面站一键订阅。 |
UI与状态 | 声明式UI范式,状态驱动UI更新。 | @ohos.arkui.ArkUI , @State , @StorageProp |
构建地面站和无人机机载的交互界面,实时显示AI结果、设备状态和任务信息。 |
第二部分:项目准备与配置
在开始编码前,我们需要正确配置项目。
-
创建项目 :在DevEco Studio中创建一个新的OpenHarmony项目,选择
Empty Ability
模板,语言选择ArkTS
,兼容API版本设置为20
。 -
配置权限 :打开
entry/src/main/module.json5
文件,添加必要的权限。分布式功能和数据访问需要明确的权限声明。json{ "module": { // ... 其他配置 "requestPermissions": [ { "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE", "reason": "$string:permission_reason_device_state", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } }, { "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO", "reason": "$string:permission_reason_device_info", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } }, { "name": "ohos.permission.OPERATE_DISTRIBUTED_DEVICE", "reason": "$string:permission_reason_operate_device", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } }, { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "reason": "$string:permission_reason_datasync", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } } ] } }
同时,请在
resources/base/element/string.json
中添加对应的权限原因说明字符串。 -
添加依赖 :在
entry/oh-package.json5
中添加MindSpore Lite的依赖。json{ "dependencies": { "@kit.MindSporeLiteKit": "^1.0.0" } }
添加后,点击DevEco Studio右上角的
Sync Now
。
第三部分:实战开发 - 模块化实现
我们将功能拆分为三个核心模块进行开发。
模块1:端侧AI感知服务
这个服务负责加载AI模型并对图像数据进行推理。
步骤1:准备模型
- 下载一个适用于移动端的轻量级分类模型,例如MobileNetV2,并将其转换为
.ms
格式(MindSpore Lite模型格式)。 - 在
entry/src/main/resources/
目录下创建一个model
文件夹,并将您的mobilenetv2.ms
模型文件放入其中。
步骤2:创建AI服务类
在entry/src/main/ets/
下创建service/AIDetectionService.ets
文件。
typescript
// entry/src/main/ets/service/AIDetectionService.ets
import mindSporeLite from '@kit.MindSporeLiteKit';
import hilog from '@ohos.hilog';
import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
const TAG = 'AIDetectionService';
export class AIDetectionService {
private model: mindSporeLite.Model | null = null;
private isModelLoaded: boolean = false;
// 模型文件路径,在resources/rawfile目录下
private readonly MODEL_PATH = 'common/model/mobilenetv2.ms';
// 初始化模型
async initModel(): Promise<void> {
if (this.isModelLoaded) {
hilog.info(0x0000, TAG, 'Model already loaded.');
return;
}
try {
// 1. 设置模型推理运行时上下文
const context: mindSporeLite.Context = {
target: ['cpu'], // 指定在CPU上运行
};
// 2. 从资源中加载模型文件
const modelBuffer = await getContext(this).resourceManager.getRawFileContent(this.MODEL_PATH);
// 3. 创建并构建模型
this.model = new mindSporeLite.Model();
await this.model.build(modelBuffer, mindSporeLite.ModelType.MINDIR, context);
this.isModelLoaded = true;
hilog.info(0x0000, TAG, 'Model loaded successfully.');
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Failed to load model. Code: ${err.code}, message: ${err.message}`);
}
}
// 执行推理
async runInference(imagePixelMap: image.PixelMap): Promise<string> {
if (!this.isModelLoaded || !this.model) {
hilog.error(0x0000, TAG, 'Model is not loaded, cannot run inference.');
return 'Error: Model not loaded';
}
try {
// 1. 获取模型输入信息
const inputs = this.model.getInputs();
if (inputs.length === 0) {
return 'Error: Model has no inputs';
}
const inputTensor = inputs[0];
// 2. 预处理图像:调整大小、归一化
const imageInfo = { size: { width: inputTensor.shape[2], height: inputTensor.shape[3] } };
const imageData = await imagePixelMap.getImageData(imageInfo);
const inputData = this.preprocessImageData(imageData.pixels, inputTensor.shape);
// 3. 创建输入Tensor并填充数据
const msTensor = new mindSporeLite.MSTensor();
msTensor.dataType = mindSporeLite.DataType.NUMBER_TYPE_FLOAT32;
msTensor.shape = inputTensor.shape;
msTensor.name = inputTensor.name;
msTensor.setData(new Float32Array(inputData));
// 4. 执行推理
const outputs = this.model.predict([msTensor]);
hilog.info(0x0000, TAG, `Inference finished, output count: ${outputs.length}`);
// 5. 后处理输出:找到概率最高的类别
const result = this.postprocessOutput(outputs[0]);
return result;
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Inference failed. Code: ${err.code}, message: ${err.message}`);
return `Inference Error: ${err.message}`;
}
}
// 图像预处理 (简化版,实际需根据模型要求调整)
private preprocessImageData(pixels: ArrayBuffer, shape: number[]): number[] {
const [batch, channels, height, width] = shape;
const float32Data = new Uint8Array(pixels);
const normalizedData: number[] = [];
for (let i = 0; i < float32Data.length; i += 4) { // RGBA
// 假设模型是RGB输入,且需要归一化到[0, 1]
normalizedData.push(float32Data[i] / 255.0); // R
normalizedData.push(float32Data[i + 1] / 255.0); // G
normalizedData.push(float32Data[i + 2] / 255.0); // B
}
return normalizedData;
}
// 输出后处理 (简化版,需要配合标签文件)
private postprocessOutput(outputTensor: mindSporeLite.MSTensor): string {
const outputData = outputTensor.getData() as Float32Array;
let maxIndex = 0;
let maxValue = outputData[0];
for (let i = 1; i < outputData.length; i++) {
if (outputData[i] > maxValue) {
maxValue = outputData[i];
maxIndex = i;
}
}
// 这里应该有一个标签列表来根据maxIndex查找类别名称
// 为了演示,我们直接返回索引和置信度
return `Detected: Class ${maxIndex} with ${(maxValue * 100).toFixed(2)}% confidence`;
}
}
模块2:飞行数据标准化与共享
我们使用DataAbility
来提供一个标准化的数据接口。
步骤1:创建DataAbility
在DevEco Studio中右键entry/src/main/ets
-> New
-> Ability
-> Data Ability
,命名为FlightDataAbility
。
步骤2:实现DataAbility逻辑
修改生成的FlightDataAbility.ets
文件。
typescript
// entry/src/main/ets/ability/FlightDataAbility.ets
import { DataAbilityHelper, ValuesBucket } from '@ohos.data.dataAbility';
import { Want } from '@kit.AbilityKit';
import hilog from '@ohos.hilog';
import { rdb } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG = 'FlightDataAbility';
const TABLE_NAME = 'flight_log';
const STORE_CONFIG: rdb.StoreConfig = { name: 'flight_data.db' };
let rdbStore: rdb.RdbStore | null = null;
const COLUMNS = ['id', 'deviceId', 'timestamp', 'battery', 'gps', 'log'];
export default class FlightDataAbility {
async onCreate(want: Want, callback: Function) {
hilog.info(0x0000, TAG, 'DataAbility onCreate');
// 初始化RdbStore
rdb.getRdbStore(getContext(this), STORE_CONFIG, 1, (err, store) => {
if (err) {
hilog.error(0x0000, TAG, `getRdbStore failed, code: ${err.code}, message: ${err.message}`);
return;
}
rdbStore = store;
// 创建表
store.executeSql(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
timestamp INTEGER NOT NULL,
battery REAL,
gps TEXT,
log TEXT
)`);
callback();
});
}
async insert(uri: string, valueBucket: ValuesBucket): Promise<number> {
hilog.info(0x0000, TAG, `DataAbility insert: ${JSON.stringify(valueBucket)}`);
if (!rdbStore) {
return -1;
}
return await rdbStore.insert(TABLE_NAME, valueBucket);
}
async query(uri: string, columns: Array<string>, predicates: rdb.DataAbilityPredicates): Promise<rdb.ResultSet> {
hilog.info(0x0000, TAG, `DataAbility query`);
if (!rdbStore) {
throw new Error("RdbStore is not initialized.");
}
return await rdbStore.query(TABLE_NAME, predicates, columns);
}
// ... 其他方法如 update, delete 可以按需实现
}
步骤3:在module.json5中注册DataAbility
确保module.json5
中包含FlightDataAbility
的声明,系统已自动生成,请检查uri
是否正确。
json
"abilities": [
// ... EntryAbility
{
"name": "FlightDataAbility",
"srcEntry": "./ets/ability/FlightDataAbility.ets",
"description": "$string:FlightDataAbility_desc",
"icon": "$media:icon",
"label": "$string:FlightDataAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"type": "data",
"uri": "dataability://com.example.lowaltitudeapp.FlightDataAbility"
}
]
步骤4:创建数据生产者
在entry/src/main/ets/service/FlightDataProducer.ets
中创建一个类,用于向DataAbility写入数据。
typescript
// entry/src/main/ets/service/FlightDataProducer.ets
import { DataAbilityHelper, ValuesBucket } from '@ohos.data.dataAbility';
import hilog from '@ohos.hilog';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG = 'FlightDataProducer';
const DATA_URI = 'dataability://com.example.lowaltitudeapp.FlightDataAbility';
export class FlightDataProducer {
private dataAbilityHelper: DataAbilityHelper | null = null;
constructor() {
this.dataAbilityHelper = DataAbilityHelper.createDataAbilityHelper(getContext(this));
}
async publishFlightLog(log: string, battery: number, gps: string) {
if (!this.dataAbilityHelper) {
hilog.error(0x0000, TAG, 'DataAbilityHelper is null.');
return;
}
const valueBucket: ValuesBucket = {
deviceId: 'UAV-001', // 实际应为设备唯一ID
timestamp: Date.now(),
battery: battery,
gps: gps,
log: log
};
try {
const uri = await this.dataAbilityHelper.insert(DATA_URI, valueBucket);
hilog.info(0x0000, TAG, `Successfully published log to URI: ${uri}`);
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Failed to publish log. Code: ${err.code}, message: ${err.message}`);
}
}
}
模块3:多机组网智慧控制
这是本教程的核心,我们将使用分布式数据对象实现领航者-跟随者模式。
步骤1:创建组网控制服务
在entry/src/main/ets/service/DroneSwarmControl.ets
中创建单例服务。
typescript
// entry/src/main/ets/service/DroneSwarmControl.ets
import deviceManager from '@ohos.distributedDevice';
import distributedObject from '@ohos.data.distributedDataObject';
import hilog from '@ohos.hilog';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG = 'DroneSwarmControl';
// 定义同步的数据结构
class MissionData {
id: number = 0;
description: string = 'Standby';
status: 'pending' | 'in-progress' | 'completed' = 'pending';
}
export class DroneSwarmControl {
private static instance: DroneSwarmControl;
private dmInstance: deviceManager.DeviceManager | null = null;
private deviceList: deviceManager.DeviceInfo[] = [];
private localObject: distributedObject.DataObject<MissionData> | null = null;
private sessionId: string = 'low-altitude-mission-session';
private constructor() {}
public static getInstance(): DroneSwarmControl {
if (!DroneSwarmControl.instance) {
DroneSwarmControl.instance = new DroneSwarmControl();
}
return DroneSwarmControl.instance;
}
// 初始化设备管理器
async initDeviceManager(): Promise<void> {
try {
this.dmInstance = await deviceManager.createDeviceManager('com.example.lowaltitudeapp');
this.dmInstance.on('deviceStateChange', (data) => {
hilog.info(0x0000, TAG, `Device state changed: ${JSON.stringify(data)}`);
this.refreshDeviceList();
});
this.dmInstance.on('deviceFound', (data) => {
hilog.info(0x0000, TAG, `Device found: ${JSON.stringify(data)}`);
});
hilog.info(0x0000, TAG, 'DeviceManager initialized.');
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Init DeviceManager failed: ${err.message}`);
}
}
// 开始发现设备
startDeviceDiscovery() {
if (!this.dmInstance) {
hilog.error(0x0000, TAG, 'DeviceManager not initialized.');
return;
}
let extraInfo = {
'targetPkgName': 'com.example.lowaltitudeapp',
'appName': 'LowAltitudeApp'
};
let subscribeId = Math.floor(Math.random() * 10000);
this.dmInstance.startDeviceDiscovery(subscribeId, extraInfo);
hilog.info(0x0000, TAG, 'Start discovering devices...');
}
// 刷新设备列表
private refreshDeviceList() {
if (!this.dmInstance) return;
this.deviceList = this.dmInstance.getTrustedDeviceListSync();
}
getDeviceList(): deviceManager.DeviceInfo[] {
return this.deviceList;
}
// 认证设备
async authenticateDevice(deviceId: string) {
if (!this.dmInstance) return;
let authParam = {
'authType': 1, // 1: 点对点认证
'extraInfo': {}
};
this.dmInstance.authenticateDevice(deviceId, authParam, (err, data) => {
if (err) {
hilog.error(0x0000, TAG, `Auth failed: ${err.message}`);
} else {
hilog.info(0x0000, TAG, `Auth success: ${JSON.stringify(data)}`);
}
});
}
// --- 分布式数据对象操作 ---
// 作为领航者,创建并设置数据
createAsLeader() {
this.localObject = distributedObject.create(getContext(this), new MissionData());
hilog.info(0x0000, TAG, 'Created local distributed object as Leader.');
}
// 作为跟随者,加入会话
async joinAsFollower() {
this.localObject = distributedObject.create(getContext(this), new MissionData());
try {
await this.localObject.setSessionId(this.sessionId);
hilog.info(0x0000, TAG, 'Joined session as Follower.');
// 监听数据变化
this.localObject.on('change', (changeData) => {
hilog.info(0x0000, TAG, `Mission data changed: ${JSON.stringify(changeData)}`);
AppStorage.SetOrCreate<MissionData>('mission', this.localObject!);
});
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Join session failed: ${err.message}`);
}
}
// 领航者发布任务
publishMission(mission: MissionData) {
if (!this.localObject) {
hilog.error(0x0000, TAG, 'Local object not created.');
return;
}
this.localObject.id = mission.id;
this.localObject.description = mission.description;
this.localObject.status = mission.status;
hilog.info(0x0000, TAG, `Published mission: ${JSON.stringify(mission)}`);
}
// 领航者将数据对象同步到网络
async syncToNetwork(deviceId: string) {
if (!this.localObject) return;
try {
const sessionId = await this.localObject.setSessionId(this.sessionId);
await this.localObject.syncToNetwork([deviceId]);
hilog.info(0x0000, TAG, `Synced mission to device: ${deviceId}`);
} catch (error) {
const err = error as BusinessError;
hilog.error(0x0000, TAG, `Sync to network failed: ${err.message}`);
}
}
}
第四部分:UI集成与场景模拟
现在,我们将所有后端逻辑整合到用户界面中。
步骤1:创建地面站视图
在entry/src/main/ets/pages/GroundStationView.ets
中创建领航者界面。
typescript
// entry/src/main/ets/pages/GroundStationView.ets
import { deviceManager } from '@ohos.distributedDevice';
import { DroneSwarmControl } from '../service/DroneSwarmControl';
import { FlightDataProducer } from '../service/FlightDataProducer';
import { DataAbilityHelper } from '@ohos.data.dataAbility';
import { rdb } from '@kit.ArkData';
@Entry
@Component
struct GroundStationView {
@State deviceList: deviceManager.DeviceInfo[] = [];
@State missionDescription: string = 'Patrol Area A';
@State logs: string[] = [];
private swarmControl: DroneSwarmControl = DroneSwarmControl.getInstance();
private dataProducer: FlightDataProducer = new FlightDataProducer();
private dataHelper: DataAbilityHelper = DataAbilityHelper.createDataAbilityHelper(getContext(this));
aboutToAppear() {
this.swarmControl.initDeviceManager();
this.swarmControl.createAsLeader(); // 地面站作为领航者
}
build() {
Column() {
Text('Ground Station - Leader')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(10)
// --- 设备发现与控制 ---
Text('1. Discover & Control Drones')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
Button('Discover Drones')
.onClick(() => {
this.swarmControl.startDeviceDiscovery();
setTimeout(() => {
this.deviceList = this.swarmControl.getDeviceList();
}, 3000); // 3秒后刷新列表
})
.width('80%')
List({ space: 10 }) {
ForEach(this.deviceList, (device: deviceManager.DeviceInfo) => {
ListItem() {
Row() {
Column() {
Text(device.deviceName).fontSize(16)
Text(device.networkId).fontSize(12).fontColor(Color.Grey)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Button('Sync Mission')
.onClick(() => {
const mission = {
id: Date.now(),
description: this.missionDescription,
status: 'pending' as const
};
this.swarmControl.publishMission(mission);
this.swarmControl.syncToNetwork(device.networkId);
})
}
.width('100%')
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#CCCCCC' })
}
})
}
.width('90%')
.layoutWeight(1)
// --- 任务发布 ---
Text('2. Mission Control')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
TextInput({ placeholder: 'Enter mission description' })
.onChange((value: string) => {
this.missionDescription = value;
})
.width('90%')
.margin({ bottom: 10 })
// --- 数据订阅 ---
Text('3. Flight Data Monitor')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
Button('Query Latest Logs')
.onClick(async () => {
try {
const predicates = new rdb.DataAbilityPredicates();
predicates.orderByDesc('timestamp').limitAs(5);
const result = await this.dataHelper.query(
'dataability://com.example.lowaltitudeapp.FlightDataAbility',
['log', 'timestamp'],
predicates
);
this.logs = [];
while (result.goToNextRow()) {
const log = result.getString(0);
const timestamp = result.getLong(1);
this.logs.push(`[${new Date(timestamp).toLocaleTimeString()}] ${log}`);
}
result.close();
} catch (error) {
console.error('Query logs failed', error);
}
})
.width('80%')
List({ space: 5 }) {
ForEach(this.logs, (log: string) => {
ListItem() {
Text(log).fontSize(12).width('100%')
}
})
}
.width('90%')
.height(150)
.backgroundColor('#F0F0F0')
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#EFEFEF')
}
}
步骤2:创建无人机机载视图
在entry/src/main/ets/pages/UAVOnboardView.ets
中创建跟随者界面。
typescript
// entry/src/main/ets/pages/UAVOnboardView.ets
import { DroneSwarmControl } from '../service/DroneSwarmControl';
import { AIDetectionService } from '../service/AIDetectionService';
import { FlightDataProducer } from '../service/FlightDataProducer';
import { MissionData } from '../service/DroneSwarmControl';
import { image } from '@kit.ImageKit';
@Entry
@Component
struct UAVOnboardView {
@State aiResult: string = 'AI Standby';
@State mission: MissionData = new MissionData();
private swarmControl: DroneSwarmControl = DroneSwarmControl.getInstance();
private aiService: AIDetectionService = new AIDetectionService();
private dataProducer: FlightDataProducer = new FlightDataProducer();
// 模拟一个图像源,实际应来自相机
private mockImagePixelMap: image.PixelMap | null = null;
aboutToAppear() {
this.swarmControl.initDeviceManager();
this.swarmControl.joinAsFollower(); // 无人机作为跟随者
this.aiService.initModel();
this.mockCameraFeed();
}
// 模拟相机数据流和日志发布
private mockCameraFeed() {
setInterval(() => {
// 模拟发布飞行日志
this.dataProducer.publishFlightLog(
`AI result: ${this.aiResult}`,
85.5 + Math.random() * 10,
'120.123, 30.456'
);
}, 5000);
}
build() {
Column() {
Text('UAV Onboard - Follower')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(10)
// --- AI感知 ---
Text('1. AI Obstacle Detection')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
Button('Run AI Inference (Mock)')
.onClick(async () => {
// 实际应用中,这里应获取相机最新的PixelMap
// this.mockImagePixelMap = await getLatestCameraPixelMap();
// if (this.mockImagePixelMap) {
// this.aiResult = await this.aiService.runInference(this.mockImagePixelMap);
// }
// 模拟推理结果
this.aiResult = `Detected: Person with 95.50% confidence`;
})
.width('80%')
Text(this.aiResult)
.fontSize(14)
.margin({ top: 10 })
.fontColor(Color.Red)
.width('90%')
// --- 任务接收 ---
Text('2. Mission from Leader')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
Column() {
Text(`ID: ${this.mission.id}`)
Text(`Description: ${this.mission.description}`)
Text(`Status: ${this.mission.status}`)
}
.width('90%')
.padding(15)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#CCCCCC' })
// 使用@StorageProp链接到分布式数据对象的变化
.onAppear(() => {
AppStorage.SetOrCreate<MissionData>('mission', this.mission);
})
.onDisAppear(() => {
AppStorage.Delete('mission');
})
// --- 数据发布状态 ---
Text('3. Data Publishing Status')
.fontSize(18)
.margin({ top: 20, bottom: 10 })
.alignSelf(ItemAlign.Start)
Text('Publishing flight logs every 5 seconds...')
.fontSize(14)
.fontColor(Color.Green)
}
.width('100%')
.height('100%')
.padding(10)
.backgroundColor('#EFEFEF')
}
// 使用@StorageProp监听AppStorage中的'mission'变化
@StorageProp('mission') missionFromStorage: MissionData = new MissionData();
// 当@StorageProp的值变化时,更新本地状态
missionStatusChange() {
this.mission = this.missionFromStorage;
}
// 在build中调用此方法以触发状态更新
build() {
this.missionStatusChange();
// ... 其余UI代码
}
}
修正:为了更清晰地响应数据变化,我们直接在DroneSwarmControl
的on('change')
回调中更新AppStorage
,然后在UAVOnboardView
中使用@StorageProp
来获取它。这是更推荐的做法。上面的UAVOnboardView
代码已按此逻辑修改。
步骤3:整合所有视图
修改主入口entry/src/main/ets/pages/Index.ets
,使用Tabs
来切换两个角色视图。
typescript
// entry/src/main/ets/pages/Index.ets
import { GroundStationView } from './GroundStationView';
import { UAVOnboardView } from './UAVOnboardView';
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
@Builder TabBuilder(title: string, targetIndex: number) {
Column() {
Text(title)
.fontSize(16)
.fontWeight(this.currentIndex === targetIndex ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentIndex === targetIndex ? Color.Blue : Color.Grey)
}
}
build() {
Tabs({ barPosition: BarPosition.End, index: this.currentIndex }) {
TabContent() {
GroundStationView()
}
.tabBar(this.TabBuilder('Ground Station', 0))
TabContent() {
UAVOnboardView()
}
.tabBar(this.TabBuilder('UAV Onboard', 1))
}
.onChange((index: number) => {
this.currentIndex = index;
})
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
第五部分:运行与验证
- 编译与安装:将应用编译并安装到至少两台OpenHarmony 6.0设备上(可以是两台开发板或两台手机)。
- 授权:在两台设备上首次运行应用时,系统会弹出权限请求,请全部允许。
- 模拟场景 :
- 在设备A 上,切换到
Ground Station
标签页。点击"Discover Drones"按钮。 - 在设备B 上,切换到
UAV Onboard
标签页。它将自动尝试加入分布式网络。 - 回到设备A,稍等片刻,设备列表中应该会出现设备B。
- 在设备A上,修改任务描述(例如改为"Inspect Powerline"),然后点击设备B旁边的"Sync Mission"按钮。
- 观察设备B的"Mission from Leader"区域,您应该能看到任务信息实时更新。
- 在设备B上,点击"Run AI Inference (Mock)"按钮,模拟AI检测。
- 在设备A上,点击"Query Latest Logs"按钮,稍等片刻,您应该能看到从设备B发布的包含AI结果的日志。
- 在设备A 上,切换到