# HDevBoard **Repository Path**: skh_7_0/hdev-board ## Basic Information - **Project Name**: HDevBoard - **Description**: 1.HarmonyOS 基于KHDVK-3861开发AIoT开发套件实例应用 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 4 - **Created**: 2022-01-12 - **Last Updated**: 2025-04-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: Java, JavaScript, CSS, hml ## README @[toc](目录) 作者:胡领情 # 前言 **本篇我们将介绍KHDVK-3861开发套件的AIoT开发套件应用案例,这是一个由鸿蒙JS ArkUI框架所开发的北向应用案例。通过学习开发该案例,我们将会初步了解鸿蒙JS开发的基本流程以及开发套件相关传感器的开发示例。** ## 1. 效果演示 ![AIoT实列](https://gitee.com/skh_7_0/hdev-board/raw/master/img/AIoT%E5%AE%9E%E5%88%97.gif) ## 2. 相关概念 相关HarmonyOS JS开发官方文档:[JS ArkUI](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-overview-0000000000500376) JS FA应用的JS模块(entry/src/main/js/module)的典型开发目录结构如下: **图1** 目录结构 ![img](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20211217153625.23842127607024933609084974953650:50521216093352:2800:E3A1772493613352B563C0C5DDA7896CFEE8A644EC693FB7097E078E04B7E5AF.png?needInitFileName=true?needInitFileName=true) **图2** 多实例资源共享目录结构 ![img](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20211217153625.25723900422513949981322659056932:50521216093352:2800:BFEF28E1E2DE043A1AA38A90F2252E40F49507DE00E1B3F9D36A7ECCD564FE7B.png?needInitFileName=true?needInitFileName=true) 目录结构中文件分类如下: - .hml结尾的HML模板文件,这个文件用来描述当前页面的文件布局结构。 - .css结尾的CSS样式文件,这个文件用于描述页面样式。 - .js结尾的JS文件,这个文件用于处理页面和用户的交互。 各个文件夹的作用: - app.js文件用于全局JavaScript逻辑和应用生命周期管理。 - pages目录用于存放所有组件页面。 - common目录用于存放公共资源文件,比如:媒体资源,自定义组件和JS文件。 - resources目录用于存放资源配置文件,比如:多分辨率加载等配置文件。 - i18n目录用于配置不同语言场景资源内容,比如应用文本词条,图片路径等资源。 - share目录用于配置多个实例共享的资源内容,比如:share中的图片和JSON文件可被default1和default2实例共享。 ## 3. 代码讲解 ### 3.1 项目结构 ![AIoT项目结构](https://gitee.com/skh_7_0/hdev-board/raw/master/img/AIoT%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.jpg) 这是我们的AIoT开发套件完整项目的项目结构示意图,**entry>src>main>js**目录就是我们的主要工程目录,**template**是封装的公共库,里面主要包括JS调java相关封装,蓝牙模块封装。 ### 3.1 js\default\pages项目组件页面 ![pages_index](https://gitee.com/skh_7_0/hdev-board/raw/master/img/pages_index.jpg) index: 此界面是入口main界面,也就是我们的主页,首页。 ### 3.2 template模板JS接口 template模板目前提供的接口按能力划分有两大类,一类是蓝牙操作相关,一类是通用操作。JS接口代码位于`HDevBoard\entry\src\main\js\share\common\opinterface `目录下,提供JS调java相关能力。 具体底层实现运用了JS FA如何调用PA,官方文档:[JS FA如何调用PA](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-fa-call-pa-0000001050435961) #### 3.2.1 蓝牙相关类 **bleOperator.js** | 方法 | 参数 | 参数描述 | 方法描述 | | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | -------------------------------------------- | | createBleConnection | deviceId | mac地址 | 创建ble连接 | | closeBleConnection | deviceId | mac地址 | 关闭ble连接 | | onBleConnectionStateChange | callback | 监听回调 | 监听蓝牙连接状态 | | getBleConnectionState | deviceId, callback | mac地址, 读取回调 | 读取蓝牙连接状态 | | openBluetoothAdapter | null | null | 打开蓝牙适配器 | | closeBluetoothAdapter | null | null | 关闭蓝牙适配器 | | getBluetoothAdapterState | callback | 读取回调 | 读取蓝牙适配器状态 | | onBluetoothAdapterStateChange | callback | 监听回调 | 监听蓝牙适配器状态 | | getDeviceId | null | null | 获取当前mac地址 | | startBluetoothDevicesDiscovery | services, mac, allowDuplicatesKey, interval | 服务过滤uuid,mac地址,是否复制,扫描间隔 | 开启设备扫描 | | stopBluetoothDevicesDiscovery | null | null | 停止设备发现 | | onBluetoothDeviceFound | callback | 监听回调 | 监听蓝牙设备扫描发现状态 | | readBleCharacteristicValue | deviceId, serviceId, characteristicId, callback | mac地址, 服务id, 特征id, 回调 | 读取指定的特征值 | | writeBleCharacteristicValue | deviceId, serviceId, characteristicId, data, callback | mac地址, 服务id, 特征id, 写入值, 写入回调 | 写入特征值 | | onBleCharacteristicValueChange | callback | 监听回调 | 监听蓝牙特征值的变化 | | onBleServicesDiscovered | callback | 监听回调 | 监听蓝牙服务扫描发现状态 | | setEnableIndication | isEnableIndication | 是否切换为Indication模式 | 切换notify或Indication模式(类似udp和tcp区别) | | onEnableNotifyIndicate | callback | 监听回调 | 监听notify或Indication模式的切换 | | notifyBleCharacteristicValueChange | deviceId, serviceId, characteristicId, state | mac地址, 服务id, 特性id, 打开或者关闭(boolean) | 打开/关闭指定通知 | | readBleDescriptorValue | deviceId, serviceId, characteristicId, descriptorId, callback | mac地址, 服务id, 特征id, 描述id, 回调 | 读取指定的描述值 | | writeBleDescriptorValue | deviceId, serviceId, characteristicId, descriptorId, data, callback | mac地址, 服务id, 特征id, 描述id, 写入值, 写入回调 | 写入描述值 | #### 3.2.2 通用操作类 **commonOperator.js** | 方法 | 参数 | 参数描述 | 方法描述 | | ------------------------ | -------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------- | | checkPermission | permissions, callback | 检查的权限, 检查回调 | 检查是否有指定权限 | | requestPermission | permissions, requestCode, callback | 请求的权限, 请求码, 请求回调 | 请求指定的权限 | | checkPackageInstallation | bundleName, callback | 包名, 检查回调 | 检查是否已安装指定应用 | | getUniqueId | callback | 读取回调 | 获取uniqueId(客户端首次启动随机生成并存储在本地的一个值) | | openUrl | url, callback | 链接地址, 操作回调 | 打开指定的url链接 | | startAbility | bundleName, abilityName, callback, params, options | 包名, ability名, 回调, 传递参数, 传递配置 | 打开指定的ability页面 | ### 3.3 template model template这里不做详细描述,我们只要知道它是一个封装库,能给JS提供那些方法就行。具体方法请看3.2相关方法描述。 这里简单介绍下蓝牙模块相关封装: 1.定义相关接口BleOperator,BleGattProcessor,BleCentralProcessor,提供相关方法说明能干什么事情,但是不实施。 2.相关实施者BleOperatorImpl,在这里具体实现接口定义的相关功能。 3.BleOperatorImpl要实现相关功能,需要相关对象的帮助(BleCentralOperator,BleGattOperator)。所以具体做事的人就是BleCentralOperator,BleGattOperator,我们只要看他们怎么干活的。JS相关调用相关接口最后都是它们来具体实现。 4.蓝牙通信流程图(左边原鸿蒙蓝牙流程;右边为template封装后流程): ![蓝牙通信流程图](https://gitee.com/skh_7_0/hdev-board/raw/master/img/%E8%93%9D%E7%89%99%E9%80%9A%E4%BF%A1%E6%B5%81%E7%A8%8B%E5%9B%BE.png) ## 4.开发调试 pages项目组件页面,提供UI相关的处理,具体功能实现通过FA调PA template里面的java代码实现。 #### 4.1 index主页 1、通过checkAndRequestPermissions()方法检测相关权限。 2、通过bleOperator.startBluetoothDevicesDiscovery()开始扫描连接设备,并设置相关监听:蓝牙连接状态(onBleConnectionStateChange);手机蓝牙开关(onBluetoothAdapterStateChange);数据上报监听(onBleCharacteristicValueChange)。 3、onBleCharacteristicValueChange回调中dataTreating()解析上报数据,根据解析的tagId来显示具体那个传感器界面。 ```js scanDevice() { this.deviceState = 1 this.showPopup = true this.connectTimeout = setTimeout(() => { Log.info('scanDevice failed due to timeout.'); this.showPopup = false if (this.deviceState == 1) { this.deviceState = 3 bleOperator.stopBluetoothDevicesDiscovery(); bleOperator.closeBleConnection(this.macAddr); } }, config.pairTimeout * 1000); bleOperator.startBluetoothDevicesDiscovery(config.bleScanFilterServices, this.macAddr, false, 30); }, initListener() { bleOperator.onBleConnectionStateChange(this.onBleConnectionStateChange); bleOperator.onBluetoothAdapterStateChange(this.onBluetoothAdapterStateChange); bleOperator.onBleCharacteristicValueChange(this.onBleCharacteristicValueChange); }, onBleConnectionStateChange(callbackJson) { //dealing with connection state change Log.info("onBleConnectionStateChange" + 'Connection state changed: ' + JSON.stringify(callbackJson)); if (callbackJson.data.isConnected) { this.macAddr = callbackJson.data.deviceId clearTimeout(this.connectTimeout); this.onDeviceConnected() } else { if (this.deviceState == 2) { showToast("设备已断开连接,请重新连接!") } this.deviceState = 3 this.tagId = '' } }, onBluetoothAdapterStateChange(callbackJson) { Log.info('Adapter state changed:' + JSON.stringify(callbackJson)); if (callbackJson.data.isAvailable == false) { this.deviceState = 3; this.tagId = '' clearTimeout(this.connectTimeout); } else { this.onAdapterAvailable() } }, onBleCharacteristicValueChange(callbackJson) { Log.info("onBleCharacteristicValueChange" + ' value changed: ' + JSON.stringify(callbackJson)); switch (callbackJson.data.characteristicId) { case config.CharacterNotify_UUID: this.dataTreating(callbackJson.data.changeData.toUpperCase()); break; default: break; } }, dataTreating(changeData) { if (!bleDataUtils.verifyProtocol(changeData)) { showToast("设备上报数据校验失败!") return; } let tagDataArray = bleDataUtils.parseCharacteristic(changeData) Log.info("dataTreating" + ' tagDataArray: ' + JSON.stringify(tagDataArray)); this.initViewByTag(tagDataArray) tagDataArray.forEach(element => { let tag = element[0] let value = parseInt(element[2], 16) switch (this.tagId) { case '00070009': //双传感器组合 if (tag == '0007') { this.reportValue1 = value } else if (tag == '0008') { this.threshold1 = value } else if (tag == '0009') { this.reportValue2 = value } else if (tag == '000A') { this.reportValue3 = value } else if (tag == '000B') { this.threshold2 = value } else if (tag == '000C') { this.threshold3 = value } break; case '0001': if (tag == '0001') { this.reportValue1 = value } break; case '0003': if (tag == '0003') { this.reportValue1 = value } else if (tag == '0004') { this.reportValue2 = value } else if (tag == '0005') { this.reportValue3 = value } break; case '0007': if (tag == '0007') { this.reportValue1 = value } else if (tag == '0008') { this.threshold1 = value } break; case '0009': if (tag == '0009') { this.reportValue1 = value } else if (tag == '000A') { this.reportValue2 = value } else if (tag == '000B') { this.threshold1 = value } else if (tag == '000C') { this.threshold2 = value } break; case '000D': if (tag == '000D') { this.reportValue1 = value / 10.0 } else if (tag == '000E') { this.threshold1 = value / 10.0 } break; case '000F': if (tag == '000F') { this.reportValue1 = value } else if (tag == '0010') { this.reportValue2 = value } else if (tag == '0011') { this.reportValue3 = value } break; case '0012': if (tag == '0012') { this.reportValue1 = value } else if (tag == '0019') { this.reportValue2 = value } break; case '0013': if (tag == '0013') { this.reportValue1 = value } else if (tag == '001A') { this.reportValue2 = value } else if (tag == '001B') { this.reportValue3 = value } else if (tag == '0014') { this.threshold1 = value } this.colorHex = "#" + parseInt(this.reportValue1).toString(16).padStart(2, "0") + parseInt(this.reportValue2).toString(16).padStart(2, "0") + parseInt(this.reportValue3).toString(16).padStart(2, "0") break; case '0015': if (tag == '0015') { this.reportValue1 = value } break; case '0016': if (tag == '0016') { this.reportValue1 = value / 10000.0 } else if (tag == '0017') { this.reportValue2 = value / 10000.0 } else if (tag == '0018') { this.reportValue3 = value } break; case '001A': if (tag == '001A') { this.reportValue1 = value } else if (tag == '001B') { this.reportValue2 = value } break; case '001C': if (tag == '001C') { this.reportValue1 = value this.threshold1 = value } break; case '001D': if (tag == '001D') { this.reportValue1 = value } else if (tag == '001E') { this.reportValue2 = value } break; default: break; } }); }, initViewByTag(tagDataArray) { let tagIds = new Array() for (var i = 0; i < tagDataArray.length; i++) { let element = tagDataArray[i] tagIds[i] = element[0] } if (tagIds.includes('0007') && tagIds.includes('0009')) { this.tagId = "00070009" this.deviceName = "智慧农业感应板" } else if (tagIds.includes('0001')) { this.tagId = "0001" this.deviceName = "火焰感应板" } else if (tagIds.includes('0003')) { this.tagId = "0003" this.deviceName = "可燃气体感应板" } else if (tagIds.includes('0007')) { this.tagId = "0007" this.deviceName = "土壤湿度传感器" } else if (tagIds.includes('0009')) { this.tagId = "0009" this.deviceName = "环境温湿度" } else if (tagIds.includes('000D')) { this.tagId = "000D" this.deviceName = "酒精乙醇感应板" } else if (tagIds.includes('000F')) { this.tagId = "000F" this.deviceName = "颜色传感器" } else if (tagIds.includes('0012')) { this.tagId = "0012" this.deviceName = "心率血氧感应板" } else if (tagIds.includes('0013')) { this.tagId = "0013" this.deviceName = "空气质量监测板" } else if (tagIds.includes('0015')) { this.tagId = "0015" this.deviceName = "红外测温感应板" } else if (tagIds.includes('0016')) { this.tagId = "0016" this.deviceName = "高度与北斗定位感应板" } else if (tagIds.includes('001C')) { this.tagId = "001C" this.deviceName = "照明功能板" } else if (tagIds.includes('001D')) { this.tagId = "001D" this.deviceName = "人体感应功能板" } Log.info("dataTreating" + ' tagIds: ' + JSON.stringify(tagIds) + ' tagId: ' + this.tagId); }, ``` 4、index界面数据,index.hml页面根据此数据联动。 ```js data: { initial_value: 0, macAddr: '11:22:88:44:55:66', // macAddr: '11:22:88:44:55:67', deviceName: "Name", deviceState: 0, //0 未连接 1 连接中 2已连接 3连接断开 deviceStateInfo: { startConnect: "开始连接", // 0 connecting: "正在连接...", //1 disconnect: "断开连接", //2 reconnect: "重新连接", //3 }, colorHex: '', showPopup: false, tagId: '', reportValue1: '0',//上报值1 reportValue2: '0',//上报值2 reportValue3: '0',//上报值3 threshold1: '0',//上报阀值1 threshold2: '0',//上报阀值2 threshold3: '0',//上报阀值3 }, ``` #### 4.2 issued阈值下发 1、通过bleDataUtils.getTagsCommandHexStr()方法拼接下发指令。 2、通过bleOperator.writeBleCharacteristicValue()方法下发指令。 ```js //下发阀值数据 writeBleData(data) { Log.info("writeBleData value = " + data); bleOperator.writeBleCharacteristicValue(this.macAddr, config.Service_UUID, config.CharacterWrite_UUID, data, callbackJson => { Log.info("writeBleCharacteristicValue : " + JSON.stringify(callbackJson.data.writeData)); showToast("阀值设置成功!") }) }, change1(e) { this.threshold1 = e.value }, enterkeyClick1(e) { showToast("value: " + this.threshold1) if (this.tagId == "000D") { let data = bleDataUtils.getTagsCommandHexStr(this.writeTagId1, config.MType[0], parseInt(this.threshold1) * 10) this.writeBleData(data) } else { let data = bleDataUtils.getTagsCommandHexStr(this.writeTagId1, config.MType[0], parseInt(this.threshold1)) this.writeBleData(data) } }, ``` #### 4.3 detail场景化 ![1641887973](https://gitee.com/skh_7_0/hdev-board/raw/master/img/1641887973.jpg) ## 5. 示例应用源码获取 案例下载地址:https://gitee.com/skh_7_0/hdev-board.git