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

6
main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
idf_component_register(SRCS "main.c"
"wifi.c"
"atecc608a.c"
"ssh_client.c"
INCLUDE_DIRS "."
REQUIRES mbedtls esp-cryptoauthlib esp_wifi nvs_flash driver)

45
main/Kconfig.projbuild Normal file
View File

@@ -0,0 +1,45 @@
menu "KeyPi"
config WIFI_SSID
string "Wi-Fi SSID"
default ""
help
SSID of the Wi-Fi network to connect to.
config WIFI_PASSWORD
string "Wi-Fi Password"
default ""
help
Password for the Wi-Fi network.
config SSH_HOSTNAME
string "SSH Server Hostname or IP"
default "192.168.1.1"
help
Hostname or IP address of the SSH server (door controller).
config SSH_PORT
int "SSH Server Port"
default 22
help
TCP port of the SSH server.
config SSH_USERNAME
string "SSH Username"
default "pi"
help
Username to authenticate with on the SSH server.
config SSH_OPEN_COMMAND
string "SSH Open Command"
default "door open"
help
Shell command to execute on the SSH server to open the door.
config SSH_LOCK_COMMAND
string "SSH Lock Command"
default "door lock"
help
Shell command to execute on the SSH server to lock the door.
endmenu

103
main/atecc608a.c Normal file
View File

@@ -0,0 +1,103 @@
#include "atecc608a.h"
#include "sdkconfig.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
static const char *TAG = "atecc608b";
/* -------------------------------------------------------------------------
* Module-private state
* ---------------------------------------------------------------------- */
static ATCAIfaceCfg s_atecc_cfg;
static uint8_t s_config_data[128];
static bool s_config_valid = false;
/* -------------------------------------------------------------------------
* Internal helpers
* ---------------------------------------------------------------------- */
static bool read_config_zone(void)
{
ATCA_STATUS status = atcab_read_config_zone(s_config_data);
if (status != ATCA_SUCCESS) {
ESP_LOGI(TAG, "Failed to read config zone (0x%02X) check wiring", status);
return false;
}
s_config_valid = true;
return true;
}
/* -------------------------------------------------------------------------
* Public API
* ---------------------------------------------------------------------- */
bool atecc608B_init(void)
{
ESP_LOGI(TAG, "Initialising ATECC608B...");
s_atecc_cfg.iface_type = ATCA_I2C_IFACE;
s_atecc_cfg.devtype = ATECC608B;
s_atecc_cfg.atcai2c.address = CONFIG_ATCA_I2C_ADDRESS;
s_atecc_cfg.atcai2c.bus = 0;
s_atecc_cfg.atcai2c.baud = CONFIG_ATCA_I2C_BAUD_RATE;
s_atecc_cfg.wake_delay = 1500;
s_atecc_cfg.rx_retries = 20;
ESP_LOGI(TAG, "SDA=GPIO%d SCL=GPIO%d addr=0x%02X (7-bit 0x%02X)",
CONFIG_ATCA_I2C_SDA_PIN, CONFIG_ATCA_I2C_SCL_PIN,
CONFIG_ATCA_I2C_ADDRESS, CONFIG_ATCA_I2C_ADDRESS >> 1);
ATCA_STATUS status = atcab_init(&s_atecc_cfg);
if (status != ATCA_SUCCESS) {
ESP_LOGE(TAG, "atcab_init failed: 0x%02X", status);
return false;
}
vTaskDelay(pdMS_TO_TICKS(100));
status = atcab_wakeup();
if (status != ATCA_SUCCESS) {
ESP_LOGW(TAG, "Wake returned 0x%02X (may be normal)", status);
}
atcab_idle();
vTaskDelay(pdMS_TO_TICKS(50));
ESP_LOGI(TAG, "ATECC608B initialised");
return true;
}
void atecc608B_print_config(void)
{
ESP_LOGI(TAG, "=== ATECC608B Configuration Zone ===");
uint8_t serial[9];
ATCA_STATUS status = atcab_read_serial_number(serial);
if (status != ATCA_SUCCESS) {
ESP_LOGE(TAG, "Failed to read serial number (0x%02X) check wiring", status);
return;
}
if (!read_config_zone()) {
return;
}
uint8_t *c = s_config_data;
for (int i = 0; i < 128; i++) {
if (i % 16 == 0) printf("\n0x%02X: ", i);
printf("%02X ", c[i]);
}
printf("\n\n");
ESP_LOGI(TAG, "=== End of ATECC608B Configuration ===");
}
void atecc608B_release(void)
{
atcab_release();
}

24
main/atecc608a.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef ATECC608B_H
#define ATECC608B_H
#include "cryptoauthlib.h"
#include <stdbool.h>
/**
* Initialise the ATECC608B over I2C.
* Returns true on success.
*/
bool atecc608B_init(void);
/**
* Read the full configuration zone and print it to the console.
* Useful for first-time setup diagnostics.
*/
void atecc608B_print_config(void);
/**
* Release cryptoauthlib resources.
*/
void atecc608B_release(void);
#endif /* ATECC608B_H */

8
main/idf_component.yml Normal file
View File

@@ -0,0 +1,8 @@
---
dependencies:
idf:
version: ">=4.1.0"
esp-cryptoauthlib:
git: https://github.com/espressif/esp-cryptoauthlib.git
libssh2_esp:
git: https://github.com/skuodi/libssh2_esp.git

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");
}

285
main/ssh_client.c Normal file
View File

@@ -0,0 +1,285 @@
#include "ssh_client.h"
#include "cryptoauthlib.h"
#include "sdkconfig.h"
#include <arpa/inet.h>
#include <esp_log.h>
#include <libssh2.h>
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
static const char *TAG = "ssh_client";
/* -------------------------------------------------------------------------
* SSH blob / key helpers
* ---------------------------------------------------------------------- */
/* Write a big-endian uint32 into buf. */
static void write_be32(uint8_t *buf, uint32_t val)
{
buf[0] = (val >> 24) & 0xFF;
buf[1] = (val >> 16) & 0xFF;
buf[2] = (val >> 8) & 0xFF;
buf[3] = val & 0xFF;
}
/**
* Format one ECDSA integer as an SSH mpint:
* 4-byte big-endian length | optional 0x00 pad | value (leading zeros stripped)
*
* Returns number of bytes written to buf.
*/
static uint32_t write_mpint(uint8_t *buf, const uint8_t *val, uint32_t size)
{
uint32_t start = 0;
while (start < (size - 1) && val[start] == 0x00) {
start++;
}
bool pad = (val[start] & 0x80) != 0;
uint32_t len = (size - start) + (pad ? 1 : 0);
uint32_t off = 0;
buf[off++] = (len >> 24) & 0xFF;
buf[off++] = (len >> 16) & 0xFF;
buf[off++] = (len >> 8) & 0xFF;
buf[off++] = len & 0xFF;
if (pad) {
buf[off++] = 0x00;
}
memcpy(&buf[off], &val[start], size - start);
return off + (size - start);
}
/**
* Build a 104-byte SSH public key blob for ecdsa-sha2-nistp256:
* [uint32 19] "ecdsa-sha2-nistp256"
* [uint32 8] "nistp256"
* [uint32 65] 0x04 || X(32) || Y(32)
*
* @param atecc_pubkey 64-byte raw public key (X||Y) from ATECC608B.
* @param out_blob Must point to a buffer of at least 104 bytes.
*/
static void build_pubkey_blob(const uint8_t *atecc_pubkey, uint8_t *out_blob)
{
uint32_t off = 0;
/* Key type */
const char *key_type = "ecdsa-sha2-nistp256";
write_be32(&out_blob[off], 19); off += 4;
memcpy(&out_blob[off], key_type, 19); off += 19;
/* Curve name */
const char *curve = "nistp256";
write_be32(&out_blob[off], 8); off += 4;
memcpy(&out_blob[off], curve, 8); off += 8;
/* Uncompressed EC point: 0x04 || X || Y */
write_be32(&out_blob[off], 65); off += 4;
out_blob[off++] = 0x04;
memcpy(&out_blob[off], atecc_pubkey, 64);
/* off += 64 — total = 104 */
}
/* -------------------------------------------------------------------------
* libssh2 signing callback (ATECC608B signs the hash)
* ---------------------------------------------------------------------- */
static int sign_callback(LIBSSH2_SESSION *session,
unsigned char **sig, size_t *sig_len,
const unsigned char *data, size_t data_len,
void **abstract)
{
(void)session;
(void)abstract;
uint8_t digest[32];
uint8_t raw_sig[64];
/* Hash the challenge data */
mbedtls_sha256(data, data_len, digest, 0);
/* Sign with the ATECC608B hardware key in slot 0 */
if (atcab_sign(0, digest, raw_sig) != ATCA_SUCCESS) {
ESP_LOGE(TAG, "ATECC608B signing failed!");
return -1;
}
/* Encode as [mpint R][mpint S] (no outer string wrapper) */
unsigned char *buf = (unsigned char *)malloc(80);
if (!buf) return -1;
uint32_t off = 0;
off += write_mpint(&buf[off], &raw_sig[0], 32); /* R */
off += write_mpint(&buf[off], &raw_sig[32], 32); /* S */
*sig = buf;
*sig_len = off;
return 0;
}
/* -------------------------------------------------------------------------
* Public API
* ---------------------------------------------------------------------- */
void ssh_print_public_key(void)
{
uint8_t raw_key[64];
ATCA_STATUS st = atcab_get_pubkey(0, raw_key);
if (st != ATCA_SUCCESS) {
ESP_LOGE(TAG, "atcab_get_pubkey failed: 0x%02X", st);
return;
}
uint8_t blob[104];
build_pubkey_blob(raw_key, blob);
size_t b64_len = 0;
mbedtls_base64_encode(NULL, 0, &b64_len, blob, sizeof(blob));
unsigned char *b64 = (unsigned char *)malloc(b64_len + 1);
if (!b64) {
ESP_LOGE(TAG, "malloc failed for base64 buffer");
return;
}
mbedtls_base64_encode(b64, b64_len, &b64_len, blob, sizeof(blob));
b64[b64_len] = '\0';
printf("\n=== SSH Public Key (add to authorized_keys) ===\n");
printf("ecdsa-sha2-nistp256 %s keypitecc\n", b64);
printf("================================================\n\n");
free(b64);
}
bool ssh_execute_command(const char *cmd)
{
if (!cmd) return false;
/* --- Read public key blob ------------------------------------------ */
uint8_t raw_key[64];
ATCA_STATUS st = atcab_get_pubkey(0, raw_key);
if (st != ATCA_SUCCESS) {
ESP_LOGE(TAG, "atcab_get_pubkey failed: 0x%02X", st);
return false;
}
uint8_t pubkey_blob[104];
build_pubkey_blob(raw_key, pubkey_blob);
/* --- TCP connect ------------------------------------------------------- */
int rc;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
ESP_LOGE(TAG, "socket() failed");
return false;
}
struct sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(CONFIG_SSH_PORT);
sin.sin_addr.s_addr = inet_addr(CONFIG_SSH_HOSTNAME);
if (sin.sin_addr.s_addr == 0xFFFFFFFF) {
struct hostent *hp = gethostbyname(CONFIG_SSH_HOSTNAME);
if (!hp) {
ESP_LOGE(TAG, "gethostbyname(%s) failed", CONFIG_SSH_HOSTNAME);
close(sock);
return false;
}
sin.sin_addr.s_addr = ((struct ip4_addr *)hp->h_addr)->addr;
}
if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
ESP_LOGE(TAG, "connect to %s:%d failed", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT);
close(sock);
return false;
}
ESP_LOGI(TAG, "TCP connected to %s:%d", CONFIG_SSH_HOSTNAME, CONFIG_SSH_PORT);
/* --- libssh2 session -------------------------------------------------- */
rc = libssh2_init(0);
if (rc) {
ESP_LOGE(TAG, "libssh2_init failed: %d", rc);
close(sock);
return false;
}
LIBSSH2_SESSION *session = libssh2_session_init();
if (!session) {
ESP_LOGE(TAG, "libssh2_session_init failed");
libssh2_exit();
close(sock);
return false;
}
rc = libssh2_session_handshake(session, sock);
if (rc) {
ESP_LOGE(TAG, "SSH handshake failed: %d", rc);
goto cleanup_session;
}
ESP_LOGI(TAG, "SSH handshake OK");
/* --- Authenticate with ATECC608B hardware key ------------------------- */
void *abstract = NULL;
rc = libssh2_userauth_publickey(session, CONFIG_SSH_USERNAME,
pubkey_blob, sizeof(pubkey_blob),
sign_callback, &abstract);
if (rc != 0) {
ESP_LOGE(TAG, "SSH public-key authentication failed: %d", rc);
goto cleanup_session;
}
ESP_LOGI(TAG, "SSH authenticated as '%s'", CONFIG_SSH_USERNAME);
/* --- Open channel and execute command --------------------------------- */
LIBSSH2_CHANNEL *channel = libssh2_channel_open_session(session);
if (!channel) {
ESP_LOGE(TAG, "Failed to open SSH channel");
goto cleanup_session;
}
ESP_LOGI(TAG, "Executing: %s", cmd);
rc = libssh2_channel_exec(channel, cmd);
if (rc != 0) {
ESP_LOGE(TAG, "channel_exec failed: %d", rc);
libssh2_channel_free(channel);
goto cleanup_session;
}
/* --- Read output ------------------------------------------------------- */
char buf[256];
int bytes;
ESP_LOGI(TAG, "--- command output ---");
while ((bytes = libssh2_channel_read(channel, buf, sizeof(buf) - 1)) > 0) {
buf[bytes] = '\0';
printf("%s", buf);
}
ESP_LOGI(TAG, "--- end output ---");
/* --- Shutdown --------------------------------------------------------- */
libssh2_channel_close(channel);
int exit_status = libssh2_channel_get_exit_status(channel);
ESP_LOGI(TAG, "Command exited with status: %d", exit_status);
libssh2_channel_free(channel);
libssh2_session_disconnect(session, "Bye");
libssh2_session_free(session);
libssh2_exit();
close(sock);
return (exit_status == 0);
cleanup_session:
libssh2_session_disconnect(session, "Error");
libssh2_session_free(session);
libssh2_exit();
close(sock);
return false;
}

25
main/ssh_client.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef SSH_CLIENT_H
#define SSH_CLIENT_H
#include <stdbool.h>
/**
* Read the ATECC608B public key, format it as an SSH authorized_keys entry,
* and print it to the console. Call this once at boot so the key can be
* added to the server's authorized_keys file.
*
* Output format:
* ecdsa-sha2-nistp256 <base64-blob> keypitecc
*/
void ssh_print_public_key(void);
/**
* Open a TCP connection to the configured SSH server, authenticate using the
* ATECC608B hardware key, execute @p cmd, log the output, and disconnect.
*
* @param cmd Shell command string to run on the remote host.
* @return true on success, false on any error.
*/
bool ssh_execute_command(const char *cmd);
#endif /* SSH_CLIENT_H */

81
main/wifi.c Normal file
View File

@@ -0,0 +1,81 @@
#include "wifi.h"
#include "sdkconfig.h"
#include <esp_event.h>
#include <esp_log.h>
#include <esp_netif.h>
#include <esp_wifi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <string.h>
static const char *TAG = "wifi";
static EventGroupHandle_t s_wifi_event_group;
static const int WIFI_CONNECTED_BIT = BIT0;
static volatile bool s_connected = false;
/* -------------------------------------------------------------------------
* Event handler
* ---------------------------------------------------------------------- */
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
ESP_LOGI(TAG, "WiFi connecting...");
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
s_connected = false;
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
esp_wifi_connect();
ESP_LOGI(TAG, "WiFi disconnected, reconnecting...");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "WiFi connected, IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_connected = true;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
/* -------------------------------------------------------------------------
* Public API
* ---------------------------------------------------------------------- */
void wifi_init(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
configASSERT(sta_netif);
ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, "keypitecc"));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL));
wifi_config_t wifi_config = {};
strncpy((char *)wifi_config.sta.ssid, CONFIG_WIFI_SSID,
sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, CONFIG_WIFI_PASSWORD,
sizeof(wifi_config.sta.password) - 1);
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Connecting to SSID: %s", CONFIG_WIFI_SSID);
}
bool wifi_is_connected(void)
{
return s_connected;
}

19
main/wifi.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef WIFI_H
#define WIFI_H
#include <stdbool.h>
/**
* Initialise WiFi in STA mode, connect to the SSID defined in Kconfig,
* and register event handlers. Blocks until a first connection attempt
* is made, but does NOT block forever reconnection is handled in the
* background.
*/
void wifi_init(void);
/**
* Returns true when the station has a valid IP address (i.e. WiFi is up).
*/
bool wifi_is_connected(void);
#endif /* WIFI_H */