文章目录
产品需要接入物联网,主要面向欧美市场,所以选择使用了亚马逊物联网平台。研究了一阵子做一些总结。
一、前言
本文中是开发的APP控制端(开发语言是Android),来接入AWS IOT平台并且实现消息的订阅和发布。由于能力有限,若有错误的理解请指正。
AWS IOT的官方文档,不得不说文档写的真“详细”啊,看得云里雾里,只能参考互联网。
由于国内使用AWS物联网平台应用不多,关于Android APP的demo更是几乎没有,网上能搜到与之相关最多的是PubSub的Android项目。AWS的官方博客中有一篇文章介绍了相关内容,还有其他一些博主也有一些探究,但由于时间过于久远很多操作都发生了变化,甚至连官方GitHub上的Android PubSub示例也下架了(猜测早期应该是有的),资料实在是匮乏,只能一步一步尝试。
二、准备
在编写APP之前首先要了解一些基础知识,如mqtt协议、IOT设备、事务、策略等等,本文中还使用了AWS IAM、Cognito,也需要做适当了解。
1.使用Android Studio构建App项目
项目下的build.gradle中导入iot使用包,AWS的开发包很多,这里使用的android版的iot开发包,服务器端应该使用java版
implementation 'com.amazonaws:aws-android-sdk-iot:2.73.0'
2.注册AWS账号,需要一张信用卡,国内的也可以,没有什么难度。审核通过后即可开始测试使用。
登录AWS控制台,主要使用的是IOT Core、IAM以及Cognito
在使用IOT Core以及Cognito控制台时需要注意右上角的区域选择,曾经我就犯过在代码里创建资源后在控制台里始终找不到的错误,原因就是区域错了。另外各区域是否都支持用户池、身份池暂时不明,我就直接选择的us-east-1测试。
3.Cognito、IAM概念
从官方文档阅读可以知道AWS IOT Core允许好几种方式来安全接入,比如X.509证书、Cognito身份等等。
从网上搜索的关于之前的PubSub项目的零散代码可以看出:
//创建证书
CreateKeysAndCertificateRequest createKeysAndCertificateRequest =
new CreateKeysAndCertificateRequest();
createKeysAndCertificateRequest.setSetAsActive(true);
final CreateKeysAndCertificateResult createKeysAndCertificateResult;
//调用了refresh方法
createKeysAndCertificateResult =
mIotAndroidClient.createKeysAndCertificate(createKeysAndCertificateRequest);
Log.i(Constants.TAG,
"Cert ID: " +
createKeysAndCertificateResult.getCertificateId() +
" created.");
// store in keystore for use in MQTT client
// saved as alias "default" so a new certificate isn't
// generated each run of this application
Log.d(Constants.TAG,certificateId);
Log.d(Constants.TAG,createKeysAndCertificateResult.getCertificatePem());
Log.d(Constants.TAG,createKeysAndCertificateResult.getKeyPair().getPrivateKey());
AWSIotKeystoreHelper.saveCertificateAndPrivateKey(certificateId,createKeysAndCertificateResult.getCertificatePem(),
createKeysAndCertificateResult.getKeyPair().getPrivateKey(),
keystorePath, keystoreName, keystorePassword);
该示例中实际是创建了一个Keystore文件存储在App的私有目录下,并且给该Keystore附加上一个IOT策略(一定要加,不加连接不了),然后调用manager去连接,如下:
mqttManager.connect(pubSubActivity.clientKeyStore ...
在连接成功后即可订阅、发布消息了。
整个过程可以概括为:
1).app内有权限的情况下生成一个iot认可的证书,且该证书上附加了一个已存在的策略
2).iot认可这个附上策略的证书,可连接发布订阅消息
但是这种方案不适用于实际生产环节,且官方也不推荐使用(keystore文件相当于在手机上存放了一个证书),这种方案应该更适用于设备端接入AWS IOT。
在每次生成Keystore文件时在IOT的后台下的证书菜单里,都会对应有一个证书。而手机APP一旦卸载之前生成的Keystore文件也会消失,再次调用会重新生成新的证书,旧的证书依然存在但其实已失效(刚开始测试使用这种方案时App经常卸载重装,iot的后台的证书菜单下就多了很多无用的数据)。
对于移动端APP推荐使用Cognito身份来接入AWS IOT平台。
Cognito没有做细致研究,仅使用了身份池。身份池的概念相当于初始进入APP时是一个游客身份,经过“登录”后变成了另一个经过验证的身份,所拥有的权限也发生了变化。我测试了身份提供商是Login with Amazon和自定义身份,其他如用户池、google等请自行测试。
4.步骤详解
1)配置身份池
这里经过身份验证的我选择了Amazon和自定义,下一步生成对应的IAM角色,直接生成新的角色,分别命名auth_xx和unauth_xx,具体名称不限只需要记住哪个对应哪个就行。
Amazon身份提供商在该处填写应用程序ID,该应用ID是在Login With Amazon控制台注册生成的ID,还是有点麻烦的,请自行查看官网文档进行注册。
最终填写的是图示中箭头的这个id。
自定义身份提供商可自己定义一个标识,合法即可,这个标识后续在android和自有服务器端都会使用到。
填写好后其他默认不需要修改,直接创建审核通过即可。
2)补充IAM角色策略
进入IAM控制台后点击角色,找到上面创建身份池时命名的两个角色名。
点击给经过验证的auth_xx的这个角色,点击附加策略。
测试可以点击附加策略后选择AWSIoTFullAccess全部权限,也可以点击创建内联策略自定义一个,json格式串可以网上搜,自定义主要可以缩小一些权限范围,后面再讲权限策略设置。
至此可以简单理解下:一个用户下载了APP,刚使用时是一个游客,当用户使用了Amazon账户联合登录或者服务器端对他进行了验证合法后用户就获得了auth_xx这个角色的权限,就能和IOT互动了。
3)创建IOT Policy
为什么还需要创建Iot Policy呢,因为官方文档里介绍这种接入AWS IOT的方式是由IAM角色的权限策略和这个identity身份上的权限策略组合而定的,至于两者的权限是如何合并界定参考官方,我也没细致研究。(题外话:如果给unauth_xx身份直接赋予了AWSIoTFullAccess权限是后不用附加IOT策略也是可以连接IOT平台的)
点击安全性->策略,创建一个新策略,内容也可以先设置全部权限,稍后和上面IAM一样再进行细分。
三 、编码
下面介绍以Login with Amzon为身份提供商接入AWS IOT
已准备好的工作:
1>创建了一个身份池,关联定义了两个IAM角色,一个是未经过认证的角色,权限默认不需要更改,另一个是经过认证的角色,附加上了AWSIoTFullAccess权限。该身份提供商Amazon的ID已填写。
2>Iot的后台创建了一个策略,策略先定义的全部权限(参照上图)
3>注意区域选择一致
Android App中接入Login with Amazon不做详解,网上资料齐全没什么难度。
在oncreat方法里先初始化Cognito认证构造方法
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(), // Context
"XXXXXXXXXXXX", //Cognito控值台创建的身份池ID
Regions.US\_EAST\_1 // 身份池所属区域
);
题外话:如果是使用的自定义身份验证的方式,就不能使用这个构造方法了,要使用下面这个
DeveloperAuthenticationProvider developerProvider = new DeveloperAuthenticationProvider
(null, Cognito\_Pool\_ID, Regions.US\_EAST\_1,baseApplication);
credentialsProvider = new CognitoCachingCredentialsProvider( context, developerProvider, Regions.US\_EAST\_1 );
developerProvider是需要自己实现的一个方法,继承自AWSAbstractCognitoDeveloperIdentityProvider
public class DeveloperAuthenticationProvider extends AWSAbstractCognitoDeveloperIdentityProvider {
private static final String developerProvider = "xxxxxx";//这是自定义的身份提供商名
private BaseApplication baseApplication;
//构造方法
public DeveloperAuthenticationProvider(String accountId, String identityPoolId, Regions region, BaseApplication baseApplication) {
super(accountId, identityPoolId, region);
this.baseApplication = baseApplication;
}
.............
这个自定义类中主要是要实现refresh方法
@Override
public String refresh() {
String result = "";
String getIdenttid = "";
String token="";
try {
//这里要调用后台服务器方法来获取权限,这里就简单传了存在sp中的用户ID到后台服务器
String appToken = (String)baseApplication.get("token","");
result = AcceletNetWork.synGet(HttpUrls.GetCognitoToken+"&appToken="+appToken);//这里采用的是同步
Gson gson = new Gson();
TokenDataBean dataBean = gson.fromJson(result, TokenDataBean.class);
getIdenttid = dataBean.getIdentityId();
token = dataBean.getToken();
} catch (Exception e) {
e.printStackTrace();
}
//服务器返回两个参数,update一下
update(getIdenttid,token);
return token;
}
服务器也是java编写,代码简单提一下:
服务器使用的AWS java版的SDK,且有V1、V2的版本,请注意区别。
try {
Map<String, String> logins = Collections.singletonMap(developerIdentityProviderName, "single" + ":" + user_sn);//developerIdentityProviderName是在身份池里填写的自定义标识,第二个参数是唯一标识,这里采用的是用户user\_sn
Builder builder = GetOpenIdTokenForDeveloperIdentityRequest.builder()
.identityPoolId(identityPoolId)//身份池ID
.logins(logins)
.tokenDuration(3600\*24L);//有效期
GetOpenIdTokenForDeveloperIdentityRequest request = builder.build();
GetOpenIdTokenForDeveloperIdentityResponse response = getClient().getOpenIdTokenForDeveloperIdentity(request);
//最主要的就是getOpenIdTokenForDeveloperIdentity方法了,官网文档介绍一定要从后台调用
if(response != null) {
JSONObject identityJson = new JSONObject();
identityJson.put("identityId", response.identityId());
identityJson.put("token", response.token());
return identityJson;
}
} catch (Exception e) {
e.printStackTrace();
}
题外话结束,回到LWA版。在使用亚马逊账户登录完成后会返回一个token,更新登录映射
public void onSuccess(AuthorizeResult result) {
String token = result.getAccessToken();
if (null != token) {
/\* The user is signed in \*/
Map<String, String> logins = new HashMap<String, String>();
logins.put("www.amazon.com", token);//标识名固定为www.amazon.com
credentialsProvider.setLogins(logins);//调用setLogins
attachPolicy();
} else {
/\* The user is not signed in \*/
}
}
然后附加IOT策略
private void attachPolicy() {
try{
AWSIotClient awsIotClient = new AWSIotClient(credentialsProvider);
awsIotClient.setRegion(Region.getRegion(Regions.US\_EAST\_1));
AttachPolicyRequest attachPolicyRequest = new AttachPolicyRequest()
.withPolicyName("xxxxx")//IOT控制台里创建的策略名称
.withTarget(credentialsProvider.getIdentityId());//身份池的唯一ID
awsIotClient.attachPolicy(attachPolicyRequest);
}catch (Exception e){
e.printStackTrace();
Log.d(Constants.TAG,"附加策略异常");
}
}
附加IOT策略说明:网上搜有不少介绍使用AWS CLI给identityId加上策略,更有甚者说没有API可调用的。
还是得参考官方文档啊,文档里介绍了核心代码但就是不提供完整的PubSub示例,无语。
这里附加IOT策略需要注意:
1>构造的awsIotClient需要有attachPolicy的权限,之前IAM里经过身份验证的角色赋予了AWSIOTFull全部的权限,所以没问题,但是你如果改了那就要加上AttachPolicy。
2>IOT控制台里创建的策略名称也要正确,如果区域不一致会报找不到策略的错误。如果是控制台里复制时注意左右不要有空格。
3>identityId身份,IOT策略附加到这个角色身上,这个身份对应的一个唯一ID,在Cognito管理后台可以查看。
这个身份ID很有用处,后面要用它连接AWS IOT CORE以及解决缓存问题。
4>由于attachPolicy方法没有回调函数之类的反应是否策略附加成功,各个管理后台也没有查看的地方。但我试了修改策略名称或者随便填一个身份ID,都会报错,所以只要程序没有报错那么策略就附加成功了。
接下来就是准备连接AWS IOT进行MQTT测试了,初始化awsIotMqttManager并进行连接
String tIdentityId= credentialsProvider.getIdentityId();
awsIotMqttManager = new AWSIotMqttManager(tIdentityId, "xxxxxxxxxx");
awsIotMqttManager.setCleanSession(true);//默认为true,类似于一个连接如果断线后服务器不再保留其相关的信息
awsIotMqttManager.setKeepAlive(10);// 将keepalive的时间设置小于服务端的超时时间,则客户端每隔 keepalive的时间就会给服务端发一个心跳包,默认是300s,单位是s
awsIotMqttManager.setAutoReconnect(true);//断开后自动重连
awsIotMqttManager.setMaxAutoReconnectAttempts(1);//设置断网后重新尝试连接的次数,不设的话默认值为-1 ,一直试着连接
try {
awsIotMqttManager.connect(credentialsProvider, (status, throwable) -> {
if(throwable!=null){
Log.d(Constants.TAG,throwable.toString());
}
if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Connecting) {
Log.d(Constants.TAG,"正在连接中");
}else if(status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Connected){
Log.d(Constants.TAG,"连上了");
}else if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Reconnecting) {
Log.d(Constants.TAG,"重连");
}else if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.ConnectionLost) {
Log.d(Constants.TAG,"失去连接");
}else{
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**
**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/2c05c11ae6f1097ee848c0bfe43f6a51.png)
![img](https://img-blog.csdnimg.cn/img_convert/8fe53d5eda07e2b4268a0b8523113ccf.jpeg)
![img](https://img-blog.csdnimg.cn/img_convert/42055fb4f64f5a6ce87c5a208c3973be.png)
![img](https://img-blog.csdnimg.cn/img_convert/303c27320fafbf1397a050fff5cefce5.png)
![img](https://img-blog.csdnimg.cn/img_convert/15070e395f3bf84bb96655537d679c83.png)
![img](https://img-blog.csdnimg.cn/img_convert/9c596c79c8b3ed9ad3d62e6a4ce946e0.png)
![](https://img-blog.csdnimg.cn/img_convert/798862929b4dbc0c03d286d4c893bd27.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!
[外链图片转存中...(img-h4HQ2LRN-1715537383375)]
[外链图片转存中...(img-dPs7Svn5-1715537383376)]
[外链图片转存中...(img-CG0gIs0w-1715537383376)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!