ZCD Rollback to Arduino functions

This commit is contained in:
Heuideog Yi @ PC RnD1 2026-04-18 21:31:48 +09:00
parent ef05f6f997
commit 394ba748f5
8 changed files with 73 additions and 1296 deletions

View File

@ -10,7 +10,7 @@ void WiFiEvent(WiFiEvent_t event) {
case IP_EVENT_STA_GOT_IP:
DPRINTF("WiFi connected, IP: %s\n", WiFi.localIP().toString().c_str());
g_bWiFiHasBeenConnected = true;
ledcWrite(PIN_LED_WIFI, PWM_FULL * 19 / 20); // LED_OFF
ledcWrite(PIN_LED_WIFI, PWM_FULL * 39 / 40); // LED_OFF
if (!g_bWiFiSetupExecuted) setupPostWiFi(false);
break;
case WIFI_EVENT_STA_DISCONNECTED:
@ -74,7 +74,7 @@ void checkWiFi(unsigned long tickMillis) {
if (status == WL_DISCONNECTED && g_bWiFiHasBeenConnected) {
DPRINTLN("Attempting WiFi reconnection...");
WiFi.reconnect();
ledcWrite(PIN_LED_WIFI, PWM_FULL * 19 / 20); // Light Blink
ledcWrite(PIN_LED_WIFI, PWM_FULL * 39 / 40); // Light Blink
lastAttempt = tickMillis;
bConnecting = true;
}
@ -93,7 +93,7 @@ void checkWiFi(unsigned long tickMillis) {
DPRINTF(" WiFi.Begin(%s,%s) returned %d\n", config.ssid, config.pw, ret);
lastAttempt = tickMillis;
ledcWrite(PIN_LED_WIFI, PWM_FULL / 10); // Light Blink
ledcWrite(PIN_LED_WIFI, PWM_FULL / 20); // Light Blink
lastAttempt = tickMillis;
bConnecting = true;
}

View File

@ -1,291 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(ESP32)
#ifndef ESP32UPDATER_H
#define ESP32UPDATER_H
#include <Arduino.h>
#include <MD5Builder.h>
#include <functional>
#include <WiFiClientSecure.h>
#include "esp_partition.h"
#define UPDATE_ERROR_OK (0)
#define UPDATE_ERROR_WRITE (1)
#define UPDATE_ERROR_ERASE (2)
#define UPDATE_ERROR_READ (3)
#define UPDATE_ERROR_SPACE (4)
#define UPDATE_ERROR_SIZE (5)
#define UPDATE_ERROR_STREAM (6)
#define UPDATE_ERROR_MD5 (7)
#define UPDATE_ERROR_MAGIC_BYTE (8)
#define UPDATE_ERROR_ACTIVATE (9)
#define UPDATE_ERROR_NO_PARTITION (10)
#define UPDATE_ERROR_BAD_ARGUMENT (11)
#define UPDATE_ERROR_ABORT (12)
#define UPDATE_ERROR_DECRYPT (13)
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
#define U_FLASH 0
#define U_SPIFFS 100
#define U_AUTH 200
#define ENCRYPTED_BLOCK_SIZE 16
#define ENCRYPTED_TWEAK_BLOCK_SIZE 32
#define ENCRYPTED_KEY_SIZE 32
#define U_AES_DECRYPT_NONE 0
#define U_AES_DECRYPT_AUTO 1
#define U_AES_DECRYPT_ON 2
#define U_AES_DECRYPT_MODE_MASK 3
#define U_AES_IMAGE_DECRYPTING_BIT 4
#define SPI_SECTORS_PER_BLOCK 16 // usually large erase block is 32k/64k
#define SPI_FLASH_BLOCK_SIZE (SPI_SECTORS_PER_BLOCK * SPI_FLASH_SEC_SIZE)
enum HTTPUpdateResult {
HTTP_UPDATE_FAILED,
HTTP_UPDATE_NO_UPDATES,
HTTP_UPDATE_OK
};
class UpdateClass {
public:
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
UpdateClass();
UpdateClass(int httpClientTimeout);
int update(WiFiClientSecure& client, String &url, uint16_t port, String& uri,
String &currentVersion, short nDeviceType, bool bForceUpdate);
int handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType);
int http_downloadUpdate(HTTPClient &httpClient);
/*
This callback will be called when Update is receiving data
*/
UpdateClass &onProgress(THandlerFunction_Progress fn);
/*
Call this to check the space needed for the update
Will return false if there is not enough space
*/
bool begin(size_t size = UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL);
/*
Setup decryption configuration
Crypt Key is 32bytes(256bits) block of data, use the same key as used to encrypt image file
Crypt Address, use the same value as used to encrypt image file
Crypt Config, use the same value as used to encrypt image file
Crypt Mode, used to select if image files should be decrypted or not
*/
bool setupCrypt(const uint8_t *cryptKey = 0, size_t cryptAddress = 0, uint8_t cryptConfig = 0xf, int cryptMode = U_AES_DECRYPT_AUTO);
/*
Writes a buffer to the flash and increments the address
Returns the amount written
*/
size_t write(uint8_t *data, size_t len);
/*
Writes the remaining bytes from the Stream to the flash
Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout
Returns the bytes written
Should be equal to the remaining bytes when called
Usable for slow streams like Serial
*/
size_t writeStream(Stream &data);
/*
If all bytes are written
this call will write the config to eboot
and return true
If there is already an update running but is not finished and !evenIfRemaining
or there is an error
this will clear everything and return false
the last error is available through getError()
evenIfRemaining is helpful when you update without knowing the final size first
*/
bool end(bool evenIfRemaining = false);
/*
sets AES256 key(32 bytes) used for decrypting image file
*/
bool setCryptKey(const uint8_t *cryptKey);
/*
sets crypt mode used on image files
*/
bool setCryptMode(const int cryptMode);
/*
sets address used for decrypting image file
*/
void setCryptAddress(const size_t cryptAddress) {
_cryptAddress = cryptAddress & 0x00fffff0;
}
/*
sets crypt config used for decrypting image file
*/
void setCryptConfig(const uint8_t cryptConfig) {
_cryptCfg = cryptConfig & 0x0f;
}
/*
Aborts the running update
*/
void abort();
/*
Prints the last error to an output stream
*/
void printError(Print &out);
const char *errorString();
/*
sets the expected MD5 for the firmware (hexString)
*/
bool setMD5(const char *expected_md5);
/*
returns the MD5 String of the successfully ended firmware
*/
String md5String(void) {
return _md5.toString();
}
/*
populated the result with the md5 bytes of the successfully ended firmware
*/
void md5(uint8_t *result) {
return _md5.getBytes(result);
}
//Helpers
uint8_t getError() {
return _error;
}
void clearError() {
_error = UPDATE_ERROR_OK;
}
bool hasError() {
return _error != UPDATE_ERROR_OK;
}
bool isRunning() {
return _size > 0;
}
bool isFinished() {
return _progress == _size;
}
size_t size() {
return _size;
}
size_t progress() {
return _progress;
}
size_t remaining() {
return _size - _progress;
}
/*
Template to write from objects that expose
available() and read(uint8_t*, size_t) methods
faster than the writeStream method
writes only what is available
*/
template<typename T> size_t write(T &data) {
size_t written = 0;
if (hasError() || !isRunning()) {
return 0;
}
size_t available = data.available();
while (available) {
if (_bufferLen + available > remaining()) {
available = remaining() - _bufferLen;
}
if (_bufferLen + available > 4096) {
size_t toBuff = 4096 - _bufferLen;
data.read(_buffer + _bufferLen, toBuff);
_bufferLen += toBuff;
if (!_writeBuffer()) {
return written;
}
written += toBuff;
} else {
data.read(_buffer + _bufferLen, available);
_bufferLen += available;
written += available;
if (_bufferLen == remaining()) {
if (!_writeBuffer()) {
return written;
}
}
}
if (remaining() == 0) {
return written;
}
available = data.available();
}
return written;
}
/*
check if there is a firmware on the other OTA partition that you can bootinto
*/
bool canRollBack();
/*
set the other OTA partition as bootable (reboot to enable)
*/
bool rollBack();
private:
void _reset();
void _abort(uint8_t err);
void _cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key);
bool _decryptBuffer();
bool _writeBuffer();
bool _verifyHeader(uint8_t data);
bool _verifyEnd();
bool _enablePartition(const esp_partition_t *partition);
bool _chkDataInBlock(const uint8_t *data, size_t len) const; // check if block contains any data or is empty
int _httpClientTimeout;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint8_t _error;
uint8_t *_cryptKey;
uint8_t *_cryptBuffer;
uint8_t *_buffer;
uint8_t *_skipBuffer;
size_t _bufferLen;
size_t _size;
THandlerFunction_Progress _progress_callback;
uint32_t _progress;
uint32_t _paroffset;
uint32_t _command;
const esp_partition_t *_partition;
String _target_md5;
MD5Builder _md5;
int _ledPin;
uint8_t _ledOn;
uint8_t _cryptMode;
size_t _cryptAddress;
uint8_t _cryptCfg;
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
extern UpdateClass Update;
#endif
#endif // defined(ESP32UPDATER_H)
#endif // defined(ESP32)

View File

@ -1,787 +0,0 @@
#if defined(ESP32)
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "HermitCrab.h"
#include "Arduino.h"
#include "spi_flash_mmap.h"
#include "esp_ota_ops.h"
#include "esp_image_format.h"
#include "mbedtls/aes.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include "HCUpdate.h"
#define TAG_FW "FW Upate"
static const char *_err2str(uint8_t _error) {
if (_error == UPDATE_ERROR_OK) {
return ("No Error");
} else if (_error == UPDATE_ERROR_WRITE) {
return ("Flash Write Failed");
} else if (_error == UPDATE_ERROR_ERASE) {
return ("Flash Erase Failed");
} else if (_error == UPDATE_ERROR_READ) {
return ("Flash Read Failed");
} else if (_error == UPDATE_ERROR_SPACE) {
return ("Not Enough Space");
} else if (_error == UPDATE_ERROR_SIZE) {
return ("Bad Size Given");
} else if (_error == UPDATE_ERROR_STREAM) {
return ("Stream Read Timeout");
} else if (_error == UPDATE_ERROR_MD5) {
return ("MD5 Check Failed");
} else if (_error == UPDATE_ERROR_MAGIC_BYTE) {
return ("Wrong Magic Byte");
} else if (_error == UPDATE_ERROR_ACTIVATE) {
return ("Could Not Activate The Firmware");
} else if (_error == UPDATE_ERROR_NO_PARTITION) {
return ("Partition Could Not be Found");
} else if (_error == UPDATE_ERROR_BAD_ARGUMENT) {
return ("Bad Argument");
} else if (_error == UPDATE_ERROR_ABORT) {
return ("Aborted");
} else if (_error == UPDATE_ERROR_DECRYPT) {
return ("Decryption error");
}
return ("UNKNOWN");
}
int UpdateClass::update(WiFiClientSecure& client, String& host, uint16_t port, String&uri, String& currentVersion, short nDeviceType, bool bForceUpdate)
{
HTTPClient http;
if (!http.begin(client, host, port, uri)) {
ESP_LOGI(TAG_FW,"OTA - httpClient begin failed\n");
return HTTP_UPDATE_FAILED;
}
int size = handleUpdate(http, currentVersion, nDeviceType);
ESP_LOGI(TAG_FW,"OTA - size(%d) Server Version: %s Mode: %s\n", size, http.header("version").c_str(),
http.header("update") && http.header("update").toInt() == 1 ? "Download" : "Check Only" );
//is there an image to download
if (size >= 0) {
if (!http.header("update") || http.header("update").toInt() == 0) {
ESP_LOGI(TAG_FW,"OTA - No Firmware available");
} else if (!http.header("version") || strcmp(http.header("version").c_str(), HC__VERSION) <= 0) {
ESP_LOGI(TAG_FW,"OTA - Firmware is upto Date");
} else {
//image avaliabe to download & update
if (!bForceUpdate) {
ESP_LOGI(TAG_FW,"OTA - Found V%s Firmware\n", http.header("version").c_str());
} else {
ESP_LOGI(TAG_FW,"OTA - Downloading & Installing V%s Firmware\n", http.header("version").c_str());
}
if (bForceUpdate) {
if (http_downloadUpdate(http) == 0) {
http.end(); //end connection
ESP_LOGI(TAG_FW,"OTA - Firmware Update Successful, rebooting");
ESP.restart();
}
}
}
}
http.end(); //end connection
return 0;
}
int UpdateClass::handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType)
{
HTTPUpdateResult ret = HTTP_UPDATE_FAILED;
// use HTTP/1.0 for update since the update handler not support any transfer Encoding
http.useHTTP10(true);
http.setTimeout(_httpClientTimeout);
http.setFollowRedirects(_followRedirects);
http.setUserAgent("ESP32-http-Update");
http.addHeader("Cache-Control", "no-cache");
http.addHeader("X-ESP32-DEVICE-TYPE", String(nDeviceType));
http.addHeader("X-ESP32-VERSION", HC__VERSION);
http.addHeader("X-ESP32-STA-MAC", WiFi.macAddress());
unsigned long nChipId = 0;
for (int i = 0; i < 17; i = i + 8) {
nChipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
http.addHeader(F("X-ESP32-Chip-ID"), String(nChipId));
http.addHeader(F("x-ESP32-free-space"), String(ESP.getFreeSketchSpace()));
http.addHeader(F("x-ESP32-sketch-size"), String(ESP.getSketchSize()));
http.addHeader(F("x-ESP32-sketch-md5"), String(ESP.getSketchMD5()));
//http.addHeader(F("x-ESP32-chip-size"), String(ESP.getFlashChipRealSize()));
http.addHeader(F("x-ESP32-sdk-version"), ESP.getSdkVersion());
//set headers to look for to get returned values in servers http response to our http request
const char *headerkeys[] = {"update", "version"}; //server returns update 0=no update found, 1=update found, version=version of update found
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);
http.collectHeaders(headerkeys, headerkeyssize);
int code = http.GET();
int len = http.getSize();
if (code == HTTP_CODE_OK) {
return (len > 0 ? len : 0); //return 0 or length of image to download
} else if (code < 0) {
ESP_LOGI(TAG_FW,"Error: %s\n", http.errorToString(code).c_str());
ESP_LOGI(TAG_FW, "%s", http.getString());
return code; //error code should be minus between -1 to -11
} else {
ESP_LOGI(TAG_FW,"Error: HTTP Server response code %i\n", code);
ESP_LOGI(TAG_FW, "%s", http.getString());
return -code; //return code should be minus between -100 to -511
}
return ret;
}
int UpdateClass::http_downloadUpdate(HTTPClient &httpClient) {
WiFiClient *stream = httpClient.getStreamPtr();
int written = 0;
// Check content length and begin update
int fileSize = httpClient.getSize();
if (fileSize <= 0) {
ESP_LOGI(TAG_FW,"Invalid content length");
return 1;
}
if (!Update.begin(fileSize, U_FLASH)) {
ESP_LOGI(TAG_FW,"Update begin failed!");
return 1;
}
// Download and write the update
while (httpClient.connected() && written < fileSize) {
size_t availableBytes = stream->available();
if (availableBytes) {
uint8_t buffer[128]; // Buffer to hold incoming data
int bytesRead = stream->readBytes(buffer, min(availableBytes, sizeof(buffer)));
int bytesWritten = Update.write(buffer, bytesRead);
if (bytesWritten != bytesRead) {
ESP_LOGI(TAG_FW,"Update write failed!");
Update.end();
return 1;
}
written += bytesWritten;
}
}
// Finalize update
if (Update.end()) {
if (Update.isFinished()) {
ESP_LOGI(TAG_FW,"Update successful!");
return 0;
} else {
ESP_LOGI(TAG_FW,"Update not finished!");
return 1;
}
} else {
ESP_LOGI(TAG_FW,"Update end failed!");
return 1;
}
}
static bool _partitionIsBootable(const esp_partition_t *partition) {
uint8_t buf[ENCRYPTED_BLOCK_SIZE];
if (!partition) {
return false;
}
if (!ESP.partitionRead(partition, 0, (uint32_t *)buf, ENCRYPTED_BLOCK_SIZE)) {
return false;
}
if (buf[0] != ESP_IMAGE_HEADER_MAGIC) {
return false;
}
return true;
}
bool UpdateClass::_enablePartition(const esp_partition_t *partition) {
if (!partition) {
return false;
}
return ESP.partitionWrite(partition, 0, (uint32_t *)_skipBuffer, ENCRYPTED_BLOCK_SIZE);
}
UpdateClass::UpdateClass()
: _error(0)
, _cryptKey(0)
, _cryptBuffer(0)
, _buffer(0)
, _skipBuffer(0)
, _bufferLen(0)
, _size(0)
, _progress_callback(NULL)
, _progress(0)
, _paroffset(0)
, _command(U_FLASH)
, _partition(NULL)
, _cryptMode(U_AES_DECRYPT_AUTO)
, _cryptAddress(0)
, _cryptCfg(0xf)
, _httpClientTimeout(8000)
{}
UpdateClass::UpdateClass(int httpClientTimeout)
: _error(0)
, _cryptKey(0)
, _cryptBuffer(0)
, _buffer(0)
, _skipBuffer(0)
, _bufferLen(0)
, _size(0)
, _progress_callback(NULL)
, _progress(0)
, _paroffset(0)
, _command(U_FLASH)
, _partition(NULL)
, _cryptMode(U_AES_DECRYPT_AUTO)
, _cryptAddress(0)
, _cryptCfg(0xf)
, _httpClientTimeout(httpClientTimeout)
{}
UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress fn) {
_progress_callback = fn;
return *this;
}
void UpdateClass::_reset() {
if (_buffer) {
delete[] _buffer;
}
if (_skipBuffer) {
delete[] _skipBuffer;
}
_cryptBuffer = nullptr;
_buffer = nullptr;
_skipBuffer = nullptr;
_bufferLen = 0;
_progress = 0;
_size = 0;
_command = U_FLASH;
if (_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // off
}
}
bool UpdateClass::canRollBack() {
if (_buffer) { //Update is running
return false;
}
const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition);
}
bool UpdateClass::rollBack() {
if (_buffer) { //Update is running
return false;
}
const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition);
}
bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) {
if (_size > 0) {
log_w("already running");
return false;
}
_ledPin = ledPin;
_ledOn = !!ledOn; // 0(LOW) or 1(HIGH)
_reset();
_error = 0;
_target_md5 = emptyString;
_md5 = MD5Builder();
if (size == 0) {
_error = UPDATE_ERROR_SIZE;
return false;
}
if (command == U_FLASH) {
_partition = esp_ota_get_next_update_partition(NULL);
if (!_partition) {
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
log_d("OTA Partition: %s", _partition->label);
} else if (command == U_SPIFFS) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, label);
_paroffset = 0;
if (!_partition) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
_paroffset = 0x1000; //Offset for ffat, assuming size is already corrected
if (!_partition) {
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
}
} else {
_error = UPDATE_ERROR_BAD_ARGUMENT;
log_e("bad command %u", command);
return false;
}
if (size == UPDATE_SIZE_UNKNOWN) {
size = _partition->size;
} else if (size > _partition->size) {
_error = UPDATE_ERROR_SIZE;
log_e("too large %u > %u", size, _partition->size);
return false;
}
//initialize
_buffer = new (std::nothrow) uint8_t[SPI_FLASH_SEC_SIZE];
if (!_buffer) {
log_e("_buffer allocation failed");
return false;
}
_size = size;
_command = command;
_md5.begin();
return true;
}
bool UpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8_t cryptConfig, int cryptMode) {
if (setCryptKey(cryptKey)) {
if (setCryptMode(cryptMode)) {
setCryptAddress(cryptAddress);
setCryptConfig(cryptConfig);
return true;
}
}
return false;
}
bool UpdateClass::setCryptKey(const uint8_t *cryptKey) {
if (!cryptKey) {
if (_cryptKey) {
delete[] _cryptKey;
_cryptKey = 0;
log_d("AES key unset");
}
return false; //key cleared, no key to decrypt with
}
//initialize
if (!_cryptKey) {
_cryptKey = new (std::nothrow) uint8_t[ENCRYPTED_KEY_SIZE];
}
if (!_cryptKey) {
log_e("new failed");
return false;
}
memcpy(_cryptKey, cryptKey, ENCRYPTED_KEY_SIZE);
return true;
}
bool UpdateClass::setCryptMode(const int cryptMode) {
if (cryptMode >= U_AES_DECRYPT_NONE && cryptMode <= U_AES_DECRYPT_ON) {
_cryptMode = cryptMode;
} else {
log_e("bad crypt mode argument %i", cryptMode);
return false;
}
return true;
}
void UpdateClass::_abort(uint8_t err) {
_reset();
_error = err;
}
void UpdateClass::abort() {
_abort(UPDATE_ERROR_ABORT);
}
void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) {
memcpy(tweaked_key, _cryptKey, ENCRYPTED_KEY_SIZE);
if (_cryptCfg == 0) {
return; //no tweaking needed, use crypt key as-is
}
const uint8_t pattern[] = {23, 23, 23, 14, 23, 23, 23, 12, 23, 23, 23, 10, 23, 23, 23, 8};
int pattern_idx = 0;
int key_idx = 0;
int bit_len = 0;
uint32_t tweak = 0;
cryptAddress &= 0x00ffffe0; //bit 23-5
cryptAddress <<= 8; //bit23 shifted to bit31(MSB)
while (pattern_idx < sizeof(pattern)) {
tweak = cryptAddress << (23 - pattern[pattern_idx]); //bit shift for small patterns
// alternative to: tweak = rotl32(tweak,8 - bit_len);
tweak = (tweak << (8 - bit_len)) | (tweak >> (24 + bit_len)); //rotate to line up with end of previous tweak bits
bit_len += pattern[pattern_idx++] - 4; //add number of bits in next pattern(23-4 = 19bits = 23bit to 5bit)
while (bit_len > 7) {
tweaked_key[key_idx++] ^= tweak; //XOR byte
// alternative to: tweak = rotl32(tweak, 8);
tweak = (tweak << 8) | (tweak >> 24); //compiler should optimize to use rotate(fast)
bit_len -= 8;
}
tweaked_key[key_idx] ^= tweak; //XOR remaining bits, will XOR zeros if no remaining bits
}
if (_cryptCfg == 0xf) {
return; //return with fully tweaked key
}
//some of tweaked key bits need to be restore back to crypt key bits
const uint8_t cfg_bits[] = {67, 65, 63, 61};
key_idx = 0;
pattern_idx = 0;
while (key_idx < ENCRYPTED_KEY_SIZE) {
bit_len += cfg_bits[pattern_idx];
if ((_cryptCfg & (1 << pattern_idx)) == 0) { //restore crypt key bits
while (bit_len > 0) {
if (bit_len > 7 || ((_cryptCfg & (2 << pattern_idx)) == 0)) { //restore a crypt key byte
tweaked_key[key_idx] = _cryptKey[key_idx];
} else { //MSBits restore crypt key bits, LSBits keep as tweaked bits
tweaked_key[key_idx] &= (0xff >> bit_len);
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (~(0xff >> bit_len)));
}
key_idx++;
bit_len -= 8;
}
} else { //keep tweaked key bits
while (bit_len > 0) {
if (bit_len < 8 && ((_cryptCfg & (2 << pattern_idx)) == 0)) { //MSBits keep as tweaked bits, LSBits restore crypt key bits
tweaked_key[key_idx] &= (~(0xff >> bit_len));
tweaked_key[key_idx] |= (_cryptKey[key_idx] & (0xff >> bit_len));
}
key_idx++;
bit_len -= 8;
}
}
pattern_idx++;
}
}
bool UpdateClass::_decryptBuffer() {
if (!_cryptKey) {
log_w("AES key not set");
return false;
}
if (_bufferLen % ENCRYPTED_BLOCK_SIZE != 0) {
log_e("buffer size error");
return false;
}
if (!_cryptBuffer) {
_cryptBuffer = new (std::nothrow) uint8_t[ENCRYPTED_BLOCK_SIZE];
}
if (!_cryptBuffer) {
log_e("new failed");
return false;
}
uint8_t tweaked_key[ENCRYPTED_KEY_SIZE]; //tweaked crypt key
int done = 0;
/*
Mbedtls functions will be replaced with esp_aes functions when hardware acceleration is available
To Do:
Replace mbedtls for the cases where there's no hardware acceleration
*/
mbedtls_aes_context ctx; //initialize AES
mbedtls_aes_init(&ctx);
while ((_bufferLen - done) >= ENCRYPTED_BLOCK_SIZE) {
for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) {
_cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i] = _buffer[i + done]; //reverse order 16 bytes to decrypt
}
if (((_cryptAddress + _progress + done) % ENCRYPTED_TWEAK_BLOCK_SIZE) == 0 || done == 0) {
_cryptKeyTweak(_cryptAddress + _progress + done, tweaked_key); //update tweaked crypt key
if (mbedtls_aes_setkey_enc(&ctx, tweaked_key, 256)) {
return false;
}
if (mbedtls_aes_setkey_dec(&ctx, tweaked_key, 256)) {
return false;
}
}
if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, _cryptBuffer, _cryptBuffer)) { //use MBEDTLS_AES_ENCRYPT to decrypt flash code
return false;
}
for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) {
_buffer[i + done] = _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i]; //reverse order 16 bytes from decrypt
}
done += ENCRYPTED_BLOCK_SIZE;
}
return true;
}
bool UpdateClass::_writeBuffer() {
//first bytes of loading image, check to see if loading image needs decrypting
if (!_progress) {
_cryptMode &= U_AES_DECRYPT_MODE_MASK;
if ((_cryptMode == U_AES_DECRYPT_ON) || ((_command == U_FLASH) && (_cryptMode & U_AES_DECRYPT_AUTO) && (_buffer[0] != ESP_IMAGE_HEADER_MAGIC))) {
_cryptMode |= U_AES_IMAGE_DECRYPTING_BIT; //set to decrypt the loading image
log_d("Decrypting OTA Image");
}
}
//check if data in buffer needs decrypting
if (_cryptMode & U_AES_IMAGE_DECRYPTING_BIT) {
if (!_decryptBuffer()) {
_abort(UPDATE_ERROR_DECRYPT);
return false;
}
}
//first bytes of new firmware
uint8_t skip = 0;
if (!_progress && _command == U_FLASH) {
//check magic
if (_buffer[0] != ESP_IMAGE_HEADER_MAGIC) {
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
//Stash the first 16 bytes of data and set the offset so they are
//not written at this point so that partially written firmware
//will not be bootable
skip = ENCRYPTED_BLOCK_SIZE;
_skipBuffer = new (std::nothrow) uint8_t[skip];
if (!_skipBuffer) {
log_e("_skipBuffer allocation failed");
return false;
}
memcpy(_skipBuffer, _buffer, skip);
}
if (!_progress && _progress_callback) {
_progress_callback(0, _size);
}
size_t offset = _partition->address + _progress;
bool block_erase =
(_size - _progress >= SPI_FLASH_BLOCK_SIZE) && (offset % SPI_FLASH_BLOCK_SIZE == 0); // if it's the block boundary, than erase the whole block from here
bool part_head_sectors =
_partition->address % SPI_FLASH_BLOCK_SIZE
&& offset < (_partition->address / SPI_FLASH_BLOCK_SIZE + 1) * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition heading block
bool part_tail_sectors =
offset >= (_partition->address + _size) / SPI_FLASH_BLOCK_SIZE * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition tailing block
if (block_erase || part_head_sectors || part_tail_sectors) {
if (!ESP.partitionEraseRange(_partition, _progress, block_erase ? SPI_FLASH_BLOCK_SIZE : SPI_FLASH_SEC_SIZE)) {
_abort(UPDATE_ERROR_ERASE);
return false;
}
}
// try to skip empty blocks on unencrypted partitions
if ((_partition->encrypted || _chkDataInBlock(_buffer + skip / sizeof(uint32_t), _bufferLen - skip))
&& !ESP.partitionWrite(_partition, _progress + skip, (uint32_t *)_buffer + skip / sizeof(uint32_t), _bufferLen - skip)) {
_abort(UPDATE_ERROR_WRITE);
return false;
}
//restore magic or md5 will fail
if (!_progress && _command == U_FLASH) {
_buffer[0] = ESP_IMAGE_HEADER_MAGIC;
}
_md5.add(_buffer, _bufferLen);
_progress += _bufferLen;
_bufferLen = 0;
if (_progress_callback) {
_progress_callback(_progress, _size);
}
return true;
}
bool UpdateClass::_verifyHeader(uint8_t data) {
if (_command == U_FLASH) {
if (data != ESP_IMAGE_HEADER_MAGIC) {
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
return true;
} else if (_command == U_SPIFFS) {
return true;
}
return false;
}
bool UpdateClass::_verifyEnd() {
if (_command == U_FLASH) {
if (!_enablePartition(_partition) || !_partitionIsBootable(_partition)) {
_abort(UPDATE_ERROR_READ);
return false;
}
if (esp_ota_set_boot_partition(_partition)) {
_abort(UPDATE_ERROR_ACTIVATE);
return false;
}
_reset();
return true;
} else if (_command == U_SPIFFS) {
_reset();
return true;
}
return false;
}
bool UpdateClass::setMD5(const char *expected_md5) {
if (strlen(expected_md5) != 32) {
return false;
}
_target_md5 = expected_md5;
_target_md5.toLowerCase();
return true;
}
bool UpdateClass::end(bool evenIfRemaining) {
if (hasError() || _size == 0) {
return false;
}
if (!isFinished() && !evenIfRemaining) {
log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size);
_abort(UPDATE_ERROR_ABORT);
return false;
}
if (evenIfRemaining) {
if (_bufferLen > 0) {
_writeBuffer();
}
_size = progress();
}
_md5.calculate();
if (_target_md5.length()) {
if (_target_md5 != _md5.toString()) {
_abort(UPDATE_ERROR_MD5);
return false;
}
}
return _verifyEnd();
}
size_t UpdateClass::write(uint8_t *data, size_t len) {
if (hasError() || !isRunning()) {
return 0;
}
if (len > remaining()) {
_abort(UPDATE_ERROR_SPACE);
return 0;
}
size_t left = len;
while ((_bufferLen + left) > SPI_FLASH_SEC_SIZE) {
size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen;
memcpy(_buffer + _bufferLen, data + (len - left), toBuff);
_bufferLen += toBuff;
if (!_writeBuffer()) {
return len - left;
}
left -= toBuff;
}
memcpy(_buffer + _bufferLen, data + (len - left), left);
_bufferLen += left;
if (_bufferLen == remaining()) {
if (!_writeBuffer()) {
return len - left;
}
}
return len;
}
size_t UpdateClass::writeStream(Stream &data) {
size_t written = 0;
size_t toRead = 0;
int timeout_failures = 0;
if (hasError() || !isRunning()) {
return 0;
}
if (_command == U_FLASH && !_cryptMode) {
if (!_verifyHeader(data.peek())) {
_reset();
return 0;
}
}
if (_ledPin != -1) {
pinMode(_ledPin, OUTPUT);
}
while (remaining()) {
if (_ledPin != -1) {
digitalWrite(_ledPin, _ledOn); // Switch LED on
}
size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen;
if (bytesToRead > remaining()) {
bytesToRead = remaining();
}
/*
Init read&timeout counters and try to read, if read failed, increase counter,
wait 100ms and try to read again. If counter > 300 (30 sec), give up/abort
*/
toRead = 0;
timeout_failures = 0;
while (!toRead) {
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if (toRead == 0) {
timeout_failures++;
if (timeout_failures >= 300) {
_abort(UPDATE_ERROR_STREAM);
return written;
}
delay(100);
}
}
if (_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // Switch LED off
}
_bufferLen += toRead;
if ((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) {
return written;
}
written += toRead;
#if CONFIG_FREERTOS_UNICORE
delay(1); // Fix solo WDT
#endif
}
return written;
}
void UpdateClass::printError(Print &out) {
out.println(_err2str(_error));
}
const char *UpdateClass::errorString() {
return _err2str(_error);
}
bool UpdateClass::_chkDataInBlock(const uint8_t *data, size_t len) const {
// check 32-bit aligned blocks only
if (!len || len % sizeof(uint32_t)) {
return true;
}
size_t dwl = len / sizeof(uint32_t);
do {
if (*(uint32_t *)data ^ 0xffffffff) { // for SPI NOR flash empty blocks are all one's, i.e. filled with 0xff byte
return true;
}
data += sizeof(uint32_t);
} while (--dwl);
return false;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
UpdateClass Update;
#endif
#endif // defined(ESP32)

View File

@ -17,7 +17,7 @@
// OTA
//
// ==============================================================
const char *HC__VERSION = "20260418016";
const char *HC__VERSION = "20260418036";
#define UPDATE_PORT ((uint16_t) 443)
const char *url = "visionsoft.kr";
const char *uri = "/sc/pages/firmware_download.php";

View File

@ -1,135 +0,0 @@
#define NO_GLOBAL_UPDATE
#include <string.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <esp_task_wdt.h>
#include "HCUpdate.h"
#include "HermitCrab.h"
#include "Config.h"
#include "ConnectWiFi.h"
#define TAG_OTA "OTA"
// ==============================================================
//
// OTA
//
// ==============================================================
const char *HC__VERSION = "20250405001";
#define UPDATE_PORT ((uint16_t) 443)
String url = "visionsoft.kr";
String uri = "/hc/hc_firmware_update.php";
const char *HTTPUPDATE_USERAGRENT = "ESP32-http-Update";
const char *COMPANY_NAME = "VisionSoft";
const char *SERVICE_NAME = "HermitCrab";
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"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\n" \
"-----END CERTIFICATE-----\n";
String getSketchSHA256(); // Function to retrieve current sketch hash
// Callback function for OTA progress
void onOTAProgress(int current, int total) {
ESP_LOGD(TAG_OTA,"OTA -- Progress: %d%%\n", (current * 100) / total);
}
//==========================================================================
String urlEncode(const String &url, const char *safeChars = "-_.~") {
String encoded = "";
char temp[4];
for (int i = 0; i < url.length(); i++) {
temp[0] = url.charAt(i);
if (temp[0] == 32) { //space
encoded.concat('+');
} else if ((temp[0] >= 48 && temp[0] <= 57) /*0-9*/
|| (temp[0] >= 65 && temp[0] <= 90) /*A-Z*/
|| (temp[0] >= 97 && temp[0] <= 122) /*a-z*/
|| (strchr(safeChars, temp[0]) != NULL) /* "=&-_.~" */
) {
encoded.concat(temp[0]);
} else { //character needs encoding
snprintf(temp, 4, "%%%02X", temp[0]);
encoded.concat(temp);
}
}
return encoded;
}
//==========================================================================
bool addQuery(String *query, const String name, const String value) {
if (name.length() && value.length()) {
if (query->length() < 3) {
*query = "?";
} else {
query->concat('&');
}
query->concat(urlEncode(name));
query->concat('=');
query->concat(urlEncode(value));
return true;
}
return false;
}
//==========================================================================
bool checkOTA(bool bForceUpdate)
{
// Set callbacks
//Update.onStart(onOTAStart);
//Update.onEnd(onOTAEnd);
String query = "";
addQuery(&query, "cmd", (bForceUpdate ? "download" : "check")); //action command
uri.concat(query);
String version = String(HC__VERSION);
ESP_LOGI(TAG_OTA,"OTA - URL: %s\n", url.c_str());
ESP_LOGI(TAG_OTA,"OTA - URI: %s\n", uri.c_str());
ESP_LOGI(TAG_OTA,"OTA - Ver: %s\n", HC__VERSION);
WiFiClientSecure client;
client.setCACert(rootCACertificate);
UpdateClass hcUpdate(5000);
hcUpdate.onProgress(onOTAProgress);
//int update(WiFiClient& client, String &url, uint16_t port, String& uri,
// String &currentVersion, short nDeviceType, bool bForceUpdate);
int result = hcUpdate.update(client, url, UPDATE_PORT, uri, version, (short)config.m_nDeviceType, true);
return false;
}

View File

@ -117,14 +117,7 @@ void setupWiFi() {
strncpy(BLE_PW, config.pw, sizeof(BLE_PW));
// Connect WiFi for OTA
if (config.ssid[0] && config.pw[0]) {
#if defined(ESP32)
esp_wifi_set_max_tx_power(74);
#elif defined(ESP8266)
WiFi.setOutputPower(20.5f);
pinMode(16,OUTPUT);
digitalWrite(16, LOW);
int c = 0;
#endif
DPRINTF("BOOT: Connecting to WiFi: SSID: '%s', PW: '%s'\n", config.ssid, config.pw);
WiFi.mode(WIFI_STA);
@ -173,7 +166,7 @@ void setupPostWiFi(bool bBoot = false) {
ui.message(2, (char *) "Update check...");
ledcWrite(PIN_LED_WIFI, PWM_FULL / 20);
checkOTA(true);
ledcWrite(PIN_LED_WIFI, PWM_FULL * 19 / 20);
ledcWrite(PIN_LED_WIFI, PWM_FULL * 39 / 40);
ui.message(2, (char *) "Update OK!");
DPRINTLN(" OTA Process completed!");
DPRINTLN("===============================\n");
@ -221,7 +214,7 @@ void setupPins() {
ledcWrite(PIN_FAN, PWM_OFF);
ledcWrite(PIN_LIGHT, PWM_OFF);
ledcWrite(PIN_MIST, PWM_OFF);
ledcWrite(PIN_LED_WIFI, PWM_FULL / 10); // 80% Initial Blink
ledcWrite(PIN_LED_WIFI, PWM_FULL / 20); // 80% Initial Blink
// Heaters (Inverted logic for MOSFET safety)
ledcWrite(PIN_LED_HEATER1, PWM_FULL - PWM_OFF);

View File

@ -188,7 +188,7 @@ void CWiFiHost::Loop(unsigned long clock)
m_bClientConnected = true;
m_bHelloSent = false;
m_nMode = MODE_PACKET;
ledcWrite(PIN_LED_WIFI, PWM_FULL * 9 / 10);
ledcWrite(PIN_LED_WIFI, PWM_FULL * 39 / 40);
}
return;
}

135
zcd.cpp
View File

@ -11,29 +11,44 @@
#include "driver/timer.h" // Needed for timer_isr_register
#include "rom/ets_sys.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
timg_dev_t *tg0 = &TIMERG0;
timg_dev_t *tg1 = &TIMERG1;
#define SET_TIMER_1_SAFE(duty) do { \
timerWrite(timerHeater1, 0); \
timerAlarm(timerHeater1, duty, false, 0); \
} while(0)
#define SET_TIMER_2_SAFE(duty) do { \
timerWrite(timerHeater2, 0); \
timerAlarm(timerHeater2, duty, false, 0); \
} while(0)
#define SET_TIMER_1(duty) do { \
timer_ll_enable_counter(tg1, 0, false); \
tg1->hw_timer[0].loadhi.val = 0; \
tg1->hw_timer[0].loadlo.val = 0; \
tg1->hw_timer[0].load.val = 1; \
\
timer_ll_set_alarm_value(tg1, 0, duty); \
timer_ll_enable_alarm(tg1, 0, true); \
timer_ll_enable_counter(tg1, 0, true); \
\
tg1->int_clr_timers.t0_int_clr = 1; \
\
tg1->hw_timer[0].update.val = 1; \
} while(0)
#define SET_TIMER_2(duty) do { \
timer_ll_enable_counter(tg1, 1, false); \
tg1->hw_timer[1].loadhi.val = 0; \
tg1->hw_timer[1].loadlo.val = 0; \
tg1->hw_timer[1].load.val = 1; \
\
timer_ll_set_alarm_value(tg1, 1, duty); \
timer_ll_enable_alarm(tg1, 1, true); \
timer_ll_enable_counter(tg1, 1, true); \
\
tg1->int_clr_timers.t1_int_clr = 1; \
\
tg1->hw_timer[1].update.val = 1; \
} while(0)
#define ESP_INTR_FLAG_LEVEL3 (1<<3)
@ -67,6 +82,8 @@ volatile uint8_t fireStatusTimer1 = 0;
volatile uint8_t fireStatusTimer2 = 0;
hw_timer_t *timer10K;
hw_timer_t *timerDummy;
hw_timer_t *timerHeater1;
hw_timer_t *timerHeater2;
@ -90,9 +107,9 @@ const char fireTable[17][16] {
{1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1} // 16 100%
};
uint8_t seqStep = 0;
uint8_t dutyAC1TableIndex = 0;
uint8_t dutyAC2TableIndex = 0;
volatile uint8_t seqStep = 0;
volatile uint8_t dutyAC1TableIndex = 0;
volatile uint8_t dutyAC2TableIndex = 0;
// AC Frequency and health status
volatile uint8_t zcdACISRCount = 0;
@ -187,34 +204,30 @@ ARDUINO_ISR_ATTR void setHeater2Duty(short duty) {
}
void ARDUINO_ISR_ATTR onTimer1(void *) {
tg1->int_clr_timers.t0_int_clr = 1; // Clear Interrupt
void ARDUINO_ISR_ATTR onTimer1() {
if (fireStatusTimer1 == 0) {
// First Trigger: Turn ON
REG_WRITE(GPIO_OUT1_W1TS_REG, (1UL << (PIN_HEATER1 - 32)));
fireStatusTimer1 = 1;
SET_TIMER_1(8); // Schedule OFF pulse 8us later
SET_TIMER_1_SAFE(18); // Schedule OFF pulse 8us later
} else {
// Second Trigger: Turn OFF
REG_WRITE(GPIO_OUT1_W1TC_REG, (1UL << (PIN_HEATER1 - 32)));
}
}
void ARDUINO_ISR_ATTR onTimer2(void *) {
tg1->int_clr_timers.t1_int_clr = 1;
void ARDUINO_ISR_ATTR onTimer2() {
if (fireStatusTimer2 == 0) {
REG_WRITE(GPIO_OUT1_W1TS_REG, (1UL << (PIN_HEATER2 - 32)));
fireStatusTimer2 = 1;
SET_TIMER_2(8);
SET_TIMER_2_SAFE(18);
} else {
REG_WRITE(GPIO_OUT1_W1TC_REG, (1UL << (PIN_HEATER2 - 32)));
}
}
// Zero-Cross Detection Interrupt Service Routine
void ARDUINO_ISR_ATTR zcdACISR(void *) {
void ARDUINO_ISR_ATTR zcdACISR() {
// 1. Power side AC ZCD Count
zcdACISRCount++;
@ -223,27 +236,29 @@ void ARDUINO_ISR_ATTR zcdACISR(void *) {
// 3. Heater 1
if (ac1ControlMode == ZCD_CONTROL) {
if (fireTable[dutyAC1TableIndex][seqStep]) SET_TIMER_1(LEADING_ZCD_COUNT);
if (fireTable[dutyAC1TableIndex][seqStep]) SET_TIMER_1_SAFE(LEADING_ZCD_COUNT);
} else if ( dutyHeater1 >= LEADING_PULSE_COUNT){
SET_TIMER_1(dutyHeater1);
SET_TIMER_1_SAFE(dutyHeater1);
}
// 4. Heater 2
if (ac2ControlMode == ZCD_CONTROL) {
if (fireTable[dutyAC2TableIndex][seqStep]) SET_TIMER_2(LEADING_ZCD_COUNT);
if (fireTable[dutyAC2TableIndex][seqStep]) SET_TIMER_2_SAFE(LEADING_ZCD_COUNT);
} else if (dutyHeater2 >= LEADING_PULSE_COUNT) {
SET_TIMER_2(dutyHeater2);
SET_TIMER_2_SAFE(dutyHeater2);
}
seqStep = ++seqStep & 0x0F;
}
void ARDUINO_ISR_ATTR zcdLoadISR(void *) {
void ARDUINO_ISR_ATTR zcdLoadISR() {
// Load side AC ZCD Count
zcdLoadISRCount++;
}
void setupZCD() {
periph_module_enable(PERIPH_TIMG0_MODULE);
periph_module_enable(PERIPH_TIMG1_MODULE);
pinMode(PIN_ZCD_AC, INPUT);
pinMode(PIN_ZCD_LOAD, INPUT);
pinMode(PIN_HEATER1, OUTPUT);
@ -264,6 +279,8 @@ void setupZCD() {
ac1ControlMode = config.ac1ControlMode;
ac2ControlMode = config.ac2ControlMode;
//===============================================================================
// --- 0. Hardware Config for TG0-Timer0 (g_millis)
timer_ll_set_clock_prescale(tg0, 0, 8000); // 10KHz
timer_ll_set_count_direction(tg0, 0, GPTIMER_COUNT_UP);
@ -273,63 +290,43 @@ void setupZCD() {
timer_ll_enable_alarm(tg0, 0, false); // No ISR needed for g_millis
timer_ll_enable_counter(tg0, 0, true);
// --- 1. Hardware Config for TG0-Timer1 (Watchdog) ---
tg0->hw_timer[1].loadhi.val = 0UL;
tg0->hw_timer[1].loadlo.val = 0UL;
tg0->hw_timer[1].load.val = 1UL;
timer_ll_set_clock_prescale(tg0, 1, 80); // 1MHz
timer_ll_set_count_direction(tg0, 1, GPTIMER_COUNT_UP);
timer_ll_enable_alarm(tg0, 1, false); // No ISR needed for zcdACISR Watchdog
timer_ll_enable_counter(tg0, 1, true);
attachInterrupt(PIN_ZCD_AC, zcdACISR, CHANGE); // Attach zero-cross detection ISR
attachInterrupt(PIN_ZCD_LOAD, zcdLoadISR, CHANGE); // Attach zero-cross detection ISR
// --- 2. Basic Hardware Config for TG1 (Heaters) ---
// Timer 0
timer_ll_set_clock_prescale(tg1, 0, 80);
timer_ll_set_count_direction(tg1, 0, GPTIMER_COUNT_UP);
timer_ll_enable_alarm(tg1, 0, true);
// Initialize and configure the timer
if ((timer10K = timerBegin(10000)) != NULL) {
// TG0-Timer0
timerStop(timer10K); // Ensure timer is stopped initially
timerStart(timer10K); // Explicitly start the timer after setup
// Timer 1
timer_ll_set_clock_prescale(tg1, 1, 80);
timer_ll_set_count_direction(tg1, 1, GPTIMER_COUNT_UP);
timer_ll_enable_alarm(tg1, 1, true);
}
// --- 3. Manual ISR Registration ---
esp_intr_alloc(ETS_TG1_T0_LEVEL_INTR_SOURCE, ESP_INTR_FLAG_IRAM, onTimer1, NULL, NULL);
esp_intr_alloc(ETS_TG1_T1_LEVEL_INTR_SOURCE, ESP_INTR_FLAG_IRAM, onTimer2, NULL, NULL);
if ((timerDummy = timerBegin(1000000)) != NULL) {
// TG0-Timer1
timerStop(timerDummy); // Ensure timer is stopped initially
timerStart(timerDummy); // Explicitly start the timer after setup
}
tg0->hw_timer[0].update.val = 1;
uint32_t startupTime = timer_ll_get_counter_value(tg0, 0);
if ((timerHeater1 = timerBegin(1000000)) != NULL) {
// TG1-Timer0
timerAttachInterrupt(timerHeater1, &onTimer1); // Attach TRIAC firing routine
timerStop(timerHeater1); // Ensure timer is stopped initially
timerStart(timerHeater1); // Explicitly start the timer after setup
zcdACISRCount = 0;
zcdLoadISRCount = 0;
}
// --- 4. Hardware Trigger ISR Registration ---
//attachInterrupt(PIN_ZCD_AC, zcdACISR, CHANGE); // Attach zero-cross detection ISR
//attachInterrupt(PIN_ZCD_LOAD, zcdLoadISR, CHANGE); // Attach zero-cross detection ISR
// Attach Load ISR with higher priority
// Set the edges
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_ANYEDGE;
io_conf.pin_bit_mask = (1ULL << PIN_ZCD_AC) | (1ULL << PIN_ZCD_LOAD);
io_conf.mode = GPIO_MODE_INPUT;
gpio_config(&io_conf);
// 2. Set the GLOBAL Priority for all GPIOs
// We use LEVEL3 so that BOTH ZCD pulses can preempt lower tasks.
// This replaces the attachInterrupt() default (Level 1).
gpio_install_isr_service(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3);
// 3. Register the independent handlers
// The service handles the "Which pin was it?" logic automatically.
gpio_isr_handler_add((gpio_num_t)PIN_ZCD_LOAD, zcdLoadISR, NULL);
gpio_isr_handler_add((gpio_num_t)PIN_ZCD_AC, zcdACISR, NULL);
if ((timerHeater2 = timerBegin(1000000)) != NULL) {
// TG1-Timer1
timerAttachInterrupt(timerHeater2, &onTimer2); // Attach TRIAC firing routine
timerStop(timerHeater2); // Ensure timer is stopped initially
timerStart(timerHeater2); // Explicitly start the timer after setup
}
}
void setACLoadStatus(uint32_t tNow) {
static uint32_t lastTick = 0;
// --- 1. Process AC/Load Window (Every 1s) ---
if (tNow - lastTick >= 9999) {
if (tNow - lastTick >= 10000) {
zcdACCount = zcdACISRCount;
zcdLoadCount = zcdLoadISRCount;