Restructure and improve
This commit is contained in:
281
main/main.c
Normal file
281
main/main.c
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* keypitecc – door controller
|
||||
*
|
||||
* Hardware:
|
||||
* GPIO 9 – Boot button (active-low, press = GND)
|
||||
* GPIO 15 – User LED (active-high, 1 = on)
|
||||
*
|
||||
* Button state machine (5-second window):
|
||||
* IDLE
|
||||
* └─ press ──► PENDING_OPEN (slow blink, will run SSH open command)
|
||||
* └─ press ──► PENDING_LOCK (fast blink, will run SSH lock command)
|
||||
* └─ press ──► IDLE (cancelled)
|
||||
* Any pending state expires after 5 s → command is executed → IDLE
|
||||
*
|
||||
* LED idle behaviour:
|
||||
* WiFi connected → LED ON
|
||||
* WiFi disconnected → LED OFF
|
||||
*/
|
||||
|
||||
#include "atecc608a.h"
|
||||
#include "ssh_client.h"
|
||||
#include "wifi.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO defines
|
||||
* ---------------------------------------------------------------------- */
|
||||
#define GPIO_BTN 9 /* Boot button, active-low */
|
||||
#define GPIO_LED 15 /* User LED, active-high */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Button / LED types
|
||||
* ---------------------------------------------------------------------- */
|
||||
typedef enum {
|
||||
BTN_STATE_IDLE,
|
||||
BTN_STATE_PENDING_OPEN,
|
||||
BTN_STATE_PENDING_LOCK,
|
||||
} btn_state_t;
|
||||
|
||||
typedef enum {
|
||||
LED_MODE_WIFI_STATUS, /* ON = connected, OFF = disconnected */
|
||||
LED_MODE_SLOW_BLINK, /* 500 ms on / 500 ms off (open pending) */
|
||||
LED_MODE_FAST_BLINK, /* 100 ms on / 100 ms off (lock pending) */
|
||||
} led_mode_t;
|
||||
|
||||
typedef enum {
|
||||
EVT_BUTTON_PRESS,
|
||||
EVT_TIMER_EXPIRED,
|
||||
} event_type_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Shared state
|
||||
* ---------------------------------------------------------------------- */
|
||||
static volatile led_mode_t s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
static volatile btn_state_t s_btn_state = BTN_STATE_IDLE;
|
||||
|
||||
static QueueHandle_t s_event_queue;
|
||||
static TimerHandle_t s_action_timer;
|
||||
|
||||
/* Debounce: ignore button edges within 200 ms of the previous one */
|
||||
static volatile TickType_t s_last_btn_tick = 0;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* SSH task – spawned on demand so the button task stays responsive
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void ssh_task(void *arg)
|
||||
{
|
||||
const char *cmd = (const char *)arg;
|
||||
ESP_LOGI(TAG, "Executing SSH command: %s", cmd);
|
||||
bool ok = ssh_execute_command(cmd);
|
||||
ESP_LOGI(TAG, "SSH command %s: %s", cmd, ok ? "succeeded" : "FAILED");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void trigger_ssh(const char *cmd)
|
||||
{
|
||||
/* Stack size 16 kB – SSH needs heap + TLS, keep priority low */
|
||||
if (xTaskCreate(ssh_task, "ssh", 16384, (void *)cmd, 3, NULL) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create SSH task");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* FreeRTOS software timer callback – runs in timer daemon task context
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void action_timer_cb(TimerHandle_t xTimer)
|
||||
{
|
||||
(void)xTimer;
|
||||
event_type_t evt = EVT_TIMER_EXPIRED;
|
||||
xQueueSend(s_event_queue, &evt, 0);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO ISR
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void IRAM_ATTR button_isr(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
TickType_t now = xTaskGetTickCountFromISR();
|
||||
if ((now - s_last_btn_tick) < pdMS_TO_TICKS(200)) {
|
||||
return; /* debounce */
|
||||
}
|
||||
s_last_btn_tick = now;
|
||||
|
||||
event_type_t evt = EVT_BUTTON_PRESS;
|
||||
xQueueSendFromISR(s_event_queue, &evt, NULL);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Button task – owns the state machine
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void button_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
event_type_t evt;
|
||||
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_event_queue, &evt, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (s_btn_state) {
|
||||
|
||||
case BTN_STATE_IDLE:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: PENDING_OPEN in 5s (press again for LOCK, third press cancels)");
|
||||
s_btn_state = BTN_STATE_PENDING_OPEN;
|
||||
s_led_mode = LED_MODE_SLOW_BLINK;
|
||||
xTimerStart(s_action_timer, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_PENDING_OPEN:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: switching to PENDING_LOCK");
|
||||
s_btn_state = BTN_STATE_PENDING_LOCK;
|
||||
s_led_mode = LED_MODE_FAST_BLINK;
|
||||
xTimerReset(s_action_timer, 0); /* restart 5s from now */
|
||||
} else if (evt == EVT_TIMER_EXPIRED) {
|
||||
ESP_LOGI(TAG, "Timer expired: executing OPEN command");
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
trigger_ssh(CONFIG_SSH_OPEN_COMMAND);
|
||||
}
|
||||
break;
|
||||
|
||||
case BTN_STATE_PENDING_LOCK:
|
||||
if (evt == EVT_BUTTON_PRESS) {
|
||||
ESP_LOGI(TAG, "Button: action CANCELLED");
|
||||
xTimerStop(s_action_timer, 0);
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
} else if (evt == EVT_TIMER_EXPIRED) {
|
||||
ESP_LOGI(TAG, "Timer expired: executing LOCK command");
|
||||
s_btn_state = BTN_STATE_IDLE;
|
||||
s_led_mode = LED_MODE_WIFI_STATUS;
|
||||
trigger_ssh(CONFIG_SSH_LOCK_COMMAND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* LED task – drives GPIO 15 according to the current led_mode
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void led_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
bool led_state = false;
|
||||
|
||||
for (;;) {
|
||||
led_mode_t mode = s_led_mode; /* atomic read (single-byte enum) */
|
||||
|
||||
switch (mode) {
|
||||
case LED_MODE_WIFI_STATUS:
|
||||
led_state = wifi_is_connected();
|
||||
gpio_set_level(GPIO_LED, led_state ? 0 : 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
break;
|
||||
|
||||
case LED_MODE_SLOW_BLINK:
|
||||
gpio_set_level(GPIO_LED, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
gpio_set_level(GPIO_LED, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
break;
|
||||
|
||||
case LED_MODE_FAST_BLINK:
|
||||
gpio_set_level(GPIO_LED, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(GPIO_LED, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* GPIO initialisation
|
||||
* ---------------------------------------------------------------------- */
|
||||
static void gpio_init(void)
|
||||
{
|
||||
/* LED output */
|
||||
gpio_config_t led_cfg = {
|
||||
.pin_bit_mask = (1ULL << GPIO_LED),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&led_cfg);
|
||||
gpio_set_level(GPIO_LED, 1); /* off (active-high) */
|
||||
|
||||
/* Button input with interrupt on falling edge (active-low) */
|
||||
gpio_config_t btn_cfg = {
|
||||
.pin_bit_mask = (1ULL << GPIO_BTN),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_NEGEDGE,
|
||||
};
|
||||
gpio_config(&btn_cfg);
|
||||
|
||||
gpio_install_isr_service(0);
|
||||
gpio_isr_handler_add(GPIO_BTN, button_isr, NULL);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* app_main
|
||||
* ---------------------------------------------------------------------- */
|
||||
void app_main(void)
|
||||
{
|
||||
/* NVS (required by WiFi) */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
/* ATECC608B */
|
||||
if (!atecc608B_init()) {
|
||||
ESP_LOGW(TAG, "ATECC608B init failed – SSH authentication will not work");
|
||||
} else {
|
||||
atecc608B_print_config();
|
||||
ssh_print_public_key(); /* print key for authorized_keys setup */
|
||||
}
|
||||
|
||||
/* WiFi */
|
||||
wifi_init();
|
||||
|
||||
/* GPIO and event infrastructure */
|
||||
gpio_init();
|
||||
|
||||
s_event_queue = xQueueCreate(8, sizeof(event_type_t));
|
||||
configASSERT(s_event_queue);
|
||||
|
||||
/* 5-second one-shot timer (auto-reload disabled) */
|
||||
s_action_timer = xTimerCreate("action", pdMS_TO_TICKS(5000),
|
||||
pdFALSE, NULL, action_timer_cb);
|
||||
configASSERT(s_action_timer);
|
||||
|
||||
/* Tasks */
|
||||
xTaskCreate(button_task, "button", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(led_task, "led", 2048, NULL, 4, NULL);
|
||||
|
||||
ESP_LOGI(TAG, "keypitecc ready – press the boot button to operate the door");
|
||||
}
|
||||
Reference in New Issue
Block a user