前言:
1、本文介绍阿里云物联网的产品快速接入教程,仅针对监听MQTT协议以及处理,HTTP协议部分需在设备烧录,这里不做介绍,仅介绍JAVA如果快速接入;
2、本文的前提是设备端已经开发好,阿里云IOT的物模型、订阅topic、心跳都已经配置好;
3、所有的代码都是基于阿里IOT SDK云端开发API,需要引入以下依赖:
注:阿里云IOT文档地址:阿里云物联网平台-阿里云帮助中心
一、配置文件准备 (阿里云物联网消息队列(MQ)支持AMQP、MNS等,这里仅做MNS示例)
1、MNS消息队列常量
2、阿里云主配置文件
yml配置
二、实体类准备
1、设备实体类
@Data public class DeviceConfig implements Serializable { /** * 验签信息 */ private String sn; /** * 产品唯一key */ private String productKey; /** * 设备编号 */ private String deviceName; /** * 设备秘钥 */ private String deviceSecret; /** * 地址 */ public String host; /** * 端口 */ public int port; /** * 阿里云实例ID */ private String iotId; /** * token验签信息 */ private String iotToken; /** * 创建时间 */ private Date createdTime; /** * 过期时间 默认7天过期 */ private Date overdueTime; /** * 时间戳 */ private String timeStamp; }
2、消息主体实体类
@Data public class MessageBody implements Serializable { private String payload; private String messageType; private String messageId; private String topic; private long timestamp; private String productKey; private String deviceName; public String getPayload() { return payload; } public String getPayloadAsString(){ String data = new String(Base64.decodeBase64(getPayload())); return data; } public byte[] getPayloadAsBytes(){ byte[] data = null; data = Base64.decodeBase64(getPayload()); return data; } public void setPayload(String payload) { this.payload = payload; } public String getMessageType() { return messageType; } @JsonProperty(value="messagetype") public void setMessageType(String messageType) { this.messageType = messageType; } public String getMessageId() { return messageId; } @JsonProperty(value="messageid") public void setMessageId(String messageId) { this.messageId = messageId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; String[] arr = topic.split("/"); if(arr.length < 3){ return; } productKey = arr[1]; deviceName = arr[2]; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public String getProductKey() { return productKey; } public String getDeviceName() { return deviceName; } public void setProductKey(String productKey) { this.productKey = productKey; } public void setDeviceName(String deviceName) { this.deviceName = deviceName; }
三、封装工具类,完成项目与物联网的基础交互
工具类主要完成设备的联网,注册,查询(基础信息、或设备状态),删除,发送指令
@Component @Slf4j public class IotUtils { private DefaultAcsClient client = null; @Resource private AppConfig appConfig; @Autowired public IotUtils(AppConfig appConfig){ this.appConfig = appConfig; //初始化 DefaultAcsClient 对象 DefaultProfile profile = DefaultProfile.getProfile(appConfig.getRegionId(), appConfig.getAccessKeyId(), appConfig.getAccessKeySecret()); client = new DefaultAcsClient(profile); } /** * 获取MQTT鉴权地址 * <a href="https://help.aliyun.com/document_detail/73742.html?spm=a2c4g.11186623.6.587.69fa1bb8kGHsBc">...</a> * 使用HTTPS认证再连接 * @param productKey 设备编号 * @param uuid 设备唯一标识 * @return 阿里云返回设备的登录MQTT的账号和密码 * @throws Exception 异常 */ public DeviceConfig getIotDeviceConfig(String productKey, String uuid) throws Exception { log.info("【阿里云设备获取】 device_name:{}",uuid); DeviceConfig iotDeviceConfig = new DeviceConfig(); //设备详细信息 QueryDeviceDetailResponse query = getDeviceInfo(productKey, uuid); //阿里云注册设备 if (!query.getSuccess() && "iot.device.NotExistedDevice".equals(query.getCode())) { registerDevice(productKey, uuid); //新激活的设备 query = getDeviceInfo(productKey, uuid); } //设备详细信息 QueryDeviceDetailResponse.Data deviceInfo = query.getData(); //获取新的账号,密码 iotDeviceConfig.setSn(uuid); iotDeviceConfig.setProductKey(productKey); iotDeviceConfig.setDeviceName(iotDeviceConfig.getSn()); iotDeviceConfig.setDeviceSecret(deviceInfo.getDeviceSecret()); iotDeviceConfig.setHost(iotDeviceConfig.getProductKey() + ".iot-as-mqtt." + appConfig.getRegionId() + ".aliyuncs.com"); iotDeviceConfig.setPort(1883); iotDeviceConfig.setCreatedTime(new Date()); //七天过期,这里保险设置6天 iotDeviceConfig.setOverdueTime(DateUtils.addDays(new Date(), 6)); iotDeviceConfig.setTimeStamp(String.valueOf(System.currentTimeMillis())); String sb = "clientId" + iotDeviceConfig.getDeviceName() + "deviceName" + iotDeviceConfig.getDeviceName() + "productKey" + iotDeviceConfig.getProductKey() + "timestamp" + iotDeviceConfig.getTimeStamp(); //公共参数签名 String sign = HmacCoder.encrypt(sb, iotDeviceConfig.getDeviceSecret(), HmacCoder.TYPE_HMAC_MD5); String iotId = uuid+"&"+productKey; iotDeviceConfig.setIotId(iotId); iotDeviceConfig.setIotToken(sign); return iotDeviceConfig; } /** * 注册设备 * @param productKey 产品编号 * @param deviceName 设备名称 非必须(不传的话aliyun会自动生成) */ public void registerDevice(String productKey, String deviceName) throws ClientException { RegisterDeviceRequest request = new RegisterDeviceRequest(); request.setProductKey(productKey); request.setDeviceName(deviceName); request.setIotInstanceId(appConfig.getIotInstanceId()); RegisterDeviceResponse response = client.getAcsResponse(request); if (!response.getSuccess()) { throw new ClientException(response.getErrorMessage()); } } /** * 获取设备信息 * @param deviceName 设备编号 * @return 响应对象 * @throws ClientException 客户端异常 */ public QueryDeviceDetailResponse getDeviceInfo(String productKey, String deviceName) throws ClientException { QueryDeviceDetailRequest request = new QueryDeviceDetailRequest(); request.setProductKey(productKey); request.setDeviceName(deviceName); request.setIotInstanceId(appConfig.getIotInstanceId()); return client.getAcsResponse(request); } /** * 删除设备 * @param deviceName 设备编号 必选 */ public void removeDevice(String deviceName){ DeleteDeviceRequest request = new DeleteDeviceRequest(); request.setProductKey(appConfig.getProductKey()); request.setDeviceName(deviceName); request.setIotInstanceId(appConfig.getIotInstanceId()); try { // 发起请求并获取返回值 DeleteDeviceResponse response = client.getAcsResponse(request); // 处理业务逻辑 log.info("【设备删除成功】 response:{}",new Gson().toJson(response)); } catch (ServerException e) { log.error("【业务处理异常】 ErrCode:{},ErrMsg:{}",e.getErrCode(),e.getErrMsg()); e.printStackTrace(); } catch (ClientException e) { log.error("【Client请求异常】 ErrCode:{},ErrMsg:{},RequestId:{}",e.getErrCode(),e.getErrMsg(),e.getRequestId()); e.printStackTrace(); } } /** * 获取设备状态 * @param deviceName 设备编号 * @return 设备状态 * @throws ClientException 客户端异常 */ public DeviceOnline getDeviceStatus(String deviceName) { DeviceOnline online = DeviceOnline.NO_DEVICE; Map<String, DeviceOnline> map = null; try { map = getDeviceStatusMap(appConfig.getProductKey(), deviceName); } catch (ClientException e) { throw new RentBoxException(RentBoxExceptionCode.ALI_IOT_CLIENT_ERROR); } if (map.size() > 0) { return map.get(deviceName); } return online; } /** * getDeviceStatusList * @param productKey 产品编号 * @param deviceNames 设备编号 * @return 在线数据 * @throws ClientException 客户端异常 */ public Map<String, DeviceOnline> getDeviceStatusMap(String productKey, String... deviceNames) throws ClientException{ Map<String, DeviceOnline> map = new LinkedHashMap<String, DeviceOnline>(); BatchGetDeviceStateRequest request = new BatchGetDeviceStateRequest(); request.setProductKey(productKey); request.setIotInstanceId(appConfig.getIotInstanceId()); List<String> devices = new ArrayList<String>(); for (String deviceName : deviceNames) { if(!StringUtils.isBlank(deviceName)){ devices.add(deviceName); map.put(deviceName, DeviceOnline.NO_DEVICE); } } if(devices.size() == 0){ return map; } request.setDeviceNames(devices); BatchGetDeviceStateResponse response = client.getAcsResponse(request); List<BatchGetDeviceStateResponse.DeviceStatus> data = response.getDeviceStatusList(); for(String key : map.keySet()){ for(BatchGetDeviceStateResponse.DeviceStatus deviceStatus : data) { if (key.equals(deviceStatus.getDeviceName())) { DeviceOnline online = DeviceOnline.getByName(deviceStatus.getStatus()); map.put(key, online); break; } } } return map; } /** * 发送异步消息 * @param topicFullName 主题 * @param messageContent 消息主体 * @param qos 消息类型 0只发送1次 1至少发送一次 * @return 推送响应 * @throws ClientException 客户端异常 */ public PubResponse sendMsgAsync( String topicFullName, String messageContent, int qos) throws ClientException { byte[] bytes = messageContent.getBytes(StandardCharsets.UTF_8); PubRequest request = new PubRequest(); //设置产品Key request.setProductKey(appConfig.getProductKey()); //设置消息内容 request.setMessageContent(Base64.getEncoder().encodeToString(bytes)); //设置主题名 request.setTopicFullName(topicFullName); //设置示例ID request.setIotInstanceId(appConfig.getIotInstanceId()); //设置消息发送类型 request.setQos(Math.min(qos, 1)); //目前支持QoS0和QoS1 return client.getAcsResponse(request); }
注:在基础的MQTT协议下,系统发送给设备的指令都是异步的,所以上面只写了发送异步消息的方法,如果设备端烧录的时候兼容了RRPC模式,那么可以实现同步指令(发送指令以后拿到设备回复,某些场景RRPC非常方便),有兴趣的可以研究:调用RRpc向设备发送请求消息并同步返回响应_物联网平台-阿里云帮助中心
四、以上关于系统-->设备的模块准备就绪以后,我们已经能控制设备基础的生命周期,完成基础的物联网交互;非RRPC模式一下,设备的发送的MQTT协议数据,都只能通过监听MNS/AMQP消息队列的形式获取,所以接下来介绍的就是监听
1、创建一个守护线程,将需要用到的工具类传进去
/** * MNS消息队列监听器 */ @Service @Slf4j public class MnsListener extends Thread{ @Resource private RentboxUtils rentboxUtils; private MnsThread mnsThread; /** * 监听MNS队列消息 */ public void startListen(){ if (mnsThread!=null) return; MnsThread mnsThread = new MnsThread(rentboxUtils); this.mnsThread=mnsThread; mnsThread.setDaemon(true); mnsThread.start(); } }
@Slf4j public class MnsThread extends Thread{ private RentboxUtils rentboxUtils; public MnsThread(RentboxUtils rentboxUtils){ this.rentboxUtils=rentboxUtils; } /** * mns消息队列监听任务 */ @Override public void run(){ super.run(); CloudAccount cloudAccount = new CloudAccount(AliMnsConst.ACCESS_KEY_ID, AliMnsConst.ACCESS_KEY_SECRET,AliMnsConst.MNS_END_POINT); MNSClient client = cloudAccount.getMNSClient(); CloudQueue queue = client.getQueueRef(AliMnsConst.QUEUE_NAME); Message message =null; while (!Thread.interrupted()){ try { message = queue.popMessage(10); if (message!=null){ String messageBodyJson = message.getMessageBodyAsString("UTF-8"); MessageBody messageBody = JSON.parseObject(messageBodyJson, MessageBody.class); switch (messageBody.getMessageType()){ case "upload": //进行设备上报操作 rentboxUtils.handleCmd(messageBody); break; case "status": //进行设备状态变更操作 rentboxUtils.handleStatusChange(messageBody); break; default: log.info("【默认】 status:{}",messageBody.getMessageType()); } }else { System.out.println("continuing"); } }catch (ServiceException e){ e.printStackTrace(); if (e.getErrorCode().equals("MessageNotExist")){ log.error("队列中暂无消息"); }else if (e.getMessage().equals("QueueNotExist")){ log.error("队列不存在"); } if (e.getErrorCode().equals("InternalServerError")) break; }catch (Exception e){ e.printStackTrace(); System.out.println(e.getClass()); }finally { if (queue!=null && message!=null){ try { queue.deleteMessage(message.getReceiptHandle()); //从队列中删除消息。 }catch (Exception e){ e.printStackTrace(); log.error("【删除队列消息异常】 messageId:{}",message.getMessageId()); } } } } }
2、本demo仅监听了设备上报数据和设备状态变更两个处理类型,具体的代码逻辑实现就需要根据自己的项目实际需求去实现;本文仅提供了基本的设备交互处理方法,还有更多地方并没有详细的介绍,对于不想了解阿里IOT而想直接用的人来说已经够了,如果想了解IOT的人来说,建议结合文档理解代码。
Last:阿里OpenApi真的是一个很庞大的体系,我也不敢说全部都能理解,也没办法每个方法步骤都作详细的介绍,只是想帮助到各位在小公司辛苦奋斗又需要接触物联网的码农(大公司这些东西也轮不到我们搭建)。