Seperti yang kita ketahui, sekarang banyak aplikasi asisten personal virtual seperti Siri, Alexa, Google Assistant, dll yang dapat mempermudah kita dalam kehidupan sehari-hari. Kita dapat memberikan perintah berupa tulisan maupun suara yang nantinya asisten personal akan memberikan balikan berupa informasi/aksi.
NLP merupakan singkatan dari Natrual Language Processing, adalah salah satu cabang ilmu kecerdasan buatan yang bertujuan untuk menghubungkan bahasa manusia dengan bahasa mesin atau komputer.
Hari ini kita akan membuat aplikasi asisten personal dengan tujuan untuk mengontrol alat IoT (menyiram tanaman). Kita akan mengkombinasikan beberapa teknologi seperti NLP, IoT, SpeechAPI, dll.
- Membuat Asisten Pintar dengan Wit.ai untuk Mengatur Perangkat IoT
Sebelum membuat aplikasi, saya akan membahas cara kerjanya dan prasyarat untuk diikuti. Setelah itu, saya akan membahas setiap langkah sebelum siap untuk dijalankan diproduksi.
Secara garis besar, aplikasi ini akan bekerja seperti ini:
- Buka browser > pergi ke alamat Dasbor Aplikasi.
- Klik tombol
Listen
> berikan perintah suara setelahLog
menampilkan pesanRecognition Started
- Apabila
Log
menampilkan pesanRecognition ended
maka perintah akan diteruskan ke server. - Server akan melakukan perintah pengguna dan menampilkan data (yang diminta) ke dasbor.
Cara kerja aplikasi ini cukup sederhana, tetapi di sini kita menggunakan banyak teknologi untuk mencapai tujuan tersebut. Ini digunakan agar aplikasi yang kita buat kali ini dapat dikembangkan dengan tujuan lebih luas lagi dengan mudah.
Di sini saya mencoba membagi aplikasi ini menjadi 5 bagian, yaitu:
- Infrastuktur (MQTT Broker & Basis Data)
- Gerbang API (Backend)
- Dasbor (Frontend)
- Sistem NLP (Wit.ai)
- Perangkat IoT (Mikrokontroller)
Saya akan membahas masing-masing bagian ini. Untuk memberikan gambaran detail mengenai bagaimana aplikasi ini bekerja dibalik layar.
Untuk dapat menjalankan keseluruhan bagian aplikasi, ada beberapa hal yang perlu dipersiapkan yaitu:
- 1x NodeMCU ESP8266
- 1x DHT 22
- 1x Relay Module
- Browser yang mensupport API
SpeechRecognition
- Docker & Docker-Compose
- NodeJS, NPM & TypeScript
- Arduino IDE
Hal pertama yang kita lakukan adalah mempersiapkan lingkungan pengembangan aplikasi. Di sini kita menggunakan TypeScript dan NPM untuk pengembangan aplikasi kita. Pastikan TypeScript sudah ter-install. Jika belum, maka jalankan perintah npm install -g typescript
untuk meng-install TypeScript.
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Lakukan konfigurasi compiler TypeScript dengan membuat file tsconfig.json
yang berisi JSON file di atas.
// package.json
{
"name": "iot-witai",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node ./dist/backend/app.js",
"dev": "nodemon ./src/backend/app.ts",
"build": "tsc -p ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.6.4",
"nodemon": "^2.0.4",
"ts-node": "^9.0.0"
},
"dependencies": {
"@types/body-parser": "^1.19.0",
"@types/mongoose": "^5.7.36",
"@types/mqtt": "^2.5.0",
"@types/node-wit": "^4.2.2",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"mongoose": "^5.10.7",
"mqtt": "^4.2.1",
"node-wit": "^6.0.0",
"path": "^0.12.7"
}
}
Lakukan instalasi package yang akan digunakan pada aplikasi kita dengan membuat file package.json
yang berisi file JSON di atas.
Kita juga akan menggunakan broker MQTT (Mosquitto) & basis data (MongoDB). Alih-alih meng-install-nya di local/langsung di perangkat yang kita gunakan, kita akan menggunakan Docker untuk menjalankan layanan tersebut.
Buat file docker-compose.yml
di ./
# Docker-compose.yml
version: '3.7'
services:
mosquitto:
image: eclipse-mosquitto:1.6.7
hostname: mosquitto
container_name: mosquitto
ports:
- 1883:1883
- 9001:9001
volumes:
- ./conf/mosquitto.conf:/mosquitto/config/mosquitto.conf
mongodb:
image: mongo:latest
hostname: mongo
container_name: mongo-iot
ports:
- 27017:27017
Untuk mengaktifkan protokol WebSocket di dalam broker MQTT, kita dapat mengatur konfigurasi file dengan membuat file ./conf/mosquitto.conf
yang nantinya akan berjalan pada port 9001.
# this will listen for mqtt on tcp
listener 1883
# this will expect websockets connections
listener 9001
protocol websockets
Untuk menyimpan konfigurasi, kita menggunakan .env
file. Buatlah file ini di /
folder dengan isi :
# .env
PORT=3000
MQTT_URI=mqtt://localhost
MONGO_URI=mongodb://localhost:27017/jarwin
WIT_TOKEN= #Your wit token
Agar file .env
dapat dibaca oleh aplikasi kita, di sini kita akan membuat file untuk men-load file .env
dengan nama config.ts
di dalam folder ./src/backend/
// config.ts
import * as dotenv from 'dotenv';
dotenv.config();
export default {
mongoURI: process.env.MONGO_URI ?? '',
mqttURI: process.env.MQTT_URI ?? '',
witToken: process.env.WIT_TOKEN ?? '',
port: process.env.PORT ?? 3000,
};
Setelah semuanya selesai, kita dapat mulai mengembangkan aplikasi.
Di sini kita menggunakan Wit.ai sebagai NLP sistem kita. Dengan ini, kita dapat membuat asisten personal yang kita buat se-natural mungkin dan tidak perlu membuat machine learning model dengan library seperti PyTorch atau TensorFlow untuk aplikasi kita.
Pada bagian ini kita ingin mencoba untuk melatih aplikasi Wit agar dapat mengenali beberapa perintah seperti set_device
dan get_device
pada perangkat IoT kita.
- Buka Wit.ai dan masuk dengan menggunakan akun Facebook
- Buat aplikasi wit
- Masukan nama aplikasi, bahasa yang digunakan, dan jenis visibility.
- Klik
Create
- Pilih menu
understanding
- Masukan kata
Nyalakan Pompa
ke dalam form Utterance - Pilih Add Intent dan masukkan
set_device
kemudianCreate Intent
- Blok kata
pompa
dan masukkandevice
pada kolom entitiy kemudianCreate Entity
- Klik
Add Trait
dan pilihwit/on_off
kemudian pilihon
- Klik
Train and validate
- Masukkan kata
matikan pompa
kedalam formUtterance
- Atur Intent =
setdevice
, Entity device =pompa
, danwit/on_off
=off
- Klik
Train and validate
- Masukan kata
ambil data pompa
ke dalam formUtterance
- Pilih Add Intent dan masukan
get_device
kemudianCreate Intent
- Pastikan Trait kosong
- Klik
Train and validate
Kita dapat latih aplikasi Wit dengan frasa lainnya. Ini dapat membuat model dari aplikasi Wit kita semakin natural dan dapat mendeteksi intens dari frasa yang lainnya. Contoh :
- Ambil statistik pompa
- Pompa nyalakan
- Pompa matikan
Lakukan proses serupa seperti di atas dan pastikan hasil output sudah valid setelah itu lakukan proses train.
Buat file wit.ts
di dalam folder ./src/backend/
.
File ini kita buat sebagai konektor gerbang API agar bisa memanggil Aplikasi Wit yang telah kita buat sebelumnya.
// wit.ts
import { Wit } from 'node-wit';
import config from './config';
const client = new Wit({
accessToken: config.witToken,
});
const get = async (message: string) => {
try {
const data = await client.message(message, {});
return data;
} catch (error) {
return error;
}
};
export default {
get,
};
Di file ini kita mempunyai sebuah fungsi get(message)
yang digunakan untuk me-request message text ke dalam aplikasi Wit kita. Fungsi ini akan mengembalikan response dari Wit.ai yang berisi Text, Inten, Trait & Entity yang setiap bagiannya memiliki bobot confidence
sebagai patokan apakah prediksi NLP tersebut kuat atau tidak.
Untuk mendapatkan token wit dari aplikasi, kita dapat membuka menu MyApps > [Nama aplikasi Wit] > Management > Setting > Server Access Token.
# .env
WIT_TOKEN= <Your wit token>
Gerbang API akan digunakan sebagai perantara antara aplikasi Dasbor, Wit.ai, MQTT & MongoDB. Di sini kita menggunakan REST API untuk berkomunikasi dengan aplikasi frontend.
Di sini kita membuat konektor MongoDB yang nantinya akan digunakan pada aplikasi kita untuk menyimpan data-data riwayat dari statistik perangkat IoT. Kita membuat file mongo.ts
di dalam folder src/backend
// mongo.ts
import mongoose from 'mongoose';
import config from './config';
const { Schema, model, connect } = mongoose;
const init = () => {
connect(config.mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });
};
const sensorSchema = new Schema({
deviceName: String,
payload: Object,
date: { type: Date, default: Date.now },
});
const sensorModel = model('sensor', sensorSchema);
export default {
init,
sensorModel,
};
Konektor ini berisi skema dari data yang nantinya akan kita simpan dan fungsi init()
yang nantinya akan digunakan untuk menginisiasi koneksi ke dalam MongoDB.
Dalam berkomunikasi dengan perangkat IoT melalui gerbang API, kita menggunakan protokol MQTT (TCP). Alasan utama kita menggunakan ini adalah supaya aplikasi kita dapat menangani request secara asinkronus dan tidak memblok proses yang lain. Selain itu juga karena protokol ini memang umum digunakan untuk berkomunikasi dengan perangkat IoT.
Untuk membuat konektornya, kita buat file mqtt.ts
di dalam direktori /src/backend/
// mqtt.ts
import Mqtt from 'mqtt';
import mongo from './mongo';
import config from './config';
const client = Mqtt.connect(config.mqttURI, {
queueQoSZero: false,
});
const pub = (topic: string, message: any) => {
client.publish(topic, message, { qos: 2 });
};
const sub = (topic: string) => {
client.subscribe(topic, { qos: 2 });
};
client.on('message', (topic, message) => {
if (topic == 'pompa-report') {
const payload = JSON.parse(message.toString());
if (payload.kind == 'pompa-stats') {
const sensor = new mongo.sensorModel({
deviceName: payload['deviceName'],
payload: {
hum: payload['humidity'],
temp: payload['temperature'],
},
});
pub(
'web',
JSON.stringify({
humidity: payload.humidity,
temp: payload.temperature,
date: Date.now(),
})
);
sensor.save();
}
}
});
export default {
pub,
sub,
};
Kode di atas secara garis besar membungkus fungsi publish
dan subscribe
yang ada pada mqtt.client
agar dapat digunakan dengan mudah oleh modul lain.
Di sini kita juga menangani pesan yang masuk dari perangkat IoT, pesan laporan yang masuk dengan jenis pompa-stats
akan dimasukkan ke dalam basis data MongoDB.
Setelah itu, isi payload yang berisi statistik perangkat akan dilanjutkan untuk dikirim ke Dasbor (Frontend) lewat MQTT Dengan topik web
File kontroler ini bertugas untuk menentukan apakah hasil prediksi dari aplikasi Wit yang kita buat presisi atau tidak. Di sini kita mengatur batas skor confidence
yang harus dipenuhi adalah 0.90
agar hasil yang didapatkan sesuai dengan ekspektasi. Jika kurang dari itu, maka hasil prediksi tidak bisa digunakan.
Buat file controller.ts
di dalam direktori ./src/backend/
// controller.ts
import Mqtt from './mqtt';
const execute = (payload: any) => {
const { intents, entities, traits } = payload;
const intent = intents
.filter((i: any) => i.confidence > 0.9)
.map((i: any) => i.name);
const entity = entities['device:device']
.filter((device: any) => device.confidence > 0.9)
.map((d: any) => d.value);
if (intent[0] == 'set_device') {
const trait = traits['wit$on_off']
.filter((t: any) => t.confidence > 0.9)
.map((obj: any) => obj.value);
const status = trait[0] == 'on' ? '1' : '0';
Mqtt.pub(entity[0], status);
} else if (intent[0] == 'get_device') {
Mqtt.pub('pompa-stats', '1');
} else {
console.log('not found');
}
};
export default {
execute,
};
Fungsi execute
akan memastikan Intent dari payload. Jika Intent tersebut adalah set_device
maka sebuah pesan akan di-publish melalui MQTT dengan tujuan nama perangkat tersebut yang berisi pesan Traits (On/Off). Jika Intent tersebut adalah get_device
, maka sebuah pesan akan di-publish melalui MQTT dengan tujuan pompa-stats
yang berisi pesan 1
(Untuk memantik sensor).
Fungsi execute
akan diekspor dan nantinya akan dipanggil dari entrypoint gerbang API.
Kita akan menggunakan REST sebagai API. Tak hanya untuk menyediakan endpoint untuk backend, disini kita juga menggunakanya untuk men-serve aplikasi dasbor (Frontend)
Buat file app.ts
di dalam direktori ./src/backend/
//app.ts
import express, { NextFunction } from 'express';
import wit from './wit';
import mqtt from './mqtt';
import controller from './controller';
import mongo from './mongo';
import bp from 'body-parser';
import path from 'path';
import config from './config';
const app = express();
const cors = require('cors');
app.use(cors());
app.use(bp.json());
app.get('/', async (req, res) => {
res.send('This app is running properly');
});
app.get('/dashboard', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend', 'index.html'));
});
app.post('/send', async (req, res) => {
const { message } = req.body;
const payload = await wit.get(message);
console.log(payload);
controller.execute(payload);
res.send('ok');
});
app.get('/sensor-data', async (req, res) => {
try {
const data = await mongo.sensorModel.find({}).limit(5).sort({ date: -1 });
res.status(200).json(data);
} catch (error) {
res.status(400).json({ msg: 'Error' });
}
});
app.listen(config.port, () => {
mongo.init();
mqtt.sub('pompa-report');
console.log(`This app runs on ${config.port}`);
});
Disini kita mempunyai 4 Endpoint, yaitu:
GET /
Endpoint ini digunakan untuk mengecek apakah aplikasi gerbang API kita sudah berjalan dan dapat diakses.GET /dashboard
Endpoint ini digunakan untuk menampilkan filefrontend/index.html
yang berisi Aplikasi Dasbor (frontend).POST /send
Endpoint ini digunakan untuk menerima payload berupa text dari aplikasi frontend, text ini merupakan hasil pengolahan APISpeechRecognition
. Setelah itu, text tersebut akan digunakan sebagai parameter pesan yang dikirimkan ke dalam aplikasi Wit. Hasil pemrosesan dari proses tersebut akan diteruskan ke dalam kontroller yang nantinya akan menentukan eksekusi apa yang akan dilakukan.GET /sensor-data
Endpoint ini digunakan untuk menampilkan 5 data terakhir dari sensor pada perangkat IoT kita. Ini digunakan sebagai data awal di dalam aplikasi Dasbor.
Aplikasi ini akan dijalankan di port yang telah dideklarasikan di dalam file .env
Agar dapat menggunakan aplikasi ini dengan mudah, maka di sini kita membuat Dasbor (Interface/**).
Aplikasi dasbor ini akan meneruskan permintaan dari pengguna ke gerbang API melalui panggilan HTTP untuk menerima data menggunakan WebSocket & HTTP.
Disini kita menggunakan *single* *file* index.html
, library css yang kita gunakan adalah Bootstrap 4, dan untuk menerima pesan MQTT (Websocket) kita menggunakan library paho-mqtt
.
Buat file index.html
di dalam direktori ./src/frontend
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Aurelio De Rosa" />
<title>Dasbor</title>
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
<style>
.recognition-options {
list-style: none;
padding: 0;
}
.recognition-options li {
display: inline;
}
fieldset {
border: 0;
margin: 0.5em 0;
padding: 0;
}
legend {
padding: 0;
}
.fade-in {
opacity: 1;
animation-name: fadeInOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 1s;
}
@keyframes fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1 class="text-center">✨ Jarwis</h1>
<p>
<small>
Who need a Jarvis If we can build a Jarwis *jk <br />
Build With ❤️ By Iqbal Syamil
</small>
</p>
<p hidden class="js-api-support">API not supported</p>
<div class="js-api-info">
<div class="form-group">
<textarea
class="form-control"
placeholder="Transcription"
style="resize: none"
aria-label="Transcription"
id="transcription"
class="log"
readonly
></textarea>
</div>
<form action="" method="get">
<button type="button" id="button-play" class="button">
Listen
</button>
</form>
<h2>Log</h2>
<div id="log" class="log"></div>
</div>
</div>
<div>
<table id="sensor-table" class="table">
<thead>
<tr>
<th>Date</th>
<th>Humidity</th>
<th>Temperature</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"
type="text/javascript"
></script>
<script>
const apiHost = 'http://localhost:3000'; // host your api gateway i.e http://localhost:3000
var mqtt;
const mqttHost = 'localhost'; // your mqtt host i.e. localhost
const mqttPort = 9001; // port mqqt (websocket) default = 9001
const dateLang = 'id-ID'; // dateLocale Format
const speechRecognitionLang = 'ID'; // language used in speechRecognition i.e "EN"
function mqttConnect() {
mqtt = new Paho.MQTT.Client(mqttHost, Number(mqttPort), 'client-web');
mqtt.connect({ onSuccess: onConnect });
mqtt.onConnectionLost = onConnectionLost;
mqtt.onMessageArrived = onMessageArrived;
}
function onConnect() {
// Once a connection has been made, make a subscription and send a message.
mqtt.subscribe('web');
}
function onMessageArrived(message) {
const payload = JSON.parse(message.payloadString);
var tablerow = tableref.insertRow(0);
tablerow.className = 'fade-in';
tablerow.innerHTML = `<tr class=fade-in> <td>${new Date(
payload.date
).toLocaleString(dateLang)}</td> <td>${payload.humidity}</td> <td>${
payload.temp
}</td> </tr>`;
}
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log('onConnectionLost:' + responseObject.errorMessage);
}
}
function send(msg) {
const raw = JSON.stringify({ message: msg });
fetch(apiHost + '/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: raw,
redirect: 'follow',
})
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log('error', error));
}
function logEvent(string) {
var log = document.getElementById('log');
log.innerHTML = string + '<br />' + log.innerHTML;
}
window.SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition || null;
if (!SpeechRecognition) {
document.querySelector('.js-api-support').removeAttribute('hidden');
document.querySelector('.js-api-info').setAttribute('hidden', '');
[].forEach.call(document.querySelectorAll('form button'), function (
button
) {
button.setAttribute('disabled', '');
});
} else {
var recognizer = new SpeechRecognition();
var transcription = document.getElementById('transcription');
// Start recognising
recognizer.addEventListener('result', function (event) {
transcription.textContent = '';
for (var i = event.resultIndex; i < event.results.length; i++) {
if (event.results[i].isFinal) {
send(event.results[i][0].transcript);
transcription.textContent =
event.results[i][0].transcript +
' (Confidence: ' +
event.results[i][0].confidence +
')';
} else {
transcription.textContent += event.results[i][0].transcript;
}
}
});
// Listen for errors
recognizer.addEventListener('error', function (event) {
logEvent('Recognition error: ' + event.message);
});
recognizer.addEventListener('end', function () {
logEvent('Recognition ended');
});
document
.getElementById('button-play')
.addEventListener('click', function () {
document.getElementById('log').textContent = '';
transcription.textContent = '';
recognizer.lang = speechRecognitionLang;
recognizer.continuous = !true;
recognizer.interimResults = true;
try {
recognizer.start();
logEvent('Recognition started');
} catch (ex) {
logEvent('Recognition error: ' + ex.message);
}
});
var tableref = document
.getElementById('sensor-table')
.getElementsByTagName('tbody')[0];
document.addEventListener('DOMContentLoaded', function (event) {
fetch(apiHost + '/sensor-data')
.then((response) => response.json())
.then((result) => {
result.forEach((res) => {
tableref.insertRow(
tableref.rows.length
).innerHTML = `<tr> <td>${new Date(res.date).toLocaleString(
'id-ID'
)}</td> <td>${res.payload.hum}</td> <td>${
res.payload.temp
}</td> </tr>`;
});
});
mqttConnect();
});
}
</script>
</body>
</html>
Ada hal yang perlu diperhatikan, yaitu memastikan bahwa konfigurasi pada aplikasi dasbor sudah tepat. Di sini kita mendefinisikan beberapa hal, seperti alamat apiHost
, alamat mqqtHost
, port mqttPort
, format tanggal dateLang
, dan format bahasa yang digunakan pada Speech Recognition API speechRecognitionLang
.
/* ... Kode Terpotong */
<script>
const apiHost = "http://localhost:3000"; // host your api gateway i.e http://localhost:3000
var mqtt;
const mqttHost = "localhost"; // your mqtt host i.e. localhost
const mqttPort = 9001; // port mqqt (websocket) default = 9001
const dateLang = "id-ID" // dateLocale Format
const speechRecognitionLang = "ID" // language used in speechRecognition i.e "EN"
/* ... Kode Terpotong */
Seperti pada bagian pendahuluan, tujuan dari perangkat IoT ini adalah agar kita dapat menyiram tanaman dengan menyalakan pompa. Jadi, kita ingin membuat perangkat IoT kita dapat melakukan 3 hal, yaitu:
- Menyalakan Pompa
- Mematikan Pompa
- Mengambil Data statistik pompa
Di sini kita menggunakan 4 library eksternal. Pastikan kita sudah meng-install library tersebut di dalam arduino IDE.
Di bawah ini merupakan gambaran skema dari perangkat IoT yang digunakan pada tulisan ini. Pastikan pin yang digunakan sudah terkoneksi sesuai dengan skema.
- Garis Biru = Jalur sinyal digital/analog
- Garis Hijau = Jalur arus + Positif
- Garis Merah = Jalur arus - (Ground/GND)
Gunakan kode di bawah ini untuk di-compile pada perangkat IoT (NodeMCU ESP8266).
#define SerialMon Serial
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h> // Include the Wi-Fi library
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "DHTesp.h"
//Create software serial object to communicate with SIM800L
WiFiClient espClient;
PubSubClient mqtt(espClient);
const char* ssid = ""; // The SSID (name) of the Wi-Fi network you want to connect to
const char* password = ""; // The password of the Wi-Fi network
const char* broker = ""; // ip / domain your mqtt broker example (192.168.1.2)
const char* deviceName = "pompa"; // name of your device
StaticJsonDocument<250> wrapper;
DHTesp dht;
boolean res;
boolean mqttConnect() {
char buffer[256];
SerialMon.print("Connecting to ");
SerialMon.print(broker);
wrapper["deviceName"] = deviceName;
// Connect to MQTT Broker
boolean status = mqtt.connect(deviceName);
if (status == false) {
SerialMon.println("fail");
return false;
}
SerialMon.println("success");
mqtt.subscribe("pompa");
mqtt.subscribe("pompa-stats");
wrapper["kind"] = "connected";
wrapper["status"] = true;
size_t n = serializeJson(wrapper,buffer);
mqtt.publish("report",buffer,n);
return mqtt.connected();
}
void callback(char* topic, byte* payload, unsigned int length);
void setup()
{
ESP.eraseConfig();
SerialMon.begin(9600);
WiFi.begin(ssid, password);
dht.setup(D2, DHTesp::DHT22); // Connect DHT sensor to GPIO 17
Serial.print("Connecting to ");
pinMode(D1, OUTPUT); // initialize pin as OUTPUT
Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
delay(1000);
Serial.print('*');
}
mqtt.setServer(broker, 1883); // connect to mqtt broker with port (default : 1883)
mqtt.setCallback(callback);
}
void loop()
{
if (!mqtt.connected()) {
SerialMon.println("Trying Connecting to mqtt broker");
if(mqttConnect()){
SerialMon.println("MQTT Connected");
}
}
mqtt.loop();
}
void callback(char* topic, byte* payload, unsigned int length) {
StaticJsonDocument<200> doc;
char buffer[256];
doc["deviceName"] = deviceName;
doc["kind"] = topic;
if(strcmp(topic, "pompa") == 0){
if(payload[0] == '1'){
digitalWrite(D1,1);
doc["status"] = true;
SerialMon.println("Pompa Nyala");
}
if(payload[0] == '0'){
digitalWrite(D1,0);
doc["status"] = false;
SerialMon.println("Pompa Mati");
}
size_t n = serializeJson(doc,buffer);
mqtt.publish("pompa-report",buffer,n);
}else if(strcmp(topic, "pompa-stats") == 0){
if(payload[0] == '1'){
float humidity = dht.getHumidity();
float temperature = dht.getTemperature();
doc["humidity"] = humidity;
doc["temperature"] = temperature;
size_t n = serializeJson(doc,buffer);
mqtt.publish("pompa-report",buffer,n);
}
}
doc.clear();
}
Cara kerja program ini:
- Perangkat akan melakukan koneksi ke dalam WiFI yang sudah didefinisikan.
- Setelah terkoneksi, perangkat akan melakukan koneksi ke MQTT Broker, men-subscrib_e topik perangkat, dan mem-_publish pesan ke topik report bahwa perangkat sudah terkoneksi.
- Perangkat akan menunggu pesan masuk dari gerbang API via protokol MQTT (TCP).
- Apabila ada pesan yang masuk dengan topik "pompa" atau nama perangkat, maka perangkat akan melakukan perintah untuk menyalakan atau mematikan pompa berdasarkan data payload yang dikirim oleh gerbang API. Setelah proses eksekusi dilakukan, statusnya akan dikirim ke topik "pompa-report" yang sudah dibungkus dengan format JSON.
- Apabila ada pesan yang masuk dengan topik "pompa-stats", maka perangkat akan mengambil data dari sensor DHT berupa Temperatur/Temperature dan Kelembabman/Humidity, dan hasil nya akan dikirim ke topik "pompa-report" yang sudah dibungkus dengan format JSON.
Lakukan pengaturan pada perangkat IoT pada file arduino/pompa.ino
dengan mengisi variable ssid
, password
, broker
.
// ... Kode Terpotong
WiFiClient espClient;
PubSubClient mqtt(espClient);
const char* ssid = ""; // SSID (name) dari Wi-Fi
const char* password = ""; // Password Wi-Fi
const char* broker = ""; // ip / domain dari mqtt broker. contoh (192.168.1.2)
const char* deviceName = "pompa"; // nama perangkat
StaticJsonDocument<250> wrapper;
DHTesp dht;
// ... Kode Terpotong
Setelah semuanya sudah terkonfigurasi, maka lakukan proses compile kode tersebut pada perangkat IoT.
Setelah kita melakukan beberapa langkah di atas, berikut ini adalah gambaran lengkap dari arsitektur yang digunakan di dalam proyek ini.
Setelah seluruh konfigurasi selesai, jalankan perintah di bawah secara berurutan.
docker-compose up
(untuk menjalankan layanan MQTT Broker & MongoDB)npm run dev
(untuk menjalankan aplikasi dalam mode pengembangan)- Hubungkan perangkat IoT dengan daya.
- Buka aplikasi Dasbor di url http://localhost:3000/dashboard
Selamat! teman-teman sudah berhasil membuat proyek untuk mengkontrol perangkat IoT. Di tulisan kali ini kita sudah belajar banyak hal seperti bagaimana cara kerja NLP, bagaimana caranya kita berkomunikasi dengan perangkat IoT, dll. Semoga apa yang sudah dipelajari dapat bermanfaat dan bisa dikembangkan menjadi hal yang lebih keren lagi! 😁
Kode seluruh proyek ini dapat diakses disini
Aplikasi ini masih belum sempurna. Jika teman-teman tertarik untuk mengembangkan, ada beberapa ide seperti:
- Mengatur perangkat (menambahkan, menghapus, dan mengedit)
- Memvisualisasikan perangkat yang aktif maupun tidak aktif
- Membuat versi aplikasi mobile
- dll