#ifndef EQSP32_h
#define EQSP32_h

#include <Arduino.h>
#include <string>

#include "time.h"

#include <unordered_map>   // For std::unordered_map
#include <map>             // For std::map
#include <functional>      // For std::function
#include <type_traits>     // For type traits utilities

#include "driver/ledc.h"
#include "driver/mcpwm.h"
#include "driver/twai.h"

#define POUT_PATCH(eq, pin, mode)    do {int native = eq.getPin(pin); eq.pinMode(pin, mode); pinMode(native, OUTPUT);} while (0);


/**
 *      EQSP32 Main unit pin codes
 */
// User available pin codes
#define EQ_PIN_1        1
#define EQ_PIN_3        3
#define EQ_PIN_2        2
#define EQ_PIN_4        4
#define EQ_PIN_5        5
#define EQ_PIN_6        6
#define EQ_PIN_7        7
#define EQ_PIN_8        8
#define EQ_PIN_9        9
#define EQ_PIN_10       10
#define EQ_PIN_11       11
#define EQ_PIN_12       12
#define EQ_PIN_13       13
#define EQ_PIN_14       14
#define EQ_PIN_15       15
#define EQ_PIN_16       16

#define EQ_AO_1         1
#define EQ_AO_2         2

// User available peripheral pin codes
#define EQ_RS232_TX     20
#define EQ_RS232_RX     21
#define EQ_RS485_TX     22
#define EQ_RS485_RX     23
#define EQ_RS485_EN     24
#define EQ_CAN_TX       25
#define EQ_CAN_RX       26

#define IS_ADIO_PIN(p)      (p >= EQ_PIN_1 && p <= EQ_PIN_16 ? true : false)
#define IS_RS232_PIN(p)     (p == EQ_RS232_TX || p == EQ_RS232_RX ? true : false)
#define IS_RS485_PIN(p)     (p >= EQ_RS485_TX && p <= EQ_RS485_EN ? true : false)
#define IS_CAN_PIN(p)       (p == EQ_CAN_TX || p == EQ_CAN_RX ? true : false)

/**
 *      EQX - EQSP32 Expansion module pin codes and channels
 */
// ==========================
// EQXTC - Thermocouple Module
// ==========================
#define EQXTC_CH_1       1  // Thermocouple channel 1
#define EQXTC_CH_2       2  // Thermocouple channel 2
#define EQXTC_CH_3       3  // Thermocouple channel 3
#define EQXTC_CH_4       4  // Thermocouple channel 4
#define EQXTC_CH_5       5  // Thermocouple channel 5
#define EQXTC_CH_6       6  // Thermocouple channel 6
#define EQXTC_CHANNELS   6  // Total number of channels

// ==========================
// EQXPT - PT100 RTD Module
// ==========================
#define EQXPT_M_1        1  // PT100 Measurement point 1
#define EQXPT_M_2        2  // PT100 Measurement point 2
#define EQXPT_CHANNELS   2  // Total number of channels

// ==========================
// EQXPH - pH Measurement Module
// ==========================
#define EQXPH_PH_1       1  // pH measurement channel
#define EQXPH_CHANNELS   1  // Total number of channels

// ==========================
// EQXIO - Digital IO Expansion Module
// ==========================
#define EQXIO_PIN_1      1  // IO Pin 1
#define EQXIO_PIN_2      2  // IO Pin 2
#define EQXIO_PIN_3      3  // IO Pin 3
#define EQXIO_PIN_4      4  // IO Pin 4
#define EQXIO_PIN_5      5  // IO Pin 5
#define EQXIO_PIN_6      6  // IO Pin 6
#define EQXIO_PIN_7      7  // IO Pin 7
#define EQXIO_PIN_8      8  // IO Pin 8
#define EQXIO_PIN_9      9  // IO Pin 9
#define EQXIO_PIN_10     10 // IO Pin 10
#define EQXIO_CHANNELS   10 // Total number of channels


// EQSP32 IoT expansion modules - Masks
#define PIN_SHIFT(id)           ((id) << 0)       // Bits 0-7
                                                // Bits 8-11 (Reserved)
#define MODULE_IDX_SHIFT(id)    ((id) << 12)      // Bits 12-15
#define MODULE_SHIFT(id)        ((id) << 16)      // Bits 16-23
#define SLAVE_SHIFT(id)         ((id) << 24)      // Bits 24-31

#define PIN_MASK            PIN_SHIFT(0xFF)         // 8-bit
#define MODULE_IDX_MASK     MODULE_IDX_SHIFT(0xF)   // 4-bit        (EQX Modules)
#define MODULE_MASK         MODULE_SHIFT(0xFF)      // 8-bit        (EQX Modules)
#define SLAVE_MASK          SLAVE_SHIFT(0xFF)       // 8-bit

#define PIN_UNSHIFT(id)         (((id) & PIN_MASK) >> 0)
#define MODULE_IDX_UNSHIFT(id)  (((id) & MODULE_IDX_MASK) >> 12)    // (EQX Modules)
#define MODULE_UNSHIFT(id)      (((id) & MODULE_MASK) >> 16)        // (EQX Modules)
#define SLAVE_UNSHIFT(id)       (((id) & SLAVE_MASK) >> 24)

// EQSP32 IoT expansion modules - Index Codes
#define COMBINED_MASK       (MODULE_MASK | MODULE_IDX_MASK | PIN_MASK)
#define MASTER(pin)         (SLAVE_SHIFT(0) | (pin & MODULE_MASK) | (pin & MODULE_IDX_MASK) | (pin & PIN_MASK))
#define SLAVE_1(pin)        (SLAVE_SHIFT(1) | (pin & MODULE_MASK) | (pin & MODULE_IDX_MASK) | (pin & PIN_MASK))
// #define SLAVE_1(pin)        (SLAVE_SHIFT(1) | (pin & COMBINED_MASK))
#define SLAVE_2(pin)        (SLAVE_SHIFT(2) | (pin & MODULE_MASK) | (pin & MODULE_IDX_MASK) | (pin & PIN_MASK))
#define SLAVE_3(pin)        (SLAVE_SHIFT(3) | (pin & MODULE_MASK) | (pin & MODULE_IDX_MASK) | (pin & PIN_MASK))
#define SLAVE_4(pin)        (SLAVE_SHIFT(4) | (pin & MODULE_MASK) | (pin & MODULE_IDX_MASK) | (pin & PIN_MASK))

#define EQ_MAIN(pin)        MASTER(pin)
#define EQ_AUX_1(pin)       SLAVE_1(pin)
#define EQ_AUX_2(pin)       SLAVE_2(pin)
#define EQ_AUX_3(pin)       SLAVE_3(pin)
#define EQ_AUX_4(pin)       SLAVE_4(pin)

// (EQX Modules)
#define MAX_MODULE_TYPES    0xFF
#define EQXIO_ID            0x01        // ADIO module                  (Supported)
#define EQXSTEP_ID          0x02        // Stepper driver module
#define EQXPH_ID            0x10        // PH sensor module             (Supported)
#define EQXTC_ID            0x20        // Thermocouple sensor module   (Supported)
#define EQXPT_ID            0x30        // PT100 sensor module          (Supported)
#define EQXCI_ID            0x50        // Current input sensor [4-20mA analog input]
#define EQXCS_ID            0x50        // Current sensor module
#define EQXLC_ID            0x60        // Load cell sensor module


#define EQXIO_MACRO(idx, pin)     (MODULE_SHIFT(EQXIO_ID) | (MODULE_IDX_SHIFT(idx & 0x0F)) | (pin & PIN_MASK))        // (EQX Modules)
#define EQXPH_MACRO(idx, pin)     (MODULE_SHIFT(EQXPH_ID) | (MODULE_IDX_SHIFT(idx & 0x0F)) | (pin & PIN_MASK))        // (EQX Modules)
#define EQXTC_MACRO(idx, pin)     (MODULE_SHIFT(EQXTC_ID) | (MODULE_IDX_SHIFT(idx & 0x0F)) | (pin & PIN_MASK))        // (EQX Modules)
#define EQXPT_MACRO(idx, pin)     (MODULE_SHIFT(EQXPT_ID) | (MODULE_IDX_SHIFT(idx & 0x0F)) | (pin & PIN_MASK))        // (EQX Modules)

// Inline wrappers with default pin = 0
inline uint32_t EQXIO(int idx, int pin = 0) { return EQXIO_MACRO(idx, pin); }
inline uint32_t EQXPH(int idx, int pin = 0) { return EQXPH_MACRO(idx, pin); }
inline uint32_t EQXTC(int idx, int pin = 0) { return EQXTC_MACRO(idx, pin); }
inline uint32_t EQXPT(int idx, int pin = 0) { return EQXPT_MACRO(idx, pin); }

// EQSP32 pin modes
enum EQ_PinMode : uint8_t {
    NO_MODE = 0xFF,
    CUSTOM  = 0xFE,
    INIT_NA = 0xFD,
    DIN     = 0,        //                                                  |IOEXP pin LOW (1-8), HIGH (9-16)
    DOUT,               // Not used
    AIN,                //                                                  |IOEXP pin LOW (1-8)
    CIN,                // Current input mode, returns mA x 100             |IOEXP pin HIGH (1-8)       (Must hasIOEXP be true)
    PCC,    // (Beta)   // Pulse Capture Counter mode, counted pulses are cleared on each readPin() call for the respective pin
    POUT,               //                                                  |IOEXP pin LOW (1-8), LOW (9-16)
    AOUT,
        // Special modes
    SWT     = 8,        // Special DIN mode with debouncing timer
    TIN,                // Special AIN mode, automatic temperature convertion
    RELAY,              // Special POUT mode, starts with set power and after set time drops to holding power
    RAIN,               // Relative analog input, this returns a value of 0-1000 representing the % of read value versus the VOut reference voltage

    PH      = 0x10,     // pH measurement       (EQX Modules)
    TC,                 // thermocouple         (EQX Modules)
    PT100_24W,          // PT100 RTD in 2/4 wire configuration      (EQX Modules)
    PT100_3W,           // PT100 RTD in 3 wire configuration        (EQX Modules)
};


/** =========================================
 * 
 *      Value conversions based on pin modes
 *    
 * Divide the respective readPin() value 
 * with the respective multiplier for conversion.
 * 
 *   =========================================*/
#define AIN_TO_V_MULT       1000.0      // AIN values are by default in mV (V * 1000)
#define TIN_TO_C_MULT       10.0        // TIN values are in C * 10
#define CIN_TO_mA_MULT      100.0       // CIN values are in mA * 100

#define PH_TO_PH_MUL        100.0       // PH values are in pH * 100
#define TC_TO_C_MUL         10.0        // TC values are in C * 10
#define PT_TO_C_MUL         100.0       // PT values are in C * 100 (higher precision temperature measurements that the other modes)

inline float CONVERT_AIN(int readPinValue) { return ( (float)readPinValue / AIN_TO_V_MULT ); }
inline float CONVERT_TIN(int readPinValue) { return ( (float)readPinValue / TIN_TO_C_MULT ); }
inline float CONVERT_CIN(int readPinValue) { return ( (float)readPinValue / CIN_TO_mA_MULT ); }
inline float CONVERT_PH(int readPinValue) { return ( (float)readPinValue / PH_TO_PH_MUL ); }
inline float CONVERT_TC(int readPinValue) { return ( (float)readPinValue / TC_TO_C_MUL ); }
inline float CONVERT_PT(int readPinValue) { return ( (float)readPinValue / PT_TO_C_MUL ); }

// ====================================

#define CIN_OC_ERROR        -1          // CIN sensor has detected an over current condition (> 21mA)

#define TIN_OPEN_CIRCUIT    -9999       // Open circuit detected
#define TIN_SHORT_CIRCUIT   9999        // Short circuit detected
#define IS_TIN_VALID(value) ((value) != TIN_OPEN_CIRCUIT && (value) != TIN_SHORT_CIRCUIT)


#define TC_FAULT_OPEN       0x8001      // Thermocouple open circuit
#define TC_FAULT_SHORT_GND  0x8002      // Thermocouple shorted to GND
#define TC_FAULT_SHORT_VCC  0x8004      // Thermocouple shorted to VCC
#define IS_TC_VALID(value) (((value) & 0xFF8000) != 0x8000)        // Macro to check if a Thermocouple (TC) sensor value is valid (we check if error bit or if negative temperature)


#define PT_FAULT_THR_HIGH   0x800080    // PT sensor RTD > High allowed threshold   (Wrong sensor value)
#define PT_FAULT_THR_LOW    0x800040    // PT sensor RTD < Low allowed threshold    (Wrong sensor value)
#define PT_FAULT_REFIN_LOW  0x800020    // PT sensor REF under expected             (No sensor)
#define PT_FAULT_REFIN_HIGH 0x800010    // PT sensor open circuit or REF over expected  (Open circuit)
#define PT_FAULT_RTDIN_LOW  0x800008    // PT sensor M- or I- open, or damaged RTD sensor   (Open or damaged circuit)
#define PT_FAULT_OVUV       0x800004    // PT sensor Over/Under voltage             (Voltage error)
#define IS_PT_VALID(value) (((value) & 0xFF800000) != 0x800000)      // Macro to check if a PT sensor value is valid (we check error bit or if negative temperature)


enum EQ_TrigMode : uint8_t {
    STATE,
    ON_RISING,
    ON_FALLING,
    ON_TOGGLE
};

enum EQ_WifiStatus : uint8_t {
    EQ_WF_DISCONNECTED  = 0,
    EQ_WF_CONNECTED,
    EQ_WF_RECONNECTING,
    EQ_WF_SCANNING
};

enum EQ_EthernetStatus : uint8_t {
    EQ_ETH_DISCONNECTED  = 0,   // Ethernet cable disconnected
    EQ_ETH_CONNECTED,           // Online
    EQ_ETH_PLUGGED_IN,          // Plugged in, not yet online
    EQ_ETH_STOPPED
};

enum EQ_WeekDay : uint8_t {
    EQ_SUNDAY = 0,
    EQ_MONDAY,    // 1
    EQ_TUESDAY,   // 2
    EQ_WEDNESDAY, // 3
    EQ_THURSDAY,  // 4
    EQ_FRIDAY,    // 5
    EQ_SATURDAY   // 6
};

// CAN Bus
#define CanMessage      twai_message_t

enum CanBitRates
{
    CAN_25K = 0,    // Enumerator for 25 kbps
    CAN_50K,        // Enumerator for 50 kbps
    CAN_100K,       // Enumerator for 100 kbps
    CAN_125K,       // Enumerator for 125 kbps
    CAN_250K,       // Enumerator for 250 kbps
    CAN_500K,       // Enumerator for 500 kbps
    CAN_800K,       // Enumerator for 800 kbps
    CAN_1000K       // Enumerator for 1000 kbps (1 Mbps)
};


enum EQSerialMode {
    RS232,
    RS232_INV,
    RS485_TX,
    RS485_RX,
};

typedef struct
{
    std::string mqttBrokerIp = "homeassistant.local";
    int mqttBrokerPort = 1883;
    std::string mqtt_broker_ca = "";    // CA certificate (empty if not required)
    std::string devSystemID = "";        // Developer system ID (assigned by the system's developer, hardcoded by developer, READ ONLY access by external app)
    std::string userDevName = "";        // Device name (assigned by the end user, default value assigned on first flash, user has READ/WRITE access)
    std::string wifiSSID = "";          // (Optional) Default network SSID
    std::string wifiPassword = "";      // (Optional) Default network password
    std::string staticIP = "0.0.0.0";   // (Default) (Optional) Leave as 0.0.0.0 to enable DHCP
    std::string gateway = "0.0.0.0";
    std::string subnet = "0.0.0.0";
    std::string DNS = "0.0.0.0";
    bool relaySequencer = false;
    bool mqttDiscovery = false;
    bool disableErqosIoT = false;
    bool disableNetSwitching = false;
} EQSP32Configs;


class EQ_Private;       // Forward declaration of the nested private class


#if ESP_ARDUINO_VERSION_MAJOR > 2
#undef Serial  // 🛡️ protect the header itself
#endif

/**
 * @brief EQSP32 ESP32 Industrial IoT PLC Controller.
 * 
 * The EQSP32 class provides a comprehensive interface for interacting with the EQSP32 IoT controller.
 * This class includes methods for initializing the module, configuring and controlling ADIO (analog/digital I/O) pins and their special modes, handling industrial communications (RS232, RS485, CANBus),
 * handling user cloud variables, IoT connectivity with cloud server (Firebase), and handling
 * MQTT device interfaces for Home Assistant integration.
 * 
 * @note It is critical to call the `begin()` function at the beginning of your EQSP32 application for proper operation.
 * 
 * @example
 * Usage example:
 * 
 * @code
 * #include <EQSP32.h>
 * 
 * EQSP32 eqsp32;
 * 
 * void setup() {
 *     EQSP32Configs configs;
 *     configs.devSystemID = "SYSTEM_ID";                       // Needed for custom mobile app
 *     configs.userDevName = "Device Name";                     // Optional
 *     configs.wifiSSID = "YourSSID";                           // Optional
 *     configs.wifiPassword = "YourPassword";                   // Optional
 *     configs.relaySequencer = true;                           // Default false
 *     configs.mqttDiscovery = true;                            // Optional
 * 
 *     eqsp32.begin(configs, true);
 * }
 * 
 * void loop() {
 *     // Your main code
 * }
 * @endcode
 */
class EQSP32 {
public:
    HardwareSerial& Serial;  // Reference to Serial2

    EQSP32();
    ~EQSP32();

        // Main EQ initialization
    /**
     * @brief Initializes the EQSP32 module.
     * 
     * This function MUST be called at the beginning of your application for the EQSP32 module to run properly.
     * The initialization includes:
     * 
     * - Setting up peripheral communication.
     * - Initializing WiFi and BLE modules.
     * - Configuring all pins in safe Digital Input (DIN) state.
     * - Initializing RS232 serial communication, default 115200 baud.
     * - Initiating the EQSP32 System Manager Task for continuous system management.
     * - Starting database-related tasks for data storage and retrieval.
     * 
     * @attention Create a single EQSP32 eqsp32; object and use this for all operations to avoid conflicts.
     * 
     * @note It is CRITICAL to call this function at the beginning of your EQSP32 application for proper operation.
     * 
     * @example
     * Usage example:
     * EQSP32 eqsp32;
     * eqsp32.begin();
     */
    void begin(EQSP32Configs eq_configs = {}, bool verboseEnabled = false);
    void begin(bool verboseEnabled);


    // Only for self-testing, NOT to be used by the user
    void beginTest(std::string command);


    /**
     * @brief Checks if a given pin identifier corresponds to a local pin on the EQSP32 module.
     * 
     * This function determines whether a given pin identifier (idMaskCode) corresponds to a local pin on the EQSP32 module.
     * A local pin refers to a pin on the EQSP32 module itself based on its mode (master, slave 1, etc).
     * 
     * For example, if the EQSP32 module is set to slave mode with ID 1, then SLAVE_1(pin) is local. If the EQSP32 module 
     * is in slave mode with ID 2 or in master mode, then SLAVE_1(pin) would return false.
     * 
     * @param idMaskCode The pin identifier mask code to check.
     * 
     * @return Returns true if the pin identifier corresponds to a local pin on the EQSP32 module, otherwise returns false.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQSP32 eqsp32;
     * uint32_t pinCode = SLAVE_1(EQ_PIN_3); // Example pin code
     * bool isLocal = eqsp32.isLocalPin(pinCode);
     * if (isLocal) {
     *     Serial.println("The pin is local to the EQSP32 module.");
     * } else {
     *     Serial.println("The pin is not local to the EQSP32 module.");
     * }
     * @endcode
     */
    bool isLocalPin(uint32_t idMaskCode);


    /**
     * @brief Checks if a given pin identifier corresponds to an expansion module pin on the EQSP32.
     * 
     * This function determines whether a given pin identifier (idMaskCode) corresponds to a pin on one of the expansion modules
     * of the EQSP32. It checks if the pin is not on the main unit but on an expansion module.
     * 
     * @param idMaskCode The pin identifier mask code to check.
     * 
     * @return Returns true if the pin identifier corresponds to a pin on an expansion module, otherwise returns false.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQSP32 eqsp32;
     * uint32_t pinCode = EQXPH(EQ_PIN_3); // Example pin code for expansion module
     * bool isExpPin = eqsp32.isExpModulePin(pinCode);
     * if (isExpPin) {
     *     Serial.println("The pin is on an expansion module.");
     * } else {
     *     Serial.println("The pin is on the main unit.");
     * }
     * @endcode
     */
    bool isExpModulePin(uint32_t idMaskCode);


    /**
     * @brief Checks whether a specific EQX expansion module is detected on the system.
     * 
     * This function allows you to check if a particular EQSP32 expansion module (EQX) is currently connected and recognized
     * by the EQSP32 controller. It verifies the presence of the module based on the provided `moduleCode`, which should
     * be constructed using the appropriate macros like `EQXTC(idx)`, `EQXPH(idx)`, `EQXIO(idx, pin)`, etc.
     * 
     * @param moduleCode The encoded module identifier. Use macros like `EQXPH(idx)`, `EQXTC(idx)`, `EQXIO(idx, pin)` to generate this value.
     *                  The `idx` argument must be in the range 1–15. Index 0 is not valid and should not be used.
     * 
     * @return true if the module is detected and properly initialized, false otherwise.
     * 
     * @note
     * - The EQSP32 system supports up to 15 expansion modules of each type.
     * - Indexing is 1-based, meaning the first module uses index 1.
     * - Indexing is per module type, not based on physical daisy-chain order.
     *   For example, if you have 2 EQXPH modules and 2 EQXIO modules connected in any order,
     *   the first detected pH module will be `EQXPH(1)` and the second will be `EQXPH(2)`, regardless of where the EQXIO modules are placed.
     *   Likewise, the first detected IO module will be `EQXIO(1)` and the second `EQXIO(2)`, even if they appear later in the chain physically.
     * 
     * @example
     * @code
     * if (eqsp32.isModuleDetected(EQXPH(1))) {
     *     Serial.println("pH module 1 detected.");
     * } else {
     *     Serial.println("ph module 1 NOT detected.");
     * }
     * @endcode
     */
    bool isModuleDetected(uint32_t moduleCode);


    /**
     * @brief Retrieves the corresponding ESP32 pin number for a given EQSP32 pin index.
     * 
     * This function maps a high-level EQSP32 pin index to the actual ESP32 GPIO number or peripheral pin.
     * It supports mapping for ADIO pins, RS232, RS485, and CAN bus pins. For ADIO pins, it sets the pin mode to CUSTOM.
     * For serial communication pins (RS232 and RS485), it configures the hardware appropriately to support the respective RS232 or RS485 protocol.
     * For CAN pins, it returns the native ESP32 pin mapped on the CAN driver and deinitializes the CAN peripheral. It is up to the user to initialize the CAN peripheral based on their needs.
     * 
     * @param pinIndex The index of the EQSP32 pin (e.g., EQ_RS232_TX, EQ_RS485_RX).
     * Master/Slave and Expansion module masks are handled automatically.
     * @return The corresponding ESP32 pin number if the pin is valid, or -1 if the pin index does not match any known configuration.
     * 
     * @example
     * int rs232Tx = eqsp32.getPin(EQ_RS232_TX); // Gets the ESP32 pin number for RS232 transmission.
     * int rs232Rx = eqsp32.getPin(EQ_RS232_RX); // Gets the ESP32 pin number for RS232 transmission.
     */
    int getPin(int pinIndex);


        // EQ pin configurations
    /**
     * @brief Sets the mode for a specified pin on the EQSP32.
     * 
     * This function allows you to set the mode for a pin on the EQSP32,
     * which has 16 analog and digital I/O pins.
     * Pins 1-8 also support both analog input, while pins 9-16 also support pseudo-analog output (pseudo-analog using PWM).
     * The pin can be set to different modes, including:
     * 
     * - Digital Input (DIN): Configures the pin as a standard digital input.
     * - Analog Input (AIN): Configures the pin as an analog input for pins 1-8.
     * - Pseudo-Analog Output (AOUT): Configures the pin as an Pseudo-Analog output for pins 9-16. You can specify the PWM frequency using the 'freq' parameter. The duty will be set using the 'pinValue' function.
     * - Power PWM Output (POUT): Configures the pin as a power PWM output. The duty will be set using the 'pinValue' function.
     *      //Special Modes
     * - Switch (SWT): Configures the pin as a special digital input mode with debouncing timer.
     * - Temperature Input (TIN): Configures the pin as a special analog input mode for automatic temperature conversion in celsius * 10 (needs /10.0 to get actual temperature as float with 0.1 precision).
     * - Relay (RELAY): Configures the pin as a special power PWM output mode for relay control. Starts with set power from 'pinValue', and after a set time drops to holding power. Needs to be set to 0 before restarting the start-hold power cycle.
     * - Relative AIN (RAIN): Pin operates in AIN mode and returns the measured voltage relative to 5V Vout in %, 1000 represents 100%.
     * 
     * The function returns true if the configuration is successful, and false otherwise.
     * 
     * @param pinIndex The index of the pin to set the mode for (1 to 16).
     * Master/Slave and Expansion module masks are handled automatically.
     * @param mode The mode to set for the pin. Available options are DIN, DOUT, AIN, AOUT, POUT, SWT, TIN, RELAY.
     * @param freq The PWM frequency to use for analog output (AOUT) mode. Ignored for other modes. Default is 500 Hz.
     * 
     * @return true if the configuration is successful, false otherwise.
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::pinMode(3, DOUT); // Configures pin 3 as a digital output
     */
    bool pinMode(int pinIndex, EQ_PinMode mode, int freq = 500);


    /**
     * @brief Reads the current mode of a specified pin on the EQSP32.
     * 
     * This function retrieves the current mode of a specified pin on the EQSP32.
     * If the pin is valid and local, it returns the current mode of the pin, else it returns `NO_MODE`.
     * 
     * @param pinIndex The index of the pin to read the mode from (1 to 16).
     * 
     * @return The current mode of the specified pin. It returns one of the following EQ_PinMode values: 
     * `DIN`, `DOUT`, `AIN`, `AOUT`, `POUT`, `SWT`, `TIN`, `RELAY`, `RAIN`, `NO_MODE`.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQSP32 eqsp32;
     * eqsp32.begin();
     * EQ_PinMode mode = eqsp32.readMode(EQ_PIN_3); // Reads the mode of pin 3
     * if (mode != NO_MODE) {
     *     Serial.println("The pin mode is valid and local.");
     * } else {
     *     Serial.println("The pin mode is not valid, not configured or not local.");
     * }
     * 
     * // Example for checking the mode of a slave ID 1 pin
     * uint32_t slavePin = SLAVE_1(EQ_PIN_3);
     * EQ_PinMode slaveMode = eqsp32.readMode(slavePin); // Reads the mode of slave ID 1 pin 3
     * if (slaveMode != NO_MODE) {
     *     Serial.println("The slave pin mode is valid and local for Slave 1.");
     * } else {
     *     Serial.println("The slave pin mode is not valid, not configured or this unit is not in Slave 1 mode.");
     * }
     * @endcode
     */
    EQ_PinMode readMode(int pinIndex);


    /**
     * @brief Sets the value for a specified pin on the EQSP32.
     * 
     * This function allows you to set the value of a pin on the EQSP32,
     * which has 16 analog and digital I/O pins.
     * Pins 1-8 also support analog input functionality,
     * while pins 9-16 support pseudo-analog output (Push-Pull PWM).
     * The pin mode will be set using the 'pinMode' function.
     * 
     * - Digital Output (DOUT): Used for standard digital output operations.
     * - Pseudo-Analog Output (AOUT): Supports pseudo-analog output for pins 9-16. The value should range from 0 to 1000, corresponding to 0% to 100% duty cycle.
     * - Power PWM Output (POUT): Generates pull-down power PWM output. The value should range from 0 to 1000, corresponding to 0% to 100% duty cycle.
     * - Relay (RELAY): Special POUT mode, starts with set power, and after a set time drops to holding power. Needs to be set to 0 before restarting the start-hold power cycle.
     * 
     * The function returns true if the configuration is successful, and false otherwise.
     * 
     * @param pinIndex The index of the pin to set the value for (1 to 16).
     * @param value The value to set for the pin. For digital pins, a non-zero value represents HIGH, and 0 represents LOW. For analog pins, the value should range from 0 to 1000, corresponding to 0% to 100%.
     * 
     * @return true if the operation is successful, false otherwise.
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::pinValue(3, 750); // Sets the value of pin 3 to 75%
     */
    bool pinValue(int pinIndex, int value);


    /**
     * @brief Reads the value of a specified pin on the EQSP32.
     * 
     * This function allows you to read the value of a pin on the EQSP32, considering different PinModes.
     * For analog input pins (AIN), it returns the analog value in mV.
     * For temperature input pins (TIN), it calculates and returns the temperature in Celsius * 10 (need to divide by 10.0 to get actual value with 0.1 precision).
     * For other pin modes, it reads the digital state of the pin and considers the specified trigger mode (default is STATE).
     * 
     * @param pinIndex The index of the pin to read the value from (1 to 16).
     * @param trigMode The trigger mode to consider when reading digital pins. Options are STATE, ON_RISING, ON_FALLING, and ON_TOGGLE. Default is STATE.
     * 
     * @return The pin value based on the EQ_PinMode. For AIN, it returns the analog value in mV. For TIN, it returns the temperature in Celsius * 10.
     * For other input type PinModes, it returns 1 if the pin state is HIGH, and 0 if the pin state is LOW. Returns -1 if the pin index is invalid.
     * For output type PinModes, it returns the user set pinValue. Ex. if eqsp32.pinValue(2, 500), eqsp32.readPin(2) will return 500 (assuming it is set in one of the oupute modes).
     * 
     * @attention The trigger mode is applicable only to digital pins. For analog and temperature pins, the trigger mode is ignored.
     * For TIN pins, the temperature calculation assumes the EQ's 5V is used as reference.
     * 
     * @note When reading a pin with a specific trigger mode (e.g., ON_RISING), the function updates the internal state. If you read the pin for ON_RISING and it is actually a falling edge, the internal state is updated. On the next read for ON_FALLING, it will return false, even if the actual edge was falling during the previous read. Take this into consideration when reading pins with trigger modes.
     * 
     * @section TrigModes Trigger Modes:
     * 
     * - STATE: Returns the current digital state of the pin (HIGH or LOW).
     * - ON_RISING: Returns true when the pin transitions from LOW to HIGH (rising edge).
     * - ON_FALLING: Returns true when the pin transitions from HIGH to LOW (falling edge).
     * - ON_TOGGLE: Returns true on each transition, alternating between HIGH and LOW.
     * 
     * @example
     * Usage example:
     * int value = EQSP32::readPin(3, ON_RISING); // Reads the digital value of pin 3, considering ON_RISING trigger mode
     */
    int readPin(int pinIndex, EQ_TrigMode trigMode = STATE);    // New method to read pin values


    /**
     * @brief Configures the PWM frequency for the Power PWM Output (POUT) mode on the EQSP32.
     * 
     * This function allows you to set the PWM frequency for the Power PWM Output (POUT) mode and all special POUT modes.
     * A change on power PWM frequency will affect all pins configured in POUT mode or any speial POUT mode.
     * The PWM frequency determines the rate at which the power is pulsed.
     * The valid frequency range is between 50 Hz and 3000 Hz.
     * The default frequency is set to 1000 Hz after EQ is initialized.
     * 
     * Affected pin modes:
     * 
     * - Power PWM Output (POUT)
     *      //Special Modes
     * - Relay (RELAY)
     * 
     * @attention All pins in POUT or special POUT mode share the same POUT frequency.
     * 
     * @param freq The PWM frequency to set for the Power PWM Output (POUT) mode. By default the frequency is set to 1000 Hz.
     * 
     * @return true if the configuration is successful, false otherwise. Returns false if the provided frequency is outside the valid range (50 Hz to 1500 Hz).
     * 
     * @note The frequency range of the power PWM output is within audible range.
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::configPOUTFreq(500); // Sets the PWM frequency for Power PWM Output (POUT) mode to 500 Hz
     */
    bool configPOUTFreq(int freq);


        // Special mode configurations
    /**
     * @brief Configures the Switch (SWT) mode on the EQSP32.
     * 
     * This function allows you to configure the Switch (SWT) mode for a specified pin on the EQSP32.
     * In SWT mode, the pin operates as a special digital input with a debouncing timer.
     * You can set the debounce time in milliseconds using the 'debounceTime_ms' parameter.
     * 
     * @param pinIndex The index of the pin to configure for Switch (SWT) mode (1 to 16).
     * @param debounceTime_ms The debounce time in milliseconds. This parameter determines the time window during which rapid changes in the pin state are filtered to prevent false triggering. Default is 100 milliseconds.
     * 
     * @return true if the configuration is successful, false otherwise. Returns false if the provided pin index is out of range (1 to 16).
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::configSWT(3, 150); // Configures pin 3 for Switch (SWT) mode with a debounce time of 150 milliseconds
     */
    bool configSWT(int pinIndex, int debounceTime_ms = 100);


    /**
     * @brief Configures the Temperature Input (TIN) mode on the EQSP32.
     * 
     * This function allows you to configure the Temperature Input (TIN) mode for a specified pin on the EQSP32.
     * In TIN mode, the pin operates as a special analog input mode designed for automatic temperature conversion.
     * You can set the beta coefficient and reference resistance values using the 'beta' and 'referenceResistance' parameters, respectively.
     * 
     * @param pinIndex The index of the pin to configure for Temperature Input (TIN) mode (1 to 8).
     * @param beta The beta coefficient value for temperature conversion. Default is 3988.
     * @param referenceResistance The reference resistance value for temperature conversion. Default is 10000.
     * 
     * @return true if the configuration is successful, false otherwise. Returns false if the provided pin index is out of range (1 to 8).
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::configTIN(2, 4000, 12000); // Configures pin 2 for Temperature Input (TIN) mode with a beta coefficient of 4000 and a reference resistance of 12000 ohms
     */
    bool configTIN(int pinIndex, int beta = 3435, int referenceResistance = 10000);


    /**
     * @brief Configures the Relay (RELAY) mode on the EQSP32.
     * 
     * This function allows you to configure the Relay (RELAY) mode for a specified pin on the EQSP32.
     * In RELAY mode, the pin operates as a special Power PWM Output (POUT) mode designed for relay control.
     * You can set the hold value and derate delay using the 'holdValue' and 'derateDelay' parameters, respectively.
     * When operating in RELAY mode, the initial power is set by 'pinValue' function and when the configured derateDelay
     * has passed, the power will automatically drop to the specified holValue.
     * 
     * @param pinIndex The index of the pin to configure for Relay (RELAY) mode (1 to 16).
     * @param holdValue The hold value for relay control. This is the power level to maintain after the derate delay. Default is 500 meaning 50% of max power.
     * @param derateDelay The derate delay in milliseconds. This is the time delay before dropping to the holding power level. Default is 1000 milliseconds.
     * 
     * @return true if the configuration is successful, false otherwise. Returns false if the provided pin index is out of range (1 to 16).
     * 
     * @example
     * Usage example:
     * bool success = EQSP32::configRELAY(4, 300, 1500); // Configures pin 4 for Relay (RELAY) mode with a hold value of 300 and a derate delay of 1500 milliseconds
     */    
    bool configRELAY(int pinIndex, int holdValue = 500, int derateDelay = 1000);


    /**
     * @brief Configures a terminal pin for PCC (Pulse Capture Count) mode using one of the four available hardware pulse counters.
     * 
     * PCC mode allows counting input pulses on selected EQSP32 terminal pins (9–16) using hardware-level pulse counters.
     * This function sets up the specified pin to count rising, falling, or both edges, depending on the selected mode.
     * 
     * - Only terminal pins `9 through 16` support PCC mode.
     * - A maximum of `four` of these pins can be used simultaneously due to hardware pulse counter limitations.
     *   If you attempt to configure a fifth pin, the function will return `false`.
     * 
     * If `configPCC()` is called for a pin that was not already set in PCC mode, the mode will be automatically assigned internally
     * if allowed.
     * 
     * Supported trigger modes:
     * - `ON_RISING`: counts on rising edges only (this is the default).
     * - `ON_FALLING`: counts on falling edges only.
     * - `ON_TOGGLE`: counts on both rising and falling edges.
     * 
     * Any unsupported or invalid mode will cause the function to return `false`.
     * 
     * @param pinIndex Terminal pin index to configure (must be between 9 and 16).
     * @param pulseCaptureMode Trigger mode: `ON_RISING` by default.
     * @return `true` if the pin was successfully configured; `false` otherwise.
     * 
     * @example
     * eqsp32.configPCC(12);                  // Uses default ON_RISING
     * eqsp32.configPCC(14, ON_FALLING);      // Falling edge counting
     * eqsp32.configPCC(16, ON_TOGGLE);       // Both rising and falling edges
     */
    bool configPCC(int pinIndex, EQ_TrigMode pulseCaptureMode = ON_RISING);

    
        // Buzzer
    /**
     * @brief Activates the buzzer with a specified frequency and optional duration.
     * 
     * This function turns on the buzzer on the EQSP32 module at a specified frequency within a valid range. 
     * The frequency can be set between 50 Hz and 20 KHz. If the frequency is outside this range, the 
     * buzzer will not be activated. Additionally, a duration can be specified for how long the buzzer 
     * will remain on. If no duration is specified (default is 0), the buzzer will remain on until the 
     * `buzzerOff()` function is called.
     * 
     * @param freq The frequency in Hertz (Hz) at which to operate the buzzer. The valid range is 50 Hz 
     *        to 20 KHz. The default frequency is 500 Hz.
     * @param duration_ms The duration in milliseconds for which the buzzer should remain on. If set to 0 
     *        (or not specified), the buzzer will remain on indefinitely until `buzzerOff()` is called. 
     *        The default value is 0.
     * 
     * @note If the frequency is outside the valid range of 50 Hz-20 KHz, the buzzer will not be activated.
     * 
     * @example
     * Usage example:
     * EQSP32 eqsp32;
     * eqsp32.buzzerOn(); // Activates the buzzer at the default frequency of 500 Hz, stays on indefinitely
     * delay(200);
     * eqsp32.buzzerOn(1000, 500); // Activates the buzzer at 1000 Hz for 500 ms after 200 ms delay
     * delay(600);
     * eqsp32.buzzerOff(); // Deactivates the buzzer (in this case, no effect since the buzzer turned off after 500 ms)
     */
    void buzzerOn(uint32_t freq = 500, uint32_t duration_ms = 0);

    /**
     * @brief Turns off the buzzer on the EQSP32 module.
     * 
     * This function stops the buzzer and ensures it is no longer emitting any sound. 
     * It should be used to turn off the buzzer after it has been activated with `buzzerOn()`, 
     * especially if no duration was specified or if the buzzer needs to be turned off early.
     * 
     * @example
     * Usage example:
     * EQSP32 eqsp32;
     * eqsp32.buzzerOn(1000, 0);  // Turns on the buzzer at 1000 Hz, remains on indefinitely
     * delay(200);
     * eqsp32.buzzerOff();        // Turns off the buzzer after 200 ms
     */
    void buzzerOff();


    /**
     * @brief Turns on the BLE communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually turns on the BLE status LED, but only if the `disableErqosIoT` flag
     * in the `EQSP32Configs` structure is set to `true`. When this flag is enabled, EQSP32's
     * internal background tasks will not control the BLE or Wi-Fi LEDs, allowing the developer
     * to reflect connection states or events manually.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing BLE LED state).
     * - The LED remains on until changed via `resetBleLed()` or `toggleBleLed()`.
     * 
     * @return `true` if the LED was successfully turned on; `false` otherwise.
     * 
     * @example
     * eqsp32.setBleLed();  // Manually turns on the BLE LED
     */
    bool setBleLed();


    /**
     * @brief Turns off the BLE communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually turns off the BLE status LED, but only if the `disableErqosIoT` flag
     * in the `EQSP32Configs` structure is set to `true`. When this flag is enabled, EQSP32's
     * internal background tasks will not control the BLE or Wi-Fi LEDs, allowing the developer
     * to reflect connection states or events manually.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing BLE LED state).
     * - The LED remains off until changed via `setBleLed()` or `toggleBleLed()`.
     * 
     * @return `true` if the LED was successfully turned off; `false` otherwise.
     * 
     * @example
     * eqsp32.resetBleLed();  // Manually turns off the BLE LED
     */
    bool resetBleLed();


    /**
     * @brief Toggles the current state of the BLE communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually toggles the BLE LED — turning it ON if it was OFF, or OFF if it was ON —
     * but only if the `disableErqosIoT` flag in the `EQSP32Configs` structure is set to `true`.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing BLE LED state).
     * - This is a single toggle action; it does not cause blinking or continuous toggling.
     * - The resulting LED state remains until changed by another `toggleBleLed()` or other LED control function.
     * 
     * @return `true` if the LED state was successfully toggled; `false` otherwise.
     * 
     * @example
     * eqsp32.toggleBleLed();  // Inverts BLE LED state (ON → OFF or OFF → ON)
     */
    bool toggleBleLed();


    /**
     * @brief Turns on the Wi-Fi communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually turns on the Wi-Fi status LED, but only if the `disableErqosIoT` flag
     * in the `EQSP32Configs` structure is set to `true`. When this flag is enabled, EQSP32's
     * internal background tasks will not control the BLE or Wi-Fi LEDs, allowing the developer
     * to reflect connection states or events manually.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing Wi-Fi LED state).
     * - The LED remains on until changed via `resetWifiLed()` or `toggleWifiLed()`.
     * 
     * @return `true` if the LED was successfully turned on; `false` otherwise.
     * 
     * @example
     * eqsp32.setWifiLed();  // Manually turns on the Wi-Fi LED
     */
    bool setWifiLed();


    /**
     * @brief Turns off the Wi-Fi communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually turns off the Wi-Fi status LED, but only if the `disableErqosIoT` flag
     * in the `EQSP32Configs` structure is set to `true`. When this flag is enabled, EQSP32's
     * internal background tasks will not control the BLE or Wi-Fi LEDs, allowing the developer
     * to reflect connection states or events manually.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing Wi-Fi LED state).
     * - The LED remains off until changed via `setWifiLed()` or `toggleWifiLed()`.
     * 
     * @return `true` if the LED was successfully turned off; `false` otherwise.
     * 
     * @example
     * eqsp32.resetWifiLed();  // Manually turns off the Wi-Fi LED
     */
    bool resetWifiLed();


    /**
     * @brief Toggles the current state of the Wi-Fi communication LED if EQSP32 is not managing it internally.
     * 
     * This function manually toggles the Wi-Fi LED — turning it ON if it was OFF, or OFF if it was ON —
     * but only if the `disableErqosIoT` flag in the `EQSP32Configs` structure is set to `true`.
     * 
     * - Has no effect if `disableErqosIoT` is `false` (i.e., EQSP32 is actively managing Wi-Fi LED state).
     * - This is a single toggle action; it does not cause blinking or continuous toggling.
     * - The resulting LED state remains until changed by another `toggleWifiLed()` or other LED control function.
     * 
     * @return `true` if the LED state was successfully toggled; `false` otherwise.
     * 
     * @example
     * eqsp32.toggleWifiLed();  // Inverts Wi-Fi LED state (ON → OFF or OFF → ON)
     */
    bool toggleWifiLed();


        // Power sensing
    /**
     * @brief Reads the input voltage supplied to the EQSP32 module in millivolts.
     * 
     * This function retrieves the current input voltage level supplied to the EQSP32 module and returns the value in millivolts (mV).
     * Monitoring the input voltage is crucial for understanding the power supply status and ensuring the module is operating within safe voltage limits.
     * 
     * @return The input voltage level supplied to the EQSP32 module, measured in millivolts (mV).
     *
     * @example
     * EQSP32 eqsp32;
     * int inputVoltage = eqsp32.readInputVoltage();
     */
    int readInputVoltage();


    /**
     * @brief Reads the output voltage provided by the EQSP32 module in millivolts.
     * 
     * This function measures and returns the current output voltage level being provided by the EQSP32 module. 
     * The returned value is in millivolts (mV). This measurement is essential for monitoring the output power 
     * status and ensuring that the module is delivering the correct voltage to connected devices or systems.
     * The 5V output is also used as referenece voltage for TIN mode.
     * 
     * @return The output voltage level being provided by the EQSP32 module, measured in millivolts (mV).
     * 
     * @example
     * EQSP32 eqsp32;
     * int outputVoltage = eqsp32.readOutputVoltage();
     */
    int readOutputVoltage();


        // RS232/RS485 serial communication
    /**
     * @brief Configures the serial communication mode and baud rate for the EQSP32 module.
     * 
     * This function sets up the EQSP32's serial communication according to the specified mode and baud rate.
     * It supports RS232, RS485 Transmit (RS485_TX), and RS485 Receive (RS485_RX) modes. The function initializes 
     * the serial communication with the new baud rate if it differs from the previous one, and configures 
     * the transmission and reception control pins accordingly. The default mode is RS232, and the default baud rate is 115200.
     * 
     * @param mode The serial communication mode to set. Available options are RS232, RS485_TX, and RS485_RX. 
     *        The default mode is RS232.
     * @param baud The baud rate for serial communication. If the baud rate is different from the previous setting, 
     *        the function reinitializes the serial communication with the new baud rate. The default baud rate is 115200.
     * 
     * @return true if the configuration is successful and the mode is valid, false otherwise.
     * 
     * @attention If no value is provided for baud, it is assumed to be 115200. So if it is needed to use for example RS485
     *          in transmit at 9600 and then switch to receive 9600, at both times 9600 should be set in cofigSerial()
     * 
     * @example
     * Usage example:
     * EQSP32 eqsp32;
     * bool isConfigured = eqsp32.configSerial(); // Configures the module for RS232 mode at 115200 baud (default values)
     * bool isConfiguredCustom = eqsp32.configSerial(RS485_TX, 9600); // Configures the module for RS485 Transmit mode at 9600 baud
     */
    bool configSerial(EQSerialMode mode = RS232, int baud = 115200);


        // CAN-Bus communication
    /**
     * @brief Configures the CAN bus interface on the EQSP32.
     * 
     * This function sets up the CAN bus peripheral using the specified bit rate,
     * with optional filtering based on the provided CAN ID and loopback operation.
     * It supports both normal and self-receiving (loopback) modes and allows you
     * to filter incoming messages to only those matching a specific 11-bit standard CAN identifier.
     * 
     * - If `canID` is set to `0` (default), the EQSP32 will accept all incoming CAN messages.
     * - If a specific `canID` is provided, only messages with that exact 11-bit ID will be received.
     * - If `loopBack` is set to `true` (default is `false`), the controller enters self-test mode
     *   and will also receive its own transmitted messages (useful for testing and development).
     * 
     * The CAN interface will be initialized at the specified bitrate using the internal TWAI controller.
     * 
     * @param CAN_BITRATE The bit rate to initialize the CAN interface with (e.g., CAN_500K).
     * @param canID The 11-bit identifier to filter incoming messages. Default is 0 (accept all).
     * @param loopBack Enable loopback mode for internal testing. Default is false (normal operation).
     * 
     * @return true if the configuration is successful, false otherwise.
     * 
     * @example
     * Usage example:
     * eqsp32.configCAN(CAN_500K);                        // Accept all messages
     * eqsp32.configCAN(CAN_125K, 0x120);                 // Accept only messages with ID 0x120
     * eqsp32.configCAN(CAN_250K, 0x000, true);           // Enable loopback (self-receive)
     */
    bool configCAN(CanBitRates CAN_BITRATE, uint32_t canID = 0, bool loopBack = false);


    /**
     * @brief Configures the CAN bus to filter messages by Node ID only (CANopen-style).
     * 
     * This setup accepts all Function Codes for the specified Node ID,
     * making it ideal for CANopen-style or other node-addressed protocols.
     * 
     * @note Broadcast messages like NMT (0x000) or SYNC (0x080) are NOT received,
     * since they use Node ID 0 and are outside this filter.
     * For accepting broadcast messages like NMT, configCAN() should be used to accept ALL IDs and implement software fitlering
     * for function codes, NMT (0x000), SYNC (0x080), etc.
     * 
     * This filter always runs in normal mode (for loopback testing use configCAN()).
     * 
     * @param CAN_BITRATE The bit rate to initialize the CAN interface with (e.g., CAN_500K).
     * @param nodeID The Node ID to filter for (1–127).
     * 
     * @return true if configuration is successful and node ID within valid range, false otherwise.
     * 
     * @example
     * eqsp32.configCANNode(CAN_500K, 0x21);    // Accepts all PDOs, SDOs, EMCY for Node ID 33 (0x21).
     */
    bool configCANNode(CanBitRates CAN_BITRATE, uint8_t nodeID);


    /**
     * @brief Transmits a CAN message using the EQSP32 CAN interface.
     * 
     * This function sends a standard 11-bit CAN message using the built-in TWAI controller.
     * The message structure must be pre-filled with an identifier, data payload, and data length.
     * Transmission is automatically handled, including support for loopback mode if enabled.
     * 
     * The function is non-blocking—if the CAN bus is not ready or the transmit queue is full,
     * it returns immediately with `false`.
     * 
     * @param canMessage A `CanMessage` struct containing the CAN ID, data bytes, and data length.
     *                   Extended and remote frames are not supported with this transmission method.
     * 
     * @return true if the message was successfully queued for transmission, false otherwise.
     * 
     * @example
     * CanMessage msg = {};
     * msg.identifier = 0x120;
     * msg.data_length_code = 3;
     * msg.data[0] = 0xAA;
     * msg.data[1] = 0xBB;
     * msg.data[2] = 0xCC;
     * 
     * bool success = eqsp32.transmitCANFrame(msg);  // Transmit the CAN message
     */
    bool transmitCANFrame(CanMessage canMessage);


    /**
     * @brief Receives a CAN message using the EQSP32 CAN interface.
     * 
     * This function attempts to retrieve a standard 11-bit CAN frame from the TWAI receive queue.
     * It operates in a non-blocking manner; if no message is available, the function returns immediately with `false`.
     * 
     * The received data, including the identifier, payload, and length, is stored in the provided `CanMessage` structure.
     * 
     * @param canMessage A reference to a `CanMessage` struct where the received data will be stored.
     * 
     * @return true if a message was successfully received and stored in the struct, false otherwise.
     * 
     * @example
     * CanMessage msg;
     * if (eqsp32.receiveCANFrame(msg)) {
     *     Serial.printf("Received CAN frame: ID=0x%03X DLC=%d\n", msg.identifier, msg.data_length_code);
     * }
     */
    bool receiveCANFrame(CanMessage &canMessage);


    /**
     * @brief Retrieves the current Wi-Fi connection status of the EQSP32 module.
     * 
     * This function returns the current Wi-Fi status, indicating whether the EQSP32 is connected, disconnected,
     * reconnecting, or scanning for available networks. The returned value corresponds to the `EQ_WifiStatus` enum.
     * 
     * The possible return values are:
     * - EQ_WF_DISCONNECTED (0): Not connected to any Wi-Fi network.
     * - EQ_WF_CONNECTED (1): Successfully connected to a Wi-Fi network.
     * - EQ_WF_RECONNECTING (2): Attempting to reconnect after disconnection.
     * - EQ_WF_SCANNING (3): Scanning for available networks.
     * 
     * @return The current Wi-Fi status as an `EQ_WifiStatus` enum.
     * 
     * @example
     * EQSP32 eqsp32;
     * EQ_WifiStatus wifiStatus = eqsp32.getWiFiStatus();
     * if (wifiStatus == EQ_WF_CONNECTED) {
     *     Serial.println("Wi-Fi is connected.");
     * } else {
     *     Serial.println("Wi-Fi is not connected.");
     * }
     */
    EQ_WifiStatus getWiFiStatus();


    /**
     * @brief Retrieves the current Ethernet connection status of the EQSP32 module.
     * 
     * This function returns the current Ethernet status of the EQSP32, indicating whether a cable is plugged in,
     * whether the Ethernet connection is established and online, or if the Ethernet interface has been stopped.
     * The returned value corresponds to the `EQ_EthernetStatus` enum.
     * 
     * The possible return values are:
     * - EQ_ETH_DISCONNECTED (0): No Ethernet cable detected or no link.
     * - EQ_ETH_CONNECTED (1): Ethernet is connected and online.
     * - EQ_ETH_PLUGGED_IN (2): Cable is detected, but connection is not yet online (e.g., IP not acquired).
     * - EQ_ETH_STOPPED (3): Ethernet interface is stopped, disabled or not detected (might be non-ethernet EQSO32 version).
     * 
     * @return The current Ethernet status as an `EQ_EthernetStatus` enum.
     * 
     * @example
     * EQSP32 eqsp32;
     * EQ_EthernetStatus ethStatus = eqsp32.getEthernetStatus();
     * if (ethStatus == EQ_ETH_CONNECTED) {
     *     Serial.println("Ethernet is online.");
     * } else {
     *     Serial.println("Ethernet is not connected or not ready.");
     * }
     */
    EQ_EthernetStatus getEthernetStatus();


    /**
     * @brief Returns the current IP address of the EQSP32 device.
     *
     * If the device is not online (neither Ethernet nor Wi-Fi connected), the function returns "0.0.0.0".
     * If the device is online, it returns the IP address of the active interface:
     * - Ethernet IP address if Ethernet is connected.
     * - Wi-Fi IP address if Wi-Fi is connected and Ethernet is not.
     *
     * @return String representing the device's current IP address, or "0.0.0.0" if offline.
     */
    String localIP();


    /**
     * @brief Returns the current Ethernet IP address of the EQSP32 device.
     *
     * If the device is connected to Ethernet, this function returns its assigned
     * Ethernet IP address as a String.
     * 
     * If Ethernet is not connected, the function returns "0.0.0.0", even if the device
     * is online via Wi-Fi.
     *
     * @return String representing the Ethernet IP address, or "0.0.0.0" if Ethernet is not connected.
     */
    String ethernetIP();


    /**
     * @brief Returns the current Wi-Fi IP address of the EQSP32 device.
     *
     * If the device is connected to Wi-Fi, this function returns its assigned
     * Wi-Fi IP address as a String.
     * 
     * If Wi-Fi is not connected, the function returns "0.0.0.0", even if the device
     * is online via Ethernet.
     *
     * @return String representing the Wi-Fi IP address, or "0.0.0.0" if Wi-Fi is not connected.
     */
    String wifiIP();


    /**
     * @brief Checks if the EQSP32 device is currently online via either Wi-Fi or Ethernet.
     * 
     * This function determines whether the EQSP32 module is currently connected to a network.
     * It returns true if either the Wi-Fi interface is connected (`EQ_WF_CONNECTED`) or the Ethernet interface
     * is connected and online (`EQ_ETH_CONNECTED`). If neither interface is connected, it returns false.
     * 
     * This is useful for checking network availability before attempting operations that require
     * internet access.
     * 
     * @return true if either Wi-Fi or Ethernet is connected and online, false otherwise.
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isDeviceOnline()) {
     *     Serial.println("Device is online.");
     * } else {
     *     Serial.println("Device is offline.");
     * }
     */
    bool isDeviceOnline();


    /**
     * @brief Retrieves the user-defined name of the EQSP32 device.
     * 
     * This function returns the current name assigned to the EQSP32 device by the end user.
     * The name is stored persistently and used across various services and interfaces for
     * identification and presentation.
     * 
     * The device name is internally applied in:
     * - BLE advertising and identification (takes effect only after reboot)
     * - Web server hostname (access via `http://DeviceName.home:8000`, where spaces from DeviceName are replaced with dashes and special characters are removed)
     * - Arduino OTA (used as the OTA target name)
     * - Home Assistant auto-discovery (used as the device name in the discovered MQTT device)
     * 
     * @note This name is editable by the end user at any time from EQConnect.
     * 
     * @return The user-defined device name as an Arduino `String`.
     * 
     * @example
     * EQSP32 eqsp32;
     * String name = eqsp32.getDeviceName();
     * Serial.print("Device Name: ");
     * Serial.println(name);
     */
    String getDeviceName();


    /**
     * @brief Retrieves the unique device ID assigned by the system.
     * 
     * This function returns a persistent, globally unique identifier for the EQSP32 device,
     * formatted as `EQSP32_XXXXXX`, where `XXXXXX` represents the last 3 bytes of the MAC address
     * in uppercase hexadecimal format (e.g., `EQSP32_A1B2C3`).
     * 
     * This ID is automatically generated and cannot be changed by the user. It is guaranteed
     * to remain constant across reboots.
     * It may be used by the developer as a unique identifier and
     * it is internally used in the MQTT topic generation structure.
     * 
     * @return The system-generated unique device ID as an Arduino `String`.
     * 
     * @example
     * EQSP32 eqsp32;
     * String id = eqsp32.getDeviceID();
     * Serial.print("Device ID: ");
     * Serial.println(id); // Output: EQSP32_A1B2C3 if device WiFi MAC ends in A1B2C3
     */
    String getDeviceID();



    /**
     * @brief Prints the current local time to the serial output in a human-readable format.
     * 
     * This function retrieves the current local time using the configured NTP server or default system time if the NTP is not synchronized. 
     * If the local time cannot be obtained, it logs an error message. Otherwise, it prints the time in the format:
     * "Day, Month Date Year Hour:Minute:Second".
     * 
     * @note The function uses a timeout to retrieve the local time. If synchronization fails within this time, 
     *       the function logs an error and exits.
     * 
     * @attention Ensure that the NTP server has been successfully configured and the system time is synchronized to get an accurate timestamp.
     * 
     * @example
     * EQSP32 eqsp32;
     * eqsp32.printLocalTime(); // Prints the current local time or logs an error if unavailable.
     */
    void printLocalTime();


    /**
     * @brief Checks if the local time is synchronized with the NTP server.
     * 
     * This function verifies whether the local system time has been synchronized with the NTP server
     * or if only the default system time is available.
     * 
     * @return true if the local time is synchronized with the NTP server, false only default time is available.
     * 
     * @attention This function is helpful to determine whether the timestamps used for logging and operations are accurate.
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     // Proceed with time-dependent operations
     * } else {
     *     // Handle unsynchronized time
     * }
     */
    bool isLocalTimeSynced();

    
    /**
     * @brief Retrieves the current weekday from the local time.
     * 
     * This function returns the current day of the week based on the local system time. 
     * If the local time cannot be obtained within the timeout, the function logs an error and returns EQ_SUNDAY.
     * 
     * The return value corresponds to the EQ_WeekDay enum, where:
     * - EQ_SUNDAY = 0
     * - EQ_MONDAY = 1
     * - EQ_TUESDAY = 2
     * - EQ_WEDNESDAY = 3
     * - EQ_THURSDAY = 4
     * - EQ_FRIDAY = 5
     * - EQ_SATURDAY = 6
     * 
     * @return The current local weekday as an EQ_WeekDay enum. If unsuccessful, returns EQ_SUNDAY.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * EQ_WeekDay weekday = eqsp32.getLocalWeekDay();
     * ::Serial.printf("Current Weekday (Numeric): %d\n", weekday);
     * 
     * @example
     * // Check if today is Monday
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     if (eqsp32.getLocalWeekDay() == EQ_MONDAY) {
     *         ::Serial.println("Today is Monday!");
     *     } else {
     *         ::Serial.println("It is not Monday.");
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual weekday.");
     * }
     */
    EQ_WeekDay getLocalWeekDay();


    /**
     * @brief Retrieves the current day of the year from the local time.
     * 
     * This function returns the current day of the year (1-366) based on the local system time.
     * If the local time cannot be obtained within the timeout, the function logs an error and returns 0.
     * 
     * The returned value represents the day count from the start of the year:
     * - January 1st returns 1
     * - December 31st returns 365 (or 366 in a leap year)
     * 
     * @return The current local day of the year (1-366). If unsuccessful, returns 0.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int yearDay = eqsp32.getLocalYearDay();
     *     ::Serial.printf("Today is the %dth day of the year.\n", yearDay);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual day of the year.");
     * }
     * 
     * @example
     * // Check if today is the first day of the year
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     if (eqsp32.getLocalYearDay() == 1) {
     *         ::Serial.println("Happy New Year!");
     *     } else {
     *         ::Serial.printf("We are on day %d of the year.\n", eqsp32.getLocalYearDay());
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual day of the year.");
     * }
     */
    int getLocalYearDay();


    /**
     * @brief Retrieves the current year from the local time.
     * 
     * This function returns the current year based on the local system time.
     * If the local time cannot be obtained within the timeout, the function logs an error and returns 0.
     * 
     * The returned value is the full four-digit year (e.g., 2024).
     * 
     * @return The current local year (e.g., 2024). If unsuccessful, returns 0.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int year = eqsp32.getLocalYear();
     *     ::Serial.printf("Current Year: %d\n", year);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual year.");
     * }
     * 
     * @example
     * // Check if the current year is valid and synchronized
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int year = eqsp32.getLocalYear();
     *     if (year > 2000) {
     *         ::Serial.printf("The current year is: %d\n", year);
     *     } else {
     *         ::Serial.println("Invalid year, time may not be correctly set.");
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual year.");
     * }
     */
    int getLocalYear();


    /**
     * @brief Retrieves the current month from the local time.
     * 
     * This function returns the current month (1-12) based on the local system time.
     * If the local time cannot be obtained within the timeout, the function logs an error and returns 0.
     * 
     * The returned value represents the month as:
     * - January returns 1
     * - February returns 2
     * - ...
     * - December returns 12
     * 
     * @return The current local month (1-12). If unsuccessful, returns 0.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int month = eqsp32.getLocalMonth();
     *     ::Serial.printf("Current Month: %d\n", month);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual month.");
     * }
     * 
     * @example
     * // Check if it is December
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     if (eqsp32.getLocalMonth() == 12) {
     *         ::Serial.println("It is December!");
     *     } else {
     *         ::Serial.printf("The current month is: %d\n", eqsp32.getLocalMonth());
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual month.");
     * }
     */
    int getLocalMonth();


    /**
     * @brief Retrieves the current day of the month from the local time.
     * 
     * This function returns the current day of the month (1-31) based on the local system time.
     * If the local time cannot be obtained within the timeout, the function logs an error and returns 0.
     * 
     * The returned value represents the specific day in the current month:
     * - The first day of the month returns 1
     * - The last day of the month (varies from 28 to 31) returns the respective value
     * 
     * @return The current local day of the month (1-31). If unsuccessful, returns 0.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int day = eqsp32.getLocalMonthDay();
     *     ::Serial.printf("Current Day of the Month: %d\n", day);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual day of the month.");
     * }
     * 
     * @example
     * // Check if today is the first of the month
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     if (eqsp32.getLocalMonthDay() == 1) {
     *         ::Serial.println("Today is the first day of the month!");
     *     } else {
     *         ::Serial.printf("We are on day %d of the month.\n", eqsp32.getLocalMonthDay());
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual day of the month.");
     * }
     */
    int getLocalMonthDay();


    /**
     * @brief Retrieves the current hour from the local time.
     * 
     * This function returns the current hour (0-23) based on the local system time. If the local time cannot be 
     * obtained within the timeout, the function logs an error and returns 0.
     * 
     * @return The current local hour (0-23) if successful, 0 otherwise.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * int hour = eqsp32.getLocalHour();
     * ::Serial.printf("Current Hour: %d\n", hour);
     * 
     * @example
     * // Check synchronization first
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int hour = eqsp32.getLocalHour();
     *     ::Serial.printf("Current Hour: %d\n", hour);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual local hour.");
     * }
     */
    int getLocalHour();


    /**
     * @brief Retrieves the current minutes from the local time.
     * 
     * This function returns the current minutes (0-59) based on the local system time. If the local time cannot be 
     * obtained within the timeout, the function logs an error and returns 0.
     * 
     * @return The current local minutes (0-59) if successful, 0 otherwise.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * int mins = eqsp32.getLocalMins();
     * ::Serial.printf("Current Minutes: %d\n", mins);
     * 
     * @example
     * // Check synchronization first
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int mins = eqsp32.getLocalMins();
     *     ::Serial.printf("Current Minutes: %d\n", mins);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual local minutes.");
     * }
     */
    int getLocalMins();


    /**
     * @brief Retrieves the current seconds from the local time.
     * 
     * This function returns the current seconds (0-59) based on the local system time.
     * If the local time cannot be obtained within the timeout, the function logs an error and returns 0.
     * 
     * The returned value represents the current second count within the minute:
     * - The start of a new minute returns 0
     * - The last second before the next minute returns 59
     * 
     * @return The current local seconds (0-59). If unsuccessful, returns 0.
     * 
     * @attention This function will return the default time if isLocalTimeSynced() is false!
     * 
     * @example
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     int seconds = eqsp32.getLocalSecs();
     *     ::Serial.printf("Current Seconds: %d\n", seconds);
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual seconds.");
     * }
     * 
     * @example
     * // Check if we are at the start of a new minute
     * EQSP32 eqsp32;
     * if (eqsp32.isLocalTimeSynced()) {
     *     if (eqsp32.getLocalSecs() == 0) {
     *         ::Serial.println("A new minute has started!");
     *     } else {
     *         ::Serial.printf("Current second: %d\n", eqsp32.getLocalSecs());
     *     }
     * } else {
     *     ::Serial.println("Local time is not synchronized. Unable to fetch actual seconds.");
     * }
     */
    int getLocalSecs();

    /**
     * @brief Retrieves the number of full days the EQSP32 has been running since power-up.
     * 
     * This function returns the number of whole days (24-hour periods) that have passed
     * since the EQSP32 was last powered on or reset.
     * 
     * It is based on the internal uptime counter and is useful for monitoring
     * system runtime, debugging, and diagnostics.
     * 
     * @return The uptime in full days as an `int`.
     * 
     * @example
     * EQSP32 eqsp32;
     * int days = eqsp32.getUptimeDays();
     * Serial.print("Uptime (days): ");
     * Serial.println(days);
     */
    int getUptimeDays();


    /**
     * @brief Retrieves the current hour value (0–23) from the uptime counter.
     * 
     * This function returns the number of full hours that have passed
     * since the last full 24-hour period (i.e., the hours within the current uptime day).
     * 
     * For example, if the device has been running for 1 day and 5 hours,
     * this function will return `5`.
     * 
     * It is useful for building a full uptime breakdown in days, hours, minutes, and seconds.
     * 
     * @return The number of uptime hours within the current day (0–23).
     * 
     * @example
     * EQSP32 eqsp32;
     * int h = eqsp32.getUptimeHours();
     * Serial.print("Hours today: ");
     * Serial.println(h);
     */
    int getUptimeHours();


    /**
     * @brief Retrieves the current minute value (0–59) from the uptime counter.
     * 
     * This function returns the number of full minutes that have passed
     * since the last full hour of uptime (i.e., the minutes within the current uptime hour).
     * 
     * For example, if the device has been running for 1 day, 5 hours, and 42 minutes,
     * this function will return `42`.
     * 
     * It is useful for building a complete uptime display in days, hours, minutes, and seconds.
     * 
     * @return The number of uptime minutes within the current hour (0–59).
     * 
     * @example
     * EQSP32 eqsp32;
     * int m = eqsp32.getUptimeMinutes();
     * Serial.print("Minutes this hour: ");
     * Serial.println(m);
     */
    int getUptimeMinutes();


    /**
     * @brief Retrieves the current second value (0–59) from the uptime counter.
     * 
     * This function returns the number of full seconds that have passed
     * since the last full minute of uptime (i.e., the seconds within the current uptime minute).
     * 
     * For example, if the device has been running for 1 day, 5 hours, 42 minutes, and 17 seconds,
     * this function will return `17`.
     * 
     * It is useful for building a complete uptime display in days, hours, minutes, and seconds.
     * 
     * @return The number of uptime seconds within the current minute (0–59).
     * 
     * @example
     * EQSP32 eqsp32;
     * int s = eqsp32.getUptimeSeconds();
     * Serial.print("Seconds this minute: ");
     * Serial.println(s);
     */
    int getUptimeSeconds();


    /**
     * @brief Prints the system uptime in a human-readable format.
     * 
     * This function retrieves the number of days, hours, minutes, and seconds
     * the EQSP32 has been running since power-up, and prints it to the default `Serial` output.
     * 
     * Format:
     * 
     *     Uptime: <days>d <hours>h <minutes>m <seconds>s
     * 
     * Example output:
     * 
     *     Uptime: 3d 12h 45m 8s
     * 
     * Useful for quick debugging, logging, or diagnostics from the serial console.
     * 
     * @note Output is printed to the global `Serial` object
     * 
     * @example
     * EQSP32 eqsp32;
     * eqsp32.printUptime();
     */
    void printUptime();


    /**
     * @brief Retrieves the current local time as a Unix timestamp.
     * 
     * This function returns the current local time (with timezone offset applied) as a Unix timestamp,
     * representing the number of seconds that have passed since January 1, 1970 (UTC).
     * 
     * It uses the EQSP32's internal timekeeping system, which is typically synchronized via NTP
     * if internet access is available. If the time is not synchronized, the returned value may be inaccurate
     * and based on the device's unsynchronized internal clock or default time after reboot.
     * 
     * This is useful for timestamping events, logging, scheduling tasks, or synchronizing with external systems.
     * 
     * @return The local time as a Unix timestamp (`long`, in seconds since Jan 1, 1970).
     * 
     * @example
     * EQSP32 eqsp32;
     * long ts = eqsp32.getLocalUnixTimestamp();
     * Serial.print("Current local timestamp: ");
     * Serial.println(ts);
     */
    long getLocalUnixTimestamp();


    /**
     * @brief Retrieves the current local time as a formatted string.
     * 
     * This function returns the local time as a human-readable string in the format:
     * 
     *     "MM-DD-YYYY HH:MM:SS"
     * 
     * The local time includes timezone offset and is typically synchronized via NTP when internet access is available.
     * If time synchronization has not occurred, the returned time may reflect the default RTC time since power-up.
     * 
     * This function is useful for logging, display on dashboards, or sending human-readable timestamps
     * over MQTT or to external systems.
     * 
     * @return The formatted local time as a `std::string` (in the format "MM-DD-YYYY HH:MM:SS").
     * 
     * @example
     * EQSP32 eqsp32;
     * std::string timeStr = eqsp32.getFormattedLocalTime();
     * Serial.print("Current local time: ");
     * Serial.println(timeStr.c_str());
     */
    std::string getFormattedLocalTime();


    /**
     * @brief Retrieves the current system time as a Unix timestamp (UTC).
     * 
     * This function returns the current system time in UTC (Coordinated Universal Time)
     * as a Unix timestamp — the number of seconds since January 1, 1970, 00:00:00 UTC.
     * 
     * Unlike `getLocalUnixTimestamp()`, this value does not include any timezone or daylight savings offset.
     * It reflects the raw system clock time, which is typically synchronized via NTP if internet access is available.
     * 
     * If time synchronization has not yet occurred, the result will be based on the default time of the internal RTC counter since boot.
     * 
     * @return The current UTC timestamp as a `long` (seconds since Jan 1, 1970).
     * 
     * @example
     * EQSP32 eqsp32;
     * long utc = eqsp32.getUnixTimestamp();
     * Serial.print("Current UTC timestamp: ");
     * Serial.println(utc);
     */
    long getUnixTimestamp();

    /**
     * @brief Retrieves the current UTC time as a formatted string.
     * 
     * This function returns the current UTC time in a human-readable string format:
     * 
     *     "MM-DD-YYYY HH:MM:SS"
     * 
     * This is useful for logging or display purposes where timestamps need to be
     * shown in Coordinated Universal Time, unaffected by local timezone offsets.
     * 
     * If time synchronization has not occurred, the result will reflect the default system time since power-up.
     * 
     * @return The formatted UTC time as a `std::string` (in the format "MM-DD-YYYY HH:MM:SS").
     * 
     * @example
     * EQSP32 eqsp32;
     * std::string utcStr = eqsp32.getFormattedUnixTimestamp();
     * Serial.print("UTC time: ");
     * Serial.println(utcStr.c_str());
     */
    std::string getFormattedUnixTimestamp();

private:
    class EQ_Private;       // Forward declaration of the nested private class
    EQ_Private* eqPrivate;
};

#if ESP_ARDUINO_VERSION_MAJOR > 2
#define Serial Serial0
#endif

/**
 * @brief EQTimer class for managing timing operations.
 * 
 * The EQTimer class provides methods to start, stop, pause, reset, and check the status of a timer.
 * It is useful for timing events and managing delays in the EQSP32 application.
 */
class EQTimer {
public:
    /**
     * @brief Constructs a EQTimer object with an optional preset value.
     * 
     * Initializes a EQTimer object, setting an optional preset value for the timer.
     * The timer is initially stopped.
     * 
     * @param preset Optional preset value in milliseconds for the timer. Default is 0.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer(5000); // Creates a EQTimer object with a 5 second preset
     * @endcode
     */
    EQTimer(unsigned long preset = 0);

    /**
     * @brief Starts the timer with an optional preset value.
     * 
     * Starts the timer if it is not already running.
     * If a preset value is provided, it updates the timer's preset value.
     * 
     * @param preset Optional preset value in milliseconds. Default is 0.
     * @return Returns true if the timer started successfully, otherwise returns false if the timer was already running.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * bool started = myTimer.start(3000); // Starts the timer with a 3 second preset
     * if (started) {
     *     Serial.println("EQTimer started.");
     * } else {
     *     Serial.println("EQTimer was already running.");
     * }
     * @endcode
     */
    bool start(unsigned long preset = 0);

    /**
     * @brief Stops the timer.
     * 
     * Stops the timer and resets the elapsed time.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(5000); // Start the timer
     * delay(2000); // Wait for 2 seconds
     * myTimer.stop(); // Stop the timer
     * @endcode
     */ 
    void stop();

    /**
     * @brief Pauses the timer.
     * 
     * Pauses the timer, retaining the elapsed time. The timer can be resumed by calling `start()`.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(5000); // Start the timer
     * delay(2000); // Wait for 2 seconds
     * myTimer.pause(); // Pause the timer
     * delay(1000); // Wait for another second
     * myTimer.start(); // Resume the timer
     * @endcode
     */
    void pause();

    /**
     * @brief Resets the timer with an optional preset value.
     * 
     * Resets the timer and optionally updates the preset value. The timer stops and can be restarted.
     * 
     * @param preset Optional preset value in milliseconds. Default is 0.
     * @return Returns true if the timer was running and has been reset, otherwise returns false.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(5000); // Start the timer with a 5 second preset
     * delay(2000); // Wait for 2 seconds
     * bool reset = myTimer.reset(3000); // Reset the timer with a new 3 second preset
     * if (reset) {
     *     Serial.println("EQTimer reset successfully.");
     * } else {
     *     Serial.println("EQTimer was not running.");
     * }
     * @endcode
     */
    bool reset(unsigned long preset = 0);

    /**
     * @brief Retrieves the current elapsed time of the timer.
     * 
     * Returns the total elapsed time since the timer was started, including any paused periods. 
     * When the timer is paused, the elapsed time stops accumulating until the timer is resumed.
     * 
     * @return The elapsed time in milliseconds.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(10000); // Start the timer with a 10 second preset
     * delay(3000); // Run for 3 seconds
     * myTimer.pause(); // Pause the timer
     * delay(2000); // Pause for 2 seconds (does not count towards the elapsed time)
     * myTimer.start(); // Resume the timer
     * delay(3000); // Run for another 3 seconds
     * unsigned long elapsedTime = myTimer.value(); // Retrieve the elapsed time
     * Serial.print("Elapsed time: ");
     * Serial.println(elapsedTime); // Should print 6000 (milliseconds)
     * @endcode
     */
    unsigned long value();

    /**
     * @brief Checks if the timer has expired.
     * 
     * Determines if the timer has reached or exceeded its preset value without stopping or resetting the timer.
     * The timer will continue running even after it has expired.
     * 
     * @return Returns true if the timer has reached or exceeded its preset value, otherwise returns false.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(3000); // Start the timer with a 3 second preset
     * delay(3500); // Wait for 3.5 seconds
     * if (myTimer.isExpired()) {
     *     Serial.println("EQTimer has expired.");
     * } else {
     *     Serial.println("EQTimer is still running.");
     * }
     * @endcode
     */
    bool isExpired();

    /**
     * @brief Checks if the timer is currently running.
     * 
     * Determines if the timer is active and counting time. The timer will continue running even after reaching
     * its preset value, and this method will return true as long as the timer has not been stopped or paused.
     * 
     * @return Returns true if the timer is running, otherwise returns false.
     * 
     * @example
     * Usage example:
     * 
     * @code
     * EQTimer myTimer;
     * myTimer.start(5000); // Start the timer with a 5 second preset
     * delay(6000); // Wait for 6 seconds
     * if (myTimer.isRunning()) {
     *     Serial.println("EQTimer is running.");
     * } else {
     *     Serial.println("EQTimer is not running.");
     * }
     * @endcode
     */
    bool isRunning();

private:
    unsigned long startMillis;
    unsigned long presetValue;
    unsigned long elapsedTime;
};


/*  ***********************************************
     ---   ---   ---   ---   ---   ---   ---   ---
    MQTT Device Interfacing Entities for Home Assistant (Node-RED or other MQTT platforms)
     ---   ---   ---   ---   ---   ---   ---   ---
    ***********************************************   */
/**
 * 
 *      Control and Display entities for Home Assistant integration over MQTT
 *      {These entities will read/write MQTT topics which may be used in other integration like Node-RED}
 * 
 */
// Overloads for when developing for HA only
void createControl_Switch(std::string name, std::string iconType_HA = "");
void createControl_Value(std::string name, int minValue = 0, int maxValue = 1000, int decimals = 0, std::string iconType_HA = "");

bool readControl_Switch(const std::string& name);
float readControl_Value(const std::string& name);

bool updateControl_Switch(const std::string& name, bool value);
bool updateControl_Value(const std::string& name, float value);

// Overloads for when developing for HA only
// binSensorType_HA (which could be omitted) could be one of the binary sensor types listed at https://www.home-assistant.io/integrations/binary_sensor/
void createDisplay_BinarySensor(std::string name, std::string iconType_HA = "", std::string binSensorType_HA = "");
// sensorType_HA (which could be omitted) could be one of the sensor types listed at https://www.home-assistant.io/integrations/sensor/
void createDisplay_Sensor(std::string name, int decimals = 0, std::string unit = "", std::string iconType_HA = "", std::string sensorType_HA = "");

bool readDisplay_BinarySensor(const std::string& name);
float readDisplay_Sensor(const std::string& name);

bool updateDisplay_BinarySensor(const std::string& name, bool value);
bool updateDisplay_Sensor(const std::string& name, float value);



// Supported icons for `iconType` parameter (Beta EQ IoT app)
#define WATER_ICON          "water"
#define FIRE_ICON           "fire"
#define AIR_ICON            "air"
#define BUBBLES_ICON        "bubbles"
#define BATTERY_ICON        "battery"
#define MULTIMETER_ICON     "multimeter"
#define CALENDAR_ICON       "calendar"
#define PUMP_ICON           "pump"
#define SPRINKLER_ICON      "sprinkler"
#define FIRE_SPRINKLER_ICON "fire_sprinkler"
#define IRRIGATION_ICON     "irrigation"
#define DOOR_ICON           "door"
#define GARAGE_DOOR_ICON    "garage_door"
#define WINDOW_ICON         "window"
#define SHUTTER_ICON        "shutter"
#define SPEED_ICON          "speed"
#define DISTANCE_ICON       "distance"
#define ANGLE_ICON          "angle"
#define FORCE_ICON          "force"
#define TORQUE_ICON         "torque"
#define PRESSURE_ICON       "pressure"
#define TEMPERATURE_ICON    "temperature"
#define HUMIDITY_ICON       "humidity"
#define LIGHT_ICON          "light"
#define SOUND_ICON          "sound"
#define AIR_QUALITY_ICON    "air_quality"
#define ELECTRICAL_MEASUREMENTS_ICON    "electrical_measurements"
#define TIME_ICON           "time"
#define DURATION_ICON       "duration"
#define PH_ICON             "ph"
#define CO2_ICON            "co2"


/**
 * 
 *      Control entities (Same as configuration entities for Home Assistant) (Beta EQ IoT app)
 * 
 */
void createControl_Switch(std::string name, std::string accessLevel, std::string iconType, std::string iconType_HA = "");
void createControl_Value(std::string name, std::string accessLevel, std::string iconType, int minValue, int maxValue, int decimals, std::string unit, std::string iconType_HA = "");


/**
 * 
 *      Display entities (Beta EQ IoT app)
 * 
 */
void createDisplay_BinarySensor(std::string name, std::string accessLevel, std::string iconType, std::string onType, std::string iconType_HA = "", std::string binSensorType_HA = "");
void createDisplay_Sensor(std::string name, std::string accessLevel, std::string iconType, int decimals, std::string unit, std::string iconType_HA = "", std::string sensorType_HA = "");


/**
 * 
 *      Configuration entities (Same as control entities for Home Assistant) (Beta EQ IoT app)
 * 
 */
void createConfig_Switch(std::string name, std::string accessLevel, std::string iconType, std::string iconType_HA = "");
void createConfig_Value(std::string name, std::string accessLevel, std::string iconType, int minValue, int maxValue, int decimals, std::string unit, std::string iconType_HA = "");
// Overloads for when developing for HA only
void createConfig_Switch(std::string name, std::string iconType_HA = "");
void createConfig_Value(std::string name, int minValue, int maxValue, int decimals, std::string iconType_HA = "");

bool readConfig_Switch(const std::string& name);
float readConfig_Value(const std::string& name);

bool updateConfig_Switch(const std::string& name, bool value);
bool updateConfig_Value(const std::string& name, float value);

#endif