Rework Mqtt Implementation
This commit is contained in:
@@ -8,6 +8,10 @@ services:
|
|||||||
- "8000:8080"
|
- "8000:8080"
|
||||||
- "5005:5005"
|
- "5005:5005"
|
||||||
environment:
|
environment:
|
||||||
|
BROKER_URL: "tcp://api-broker:1883"
|
||||||
|
BROKER_SECURE_URL: "tcp://api-broker:8883"
|
||||||
|
BROKER_USERNAME: "board-mate-api"
|
||||||
|
BROKER_PASSWORD: "hepl"
|
||||||
JWT_SECRET: "enY3OWU4djFyMTByNTZhcG9uY3Z0djQ5cnY0eDhhNWM0bjg5OTRjNDhidA=="
|
JWT_SECRET: "enY3OWU4djFyMTByNTZhcG9uY3Z0djQ5cnY0eDhhNWM0bjg5OTRjNDhidA=="
|
||||||
SSL_KEYSTORE_PATH: "/certs/keystore.p12"
|
SSL_KEYSTORE_PATH: "/certs/keystore.p12"
|
||||||
SPRING_DATA_MONGODB_URI: "mongodb://board-mate-user:apx820kcng@mongodb:27017/board-mate-db"
|
SPRING_DATA_MONGODB_URI: "mongodb://board-mate-user:apx820kcng@mongodb:27017/board-mate-db"
|
||||||
@@ -50,7 +54,7 @@ services:
|
|||||||
|
|
||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:latest
|
image: mongo:latest
|
||||||
container_name: mongo-db
|
container_name: api-database
|
||||||
environment:
|
environment:
|
||||||
- MONGO_INITDB_DATABASE=board-mate-db
|
- MONGO_INITDB_DATABASE=board-mate-db
|
||||||
- MONGO_INITDB_ROOT_PASSWORD=secret
|
- MONGO_INITDB_ROOT_PASSWORD=secret
|
||||||
@@ -63,10 +67,11 @@ services:
|
|||||||
|
|
||||||
mosquitto:
|
mosquitto:
|
||||||
image: eclipse-mosquitto:latest
|
image: eclipse-mosquitto:latest
|
||||||
container_name: mosquitto
|
container_name: api-broker
|
||||||
ports:
|
ports:
|
||||||
- "1883:1883"
|
- "1883:1883"
|
||||||
- "8883:8883"
|
- "8883:8883"
|
||||||
|
command: ["sh", "/mosquitto/config/init-mosquitto.sh"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./mosquitto/config:/mosquitto/config
|
- ./mosquitto/config:/mosquitto/config
|
||||||
- ./mosquitto/data:/mosquitto/data
|
- ./mosquitto/data:/mosquitto/data
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
db = db.getSiblingDB("board-mate-db");
|
db = db.getSiblingDB("board-mate-db");
|
||||||
|
|
||||||
|
db.createCollection("clients");
|
||||||
|
db.createCollection("telemetry");
|
||||||
db.createCollection("games");
|
db.createCollection("games");
|
||||||
|
|
||||||
db.createUser({
|
db.createUser({
|
||||||
|
|||||||
18
api/mosquitto/config/init-mosquitto.sh
Normal file
18
api/mosquitto/config/init-mosquitto.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PASSWORD_FILE=/mosquitto/config/passwords
|
||||||
|
|
||||||
|
echo "Creating password file with pre-registered users..."
|
||||||
|
if [ ! -f "$PASSWORD_FILE" ]; then
|
||||||
|
touch "$PASSWORD_FILE"
|
||||||
|
chmod 600 "$PASSWORD_FILE"
|
||||||
|
|
||||||
|
# Add pre-registered users
|
||||||
|
mosquitto_passwd -b "$PASSWORD_FILE" board-mate-api hepl
|
||||||
|
echo "Password file created !"
|
||||||
|
else
|
||||||
|
echo "Password file exists, skipping this step"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting mosquitto..."
|
||||||
|
exec mosquitto -c /mosquitto/config/mosquitto.conf -v
|
||||||
Binary file not shown.
@@ -183,3 +183,49 @@ To fix this, use `chmod 0700 /mosquitto/config/passwords`.
|
|||||||
1766955294: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
1766955294: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
||||||
1766955496: mosquitto version 2.0.22 terminating
|
1766955496: mosquitto version 2.0.22 terminating
|
||||||
1766955496: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
1766955496: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
||||||
|
1767024228: mosquitto version 2.0.22 starting
|
||||||
|
1767024228: Config loaded from /mosquitto/config/mosquitto.conf.
|
||||||
|
1767024228: Warning: File /mosquitto/config/passwords has world readable permissions. Future versions will refuse to load this file.
|
||||||
|
To fix this, use `chmod 0700 /mosquitto/config/passwords`.
|
||||||
|
1767024228: Opening ipv4 listen socket on port 1883.
|
||||||
|
1767024228: Opening ipv6 listen socket on port 1883.
|
||||||
|
1767024228: Opening websockets listen socket on port 9001.
|
||||||
|
1767024228: Opening ipv4 listen socket on port 8883.
|
||||||
|
1767024228: Opening ipv6 listen socket on port 8883.
|
||||||
|
1767024228: mosquitto version 2.0.22 running
|
||||||
|
1767025666: mosquitto version 2.0.22 terminating
|
||||||
|
1767025666: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
||||||
|
1767025725: mosquitto version 2.0.22 starting
|
||||||
|
1767025725: Config loaded from /mosquitto/config/mosquitto.conf.
|
||||||
|
1767025725: Warning: File /mosquitto/config/passwords has world readable permissions. Future versions will refuse to load this file.
|
||||||
|
To fix this, use `chmod 0700 /mosquitto/config/passwords`.
|
||||||
|
1767025725: Opening ipv4 listen socket on port 1883.
|
||||||
|
1767025725: Opening ipv6 listen socket on port 1883.
|
||||||
|
1767025725: Opening websockets listen socket on port 9001.
|
||||||
|
1767025725: Opening ipv4 listen socket on port 8883.
|
||||||
|
1767025725: Opening ipv6 listen socket on port 8883.
|
||||||
|
1767025725: mosquitto version 2.0.22 running
|
||||||
|
1767027264: mosquitto version 2.0.22 terminating
|
||||||
|
1767027264: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
||||||
|
1767027266: mosquitto version 2.0.22 starting
|
||||||
|
1767027266: Config loaded from /mosquitto/config/mosquitto.conf.
|
||||||
|
1767027266: Warning: File /mosquitto/config/passwords has world readable permissions. Future versions will refuse to load this file.
|
||||||
|
To fix this, use `chmod 0700 /mosquitto/config/passwords`.
|
||||||
|
1767027266: Opening ipv4 listen socket on port 1883.
|
||||||
|
1767027266: Opening ipv6 listen socket on port 1883.
|
||||||
|
1767027266: Opening websockets listen socket on port 9001.
|
||||||
|
1767027266: Opening ipv4 listen socket on port 8883.
|
||||||
|
1767027266: Opening ipv6 listen socket on port 8883.
|
||||||
|
1767027266: mosquitto version 2.0.22 running
|
||||||
|
1767027325: mosquitto version 2.0.22 terminating
|
||||||
|
1767027325: Saving in-memory database to /mosquitto/data//mosquitto.db.
|
||||||
|
1767027327: mosquitto version 2.0.22 starting
|
||||||
|
1767027327: Config loaded from /mosquitto/config/mosquitto.conf.
|
||||||
|
1767027327: Warning: File /mosquitto/config/passwords has world readable permissions. Future versions will refuse to load this file.
|
||||||
|
To fix this, use `chmod 0700 /mosquitto/config/passwords`.
|
||||||
|
1767027327: Opening ipv4 listen socket on port 1883.
|
||||||
|
1767027327: Opening ipv6 listen socket on port 1883.
|
||||||
|
1767027327: Opening websockets listen socket on port 9001.
|
||||||
|
1767027327: Opening ipv4 listen socket on port 8883.
|
||||||
|
1767027327: Opening ipv6 listen socket on port 8883.
|
||||||
|
1767027327: mosquitto version 2.0.22 running
|
||||||
|
|||||||
45
api/src/main/java/MqttStarter.java
Normal file
45
api/src/main/java/MqttStarter.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import be.naaturel.boardmateapi.common.helpers.Logger;
|
||||||
|
import be.naaturel.boardmateapi.services.MqttService;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MqttStarter {
|
||||||
|
|
||||||
|
private final MqttService service;
|
||||||
|
|
||||||
|
public MqttStarter(MqttService service){
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void start(){
|
||||||
|
try {
|
||||||
|
setCallback();
|
||||||
|
service.subscribe("/board-mate/+/telemetry");
|
||||||
|
} catch (Exception e){
|
||||||
|
System.err.println(Arrays.toString(e.getStackTrace()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void stop() {
|
||||||
|
service.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCallback(){
|
||||||
|
service.onConnectionLost((cause) -> {
|
||||||
|
Logger.displayError("Connection lost: " + cause.getMessage());
|
||||||
|
});
|
||||||
|
|
||||||
|
service.onMessageReceived((msg) -> {
|
||||||
|
Logger.displayInfo("Received message on topic " + msg.getTopic() + ": " + msg.getContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
service.registerCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package be.naaturel.boardmateapi.common.models;
|
||||||
|
|
||||||
|
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class MqttMessageWrapper {
|
||||||
|
|
||||||
|
private final String topic;
|
||||||
|
private final MqttMessage message;
|
||||||
|
|
||||||
|
public MqttMessageWrapper(String topic, MqttMessage message){
|
||||||
|
this.topic = topic;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return new String(message.getPayload(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTopic() {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,26 +29,7 @@ public class MqttConfig {
|
|||||||
@Bean("mqttSubscriber")
|
@Bean("mqttSubscriber")
|
||||||
public MqttClient mqttSubscriber() throws MqttException {
|
public MqttClient mqttSubscriber() throws MqttException {
|
||||||
String subscriberId = properties.getClientId() + "-sub";
|
String subscriberId = properties.getClientId() + "-sub";
|
||||||
MqttClient client = new MqttClient(properties.getBrokerUrl(), subscriberId);
|
return new MqttClient(properties.getBrokerUrl(), subscriberId);
|
||||||
|
|
||||||
client.setCallback(new MqttCallback() {
|
|
||||||
@Override
|
|
||||||
public void connectionLost(Throwable cause) {
|
|
||||||
Logger.displayError("Connection lost: " + cause.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageArrived(String topic, MqttMessage message) {
|
|
||||||
Logger.displayInfo("Received message on topic " + topic + ": " + message.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
|
||||||
// Not needed for subscriber
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class MqttProperies {
|
public class MqttProperies {
|
||||||
|
|
||||||
@Value("${mqtt.broker-url}")
|
@Value("${mqtt.plain.broker-url}")
|
||||||
private String brokerUrl;
|
private String brokerUrl;
|
||||||
|
|
||||||
|
@Value("${mqtt.ssl.broker-url}")
|
||||||
|
private String brokerSecureUrl;
|
||||||
|
|
||||||
@Value("${mqtt.client-id}")
|
@Value("${mqtt.client-id}")
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
@@ -18,12 +21,9 @@ public class MqttProperies {
|
|||||||
@Value("${mqtt.password}")
|
@Value("${mqtt.password}")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Value("${mqtt.topic}")
|
|
||||||
private String topic;
|
|
||||||
|
|
||||||
public String getBrokerUrl() { return brokerUrl; }
|
public String getBrokerUrl() { return brokerUrl; }
|
||||||
|
public String getBrokerSecureUrl() { return brokerSecureUrl; }
|
||||||
public String getClientId() { return clientId; }
|
public String getClientId() { return clientId; }
|
||||||
public String getUsername() { return username; }
|
public String getUsername() { return username; }
|
||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
public String getTopic() { return topic; }
|
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ public class BrokerController {
|
|||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/publish/{topic}")
|
/*@PostMapping("/publish/{topic}")
|
||||||
public ResponseEntity<ResponseBody<?>> publish(@PathVariable String topic, @RequestBody String message){
|
public ResponseEntity<ResponseBody<?>> publish(@PathVariable String topic, @RequestBody String message){
|
||||||
ResponseBody<?> body = ResponseBody.createEmpty();
|
ResponseBody<?> body = ResponseBody.createEmpty();
|
||||||
try {
|
try {
|
||||||
@@ -38,6 +38,6 @@ public class BrokerController {
|
|||||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body(body);
|
.body(body);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,21 @@ package be.naaturel.boardmateapi.services;
|
|||||||
|
|
||||||
import be.naaturel.boardmateapi.common.exceptions.ServiceException;
|
import be.naaturel.boardmateapi.common.exceptions.ServiceException;
|
||||||
import be.naaturel.boardmateapi.common.helpers.Logger;
|
import be.naaturel.boardmateapi.common.helpers.Logger;
|
||||||
|
import be.naaturel.boardmateapi.common.models.MqttMessageWrapper;
|
||||||
import org.eclipse.paho.client.mqttv3.*;
|
import org.eclipse.paho.client.mqttv3.*;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MqttService {
|
public class MqttService {
|
||||||
|
|
||||||
private final MqttClient publisher;
|
private final MqttClient publisher;
|
||||||
private final MqttClient subscriber;
|
private final MqttClient subscriber;
|
||||||
|
private Consumer<Throwable> onConnectionLost;
|
||||||
|
private Consumer<MqttMessageWrapper> onMessageReceived;
|
||||||
|
|
||||||
public MqttService(
|
public MqttService(
|
||||||
@Qualifier("mqttPublisher") MqttClient publisher,
|
@Qualifier("mqttPublisher") MqttClient publisher,
|
||||||
@@ -18,43 +24,88 @@ public class MqttService {
|
|||||||
) {
|
) {
|
||||||
this.publisher = publisher;
|
this.publisher = publisher;
|
||||||
this.subscriber = subscriber;
|
this.subscriber = subscriber;
|
||||||
|
this.onConnectionLost = null;
|
||||||
|
this.onMessageReceived = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publish(String topic, String payload) throws ServiceException {
|
public void onConnectionLost(Consumer<Throwable> consumer){
|
||||||
|
this.onConnectionLost = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessageReceived(Consumer<MqttMessageWrapper> consumer){
|
||||||
|
this.onMessageReceived = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
try {
|
try {
|
||||||
MqttMessage message = new MqttMessage(payload.getBytes());
|
disconnect(publisher);
|
||||||
message.setQos(1);
|
disconnect(subscriber);
|
||||||
if(!publisher.isConnected()){
|
} catch (ServiceException e) {
|
||||||
publisher.connect();
|
Logger.displayError("Failed to disconnect MQTT client" + e.getMessage());
|
||||||
}
|
|
||||||
publisher.publish(topic, message);
|
|
||||||
Logger.displayInfo("Published message: " + payload);
|
|
||||||
} catch (MqttException e) {
|
|
||||||
throw new ServiceException("Unable to publish message", e);
|
|
||||||
} finally {
|
|
||||||
try{
|
|
||||||
if (publisher.isConnected()) publisher.disconnect();
|
|
||||||
} catch (MqttException e){
|
|
||||||
Logger.displayError("Failed to disconnect MQTT client: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe(String topic) throws ServiceException {
|
public void publish(String topic, String payload) {
|
||||||
|
try {
|
||||||
|
connect(publisher, null);
|
||||||
|
MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
message.setQos(1);
|
||||||
|
publisher.publish(topic, message);
|
||||||
|
} catch (MqttException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subscribe(String topic) {
|
||||||
try {
|
try {
|
||||||
MqttConnectOptions options = new MqttConnectOptions();
|
MqttConnectOptions options = new MqttConnectOptions();
|
||||||
options.setAutomaticReconnect(true);
|
options.setAutomaticReconnect(true);
|
||||||
options.setCleanSession(true);
|
options.setCleanSession(false);
|
||||||
|
|
||||||
|
connect(subscriber, options);
|
||||||
|
|
||||||
if(!subscriber.isConnected()){
|
|
||||||
subscriber.connect(options);
|
|
||||||
}
|
|
||||||
subscriber.subscribe(topic, 1);
|
subscriber.subscribe(topic, 1);
|
||||||
Logger.displayInfo("Subscribed to topic: " + topic);
|
|
||||||
|
|
||||||
} catch (MqttException e) {
|
} catch (MqttException e) {
|
||||||
throw new ServiceException("Unable to subscribe", e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerCallback(){
|
||||||
|
this.subscriber.setCallback(new MqttCallback() {
|
||||||
|
@Override
|
||||||
|
public void connectionLost(Throwable cause) {
|
||||||
|
onConnectionLost.accept(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageArrived(String topic, MqttMessage message) {
|
||||||
|
MqttMessageWrapper msg = new MqttMessageWrapper(topic, message);
|
||||||
|
onMessageReceived.accept(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||||
|
// Not needed for subscriber
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disconnect(MqttClient client) throws ServiceException {
|
||||||
|
if(client.isConnected()){
|
||||||
|
try {
|
||||||
|
client.disconnect();
|
||||||
|
} catch (MqttException e) {
|
||||||
|
throw new ServiceException("Failed to disconnect the broker", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(MqttClient client, MqttConnectOptions options) throws MqttException {
|
||||||
|
if (client.isConnected()) return;
|
||||||
|
if (options == null) {
|
||||||
|
client.connect();
|
||||||
|
} else {
|
||||||
|
client.connect(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ server.ssl.key-store-type=PKCS12
|
|||||||
server.ssl.key-alias=board-mate-api
|
server.ssl.key-alias=board-mate-api
|
||||||
|
|
||||||
#=============MQTT=============
|
#=============MQTT=============
|
||||||
mqtt.broker-url=tcp://test.mosquitto.org:1883
|
mqtt.plain.broker-url=${BROKER_URL}
|
||||||
mqtt.client-id=board-mate-client
|
mqtt.ssl.broker-url=${BROKER_SECURE_URL}
|
||||||
|
mqtt.client-id=board-mate-api
|
||||||
|
|
||||||
mqtt.topic=board-mate-test/topic
|
mqtt.username=${BROKER_USERNAME}
|
||||||
|
mqtt.password=${BROKER_PASSWORD}
|
||||||
|
|
||||||
mqtt.username=yourUsername
|
|
||||||
mqtt.password=yourPassword
|
|
||||||
#=============METRICS=============
|
#=============METRICS=============
|
||||||
management.endpoint.health.show-details=always
|
management.endpoint.health.show-details=always
|
||||||
management.endpoints.web.exposure.include=*
|
management.endpoints.web.exposure.include=*
|
||||||
|
|||||||
Reference in New Issue
Block a user