├── mosquitto ├── log │ └── mosquitto.log ├── config │ ├── passFile │ └── mosquitto.conf └── docker-compose.yaml ├── mqtt-mobile ├── app │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── themes.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── xml │ │ │ │ │ ├── backup_rules.xml │ │ │ │ │ └── data_extraction_rules.xml │ │ │ │ ├── values-night │ │ │ │ │ └── themes.xml │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ └── drawable │ │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── gucardev │ │ │ │ │ └── mqttmobileclient │ │ │ │ │ ├── LocationUpdater.kt │ │ │ │ │ ├── StateData.java │ │ │ │ │ ├── LocationPermissionManager.kt │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── MqttHandler.kt │ │ │ └── AndroidManifest.xml │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── gucardev │ │ │ │ └── mqttmobileclient │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── gucardev │ │ │ └── mqttmobileclient │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle ├── settings.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── images ├── 1.png ├── docker.png ├── device1.png └── device2.png ├── mqtt-cli-fe ├── src │ ├── res │ │ ├── Constants.js │ │ ├── marker.png │ │ ├── marker2.png │ │ └── exampleData.js │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── reportWebVitals.js │ ├── components │ │ ├── ButtonComponent.jsx │ │ ├── DeviceSelectComponent.jsx │ │ └── MapComponent.jsx │ ├── index.js │ ├── pages │ │ └── Home.jsx │ ├── App.css │ ├── App.js │ └── logo.svg ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore ├── Dockerfile ├── package.json └── README.md ├── backend ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── Dockerfile ├── Dockerfile2 ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── gucardev │ │ │ └── mqttpoc │ │ │ └── MqqtPocApplicationTests.java │ └── main │ │ ├── resources │ │ ├── application-dev.yml │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── gucardev │ │ └── mqttpoc │ │ ├── service │ │ ├── StateService.java │ │ ├── StateCacheService.java │ │ ├── MqttGateway.java │ │ ├── ServerSentEventService.java │ │ └── impl │ │ │ ├── StateCacheServiceImpl.java │ │ │ ├── MqttMessageHandler.java │ │ │ ├── StateServiceImpl.java │ │ │ └── ServerSentEventServiceImp.java │ │ ├── MqqtPocApplication.java │ │ ├── repository │ │ ├── StateDataCacheRepository.java │ │ └── StateDataRepository.java │ │ ├── config │ │ ├── WebConfig.java │ │ ├── BeanConfig.java │ │ ├── RedisConfig.java │ │ └── MqttServerConfig.java │ │ ├── model │ │ ├── StateData.java │ │ └── StateDataCache.java │ │ └── controller │ │ ├── MqttController.java │ │ └── StateDataController.java ├── Dockerfile3 ├── pom.xml ├── mvnw.cmd └── mvnw ├── nginx.conf ├── docker-compose.yml ├── readme.md ├── doc.txt └── .gitignore /mosquitto/log/mosquitto.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mqtt-mobile/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/images/1.png -------------------------------------------------------------------------------- /images/docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/images/docker.png -------------------------------------------------------------------------------- /images/device1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/images/device1.png -------------------------------------------------------------------------------- /images/device2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/images/device2.png -------------------------------------------------------------------------------- /mqtt-cli-fe/src/res/Constants.js: -------------------------------------------------------------------------------- 1 | export const BASE_URL = process.env.REACT_APP_API_BASE_URL; 2 | -------------------------------------------------------------------------------- /mqtt-cli-fe/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /mqtt-cli-fe/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-cli-fe/public/favicon.ico -------------------------------------------------------------------------------- /mqtt-cli-fe/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-cli-fe/public/logo192.png -------------------------------------------------------------------------------- /mqtt-cli-fe/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-cli-fe/public/logo512.png -------------------------------------------------------------------------------- /mqtt-cli-fe/src/res/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-cli-fe/src/res/marker.png -------------------------------------------------------------------------------- /mqtt-cli-fe/src/res/marker2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-cli-fe/src/res/marker2.png -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MqttMobileClient 3 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/backend/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /mosquitto/config/passFile: -------------------------------------------------------------------------------- 1 | admin:$7$101$H1/H/HwtrXoe+tws$sAAsYAISIC2RMHfh96IsnwBgbnupNbZxzhzWqGcMwCltH4s94+yp0xSFZb8P2D/1D4uml44UKQ2Jwho5B19ByQ== 2 | -------------------------------------------------------------------------------- /mqtt-mobile/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurkanucar/mqtt-example/HEAD/mqtt-mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | ARG JAR_FILE=target/mqtt-poc-0.0.1-SNAPSHOT.jar 3 | ADD ${JAR_FILE} mqtt-poc-0.0.1-SNAPSHOT.jar 4 | EXPOSE 8081 5 | ENTRYPOINT ["java", "-jar" , "mqtt-poc-0.0.1-SNAPSHOT.jar"] -------------------------------------------------------------------------------- /mosquitto/config/mosquitto.conf: -------------------------------------------------------------------------------- 1 | listener 1883 2 | persistence true 3 | persistence_location /mosquitto/data/ 4 | log_dest file /mosquitto/log/mosquitto.log 5 | password_file /mosquitto/config/passFile 6 | allow_anonymous false 7 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /mqtt-mobile/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 29 03:34:46 TRT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /backend/Dockerfile2: -------------------------------------------------------------------------------- 1 | #official docker website https://docs.docker.com/language/java/build-images/ 2 | 3 | FROM openjdk:17 4 | 5 | WORKDIR /app 6 | 7 | COPY .mvn/ .mvn 8 | COPY mvnw pom.xml ./ 9 | RUN ./mvnw dependency:resolve 10 | 11 | COPY src ./src 12 | 13 | CMD ["./mvnw", "spring-boot:run"] 14 | -------------------------------------------------------------------------------- /mqtt-mobile/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /mqtt-mobile/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '7.3.1' apply false 4 | id 'com.android.library' version '7.3.1' apply false 5 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false 6 | } -------------------------------------------------------------------------------- /backend/src/test/java/com/gucardev/mqttpoc/MqqtPocApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MqqtPocApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mosquitto/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mosquitto: 4 | image: eclipse-mosquitto 5 | ports: 6 | - "1883:1883" 7 | - "9001:9001" 8 | volumes: 9 | - ./config/passFile:/mosquitto/config/passFile 10 | - ./config/mosquitto.conf:/mosquitto/config/mosquitto.conf 11 | - ./log:/mosquitto/log 12 | -------------------------------------------------------------------------------- /backend/Dockerfile3: -------------------------------------------------------------------------------- 1 | # Build stage 2 | FROM maven:3.6.3-openjdk-17 AS build 3 | COPY . /app 4 | WORKDIR /app 5 | RUN mvn package 6 | 7 | # Package stage 8 | FROM openjdk:17 9 | ARG JAR_FILE=target/mqtt-poc-0.0.1-SNAPSHOT.jar 10 | COPY --from=build /app/${JAR_FILE} mqtt-poc-0.0.1-SNAPSHOT.jar 11 | EXPOSE 8080 12 | ENTRYPOINT ["java", "-jar" , "mqtt-poc-0.0.1-SNAPSHOT.jar"] 13 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | data: 5 | redis: 6 | host: localhost 7 | port: 6379 8 | h2: 9 | console: 10 | enabled: true 11 | settings: 12 | web-allow-others: true 13 | datasource: 14 | url: jdbc:h2:mem:database 15 | 16 | mqqt-config: 17 | server-uri: tcp://localhost:1883 -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/StateService.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import java.util.List; 5 | 6 | public interface StateService { 7 | 8 | StateData save(StateData stateData); 9 | 10 | List getListByDeviceId(String id); 11 | 12 | List getDevices(); 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | spring: 4 | data: 5 | redis: 6 | host: ${SPRING_REDIS_HOST} 7 | port: 6379 8 | h2: 9 | console: 10 | enabled: true 11 | settings: 12 | web-allow-others: true 13 | datasource: 14 | url: jdbc:h2:mem:database 15 | 16 | mqqt-config: 17 | server-uri: tcp://mosquitto:1883 -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/StateCacheService.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service; 2 | 3 | import com.gucardev.mqttpoc.model.StateDataCache; 4 | import java.util.List; 5 | 6 | public interface StateCacheService { 7 | 8 | void addToCache(StateDataCache stateDataCache); 9 | 10 | void deleteAll(String deviceId); 11 | 12 | List retrieveDataByDeviceId(String deviceId); 13 | } 14 | -------------------------------------------------------------------------------- /mqtt-mobile/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "MqttMobileClient" 16 | include ':app' 17 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/MqqtPocApplication.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MqqtPocApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MqqtPocApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mqtt-cli-fe/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/components/ButtonComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const ButtonComponent = (props) => { 4 | const { value, onClick } = props; 5 | 6 | return ( 7 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/test/java/com/gucardev/mqttmobileclient/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/MqttGateway.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service; 2 | 3 | import org.springframework.integration.annotation.MessagingGateway; 4 | import org.springframework.integration.mqtt.support.MqttHeaders; 5 | import org.springframework.messaging.handler.annotation.Header; 6 | 7 | @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel") 8 | public interface MqttGateway { 9 | 10 | void senToMqtt(String data, @Header(MqttHeaders.TOPIC) String topic); 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/repository/StateDataCacheRepository.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.repository; 2 | 3 | import com.gucardev.mqttpoc.model.StateDataCache; 4 | import java.util.List; 5 | import org.springframework.data.repository.CrudRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface StateDataCacheRepository extends CrudRepository { 10 | 11 | List findAllByDeviceId(String deviceId); 12 | } 13 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | upstream fe { 2 | server fe:3000; 3 | } 4 | 5 | upstream app { 6 | server app:8081; 7 | } 8 | 9 | server { 10 | listen 80; 11 | 12 | location / { 13 | proxy_pass http://fe; 14 | } 15 | 16 | location /sockjs-node { 17 | proxy_pass http://fe; 18 | proxy_http_version 1.1; 19 | proxy_set_header Upgrade $http_upgrade; 20 | proxy_set_header Connection "Upgrade"; 21 | } 22 | 23 | location /app { 24 | rewrite /app/(.*) /$1 break; 25 | proxy_pass http://app; 26 | } 27 | } -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/ServerSentEventService.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import com.gucardev.mqttpoc.model.StateDataCache; 5 | import java.util.List; 6 | import org.springframework.http.codec.ServerSentEvent; 7 | import reactor.core.publisher.Flux; 8 | 9 | public interface ServerSentEventService { 10 | 11 | Flux>> getStateDataByDeviceId(String id); 12 | 13 | Flux>> getStateCacheDataByDeviceId(String id); 14 | } 15 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/repository/StateDataRepository.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.repository; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import java.util.List; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface StateDataRepository extends JpaRepository { 11 | 12 | @Query(nativeQuery = true, value = "select device_id from STATE_DATA group by device_id") 13 | List retrieveDeviceNames(); 14 | } 15 | -------------------------------------------------------------------------------- /mqtt-cli-fe/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class WebConfig implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void addCorsMappings(CorsRegistry registry) { 12 | registry 13 | .addMapping("/**") 14 | .allowedOrigins("*") 15 | .allowedMethods("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mqtt-cli-fe/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a node image as the base image 2 | FROM node:14 3 | 4 | # Set the working directory in the container to /app 5 | WORKDIR /app 6 | 7 | # Copy the package.json and package-lock.json to the container 8 | COPY package*.json ./ 9 | 10 | # Install the dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the app files to the container 14 | COPY . . 15 | 16 | # Install react-scripts globally 17 | RUN npm install -g react-scripts 18 | 19 | # Build the React app 20 | RUN npm run build 21 | 22 | # Expose port 3000 for incoming traffic to the container 23 | EXPOSE 3000 24 | 25 | # Start the React app 26 | CMD [ "npm", "run", "start" ] 27 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/model/StateData.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.model; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Entity 17 | public class StateData { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | 23 | private String deviceId; 24 | private String topic; 25 | private String message; 26 | private String lat; 27 | private String lon; 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/config/BeanConfig.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.config; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.modelmapper.ModelMapper; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class BeanConfig { 11 | 12 | @Bean 13 | public ModelMapper modelMapper() { 14 | return new ModelMapper(); 15 | } 16 | 17 | @Bean 18 | public ObjectMapper objectMapper() { 19 | ObjectMapper mapper = new ObjectMapper(); 20 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 21 | return mapper; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/components/DeviceSelectComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DeviceSelectComponent = (props) => { 4 | const { selectDevice, deviceIds } = props; 5 | 6 | return ( 7 |
8 | 27 |
28 | ); 29 | }; 30 | 31 | export default DeviceSelectComponent; 32 | -------------------------------------------------------------------------------- /mqtt-mobile/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /mqtt-mobile/app/src/androidTest/java/com/gucardev/mqttmobileclient/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.gucardev.mqttmobileclient", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/java/com/gucardev/mqttmobileclient/LocationUpdater.kt: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.content.Context.LOCATION_SERVICE 7 | import android.location.Location 8 | import android.location.LocationManager 9 | 10 | class LocationUpdater(context: Context, private val locationChangeHandler: (Location) -> Unit) { 11 | 12 | private val mGpsLocationClient: LocationManager = 13 | context.getSystemService(LOCATION_SERVICE) as LocationManager 14 | 15 | @SuppressLint("MissingPermission") 16 | fun start() { 17 | mGpsLocationClient.requestLocationUpdates( 18 | LocationManager.GPS_PROVIDER, 19 | 2000L, 20 | 1f, 21 | locationListener 22 | 23 | ) 24 | } 25 | 26 | private val locationListener = android.location.LocationListener { location -> 27 | locationChangeHandler(location) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/model/StateDataCache.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.model; 2 | 3 | import jakarta.persistence.Id; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.data.redis.core.RedisHash; 10 | import org.springframework.data.redis.core.index.Indexed; 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @RedisHash("StateData") 17 | public class StateDataCache implements Serializable { 18 | @Indexed 19 | @Id String id; 20 | 21 | @Indexed private String deviceId; 22 | private String topic; 23 | private String message; 24 | private String lat; 25 | private String lon; 26 | 27 | public StateDataCache(StateData stateData) { 28 | this.deviceId = stateData.getDeviceId(); 29 | this.topic = stateData.getTopic(); 30 | this.message = stateData.getMessage(); 31 | this.lat = stateData.getLat(); 32 | this.lon = stateData.getLon(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { MapComponent } from "../components/MapComponent"; 3 | import { BASE_URL } from "../res/Constants"; 4 | import { exampleData } from "../res/exampleData"; 5 | 6 | export const Home = (props) => { 7 | const { initialData, selectedDevice } = props; 8 | const [incomingData, setIncomingData] = useState(initialData); 9 | 10 | useEffect(() => { 11 | console.log(incomingData.length); 12 | }, [incomingData]); 13 | 14 | useEffect(() => { 15 | const sse = new EventSource(`${BASE_URL}/data/cached/${selectedDevice}`); 16 | sse.addEventListener("states-list-event", (event) => { 17 | const data = JSON.parse(event.data); 18 | if (data.length > 0) { 19 | setIncomingData((prevData) => [...prevData, ...data]); 20 | } 21 | }); 22 | 23 | sse.onerror = () => { 24 | sse.close(); 25 | }; 26 | return () => { 27 | sse.close(); 28 | }; 29 | }, []); 30 | 31 | return ( 32 |
33 |

Track Location

34 | 35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | 17 | .map { 18 | height: 100%; 19 | width: 100%; 20 | } 21 | 22 | .mapMainContainer { 23 | display: flex; 24 | flex-direction: column; 25 | height: 500px; 26 | width: 80%; 27 | margin: auto; 28 | } 29 | 30 | .buttonWrapper { 31 | display: flex; 32 | padding-bottom: 10px; 33 | flex-direction: row; 34 | justify-content: flex-start; 35 | } 36 | 37 | .backButton { 38 | height: 40px; 39 | font-size: 14px; 40 | } 41 | 42 | .App-header { 43 | background-color: #282c34; 44 | min-height: 100vh; 45 | display: flex; 46 | flex-direction: column; 47 | align-items: center; 48 | justify-content: center; 49 | font-size: calc(10px + 2vmin); 50 | color: white; 51 | } 52 | 53 | .App-link { 54 | color: #61dafb; 55 | } 56 | 57 | @keyframes App-logo-spin { 58 | from { 59 | transform: rotate(0deg); 60 | } 61 | to { 62 | transform: rotate(360deg); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mqtt-cli-fe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-cli-fe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/react": "^18.0.27", 10 | "@types/react-dom": "^18.0.10", 11 | "env-cmd": "^10.1.0", 12 | "path-key": "^3.0.0", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-leaflet": "^4.0.2", 16 | "leaflet": "^1.8.0", 17 | "react-scripts": "5.0.1", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/impl/StateCacheServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service.impl; 2 | 3 | import com.gucardev.mqttpoc.model.StateDataCache; 4 | import com.gucardev.mqttpoc.repository.StateDataCacheRepository; 5 | import com.gucardev.mqttpoc.service.StateCacheService; 6 | import java.util.List; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @Slf4j 12 | public class StateCacheServiceImpl implements StateCacheService { 13 | 14 | private final StateDataCacheRepository repository; 15 | 16 | public StateCacheServiceImpl(StateDataCacheRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | @Override 21 | public void addToCache(StateDataCache stateDataCache) { 22 | repository.save(stateDataCache); 23 | } 24 | 25 | @Override 26 | public List retrieveDataByDeviceId(String deviceId) { 27 | var stateData = repository.findAllByDeviceId(deviceId); 28 | deleteAll(deviceId); 29 | return stateData; 30 | } 31 | 32 | @Override 33 | public void deleteAll(String deviceId) { 34 | repository.deleteAll(repository.findAllByDeviceId(deviceId)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/controller/MqttController.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.gucardev.mqttpoc.model.StateData; 5 | import com.gucardev.mqttpoc.service.MqttGateway; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class MqttController { 13 | 14 | private final MqttGateway mqtGateway; 15 | private final ObjectMapper objectMapper; 16 | 17 | public MqttController(MqttGateway mqtGateway, ObjectMapper objectMapper) { 18 | this.mqtGateway = mqtGateway; 19 | this.objectMapper = objectMapper; 20 | } 21 | 22 | @PostMapping("/sendMessage") 23 | public ResponseEntity publish(@RequestBody StateData mqttMessage) { 24 | try { 25 | mqtGateway.senToMqtt(objectMapper.writeValueAsString(mqttMessage), mqttMessage.getTopic()); 26 | return ResponseEntity.ok("Success"); 27 | } catch (Exception ex) { 28 | ex.printStackTrace(); 29 | return ResponseEntity.ok("fail"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 7 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 10 | 11 | @Configuration 12 | @EnableRedisRepositories 13 | public class RedisConfig { 14 | 15 | @Value("${spring.data.redis.host}") 16 | private String redisHost; 17 | 18 | @Value("${spring.data.redis.port}") 19 | private int redisPort; 20 | 21 | @Bean 22 | public LettuceConnectionFactory redisConnectionFactory() { 23 | return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort)); 24 | } 25 | 26 | @Bean 27 | public RedisTemplate redisTemplate() { 28 | RedisTemplate template = new RedisTemplate<>(); 29 | template.setConnectionFactory(redisConnectionFactory()); 30 | return template; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/impl/MqttMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service.impl; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.gucardev.mqttpoc.model.StateData; 5 | import com.gucardev.mqttpoc.service.StateService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.integration.mqtt.support.MqttHeaders; 8 | import org.springframework.messaging.Message; 9 | import org.springframework.messaging.MessageHandler; 10 | import org.springframework.messaging.MessagingException; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | @Slf4j 15 | public class MqttMessageHandler implements MessageHandler { 16 | 17 | private final StateService stateService; 18 | private final ObjectMapper mapper; 19 | 20 | public MqttMessageHandler(StateService stateService, ObjectMapper mapper) { 21 | this.stateService = stateService; 22 | this.mapper = mapper; 23 | } 24 | 25 | @Override 26 | public void handleMessage(Message message) throws MessagingException { 27 | try { 28 | String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); 29 | var rawData = message.getPayload().toString(); 30 | StateData myMessage = mapper.readValue(rawData, StateData.class); 31 | stateService.save(myMessage); 32 | log.info(myMessage.toString()); 33 | } catch (Exception e) { 34 | log.error("something went wrong!"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mqtt-mobile/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.enableJetifier=true -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/java/com/gucardev/mqttmobileclient/StateData.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient; 2 | 3 | 4 | public class StateData { 5 | private Long id; 6 | private String deviceId; 7 | private String topic; 8 | private String message; 9 | private String lat; 10 | private String lon; 11 | 12 | public StateData( String deviceId, String topic, String message, String lat, String lon) { 13 | this.deviceId = deviceId; 14 | this.topic = topic; 15 | this.message = message; 16 | this.lat = lat; 17 | this.lon = lon; 18 | } 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public void setId(Long id) { 25 | this.id = id; 26 | } 27 | 28 | public String getDeviceId() { 29 | return deviceId; 30 | } 31 | 32 | public void setDeviceId(String deviceId) { 33 | this.deviceId = deviceId; 34 | } 35 | 36 | public String getTopic() { 37 | return topic; 38 | } 39 | 40 | public void setTopic(String topic) { 41 | this.topic = topic; 42 | } 43 | 44 | public String getMessage() { 45 | return message; 46 | } 47 | 48 | public void setMessage(String message) { 49 | this.message = message; 50 | } 51 | 52 | public String getLat() { 53 | return lat; 54 | } 55 | 56 | public void setLat(String lat) { 57 | this.lat = lat; 58 | } 59 | 60 | public String getLon() { 61 | return lon; 62 | } 63 | 64 | public void setLon(String lon) { 65 | this.lon = lon; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | redisdb: 5 | image: "redis" 6 | ports: 7 | - "6379:6379" 8 | environment: 9 | - ALLOW_EMPTY_PASSWORD=yes 10 | networks: 11 | - shared-net 12 | app: 13 | build: 14 | context: ./backend/ 15 | dockerfile: Dockerfile 16 | ports: 17 | - "8081:8081" 18 | environment: 19 | SPRING_REDIS_HOST: redisdb 20 | depends_on: 21 | - redisdb 22 | networks: 23 | - shared-net 24 | 25 | mosquitto: 26 | image: eclipse-mosquitto 27 | ports: 28 | - "1883:1883" 29 | - "9001:9001" 30 | volumes: 31 | - ./mosquitto/config/passFile:/mosquitto/config/passFile 32 | - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf 33 | #- ./mosquitto/log:/mosquitto/log 34 | - ~/mosquitto/log:/mosquitto/log 35 | networks: 36 | - shared-net 37 | 38 | fe: 39 | build: 40 | context: ./mqtt-cli-fe/ 41 | dockerfile: Dockerfile 42 | volumes: 43 | - './mqtt-cli-fe/:/app' 44 | ports: 45 | - 3000:3000 46 | environment: 47 | - REACT_APP_API_BASE_URL=http://${EC2_IP:-localhost}:8081 48 | networks: 49 | - shared-net 50 | nginx: 51 | image: nginx:alpine 52 | ports: 53 | - 80:80 54 | volumes: 55 | - ./nginx.conf:/etc/nginx/conf.d/default.conf 56 | depends_on: 57 | - fe 58 | - app 59 | networks: 60 | - shared-net 61 | 62 | 63 | 64 | networks: 65 | shared-net: 66 | driver: bridge 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/impl/StateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service.impl; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import com.gucardev.mqttpoc.model.StateDataCache; 5 | import com.gucardev.mqttpoc.repository.StateDataRepository; 6 | import com.gucardev.mqttpoc.service.StateCacheService; 7 | import com.gucardev.mqttpoc.service.StateService; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class StateServiceImpl implements StateService { 14 | 15 | private final StateDataRepository stateDataRepository; 16 | private final StateCacheService stateCacheService; 17 | 18 | public StateServiceImpl( 19 | StateDataRepository stateDataRepository, StateCacheService stateCacheService) { 20 | this.stateDataRepository = stateDataRepository; 21 | this.stateCacheService = stateCacheService; 22 | } 23 | 24 | public StateData save(StateData data) { 25 | var saved = stateDataRepository.save(data); 26 | stateCacheService.addToCache(new StateDataCache(saved)); 27 | return saved; 28 | } 29 | 30 | public List getListByDeviceId(String deviceId) { 31 | stateCacheService.deleteAll(deviceId); 32 | return stateDataRepository.findAll().stream() 33 | .filter(x -> x.getDeviceId().equals(deviceId)) 34 | .collect(Collectors.toList()); 35 | } 36 | 37 | @Override 38 | public List getDevices() { 39 | return stateDataRepository.retrieveDeviceNames(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mosquitto MQTT - Spring Boot & Android & React JS Location Tracking App 2 | 3 | #### The Project consists of 3 parts: 4 | 5 | - Backend: Java Spring Boot 6 | - Frontend: React JS 7 | - Mobile: Android Kotlin 8 | 9 | #### Technologies 10 | 11 | - Spring Boot 12 | - Server Sent Events 13 | - Mosquitto MQTT Server 14 | - Redis 15 | 16 | 17 | ### How to run ~project is completely dockerized 18 | 19 | #### backend will work on port 8081 in docker and 8080 in localhost! 20 | 21 | #### clone the project: https://github.com/gurkanucar/mqtt-example 22 | 23 | ```bash 24 | git clone https://github.com/gurkanucar/mqtt-example 25 | ``` 26 | 27 | #### Please firstly run this maven command to get jar file: 28 | 29 | ```bash 30 | cd backend 31 | mvn clean package -DskipTests 32 | ``` 33 | 34 | ```bash 35 | cd .. 36 | ``` 37 | 38 | 39 | #### if you want to run it on local 40 | 41 | ```bash 42 | docker-compose up --build --force-recreate -d 43 | ``` 44 | 45 | #### if you want to run it on server give your ip address as arg for frontend 46 | 47 | ```bash 48 | EC2_IP= docker-compose up --build --force-recreate -d 49 | ``` 50 | 51 | 52 | #### You can access the frontend from http://localhost:3000 53 | 54 | #### ! Don't forget to update the backend address in the mobile application. 55 | 56 | 57 | ## Example Video 58 | 59 | [https://youtu.be/WXTXZTauM7A](https://youtu.be/WXTXZTauM7A) 60 | 61 | 62 | ## Example Images 63 | 64 | ![image](./images/docker.png) 65 | 66 | ![image](./images/1.png) 67 | 68 | ![image](./images/device1.png) 69 | 70 | ![image](./images/device2.png) 71 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/controller/StateDataController.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.controller; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import com.gucardev.mqttpoc.model.StateDataCache; 5 | import com.gucardev.mqttpoc.service.ServerSentEventService; 6 | import com.gucardev.mqttpoc.service.StateService; 7 | import java.util.List; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.http.codec.ServerSentEvent; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import reactor.core.publisher.Flux; 15 | 16 | @RestController 17 | @RequestMapping("/data") 18 | @RequiredArgsConstructor 19 | public class StateDataController { 20 | 21 | private final StateService stateService; 22 | private final ServerSentEventService eventService; 23 | 24 | @GetMapping("/devices") 25 | public List getDevices() { 26 | return stateService.getDevices(); 27 | } 28 | 29 | @GetMapping("/stored-data/{clientId}") 30 | public List getStoredData(@PathVariable String clientId) { 31 | return stateService.getListByDeviceId(clientId); 32 | } 33 | 34 | @GetMapping("/{clientId}") 35 | public Flux>> streamLastMessage(@PathVariable String clientId) { 36 | return eventService.getStateDataByDeviceId(clientId); 37 | } 38 | 39 | @GetMapping("/cached/{clientId}") 40 | public Flux>> streamCachedData( 41 | @PathVariable String clientId) { 42 | return eventService.getStateCacheDataByDeviceId(clientId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mqtt-mobile/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.gucardev.mqttmobileclient' 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | applicationId "com.gucardev.mqttmobileclient" 12 | minSdk 21 13 | targetSdk 32 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | buildFeatures { 34 | viewBinding true 35 | } 36 | } 37 | 38 | dependencies { 39 | 40 | implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.4' 41 | implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' 42 | implementation 'com.google.code.gson:gson:2.8.5' 43 | 44 | implementation 'androidx.core:core-ktx:1.7.0' 45 | implementation 'androidx.appcompat:appcompat:1.5.1' 46 | implementation 'com.google.android.material:material:1.7.0' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 48 | implementation 'com.google.android.gms:play-services-location:21.0.1' 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.4' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' 52 | } -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/java/com/gucardev/mqttmobileclient/LocationPermissionManager.kt: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.pm.PackageManager 6 | import androidx.core.app.ActivityCompat 7 | import androidx.core.content.ContextCompat 8 | 9 | class LocationPermissionManager(private val activity: Activity) { 10 | 11 | private val LOCATION_PERMISSION_REQUEST_CODE = 1 12 | 13 | fun requestLocationPermission() { 14 | if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) 15 | != PackageManager.PERMISSION_GRANTED) { 16 | 17 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, 18 | Manifest.permission.ACCESS_FINE_LOCATION)) { 19 | // Show an explanation to the user 20 | } else { 21 | // No explanation needed, we can request the permission 22 | ActivityCompat.requestPermissions(activity, 23 | arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 24 | LOCATION_PERMISSION_REQUEST_CODE) 25 | } 26 | } else { 27 | // Permission has already been granted 28 | } 29 | } 30 | 31 | fun onRequestPermissionsResult(requestCode: Int, 32 | permissions: Array, grantResults: IntArray) { 33 | when (requestCode) { 34 | LOCATION_PERMISSION_REQUEST_CODE -> { 35 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 36 | // permission was granted 37 | } else { 38 | // permission denied 39 | } 40 | return 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /mqtt-cli-fe/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/service/impl/ServerSentEventServiceImp.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.service.impl; 2 | 3 | import com.gucardev.mqttpoc.model.StateData; 4 | import com.gucardev.mqttpoc.model.StateDataCache; 5 | import com.gucardev.mqttpoc.service.ServerSentEventService; 6 | import com.gucardev.mqttpoc.service.StateCacheService; 7 | import com.gucardev.mqttpoc.service.StateService; 8 | import java.time.Duration; 9 | import java.util.List; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.http.codec.ServerSentEvent; 12 | import org.springframework.stereotype.Service; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.scheduler.Schedulers; 15 | 16 | @Service 17 | @Slf4j 18 | public class ServerSentEventServiceImp implements ServerSentEventService { 19 | 20 | private final StateService stateService; 21 | private final StateCacheService stateCacheService; 22 | 23 | public ServerSentEventServiceImp(StateService stateService, StateCacheService stateCacheService) { 24 | this.stateService = stateService; 25 | this.stateCacheService = stateCacheService; 26 | } 27 | 28 | public Flux>> getStateCacheDataByDeviceId(String id) { 29 | return Flux.interval(Duration.ofSeconds(1)) 30 | .publishOn(Schedulers.boundedElastic()) 31 | .map( 32 | sequence -> 33 | ServerSentEvent.>builder() 34 | .id(String.valueOf(sequence)) 35 | .event("states-list-event") 36 | .data(stateCacheService.retrieveDataByDeviceId(id)) 37 | .build()); 38 | } 39 | 40 | public Flux>> getStateDataByDeviceId(String id) { 41 | return Flux.interval(Duration.ofSeconds(1)) 42 | .publishOn(Schedulers.boundedElastic()) 43 | .map( 44 | sequence -> 45 | ServerSentEvent.>builder() 46 | .id(String.valueOf(sequence)) 47 | .event("states-list-event") 48 | .data(stateService.getListByDeviceId(id)) 49 | .build()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from "./logo.svg"; 2 | import "./App.css"; 3 | import { Home } from "./pages/Home"; 4 | import { useEffect, useState } from "react"; 5 | import { BASE_URL } from "./res/Constants"; 6 | import DeviceSelectComponent from "./components/DeviceSelectComponent"; 7 | import { ButtonComponent } from "./components/ButtonComponent"; 8 | 9 | function App() { 10 | const [initialData, setInitialData] = useState(); 11 | const [deviceIds, setDeviceIds] = useState([]); 12 | const [selectedDevice, setSelectedDevice] = useState(); 13 | const [deviceSelected, setdeviceSelected] = useState(false); 14 | 15 | const fetchDeviceIds = () => { 16 | console.log("Base Url:", BASE_URL); 17 | fetch(`${BASE_URL}/data/devices`).then((response) => { 18 | return response 19 | .json() 20 | .then((data) => { 21 | setDeviceIds(data); 22 | }) 23 | .catch((err) => { 24 | console.log(err); 25 | }); 26 | }); 27 | }; 28 | 29 | const fetchInitialData = () => { 30 | console.log("Base Url:", BASE_URL); 31 | fetch(`${BASE_URL}/data/stored-data/${selectedDevice}`).then((response) => { 32 | return response 33 | .json() 34 | .then((data) => { 35 | setInitialData(data); 36 | }) 37 | .catch((err) => { 38 | console.log(err); 39 | }); 40 | }); 41 | }; 42 | 43 | useEffect(() => { 44 | if (deviceSelected == true) { 45 | fetchInitialData(); 46 | } else { 47 | setSelectedDevice(""); 48 | } 49 | }, [deviceSelected]); 50 | 51 | useEffect(() => { 52 | fetchDeviceIds(); 53 | }, []); 54 | 55 | return ( 56 |
57 | {!deviceSelected && ( 58 | setSelectedDevice(e.target.value)} 61 | /> 62 | )} 63 | { 65 | if (selectedDevice != "" && selectedDevice != undefined) { 66 | setdeviceSelected(!deviceSelected); 67 | } 68 | }} 69 | value={deviceSelected ? "Select another" : "View Location"} 70 | /> 71 | {deviceSelected == true && initialData != undefined && ( 72 | 73 | )} 74 |
75 | ); 76 | } 77 | 78 | export default App; 79 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/components/MapComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import "leaflet/dist/leaflet.css"; 3 | import L from "leaflet"; 4 | import marker from "../res/marker2.png"; 5 | import { 6 | CircleMarker, 7 | MapContainer, 8 | Marker, 9 | Polyline, 10 | Popup, 11 | TileLayer, 12 | } from "react-leaflet"; 13 | 14 | export const MapComponent = (props) => { 15 | const { markers } = props; 16 | 17 | const myIcon = new L.Icon({ 18 | iconUrl: marker, 19 | iconRetinaUrl: marker, 20 | popupAnchor: [-0, -0], 21 | iconSize: [48, 48], 22 | }); 23 | 24 | const renderPositions = (positions) => { 25 | return ( 26 | <> 27 | 28 | {positions.map((position, index) => ( 29 | 36 | 37 | device: {position.deviceId}
38 | lat: {position.lat}
39 | lon: {position.lon}
40 |
41 |
42 | ))} 43 | 44 | ); 45 | }; 46 | 47 | const putLastLocation = (markers) => { 48 | if (markers != undefined && markers.length != 0) { 49 | const location = markers[markers.length - 1]; 50 | return ( 51 | <> 52 | 57 | 58 | Current Location 59 | 60 | 61 | 62 | ); 63 | } 64 | }; 65 | 66 | return ( 67 |
68 | 74 | 78 | {renderPositions(markers)} 79 | {putLastLocation(markers)} 80 | 81 |
82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /mqtt-cli-fe/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/java/com/gucardev/mqttmobileclient/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttmobileclient 2 | 3 | import android.location.Location 4 | import android.os.Bundle 5 | import android.util.Log 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.gucardev.mqttmobileclient.databinding.ActivityMainBinding 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | private var _binding: ActivityMainBinding? = null 12 | private val binding get() = _binding!! 13 | 14 | private lateinit var mqttHandler: MqttHandler 15 | 16 | private lateinit var deviceId: String 17 | private var isConnected: Boolean = false 18 | 19 | private val locationPermissionManager = LocationPermissionManager(this) 20 | private lateinit var locationUpdater: LocationUpdater 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | _binding = ActivityMainBinding.inflate(layoutInflater) 25 | setContentView(binding.root) 26 | locationPermissionManager.requestLocationPermission() 27 | 28 | deviceId = "device1" 29 | 30 | 31 | binding.buttonSetDeviceId.setOnClickListener { 32 | deviceId = binding.deviceNameText.text.toString() 33 | Log.i("Info", "setDeviceId clicked, deviceId: $deviceId") 34 | } 35 | 36 | binding.buttonConnect.setOnClickListener { 37 | 38 | mqttHandler = MqttHandler(this, deviceId) 39 | mqttHandler.connect { isConnected -> 40 | this.isConnected = isConnected 41 | Log.i( 42 | "Info", 43 | "buttonConnect clicked, isConnected: $isConnected , deviceId: $deviceId" 44 | ) 45 | } 46 | isConnected = mqttHandler.isConnected 47 | } 48 | 49 | binding.buttonSend.setOnClickListener { 50 | Log.i("Info", "btnSend clicked, isConnected: $isConnected") 51 | if (isConnected) { 52 | mqttHandler.sendMessage(deviceId, "myTopic", "hi from app!", "0.0", "0.0") 53 | } 54 | } 55 | 56 | locationUpdater = LocationUpdater(this) { location -> 57 | handleLocationChange(location) 58 | } 59 | locationUpdater.start() 60 | 61 | 62 | } 63 | 64 | private fun handleLocationChange(location: Location) { 65 | println(location) 66 | binding.locationText.text = "lat: ${location.latitude}, lon: ${location.longitude}" 67 | if (isConnected) { 68 | mqttHandler.sendMessage( 69 | deviceId, 70 | deviceId, 71 | "hi from app!", 72 | location.latitude.toString(), 73 | location.longitude.toString() 74 | ) 75 | } 76 | } 77 | 78 | override fun onDestroy() { 79 | super.onDestroy() 80 | _binding = null 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /mqtt-mobile/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /backend/src/main/java/com/gucardev/mqttpoc/config/MqttServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.gucardev.mqttpoc.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.gucardev.mqttpoc.service.StateService; 5 | import com.gucardev.mqttpoc.service.impl.MqttMessageHandler; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.integration.annotation.ServiceActivator; 13 | import org.springframework.integration.channel.DirectChannel; 14 | import org.springframework.integration.core.MessageProducer; 15 | import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; 16 | import org.springframework.integration.mqtt.core.MqttPahoClientFactory; 17 | import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; 18 | import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; 19 | import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; 20 | import org.springframework.messaging.MessageChannel; 21 | import org.springframework.messaging.MessageHandler; 22 | 23 | @Configuration 24 | @Slf4j 25 | @RequiredArgsConstructor 26 | public class MqttServerConfig { 27 | 28 | @Value("${mqqt-config.server-uri}") 29 | private String mqqtServerURI; 30 | 31 | @Bean 32 | public MqttPahoClientFactory mqttClientFactory() { 33 | DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); 34 | MqttConnectOptions options = new MqttConnectOptions(); 35 | 36 | options.setServerURIs(new String[] {mqqtServerURI}); 37 | options.setUserName("admin"); 38 | String pass = "12345678"; 39 | options.setPassword(pass.toCharArray()); 40 | options.setCleanSession(true); 41 | 42 | factory.setConnectionOptions(options); 43 | 44 | return factory; 45 | } 46 | 47 | @Bean 48 | public MessageChannel mqttInputChannel() { 49 | return new DirectChannel(); 50 | } 51 | 52 | @Bean 53 | public MessageProducer inbound() { 54 | MqttPahoMessageDrivenChannelAdapter adapter = 55 | new MqttPahoMessageDrivenChannelAdapter("serverIn", mqttClientFactory(), "#"); 56 | 57 | adapter.setCompletionTimeout(5000); 58 | adapter.setConverter(new DefaultPahoMessageConverter()); 59 | adapter.setQos(2); 60 | adapter.setOutputChannel(mqttInputChannel()); 61 | return adapter; 62 | } 63 | 64 | @Bean 65 | @ServiceActivator(inputChannel = "mqttInputChannel") 66 | public MessageHandler handler(StateService stateService, ObjectMapper mapper) { 67 | return new MqttMessageHandler(stateService, mapper); 68 | } 69 | 70 | @Bean 71 | public MessageChannel mqttOutboundChannel() { 72 | return new DirectChannel(); 73 | } 74 | 75 | @Bean 76 | @ServiceActivator(inputChannel = "mqttOutboundChannel") 77 | public MessageHandler mqttOutbound() { 78 | // clientId is generated using a random number 79 | MqttPahoMessageHandler messageHandler = 80 | new MqttPahoMessageHandler("serverOut", mqttClientFactory()); 81 | messageHandler.setAsync(true); 82 | messageHandler.setDefaultTopic("#"); 83 | messageHandler.setDefaultRetained(false); 84 | return messageHandler; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /mqtt-mobile/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 45 | 46 |