记录一次项目需求中要求使用emqx平台并通过mqtt协议的任务,首先必须安装搭建emqx,官网根据需求安装即可。
本文参考转载于- java连接MQTT服务器(Springboot整合MQTT)_mqtt java-CSDN博客
1、首先springboot项目中引入有关mqtt的 pom文件依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
2、application.properties中配置
## MQTT##
mqtt.host=tcp://xxx.xx.xx.xx:1883
mqtt.clientId=xxxx
mqtt.username=xxxxxxxx
mqtt.password=xxxxxxxx
3、mqtt工具类
/**
* MQTT工具类操作
*
*/
@Slf4j
@Component
public class MQTTConnect {
@Value("${mqtt.host}")
private String HOST;
@Value("${mqtt.clientId}")
private String clientId;
private MqttClient mqttClient;
/**
* 客户端connect连接mqtt服务器
*
* @param username 用户名
* @param password 密码
* @param mqttCallback 回调函数
**/
public void setMqttClient(String username, String password, MqttCallback mqttCallback)
throws MqttException {
MqttConnectOptions options = mqttConnectOptions(username, password);
/*if (mqttCallback == null) {
mqttClient.setCallback(new Callback());
} else {
}*/
mqttClient.setCallback(mqttCallback);
mqttClient.connect(options);
}
/**
* MQTT连接参数设置
*/
private MqttConnectOptions mqttConnectOptions(String userName, String passWord)
throws MqttException {
mqttClient = new MqttClient(HOST, clientId + SnowflakeIdWorker.generateId().toString(), new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(userName);
options.setPassword(passWord.toCharArray());
options.setConnectionTimeout(10);///默认:30
options.setAutomaticReconnect(true);//默认:false
options.setCleanSession(true);//默认:true
//options.setKeepAliveInterval(20);//默认:60
return options;
}
/**
* 关闭MQTT连接
*/
public void close() throws MqttException {
mqttClient.close();
mqttClient.disconnect();
}
/**
* 向某个主题发布消息 默认qos:1
*/
public void pub(String topic, byte[] msg) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(2);
mqttMessage.setPayload(msg);
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 向某个主题发布消息
*
* @param topic: 发布的主题
* @param msg: 发布的消息
* @param qos: 消息质量 Qos:0、1、2
*/
public void pub(String topic, byte[] msg, int qos) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(qos);
mqttMessage.setPayload(msg);
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 订阅某一个主题 ,此方法默认的的Qos等级为:1
*
* @param topic 主题
*/
public void sub(String topic) throws MqttException {
mqttClient.subscribe(topic);
}
/**
* 订阅某一个主题,可携带Qos
*
* @param topic 所要订阅的主题
* @param qos 消息质量:0、1、2
*/
public void sub(String topic, int qos) throws MqttException {
mqttClient.subscribe(topic, qos);
}
}
其中
mqttClient = new MqttClient(HOST, clientId + SnowflakeIdWorker.generateId().toString(), new MemoryPersistence());中使用雪花算法原因是每次生成不一样的clientId,我就是在这里踩了坑,如果使用固定的一个clientId的话,其他地方就不能在使用这个clientId,否则设备会一直断线重连....
4、mqtt回调函数
/**
* MQTT回调函数
*
*/
@Slf4j
@Component
public class InitCallback implements MqttCallback {
@Autowired
private xxxService xxxService;
/**
* MQTT 断开连接会执行此方法
*/
@Override
public void connectionLost(Throwable cause) {
// log.error(cause.getMessage(), cause);
log.info(cause.getMessage());
}
/**
* publish发布成功后会执行到这里
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
log.info("publish已成功发布完毕");
}
/**
* subscribe订阅后得到的消息会执行到这里
*/
@Override
public void messageArrived(String topic, MqttMessage message) {
log.info("[{}] : {}", topic, new String(message.getPayload()));
xxxService.subscribeMessage(topic,message);
}
}
5、mqtt监听器
/**
* 项目启动 监听主题
*
*/
@Slf4j
@Component
public class MQTTListener implements ApplicationListener<ContextRefreshedEvent> {
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
private final MQTTConnect server;
private final InitCallback initCallback;
@Autowired
public MQTTListener(MQTTConnect server, InitCallback initCallback) {
this.server = server;
this.initCallback = initCallback;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
try {
server.setMqttClient(username, password, initCallback);
server.sub("/xxx/+"); //此处定义监听主题
log.info("我是订阅者");
} catch (MqttException e) {
log.error(e.getMessage(), e);
}
}
}
6、业务类
@Service
public class xxxServiceImpl implements xxxService {
@Autowired
private MQTTConnect server;
@Override
public MessageBody test(Dto dto) {
String token = "xxxxxxx";
String instruction = token+GetCRC_MODBUS(token)+"88"; //此处生成的instructon为设备厂商指定的发布命令,组成部分为 发出命令 + 命令的CRC值 + 固定值88,此处为样例可按照具体需求更改
byte[] testBytes = hex2Byte(instruction);
server.pub("/xxx/xxxx",testBytes ,1);
}
}
7、java计算 crc16校验
/*
* 计算CRC16值
* */
public static String GetCRC_MODBUS(String str) {
byte[] bytes = toBytes(str);
int CRC = 0x0000ffff;
int POLYNOMIAL = 0x0000a001;
int i, j;
for (i = 0; i < bytes.length; i++) {
CRC ^= ((int) bytes[i] & 0x000000ff);
for (j = 0; j < 8; j++) {
if ((CRC & 0x00000001) != 0) {
CRC >>= 1;
CRC ^= POLYNOMIAL;
} else {
CRC >>= 1;
}
}
}
String crc = Integer.toHexString(CRC).toLowerCase();
if(crc.length() < 4){
crc = "0" + crc;
}
return crc.substring(2)+crc.substring(0,2);
}
8、将16进制字符转换成byte[]数组方法
public static byte[] hex2Byte(String string) {
if (string == null || string.length() < 1) {
return null;
}
// 因为一个byte生成两个字符,长度对应1:2,所以byte[]数组长度是字符串长度一半
byte[] bytes = new byte[string.length() / 2];
// 遍历byte[]数组,遍历次数是字符串长度一半
for (int i = 0; i < string.length() / 2; i++) {
// 截取没两个字符的前一个,将其转为int数值
int high = Integer.parseInt(string.substring(i * 2, i * 2 + 1), 16);
// 截取没两个字符的后一个,将其转为int数值
int low = Integer.parseInt(string.substring(i * 2 + 1, i * 2 + 2), 16);
// 高位字符对应的int值*16+低位的int值,强转成byte数值即可
// 如dd,高位13*16+低位13=221(强转成byte二进制11011101,对应十进制-35)
bytes[i] = (byte) (high * 16 + low);
}
return bytes;
}