写点什么

鸿蒙版《智慧农业 APP》通过华为云 IoT 平台实现软件硬件互联

作者:九九花开
  • 2025-05-26
    广东
  • 本文字数:6828 字

    阅读完需:约 22 分钟

一、原理图


本篇不涉及硬件相关的功能开发,硬件设备使用 MQTT 客户端模拟,如果有硬件相关经验的可以直接使用真实硬件代替 MQTT 客户端。

1、华为云物联网服务器

华为云物联网平台是硬件设备端跟移动 APP 端数据的中转平台,硬件设备端定时上报采集都的数据到云平台,移动 APP 端可以从云平台定时的拉取硬件设备端上报的数据并且显示给管理员,移动 APP 端可以发送指令到云平台,硬件设备端通过”订阅“云平台指定的服务,从而获得移动 APP 端发送的指令,从而对设备端进行相应的操作。

2、硬件设备端

(1)订阅

硬件设备端可以”订阅“云平台设备的服务,从而获取到移动 APP 端通过云平台下发给硬件设备端的指令。

(2)发布

硬件设备端可以定时的通过自身的 4G 或者 WIFI 模块向云平台上报自己采集的数据。

3、移动 APP 端

(1)数据展示

移动 APP 端通过丰富直观的页面效果将传感器采集到的数据展示给管理员。

(2)拉取影子数据

移动 APP 端可以通过平台提供的接口,通过 HTTP 请求获取硬件设备端上报的数据。

(3)发送指令

移动 APP 端可以通过平台提供的接口,通过 HTTP 请求向硬件设备端发送操作指令。

二、华为 IOT 云平台

1、注册登录

需要先注册并且登录完成实名认证(https://www.huaweicloud.com/

2、购买免费版的 I0TDA


3、创建产品

相当于 java 的类概念,在物联网平台中,某一类具有相同能力或特征的设备的合集被称为一款产品,比如智慧农业中所有的温度传感器,或者湿度传感器等。



4、给产品创建模型

所谓的模型就是该产品需要上传那些数据,这些数据在云平台对应的存储变量,比如温度计需要上传温度。


4.1、创建服务 ID

4.2、添加属性

5、创建设备

相当于 java 的对象概念。

5.1、设备注册

5.2、保存设备 id 以及密码(重要)

三、模拟硬件设备客户端

我们目前不考虑硬件设备端的开发过程,我们使用模拟客户端实现硬件设备的模拟。(所有的硬件设备都会遵循相同的数据交互规范,即 MQTT 协议)

1、模拟客户端下载

一位大佬自研开发的客户端,大家多多支持。


链接:https://pan.baidu.com/s/1xpROACEco3CJj3Vlo3sObQ 提取码:qgm2


2、获得云平台 IP 以及登录端口

2.1、云平台 IP

复制第三步的域名,打开 cmd,输入命令:ping 域名


2.2、端口号

MQTT 默认端口号 1883

3、换取硬件设备登录云平台的三元组

所谓的三原则就是客户端 id、用户名、以及密码,其中需要两个重要的数据就是 5.2 小节保存的文件里面的 device_id 跟 secret,通过以下链接换取:


https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/


4、把三元组信息填入模拟客户端

设备上线成功


四、硬件设备端连接云平台

1、订阅

1.1、订阅地址

设备端订阅云平台数据,当移动 APP 端发送指令,设备端可以获得该指令数据。


固定的订阅地址格式:$oc/devices/{device_Id}/sys/commands/#


在该案例中 device_Id 替换为 668758a55830dc113ecaf2f0_temperature_sen##_###,完整的订阅路径为:


$oc/devices/668758a55830dc113ecaf2f0_temperature_sen##_###/sys/commands/#


2、上报数据

2.1、上报地址

设备端可以上报自己采集到的数据,上报的数据格式是 JSON 格式。


固定的上报地址格式:$oc/devices/{device_Id}/sys/properties/report


在该案例中 device_Id 替换为 668758a55830dc113ecaf2f0_temperature_sen###_###,完整的上报路径为:


$oc/devices/668758a55830dc113ecaf2f0_temperature_sen###_###/sys/properties/report

2.2、上报数据格式

{  "services":[    {      "service_id":"all_temperature_sensor",      "properties":{        "temperature":11      }    }  ]}
复制代码


{"services":[{"service_id":"all_temperature_sensor","properties":{"temperature":11}}]}
复制代码


其中的 service_id 为:



properties 为模型里面的数据:


2.3、模拟客户端


云平台查看


五、移动 APP 端连接云平台


移动 APP 端基本有两个操作,一个是获取平台存储的设备端上传的数据,二是通过云平台给设备端发送指定。

1、创建 IAM 账号

我们每次从云平台上获取设备端上传的数据都需要身份认证,认证的令牌就是 token,我们需要调用指定的接口,上传我们个人信息获取 token,在线调试以及官方文档:https://console.huaweicloud.com/apiexplorer/#/openapi/IAM/doc?api=KeystoneCreateAgencyToken

1.1、创建 IAM 账号(要求必须新建一个用户)





赋予管理员权限(实际生产根据自身情况赋予权限)



在该过程中包含获取 token 三个非常重要的数据:


IAMDomain:IAM用户所属账号名,就是主账号名称IAMUser:IAM用户名,就是刚才创建的用户的用户名IAMPassword:IAM用户密码,就是刚才创建的用户的密码
复制代码



另外一个参数是项目名称:根据自己创建 IoTDA 实例的地区取对应的值


cn-north-1:项目名称,取值就是cn-north-4
复制代码



然后到统一身份认证里面的项目分类中查看


1.2、请求地址

POST https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens?nocatalog=true
复制代码

1.3、请求参数格式

请求的header参数:{  "Content-Type": "application/json"}请求的body参数:{    "auth": {        "identity": {            "methods": [                "password"                         //固定写法            ],            "password": {                "user": {                    "domain": {                        "name": "IAMDomain"        //IAM用户所属账号名                    },                    "name": "IAMUser",             //IAM用户名                    "password": "IAMPassword"      //IAM用户密码                }            }        },        "scope": {            "project": {                "name": "cn-north-4"               //项目名称            }        }    }}
复制代码

2、应用内获取 token

2.1、安装 axios 模块

本次项目的 HTTP 请求使用了 axios 模块,所以需要先给项目安装 axios 模块


打开编辑器终端输入命令回车即可:
ohpm install @ohos/axios
复制代码


2.2、构建请求体数据模型

因为 harmony OS NEXT 严格要求数据类型,所以需要创建用于构建请求 body 参数的数据模型对象


代码简单并且没有技术含量这里省略代码
复制代码


2.3、发送请求获取 token

因为 token 的有效期是 24 小时,所以我们没有必须每次跟云平台交货都请求 token,所以在移动 App 端把第一次获取到的 token 存储到首选项中,然后通过定时器每隔 24 小时以后再次获取一次即可。获取 token 是全局的方式,所以可以把获取 token 相关的操作放到项目启动的时候经行,对应到程序中就是写在 EntryAbility 的 onWindowStageCreate 方法里面即可


import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';import { hilog } from '@kit.PerformanceAnalysisKit';import { window } from '@kit.ArkUI';import { IAMTokenPreferencesDataUtil } from '../utils/IAMTokenPreferencesDataUtil';import { AuthInfo } from '../IAMAuth/AuthInfo';import { Auth } from '../IAMAuth/Auth';import { Identity } from '../IAMAuth/Identity';import { Password } from '../IAMAuth/Password';import { User } from '../IAMAuth/User';import { Domain } from '../IAMAuth/Domain';import { Scope } from '../IAMAuth/Scope';import axios, { AxiosError, AxiosResponse } from '@ohos/axios';import { Project } from '../IAMAuth/Project';
export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); } getIAMToken = ():void => { /** * 封装请求body参数 */ const authInfo: AuthInfo = new AuthInfo(); const auth: Auth = new Auth();
const identity: Identity = new Identity(); identity.methods = ['password'];
const password: Password = new Password();
const user: User = new User(); user.name = 'IAM用户名'; user.password = 'IAM用户密码'; const domain: Domain = new Domain(); domain.name = 'IAM用户所属账号名'; user.domain = domain; password.user = user; identity.password = password;
const scope: Scope = new Scope(); const project: Project = new Project(); project.name = 'cn-north-4'; scope.project = project;
auth.identity = identity; auth.scope = scope; authInfo.auth = auth;
/** * 通过axios发送请求 */ const url: string = 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens?nocatalog=true'; const axiosInstance = axios.create({ headers:{ "Content-Type": "application/json" } }); axiosInstance.post(url, authInfo).then((response: AxiosResponse) => { /** * 相应头里面的x-subject-token就是我们要的token */ const token: string = response.headers['x-subject-token']; console.log('testTag','getIAMToken获得的token是:', token); /** * 将token存储到首选项中 */ IAMTokenPreferencesDataUtil.init().setPreferencesData('token', token); }).catch((error: AxiosError) => { console.log('testTag',JSON.stringify(error)); }) }

onDestroy(): void { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); }
onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); /** 定时器默认不会先执行先执行一次,必须要到24小时以后才会执行,所以自己手动先调用下 */ this.getIAMToken(); setInterval(this.getIAMToken.bind(this), 24 * 60 * 59 * 10000);
windowStage.loadContent('pages/Index', (err) => { if (err.code) { hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); }); }
onWindowStageDestroy(): void { // Main window is destroyed, release UI related resources hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); }
onForeground(): void { // Ability has brought to foreground hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); }
onBackground(): void { // Ability has back to background hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); }}
复制代码

2.4、保存 token 到首选项

在上面的代码中涉及到的通过首选项保存用户数据的操作,一下是工具类


import { preferences } from '@kit.ArkData';
export class IAMTokenPreferencesDataUtil{ private static initObject: IAMTokenPreferencesDataUtil = new IAMTokenPreferencesDataUtil(); private constructor() { } static init(): IAMTokenPreferencesDataUtil{ return IAMTokenPreferencesDataUtil.initObject; }
private getPreferencesObject(): preferences.Preferences { const preferencesObject = preferences.getPreferencesSync(getContext(), {name: 'iamToken'}); return preferencesObject; }
getPreferencesData(name: string) { const obj: preferences.Preferences = this.getPreferencesObject(); const token = obj.getSync(name, '') as string; return token; } setPreferencesData(name: string, value: string) { const obj: preferences.Preferences = this.getPreferencesObject(); obj.put(name, value) }}
复制代码

3、获取云平台设备上传的数据

3.1、请求地址

先通过测试地址显示的配置参数,从而获取真实的请求地址,测试地址如下:


https://console.huaweicloud.com/apiexplorer/#/openapi/IoTDA/doc?api=ShowDeviceShadow



3.2、请求参数格式

只需要带两个headers参数:{    "Content-Type": "application/json",    "X-Auth-Token": token}
复制代码

3.3、获取云平台数据

我们可以每隔 5 秒获取一下数据,保证移动 APP 端和设备端数据同步


import { SensorInfo } from '../../models/SensorInfo';import { WeatherDetailsCard } from './WeatherDetailsCard'import { IAMTokenPreferencesDataUtil } from '../../utils/IAMTokenPreferencesDataUtil';import axios, { AxiosError, AxiosResponse } from '@ohos/axios';import { hilog } from '@kit.PerformanceAnalysisKit';
@Componentexport struct WeatherDetailsCardList {
@State sensorInfo: SensorInfo = new SensorInfo();
aboutToAppear(): void { this.getSensorAll(); setInterval(this.getSensorAll.bind(this), 3000); } getSensorAll = (): void => { const token = IAMTokenPreferencesDataUtil.init().getPreferencesData('token'); //这里记住该成自己的地址 const url = 'https://#######:443/v5/iot/c7214059c7394275a056d234b44af71c/devices/6684b5b95830dc113eca9620_temperature_sensor_1/shadow'; const axiosInstance = axios.create({ headers:{ "Content-Type": "application/json", "X-Auth-Token": token } });
axiosInstance.get(url).then((response: AxiosResponse) => { const shadow: string = JSON.stringify(response.data['shadow']); const shadowObject = JSON.parse(shadow)[0].reported.properties as SensorInfo; // 逐个属性进行赋值 this.sensorInfo.outdoor_temperature = shadowObject.outdoor_temperature; this.sensorInfo.longhua_1_green_house_temperature = shadowObject.longhua_1_green_house_temperature; this.sensorInfo.longhua_1_green_house_humidity = shadowObject.longhua_1_green_house_humidity; this.sensorInfo.longhua_1_green_house_blower = shadowObject.longhua_1_green_house_blower; console.log('获取的影子数据是: : ', JSON.stringify(shadow)) hilog.info(2311, 'testTag', '获取的影子数据是: ', JSON.stringify(this.sensorInfo)) }).catch((error: AxiosError) => { console.log('testTag', JSON.stringify(error)); }) } build() { Row({space: 20}) { WeatherDetailsCard({ title: '室外温度', value: this.sensorInfo.outdoor_temperature + '°C', icon: $r('app.media.wendu'), errorValue: '', errorFlagIcon: $r('app.media.shangjiantou'), flag: false }); WeatherDetailsCard({ title: '室内温度', value: this.sensorInfo.longhua_1_green_house_temperature + '°C', icon: $r('app.media.wendu'), errorValue: '10°C', errorFlagIcon: $r('app.media.shangjiantou'), flag: true }); } }}
复制代码


注意:1、setInterval(this.getSensorAll.bind(this), 3000);定时器的这个语法会导致getSensorAll方法内丢失this,我们需要进行两步操作,第一步在定义getSensorAll方法的时候一箭头函数的方式定义: getSensorAll = (): void => {},第二步在定时器的第一个参数上绑定this:this.getSensorAll.bind(this)2、请求响应回来的数据在获取的时候比较麻烦(相应JSON嵌套的有点多),所以建议一个一个获取。
复制代码

3.4、数据显示

正确的绑定到页面组件即可

4、通过云平台给设备下发指令

因为刚才我们创建的温度计产品不需要下发指令,我们可以创建另外的产品,比如补光灯、鼓风机等,原理基本相同,这里使用鼓风机为例(产品以及设备自己创建)

4.1、请求地址

同样的先通过测试地址测试获取真实的请求地址,测试地址如下:


https://console.huaweicloud.com/apiexplorer/#/openapi/IoTDA/doc?api=UpdateProperties



测试成功获得真实请求地址:



注意: 因为是模拟设备,所以模拟设备收到指定以后无法自动回应,这样会导致当前请求超时,但是没关系,模拟设备已经接收到的指令。


4.2、请求参数格式

{  "services": {    "longhua_1_green_house_blower": 0  }}
复制代码


4.3、移动 APP 下发指令

4.1、4.2 已经实现了在云平台通过 HTTPS 请求下发指令给设备了,把这个操作通过 APP 里面的一个按钮点击发送对应的 HTTPS 请求即可。

用户头像

九九花开

关注

还未添加个人签名 2025-05-15 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙版《智慧农业APP》通过华为云IoT平台实现软件硬件互联_物联网_九九花开_InfoQ写作社区