From 3dcb16970e27f25ff9e0bcf6abfa942fccc52023 Mon Sep 17 00:00:00 2001 From: Zefek Date: Thu, 30 Jul 2026 13:13:52 +0200 Subject: [PATCH 1/6] Init migrace --- .github/workflows/build.yml | 6 +- .github/workflows/deployment.yml | 2 +- .gitignore | 1 + Garage.ino => src/Arduino/Arduino.ino | 0 .../Arduino/config_default.h | 0 .../Arduino/secret_default.h | 0 sha256.cpp => src/Arduino/sha256.cpp | 0 sha256.h => src/Arduino/sha256.h | 0 sketch.yaml => src/Arduino/sketch.yaml | 2 +- src/ESP32/ESP32.ino | 428 ++++++++++++++++++ src/ESP32/config_default.h | 12 + src/ESP32/secret_default.h | 6 + src/ESP32/sketch.yaml | 12 + 13 files changed, 464 insertions(+), 5 deletions(-) rename Garage.ino => src/Arduino/Arduino.ino (100%) rename config_default.h => src/Arduino/config_default.h (100%) rename secret_default.h => src/Arduino/secret_default.h (100%) rename sha256.cpp => src/Arduino/sha256.cpp (100%) rename sha256.h => src/Arduino/sha256.h (100%) rename sketch.yaml => src/Arduino/sketch.yaml (85%) create mode 100644 src/ESP32/ESP32.ino create mode 100644 src/ESP32/config_default.h create mode 100644 src/ESP32/secret_default.h create mode 100644 src/ESP32/sketch.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2ed4d0..fb3073d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,9 +29,9 @@ jobs: - name: Set configs run: | - cp config_default.h config.h - cp secret_default.h secret.h + cp ./src/Arduino/config_default.h ./src/Arduino/config.h + cp ./src/Arduino/secret_default.h ./src/Arduino/secret.h - name: Compile Arduino project run: | - arduino-cli compile --profile Garage_UNO ./ + arduino-cli compile ./src/Arduino diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index baa3d90..81a475b 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -23,5 +23,5 @@ jobs: environment: ${{ inputs.environment }} serial_port: ${{ inputs.serial_port }} device_name: Garage - source_dir: "." + source_dir: "./src/Arduino" secrets: inherit diff --git a/.gitignore b/.gitignore index a7fbec4..3fc0ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ secret.h +config.h diff --git a/Garage.ino b/src/Arduino/Arduino.ino similarity index 100% rename from Garage.ino rename to src/Arduino/Arduino.ino diff --git a/config_default.h b/src/Arduino/config_default.h similarity index 100% rename from config_default.h rename to src/Arduino/config_default.h diff --git a/secret_default.h b/src/Arduino/secret_default.h similarity index 100% rename from secret_default.h rename to src/Arduino/secret_default.h diff --git a/sha256.cpp b/src/Arduino/sha256.cpp similarity index 100% rename from sha256.cpp rename to src/Arduino/sha256.cpp diff --git a/sha256.h b/src/Arduino/sha256.h similarity index 100% rename from sha256.h rename to src/Arduino/sha256.h diff --git a/sketch.yaml b/src/Arduino/sketch.yaml similarity index 85% rename from sketch.yaml rename to src/Arduino/sketch.yaml index 687fc4f..e0f7f05 100644 --- a/sketch.yaml +++ b/src/Arduino/sketch.yaml @@ -5,5 +5,5 @@ profiles: - platform: arduino:avr (1.8.7) libraries: - AM2302-Sensor (1.4.0) - - dir: MQTTESP8266 + - dir: ../../MQTTESP8266 default_profile: Garage_UNO diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino new file mode 100644 index 0000000..93fa8e2 --- /dev/null +++ b/src/ESP32/ESP32.ino @@ -0,0 +1,428 @@ +#include +#include +#include +#include "config.h" +#include "secret.h" +#include "sha256.h" +#include +#include +#include + +#ifndef FW_VERSION +#define FW_VERSION 0 +#endif +#define SENSOR_ID_ADDR 0 +#define SENSOR_CHANNEL 4 + +#define DOORSWITCH_PIN 2 +#define DOORBUTTON_PIN 3 +#define RX_PIN 4 +#define TX_PIN 5 +#define DOORFLASH_PIN 6 +#define TEMPERATURE_SENSOR_PIN 7 + +void MQTTMessageReceive(char* topic, uint8_t* payload, uint16_t length); +void OnBusy(uint8_t count); +void DataTimeout(); + +#pragma pack(push, 1) +struct DiagData { + uint32_t uptime; + uint16_t freeRam; + uint16_t wifiReconn; + uint16_t mqttReconn; + uint8_t sensorErr; + uint8_t resetReason; + uint16_t loopMaxMs; + uint16_t doorCycles; + int8_t rssi; +}; +#pragma pack(pop) + +unsigned long doorChangeTime = 0; +int doorState = LOW; +int moveState = HIGH; +bool doorSignal = false; +SoftwareSerial serial(RX_PIN, TX_PIN); +EspDrv espDrv(&serial); +MQTTClient mqttClient(&espDrv, MQTTMessageReceive); +MQTTConnectData mqttConnectData = { MQTTHost, 1883, "Garage", MQTTUsername, MQTTPassword, "", 0, false, "", false, 0x0 }; +unsigned long mqttConnectionTimeout = 0; +unsigned long mqttLastConnectionTry = 0; +unsigned long currentMillis = 0; +unsigned long temperatureHumidityReadMillis = 0; +AM2302::AM2302_Sensor am2302{ TEMPERATURE_SENSOR_PIN }; +char temperatureData[20]; +uint16_t sensorId = 0; +bool doorMoveDetected = false; +bool closeRequired = false; +DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +unsigned long lastDiagSendMillis = 0; + +extern int __heap_start, *__brkval; +int freeRam() { + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); +} + +#define GARAGE_STATUS_OPENED 1 +#define GARAGE_STATUS_EXPIRED 2 +#define GARAGE_STATUS_BADSIG 3 + +uint8_t signingKey[GARAGE_KEY_LEN]; +struct OpenSlot { + uint8_t nonce[GARAGE_NONCE_LEN]; + uint32_t issuedAt; + uint32_t correlationId; + bool valid; +}; +OpenSlot openSlot = { { 0 }, 0, 0, false }; +bool challengePending = false; +bool responsePending = false; +uint8_t reqBuf[4]; +uint8_t respBuf[4 + GARAGE_SIG_LEN]; +uint8_t entropyPool[32]; + +static uint32_t readLE32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void writeLE32(uint8_t* p, uint32_t v) { + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static uint8_t hexNibble(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return 0; +} + +static bool isHexChar(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +static bool parseSigningKey() { + const char* h = SigningKeyHex; + if (strnlen(h, GARAGE_KEY_LEN * 2 + 2) != (size_t)(GARAGE_KEY_LEN * 2)) return false; + for (uint8_t i = 0; i < GARAGE_KEY_LEN * 2; i++) { + if (!isHexChar(h[i])) return false; + } + for (uint8_t i = 0; i < GARAGE_KEY_LEN; i++) { + signingKey[i] = (hexNibble(h[i * 2]) << 4) | hexNibble(h[i * 2 + 1]); + } + return true; +} + +static void seedEntropy() { + for (uint8_t i = 0; i < 32; i++) { + entropyPool[i] = (uint8_t)analogRead(A0) ^ (uint8_t)(micros() >> (i & 7)); + delayMicroseconds(29); + } +} + +static void stirEntropy() { + static uint8_t idx = 0; + entropyPool[idx & 31] ^= (uint8_t)analogRead(A0); + entropyPool[(idx + 13) & 31] += (uint8_t)micros(); + idx++; +} + +static void generateNonce(uint8_t* out) { + entropyPool[0] ^= (uint8_t)micros(); + entropyPool[1] ^= (uint8_t)analogRead(A0); + uint8_t hash[32]; + Sha256Ctx ctx; + sha256_init(&ctx); + sha256_update(&ctx, entropyPool, sizeof(entropyPool)); + sha256_final(&ctx, hash); + memcpy(out, hash, GARAGE_NONCE_LEN); + for (uint8_t i = 0; i < 32; i++) { + entropyPool[i] ^= hash[i]; + } +} + +static bool cryptoSelfTest() { + uint8_t mac[32]; + hmac_sha256((const uint8_t*)"Jefe", 4, (const uint8_t*)"what do ya want for nothing?", 28, mac); + const uint8_t expected[8] = { 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e }; + for (uint8_t i = 0; i < 8; i++) { + if (mac[i] != expected[i]) return false; + } + return true; +} + +static void publishChallenge(uint32_t r) { + uint8_t buf[4 + GARAGE_NONCE_LEN]; + writeLE32(buf, r); + memcpy(buf + 4, openSlot.nonce, GARAGE_NONCE_LEN); + mqttClient.Publish(GARAGE_OPEN_CHALLENGE, buf, sizeof(buf), false); +} + +static void publishResult(uint32_t r, uint8_t status) { + uint8_t buf[5]; + writeLE32(buf, r); + buf[4] = status; + mqttClient.Publish(GARAGE_OPEN_RESULT, buf, sizeof(buf), false); +} + +static void processHandshake() { + if (challengePending) { + challengePending = false; + uint32_t r = readLE32(reqBuf); + generateNonce(openSlot.nonce); + openSlot.issuedAt = currentMillis; + openSlot.correlationId = r; + openSlot.valid = true; + publishChallenge(r); + } + if (responsePending) { + responsePending = false; + uint32_t r = readLE32(respBuf); + uint8_t status = GARAGE_STATUS_EXPIRED; + if (openSlot.valid + && (currentMillis - openSlot.issuedAt) <= GARAGE_OPEN_TTL + && r == openSlot.correlationId) { + uint8_t msg[4 + GARAGE_NONCE_LEN + 4]; + writeLE32(msg, r); + memcpy(msg + 4, openSlot.nonce, GARAGE_NONCE_LEN); + memcpy(msg + 4 + GARAGE_NONCE_LEN, "open", 4); + uint8_t mac[32]; + hmac_sha256(signingKey, GARAGE_KEY_LEN, msg, sizeof(msg), mac); + uint8_t diff = 0; + for (uint8_t i = 0; i < GARAGE_SIG_LEN; i++) { + diff |= mac[i] ^ respBuf[4 + i]; + } + if (diff == 0) { + doorSignal = true; + openSlot.valid = false; + status = GARAGE_STATUS_OPENED; + } else { + status = GARAGE_STATUS_BADSIG; + } + } + publishResult(r, status); + } +} + +void MQTTMessageReceive(char* topic, uint8_t* payload, uint16_t length) +{ + if(strcmp(topic, GARAGE_CMD) == 0) + { + doorSignal = true; + } + else if(strcmp(topic, GARAGE_OPEN_REQUEST) == 0 && length >= 4) + { + memcpy(reqBuf, payload, 4); + challengePending = true; + } + else if(strcmp(topic, GARAGE_OPEN_RESPONSE) == 0 && length == sizeof(respBuf)) + { + memcpy(respBuf, payload, sizeof(respBuf)); + responsePending = true; + } +} + +void DataTimeout() +{ + Serial.print("Overflow :"); + Serial.println(serial.overflow()); + closeRequired = true; +} + +void OnBusy(uint8_t count) +{ + if(count > 10) + { + closeRequired = true; + } +} + +void PublishDoorState(int state, int movement) +{ + //state - HIGH (door closed), LOW (door open) + //movement - HIGH (door stopped), LOW (door moving) + if (state == HIGH && movement == HIGH) + { + mqttClient.Publish(GARAGE_STATE, "Closed;Stop", true); + } + if (state == LOW && movement == HIGH) + { + mqttClient.Publish(GARAGE_STATE, "Open;Stop", true); + } + if (state == HIGH && movement == LOW) + { + mqttClient.Publish(GARAGE_STATE, "Closed;Move", true); + } + if (state == LOW && movement == LOW) + { + mqttClient.Publish(GARAGE_STATE, "Open;Move", true); + } +} + +void Connect() +{ + if(currentMillis - mqttLastConnectionTry < mqttConnectionTimeout) + { + return; + } + int wifiStatus = espDrv.GetConnectionStatus(); + bool wifiConnected = wifiStatus == WL_CONNECTED; + if(wifiStatus == WL_DISCONNECTED || wifiStatus == WL_IDLE_STATUS) + { + if(currentDiagData.wifiReconn < 0xFFFF) + { + currentDiagData.wifiReconn++; + } + wifiConnected = espDrv.Connect(WifiSSID, WifiPassword); + } + if(wifiConnected) + { + bool isConnected = mqttClient.IsConnected(); + if(!isConnected) + { + if(currentDiagData.mqttReconn < 0xFFFF) + { + currentDiagData.mqttReconn++; + } + if(mqttClient.Connect(mqttConnectData)) + { + PublishDoorState(doorState, moveState); + mqttClient.Subscribe(GARAGE_CMD); + mqttClient.Subscribe(GARAGE_OPEN_REQUEST, 1); + mqttClient.Subscribe(GARAGE_OPEN_RESPONSE, 1); + + mqttLastConnectionTry = currentMillis; + mqttConnectionTimeout = 0; + + } + else + { + mqttLastConnectionTry = currentMillis; + mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), 300000); + } + } + } + else + { + mqttLastConnectionTry = currentMillis; + mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), 300000); + } +} + +void sendDiag() +{ + currentDiagData.uptime = currentMillis / 60000; + currentDiagData.freeRam = freeRam(); + currentDiagData.rssi = espDrv.GetRssi(); + uint8_t buffer[sizeof(DiagData)]; + memcpy(buffer, ¤tDiagData, sizeof(DiagData)); + mqttClient.Publish(GARAGE_DIAG, buffer, sizeof(DiagData), false); + currentDiagData.loopMaxMs = 0; + currentDiagData.sensorErr = 0; +} + +void setup() { + currentDiagData.resetReason = MCUSR; + MCUSR = 0; + pinMode(DOORSWITCH_PIN, INPUT_PULLUP); + pinMode(DOORBUTTON_PIN, OUTPUT); + pinMode(DOORFLASH_PIN, INPUT_PULLUP); + pinMode(TEMPERATURE_SENSOR_PIN, INPUT); + digitalWrite(DOORBUTTON_PIN, HIGH); + Serial.begin(57600); + serial.begin(19200); + espDrv.Init(64); + espDrv.OnBusy = OnBusy; + espDrv.DataTimeout = DataTimeout; + espDrv.Connect(WifiSSID, WifiPassword); + doorState = digitalRead(DOORSWITCH_PIN); + if (am2302.begin()) + { + delay(3000); + } + EEPROM.get(SENSOR_ID_ADDR, sensorId); + if (sensorId == 0xFFFF || sensorId == 0) + { + randomSeed(analogRead(A0)); + sensorId = random(256, 65535); + EEPROM.put(SENSOR_ID_ADDR, sensorId); + } + randomSeed(analogRead(A0) ^ micros()); + seedEntropy(); + if (!parseSigningKey()) { + Serial.println(F("SigningKey INVALID")); + } + Serial.println(cryptoSelfTest() ? F("HMAC selftest OK") : F("HMAC selftest FAIL")); + wdt_enable(WDTO_8S); + Serial.println("Setup OK"); +} + +void loop() { + wdt_reset(); + currentMillis = millis(); + stirEntropy(); + if(closeRequired) + { + espDrv.Close(); + closeRequired = false; + } + if(!mqttClient.Loop()) + { + Connect(); + } + if(digitalRead(DOORFLASH_PIN) == LOW) + { + doorMoveDetected = true; + } + if(currentMillis - doorChangeTime > 1000) + { + int doorValue = digitalRead(DOORSWITCH_PIN); + if(doorValue != doorState || (doorMoveDetected && moveState == HIGH) || (!doorMoveDetected && moveState == LOW)) + { + if(doorState == HIGH && doorValue == LOW && currentDiagData.doorCycles < 0xFFFF) + { + currentDiagData.doorCycles++; + } + doorState = doorValue; + moveState = doorMoveDetected? LOW : HIGH; + PublishDoorState(doorState, moveState); + } + doorChangeTime = currentMillis; + doorMoveDetected = false; + } + processHandshake(); + if(doorSignal) + { + digitalWrite(DOORBUTTON_PIN, LOW); + delay(500); + digitalWrite(DOORBUTTON_PIN, HIGH); + doorSignal = false; + } + if(currentMillis - temperatureHumidityReadMillis > 60000) + { + uint8_t status = am2302.read(); + if(status != AM2302::AM2302_READ_OK) + { + currentDiagData.sensorErr |= 0x01; + } + int temperature = (int)(am2302.get_Temperature() * 10); + int humidity = (int)am2302.get_Humidity(); + sprintf(temperatureData, "%u;%d;%d;%d", sensorId, temperature, humidity, SENSOR_CHANNEL); + mqttClient.Publish(GARAGE_TEMPERATURE, temperatureData); + temperatureHumidityReadMillis = currentMillis; + } + if(currentMillis - lastDiagSendMillis > 900000UL) + { + sendDiag(); + lastDiagSendMillis = currentMillis; + } + uint16_t loopDuration = (uint16_t)(millis() - currentMillis); + if(loopDuration > currentDiagData.loopMaxMs) + { + currentDiagData.loopMaxMs = loopDuration; + } +} diff --git a/src/ESP32/config_default.h b/src/ESP32/config_default.h new file mode 100644 index 0000000..338e609 --- /dev/null +++ b/src/ESP32/config_default.h @@ -0,0 +1,12 @@ +#define GARAGE_STATE "GARAGE_STATE" +#define GARAGE_CMD "GARAGE_CMD" +#define GARAGE_TEMPERATURE "GARAGE_TEMPERATURE" +#define GARAGE_DIAG "GARAGE_DIAG" +#define GARAGE_OPEN_REQUEST "GARAGE_OPEN_REQUEST" +#define GARAGE_OPEN_CHALLENGE "GARAGE_OPEN_CHALLENGE" +#define GARAGE_OPEN_RESPONSE "GARAGE_OPEN_RESPONSE" +#define GARAGE_OPEN_RESULT "GARAGE_OPEN_RESULT" +#define GARAGE_NONCE_LEN 8 +#define GARAGE_KEY_LEN 16 +#define GARAGE_SIG_LEN 16 +#define GARAGE_OPEN_TTL 20000UL diff --git a/src/ESP32/secret_default.h b/src/ESP32/secret_default.h new file mode 100644 index 0000000..c44f11a --- /dev/null +++ b/src/ESP32/secret_default.h @@ -0,0 +1,6 @@ +#define WifiSSID "WifiSSID" +#define WifiPassword "Password" +#define MQTTUsername "UserName" +#define MQTTPassword "Password" +#define MQTTHost "Host" +#define SigningKeyHex "000102030405060708090a0b0c0d0e0f" diff --git a/src/ESP32/sketch.yaml b/src/ESP32/sketch.yaml new file mode 100644 index 0000000..868c9bf --- /dev/null +++ b/src/ESP32/sketch.yaml @@ -0,0 +1,12 @@ +profiles: + Garage_ESP32: + fqbn: esp32:esp32:esp32:PartitionScheme=default + platforms: + - platform: esp32:esp32 (3.2.0) + platform_index_url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + libraries: + - PubSubClient (2.8.0) + - AM2302-Sensor (1.4.0) + port_config: + baudrate: 115200 +default_profile: Garage_ESP32 \ No newline at end of file From 468ad2e8a514252c4e3fb9ba14ba56882d4a99a2 Mon Sep 17 00:00:00 2001 From: Zefek Date: Thu, 30 Jul 2026 13:57:21 +0200 Subject: [PATCH 2/6] ESP32 implementace --- .github/workflows/build.yml | 5 +- src/ESP32/ESP32.ino | 397 ++++++++++++++++++++---------------- src/ESP32/config_default.h | 1 - src/ESP32/crypto.cpp | 8 + src/ESP32/crypto.h | 9 + src/ESP32/ota.cpp | 78 +++++++ src/ESP32/ota.h | 6 + src/ESP32/secret_default.h | 13 ++ 8 files changed, 336 insertions(+), 181 deletions(-) create mode 100644 src/ESP32/crypto.cpp create mode 100644 src/ESP32/crypto.h create mode 100644 src/ESP32/ota.cpp create mode 100644 src/ESP32/ota.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb3073d..b681ff2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,10 @@ jobs: run: | cp ./src/Arduino/config_default.h ./src/Arduino/config.h cp ./src/Arduino/secret_default.h ./src/Arduino/secret.h + cp ./src/ESP32/config_default.h ./src/ESP32/config.h + cp ./src/ESP32/secret_default.h ./src/ESP32/secret.h - - name: Compile Arduino project + - name: Compile run: | arduino-cli compile ./src/Arduino + arduino-cli compile ./src/ESP32 diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino index 93fa8e2..7e43273 100644 --- a/src/ESP32/ESP32.ino +++ b/src/ESP32/ESP32.ino @@ -1,34 +1,50 @@ -#include -#include -#include +#include +#include +#include +#include +#include +#include "time.h" +#include "esp_task_wdt.h" +#include "esp_random.h" #include "config.h" #include "secret.h" -#include "sha256.h" -#include -#include -#include +#include "crypto.h" +#include "ota.h" #ifndef FW_VERSION #define FW_VERSION 0 #endif -#define SENSOR_ID_ADDR 0 + #define SENSOR_CHANNEL 4 +#define DEFAULT_SENSOR_ID 39033 + +#define DOORSWITCH_PIN 22 +#define DOORBUTTON_PIN 23 +#define DOORFLASH_PIN 21 +#define TEMPERATURE_SENSOR_PIN 19 -#define DOORSWITCH_PIN 2 -#define DOORBUTTON_PIN 3 -#define RX_PIN 4 -#define TX_PIN 5 -#define DOORFLASH_PIN 6 -#define TEMPERATURE_SENSOR_PIN 7 +#define MQTT_CLIENT_ID "GarageESP32" +#define MQTT_TLS_PORT 8883 +#define WDT_TIMEOUT_S 90 +#define WIFI_CONNECT_TIMEOUT_MS 15000UL +#define TIME_SYNC_TIMEOUT_MS 15000UL +#define TIME_VALID_THRESHOLD 1700000000UL +#define MQTT_BACKOFF_MAX_MS 60000UL +#define DOOR_DEBOUNCE_MS 1000UL +#define DOOR_PULSE_MS 500UL +#define TEMPERATURE_INTERVAL 60000UL +#define DIAG_INTERVAL 900000UL + +#define GARAGE_STATUS_OPENED 1 +#define GARAGE_STATUS_EXPIRED 2 +#define GARAGE_STATUS_BADSIG 3 -void MQTTMessageReceive(char* topic, uint8_t* payload, uint16_t length); -void OnBusy(uint8_t count); -void DataTimeout(); +void MQTTMessageReceive(char* topic, uint8_t* payload, unsigned int length); #pragma pack(push, 1) struct DiagData { uint32_t uptime; - uint16_t freeRam; + uint16_t freeRamKb; uint16_t wifiReconn; uint16_t mqttReconn; uint8_t sensorErr; @@ -36,38 +52,40 @@ struct DiagData { uint16_t loopMaxMs; uint16_t doorCycles; int8_t rssi; + uint16_t fwVersion; + uint16_t otaFailCount; }; #pragma pack(pop) +static_assert(sizeof(DiagData) == 21, "DiagData wire layout must stay 21 bytes"); +WiFiClientSecure net; +PubSubClient mqtt(net); +Preferences preferences; +AM2302::AM2302_Sensor am2302{ TEMPERATURE_SENSOR_PIN }; + +unsigned long currentMillis = 0; unsigned long doorChangeTime = 0; -int doorState = LOW; -int moveState = HIGH; -bool doorSignal = false; -SoftwareSerial serial(RX_PIN, TX_PIN); -EspDrv espDrv(&serial); -MQTTClient mqttClient(&espDrv, MQTTMessageReceive); -MQTTConnectData mqttConnectData = { MQTTHost, 1883, "Garage", MQTTUsername, MQTTPassword, "", 0, false, "", false, 0x0 }; +unsigned long doorPulseStart = 0; unsigned long mqttConnectionTimeout = 0; unsigned long mqttLastConnectionTry = 0; -unsigned long currentMillis = 0; unsigned long temperatureHumidityReadMillis = 0; -AM2302::AM2302_Sensor am2302{ TEMPERATURE_SENSOR_PIN }; -char temperatureData[20]; -uint16_t sensorId = 0; -bool doorMoveDetected = false; -bool closeRequired = false; -DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; unsigned long lastDiagSendMillis = 0; -extern int __heap_start, *__brkval; -int freeRam() { - int v; - return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); -} +int doorState = HIGH; +int moveState = HIGH; +bool doorSignal = false; +bool doorPulseActive = false; +bool doorMoveDetected = false; +bool timeSynced = false; -#define GARAGE_STATUS_OPENED 1 -#define GARAGE_STATUS_EXPIRED 2 -#define GARAGE_STATUS_BADSIG 3 +portMUX_TYPE flashMux = portMUX_INITIALIZER_UNLOCKED; +volatile bool doorFlashSeen = false; + +char temperatureData[20]; +uint16_t sensorId = 0; + +DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +uint16_t otaFailures = 0; uint8_t signingKey[GARAGE_KEY_LEN]; struct OpenSlot { @@ -81,7 +99,6 @@ bool challengePending = false; bool responsePending = false; uint8_t reqBuf[4]; uint8_t respBuf[4 + GARAGE_SIG_LEN]; -uint8_t entropyPool[32]; static uint32_t readLE32(const uint8_t* p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); @@ -117,34 +134,6 @@ static bool parseSigningKey() { return true; } -static void seedEntropy() { - for (uint8_t i = 0; i < 32; i++) { - entropyPool[i] = (uint8_t)analogRead(A0) ^ (uint8_t)(micros() >> (i & 7)); - delayMicroseconds(29); - } -} - -static void stirEntropy() { - static uint8_t idx = 0; - entropyPool[idx & 31] ^= (uint8_t)analogRead(A0); - entropyPool[(idx + 13) & 31] += (uint8_t)micros(); - idx++; -} - -static void generateNonce(uint8_t* out) { - entropyPool[0] ^= (uint8_t)micros(); - entropyPool[1] ^= (uint8_t)analogRead(A0); - uint8_t hash[32]; - Sha256Ctx ctx; - sha256_init(&ctx); - sha256_update(&ctx, entropyPool, sizeof(entropyPool)); - sha256_final(&ctx, hash); - memcpy(out, hash, GARAGE_NONCE_LEN); - for (uint8_t i = 0; i < 32; i++) { - entropyPool[i] ^= hash[i]; - } -} - static bool cryptoSelfTest() { uint8_t mac[32]; hmac_sha256((const uint8_t*)"Jefe", 4, (const uint8_t*)"what do ya want for nothing?", 28, mac); @@ -155,25 +144,37 @@ static bool cryptoSelfTest() { return true; } +void IRAM_ATTR OnDoorFlash() { + doorFlashSeen = true; +} + +static bool TakeDoorFlash() { + portENTER_CRITICAL(&flashMux); + bool seen = doorFlashSeen; + doorFlashSeen = false; + portEXIT_CRITICAL(&flashMux); + return seen; +} + static void publishChallenge(uint32_t r) { uint8_t buf[4 + GARAGE_NONCE_LEN]; writeLE32(buf, r); memcpy(buf + 4, openSlot.nonce, GARAGE_NONCE_LEN); - mqttClient.Publish(GARAGE_OPEN_CHALLENGE, buf, sizeof(buf), false); + mqtt.publish(GARAGE_OPEN_CHALLENGE, buf, sizeof(buf), false); } static void publishResult(uint32_t r, uint8_t status) { uint8_t buf[5]; writeLE32(buf, r); buf[4] = status; - mqttClient.Publish(GARAGE_OPEN_RESULT, buf, sizeof(buf), false); + mqtt.publish(GARAGE_OPEN_RESULT, buf, sizeof(buf), false); } static void processHandshake() { if (challengePending) { challengePending = false; uint32_t r = readLE32(reqBuf); - generateNonce(openSlot.nonce); + esp_fill_random(openSlot.nonce, GARAGE_NONCE_LEN); openSlot.issuedAt = currentMillis; openSlot.correlationId = r; openSlot.valid = true; @@ -208,13 +209,9 @@ static void processHandshake() { } } -void MQTTMessageReceive(char* topic, uint8_t* payload, uint16_t length) +void MQTTMessageReceive(char* topic, uint8_t* payload, unsigned int length) { - if(strcmp(topic, GARAGE_CMD) == 0) - { - doorSignal = true; - } - else if(strcmp(topic, GARAGE_OPEN_REQUEST) == 0 && length >= 4) + if(strcmp(topic, GARAGE_OPEN_REQUEST) == 0 && length >= 4) { memcpy(reqBuf, payload, 4); challengePending = true; @@ -226,159 +223,186 @@ void MQTTMessageReceive(char* topic, uint8_t* payload, uint16_t length) } } -void DataTimeout() -{ - Serial.print("Overflow :"); - Serial.println(serial.overflow()); - closeRequired = true; -} - -void OnBusy(uint8_t count) -{ - if(count > 10) - { - closeRequired = true; - } -} - void PublishDoorState(int state, int movement) { - //state - HIGH (door closed), LOW (door open) - //movement - HIGH (door stopped), LOW (door moving) if (state == HIGH && movement == HIGH) { - mqttClient.Publish(GARAGE_STATE, "Closed;Stop", true); + mqtt.publish(GARAGE_STATE, "Closed;Stop", true); } if (state == LOW && movement == HIGH) { - mqttClient.Publish(GARAGE_STATE, "Open;Stop", true); + mqtt.publish(GARAGE_STATE, "Open;Stop", true); } if (state == HIGH && movement == LOW) { - mqttClient.Publish(GARAGE_STATE, "Closed;Move", true); + mqtt.publish(GARAGE_STATE, "Closed;Move", true); } if (state == LOW && movement == LOW) { - mqttClient.Publish(GARAGE_STATE, "Open;Move", true); + mqtt.publish(GARAGE_STATE, "Open;Move", true); } } -void Connect() +bool SyncTime() { + configTime(0, 0, "pool.ntp.org", "time.nist.gov"); + unsigned long start = millis(); + time_t now = time(nullptr); + while(now < TIME_VALID_THRESHOLD && millis() - start < TIME_SYNC_TIMEOUT_MS) + { + esp_task_wdt_reset(); + delay(200); + now = time(nullptr); + } + return now >= TIME_VALID_THRESHOLD; +} + +bool Connect() +{ + if(mqtt.connected()) + { + return true; + } if(currentMillis - mqttLastConnectionTry < mqttConnectionTimeout) { - return; + return false; } - int wifiStatus = espDrv.GetConnectionStatus(); - bool wifiConnected = wifiStatus == WL_CONNECTED; - if(wifiStatus == WL_DISCONNECTED || wifiStatus == WL_IDLE_STATUS) + mqttLastConnectionTry = currentMillis; + if(WiFi.status() != WL_CONNECTED) { if(currentDiagData.wifiReconn < 0xFFFF) { currentDiagData.wifiReconn++; } - wifiConnected = espDrv.Connect(WifiSSID, WifiPassword); + WiFi.begin(WifiSSID, WifiPassword); + unsigned long start = millis(); + while(WiFi.status() != WL_CONNECTED && millis() - start < WIFI_CONNECT_TIMEOUT_MS) + { + esp_task_wdt_reset(); + delay(100); + } } - if(wifiConnected) + if(WiFi.status() != WL_CONNECTED) { - bool isConnected = mqttClient.IsConnected(); - if(!isConnected) + mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), MQTT_BACKOFF_MAX_MS); + return false; + } + if(!timeSynced) + { + timeSynced = SyncTime(); + if(!timeSynced) { - if(currentDiagData.mqttReconn < 0xFFFF) - { - currentDiagData.mqttReconn++; - } - if(mqttClient.Connect(mqttConnectData)) - { - PublishDoorState(doorState, moveState); - mqttClient.Subscribe(GARAGE_CMD); - mqttClient.Subscribe(GARAGE_OPEN_REQUEST, 1); - mqttClient.Subscribe(GARAGE_OPEN_RESPONSE, 1); - - mqttLastConnectionTry = currentMillis; - mqttConnectionTimeout = 0; - - } - else - { - mqttLastConnectionTry = currentMillis; - mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), 300000); - } + mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), MQTT_BACKOFF_MAX_MS); + return false; } } - else + if(currentDiagData.mqttReconn < 0xFFFF) + { + currentDiagData.mqttReconn++; + } + if(!mqtt.connect(MQTT_CLIENT_ID, MQTTUsername, MQTTPassword)) { - mqttLastConnectionTry = currentMillis; - mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), 300000); + mqttConnectionTimeout = min(mqttConnectionTimeout * 2 + random(0, 5000), MQTT_BACKOFF_MAX_MS); + return false; } + mqtt.subscribe(GARAGE_OPEN_REQUEST, 1); + mqtt.subscribe(GARAGE_OPEN_RESPONSE, 1); + PublishDoorState(doorState, moveState); + mqttConnectionTimeout = 0; + return true; } void sendDiag() { - currentDiagData.uptime = currentMillis / 60000; - currentDiagData.freeRam = freeRam(); - currentDiagData.rssi = espDrv.GetRssi(); - uint8_t buffer[sizeof(DiagData)]; - memcpy(buffer, ¤tDiagData, sizeof(DiagData)); - mqttClient.Publish(GARAGE_DIAG, buffer, sizeof(DiagData), false); + currentDiagData.uptime = currentMillis / 60000UL; + currentDiagData.freeRamKb = (uint16_t)(ESP.getFreeHeap() / 1024); + currentDiagData.rssi = (int8_t)WiFi.RSSI(); + currentDiagData.fwVersion = (uint16_t)FW_VERSION; + currentDiagData.otaFailCount = otaFailures; + mqtt.publish(GARAGE_DIAG, (const uint8_t*)¤tDiagData, sizeof(DiagData), false); currentDiagData.loopMaxMs = 0; currentDiagData.sensorErr = 0; } +bool OtaAllowed() +{ + return doorState == HIGH + && moveState == HIGH + && !doorPulseActive + && !doorSignal + && !openSlot.valid + && !challengePending + && !responsePending; +} + void setup() { - currentDiagData.resetReason = MCUSR; - MCUSR = 0; - pinMode(DOORSWITCH_PIN, INPUT_PULLUP); + currentDiagData.resetReason = (uint8_t)esp_reset_reason(); + + digitalWrite(DOORBUTTON_PIN, HIGH); pinMode(DOORBUTTON_PIN, OUTPUT); - pinMode(DOORFLASH_PIN, INPUT_PULLUP); - pinMode(TEMPERATURE_SENSOR_PIN, INPUT); digitalWrite(DOORBUTTON_PIN, HIGH); - Serial.begin(57600); - serial.begin(19200); - espDrv.Init(64); - espDrv.OnBusy = OnBusy; - espDrv.DataTimeout = DataTimeout; - espDrv.Connect(WifiSSID, WifiPassword); - doorState = digitalRead(DOORSWITCH_PIN); - if (am2302.begin()) + + pinMode(DOORSWITCH_PIN, INPUT_PULLUP); + pinMode(DOORFLASH_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(DOORFLASH_PIN), OnDoorFlash, FALLING); + + Serial.begin(115200); + + preferences.begin("garage", false); + sensorId = preferences.getUShort("sensorId", 0); + if (sensorId == 0xFFFF || sensorId == 0) { - delay(3000); + sensorId = DEFAULT_SENSOR_ID; + preferences.putUShort("sensorId", sensorId); } - EEPROM.get(SENSOR_ID_ADDR, sensorId); - if (sensorId == 0xFFFF || sensorId == 0) + + WiFi.mode(WIFI_STA); + WiFi.begin(WifiSSID, WifiPassword); + net.setCACert(MQTTCACert); + mqtt.setServer(MQTTHost, MQTT_TLS_PORT); + mqtt.setCallback(MQTTMessageReceive); + mqtt.setBufferSize(256); + mqtt.setKeepAlive(60); + + if (am2302.begin()) { - randomSeed(analogRead(A0)); - sensorId = random(256, 65535); - EEPROM.put(SENSOR_ID_ADDR, sensorId); + delay(3000); } - randomSeed(analogRead(A0) ^ micros()); - seedEntropy(); + + doorState = digitalRead(DOORSWITCH_PIN); + randomSeed(esp_random()); + if (!parseSigningKey()) { - Serial.println(F("SigningKey INVALID")); + Serial.println("SigningKey INVALID"); } - Serial.println(cryptoSelfTest() ? F("HMAC selftest OK") : F("HMAC selftest FAIL")); - wdt_enable(WDTO_8S); + Serial.println(cryptoSelfTest() ? "HMAC selftest OK" : "HMAC selftest FAIL"); + + esp_task_wdt_config_t wdtConfig = { + .timeout_ms = WDT_TIMEOUT_S * 1000, + .idle_core_mask = 0, + .trigger_panic = true + }; + esp_task_wdt_reconfigure(&wdtConfig); + esp_task_wdt_add(NULL); Serial.println("Setup OK"); } void loop() { - wdt_reset(); currentMillis = millis(); - stirEntropy(); - if(closeRequired) - { - espDrv.Close(); - closeRequired = false; - } - if(!mqttClient.Loop()) + esp_task_wdt_reset(); + + if(!mqtt.connected()) { Connect(); } - if(digitalRead(DOORFLASH_PIN) == LOW) + mqtt.loop(); + + if(TakeDoorFlash() || digitalRead(DOORFLASH_PIN) == LOW) { doorMoveDetected = true; } - if(currentMillis - doorChangeTime > 1000) + + if(currentMillis - doorChangeTime > DOOR_DEBOUNCE_MS) { int doorValue = digitalRead(DOORSWITCH_PIN); if(doorValue != doorState || (doorMoveDetected && moveState == HIGH) || (!doorMoveDetected && moveState == LOW)) @@ -394,15 +418,23 @@ void loop() { doorChangeTime = currentMillis; doorMoveDetected = false; } + processHandshake(); - if(doorSignal) + + if(doorSignal && !doorPulseActive) { digitalWrite(DOORBUTTON_PIN, LOW); - delay(500); - digitalWrite(DOORBUTTON_PIN, HIGH); + doorPulseStart = currentMillis; + doorPulseActive = true; doorSignal = false; } - if(currentMillis - temperatureHumidityReadMillis > 60000) + if(doorPulseActive && currentMillis - doorPulseStart >= DOOR_PULSE_MS) + { + digitalWrite(DOORBUTTON_PIN, HIGH); + doorPulseActive = false; + } + + if(currentMillis - temperatureHumidityReadMillis > TEMPERATURE_INTERVAL) { uint8_t status = am2302.read(); if(status != AM2302::AM2302_READ_OK) @@ -412,17 +444,24 @@ void loop() { int temperature = (int)(am2302.get_Temperature() * 10); int humidity = (int)am2302.get_Humidity(); sprintf(temperatureData, "%u;%d;%d;%d", sensorId, temperature, humidity, SENSOR_CHANNEL); - mqttClient.Publish(GARAGE_TEMPERATURE, temperatureData); + mqtt.publish(GARAGE_TEMPERATURE, temperatureData); temperatureHumidityReadMillis = currentMillis; } - if(currentMillis - lastDiagSendMillis > 900000UL) + + if(currentMillis - lastDiagSendMillis > DIAG_INTERVAL) { sendDiag(); lastDiagSendMillis = currentMillis; } - uint16_t loopDuration = (uint16_t)(millis() - currentMillis); + + if(OtaAllowed()) + { + otaLoop(); + } + + unsigned long loopDuration = millis() - currentMillis; if(loopDuration > currentDiagData.loopMaxMs) { - currentDiagData.loopMaxMs = loopDuration; + currentDiagData.loopMaxMs = (loopDuration > 65535UL) ? 65535 : (uint16_t)loopDuration; } } diff --git a/src/ESP32/config_default.h b/src/ESP32/config_default.h index 338e609..ae52466 100644 --- a/src/ESP32/config_default.h +++ b/src/ESP32/config_default.h @@ -1,5 +1,4 @@ #define GARAGE_STATE "GARAGE_STATE" -#define GARAGE_CMD "GARAGE_CMD" #define GARAGE_TEMPERATURE "GARAGE_TEMPERATURE" #define GARAGE_DIAG "GARAGE_DIAG" #define GARAGE_OPEN_REQUEST "GARAGE_OPEN_REQUEST" diff --git a/src/ESP32/crypto.cpp b/src/ESP32/crypto.cpp new file mode 100644 index 0000000..66865d7 --- /dev/null +++ b/src/ESP32/crypto.cpp @@ -0,0 +1,8 @@ +#include "crypto.h" +#include + +void hmac_sha256(const uint8_t* key, size_t keylen, const uint8_t* msg, size_t msglen, uint8_t out[32]) +{ + const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + mbedtls_md_hmac(info, key, keylen, msg, msglen, out); +} diff --git a/src/ESP32/crypto.h b/src/ESP32/crypto.h new file mode 100644 index 0000000..72a34a8 --- /dev/null +++ b/src/ESP32/crypto.h @@ -0,0 +1,9 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +#include +#include + +void hmac_sha256(const uint8_t* key, size_t keylen, const uint8_t* msg, size_t msglen, uint8_t out[32]); + +#endif diff --git a/src/ESP32/ota.cpp b/src/ESP32/ota.cpp new file mode 100644 index 0000000..dac0258 --- /dev/null +++ b/src/ESP32/ota.cpp @@ -0,0 +1,78 @@ +#include "ota.h" +#include +#include +#include +#include +#include "time.h" +#include "config.h" +#include "secret.h" + +#ifndef OTA_CHECK_INTERVAL_MS +#define OTA_CHECK_INTERVAL_MS (60UL * 60UL * 1000UL) +#endif +#ifndef FW_VERSION +#define FW_VERSION 0 +#endif +#define OTA_TIME_VALID_THRESHOLD 1700000000UL + +extern uint16_t otaFailures; + +unsigned long otaLastCheck = 0; +bool otaFirstRun = true; + +void doOTA() +{ + Serial.printf("OTA: kontrola z %s (verze %d)\n", OtaUrl, (int)FW_VERSION); + WiFiClientSecure client; + client.setCACert(OtaRootCA); + + HTTPUpdate updater(30000); + updater.setAuthorization(OtaUser, OtaPassword); + updater.onStart([]() { esp_task_wdt_reset(); }); + updater.onEnd([]() { esp_task_wdt_reset(); }); + updater.onProgress([](int cur, int total) { esp_task_wdt_reset(); }); + updater.onError([](int err) { Serial.printf("OTA: chyba %d\n", err); }); + updater.rebootOnUpdate(true); + + t_httpUpdate_return ret = updater.update(client, OtaUrl, String((int)FW_VERSION)); + + switch(ret) + { + case HTTP_UPDATE_FAILED: + Serial.printf("OTA: SELHALA (%d): %s\n", updater.getLastError(), updater.getLastErrorString().c_str()); + if(otaFailures < 65535) + { + otaFailures++; + } + break; + case HTTP_UPDATE_NO_UPDATES: + Serial.println("OTA: firmware je aktualni."); + break; + case HTTP_UPDATE_OK: + Serial.println("OTA: OK."); + break; + } +} + +void otaLoop() +{ + if(!otaFirstRun && millis() - otaLastCheck < OTA_CHECK_INTERVAL_MS) + { + return; + } + if(WiFi.status() != WL_CONNECTED) + { + return; + } + if(time(nullptr) < OTA_TIME_VALID_THRESHOLD) + { + return; + } + if(otaFirstRun) + { + Serial.printf("OTA: interval kontroly %lu ms\n", (unsigned long)OTA_CHECK_INTERVAL_MS); + } + otaFirstRun = false; + otaLastCheck = millis(); + doOTA(); +} diff --git a/src/ESP32/ota.h b/src/ESP32/ota.h new file mode 100644 index 0000000..5b30e8e --- /dev/null +++ b/src/ESP32/ota.h @@ -0,0 +1,6 @@ +#ifndef OTA_H +#define OTA_H + +void otaLoop(); + +#endif diff --git a/src/ESP32/secret_default.h b/src/ESP32/secret_default.h index c44f11a..d60cdab 100644 --- a/src/ESP32/secret_default.h +++ b/src/ESP32/secret_default.h @@ -4,3 +4,16 @@ #define MQTTPassword "Password" #define MQTTHost "Host" #define SigningKeyHex "000102030405060708090a0b0c0d0e0f" +#define MQTTCACert \ +"-----BEGIN CERTIFICATE-----\n" \ +"REPLACE_WITH_BROKER_CA_CERT_PEM\n" \ +"-----END CERTIFICATE-----\n" +#define OtaUrl "https://OtaHost/garage.bin" +#define OtaUser "OtaUser" +#define OtaPassword "OtaPassword" +#define OTA_CHECK_INTERVAL_MS 3600000 +#define OtaRootCA R"EOF( +-----BEGIN CERTIFICATE----- +REPLACE_WITH_OTA_SERVER_ROOT_CA_PEM +-----END CERTIFICATE----- +)EOF" From f4e8d9c0a887ca39ea633e1b18ffefdfaff138df Mon Sep 17 00:00:00 2001 From: Zefek Date: Thu, 30 Jul 2026 14:32:32 +0200 Subject: [PATCH 3/6] Pohyb vrat a reverzace chodu --- src/ESP32/ESP32.ino | 317 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 261 insertions(+), 56 deletions(-) diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino index 7e43273..bcc83b0 100644 --- a/src/ESP32/ESP32.ino +++ b/src/ESP32/ESP32.ino @@ -30,7 +30,15 @@ #define TIME_SYNC_TIMEOUT_MS 15000UL #define TIME_VALID_THRESHOLD 1700000000UL #define MQTT_BACKOFF_MAX_MS 60000UL -#define DOOR_DEBOUNCE_MS 1000UL + +#define T_FULL_MS 16000UL +#define T_FULL_MARGIN_MS 1000UL +#define T_LEAD_DEFAULT_MS 1500UL +#define FLASH_TIMEOUT_MS 1200UL +#define REVERSE_MARGIN_MS 2000UL +#define REED_DEBOUNCE_MS 50UL +#define MOVE_PUBLISH_INTERVAL 1000UL + #define DOOR_PULSE_MS 500UL #define TEMPERATURE_INTERVAL 60000UL #define DIAG_INTERVAL 900000UL @@ -41,6 +49,8 @@ void MQTTMessageReceive(char* topic, uint8_t* payload, unsigned int length); +enum DoorState { DoorUnknown, DoorClosed, DoorOpening, DoorOpen, DoorClosing, DoorStopped }; + #pragma pack(push, 1) struct DiagData { uint32_t uptime; @@ -54,9 +64,11 @@ struct DiagData { int8_t rssi; uint16_t fwVersion; uint16_t otaFailCount; + uint16_t lastTravelMs; + uint16_t lastLeadMs; }; #pragma pack(pop) -static_assert(sizeof(DiagData) == 21, "DiagData wire layout must stay 21 bytes"); +static_assert(sizeof(DiagData) == 25, "DiagData wire layout must stay 25 bytes"); WiFiClientSecure net; PubSubClient mqtt(net); @@ -64,27 +76,42 @@ Preferences preferences; AM2302::AM2302_Sensor am2302{ TEMPERATURE_SENSOR_PIN }; unsigned long currentMillis = 0; -unsigned long doorChangeTime = 0; unsigned long doorPulseStart = 0; unsigned long mqttConnectionTimeout = 0; unsigned long mqttLastConnectionTry = 0; unsigned long temperatureHumidityReadMillis = 0; unsigned long lastDiagSendMillis = 0; +unsigned long lastStatePublish = 0; -int doorState = HIGH; -int moveState = HIGH; bool doorSignal = false; bool doorPulseActive = false; -bool doorMoveDetected = false; bool timeSynced = false; -portMUX_TYPE flashMux = portMUX_INITIALIZER_UNLOCKED; -volatile bool doorFlashSeen = false; +DoorState doorState = DoorUnknown; +int8_t lastDirection = 0; +unsigned long travelMs = 0; +unsigned long travelBase = 0; +unsigned long movementOrigin = 0; +bool movementOriginValid = false; +bool moving = false; +bool awaitingReed = false; +bool travelFromClosed = false; +unsigned long leadMs = T_LEAD_DEFAULT_MS; +uint16_t measuredTravelMs = 0; +uint16_t measuredLeadMs = 0; + +volatile unsigned long lastFlashMillis = 0; +bool flashActive = false; +unsigned long burstStartMillis = 0; + +int reedStable = HIGH; +int reedRaw = HIGH; +unsigned long reedRawChangeAt = 0; char temperatureData[20]; uint16_t sensorId = 0; -DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uint16_t otaFailures = 0; uint8_t signingKey[GARAGE_KEY_LEN]; @@ -145,15 +172,196 @@ static bool cryptoSelfTest() { } void IRAM_ATTR OnDoorFlash() { - doorFlashSeen = true; + lastFlashMillis = millis(); +} + +static unsigned long currentTravel() +{ + if(!moving || !movementOriginValid) + { + return travelMs; + } + long delta = (long)(currentMillis - movementOrigin); + if(delta <= 0) + { + return travelBase; + } + unsigned long elapsed = (unsigned long)delta; + if(lastDirection > 0) + { + unsigned long t = travelBase + elapsed; + return t > T_FULL_MS ? T_FULL_MS : t; + } + if(lastDirection < 0) + { + return elapsed >= travelBase ? 0 : travelBase - elapsed; + } + return travelBase; +} + +static int positionPercent() +{ + if(doorState == DoorUnknown) + { + return -1; + } + return (int)((currentTravel() * 100UL) / T_FULL_MS); +} + +static const char* motionText() +{ + if(!moving) + { + return "Stop"; + } + if(lastDirection > 0) + { + return "Opening"; + } + if(lastDirection < 0) + { + return "Closing"; + } + return "Move"; +} + +void PublishDoorState() +{ + char payload[24]; + sprintf(payload, "%s;%s;%d", reedStable == HIGH ? "Closed" : "Open", motionText(), positionPercent()); + mqtt.publish(GARAGE_STATE, payload, true); + lastStatePublish = currentMillis; +} + +void OnMovementStart(unsigned long firstFlashAt) +{ + burstStartMillis = firstFlashAt; + travelBase = travelMs; + travelFromClosed = false; + awaitingReed = false; + movementOriginValid = false; + moving = true; + + if(doorState == DoorClosed) + { + lastDirection = 1; + doorState = DoorOpening; + travelBase = 0; + awaitingReed = true; + } + else if(doorState == DoorOpen) + { + lastDirection = -1; + doorState = DoorClosing; + movementOrigin = firstFlashAt + leadMs; + movementOriginValid = true; + } + else if(doorState == DoorStopped && lastDirection != 0) + { + lastDirection = lastDirection > 0 ? -1 : 1; + doorState = lastDirection > 0 ? DoorOpening : DoorClosing; + movementOrigin = firstFlashAt + leadMs; + movementOriginValid = true; + } + else + { + lastDirection = 0; + doorState = DoorUnknown; + } + PublishDoorState(); +} + +void OnMovementEnd(unsigned long lastFlashAt) +{ + moving = false; + if(movementOriginValid) + { + long delta = (long)(lastFlashAt - movementOrigin); + unsigned long elapsed = delta > 0 ? (unsigned long)delta : 0; + if(lastDirection > 0) + { + travelMs = travelBase + elapsed; + if(travelMs > T_FULL_MS) + { + travelMs = T_FULL_MS; + } + if(travelFromClosed) + { + measuredTravelMs = elapsed > 65535UL ? 65535 : (uint16_t)elapsed; + } + } + else if(lastDirection < 0) + { + if(elapsed > travelBase + REVERSE_MARGIN_MS) + { + travelMs = T_FULL_MS; + lastDirection = 1; + currentDiagData.sensorErr |= 0x02; + } + else + { + travelMs = elapsed >= travelBase ? 0 : travelBase - elapsed; + } + } + } + movementOriginValid = false; + awaitingReed = false; + travelFromClosed = false; + + if(reedStable == HIGH) + { + travelMs = 0; + doorState = DoorClosed; + } + else if(lastDirection == 0) + { + doorState = DoorUnknown; + } + else if(travelMs + T_FULL_MARGIN_MS >= T_FULL_MS) + { + travelMs = T_FULL_MS; + doorState = DoorOpen; + } + else + { + doorState = DoorStopped; + } + PublishDoorState(); } -static bool TakeDoorFlash() { - portENTER_CRITICAL(&flashMux); - bool seen = doorFlashSeen; - doorFlashSeen = false; - portEXIT_CRITICAL(&flashMux); - return seen; +void OnReedChanged(unsigned long edgeAt) +{ + if(reedStable == HIGH) + { + travelMs = 0; + travelBase = 0; + movementOriginValid = false; + awaitingReed = false; + travelFromClosed = false; + doorState = DoorClosed; + } + else + { + if(currentDiagData.doorCycles < 0xFFFF) + { + currentDiagData.doorCycles++; + } + if(awaitingReed) + { + awaitingReed = false; + movementOrigin = edgeAt; + movementOriginValid = true; + travelBase = 0; + travelFromClosed = true; + long lead = (long)(edgeAt - burstStartMillis); + if(lead > 0) + { + leadMs = (unsigned long)lead; + measuredLeadMs = lead > 65535L ? 65535 : (uint16_t)lead; + } + } + } + PublishDoorState(); } static void publishChallenge(uint32_t r) { @@ -223,26 +431,6 @@ void MQTTMessageReceive(char* topic, uint8_t* payload, unsigned int length) } } -void PublishDoorState(int state, int movement) -{ - if (state == HIGH && movement == HIGH) - { - mqtt.publish(GARAGE_STATE, "Closed;Stop", true); - } - if (state == LOW && movement == HIGH) - { - mqtt.publish(GARAGE_STATE, "Open;Stop", true); - } - if (state == HIGH && movement == LOW) - { - mqtt.publish(GARAGE_STATE, "Closed;Move", true); - } - if (state == LOW && movement == LOW) - { - mqtt.publish(GARAGE_STATE, "Open;Move", true); - } -} - bool SyncTime() { configTime(0, 0, "pool.ntp.org", "time.nist.gov"); @@ -307,7 +495,7 @@ bool Connect() } mqtt.subscribe(GARAGE_OPEN_REQUEST, 1); mqtt.subscribe(GARAGE_OPEN_RESPONSE, 1); - PublishDoorState(doorState, moveState); + PublishDoorState(); mqttConnectionTimeout = 0; return true; } @@ -319,6 +507,8 @@ void sendDiag() currentDiagData.rssi = (int8_t)WiFi.RSSI(); currentDiagData.fwVersion = (uint16_t)FW_VERSION; currentDiagData.otaFailCount = otaFailures; + currentDiagData.lastTravelMs = measuredTravelMs; + currentDiagData.lastLeadMs = measuredLeadMs; mqtt.publish(GARAGE_DIAG, (const uint8_t*)¤tDiagData, sizeof(DiagData), false); currentDiagData.loopMaxMs = 0; currentDiagData.sensorErr = 0; @@ -326,8 +516,8 @@ void sendDiag() bool OtaAllowed() { - return doorState == HIGH - && moveState == HIGH + return doorState == DoorClosed + && !moving && !doorPulseActive && !doorSignal && !openSlot.valid @@ -369,7 +559,9 @@ void setup() { delay(3000); } - doorState = digitalRead(DOORSWITCH_PIN); + reedRaw = digitalRead(DOORSWITCH_PIN); + reedStable = reedRaw; + doorState = reedStable == HIGH ? DoorClosed : DoorUnknown; randomSeed(esp_random()); if (!parseSigningKey()) { @@ -397,26 +589,39 @@ void loop() { } mqtt.loop(); - if(TakeDoorFlash() || digitalRead(DOORFLASH_PIN) == LOW) + if(digitalRead(DOORFLASH_PIN) == LOW) { - doorMoveDetected = true; + lastFlashMillis = currentMillis; } + unsigned long flashAt = lastFlashMillis; + bool nowFlashing = flashAt != 0 && currentMillis - flashAt < FLASH_TIMEOUT_MS; - if(currentMillis - doorChangeTime > DOOR_DEBOUNCE_MS) + int raw = digitalRead(DOORSWITCH_PIN); + if(raw != reedRaw) { - int doorValue = digitalRead(DOORSWITCH_PIN); - if(doorValue != doorState || (doorMoveDetected && moveState == HIGH) || (!doorMoveDetected && moveState == LOW)) - { - if(doorState == HIGH && doorValue == LOW && currentDiagData.doorCycles < 0xFFFF) - { - currentDiagData.doorCycles++; - } - doorState = doorValue; - moveState = doorMoveDetected? LOW : HIGH; - PublishDoorState(doorState, moveState); - } - doorChangeTime = currentMillis; - doorMoveDetected = false; + reedRaw = raw; + reedRawChangeAt = currentMillis; + } + else if(reedStable != reedRaw && currentMillis - reedRawChangeAt >= REED_DEBOUNCE_MS) + { + reedStable = reedRaw; + OnReedChanged(reedRawChangeAt); + } + + if(nowFlashing && !flashActive) + { + flashActive = true; + OnMovementStart(flashAt); + } + else if(!nowFlashing && flashActive) + { + flashActive = false; + OnMovementEnd(flashAt); + } + + if(moving && currentMillis - lastStatePublish >= MOVE_PUBLISH_INTERVAL) + { + PublishDoorState(); } processHandshake(); From 3f8382197603c2a09accb7bb29fdacfbc0c77e4d Mon Sep 17 00:00:00 2001 From: Zefek Date: Fri, 31 Jul 2026 20:37:17 +0200 Subject: [PATCH 4/6] Testy a opravy --- .gitignore | 1 + src/ESP32/ESP32.ino | 22 +++-- tests/README.md | 46 ++++++++++ tests/garage_open_test.py | 188 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/garage_open_test.py diff --git a/.gitignore b/.gitignore index 3fc0ad3..2a05eac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ secret.h config.h +/tests/__pycache__ diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino index bcc83b0..4f3de87 100644 --- a/src/ESP32/ESP32.ino +++ b/src/ESP32/ESP32.ino @@ -30,6 +30,7 @@ #define TIME_SYNC_TIMEOUT_MS 15000UL #define TIME_VALID_THRESHOLD 1700000000UL #define MQTT_BACKOFF_MAX_MS 60000UL +#define MQTT_KEEPALIVE_S 60 #define T_FULL_MS 16000UL #define T_FULL_MARGIN_MS 1000UL @@ -48,6 +49,7 @@ #define GARAGE_STATUS_BADSIG 3 void MQTTMessageReceive(char* topic, uint8_t* payload, unsigned int length); +void PublishDoorState(bool force = false); enum DoorState { DoorUnknown, DoorClosed, DoorOpening, DoorOpen, DoorClosing, DoorStopped }; @@ -109,6 +111,7 @@ int reedRaw = HIGH; unsigned long reedRawChangeAt = 0; char temperatureData[20]; +char lastPayload[24] = ""; uint16_t sensorId = 0; DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -181,7 +184,7 @@ static unsigned long currentTravel() { return travelMs; } - long delta = (long)(currentMillis - movementOrigin); + long delta = (long)(lastFlashMillis - movementOrigin); if(delta <= 0) { return travelBase; @@ -225,12 +228,17 @@ static const char* motionText() return "Move"; } -void PublishDoorState() +void PublishDoorState(bool force) { char payload[24]; sprintf(payload, "%s;%s;%d", reedStable == HIGH ? "Closed" : "Open", motionText(), positionPercent()); - mqtt.publish(GARAGE_STATE, payload, true); lastStatePublish = currentMillis; + if(!force && strcmp(payload, lastPayload) == 0) + { + return; + } + strcpy(lastPayload, payload); + mqtt.publish(GARAGE_STATE, payload, true); } void OnMovementStart(unsigned long firstFlashAt) @@ -495,7 +503,7 @@ bool Connect() } mqtt.subscribe(GARAGE_OPEN_REQUEST, 1); mqtt.subscribe(GARAGE_OPEN_RESPONSE, 1); - PublishDoorState(); + PublishDoorState(true); mqttConnectionTimeout = 0; return true; } @@ -552,7 +560,7 @@ void setup() { mqtt.setServer(MQTTHost, MQTT_TLS_PORT); mqtt.setCallback(MQTTMessageReceive); mqtt.setBufferSize(256); - mqtt.setKeepAlive(60); + mqtt.setKeepAlive(MQTT_KEEPALIVE_S); if (am2302.begin()) { @@ -646,8 +654,8 @@ void loop() { { currentDiagData.sensorErr |= 0x01; } - int temperature = (int)(am2302.get_Temperature() * 10); - int humidity = (int)am2302.get_Humidity(); + int temperature = (int)lroundf(am2302.get_Temperature() * 10); + int humidity = (int)lroundf(am2302.get_Humidity()); sprintf(temperatureData, "%u;%d;%d;%d", sensorId, temperature, humidity, SENSOR_CHANNEL); mqtt.publish(GARAGE_TEMPERATURE, temperatureData); temperatureHumidityReadMillis = currentMillis; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..d6d470e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,46 @@ +# Testy + +Ruční testy proti živému zařízení. Nejsou součástí CI – potřebují běžící desku, +broker a vyplněný `secret.h`. + +## garage_open_test.py + +Zastupuje OdectyStat v secure-open handshaku a ověřuje, že firmware povel +správně přijme i odmítne. Podpisový klíč, credentials, CA i názvy topiců si čte +z `../src/ESP32/config.h` a `secret.h`, takže se nic nezadává ručně. + +``` +pip install paho-mqtt + +python garage_open_test.py # platný podpis -> OPENED +python garage_open_test.py --badsig # prohozený bit -> BADSIG +python garage_open_test.py --expire # odpověď až po TTL -> EXPIRED +python garage_open_test.py --replay # stejný podpis 2x -> podruhé EXPIRED +``` + +Návratový kód je 0, když status odpovídá očekávání. Jiný sketch se dá zvolit +přes `--sketch-dir`. + +**Posílá skutečný povel k otevření vrat.** Spouštět jen proti sketchi, který má +v `config.h` testovací topicy – proti produkčním názvům by povel dorazil do +garáže. Zkontroluj si to před prvním během. + +## Simulace stavového automatu bez hardwaru + +Polohu vrat lze odladit dvěma propojkami na GND: + +- **`DOORFLASH` na GND** = maják bliká; délka přidržení = délka jízdy +- **`DOORSWITCH` na GND** = vrata nejsou zavřená; odpojení = reed hlásí zavřeno + +Průběh se sleduje na retained topicu `GARAGE_STATE` ve tvaru +`;;`. + +Ověřit se dá zejména: + +- střídání směru při každém rozjezdu (`Opening` -> `Closing` -> `Opening`) +- předblik – rozjezd z `Closed` nezačne počítat, dokud nepustí reed +- detekce reverzace – zavírání delší než ujetá vzdálenost skončí na 100 % + a nastaví bit `0x02` v `sensorErr` + +Naměřené `lastTravelMs` a `lastLeadMs` chodí v `GARAGE_DIAG` a slouží +ke kalibraci `T_FULL_MS` a `T_LEAD_DEFAULT_MS`. diff --git a/tests/garage_open_test.py b/tests/garage_open_test.py new file mode 100644 index 0000000..38e74b5 --- /dev/null +++ b/tests/garage_open_test.py @@ -0,0 +1,188 @@ +"""Testovaci podepisovatel pro secure-open handshake garaze. + +Zastupuje OdectyStat a overuje, ze firmware handshake spravne prijme i odmitne. +Konfiguraci, credentials i podpisovy klic cte primo z config.h / secret.h +prislusneho sketche, nic se nezadava rucne a nic se nevypisuje na obrazovku. + +Pouziti: + python garage_open_test.py # platny podpis -> OPENED + python garage_open_test.py --badsig # prohozeny bit -> BADSIG + python garage_open_test.py --expire # odpoved az po TTL -> EXPIRED + python garage_open_test.py --replay # stejny podpis 2x -> podruhe EXPIRED + +Vyzaduje paho-mqtt (pip install paho-mqtt). + +POZOR: posila povel k otevreni vrat. Spoustet jen proti sketchi, ktery ma +v config.h testovaci topicy, jinak povel dorazi na produkcni garaz. +""" + +import argparse +import hashlib +import hmac +import os +import re +import secrets +import ssl +import struct +import sys +import tempfile +import threading +import time + +import paho.mqtt.client as mqtt + +DEFAULT_SKETCH_DIR = os.path.normpath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "ESP32") +) + +STATUS = {1: "OPENED", 2: "EXPIRED", 3: "BADSIG"} + + +def parse_defines(path): + """Vytahne #define NAME hodnota. + + Zvlada obe formy, ktere se v secret.h vyskytuji: + - viceradkove retezcove konkatenace s backslash pokracovanim + - surove retezce R"DELIM( ... )DELIM" pres vic radku + """ + with open(path, encoding="utf-8") as fh: + text = fh.read() + out = {} + + raw_re = re.compile(r'#define\s+(\w+)\s+R"([^("]*)\((.*?)\)\2"', re.S) + for m in raw_re.finditer(text): + out[m.group(1)] = m.group(3).strip() + "\n" + text = raw_re.sub("", text) + + text = re.sub(r"\\\r?\n", " ", text) + for line in text.splitlines(): + m = re.match(r'\s*#define\s+(\w+)\s+(.*)', line) + if not m: + continue + name, rest = m.group(1), m.group(2).strip() + parts = re.findall(r'"((?:[^"\\]|\\.)*)"', rest) + if parts: + out[name] = "".join(parts).replace("\\n", "\n").replace('\\"', '"') + elif rest: + out[name] = rest + return out + + +def sign(key, correlation_id, nonce): + msg = struct.pack(" request R={state['r']} ({payload.hex(' ')})") + + def on_message(client, userdata, msg): + if msg.topic == t_chal: + if len(msg.payload) != 4 + nonce_len: + print(f"!! challenge ma {len(msg.payload)} B, cekano {4 + nonce_len}") + return + r, = struct.unpack(" response {len(out)} B sig={state['sig'].hex(' ')}") + + elif msg.topic == t_res: + if len(msg.payload) != 5: + print(f"!! result ma {len(msg.payload)} B, cekano 5") + return + r, status = struct.unpack(" Date: Fri, 31 Jul 2026 21:06:55 +0200 Subject: [PATCH 5/6] =?UTF-8?q?n=C4=9Bjak=C3=A9=20opravy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ESP32/ESP32.ino | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino index 4f3de87..565291a 100644 --- a/src/ESP32/ESP32.ino +++ b/src/ESP32/ESP32.ino @@ -6,6 +6,7 @@ #include "time.h" #include "esp_task_wdt.h" #include "esp_random.h" +#include "esp_timer.h" #include "config.h" #include "secret.h" #include "crypto.h" @@ -31,6 +32,9 @@ #define TIME_VALID_THRESHOLD 1700000000UL #define MQTT_BACKOFF_MAX_MS 60000UL #define MQTT_KEEPALIVE_S 60 +#define STATE_PAYLOAD_LEN 32 +#define DOORBUTTON_ACTIVE LOW +#define DOORBUTTON_IDLE (DOORBUTTON_ACTIVE == LOW ? HIGH : LOW) #define T_FULL_MS 16000UL #define T_FULL_MARGIN_MS 1000UL @@ -111,7 +115,7 @@ int reedRaw = HIGH; unsigned long reedRawChangeAt = 0; char temperatureData[20]; -char lastPayload[24] = ""; +char lastPayload[STATE_PAYLOAD_LEN] = ""; uint16_t sensorId = 0; DiagData currentDiagData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -154,9 +158,16 @@ static bool isHexChar(char c) { static bool parseSigningKey() { const char* h = SigningKeyHex; - if (strnlen(h, GARAGE_KEY_LEN * 2 + 2) != (size_t)(GARAGE_KEY_LEN * 2)) return false; + size_t len = strnlen(h, GARAGE_KEY_LEN * 2 + 2); + if (len != (size_t)(GARAGE_KEY_LEN * 2)) { + Serial.printf("SigningKey: delka %u znaku, ocekavano %u\n", (unsigned)len, (unsigned)(GARAGE_KEY_LEN * 2)); + return false; + } for (uint8_t i = 0; i < GARAGE_KEY_LEN * 2; i++) { - if (!isHexChar(h[i])) return false; + if (!isHexChar(h[i])) { + Serial.printf("SigningKey: nehexadecimalni znak na pozici %u\n", (unsigned)i); + return false; + } } for (uint8_t i = 0; i < GARAGE_KEY_LEN; i++) { signingKey[i] = (hexNibble(h[i * 2]) << 4) | hexNibble(h[i * 2 + 1]); @@ -230,14 +241,14 @@ static const char* motionText() void PublishDoorState(bool force) { - char payload[24]; - sprintf(payload, "%s;%s;%d", reedStable == HIGH ? "Closed" : "Open", motionText(), positionPercent()); + char payload[STATE_PAYLOAD_LEN]; + snprintf(payload, sizeof payload, "%s;%s;%d", reedStable == HIGH ? "Closed" : "Open", motionText(), positionPercent()); lastStatePublish = currentMillis; if(!force && strcmp(payload, lastPayload) == 0) { return; } - strcpy(lastPayload, payload); + strlcpy(lastPayload, payload, sizeof lastPayload); mqtt.publish(GARAGE_STATE, payload, true); } @@ -510,7 +521,7 @@ bool Connect() void sendDiag() { - currentDiagData.uptime = currentMillis / 60000UL; + currentDiagData.uptime = (uint32_t)(esp_timer_get_time() / 60000000LL); currentDiagData.freeRamKb = (uint16_t)(ESP.getFreeHeap() / 1024); currentDiagData.rssi = (int8_t)WiFi.RSSI(); currentDiagData.fwVersion = (uint16_t)FW_VERSION; @@ -536,9 +547,9 @@ bool OtaAllowed() void setup() { currentDiagData.resetReason = (uint8_t)esp_reset_reason(); - digitalWrite(DOORBUTTON_PIN, HIGH); + gpio_set_level((gpio_num_t)DOORBUTTON_PIN, DOORBUTTON_IDLE); pinMode(DOORBUTTON_PIN, OUTPUT); - digitalWrite(DOORBUTTON_PIN, HIGH); + digitalWrite(DOORBUTTON_PIN, DOORBUTTON_IDLE); pinMode(DOORSWITCH_PIN, INPUT_PULLUP); pinMode(DOORFLASH_PIN, INPUT_PULLUP); @@ -636,14 +647,14 @@ void loop() { if(doorSignal && !doorPulseActive) { - digitalWrite(DOORBUTTON_PIN, LOW); + digitalWrite(DOORBUTTON_PIN, DOORBUTTON_ACTIVE); doorPulseStart = currentMillis; doorPulseActive = true; doorSignal = false; } if(doorPulseActive && currentMillis - doorPulseStart >= DOOR_PULSE_MS) { - digitalWrite(DOORBUTTON_PIN, HIGH); + digitalWrite(DOORBUTTON_PIN, DOORBUTTON_IDLE); doorPulseActive = false; } From 1e83b2fbc20d755b1249ebc1a50ab8b538091b2d Mon Sep 17 00:00:00 2001 From: Zefek Date: Fri, 31 Jul 2026 21:27:45 +0200 Subject: [PATCH 6/6] opravy a cppcheck --- .github/workflows/build.yml | 36 ++++++++++++++++++++++++++++++++++++ src/ESP32/ESP32.ino | 5 +++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b681ff2..c03f91b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,3 +38,39 @@ jobs: run: | arduino-cli compile ./src/Arduino arduino-cli compile ./src/ESP32 + + - name: Install cppcheck + run: | + sudo apt-get update + sudo apt-get install -y cppcheck + + - name: Static analysis (ESP32) + run: | + cppcheck --version + cppcheck \ + --enable=warning,style,performance,portability \ + --language=c++ \ + --std=c++20 \ + --platform=unix32 \ + --inline-suppr \ + --suppress=missingInclude \ + --suppress=missingIncludeSystem \ + --suppress=constParameterCallback:./src/ESP32/ESP32.ino \ + --error-exitcode=1 \ + -I ./src/ESP32 \ + ./src/ESP32/ESP32.ino ./src/ESP32/crypto.cpp ./src/ESP32/ota.cpp + + - name: Static analysis (Arduino) + run: | + cppcheck \ + --enable=warning,performance,portability \ + --language=c++ \ + --std=c++11 \ + --platform=avr8 \ + --inline-suppr \ + -D PROGMEM= \ + --suppress=missingInclude \ + --suppress=missingIncludeSystem \ + --error-exitcode=1 \ + -I ./src/Arduino \ + ./src/Arduino/Arduino.ino ./src/Arduino/sha256.cpp diff --git a/src/ESP32/ESP32.ino b/src/ESP32/ESP32.ino index 565291a..69efb81 100644 --- a/src/ESP32/ESP32.ino +++ b/src/ESP32/ESP32.ino @@ -177,7 +177,8 @@ static bool parseSigningKey() { static bool cryptoSelfTest() { uint8_t mac[32]; - hmac_sha256((const uint8_t*)"Jefe", 4, (const uint8_t*)"what do ya want for nothing?", 28, mac); + hmac_sha256(reinterpret_cast("Jefe"), 4, + reinterpret_cast("what do ya want for nothing?"), 28, mac); const uint8_t expected[8] = { 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e }; for (uint8_t i = 0; i < 8; i++) { if (mac[i] != expected[i]) return false; @@ -528,7 +529,7 @@ void sendDiag() currentDiagData.otaFailCount = otaFailures; currentDiagData.lastTravelMs = measuredTravelMs; currentDiagData.lastLeadMs = measuredLeadMs; - mqtt.publish(GARAGE_DIAG, (const uint8_t*)¤tDiagData, sizeof(DiagData), false); + mqtt.publish(GARAGE_DIAG, reinterpret_cast(¤tDiagData), sizeof(DiagData), false); currentDiagData.loopMaxMs = 0; currentDiagData.sensorErr = 0; }