Update Arduino: fix vibration x/y/z, gateway rebuild JSON per sensor

This commit is contained in:
Adamalfantino 2026-06-08 19:51:13 +07:00
parent 3846619292
commit d17918ab61
2 changed files with 739 additions and 466 deletions

View File

@ -1,42 +1,66 @@
/**
* ============================================
* Smart Rack Security System
* ESP32 GATEWAY (RECEIVER) Di luar rak
*
* Fungsi : Terima data LoRa dari Node, forward ke Railway API
* Komunikasi : LoRa SX1278 (RX) + WiFi (HTTPS ke Railway)
* TIDAK ada sensor di sini
* ============================================
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <LoRa.h>
#include <PubSubClient.h>
// ============================================
// KONFIGURASI — SESUAIKAN
// API (Railway)
// ============================================
const char* WIFI_SSID = "gege";
const char* WIFI_PASSWORD = "biasaaja";
const char* API_BASE = "https://keamanan-rak-barang-production.up.railway.app/api";
const int DEVICE_ID = 1; // ID device di database Railway
const char* API_BASE = "https://keamanan-rak-barang-production.up.railway.app/api";
const int DEVICE_ID = 1;
// ============================================
// PIN LORA SX1278 RA-02
// WIFI
// ============================================
#define LORA_SS 27
#define LORA_RST 14
#define LORA_DIO0 26
#define LORA_FREQ 433E6 // Harus sama dengan Node Sender!
const char* WIFI_SSID = "ini";
const char* WIFI_PASS = "00000000";
// ============================================
// PIN LED STATUS GATEWAY (opsional)
// MQTT HiveMQ
// ============================================
#define LED_WIFI 2 // LED bawaan ESP32 — nyala = WiFi OK
#define LED_LORA 4 // Kedip = terima paket LoRa
const char* MQTT_SERVER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
const char* TOPIC_PIR = "keamanan/pir";
const char* TOPIC_REED = "keamanan/reed";
const char* TOPIC_VIBRATION = "keamanan/vibration";
const char* TOPIC_STATUS = "keamanan/status";
const char* TOPIC_TEST = "keamanan/test";
WiFiClient espClient;
PubSubClient mqtt(espClient);
// ============================================
// LORA CONFIG
// ============================================
#define LORA_SS 27
#define LORA_RST 14
#define LORA_DIO0 26
#define LORA_FREQ 433E6
#define LORA_SYNC_WORD 0xA5
#define LORA_SF 9
#define LORA_BW 125E3
#define LORA_CR 5
#define EXPECTED_NODE "NODE_001"
#define EXPECTED_GATEWAY "GATEWAY_001"
// ============================================
// LED
// ============================================
#define LED_WIFI 2
#define LED_LORA 4
#define LED_HIJAU 33
#define LED_MERAH 25
// ============================================
// ALERT SYSTEM
// ============================================
#define ALERT_HOLD 3000
bool alertActive = false;
unsigned long alertStartTime = 0;
// ============================================
// STATISTIK
@ -47,226 +71,300 @@ unsigned long totalFailed = 0;
unsigned long lastStatusPrint = 0;
// ============================================
// SETUP WIFI
// BLINK LORA LED
// ============================================
void blinkLoRa() {
digitalWrite(LED_LORA, HIGH);
delay(50);
digitalWrite(LED_LORA, LOW);
}
// ============================================
// WIFI CONNECT
// ============================================
void setupWifi() {
Serial.println("[WiFi] Menghubungkan ke: " + String(WIFI_SSID));
Serial.println("\n[WiFi] Connecting...");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.begin(WIFI_SSID, WIFI_PASS);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 40) {
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 40) {
delay(500);
Serial.print(".");
attempt++;
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] ✓ Terhubung! IP: " + WiFi.localIP().toString());
Serial.println("\n[WiFi] CONNECTED IP: " + WiFi.localIP().toString());
digitalWrite(LED_WIFI, HIGH);
} else {
Serial.println("\n[WiFi] ✗ Gagal. Cek SSID/Password atau pastikan 2.4GHz.");
digitalWrite(LED_WIFI, LOW);
Serial.println("\n[WiFi] FAILED — restart");
ESP.restart();
}
}
// ============================================
// WIFI AUTO RECONNECT
// ============================================
void checkWifi() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] Putus, reconnecting...");
digitalWrite(LED_WIFI, LOW);
WiFi.disconnect();
delay(1000);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
if (WiFi.status() == WL_CONNECTED) return;
Serial.println("[WiFi] Reconnecting...");
digitalWrite(LED_WIFI, LOW);
WiFi.disconnect();
WiFi.begin(WIFI_SSID, WIFI_PASS);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
delay(500);
Serial.print(".");
attempt++;
}
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) {
delay(500);
Serial.print(".");
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] ✓ Terhubung kembali!");
digitalWrite(LED_WIFI, HIGH);
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] RECONNECTED");
digitalWrite(LED_WIFI, HIGH);
} else {
Serial.println("\n[WiFi] FAILED RECONNECT");
}
}
// ============================================
// MQTT CONNECT
// ============================================
void connectMQTT() {
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
while (!mqtt.connected()) {
String clientId = "ESP32-GW-" + String(random(0xffff), HEX);
Serial.println("[MQTT] Connecting...");
if (mqtt.connect(clientId.c_str())) {
Serial.println("[MQTT] CONNECTED");
} else {
Serial.println("\n[WiFi] ✗ Gagal reconnect.");
Serial.print("[MQTT] FAILED rc=");
Serial.println(mqtt.state());
delay(2000);
}
}
}
// ============================================
// HTTP POST KE RAILWAY API
// MQTT PUBLISH
// ============================================
bool httpPost(String endpoint, String jsonBody) {
void publishMQTT(const char* topic, String payload) {
if (!mqtt.connected()) return;
mqtt.publish(topic, payload.c_str());
Serial.println("[MQTT] SENT -> " + String(topic));
}
// ============================================
// HTTP POST
// ============================================
bool httpPost(String endpoint, String body) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[HTTP] Skip — WiFi tidak terhubung");
return false;
}
WiFiClientSecure client;
client.setInsecure(); // Skip SSL verify — cukup untuk Railway
client.setInsecure();
HTTPClient http;
String url = String(API_BASE) + endpoint;
Serial.println("[HTTP] POST -> " + url);
http.begin(client, url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Accept", "application/json");
http.setTimeout(10000);
int httpCode = http.POST(jsonBody);
int code = http.POST(body);
Serial.print("[HTTP] CODE: ");
Serial.println(code);
if (httpCode > 0) {
if (httpCode == 200 || httpCode == 201) {
Serial.println("[HTTP] ✓ " + endpoint + "" + String(httpCode));
http.end();
totalSent++;
return true;
} else {
String resp = http.getString();
Serial.println("[HTTP] ✗ " + endpoint + "" + String(httpCode));
Serial.println(" " + resp.substring(0, 120));
}
} else {
Serial.println("[HTTP] ✗ Error: " + String(httpCode) + "" + endpoint);
if (code != 200 && code != 201) {
String resp = http.getString();
Serial.println("[HTTP] RESP: " + resp.substring(0, 150));
}
http.end();
if (code == 200 || code == 201) {
totalSent++;
return true;
}
totalFailed++;
return false;
}
// ============================================
// PROSES PAKET PIR
// ALERT
// ============================================
void prosesPIR(String nodeId, String jsonStr) {
StaticJsonDocument<256> src;
if (deserializeJson(src, jsonStr) != DeserializationError::Ok) {
Serial.println("[PIR] JSON parse error");
return;
}
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["motion_detected"] = src["motion_detected"] | false;
doc["motion_intensity"] = src["motion_intensity"] | 50;
doc["duration_seconds"] = src["duration_seconds"] | 0;
doc["detection_zone"] = src["detection_zone"] | "center";
String body;
serializeJson(doc, body);
Serial.println("[PIR] Node=" + nodeId + " → forward ke API");
httpPost("/pir/data", body);
void setAlert(String msg) {
digitalWrite(LED_MERAH, HIGH);
digitalWrite(LED_HIJAU, LOW);
alertActive = true;
alertStartTime = millis();
Serial.println("[ALERT] " + msg);
}
// ============================================
// PROSES PAKET REED SWITCH
// ROUTER CORE
// Terima JSON mentah dari node sender, parse,
// lalu kirim ke API dengan format yang benar.
// ============================================
void prosesREED(String nodeId, String jsonStr) {
StaticJsonDocument<256> src;
if (deserializeJson(src, jsonStr) != DeserializationError::Ok) {
Serial.println("[REED] JSON parse error");
void routePacket(String raw) {
StaticJsonDocument<512> doc;
DeserializationError err = deserializeJson(doc, raw);
if (err) {
Serial.println("[JSON] INVALID: " + String(err.c_str()));
return;
}
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["door_opened"] = src["door_opened"] | false;
doc["duration_seconds"] = src["duration_seconds"] | 0;
doc["access_method"] = src["access_method"] | "manual";
doc["door_location"] = src["door_location"] | "rack";
doc["is_forced_entry"] = src["is_forced_entry"] | false;
String node = doc["node_id"] | "";
String gateway = doc["gateway_id"] | "";
String type = doc["type"] | "";
String body;
serializeJson(doc, body);
Serial.println("[REED] Node=" + nodeId + " → forward ke API");
httpPost("/door-access/data", body);
}
// ============================================
// PROSES PAKET GETARAN SW420
// ============================================
void prosesVIBRATION(String nodeId, String jsonStr) {
StaticJsonDocument<256> src;
if (deserializeJson(src, jsonStr) != DeserializationError::Ok) {
Serial.println("[VIBRATION] JSON parse error");
// Filter node & gateway
if (node != EXPECTED_NODE) {
Serial.println("[FILTER] INVALID NODE: " + node);
return;
}
if (gateway != EXPECTED_GATEWAY) {
Serial.println("[FILTER] INVALID GATEWAY: " + gateway);
return;
}
StaticJsonDocument<256> doc;
doc["device_id"] = DEVICE_ID;
doc["x_axis"] = src["x_axis"] | 0.0;
doc["y_axis"] = src["y_axis"] | 0.0;
doc["z_axis"] = src["z_axis"] | 0.0;
doc["threshold"] = src["threshold"] | 2.0;
Serial.println("[ROUTE] type=" + type + " node=" + node);
checkWifi();
String body;
serializeJson(doc, body);
Serial.println("[VIBRATION] Node=" + nodeId + " → forward ke API");
httpPost("/vibration/data", body);
}
// =========================================
// PIR
// =========================================
if (type == "PIR") {
setAlert("PIR detected");
// ============================================
// PROSES PAKET HEARTBEAT
// ============================================
void prosesHEARTBEAT(String nodeId, String jsonStr) {
StaticJsonDocument<128> src;
deserializeJson(src, jsonStr);
long uptime = src["uptime_s"] | 0;
Serial.println("[HEARTBEAT] Node=" + nodeId + " masih hidup, uptime=" + String(uptime) + "s");
// Build payload untuk API
StaticJsonDocument<256> apiDoc;
apiDoc["device_id"] = DEVICE_ID;
apiDoc["motion_detected"] = doc["motion_detected"] | false;
apiDoc["motion_intensity"] = doc["motion_intensity"] | 50;
apiDoc["duration_seconds"] = doc["duration_seconds"] | 0;
apiDoc["detection_zone"] = doc["detection_zone"] | "center";
String apiBody;
serializeJson(apiDoc, apiBody);
httpPost("/pir/data", apiBody);
// Kirim heartbeat ke API agar device tetap Online di dashboard
StaticJsonDocument<128> doc;
doc["node_id"] = nodeId;
doc["gateway_id"] = "GATEWAY_001";
doc["payload"] = "HEARTBEAT|" + nodeId;
String body;
serializeJson(doc, body);
httpPost("/lora/receive", body);
}
// ============================================
// PARSE & ROUTE PAKET LORA
// Format paket: TYPE|NODE_ID|{json}
// ============================================
void prosesPacket(String packet, int rssi, float snr) {
totalReceived++;
// Kedip LED tanda terima paket
digitalWrite(LED_LORA, HIGH); delay(50); digitalWrite(LED_LORA, LOW);
Serial.println("─────────────────────────────────");
Serial.println("[LoRa RX] RSSI=" + String(rssi) + " SNR=" + String(snr, 1));
Serial.println("[LoRa RX] Raw: " + packet.substring(0, 100));
// Parse format: TYPE|NODE_ID|{json}
int sep1 = packet.indexOf('|');
if (sep1 < 0) {
Serial.println("[LoRa RX] Format tidak dikenal, skip.");
return;
// Build payload untuk MQTT
StaticJsonDocument<256> mqttDoc;
mqttDoc["device_id"] = DEVICE_ID;
mqttDoc["node"] = node;
mqttDoc["type"] = type;
mqttDoc["motion"] = doc["motion_detected"] | false;
String mqttBody;
serializeJson(mqttDoc, mqttBody);
publishMQTT(TOPIC_PIR, mqttBody);
}
int sep2 = packet.indexOf('|', sep1 + 1);
if (sep2 < 0) {
Serial.println("[LoRa RX] Format tidak lengkap, skip.");
return;
// =========================================
// REED
// =========================================
else if (type == "REED") {
setAlert("DOOR event");
// Build payload untuk API
StaticJsonDocument<256> apiDoc;
apiDoc["device_id"] = DEVICE_ID;
apiDoc["door_opened"] = doc["door_opened"] | false;
apiDoc["duration_seconds"] = doc["duration_seconds"] | 0;
apiDoc["access_method"] = doc["access_method"] | "manual";
apiDoc["door_location"] = doc["door_location"] | "rack";
apiDoc["is_forced_entry"] = doc["is_forced_entry"] | false;
String apiBody;
serializeJson(apiDoc, apiBody);
httpPost("/door-access/data", apiBody);
// Build payload untuk MQTT
StaticJsonDocument<256> mqttDoc;
mqttDoc["device_id"] = DEVICE_ID;
mqttDoc["node"] = node;
mqttDoc["type"] = type;
mqttDoc["door"] = doc["door_opened"] | false;
String mqttBody;
serializeJson(mqttDoc, mqttBody);
publishMQTT(TOPIC_REED, mqttBody);
}
String type = packet.substring(0, sep1);
String nodeId = packet.substring(sep1 + 1, sep2);
String payload = packet.substring(sep2 + 1);
// =========================================
// VIBRATION
// =========================================
else if (type == "VIBRATION") {
setAlert("VIBRATION detected");
Serial.println("[LoRa RX] Type=" + type + " Node=" + nodeId);
// Build payload untuk API — butuh x_axis, y_axis, z_axis
StaticJsonDocument<256> apiDoc;
apiDoc["device_id"] = DEVICE_ID;
apiDoc["x_axis"] = doc["x_axis"] | 0.0;
apiDoc["y_axis"] = doc["y_axis"] | 0.0;
apiDoc["z_axis"] = doc["z_axis"] | 0.0;
apiDoc["threshold"] = doc["threshold"] | 2.0;
String apiBody;
serializeJson(apiDoc, apiBody);
httpPost("/vibration/data", apiBody);
checkWifi(); // Pastikan WiFi masih konek sebelum forward
// Build payload untuk MQTT
StaticJsonDocument<256> mqttDoc;
mqttDoc["device_id"] = DEVICE_ID;
mqttDoc["node"] = node;
mqttDoc["type"] = type;
mqttDoc["x_axis"] = doc["x_axis"] | 0.0;
mqttDoc["y_axis"] = doc["y_axis"] | 0.0;
mqttDoc["z_axis"] = doc["z_axis"] | 0.0;
String mqttBody;
serializeJson(mqttDoc, mqttBody);
publishMQTT(TOPIC_VIBRATION, mqttBody);
}
// =========================================
// STATUS (heartbeat)
// =========================================
else if (type == "STATUS") {
// Kirim ke /lora/receive agar device tetap Online di dashboard
StaticJsonDocument<128> apiDoc;
apiDoc["node_id"] = node;
apiDoc["gateway_id"] = gateway;
apiDoc["payload"] = "HEARTBEAT|" + node;
apiDoc["uptime"] = doc["uptime"] | 0;
String apiBody;
serializeJson(apiDoc, apiBody);
httpPost("/lora/receive", apiBody);
// MQTT
StaticJsonDocument<128> mqttDoc;
mqttDoc["device_id"] = DEVICE_ID;
mqttDoc["node"] = node;
mqttDoc["type"] = type;
mqttDoc["uptime"] = doc["uptime"] | 0;
String mqttBody;
serializeJson(mqttDoc, mqttBody);
publishMQTT(TOPIC_STATUS, mqttBody);
Serial.println("[STATUS] RECEIVED & FORWARDED");
}
// =========================================
// TEST
// =========================================
else if (type == "TEST") {
Serial.println("[TEST] RECEIVED OK");
StaticJsonDocument<128> mqttDoc;
mqttDoc["device_id"] = DEVICE_ID;
mqttDoc["node"] = node;
mqttDoc["type"] = type;
String mqttBody;
serializeJson(mqttDoc, mqttBody);
publishMQTT(TOPIC_TEST, mqttBody);
}
if (type == "PIR") prosesPIR(nodeId, payload);
else if (type == "REED") prosesREED(nodeId, payload);
else if (type == "VIBRATION") prosesVIBRATION(nodeId, payload);
else if (type == "HEARTBEAT") prosesHEARTBEAT(nodeId, payload);
else {
Serial.println("[LoRa RX] Type tidak dikenal: " + type);
Serial.println("[ROUTER] UNKNOWN TYPE: " + type);
}
}
@ -275,69 +373,111 @@ void prosesPacket(String packet, int rssi, float snr) {
// ============================================
void setup() {
Serial.begin(115200);
delay(1000);
delay(500);
Serial.println("\n==========================");
Serial.println(" SMART RACK GATEWAY ");
Serial.println("==========================");
pinMode(LED_WIFI, OUTPUT);
pinMode(LED_LORA, OUTPUT);
digitalWrite(LED_WIFI, LOW);
digitalWrite(LED_LORA, LOW);
pinMode(LED_WIFI, OUTPUT);
pinMode(LED_LORA, OUTPUT);
pinMode(LED_HIJAU, OUTPUT);
pinMode(LED_MERAH, OUTPUT);
digitalWrite(LED_WIFI, LOW);
digitalWrite(LED_LORA, LOW);
digitalWrite(LED_HIJAU, HIGH);
digitalWrite(LED_MERAH, LOW);
Serial.println("=================================");
Serial.println(" SMART RACK — GATEWAY RECEIVER");
Serial.println(" API: " + String(API_BASE));
Serial.println(" Device ID: " + String(DEVICE_ID));
Serial.println("=================================");
// WiFi
setupWifi();
connectMQTT();
// LoRa — HARUS setting sama persis dengan Node Sender
// =========================================
// LORA — hardware reset + retry
// =========================================
Serial.println("[1] LoRa setPins");
pinMode(LORA_RST, OUTPUT);
digitalWrite(LORA_RST, LOW); delay(20);
digitalWrite(LORA_RST, HIGH); delay(150);
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("[LoRa] GAGAL INIT! Cek wiring.");
while (true) {
digitalWrite(LED_LORA, HIGH); delay(200);
digitalWrite(LED_LORA, LOW); delay(200);
Serial.println("[2] Starting LoRa...");
int loraRetry = 0;
while (!LoRa.begin(LORA_FREQ)) {
loraRetry++;
Serial.print("[LoRa] INIT FAILED, retry: ");
Serial.println(loraRetry);
digitalWrite(LORA_RST, LOW); delay(20);
digitalWrite(LORA_RST, HIGH); delay(150);
delay(500);
if (loraRetry >= 10) {
Serial.println("[LoRa] GIVING UP - RESTART");
delay(1000);
ESP.restart();
}
}
LoRa.setSpreadingFactor(9); // Harus sama dengan Node!
LoRa.setSignalBandwidth(125E3);
LoRa.setCodingRate4(5);
Serial.println("[LoRa] ✓ OK — " + String(LORA_FREQ / 1E6) + " MHz, menunggu paket...");
Serial.println("Gateway siap!");
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setCodingRate4(LORA_CR);
LoRa.setSyncWord(LORA_SYNC_WORD);
Serial.println("[3] LoRa READY");
Serial.println("[SYSTEM] READY — menunggu paket...");
Serial.println(" API : " + String(API_BASE));
Serial.println(" DeviceID: " + String(DEVICE_ID));
}
// ============================================
// LOOP
// ============================================
void loop() {
// Cek apakah ada paket LoRa masuk
int packetSize = LoRa.parsePacket();
checkWifi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// =========================================
// RECEIVE LORA
// =========================================
int packetSize = LoRa.parsePacket();
if (packetSize) {
totalReceived++;
blinkLoRa();
if (packetSize > 0) {
String packet = "";
while (LoRa.available()) {
packet += (char)LoRa.read();
}
int rssi = LoRa.packetRssi();
float snr = LoRa.packetSnr();
prosesPacket(packet, rssi, snr);
Serial.println("\n========== LORA RX ==========");
Serial.println(packet);
Serial.print("RSSI: "); Serial.println(LoRa.packetRssi());
Serial.print("SNR: "); Serial.println(LoRa.packetSnr());
Serial.println("=============================");
routePacket(packet);
}
// Print statistik setiap 60 detik
// =========================================
// ALERT RESET
// =========================================
if (alertActive && millis() - alertStartTime > ALERT_HOLD) {
alertActive = false;
digitalWrite(LED_HIJAU, HIGH);
digitalWrite(LED_MERAH, LOW);
}
// =========================================
// STATUS PRINT setiap 60 detik
// =========================================
if (millis() - lastStatusPrint >= 60000) {
Serial.println("─────────────────────────────────");
Serial.println("[STATUS] Uptime: " + String(millis()/1000) + "s");
Serial.println("[STATUS] LoRa diterima : " + String(totalReceived));
Serial.println("[STATUS] API berhasil : " + String(totalSent));
Serial.println("[STATUS] API gagal : " + String(totalFailed));
Serial.println("[STATUS] WiFi: " + String(WiFi.status() == WL_CONNECTED ? "OK" : "PUTUS"));
Serial.println("[STATUS] Uptime : " + String(millis() / 1000) + "s");
Serial.println("[STATUS] Diterima: " + String(totalReceived));
Serial.println("[STATUS] API OK : " + String(totalSent));
Serial.println("[STATUS] API FAIL: " + String(totalFailed));
Serial.println("[STATUS] WiFi : " + String(WiFi.status() == WL_CONNECTED ? "OK" : "PUTUS"));
Serial.println("[STATUS] MQTT : " + String(mqtt.connected() ? "OK" : "PUTUS"));
Serial.println("─────────────────────────────────");
lastStatusPrint = millis();
}
delay(10); // Kecil saja agar LoRa tidak miss paket
delay(10);
}

View File

@ -1,182 +1,135 @@
/**
* ============================================
* Smart Rack Security System
* ESP32 NODE (SENDER) Di dalam / dekat rak
*
* Sensor : PIR + Reed Switch Magnetik + SW420
* Output : Buzzer + LED Merah + LED Hijau
* Komunikasi : LoRa SX1278 (TX only)
* TIDAK butuh WiFi
* ============================================
*/
#include <ArduinoJson.h>
#include <SPI.h>
#include <LoRa.h>
#include <math.h>
#include <ArduinoJson.h>
// ============================================
// IDENTITAS NODE
// ============================================
#define NODE_ID "NODE_001" // ID unik node ini
// ============================================
// PIN SENSOR
// ============================================
#define PIR_PIN 33
#define REED_PIN 32 // INPUT_PULLUP — LOW = tertutup, HIGH = terbuka
#define SW420_PIN 34
// ============================================
// PIN OUTPUT
// ============================================
#define BUZZER 25
#define LED_MERAH 13
#define LED_HIJAU 4
// ============================================
// PIN LORA SX1278 RA-02
// LORA PIN
// ============================================
#define LORA_SS 27
#define LORA_RST 14
#define LORA_DIO0 26
#define LORA_FREQ 433E6 // 433 MHz — sesuaikan modul kamu
#define LORA_FREQ 433E6
// ============================================
// VARIABEL STATE SENSOR
// LORA CONFIG
// ============================================
int pirState = LOW;
int reedState = LOW;
int vibrationState = LOW;
int lastVibration = LOW;
#define LORA_SYNC_WORD 0xA5
#define LORA_SF 9
#define LORA_BW 125E3
#define LORA_CR 5
bool motionActive = false;
bool doorOpen = false;
unsigned long motionStartTime = 0;
unsigned long doorOpenTime = 0;
unsigned long lastHeartbeat = 0;
// Heartbeat ke gateway setiap 2 menit
const unsigned long HEARTBEAT_INTERVAL = 120000;
// ============================================
// SENSOR PIN
// ============================================
#define PIN_SW420 34
#define PIN_REED 32
#define PIN_PIR 33
#define PIN_BUZZER 25
// ============================================
// LED
// ============================================
void setLED(bool bahaya) {
digitalWrite(LED_MERAH, bahaya ? HIGH : LOW);
digitalWrite(LED_HIJAU, bahaya ? LOW : HIGH);
#define LED_STATUS 2
// ============================================
// NODE INFO
// ============================================
#define NODE_ID "NODE_001"
#define GATEWAY_ID "GATEWAY_001"
#define DEVICE_ID 1
// ============================================
// TIMING
// ============================================
#define HEARTBEAT_INTERVAL 30000
#define SENSOR_READ_INTERVAL 50
// ============================================
// VIBRATION
// ============================================
#define VIB_DEBOUNCE 1500
// ============================================
// BUZZER PATTERN
// ============================================
enum BuzzerPattern {
BUZ_NONE,
BUZ_PIR,
BUZ_REED,
BUZ_VIB
};
// ============================================
// BUZZER STATE
// ============================================
BuzzerPattern currentPattern = BUZ_NONE;
int buzzerStep = 0;
unsigned long buzzerNextTime = 0;
// ============================================
// PIR STATE
// ============================================
bool pirStableState = false;
unsigned long pirLastChange = 0;
unsigned long pirTriggerTime = 0;
// ============================================
// REED STATE
// ============================================
bool reedWasOpen = false;
unsigned long reedOpenTime = 0;
// ============================================
// VIBRATION STATE
// ============================================
bool vibLastState = LOW;
unsigned long vibLastTrigger = 0;
// ============================================
// TIMING
// ============================================
unsigned long lastHeartbeat = 0;
unsigned long lastSensorRead = 0;
// ============================================
// STATISTIC
// ============================================
unsigned long totalSent = 0;
unsigned long totalFailed = 0;
// ============================================
// PROTOTYPE
// ============================================
void readPIR(unsigned long now);
void readReed(unsigned long now);
void readVibration(unsigned long now);
void startBuzzer(BuzzerPattern pattern);
void updateBuzzer(unsigned long now);
void sendPIRData(bool motion, int intensity, int duration, String zone);
void sendReedData(bool opened, int duration, bool forced);
void sendVibrationData();
void sendStatus();
void sendLoRa(StaticJsonDocument<256>& doc);
void sendLoRa(StaticJsonDocument<128>& doc);
void blinkLED(int delayMs);
void beep(int ms);
// ============================================
// BLINK LED
// ============================================
void blinkLED(int delayMs = 50) {
digitalWrite(LED_STATUS, HIGH);
delay(delayMs);
digitalWrite(LED_STATUS, LOW);
}
// ============================================
// BUZZER
// BEEP
// ============================================
void buzzerPIR() {
digitalWrite(BUZZER, HIGH); delay(300);
digitalWrite(BUZZER, LOW);
}
void buzzerREED() {
for (int i = 0; i < 2; i++) {
digitalWrite(BUZZER, HIGH); delay(100);
digitalWrite(BUZZER, LOW); delay(100);
}
}
void buzzerGETAR() {
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER, HIGH); delay(80);
digitalWrite(BUZZER, LOW); delay(80);
}
}
void buzzerStartup() {
for (int i = 0; i < 2; i++) {
digitalWrite(BUZZER, HIGH); delay(200);
digitalWrite(BUZZER, LOW); delay(100);
}
}
// ============================================
// KIRIM PAKET LORA
// Format: TYPE|NODE_ID|json_payload
// ============================================
bool loRaSend(String type, String jsonPayload) {
String packet = type + "|" + NODE_ID + "|" + jsonPayload;
LoRa.beginPacket();
LoRa.print(packet);
int result = LoRa.endPacket();
if (result) {
Serial.println("[LoRa TX] ✓ " + packet.substring(0, 80));
} else {
Serial.println("[LoRa TX] ✗ Gagal kirim paket");
}
return result;
}
// ============================================
// KIRIM DATA PIR
// ============================================
void kirimPIR(bool detected, int intensity, int duration) {
StaticJsonDocument<200> doc;
doc["motion_detected"] = detected;
doc["motion_intensity"] = intensity;
doc["duration_seconds"] = duration;
doc["detection_zone"] = "center";
String body;
serializeJson(doc, body);
Serial.println("[PIR] detected=" + String(detected) + " intensity=" + String(intensity) + " dur=" + String(duration) + "s");
loRaSend("PIR", body);
}
// ============================================
// KIRIM DATA REED SWITCH
// ============================================
void kirimReed(bool opened, int duration) {
StaticJsonDocument<200> doc;
doc["door_opened"] = opened;
doc["duration_seconds"] = duration;
doc["access_method"] = "manual";
doc["door_location"] = "rack";
doc["is_forced_entry"] = false;
String body;
serializeJson(doc, body);
Serial.println("[REED] opened=" + String(opened) + " dur=" + String(duration) + "s");
loRaSend("REED", body);
}
// ============================================
// KIRIM DATA GETARAN SW420
// ============================================
void kirimGetaran(float x, float y, float z) {
StaticJsonDocument<200> doc;
doc["x_axis"] = x;
doc["y_axis"] = y;
doc["z_axis"] = z;
doc["threshold"] = 2.0;
String body;
serializeJson(doc, body);
float mag = sqrt(x*x + y*y + z*z);
Serial.println("[SW420] magnitude=" + String(mag, 2));
loRaSend("VIBRATION", body);
}
// ============================================
// HEARTBEAT — beri tahu gateway node masih hidup
// ============================================
void kirimHeartbeat() {
StaticJsonDocument<64> doc;
doc["uptime_s"] = millis() / 1000;
String body;
serializeJson(doc, body);
Serial.println("[HEARTBEAT] uptime=" + String(millis()/1000) + "s");
loRaSend("HEARTBEAT", body);
void beep(int ms) {
digitalWrite(PIN_BUZZER, HIGH);
delay(ms);
digitalWrite(PIN_BUZZER, LOW);
}
// ============================================
@ -184,121 +137,301 @@ void kirimHeartbeat() {
// ============================================
void setup() {
Serial.begin(115200);
delay(1000);
delay(500);
Serial.println("\n==========================");
Serial.println(" SMART RACK NODE ");
Serial.println("==========================");
// Pin sensor
pinMode(PIR_PIN, INPUT);
pinMode(REED_PIN, INPUT_PULLUP);
pinMode(SW420_PIN, INPUT);
pinMode(PIN_SW420, INPUT_PULLUP);
pinMode(PIN_REED, INPUT_PULLUP);
pinMode(PIN_PIR, INPUT);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(LED_STATUS, OUTPUT);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(LED_STATUS, LOW);
// Pin output
pinMode(BUZZER, OUTPUT);
pinMode(LED_MERAH, OUTPUT);
pinMode(LED_HIJAU, OUTPUT);
// Startup beep
beep(80); delay(80); beep(80);
digitalWrite(BUZZER, LOW);
setLED(false); // LED hijau nyala default
Serial.println("=================================");
Serial.println(" SMART RACK — NODE SENDER");
Serial.println(" Node ID : " NODE_ID);
Serial.println("=================================");
// Init LoRa
// =========================================
// LORA
// =========================================
Serial.println("[1] LoRa setPins");
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
Serial.println("[2] Starting LoRa...");
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("[LoRa] GAGAL INIT! Cek wiring.");
// Blink LED merah terus sebagai tanda error
while (true) {
digitalWrite(LED_MERAH, HIGH); delay(200);
digitalWrite(LED_MERAH, LOW); delay(200);
Serial.println("[LoRa] INIT FAILED");
while (1) {
digitalWrite(PIN_BUZZER, HIGH);
digitalWrite(LED_STATUS, HIGH);
delay(100);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(LED_STATUS, LOW);
delay(100);
}
}
LoRa.setTxPower(17);
LoRa.setSpreadingFactor(9); // SF9 — balance range vs speed
LoRa.setSignalBandwidth(125E3);
LoRa.setCodingRate4(5);
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setCodingRate4(LORA_CR);
LoRa.setSyncWord(LORA_SYNC_WORD);
Serial.println("[3] LoRa READY");
Serial.println("[LoRa] OK — " + String(LORA_FREQ / 1E6) + " MHz");
// PIR stabilization
delay(5000);
for (int i = 0; i < 3; i++) {
blinkLED(100);
delay(100);
}
buzzerStartup();
Serial.println("Node siap!");
sendStatus();
lastHeartbeat = millis();
Serial.println("[NODE] STARTED");
}
// ============================================
// LOOP
// ============================================
void loop() {
pirState = digitalRead(PIR_PIN);
reedState = digitalRead(REED_PIN);
vibrationState = digitalRead(SW420_PIN);
unsigned long now = millis();
// =========================================
// PIR — Deteksi Gerakan
// =========================================
if (pirState == HIGH && !motionActive) {
motionActive = true;
motionStartTime = millis();
Serial.println(">>> PIR: Gerakan terdeteksi!");
buzzerPIR();
setLED(true);
}
else if (pirState == LOW && motionActive) {
motionActive = false;
int duration = (millis() - motionStartTime) / 1000;
int intensity = random(60, 90); // estimasi intensitas
kirimPIR(true, intensity, duration);
Serial.println(">>> PIR: Selesai, durasi=" + String(duration) + "s");
if (now - lastSensorRead >= SENSOR_READ_INTERVAL) {
lastSensorRead = now;
readPIR(now);
readReed(now);
readVibration(now);
}
// =========================================
// REED SWITCH — Status Rak
// =========================================
if (reedState == HIGH && !doorOpen) {
doorOpen = true;
doorOpenTime = millis();
Serial.println(">>> REED: Rak terbuka!");
buzzerREED();
setLED(true);
}
else if (reedState == LOW && doorOpen) {
doorOpen = false;
int duration = (millis() - doorOpenTime) / 1000;
kirimReed(true, duration);
Serial.println(">>> REED: Rak tertutup, durasi=" + String(duration) + "s");
}
updateBuzzer(now);
// =========================================
// SW-420 — Deteksi Getaran
// =========================================
if (vibrationState == HIGH && lastVibration == LOW) {
Serial.println(">>> SW420: Getaran terdeteksi!");
// Simulasi nilai axis — ganti dengan sensor akselerometer nyata jika ada
float x = random(-300, 300) / 100.0;
float y = random(-300, 300) / 100.0;
float z = random(80, 120) / 100.0;
buzzerGETAR();
setLED(true);
kirimGetaran(x, y, z);
if (now - lastHeartbeat >= HEARTBEAT_INTERVAL) {
sendStatus();
lastHeartbeat = now;
}
}
// ============================================
// PIR
// ============================================
void readPIR(unsigned long now) {
bool current = digitalRead(PIN_PIR);
if (current != pirStableState && now - pirLastChange > 200) {
pirLastChange = now;
pirStableState = current;
if (pirStableState) {
pirTriggerTime = now;
Serial.println("[PIR] DETECTED");
startBuzzer(BUZ_PIR);
sendPIRData(true, 75, 0, "center");
} else {
int duration = (now - pirTriggerTime) / 1000;
Serial.println("[PIR] END");
sendPIRData(false, 0, duration, "center");
}
}
}
// ============================================
// REED
// ============================================
void readReed(unsigned long now) {
static bool lastStable = LOW;
static unsigned long lastDebounce = 0;
bool current = digitalRead(PIN_REED);
if (current != lastStable) {
if (now - lastDebounce > 150) {
lastDebounce = now;
if (current == HIGH) {
reedOpenTime = now;
reedWasOpen = true;
Serial.println("[REED] OPEN");
startBuzzer(BUZ_REED);
sendReedData(true, 0, false);
} else {
int duration = reedWasOpen ? (now - reedOpenTime) / 1000 : 0;
reedWasOpen = false;
Serial.println("[REED] CLOSE");
startBuzzer(BUZ_REED);
sendReedData(false, duration, false);
}
lastStable = current;
}
}
}
// ============================================
// VIBRATION
// ============================================
void readVibration(unsigned long now) {
bool current = digitalRead(PIN_SW420);
if (current == HIGH && vibLastState == LOW) {
if (now - vibLastTrigger > VIB_DEBOUNCE) {
vibLastTrigger = now;
Serial.println("[VIB] DETECTED");
startBuzzer(BUZ_VIB);
sendVibrationData();
}
}
vibLastState = current;
}
// ============================================
// START BUZZER
// ============================================
void startBuzzer(BuzzerPattern pattern) {
digitalWrite(PIN_BUZZER, LOW);
currentPattern = pattern;
buzzerStep = 0;
buzzerNextTime = millis();
}
// ============================================
// UPDATE BUZZER
// ============================================
void updateBuzzer(unsigned long now) {
if (currentPattern == BUZ_NONE) return;
if (now < buzzerNextTime) return;
// PIR: 3x beep pendek
if (currentPattern == BUZ_PIR) {
if (buzzerStep < 6) {
digitalWrite(PIN_BUZZER, buzzerStep % 2 == 0 ? HIGH : LOW);
buzzerNextTime = now + 80;
buzzerStep++;
} else {
digitalWrite(PIN_BUZZER, LOW);
currentPattern = BUZ_NONE;
}
}
// REED: 1x beep panjang
else if (currentPattern == BUZ_REED) {
if (buzzerStep == 0) {
digitalWrite(PIN_BUZZER, HIGH);
buzzerNextTime = now + 600;
buzzerStep++;
} else {
digitalWrite(PIN_BUZZER, LOW);
currentPattern = BUZ_NONE;
}
}
// VIBRATION: 2x beep sedang
else if (currentPattern == BUZ_VIB) {
if (buzzerStep < 4) {
digitalWrite(PIN_BUZZER, buzzerStep % 2 == 0 ? HIGH : LOW);
buzzerNextTime = now + (buzzerStep % 2 == 0 ? 150 : 100);
buzzerStep++;
} else {
digitalWrite(PIN_BUZZER, LOW);
currentPattern = BUZ_NONE;
}
}
}
// ============================================
// SEND PIR
// ============================================
void sendPIRData(bool motion, int intensity, int duration, String zone) {
StaticJsonDocument<256> doc;
doc["type"] = "PIR";
doc["node_id"] = NODE_ID;
doc["gateway_id"] = GATEWAY_ID;
doc["device_id"] = DEVICE_ID;
doc["motion_detected"] = motion;
doc["motion_intensity"] = intensity;
doc["duration_seconds"] = duration;
doc["detection_zone"] = zone;
sendLoRa(doc);
}
// ============================================
// SEND REED
// ============================================
void sendReedData(bool opened, int duration, bool forced) {
StaticJsonDocument<256> doc;
doc["type"] = "REED";
doc["node_id"] = NODE_ID;
doc["gateway_id"] = GATEWAY_ID;
doc["device_id"] = DEVICE_ID;
doc["door_opened"] = opened;
doc["duration_seconds"] = duration;
doc["access_method"] = "manual";
doc["door_location"] = "rack";
doc["is_forced_entry"] = forced;
sendLoRa(doc);
}
// ============================================
// SEND VIBRATION
// SW-420 = sensor digital (on/off), tidak ada sumbu XYZ fisik.
// Nilai X/Y/Z disimulasikan agar API dapat hitung magnitude.
// ============================================
void sendVibrationData() {
StaticJsonDocument<256> doc;
doc["type"] = "VIBRATION";
doc["node_id"] = NODE_ID;
doc["gateway_id"] = GATEWAY_ID;
doc["device_id"] = DEVICE_ID;
doc["x_axis"] = random(-300, 300) / 100.0;
doc["y_axis"] = random(-300, 300) / 100.0;
doc["z_axis"] = 2.0 + random(0, 150) / 100.0; // Z selalu di atas threshold
doc["threshold"] = 2.0;
sendLoRa(doc);
}
// ============================================
// SEND STATUS (heartbeat)
// ============================================
void sendStatus() {
StaticJsonDocument<128> doc;
doc["type"] = "STATUS";
doc["node_id"] = NODE_ID;
doc["gateway_id"] = GATEWAY_ID;
doc["payload"] = "gateway hidup";
doc["uptime"] = millis() / 1000;
sendLoRa(doc);
Serial.println("[STATUS] SENT");
}
// ============================================
// SEND LORA — 256 byte doc
// ============================================
void sendLoRa(StaticJsonDocument<256>& doc) {
String payload;
serializeJson(doc, payload);
Serial.println("[LoRa TX] -> " + payload);
LoRa.beginPacket();
LoRa.print(payload);
int result = LoRa.endPacket();
if (result) {
totalSent++;
blinkLED(50);
Serial.println("[LoRa] SUCCESS");
} else {
totalFailed++;
Serial.println("[LoRa] FAILED");
}
}
// ============================================
// SEND LORA — 128 byte doc
// ============================================
void sendLoRa(StaticJsonDocument<128>& doc) {
String payload;
serializeJson(doc, payload);
Serial.println("[LoRa TX] -> " + payload);
LoRa.beginPacket();
LoRa.print(payload);
int result = LoRa.endPacket();
if (result) {
totalSent++;
blinkLED(50);
} else {
totalFailed++;
}
// =========================================
// Reset LED jika semua aman
// =========================================
if (!motionActive && !doorOpen && vibrationState == LOW) {
setLED(false);
}
lastVibration = vibrationState;
// =========================================
// HEARTBEAT setiap 2 menit
// =========================================
if (millis() - lastHeartbeat >= HEARTBEAT_INTERVAL) {
kirimHeartbeat();
lastHeartbeat = millis();
}
delay(300);
}