mohan-bee/compact-pir

Compact 3.3 V ESP8684 wireless motion/GPIO breakout board with LiPo regulation, 40 MHz crystal, U.FL antenna matching, digital PIR sensor, switched LED, decoupling, and dual 10-pin headers.

Version
1.0.2
License
unset
Stars
0

firmware/main/main.c

#include <stdio.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "motion_logic.h"

enum { PIR_PIN = GPIO_NUM_2, LED_PIN = GPIO_NUM_7 };
static const char *TAG = "motion";

static uint64_t millis(void)
{
    return (uint64_t)esp_timer_get_time() / 1000;
}

void app_main(void)
{
    // Set the output latch LOW before enabling the transistor's GPIO output.
    ESP_ERROR_CHECK(gpio_set_level(LED_PIN, 0));
    const gpio_config_t led = {
        .pin_bit_mask = 1ULL << LED_PIN,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    ESP_ERROR_CHECK(gpio_config(&led));
    const gpio_config_t pir = {
        .pin_bit_mask = 1ULL << PIR_PIN,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    ESP_ERROR_CHECK(gpio_config(&pir));
    ESP_ERROR_CHECK(gpio_wakeup_enable(PIR_PIN, GPIO_INTR_HIGH_LEVEL));
    ESP_ERROR_CHECK(esp_sleep_enable_gpio_wakeup());
    ESP_ERROR_CHECK(esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_FLUSH_UART));

    motion_state_t state = {.boot_ms = millis()};
    bool last_pir = gpio_get_level(PIR_PIN) != 0;
    bool last_led = false;
    ESP_LOGI(TAG, "ESP8684H4X / GPIO2 PIR / GPIO7 LED; radios not initialized");
    ESP_LOGI(TAG, "LED OFF; startup settle %d s; motion hold %d s",
             CONFIG_MOTION_SETTLE_SECONDS, CONFIG_MOTION_HOLD_SECONDS);
    ESP_LOGI(TAG, "Initial PIR %s", last_pir ? "HIGH" : "LOW");

    for (;;) {
        const uint64_t now_ms = millis();
        const bool pir_high = gpio_get_level(PIR_PIN) != 0;
        const bool was_settled = state.settled;
        const bool led_on = motion_update(&state, now_ms, pir_high,
            CONFIG_MOTION_SETTLE_SECONDS * 1000U,
            CONFIG_MOTION_HOLD_SECONDS * 1000U);
        if (!was_settled && state.settled) {
            ESP_LOGI(TAG, "Startup settle complete; motion input enabled");
        }
        if (pir_high != last_pir) {
            ESP_LOGI(TAG, "PIR %s%s", pir_high ? "HIGH" : "LOW",
                     state.settled ? "" : " (ignored during settle)");
            last_pir = pir_high;
        }
        if (led_on != last_led) {
            ESP_ERROR_CHECK(gpio_set_level(LED_PIN, led_on));
            ESP_LOGI(TAG, "LED %s", led_on ? "ON" : "OFF");
            last_led = led_on;
        }
        if (state.settled && !led_on && !pir_high) {
            ESP_LOGI(TAG, "Idle; light sleep until PIR HIGH");
            fflush(stdout);
            // Recheck after logging; HIGH-level wake also covers the entry race.
            if (gpio_get_level(PIR_PIN) == 0) {
                const esp_err_t result = esp_light_sleep_start();
                if (result == ESP_OK) {
                    ESP_LOGI(TAG, "Awake; reading PIR again");
                } else if (result != ESP_ERR_SLEEP_REJECT) {
                    ESP_ERROR_CHECK(result);
                }
            }
        }
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}