This commit is contained in:
2026-03-06 12:29:58 +01:00
commit ab00a59f29
21 changed files with 1564 additions and 0 deletions

281
main/main.c Normal file
View 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 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");
}