first commit
This commit is contained in:
commit
95cc051f4d
|
@ -0,0 +1,445 @@
|
|||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <kontrol_suara_inferencing.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2s.h"
|
||||
|
||||
// === WiFi & MQTT ===
|
||||
const char* ssid = "Koala";
|
||||
const char* password = "joinprayerdulu";
|
||||
const char* mqtt_server = "test.mosquitto.org";
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
bool mqtt_connected = false;
|
||||
|
||||
|
||||
// === Variabel LED saat rekaman window audio ===
|
||||
bool window_led_active = false;
|
||||
unsigned long window_listen_start_time = 0;
|
||||
const unsigned long window_duration_ms = (EI_CLASSIFIER_RAW_SAMPLE_COUNT * 1000UL) / EI_CLASSIFIER_FREQUENCY;
|
||||
bool voiceAutoLockInProgress = false;
|
||||
unsigned long voiceAutoLockStart = 0;
|
||||
unsigned long ledper_last_blink = 0;
|
||||
bool ledper_state = false;
|
||||
const unsigned long ledper_blink_interval = 100;
|
||||
bool skip_blink_active = false;
|
||||
|
||||
|
||||
// === Mode Wake Word dan Perintah ===
|
||||
bool wake_word_active = false;
|
||||
unsigned long command_start_time = 0;
|
||||
const unsigned long command_window_ms =20000;
|
||||
|
||||
|
||||
// === Output Pin ===
|
||||
#define PIN_LAMPU_KAMAR 15
|
||||
#define PIN_LAMPU_TERAS 4
|
||||
#define PIN_KIPAS 5
|
||||
#define PIN_SOLENOID 18
|
||||
#define PIN_LED 19
|
||||
#define PIN_LEDPER 21
|
||||
|
||||
// === Inferensi Audio ===
|
||||
typedef struct {
|
||||
signed short* buffers[2];
|
||||
unsigned char buf_select;
|
||||
unsigned char buf_ready;
|
||||
unsigned int buf_count;
|
||||
unsigned int n_samples;
|
||||
} inference_t;
|
||||
|
||||
static inference_t inference;
|
||||
static const uint32_t sample_buffer_size = 2048;
|
||||
static signed short sampleBuffer[sample_buffer_size];
|
||||
static bool debug_nn = false;
|
||||
static int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);
|
||||
static bool record_status = true;
|
||||
static TaskHandle_t captureTaskHandle = NULL;
|
||||
|
||||
// === I2S Audio ===
|
||||
static int i2s_init(uint32_t sampling_rate) {
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
|
||||
.sample_rate = sampling_rate,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_I2S,
|
||||
.intr_alloc_flags = 0,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 512,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = -1,
|
||||
};
|
||||
i2s_pin_config_t pin_config = {
|
||||
.bck_io_num = 26,
|
||||
.ws_io_num = 32,
|
||||
.data_out_num = -1,
|
||||
.data_in_num = 33,
|
||||
};
|
||||
|
||||
esp_err_t ret = i2s_driver_install(I2S_NUM_1, &i2s_config, 0, NULL);
|
||||
if (ret != ESP_OK) ei_printf("i2s_driver_install error\n");
|
||||
ret = i2s_set_pin(I2S_NUM_1, &pin_config);
|
||||
if (ret != ESP_OK) ei_printf("i2s_set_pin error\n");
|
||||
i2s_zero_dma_buffer(I2S_NUM_1);
|
||||
return ret == ESP_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
static void audio_inference_callback(uint32_t n_bytes) {
|
||||
for (int i = 0; i < n_bytes >> 1; i++) {
|
||||
inference.buffers[inference.buf_select][inference.buf_count++] = sampleBuffer[i];
|
||||
if (inference.buf_count >= inference.n_samples) {
|
||||
inference.buf_select ^= 1;
|
||||
inference.buf_count = 0;
|
||||
inference.buf_ready = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void capture_samples(void* arg) {
|
||||
size_t bytes_read = sample_buffer_size;
|
||||
while (record_status) {
|
||||
i2s_read(I2S_NUM_1, (void*)sampleBuffer, sample_buffer_size, &bytes_read, 100);
|
||||
if (bytes_read > 0) {
|
||||
for (int x = 0; x < bytes_read / 2; x++) {
|
||||
sampleBuffer[x] = (int16_t)(sampleBuffer[x]) * 16;
|
||||
}
|
||||
audio_inference_callback(bytes_read);
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static bool microphone_inference_start(uint32_t n_samples) {
|
||||
inference.buffers[0] = (short*)malloc(n_samples * sizeof(short));
|
||||
inference.buffers[1] = (short*)malloc(n_samples * sizeof(short));
|
||||
if (!inference.buffers[0] || !inference.buffers[1]) return false;
|
||||
|
||||
inference.buf_select = 0;
|
||||
inference.buf_count = 0;
|
||||
inference.n_samples = n_samples;
|
||||
inference.buf_ready = 0;
|
||||
|
||||
if (i2s_init(EI_CLASSIFIER_FREQUENCY) != 0) return false;
|
||||
record_status = true;
|
||||
|
||||
if (captureTaskHandle == NULL) {
|
||||
xTaskCreate(capture_samples, "CaptureSamples", 1024 * 32, NULL, 10, &captureTaskHandle);
|
||||
ei_printf("🎙 Task audio capture dimulai\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool microphone_inference_record(void) {
|
||||
if (inference.buf_ready == 1) return false;
|
||||
while (inference.buf_ready == 0) delay(1);
|
||||
inference.buf_ready = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int microphone_audio_signal_get_data(size_t offset, size_t length, float* out_ptr) {
|
||||
numpy::int16_to_float(&inference.buffers[inference.buf_select ^ 1][offset], out_ptr, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// === WiFi Setup ===
|
||||
void setup_wifi() {
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("\n✅ WiFi terhubung!");
|
||||
}
|
||||
|
||||
// === MQTT Callback ===
|
||||
bool autoLockInProgress = false;
|
||||
unsigned long autoLockStart = 0;
|
||||
|
||||
void publishMQTT(const char* topic, const char* message) {
|
||||
if (client.connected()) client.publish(topic, message, true);
|
||||
}
|
||||
|
||||
void publishStatus(const char* device, bool state) {
|
||||
String topic = String("cristine/status/") + device;
|
||||
const char* message = state ? "on" : "off";
|
||||
publishMQTT(topic.c_str(), message);
|
||||
}
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
String message;
|
||||
for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
|
||||
message.trim();
|
||||
message.toLowerCase();
|
||||
|
||||
if (String(topic) == "cristine/control/lampu_kamar") {
|
||||
bool state = (message == "on");
|
||||
digitalWrite(PIN_LAMPU_KAMAR, state ? HIGH : LOW);
|
||||
publishStatus("lampu_kamar", state);
|
||||
ei_printf("📡 Lampu kamar %s via MQTT\n", state ? "dinyalakan" : "dimatikan");
|
||||
} else if (String(topic) == "cristine/control/lampu_teras") {
|
||||
bool state = (message == "on");
|
||||
digitalWrite(PIN_LAMPU_TERAS, state ? HIGH : LOW);
|
||||
publishStatus("lampu_teras", state);
|
||||
ei_printf("📡 Lampu teras %s via MQTT\n", state ? "dinyalakan" : "dimatikan");
|
||||
} else if (String(topic) == "cristine/control/kipas") {
|
||||
bool state = (message == "on");
|
||||
digitalWrite(PIN_KIPAS, state ? HIGH : LOW);
|
||||
publishStatus("kipas", state);
|
||||
ei_printf("📡 Kipas %s via MQTT\n", state ? "dinyalakan" : "dimatikan");
|
||||
} else if (String(topic) == "cristine/control/pintu") {
|
||||
if (message == "on" && !autoLockInProgress) {
|
||||
ei_printf("📡 Pintu dibuka via MQTT\n");
|
||||
digitalWrite(PIN_SOLENOID, HIGH);
|
||||
publishStatus("pintu", true);
|
||||
autoLockStart = millis();
|
||||
autoLockInProgress = true;
|
||||
}
|
||||
} else if (String(topic) == "cristine/request/status") {
|
||||
String statusPayload = "{";
|
||||
statusPayload += "\"lampu kamar\": " + String(digitalRead(PIN_LAMPU_KAMAR) == HIGH ? "true" : "false") + ",";
|
||||
statusPayload += "\"lampu teras\": " + String(digitalRead(PIN_LAMPU_TERAS) == HIGH ? "true" : "false") + ",";
|
||||
statusPayload += "\"kipas\": " + String(digitalRead(PIN_KIPAS) == HIGH ? "true" : "false") + ",";
|
||||
statusPayload += "\"pintu\": \"" + String(digitalRead(PIN_SOLENOID) == HIGH ? "dibuka" : "dikunci") + "\"";
|
||||
statusPayload += "}";
|
||||
client.publish("cristine/status", statusPayload.c_str());
|
||||
ei_printf("📤 Status terkirim ke bot WhatsApp\n");
|
||||
}
|
||||
}
|
||||
|
||||
// === MQTT Reconnect ===
|
||||
void reconnect() {
|
||||
while (!client.connected()) {
|
||||
Serial.println("🔁 Reconnecting MQTT...");
|
||||
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
|
||||
if (client.connect(clientId.c_str())) {
|
||||
Serial.println("✅ MQTT Connected!");
|
||||
mqtt_connected = true;
|
||||
|
||||
client.subscribe("cristine/control/lampu_kamar");
|
||||
client.subscribe("cristine/control/lampu_teras");
|
||||
client.subscribe("cristine/control/kipas");
|
||||
client.subscribe("cristine/control/pintu");
|
||||
client.subscribe("cristine/request/status");
|
||||
} else {
|
||||
Serial.print("❌ MQTT gagal, rc=");
|
||||
Serial.println(client.state());
|
||||
mqtt_connected = false;
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Stop Inference ===
|
||||
void stop_inference() {
|
||||
record_status = false;
|
||||
if (captureTaskHandle != NULL) {
|
||||
vTaskDelete(captureTaskHandle);
|
||||
captureTaskHandle = NULL;
|
||||
}
|
||||
i2s_driver_uninstall(I2S_NUM_1);
|
||||
free(inference.buffers[0]);
|
||||
free(inference.buffers[1]);
|
||||
ei_printf("🛑 Inferensi dihentikan\n");
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
setup_wifi();
|
||||
client.setServer(mqtt_server, 1883);
|
||||
client.setCallback(callback);
|
||||
|
||||
pinMode(PIN_LAMPU_KAMAR, OUTPUT);
|
||||
pinMode(PIN_LAMPU_TERAS, OUTPUT);
|
||||
pinMode(PIN_KIPAS, OUTPUT);
|
||||
pinMode(PIN_SOLENOID, OUTPUT);
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
pinMode(PIN_LEDPER, OUTPUT);
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
digitalWrite(PIN_LEDPER, LOW);
|
||||
|
||||
run_classifier_init();
|
||||
ei_printf("\n🎬 Menunggu koneksi MQTT...\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
client.loop();
|
||||
if (!client.connected()) {
|
||||
mqtt_connected = false;
|
||||
if (captureTaskHandle != NULL) stop_inference();
|
||||
reconnect();
|
||||
}
|
||||
|
||||
if (autoLockInProgress && millis() - autoLockStart >= 10000) {
|
||||
digitalWrite(PIN_SOLENOID, LOW);
|
||||
publishStatus("pintu", false);
|
||||
ei_printf("🔒 Pintu dikunci otomatis setelah 10 detik\n");
|
||||
autoLockInProgress = false;
|
||||
}
|
||||
|
||||
if (voiceAutoLockInProgress && millis() - voiceAutoLockStart >= 10000) {
|
||||
digitalWrite(PIN_SOLENOID, LOW);
|
||||
publishStatus("pintu", false);
|
||||
ei_printf("🔒 Pintu dikunci otomatis setelah 10 detik (via suara)\n");
|
||||
voiceAutoLockInProgress = false;
|
||||
}
|
||||
|
||||
if (mqtt_connected && captureTaskHandle == NULL) {
|
||||
ei_printf("🎙 Mulai inferensi\n");
|
||||
if (!microphone_inference_start(EI_CLASSIFIER_SLICE_SIZE)) return;
|
||||
}
|
||||
|
||||
if (!microphone_inference_record()) return;
|
||||
|
||||
// === LED Perintah: Berkedip saat skip aktif
|
||||
if (skip_blink_active) {
|
||||
if (millis() - ledper_last_blink >= ledper_blink_interval) {
|
||||
ledper_last_blink = millis();
|
||||
ledper_state = !ledper_state;
|
||||
digitalWrite(PIN_LEDPER, ledper_state);
|
||||
}
|
||||
}
|
||||
|
||||
// === LED Indikator Window Audio
|
||||
if (!window_led_active) {
|
||||
window_listen_start_time = millis();
|
||||
window_led_active = true;
|
||||
digitalWrite(PIN_LED, HIGH);
|
||||
}
|
||||
if (window_led_active && millis() - window_listen_start_time >= window_duration_ms) {
|
||||
window_led_active = false;
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
}
|
||||
|
||||
// === Inference Voice Command
|
||||
signal_t signal;
|
||||
signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
|
||||
signal.get_data = µphone_audio_signal_get_data;
|
||||
ei_impulse_result_t result = { 0 };
|
||||
EI_IMPULSE_ERROR r = run_classifier_continuous(&signal, &result, debug_nn);
|
||||
if (r != EI_IMPULSE_OK) return;
|
||||
|
||||
static int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);
|
||||
static bool skip_next_command_window = false;
|
||||
static unsigned long perintah_start_time = 0;
|
||||
|
||||
if (++print_results >= EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW) {
|
||||
print_results = 0;
|
||||
|
||||
const char* top_label = "";
|
||||
float top_score = 0.0f;
|
||||
float silent_score = 0.0f, unknown_score = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
|
||||
float score = result.classification[i].value;
|
||||
if (score > top_score) {
|
||||
top_score = score;
|
||||
top_label = result.classification[i].label;
|
||||
}
|
||||
|
||||
if (strcmp(result.classification[i].label, "silent") == 0) silent_score = score;
|
||||
if (strcmp(result.classification[i].label, "unknown") == 0) unknown_score = score;
|
||||
}
|
||||
|
||||
bool noise_only = (silent_score > 0.7f || unknown_score > 0.7f);
|
||||
|
||||
// === Logging Confidence
|
||||
if (!wake_word_active && !skip_next_command_window) {
|
||||
ei_printf("[Menunggu Wake Word] %s: %.2f\n", top_label, top_score);
|
||||
} else if (wake_word_active && skip_next_command_window && print_results == 0) {
|
||||
ei_printf("[Lewati 1 Window] %s: %.2f\n", top_label, top_score);
|
||||
} else if (wake_word_active && !skip_next_command_window) {
|
||||
ei_printf("[Mode Perintah] %s: %.2f\n", top_label, top_score);
|
||||
}
|
||||
|
||||
// === Wake Word Detection
|
||||
if (!wake_word_active && strcmp(top_label, "wake_word") == 0 && top_score > 0.90f && !noise_only) {
|
||||
ei_printf("🟢 Wake word terdeteksi, masuk ke mode perintah...\n");
|
||||
wake_word_active = true;
|
||||
skip_next_command_window = true;
|
||||
skip_blink_active = true;
|
||||
perintah_start_time = millis();
|
||||
}
|
||||
|
||||
// === Mode Perintah
|
||||
else if (wake_word_active) {
|
||||
unsigned long elapsed = millis() - perintah_start_time;
|
||||
|
||||
// LED perintah: berkedip saat skip, nyala tetap saat mode perintah aktif
|
||||
if (skip_next_command_window) {
|
||||
if (millis() - ledper_last_blink >= ledper_blink_interval) {
|
||||
ledper_last_blink = millis();
|
||||
ledper_state = !ledper_state;
|
||||
digitalWrite(PIN_LEDPER, ledper_state);
|
||||
}
|
||||
|
||||
// Pastikan hanya 1 window dilewati (durasi window)
|
||||
if (elapsed >= window_duration_ms) {
|
||||
skip_next_command_window = false;
|
||||
skip_blink_active = false;
|
||||
ei_printf("✅ Mode perintah aktif, siap menerima perintah\n");
|
||||
digitalWrite(PIN_LEDPER, HIGH); // nyala tetap setelah skip
|
||||
}
|
||||
} else {
|
||||
digitalWrite(PIN_LEDPER, HIGH);
|
||||
}
|
||||
|
||||
if (elapsed >= command_window_ms) {
|
||||
ei_printf("⏱ Timeout! Kembali ke mode wake word\n");
|
||||
wake_word_active = false;
|
||||
skip_next_command_window = false;
|
||||
digitalWrite(PIN_LEDPER, LOW);
|
||||
}
|
||||
|
||||
else if (!skip_next_command_window) {
|
||||
if (strcmp(top_label, "silent") != 0 &&
|
||||
strcmp(top_label, "unknown") != 0 &&
|
||||
strcmp(top_label, "wake_word") != 0 &&
|
||||
top_score > 0.88f) {
|
||||
|
||||
ei_printf("🗣 Perintah terdeteksi: %s (%.2f)\n", top_label, top_score);
|
||||
|
||||
if (strcmp(top_label, "nyalakan_lampu_kamar") == 0) {
|
||||
digitalWrite(PIN_LAMPU_KAMAR, HIGH);
|
||||
publishMQTT("cristine/control/lampu_kamar", "on");
|
||||
publishStatus("lampu_kamar", true);
|
||||
} else if (strcmp(top_label, "matikan_lampu_kamar") == 0) {
|
||||
digitalWrite(PIN_LAMPU_KAMAR, LOW);
|
||||
publishMQTT("cristine/control/lampu_kamar", "off");
|
||||
publishStatus("lampu_kamar", false);
|
||||
} else if (strcmp(top_label, "nyalakan_lampu_teras") == 0) {
|
||||
digitalWrite(PIN_LAMPU_TERAS, HIGH);
|
||||
publishMQTT("cristine/control/lampu_teras", "on");
|
||||
publishStatus("lampu_teras", true);
|
||||
} else if (strcmp(top_label, "matikan_lampu_teras") == 0) {
|
||||
digitalWrite(PIN_LAMPU_TERAS, LOW);
|
||||
publishMQTT("cristine/control/lampu_teras", "off");
|
||||
publishStatus("lampu_teras", false);
|
||||
} else if (strcmp(top_label, "nyalakan_kipas") == 0) {
|
||||
digitalWrite(PIN_KIPAS, HIGH);
|
||||
publishMQTT("cristine/control/kipas", "on");
|
||||
publishStatus("kipas", true);
|
||||
} else if (strcmp(top_label, "matikan_kipas") == 0) {
|
||||
digitalWrite(PIN_KIPAS, LOW);
|
||||
publishMQTT("cristine/control/kipas", "off");
|
||||
publishStatus("kipas", false);
|
||||
} else if (strcmp(top_label, "buka_pintu") == 0) {
|
||||
digitalWrite(PIN_SOLENOID, HIGH);
|
||||
publishMQTT("cristine/control/pintu", "on");
|
||||
publishStatus("pintu", true);
|
||||
voiceAutoLockStart = millis();
|
||||
voiceAutoLockInProgress = true;
|
||||
} else {
|
||||
ei_printf("❓ Perintah '%s' tidak dikenali\n", top_label);
|
||||
}
|
||||
|
||||
wake_word_active = false;
|
||||
digitalWrite(PIN_LEDPER, LOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,212 @@
|
|||
import pkg, { DisconnectReason } from '@whiskeysockets/baileys';
|
||||
import pino from 'pino';
|
||||
import mqtt from 'mqtt';
|
||||
import fs from 'fs';
|
||||
|
||||
const {
|
||||
makeWASocket,
|
||||
useMultiFileAuthState,
|
||||
fetchLatestBaileysVersion,
|
||||
} = pkg;
|
||||
|
||||
const allowedNumbers = [
|
||||
'62882009051780@s.whatsapp.net',
|
||||
'6287862977046@s.whatsapp.net',
|
||||
'6289521851079@s.whatsapp.net',
|
||||
];
|
||||
|
||||
// Status lokal perabotan (backup jika diperlukan)
|
||||
const status = {
|
||||
'lampu kamar': false,
|
||||
'lampu teras': false,
|
||||
'kipas': false,
|
||||
'pintu': false,
|
||||
};
|
||||
|
||||
// Penyimpan siapa yang meminta status
|
||||
const pendingStatusRequests = {};
|
||||
|
||||
// Fungsi konversi status jadi teks
|
||||
|
||||
function getStatusString(statusObj) {
|
||||
return Object.entries(statusObj)
|
||||
.map(([device, value]) => {
|
||||
if (device === 'pintu') {
|
||||
return `- ${device} : ${value === 'dibuka' ? '🔓 Dibuka' : '🔒 Dikunci'}`;
|
||||
} else {
|
||||
return `- ${device} : ${value === true || value === 'true' ? '✅ Menyala' : '❌ Mati'}`;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
// MQTT setup
|
||||
const mqttClient = mqtt.connect('mqtt://test.mosquitto.org');
|
||||
|
||||
mqttClient.on('connect', () => {
|
||||
console.log('📡 Terhubung ke MQTT broker: test.mosquitto.org');
|
||||
});
|
||||
|
||||
mqttClient.on('error', (err) => {
|
||||
console.error('❌ MQTT Error:', err);
|
||||
});
|
||||
|
||||
// Tangani balasan dari ESP32 ke topik status
|
||||
mqttClient.on('message', async (topic, message) => {
|
||||
if (topic === 'cristine/status') {
|
||||
try {
|
||||
const parsedStatus = JSON.parse(message.toString());
|
||||
const now = Date.now();
|
||||
|
||||
for (const [jid, time] of Object.entries(pendingStatusRequests)) {
|
||||
if (now - time <= 10000) { // valid 10 detik
|
||||
await sock.sendMessage(jid, {
|
||||
text: `📊 Status Real-time:\n${getStatusString(parsedStatus)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Kosongkan request setelah dibalas
|
||||
for (const jid of Object.keys(pendingStatusRequests)) {
|
||||
delete pendingStatusRequests[jid];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Gagal parsing status dari ESP32:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mqttClient.subscribe('cristine/status');
|
||||
|
||||
// Fungsi utama bot
|
||||
let sock;
|
||||
async function startBot() {
|
||||
const { version } = await fetchLatestBaileysVersion();
|
||||
const { state, saveCreds } = await useMultiFileAuthState('./auth_info_baileys');
|
||||
|
||||
sock = makeWASocket({
|
||||
version,
|
||||
auth: state,
|
||||
printQRInTerminal: true,
|
||||
logger: pino({ level: 'silent' }),
|
||||
});
|
||||
|
||||
sock.ev.on('creds.update', saveCreds);
|
||||
|
||||
sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => {
|
||||
if (connection === 'close') {
|
||||
const reasonCode = lastDisconnect?.error?.output?.statusCode;
|
||||
const isLoggedOut = reasonCode === DisconnectReason.loggedOut;
|
||||
|
||||
console.log('⚠️ Koneksi terputus:', reasonCode);
|
||||
|
||||
if (isLoggedOut) {
|
||||
fs.rmSync('./auth_info_baileys', { recursive: true, force: true });
|
||||
console.log('🔁 Silakan restart untuk login ulang.');
|
||||
} else {
|
||||
startBot();
|
||||
}
|
||||
}
|
||||
|
||||
if (connection === 'open') {
|
||||
console.log('✅ Bot WhatsApp berhasil online!');
|
||||
}
|
||||
});
|
||||
|
||||
sock.ev.on('messages.upsert', async ({ messages }) => {
|
||||
const msg = messages[0];
|
||||
if (!msg.message || msg.key.fromMe) return;
|
||||
|
||||
const sender = msg.key.remoteJid;
|
||||
if (!allowedNumbers.includes(sender)) return;
|
||||
|
||||
const text =
|
||||
msg.message.conversation ||
|
||||
msg.message?.extendedTextMessage?.text ||
|
||||
msg.message?.imageMessage?.caption ||
|
||||
msg.message?.buttonsResponseMessage?.selectedButtonId ||
|
||||
msg.message?.listResponseMessage?.singleSelectReply?.selectedRowId;
|
||||
|
||||
if (!text) return;
|
||||
const command = text.trim().toLowerCase();
|
||||
console.log(`📩 Pesan dari ${sender}:`, command);
|
||||
|
||||
switch (command) {
|
||||
case 'menu':
|
||||
await sock.sendMessage(sender, {
|
||||
text: '📋 Menu Kontrol:\n' +
|
||||
'- nyalakan lampu kamar\n' +
|
||||
'- matikan lampu kamar\n' +
|
||||
'- nyalakan lampu teras\n' +
|
||||
'- matikan lampu teras\n' +
|
||||
'- nyalakan kipas\n' +
|
||||
'- matikan kipas\n' +
|
||||
'- buka pintu\n' +
|
||||
'- kunci pintu\n' +
|
||||
'- status',
|
||||
});
|
||||
break;
|
||||
|
||||
case 'nyalakan lampu kamar':
|
||||
status['lampu kamar'] = true;
|
||||
mqttClient.publish('cristine/control/lampu_kamar', 'ON');
|
||||
await sock.sendMessage(sender, { text: '💡 Lampu kamar *dinyalakan*.' });
|
||||
break;
|
||||
|
||||
case 'matikan lampu kamar':
|
||||
status['lampu kamar'] = false;
|
||||
mqttClient.publish('cristine/control/lampu_kamar', 'OFF');
|
||||
await sock.sendMessage(sender, { text: '💡 Lampu kamar *dimatikan*.' });
|
||||
break;
|
||||
|
||||
case 'nyalakan lampu teras':
|
||||
status['lampu teras'] = true;
|
||||
mqttClient.publish('cristine/control/lampu_teras', 'ON');
|
||||
await sock.sendMessage(sender, { text: '💡 Lampu teras *dinyalakan*.' });
|
||||
break;
|
||||
|
||||
case 'matikan lampu teras':
|
||||
status['lampu teras'] = false;
|
||||
mqttClient.publish('cristine/control/lampu_teras', 'OFF');
|
||||
await sock.sendMessage(sender, { text: '💡 Lampu teras *dimatikan*.' });
|
||||
break;
|
||||
|
||||
case 'nyalakan kipas':
|
||||
status['kipas'] = true;
|
||||
mqttClient.publish('cristine/control/kipas', 'ON');
|
||||
await sock.sendMessage(sender, { text: '🌀 Kipas *dinyalakan*.' });
|
||||
break;
|
||||
|
||||
case 'matikan kipas':
|
||||
status['kipas'] = false;
|
||||
mqttClient.publish('cristine/control/kipas', 'OFF');
|
||||
await sock.sendMessage(sender, { text: '🌀 Kipas *dimatikan*.' });
|
||||
break;
|
||||
|
||||
case 'buka pintu':
|
||||
status['pintu'] = true;
|
||||
mqttClient.publish('cristine/control/pintu', 'ON');
|
||||
await sock.sendMessage(sender, { text: '🚪 Pintu *dibuka*.' });
|
||||
break;
|
||||
|
||||
case 'kunci pintu':
|
||||
status['pintu'] = false;
|
||||
mqttClient.publish('cristine/control/pintu', 'OFF');
|
||||
await sock.sendMessage(sender, { text: '🚪 Pintu *dikunci*.' });
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
pendingStatusRequests[sender] = Date.now();
|
||||
mqttClient.publish('cristine/request/status', 'REQUEST');
|
||||
await sock.sendMessage(sender, { text: '⏳ Meminta status dari perangkat, mohon tunggu...' });
|
||||
break;
|
||||
|
||||
default:
|
||||
await sock.sendMessage(sender, {
|
||||
text: `⚠️ Perintah tidak dikenali.\nKetik *menu* untuk melihat daftar perintah.`
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
startBot();
|
Loading…
Reference in New Issue