#include #include #include #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); } } } } }