本文将介绍如何使用AndroidStudio开发APP完成与接入华为云IoTDA设备的对接,包括属性参数获以及取命令下发。
一、鉴权认证
应用侧需要通过IAM服务鉴权,获取token,华为账号创建 IAM 用户, 可以为创建的用户分配权限
1. 创建IAM账户
- 在统一身份认证服务,左侧导航窗格中,单击“用户”--->“创建用户”。
- 配置基本信息。在“创建用户”界面填写“用户信息”和“访问方式”。如需一次创建多个用户,可以单击“添加用户”进行批量创建,每次最多可创建10个用户。
- 创建用户组
- 加入用户组并创建用户
- 为用户组授权
2. 获取IAM用户Token
IAM 用户需要通过 HTTPS 协议 POST 请求调用API 接口, 获取 IAM 用户的 Token,华为云认证通过后向应用服务器返回鉴权 X-SubjectToken。 该 Token 用于后续进一步的身份验证和授权操作。Token的有效期为24小时 。
2.1. 请求示例
POST: https://iam.myhuaweicloud.com/v3/auth/tokens
{
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"domain": {
"name": "IAMDomain" //IAM用户所属账号名
},
"name": "IAMUser", //IAM用户名
"password": "IAMPassword" //IAM用户密码
}
}
},
"scope": {
"project": {
"name": "xxxxxxxx"
}
}
}
}
其中IAM用户名为“IAMUser”,IAM用户密码为“IAMPassword”,所属账号名为“IAMDomain”,在本次实验中个参数如下:
IAMUser:创建用户时填写的名字 DJ_IOT
IAMPassword:创建用户时填写的密码 ********
IAMDomain:在“我的凭证”界面,API凭证页签中,查看账号名、账号ID、用户名、用户ID、项目名称、项目ID。
3. 代码构造
3.1. 添加依赖
本次实验中使用OkHttp3库来发送HTTP请求,首先,在build.gradle(app/build.gradle)文件中添加OkHttp的依赖。
dependencies {
implementation ("com.squareup.okhttp3:okhttp:4.9.3")
}
3.2. 声明权限
在AndroidManifest.xml中声明网络权限,
<uses-permission android:name="android.permission.INTERNET" />
3.3. 构建方法
该方法有4个入参分别是:租户名IAMDomain,用户名IAMUser,密码IAMPassword,请求地址iamTokenUrl,具体内容见上文。
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final OkHttpClient client = new OkHttpClient(); // 假设OkHttpClient是线程安全的,可以作为静态成员
// 方法签名修改为接受必要的参数
public String getIAMToken(String huaweiName, String iamIname, String iamPassword, String iamTokenUrl) throws IOException {
// 构建JSON请求体
String jsonBody = String.format("{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"domain\": {\"name\": \"%s\"},\"name\": \"%s\",\"password\": \"%s\"}}},\"scope\": {\"project\": {\"name\": \"cn-north-4\"}}}}",
huaweiName, iamIname, iamPassword);
RequestBody body = RequestBody.create(jsonBody, JSON);
// 构建请求
Request request = new Request.Builder()
.url(iamTokenUrl)
.post(body)
.addHeader("Content-Type", "application/json")
.build();
// 发送请求并处理响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
// 输出错误内容
System.out.println("Unexpected code " + response);
throw new IOException("Unexpected code " + response);
}
// 从响应头中提取IAM令牌
String iamToken = response.header("X-Subject-Token");
if (iamToken == null || iamToken.isEmpty()) {
// 记录警告日志(如果没有找到令牌)
throw new IOException("IAM token not found");
}
return iamToken;
} catch (IOException e) {
// 记录异常日志
throw e; // 重新抛出异常以便调用者可以处理
}
}
3.4. 调用
在APP启动时调用一次,Android 禁止在主线程中进行网络操作,因为这可能会导致应用界面卡顿或冻结,所以调用的时候使用new Thread创建并启动线程。
String HUAWEINAME="xxxxxxx"; //华为账号名
String IAMINAME="xxxxxxx"; //IAM账户名
String IAMPASSWORD="xxxxxxx"; //IAM账户密码
String URL="https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens?nocatalog=false";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Thread t = new Thread() {
@Override
public void run() {
try {
IoTDAUtils hw = new IoTDAUtils();
String token = hw.getIAMToken(HUAWEINAME,IAMINAME,IAMPASSWORD,URL);
System.out.println("获取token:" + token);
} catch (Exception e) {
e.printStackTrace();
System.out.println("错误" + e.getMessage().toString());
}
}};
t.start();
}
3.5. token
在logcat中token已经打印出来
二、查看影子消息
设备影子是一个用于存储和检索设备当前状态信息的JSON文档。每个设备有且只有一个设备影子,由设备ID唯一标识,设备影子用于存储设备上报的(状态)属性和应用程序期望的设备(状态)属性,无论该设备是否在线,都可以通过该影子获取和设置设备的属性。
利用设备影子消息获取设备属性无论设备是否在线都可以查看最近一次上报的属性,如果使用查询设备属性功能需要与设备端联动下发查询指令设备端收到后上报指令,本次采用影子消息来获取设备属性。
1. URI
GET https://{endpoint}/v5/iot/{project_id}/devices/{device_id}/shadow
路径参数如下:
是否必选 | 参数类型 | 描述 | |
project_id | 是 | String | 参数说明:项目ID。获取方法请参见 获取项目ID 。 |
device_id | 是 | String | 参数说明:设备ID,用于唯一标识一个设备。在注册设备时直接指定, 或者由物联网平台分配获得。 取值范围:长度不超过128,只允许字母、数字、下划线(_)、连接符(-)的组合。 |
请求参数如下:
是否必选 | 参数类型 | 描述 | |
X-Auth-Token | 否 | String | 参数说明:用户Token。通过调用IAM服务 获取IAM用户Token接口获取,接口返回的响应消息头中“X-Subject-Token”就是需要获取的用户Token。简要的获取方法样例请参见 Token认证。 |
Instance-Id | 否 | String | 参数说明:实例ID。物理多租下各实例的唯一标识,建议携带该参数,在使用专业版时必须携带该参数。您可以在IoTDA管理控制台界面,选择左侧导航栏“总览”页签查看当前实例的ID,具体获取方式请参考查看实例详情 。 |
响应Body参数:
参数类型 | 描述 | |
device_id | String | 设备ID,用于唯一标识一个设备。在注册设备时直接指定,或者由物联网平台分配获得。由物联网平台分配时,生成规则为"product_id" + "_" + "node_id"拼接而成。 |
shadow | Array of DeviceShadowData objects | 设备影子数据结构体。 |
2. 代码构造
2.1. 构建方法
private static String IOTDA_ENDPOINT = "https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/%s/devices/%s/shadow";
String project_id="*******************";
String device_id="********************";
public void getDeviceShadow(String token,Callback callback) {
IOTDA_ENDPOINT = String.format(IOTDA_ENDPOINT, project_id,device_id);
Request request = new Request.Builder()
.url(IOTDA_ENDPOINT)
.addHeader("Content-Type", "application/json")
.addHeader("X-Auth-Token", token) // 注意:这里应该使用有效的认证令牌
.build();
Call call = client.newCall(request);
call.enqueue(callback);
}
2.2. 调用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tev = findViewById(R.id.tev);
final Thread t = new Thread() {
@Override
public void run() {
try {
IoTDAUtils hw = new IoTDAUtils();
AUTH_TOKEN = hw.getIAMToken(HUAWEINAME,IAMINAME,IAMPASSWORD,URL);
System.out.println("获取token:" + AUTH_TOKEN);
hw.getDeviceShadow(AUTH_TOKEN,new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 处理网络错误或请求失败
runOnUiThread(() -> tev.setText("Error: " + e.getMessage()));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
// 处理成功的响应并更新UI
String responseBody = response.body().string();
runOnUiThread(() -> tev.setText("Device Shadow: " + responseBody));
} else {
// 处理不成功的响应(如认证失败、资源未找到等)
runOnUiThread(() -> tev.setText("Request failed: " + response.code()));
}
}
});
} catch (Exception e) {
e.printStackTrace();
System.out.println("错误" + e.getMessage().toString());
}
}};
t.start();
}
}
三、命令下发
1. URI
POST https://{endpoint}/v5/iot/{project_id}/devices/{device_id}/commands
路径参数
是否必选 | 参数类型 | 描述 | |
project_id | 是 | String | 参数说明:项目ID。获取方法请参见 获取项目ID 。 |
device_id | 是 | String | 参数说明:下发消息的设备ID,用于唯一标识一个设备,在注册设备时由物联网平台分配获得。 取值范围:长度不超过128,只允许字母、数字、下划线(_)、连接符(-)的组合。 |
请求Header参数
是否必选 | 参数类型 | 描述 | |
X-Auth-Token | 否 | String | 参数说明:用户Token。通过调用IAM服务 获取IAM用户Token接口获取,接口返回的响应消息头中“X-Subject-Token”就是需要获取的用户Token。简要的获取方法样例请参见 Token认证。 |
Instance-Id | 否 | String | 参数说明:实例ID。物理多租下各实例的唯一标识,建议携带该参数,在使用专业版时必须携带该参数。您可以在IoTDA管理控制台界面,选择左侧导航栏“总览”页签查看当前实例的ID,具体获取方式请参考查看实例详情 。 |
请求Body参数
是否必选 | 参数类型 | 描述 | |
service_id | 否 | String | 参数说明:设备命令所属的设备服务ID,在设备关联的产品模型中定义。 取值范围:长度不超过64的字符串。 最大长度:64 |
command_name | 否 | String | 参数说明:设备命令名称,在设备关联的产品模型中定义。 取值范围:长度不超过128的字符串。 最大长度:128 |
paras | 是 | Object | 参数说明:设备执行的命令,Json格式,里面是一个个键值对,如果serviceId不为空,每个键都是profile中命令的参数名(paraName);如果serviceId为空则由用户自定义命令格式。设备命令示例:{"value":"1"},具体格式需要应用和设备约定。此参数仅支持Json格式,暂不支持字符串。 最大长度:261952 |
请求示例
POST https://{endpoint}/v5/iot/{project_id}/devices/{device_id}/commands
{
"service_id" : "reboot",
"command_name" : "ON_OFF",
"paras" : {
"value" : "ON"
}
}
2. 代码构造
2.1. 方法构造
private static String IOTDA_ENDPOINT_CMD = "https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/%s/devices/%s/commands";
public void sendDeviceCommand(String commandPayload,String token, Callback callback) {
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, commandPayload);
IOTDA_ENDPOINT_CMD = String.format(IOTDA_ENDPOINT_CMD, project_id,device_id);
Request request = new Request.Builder()
.url(IOTDA_ENDPOINT_CMD)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("X-Auth-Token", token)
.build();
client.newCall(request).enqueue(callback);
}
2.2. 调用
@Override
public void onClick(View view) {
final Thread t = new Thread() {
@Override
public void run() {
try {
IoTDAUtils hw = new IoTDAUtils();
str_cmd = String.format(str_cmd, service_id,command_name,paras);
System.out.println(str_cmd);
hw.sendDeviceCommand(str_cmd,AUTH_TOKEN,new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 处理网络错误或请求失败
runOnUiThread(() -> tev.setText("Error: " + e.getMessage()));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
// 处理成功的响应并更新UI
String responseBody = response.body().string();
runOnUiThread(() -> tev.setText("Device Shadow: " + responseBody));
} else {
// 处理不成功的响应(如认证失败、资源未找到等)
runOnUiThread(() -> tev.setText("Request failed: " + response.code()));
}
}
});
} catch (Exception e) {
e.printStackTrace();
System.out.println("错误" + e.getMessage().toString());
}
}};
t.start();
}
四、工具类
根据以上内容整理完成IoTDAUtils工具类
package com.example.huawei_iotda_app;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class IoTDAUtils {
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final OkHttpClient client = new OkHttpClient();
public String getIAMToken(String huaweiName, String iamIname, String iamPassword, String iamTokenUrl) throws IOException {
// 构建JSON请求体
String jsonBody = String.format("{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"domain\": {\"name\": \"%s\"},\"name\": \"%s\",\"password\": \"%s\"}}},\"scope\": {\"project\": {\"name\": \"cn-north-4\"}}}}",
huaweiName, iamIname, iamPassword);
RequestBody body = RequestBody.create(jsonBody, JSON);
// 构建请求
Request request = new Request.Builder()
.url(iamTokenUrl)
.post(body)
.addHeader("Content-Type", "application/json")
.build();
// 发送请求并处理响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
// 输出错误内容
System.out.println("Unexpected code " + response);
throw new IOException("Unexpected code " + response);
}
// 从响应头中提取IAM令牌
String iamToken = response.header("X-Subject-Token");
if (iamToken == null || iamToken.isEmpty()) {
// 记录警告日志(如果没有找到令牌)
throw new IOException("IAM token not found");
}
return iamToken;
} catch (IOException e) {
// 记录异常日志
throw e; // 重新抛出异常以便调用者可以处理
}
}
public void getDeviceShadow(String token,String url,Callback callback) {
Request request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json")
.addHeader("X-Auth-Token", token) // 注意:这里应该使用有效的认证令牌
.build();
Call call = client.newCall(request);
call.enqueue(callback);
}
public void sendDeviceCommand(String commandPayload,String token,String url, Callback callback) {
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, commandPayload);
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("X-Auth-Token", token)
.build();
client.newCall(request).enqueue(callback);
}
}
后续将按照以上内容开发一个便于测试的APP,可关注后续文章。
文章及源码地址:华为云应用侧Android测试APP-CSDN博客