commit ab00a59f29b66e585f3cbaed53836af91fe7ea58 Author: Jonathan Berrisch Date: Fri Mar 6 12:29:58 2026 +0100 Init diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..67bfde5 --- /dev/null +++ b/.envrc @@ -0,0 +1,31 @@ +# disable nix builders because they are slow for copy operations +export NIX_BUILDERS="" + +# Automatically load the Nix development environment when entering this directory +use flake + +# Export ESP-IDF related environment variables +export IDF_TOOLS_PATH="$HOME/.espressif" +export CCACHE_DIR="$HOME/.ccache" + +# Prevent Python bytecode generation +export PYTHONDONTWRITEBYTECODE=1 + +# Enable colored output for various tools +export FORCE_COLOR=1 + +# Set common ESP32 development variables +export ESP_TARGET=esp32 +export ESP_BOARD=esp32dev + +# Set locale to avoid issues +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 + +# Create necessary directories +mkdir -p "$IDF_TOOLS_PATH" +mkdir -p "$CCACHE_DIR" + +# Show a message when the environment is loaded +echo "🚀 ESP32 Tang Server environment loaded!" +echo "ESP-IDF ready - run 'make help' for available commands" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28953ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Linker files +*.ilk + +# Debugger Files +*.pdb + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# debug information files +*.dwo + +# direnv +.direnv + +# gitignore template for esp-idf, the official development framework for ESP32 +# https://github.com/espressif/esp-idf +build/ +sdkconfig +sdkconfig.old + +managed_components/ +.cache/ + +# Cryptographic files +*.jwk +*.jwe + +# Other Binary files +*.pdf \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..0af50ed --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "NixOS", + "compileCommands": [ + "${workspaceFolder}/build/compile_commands.json" + ], + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/managed_components/**", + "${workspaceFolder}/managed_components/esp-cryptoauthlib/**" + ], + "compilerPath": "/nix/store/x45d95acx6rybkb5cidrxxffval9k2pg-gcc-wrapper-14.3.0/bin/gcc" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8dfb64c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cmake.automaticReconfigure": true, + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dcf845d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +# Top-level CMakeLists.txt for ESP32 Tang Server +cmake_minimum_required(VERSION 3.16) + +# Set the project name +set(PROJECT_NAME "esp32-tang") + +# Include ESP-IDF build system +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# Define the project +project(${PROJECT_NAME}) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd39382 --- /dev/null +++ b/Makefile @@ -0,0 +1,183 @@ +# ESP32 Tang Server Makefile +# Convenient shortcuts for ESP-IDF development tasks + +.DEFAULT_GOAL := help + +# Configuration variables +PORT ?= /dev/ttyUSB0 +BAUD ?= 115200 +TARGET ?= esp32 + +# Help target +.PHONY: help +help: + @echo "ESP32 Tang Server Development Commands" + @echo "=====================================" + @echo "" + @echo "Setup:" + @echo " setup-target Set ESP32 as build target" + @echo " menuconfig Open configuration menu" + @echo "" + @echo "Build:" + @echo " build Build the project" + @echo " clean Clean build files" + @echo " fullclean Full clean (including config)" + @echo "" + @echo "Flash & Monitor:" + @echo " flash Flash to device" + @echo " monitor Open serial monitor" + @echo " flash-monitor Flash and immediately monitor" + @echo "" + @echo "Development:" + @echo " size Show binary size analysis" + @echo " erase Erase flash completely" + @echo " bootloader Flash bootloader only" + @echo "" + @echo "Board Management:" + @echo " list-boards List connected boards" + @echo " detect-port Detect serial ports" + @echo " board-info Show board information" + @echo "" + @echo "Environment variables:" + @echo " PORT=/dev/ttyUSB0 (default serial port)" + @echo " BAUD=115200 (default baud rate)" + @echo " TARGET=esp32 (default target)" + +# Setup commands +.PHONY: setup-target +setup-target: + idf.py set-target $(TARGET) + +.PHONY: menuconfig +menuconfig: + idf.py menuconfig + +# Build commands +.PHONY: build +build: + idf.py build + +.PHONY: clean +clean: + idf.py clean + +.PHONY: fullclean +fullclean: + idf.py fullclean + +# Flash and monitor commands +.PHONY: flash +flash: + idf.py -p $(PORT) -b $(BAUD) flash + +.PHONY: monitor +monitor: + idf.py -p $(PORT) -b $(BAUD) monitor + +.PHONY: flash-monitor +flash-monitor: + idf.py -p $(PORT) -b $(BAUD) flash monitor + +# Development utilities +.PHONY: size +size: + idf.py size + +.PHONY: size-components +size-components: + idf.py size-components + +.PHONY: size-files +size-files: + idf.py size-files + +.PHONY: erase +erase: + idf.py -p $(PORT) erase-flash + +.PHONY: bootloader +bootloader: + idf.py -p $(PORT) bootloader-flash + +# Show partition table +.PHONY: partition-table +partition-table: + idf.py partition-table + +# Generate compilation database for IDE support +.PHONY: compile-commands +compile-commands: + idf.py build --cmake-args="-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" + +# Development shortcuts +.PHONY: dev +dev: build flash-monitor + +.PHONY: quick +quick: build flash + +# Board management +.PHONY: list-boards +list-boards: + idf.py -p $(PORT) board_info || echo "Connect board to $(PORT)" + +.PHONY: detect-port +detect-port: + @echo "Scanning for ESP32 devices..." + @if ls /dev/ttyUSB* >/dev/null 2>&1; then \ + echo "Found USB serial devices:"; \ + ls -la /dev/ttyUSB* | head -5; \ + elif ls /dev/ttyACM* >/dev/null 2>&1; then \ + echo "Found ACM serial devices:"; \ + ls -la /dev/ttyACM* | head -5; \ + else \ + echo "No serial devices found. Make sure ESP32 is connected."; \ + fi + +.PHONY: board-info +board-info: + @echo "Board Information:" + @echo "==================" + @echo "Target: $(TARGET)" + @echo "Port: $(PORT)" + @echo "Baud: $(BAUD)" + @echo "" + @echo "ESP-IDF Version:" + @idf.py --version + @echo "" + @echo "Project status:" + @idf.py show-port-info -p $(PORT) 2>/dev/null || echo "No device connected to $(PORT)" + +# Show ESP-IDF version and tools +.PHONY: version +version: + @echo "ESP-IDF Version Information:" + @idf.py --version + @echo "" + @echo "Toolchain versions:" + @xtensa-esp32-elf-gcc --version | head -1 || echo "Toolchain not found" + @python3 --version + @cmake --version | head -1 + +# Validate project structure +.PHONY: validate +validate: + @echo "Validating ESP-IDF project structure..." + @if [ ! -f "CMakeLists.txt" ]; then \ + echo "Error: CMakeLists.txt not found"; \ + exit 1; \ + fi + @if [ ! -d "main" ]; then \ + echo "Error: main directory not found"; \ + exit 1; \ + fi + @echo "Project validation passed!" + +# Full development cycle +.PHONY: full-setup +full-setup: setup-target menuconfig build + @echo "" + @echo "Full setup complete! You can now:" + @echo " make flash # Flash to device" + @echo " make monitor # View serial output" + @echo " make dev # Flash and monitor" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c375285 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# KeyPi Client using SSH and ATECC608B + +### Build + +Set the target: + +```sh +idf.py set-target +``` + +Use `menuconfig` to configure the KeyPi (choose `KeyPi` from the list): + +```sh +idf.py menuconfig +``` + +To build, flash and monitor: + +```sh +idf.py build flash monitor +``` + +### Usage + +Once the ESP32 connects to the wifi: user LED will light up. + +Press button once: LED starts blinking slowly -> Sends open command after 5s. +Press button twice: LED starts blinking fast -> Sends close command after 5s. +Press button again: cancels the action. \ No newline at end of file diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..4d0aa9e --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,30 @@ +dependencies: + esp-cryptoauthlib: + component_hash: 8d24c37df1e906f9bbee9a869ca86f263ab135179d1c2ba6062448269437b192 + dependencies: + - name: idf + version: '>=4.3' + source: + git: https://github.com/espressif/esp-cryptoauthlib.git + path: . + type: git + version: d9792119ebaec0c54839e6605acd3f11dd937205 + idf: + source: + type: idf + version: 5.5.2 + libssh2_esp: + component_hash: 75612f8fe15b7793de2d9d2eba920e66a7aab7424963012282a419cdb86399ad + dependencies: [] + source: + git: https://github.com/skuodi/libssh2_esp.git + path: . + type: git + version: 378f0bd47900bffacbf29cac328c6e9b5391c886 +direct_dependencies: +- esp-cryptoauthlib +- idf +- libssh2_esp +manifest_hash: a6766e71931c845fac37dab1b735cded43d414aa83e5ce0443ba4285e1980180 +target: esp32c6 +version: 2.0.0 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..468b1a1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,117 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1771419570, + "narHash": "sha256-bxAlQgre3pcQcaRUm/8A0v/X8d2nhfraWSFqVmMcBcU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6d41bc27aaf7b6a3ba6b169db3bd5d6159cfaa47", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-esp-dev": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1767865407, + "narHash": "sha256-QWF1rZYd+HvNzLIeRS+OEBX7HF0EhWCGeLbMkgtbsIo=", + "owner": "mirrexagon", + "repo": "nixpkgs-esp-dev", + "rev": "5287d6e1ca9e15ebd5113c41b9590c468e1e001b", + "type": "github" + }, + "original": { + "owner": "mirrexagon", + "repo": "nixpkgs-esp-dev", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs-esp-dev", + "nixpkgs" + ], + "nixpkgs-esp-dev": "nixpkgs-esp-dev" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..5589d6c --- /dev/null +++ b/flake.nix @@ -0,0 +1,189 @@ +{ + description = "ESP32 Tang Server Development Environment"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs-esp-dev = { + url = "github:mirrexagon/nixpkgs-esp-dev"; + }; + nixpkgs.follows = "nixpkgs-esp-dev/nixpkgs"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + nixpkgs-esp-dev, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + config = { + permittedInsecurePackages = [ + "python3.13-ecdsa-0.19.1" + ]; + }; + overlays = [ nixpkgs-esp-dev.overlays.default ]; + }; + in + { + devShells.default = pkgs.mkShell { + name = "esp32-tang-dev"; + + buildInputs = + with pkgs; + [ + python3Packages.pandas + + # ESP-IDF with full toolchain + esp-idf-full + jose + clevis + + # Development tools + gnumake + cmake + ninja + ccache + + # Serial communication tools + picocom + screen + minicom + + # Development utilities + curl + wget + unzip + file + which + jq + + # Text processing tools + gawk + gnused + gnugrep + + # Optional development tools + clang-tools + bear + ] + ++ lib.optionals stdenv.isLinux [ + # Linux-specific packages for USB device access + udev + libusb1 + ]; + + shellHook = '' + echo "🚀 ESP32 Tang Server Development Environment" + echo "=============================================" + echo + echo "ESP-IDF: $(idf.py --version 2>/dev/null || echo 'Ready')" + echo "Python: $(python3 --version)" + echo "CMake: $(cmake --version | head -1)" + echo + echo "Available commands:" + echo " 📦 idf.py build - Build the project" + echo " 📡 idf.py flash - Flash to device" + echo " 💻 idf.py monitor - Serial monitor" + echo " 🔧 idf.py flash monitor - Flash and monitor" + echo " ⚙️ idf.py menuconfig - Configuration menu" + echo " 🎯 idf.py set-target - Set target (esp32)" + echo + echo "Or use the Makefile shortcuts:" + echo " make setup-target - Set ESP32 target" + echo " make build - Build project" + echo " make flash-monitor - Flash and monitor" + echo " make menuconfig - Configuration" + echo + echo "Quick start:" + echo " 1. Set target: make setup-target" + echo " 2. Configure: make menuconfig" + echo " 3. Build: make build" + echo " 4. Flash & Monitor: make flash-monitor PORT=/dev/ttyUSB0" + echo + + # Set up development environment + export IDF_TOOLS_PATH="$HOME/.espressif" + export CCACHE_DIR="$HOME/.ccache" + + # Create necessary directories + mkdir -p "$IDF_TOOLS_PATH" + mkdir -p "$CCACHE_DIR" + + # Check for serial devices + if ls /dev/ttyUSB* >/dev/null 2>&1; then + echo "📡 Found USB serial devices:" + ls -la /dev/ttyUSB* 2>/dev/null + elif ls /dev/ttyACM* >/dev/null 2>&1; then + echo "📡 Found ACM serial devices:" + ls -la /dev/ttyACM* 2>/dev/null + else + echo "📡 No serial devices found. Connect your ESP32 board." + fi + + # Check serial permissions + if ! groups | grep -q dialout 2>/dev/null && ! groups | grep -q uucp 2>/dev/null; then + echo + echo "⚠️ Note: You may need to add your user to the 'dialout' group" + echo " to access serial devices:" + echo " sudo usermod -a -G dialout $USER" + echo " Then log out and log back in." + fi + + echo + echo "Ready for ESP32 development with Arduino support! 🎯" + echo + ''; + + # allow /dev/ access + extraDevPaths = [ + "/dev/ttyUSB*" + "/dev/ttyACM*" + ]; + + # Environment variables + IDF_TOOLS_PATH = "$HOME/.espressif"; + CCACHE_DIR = "$HOME/.ccache"; + + # Prevent Python from creating __pycache__ directories + PYTHONDONTWRITEBYTECODE = "1"; + + # Enable colored output + FORCE_COLOR = "1"; + + # Set locale to avoid issues + LANG = "C.UTF-8"; + LC_ALL = "C.UTF-8"; + }; + + devShells.minimal = pkgs.mkShell { + name = "esp32-tang-minimal"; + + buildInputs = with pkgs; [ + esp-idf-full + picocom + ]; + + shellHook = '' + echo "⚡ ESP32 Tang Server (Minimal Environment)" + echo "========================================" + echo "Ready for ESP32 development!" + echo + + export IDF_TOOLS_PATH="$HOME/.espressif" + export CCACHE_DIR="$HOME/.ccache" + mkdir -p "$CCACHE_DIR" + ''; + + extraDevPaths = [ + "/dev/ttyUSB*" + "/dev/ttyACM*" + ]; + }; + } + ); +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..1c39a2b --- /dev/null +++ b/main/CMakeLists.txt @@ -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) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..05413b4 --- /dev/null +++ b/main/Kconfig.projbuild @@ -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 diff --git a/main/atecc608a.c b/main/atecc608a.c new file mode 100644 index 0000000..90ce8d1 --- /dev/null +++ b/main/atecc608a.c @@ -0,0 +1,103 @@ +#include "atecc608a.h" + +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include + +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(); +} diff --git a/main/atecc608a.h b/main/atecc608a.h new file mode 100644 index 0000000..ca3dc38 --- /dev/null +++ b/main/atecc608a.h @@ -0,0 +1,24 @@ +#ifndef ATECC608B_H +#define ATECC608B_H + +#include "cryptoauthlib.h" +#include + +/** + * 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 */ diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..5fb942f --- /dev/null +++ b/main/idf_component.yml @@ -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 diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..5501103 --- /dev/null +++ b/main/main.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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"); +} diff --git a/main/ssh_client.c b/main/ssh_client.c new file mode 100644 index 0000000..a86218c --- /dev/null +++ b/main/ssh_client.c @@ -0,0 +1,285 @@ +#include "ssh_client.h" + +#include "cryptoauthlib.h" +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/main/ssh_client.h b/main/ssh_client.h new file mode 100644 index 0000000..9b1bfbb --- /dev/null +++ b/main/ssh_client.h @@ -0,0 +1,25 @@ +#ifndef SSH_CLIENT_H +#define SSH_CLIENT_H + +#include + +/** + * 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 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 */ diff --git a/main/wifi.c b/main/wifi.c new file mode 100644 index 0000000..5e9143c --- /dev/null +++ b/main/wifi.c @@ -0,0 +1,81 @@ +#include "wifi.h" + +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/main/wifi.h b/main/wifi.h new file mode 100644 index 0000000..d4ed3df --- /dev/null +++ b/main/wifi.h @@ -0,0 +1,19 @@ +#ifndef WIFI_H +#define WIFI_H + +#include + +/** + * 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 */ diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..2a819a8 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,15 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.5.2 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32c6" +CONFIG_WIFI_SSID="DoNotSetTheRealValueHere" +CONFIG_WIFI_PASSWORD="PutTheRealPassInTheSdkconfigFile" +CONFIG_SSH_HOSTNAME="192.168.178.1" +CONFIG_SSH_USERNAME="user" +CONFIG_SSH_OPEN_COMMAND="open" +CONFIG_SSH_LOCK_COMMAND="lock" +CONFIG_ATECC608A_TCUSTOM=y +CONFIG_ATCA_I2C_SDA_PIN=22 +CONFIG_ATCA_I2C_SCL_PIN=23 +CONFIG_ATCA_I2C_BAUD_RATE=1000000 +CONFIG_LIBSSH2_DEBUG_ENABLE=n