近两年时常有市场对技术的咨询,关于各种场景下的智能控制,例如农业大棚的水帘风机控制如何实现,抽水泵如何远程操作控制,应急仓库的带电池的设备如何保障电量长期为90%以上以便应急使用,后面都采用了最经济的方案,通过对智能插座的控制与电流采集+业务软件定制开发得以实现,满足了大部分的业务要求,包括对暖通设备的控制,民宿酒店房间用电的控制等,由传统的施工布线变为快速改造即可实现,降低了对现场施工的要求,加快了施工进度。
开发前准备
pom.xml 应用MQTT包
<!-- MQTT -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
智能插座配网连接到GeekOpen云端MQTT服务
配网操作请参考教程 智能插座配网教程
没有设备的朋友,请点击此处点击此处查看参数是否满足现场要求
创建MQTT客户端连接,对智能插座进行管理
MqttService.java
package com.geekopen.iot.demo.service;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Arrays;
/**
* MQTT消息收发服务
*/
@Service
public class MqttService {
private static final Logger log = LoggerFactory.getLogger(MqttService.class);
private static MqttClient client;
/**
*
*/
private static final String CLIENT_ID = "myDemo123";
/**
*
*/
private static final Boolean CLEAN_SESSION = false;
// 以下配置信息为 GeekOpen云端MQTT服务信息
// 可以登录官网 http://www.geek-open.com 获得
// 也可以搭建自己的 MQTT 服务生成
/**
* GeekOpen MQTT服务器地址,固定参数
*/
public static final String HOST = "tcp://mqtt.geek-smart.cn";
/**
* GeekOpen MQTT服务器端口,固定参数
*/
public static final String PORT = "1883";
/**
* 订阅主题
* 订阅GeekOpen智能设备的消息,统一格式为 /${appId}/${deviceKey}/${deviceMAC}/subscribe
* 请更换,登录GeekOpen可查询到设备的订阅主题信息
*/
public static final String TOPIC = "/lsXGEo/hPHBVjJNAzRH/c82b96746f1a/subscribe";
/**
* 用户名
* 请更换,可登录GeekOpen官网查询
*/
private static final String USER_NAME = "hkAhCufcFGzs";
/**
* 密码
* 请更换,可登录GeekOpen官网查询
*/
private static final String PASSWORD = "xNfsGgPQzZvMRjLVnC";
public MqttService() throws MqttException {
// MemoryPersistence设置
client = new MqttClient(HOST+":"+PORT, CLIENT_ID, new MemoryPersistence());
}
@PostConstruct
public void init(){
connect();
}
/**
* 服务端连接MQTT
*/
private void connect() {
log.info("HOST:"+HOST);
log.info("PORT:"+PORT);
log.info("TOPIC:"+TOPIC);
log.info("CLIENT_ID:"+CLIENT_ID);
log.info("USER_NAME:"+USER_NAME);
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(USER_NAME);
options.setPassword(PASSWORD.toCharArray());
// 设置超时时间
options.setConnectionTimeout(20);
// 设置会话心跳时间
options.setKeepAliveInterval(30);
// 重连
options.setAutomaticReconnect(true);
// 设置是否清除会话
options.setCleanSession(CLEAN_SESSION);
try {
client.setCallback(new MessageCallback());
client.connect(options);
} catch (Exception e) {
e.printStackTrace();
}
//订阅消息
subscribe();
}
/**
* 订阅消息
*/
public static void subscribe(){
try{
// 订阅消息topic
String[] topic = TOPIC.split(",");
int[] qos = new int[topic.length];
// 循环将所有主题的Qos设置为1
Arrays.fill(qos, 1);
// subscribe 订阅
client.subscribe(topic, qos);
log.info("GeekOpen --> 已添加订阅,topic:" + TOPIC);
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送消息
* @param message byte
* @param topic 主题
* @param qos qos
*/
public static void sendMessage(byte[] message,String topic,Integer qos){
MqttMessage mess = new MqttMessage();
mess.setQos(qos);
mess.setRetained(false);
mess.setPayload(message);
try {
client.publish(topic, mess);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 消息发送
* @param message 消息体
* @param topic 主题
* @param qos qos
*/
public static void sendMessage(String message,String topic,Integer qos){
MqttMessage mess = new MqttMessage();
mess.setQos(qos);
mess.setRetained(false);
mess.setPayload(message.getBytes());
try {
log.info("GeekOpen --> 服务端发送文本消息到设备,message:" + message);
log.info("GeekOpen --> 服务端发送文本消息到设备,topic:" + topic);
log.info("GeekOpen --> 服务端发送文本消息到设备,qos:" + qos);
// publish 发布
client.publish(topic, mess);
} catch (Exception e) {
e.printStackTrace();
}
}
}
监听智能插座的行为信息,实现自动上报数据
MessageCallback.java
package com.geekopen.iot.demo.service;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* MQTT自定义回调
*/
public class MessageCallback implements MqttCallbackExtended {
private static final Logger log = LoggerFactory.getLogger(MessageCallback.class);
/**
* 服务端连接MQTT成功
* @param b
* @param s
*/
@Override
public void connectComplete(boolean b, String s) {
log.info("GeekOpen --> 连接成功:("+b+")"+s);
//处理自定义业务
//……
}
/**
* 服务端连接MQTT断开
* @param throwable
*/
@Override
public void connectionLost(Throwable throwable) {
log.info("GeekOpen --> 连接断开:"+throwable.getMessage());
//处理自定义业务
//……
}
/**
* subscribe 接收到设备端消息
* @param topic
* @param mqttMessage
* @throws Exception
*/
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
try{
//消息内容mqttMessage由byte转string
log.info("GeekOpen --> 服务端收到设备消息,内容:" + new String(mqttMessage.getPayload()));
log.info("GeekOpen --> 服务端收到设备消息,topic: " + topic);
//处理自定义业务
//……
}catch(Exception e){
e.printStackTrace();
}
}
/**
* publish 发送消息到设备完成
* @param iMqttDeliveryToken
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
try {
//消息内容mqttMessage由byte转string
log.info("GeekOpen --> 服务端发送文本消息到设备完成,消息:" + new String(iMqttDeliveryToken.getMessage().getPayload()));
//处理自定义业务
//……
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
}
开启定时任务,每15秒采集一次用电信息
TimeTask.java
package com.geekopen.iot.demo.service;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
/**
* 定时任务,查询用电信息
*/
@Configuration
@EnableScheduling
public class TimeTask {
/**
* 每15秒采集一次用电信息
*/
@Scheduled(cron = "0/15 * * * * *")
private void statistic()
{
String messageId = "timeTask.statistic";
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"statistic\"}","/lsXGEo/hPHBVjJNAzRH/c82b96746f1a/publish",0);
}
}
创建前端应用 API,各种业务功能实现
MqttController.java
实现restful API,方便前端应用调用,通过发送json指令即可控制插座的通电与断电,更多指令与属性浏览GeekOpen官方文档
package com.geekopen.iot.demo.controller;
import com.geekopen.iot.demo.service.MqttService;
import org.springframework.web.bind.annotation.*;
import java.util.Random;
/**
* MQTT协议提供API调用
*/
@RestController
@RequestMapping("/api/mqtt")
public class MqttController {
private String topic = "/lsXGEo/hPHBVjJNAzRH/c82b96746f1a/publish";
/**
* 智能插座控制 - 通电
* @return
*/
@GetMapping("/open")
@ResponseBody
public String open()
{
String messageId = "myMessageId"+new Random().nextInt(1,100) ;
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"event\",\"key\":0}",topic,0);
return messageId;
}
/**
* 智能插座控制 - 断电
* @return
*/
@GetMapping("/close")
@ResponseBody
public String close()
{
String messageId = "myMessageId"+new Random().nextInt(1,100) ;
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"event\",\"key\":1}",topic,0);
return messageId;
}
/**
* 智能插座用电信息查询 - 电量采集、电压查询、功率监控
* @return
*/
@GetMapping("/statistic")
@ResponseBody
public String statistic()
{
String messageId = "myMessageId"+new Random().nextInt(1,100) ;
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"statistic\"}",topic,0);
return messageId;
}
/**
* 智能插座软重启
* @return
*/
@GetMapping("/restart")
@ResponseBody
public String restart()
{
String messageId = "myMessageId"+new Random().nextInt(1,100) ;
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"setting\",\"system\":\"restart\"}",topic,0);
return messageId;
}
/**
* 智能插座 重置/恢复出厂设置
* @return
*/
@GetMapping("/reset")
@ResponseBody
public String reset()
{
String messageId = "myMessageId"+new Random().nextInt(1,100) ;
MqttService.sendMessage("{\"messageId\":\""+ messageId +"\",\"type\":\"setting\",\"system\":\"reset\"}",topic,0);
return messageId;
}
}