From 224f22f23d0f96a1084222c99fc5e526de620d3e Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 12 Sep 2025 22:27:55 +0200 Subject: [PATCH] Add encryption via ESP-NOW --- README.org | 37 ++++++++++++ client/src/connect.cpp | 111 +++++++++++++++++++++++++---------- client/src/connect.h | 18 +++++- client/src/main.cpp | 76 +++++++++--------------- client/src/secrets.h.example | 7 ++- server/src/IPAddress.h | 0 server/src/connect.cpp | 99 +++++++++++++++++++++++++++++++ server/src/connect.h | 6 ++ server/src/main.cpp | 94 +++++++++-------------------- server/src/secrets.h.example | 7 ++- 10 files changed, 304 insertions(+), 151 deletions(-) create mode 100644 server/src/IPAddress.h create mode 100644 server/src/connect.cpp create mode 100644 server/src/connect.h diff --git a/README.org b/README.org index 64ea10c..973cd6b 100644 --- a/README.org +++ b/README.org @@ -15,3 +15,40 @@ platformio run --target upload ** Console platformio device monitor + +** Configuration + +The following fields in =secrets.h= have to be configured: + +- CLIENT_MAC +- SERVER_MAC +- PMK +- LMK + +You have to configure the real STA MAC address of *both* the client and server. +It might be odd that the AP MAC of the receiver isn't used, but that's how +ESP-NOW works. You can find the MAC address with the following code. + +#+BEGIN_SRC cpp +#include + +void printMacAddress() +{ + uint8_t mac[6]; + esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, mac); + if (ret == ESP_OK) { + Serial.printf("{ %#x, %#x, %#x, %#x, %#x, %#x }\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + else { + Serial.println("Failed to read MAC address"); + } +} +#+END_SRC + +PMK and LMK have to be 16 characters long and should be the same between the +client and server. + +* References + +- https://randomnerdtutorials.com/esp32-esp-now-encrypted-messages/ diff --git a/client/src/connect.cpp b/client/src/connect.cpp index c4ae021..7d53c7f 100644 --- a/client/src/connect.cpp +++ b/client/src/connect.cpp @@ -1,67 +1,118 @@ #include #include -#include #include +#include +#include #include "connect.h" -#include "esp32-hal.h" #include "secrets.h" -uint64_t start; +uint8_t client_mac[6] = CLIENT_MAC; +uint8_t server_mac[6] = SERVER_MAC; + +uint64_t connect = 0; uint64_t is_connecting = 0; -IPAddress host; +uint64_t ack = 0; +uint8_t ack_speed = 50; + +// ----------------------------------------- + +void wifiSetupEncryption(); -void timerStart() +void timerStart(uint64_t* timer) { - start = millis(); + *timer = millis(); } -uint64_t timerElapsed() +uint64_t timerElapsed(uint64_t* timer) { auto now = millis(); - uint64_t elapsed = now - start; + uint64_t elapsed = now - *timer; return elapsed; } -// ----------------------------------------- - void wifiSetup() { WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(false); WiFi.disconnect(); delay(100); + + esp_wifi_set_channel(static_cast(CHANNEL), WIFI_SECOND_CHAN_NONE); + + wifiSetupEncryption(); } -void wifiConnect() +// ----------------------------------------- + +void onDataSent(const uint8_t* mac_addr, esp_now_send_status_t status) { - if (WiFi.status() == WL_CONNECTED) { - if (is_connecting > 0) { - host = WiFi.gatewayIP(); - Serial.println("Connected to AP!"); - } - is_connecting = 0; + if (status != ESP_NOW_SEND_SUCCESS) { return; } - if (is_connecting == 0 || timerElapsed() > is_connecting * 1000) { - if (is_connecting == 0) { - is_connecting = 10; // start with a 10 second delay - } - else { - is_connecting *= 2; // exponential backoff - } - - timerStart(); - Serial.println("Connecting"); - WiFi.begin(SSID, PASSWORD); + Serial.println("Receive ACK"); + + // Flash PWR LED while still receiving acknowledgments + if (timerElapsed(&ack) > ack_speed * 2) { + timerStart(&ack); + digitalWrite(POWER_LED_PIN, HIGH); + delay(ack_speed); + digitalWrite(POWER_LED_PIN, LOW); + } +} + +void onDataRecv(const uint8_t* mac_addr, const uint8_t* data, int data_len) +{ + Serial.print("Received: "); + Serial.write(data, data_len); + Serial.println(); + + auto msg = String((const char*)data, data_len); + + if (msg == "power_released_ack") { + need_power_ack = false; + wait_power_ack = false; + } + + if (msg == "reset_released_ack") { + need_reset_ack = false; + wait_reset_ack = false; } } -IPAddress wifiHost() +void wifiSetupEncryption() { - return host; + esp_now_deinit(); + + if (esp_now_init() != ESP_OK) { + Serial.println("Failed to init ESP-NOW"); + return; + } + + if (esp_now_set_pmk((const uint8_t*)PMK) != ESP_OK) { + Serial.println("Failed to set PMK"); + return; + } + + auto peerInfo = esp_now_peer_info_t { + .channel = static_cast(CHANNEL), + .encrypt = true, + }; + + memcpy(peerInfo.peer_addr, server_mac, 6); + memcpy(peerInfo.lmk, LMK, 16); + + if (esp_now_add_peer(&peerInfo) != ESP_OK) { + Serial.println("Failed to add peer"); + return; + } + + esp_now_register_send_cb(onDataSent); + esp_now_register_recv_cb(onDataRecv); + + Serial.println("Configured encryption!"); } // https://docs.espressif.com/projects/arduino-esp32/en/latest/api/wifi.html diff --git a/client/src/connect.h b/client/src/connect.h index 1e81a54..f98d2dd 100644 --- a/client/src/connect.h +++ b/client/src/connect.h @@ -1,5 +1,17 @@ -#include "IPAddress.h" +#include + +#define CHANNEL 11 + +#define POWER_LED_PIN 3 +#define RESET_LED_PIN 1 + +extern bool need_power_ack; +extern bool need_reset_ack; + +extern bool wait_power_ack; +extern bool wait_reset_ack; + +extern uint8_t client_mac[6]; +extern uint8_t server_mac[6]; void wifiSetup(); -void wifiConnect(); -IPAddress wifiHost(); diff --git a/client/src/main.cpp b/client/src/main.cpp index e9d4669..6cc2335 100644 --- a/client/src/main.cpp +++ b/client/src/main.cpp @@ -1,21 +1,19 @@ #include -#include +#include #include "connect.h" -#define PORT 1234 - #define POWER_BUTTON_PIN 10 #define RESET_BUTTON_PIN 2 -#define POWER_LED_PIN 3 -#define RESET_LED_PIN 1 - -WiFiClient client; int previousPowerButtonState = HIGH; int previousResetButtonState = HIGH; -void ack(); +bool need_power_ack = false; +bool need_reset_ack = false; + +bool wait_power_ack = false; +bool wait_reset_ack = false; void setup() { @@ -28,19 +26,17 @@ void setup() pinMode(POWER_LED_PIN, OUTPUT); pinMode(RESET_LED_PIN, OUTPUT); - wifiSetup(); - delay(3000); Serial.println("Client booted!"); + + wifiSetup(); } void loop() { delay(20); // used for button debounce - wifiConnect(); - - digitalWrite(POWER_LED_PIN, LOW); + // digitalWrite(POWER_LED_PIN, LOW); digitalWrite(RESET_LED_PIN, LOW); int powerButtonState = digitalRead(POWER_BUTTON_PIN); @@ -50,9 +46,6 @@ void loop() if (powerButtonState == LOW && resetButtonState == LOW) { previousPowerButtonState = HIGH; previousResetButtonState = HIGH; - if (client.connected()) { - client.stop(); - } return; } @@ -68,46 +61,31 @@ void loop() Serial.println("Pressed " + button + " button!"); - if (!client.connected() && !client.connect(wifiHost(), PORT)) { - Serial.println("Connection failed"); - return; - } - - client.print(button + "_pressed\n"); + String msg = button + "_pressed"; + esp_now_send(server_mac, (const uint8_t*)msg.c_str(), msg.length()); Serial.println("Sent " + button + " button press"); - ack(); + need_power_ack = true; + need_reset_ack = true; + + wait_power_ack = false; + wait_reset_ack = false; + + return; } - else { - if (!client.connected()) { - return; - } - client.print("power_released\n"); + if (need_power_ack && !wait_power_ack) { + String msg = "power_released"; + esp_now_send(server_mac, (const uint8_t*)msg.c_str(), msg.length()); Serial.println("Sent power button release"); - ack(); - - client.print("reset_released\n"); - Serial.println("Sent reset button release"); - ack(); - client.stop(); + wait_power_ack = true; } -} - -// ----------------------------------------- + if (need_reset_ack && !wait_reset_ack) { + String msg = "reset_released"; + esp_now_send(server_mac, (const uint8_t*)msg.c_str(), msg.length()); + Serial.println("Sent reset button release"); -void ack() -{ - while (client.connected()) { - if (client.available()) { - // Wait for acknowledgment from the receiver - String response = client.readStringUntil('\n'); - if (response == "ACK") { - digitalWrite(POWER_LED_PIN, HIGH); - Serial.println("Received ACK"); - return; - } - } + wait_reset_ack = true; } } diff --git a/client/src/secrets.h.example b/client/src/secrets.h.example index 454930f..9722f16 100644 --- a/client/src/secrets.h.example +++ b/client/src/secrets.h.example @@ -1,4 +1,7 @@ #pragma once -#define SSID "EXAMPLE" -#define PASSWORD "12345678" +#define CLIENT_MAC { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } +#define SERVER_MAC { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + +#define PMK "MASTERKEY1234567" // ESP_NOW_KEY_LEN = 16 +#define LMK "1234567890ABCDEF" // ESP_NOW_KEY_LEN = 16 diff --git a/server/src/IPAddress.h b/server/src/IPAddress.h new file mode 100644 index 0000000..e69de29 diff --git a/server/src/connect.cpp b/server/src/connect.cpp new file mode 100644 index 0000000..a6478d7 --- /dev/null +++ b/server/src/connect.cpp @@ -0,0 +1,99 @@ +#include + +#include +#include +#include +#include + +#include "connect.h" +#include "secrets.h" + +uint8_t client_mac[6] = CLIENT_MAC; +uint8_t server_mac[6] = SERVER_MAC; + +bool powerRemotePressed = false; +bool resetRemotePressed = false; + +// ----------------------------------------- + +void wifiSetupEncryption(); + +void onDataSent(const uint8_t* mac_addr, esp_now_send_status_t status) +{ +} + +void onDataRecv(const uint8_t* mac_addr, const uint8_t* data, int data_len) +{ + Serial.print("Received: "); + Serial.write(data, data_len); + Serial.println(); + + auto msg = String((const char*)data, data_len); + + if (msg == "power_pressed") { + powerRemotePressed = true; + } + else if (msg == "power_released") { + powerRemotePressed = false; + } + else if (msg == "reset_pressed") { + resetRemotePressed = true; + } + else if (msg == "reset_released") { + resetRemotePressed = false; + } + else { + return; + } + + String reply = msg + "_ack"; + esp_now_send(client_mac, (const uint8_t*)reply.c_str(), reply.length()); +} + +void wifiSetup() +{ + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(false); + WiFi.disconnect(); + delay(100); + + esp_wifi_set_channel(static_cast(CHANNEL), WIFI_SECOND_CHAN_NONE); + + wifiSetupEncryption(); +} + +void wifiSetupEncryption() +{ + esp_now_deinit(); + + if (esp_now_init() != ESP_OK) { + Serial.println("Failed to init ESP-NOW"); + return; + } + + if (esp_now_set_pmk((const uint8_t*)PMK) != ESP_OK) { + Serial.println("Failed to set PMK"); + return; + } + + auto peerInfo = esp_now_peer_info_t { + .channel = static_cast(CHANNEL), + .encrypt = true, + }; + + memcpy(peerInfo.peer_addr, client_mac, 6); + memcpy(peerInfo.lmk, LMK, 16); + + if (esp_now_add_peer(&peerInfo) != ESP_OK) { + Serial.println("Failed to add peer"); + return; + } + + esp_now_register_send_cb(onDataSent); + esp_now_register_recv_cb(onDataRecv); + + Serial.println("Configured encryption!"); +} + +// https://docs.espressif.com/projects/arduino-esp32/en/latest/api/wifi.html +// https://docs.arduino.cc/libraries/wifi/ (old) diff --git a/server/src/connect.h b/server/src/connect.h new file mode 100644 index 0000000..80d8ee6 --- /dev/null +++ b/server/src/connect.h @@ -0,0 +1,6 @@ +#define CHANNEL 11 + +extern bool powerRemotePressed; +extern bool resetRemotePressed; + +void wifiSetup(); diff --git a/server/src/main.cpp b/server/src/main.cpp index 33581ce..6a66550 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -1,30 +1,21 @@ #include -#include -#include -#include -#include "secrets.h" - -// Default gateway = 192.168.4.1 -// Default IP address = 192.168.4.2 - -#define PORT 1234 -#define CHANNEL 11 -#define HIDDEN true -#define MAX_CONNECTION 1 +#include "connect.h" #define POWER_BUTTON_PIN 10 #define RESET_BUTTON_PIN 2 + #define POWER_LED_PIN 3 #define RESET_LED_PIN 1 #define POWER_GATE_PIN 6 #define RESET_GATE_PIN 7 -WiFiServer server(PORT, MAX_CONNECTION); +int previousPowerButtonState = HIGH; +int previousResetButtonState = HIGH; -bool powerPressed = false; -bool resetPressed = false; +bool powerButtonPressed = false; +bool resetButtonPressed = false; void processButtons(); void setPowerPin(bool enable); @@ -35,6 +26,9 @@ void setup() Serial.begin(9600); Serial.setDebugOutput(true); + pinMode(POWER_BUTTON_PIN, INPUT_PULLUP); + pinMode(RESET_BUTTON_PIN, INPUT_PULLUP); + pinMode(POWER_GATE_PIN, OUTPUT); pinMode(POWER_LED_PIN, OUTPUT); pinMode(RESET_GATE_PIN, OUTPUT); @@ -43,85 +37,55 @@ void setup() delay(3000); Serial.println("Server booted!"); - // Start button server - WiFi.softAP(SSID, PASSWORD, CHANNEL, HIDDEN, MAX_CONNECTION); - Serial.println("AP Started"); - server.begin(); + wifiSetup(); } void loop() { - WiFiClient client = server.available(); - if (client) { - Serial.println("Client connected"); - - unsigned long start = 0; - while (client.connected()) { - if (client.available()) { - String msg = client.readStringUntil('\n'); - Serial.print("Received: "); - Serial.println(msg); - - if (msg == "power_pressed") { - powerPressed = true; - } - else if (msg == "power_released") { - powerPressed = false; - } - else if (msg == "reset_pressed") { - resetPressed = true; - } - else if (msg == "reset_released") { - resetPressed = false; - } - - client.print("ACK\n"); - Serial.println("Sent ACK"); - start = millis(); - } - - // Kill lingering connections - if (start != 0 && millis() - start > 200) { - Serial.println("Client kill.."); - break; - } - - processButtons(); - } + delay(20); // used for button debounce - setPowerPin(false); - setResetPin(false); + int powerButtonState = digitalRead(POWER_BUTTON_PIN); + int resetButtonState = digitalRead(RESET_BUTTON_PIN); - client.stop(); - Serial.println("Client disconnected"); + // Unsupported usecase + if (powerButtonState == LOW && resetButtonState == LOW) { + previousPowerButtonState = HIGH; + previousResetButtonState = HIGH; + powerButtonPressed = false; + resetButtonPressed = false; + return; } + + powerButtonPressed = powerButtonState == LOW; + resetButtonPressed = resetButtonState == LOW; + + processButtons(); } // ----------------------------------------- void processButtons() { - if (powerPressed && resetPressed) { + if ((powerRemotePressed && resetRemotePressed) + || (powerButtonPressed && resetButtonPressed)) { Serial.println("Double input.."); setPowerPin(false); setResetPin(false); return; } - setPowerPin(powerPressed); - setResetPin(resetPressed); + setPowerPin(powerRemotePressed || powerButtonPressed); + setResetPin(resetRemotePressed || resetButtonPressed); } void setPowerPin(bool enable) { - powerPressed = enable; // digitalWrite(POWER_GATE_PIN, (enable) ? HIGH : LOW); digitalWrite(POWER_LED_PIN, (enable) ? HIGH : LOW); } void setResetPin(bool enable) { - resetPressed = enable; // digitalWrite(RESET_GATE_PIN, (enable) ? HIGH : LOW); digitalWrite(RESET_LED_PIN, (enable) ? HIGH : LOW); } diff --git a/server/src/secrets.h.example b/server/src/secrets.h.example index 454930f..9722f16 100644 --- a/server/src/secrets.h.example +++ b/server/src/secrets.h.example @@ -1,4 +1,7 @@ #pragma once -#define SSID "EXAMPLE" -#define PASSWORD "12345678" +#define CLIENT_MAC { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } +#define SERVER_MAC { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + +#define PMK "MASTERKEY1234567" // ESP_NOW_KEY_LEN = 16 +#define LMK "1234567890ABCDEF" // ESP_NOW_KEY_LEN = 16