Files
keypitecc/main/main.c
2026-03-01 20:26:41 +01:00

282 lines
9.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 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(4, 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");
}