.doc_for_ai/TASMOTA_SUPPORT_DEEP_ANALYSIS.md
This document provides a comprehensive analysis of Tasmota's support infrastructure, examining the core support functions, language localization system, and include files that form the foundation of the Tasmota IoT firmware. The analysis covers 27 support files, 28 language files, and 18 include files that collectively implement the core functionality for ESP8266/ESP32-based IoT devices.
Tasmota's support infrastructure follows a modular, layered architecture designed for embedded systems with strict memory constraints. The system is built around several key principles:
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Drivers, Sensors, Automation, Web Interface) │
├─────────────────────────────────────────────────────────┤
│ Support Layer │
│ (Commands, Settings, Network, I2C, GPIO) │
├─────────────────────────────────────────────────────────┤
│ Hardware Abstraction │
│ (ESP8266/ESP32 specific implementations) │
├─────────────────────────────────────────────────────────┤
│ Platform Layer │
│ (Arduino Framework, ESP-IDF, FreeRTOS) │
└─────────────────────────────────────────────────────────┘
The support system is organized into logical modules:
support.ino, support_tasmota.ino - fundamental system functionssettings.ino - persistent configuration storagesupport_command.ino - unified command interfacesupport_wifi.ino, support_network.ino - connectivitysupport_a_i2c.ino, support_a_spi.ino - peripheral communicationsupport_esp8266.ino, support_esp32.ino - hardware abstractionsupport_rtc.ino, support_pwm.ino, etc.support.ino)The watchdog system provides critical system monitoring and recovery capabilities:
const uint32_t OSWATCH_RESET_TIME = 120; // 2 minutes timeout
static unsigned long oswatch_last_loop_time;
uint8_t oswatch_blocked_loop = 0;
void OsWatchTicker(void) {
uint32_t t = millis();
uint32_t last_run = t - oswatch_last_loop_time;
if (last_run >= (OSWATCH_RESET_TIME * 1000)) {
RtcSettings.oswatch_blocked_loop = 1;
RtcSettingsSave();
// Force exception to get stackdump
volatile uint32_t dummy;
dummy = *((uint32_t*) 0x00000000);
}
}
Key Features:
extern "C" void yield(void) {
__yield();
feedLoopWDT(); // Feed hardware watchdog
}
extern "C" void __wrap_delay(uint32_t ms) {
if (ms) { feedLoopWDT(); }
__real_delay(ms);
feedLoopWDT();
}
ESP32 Enhancements:
The system provides detailed reset reason tracking:
uint32_t ResetReason(void) {
// REASON_DEFAULT_RST = 0 - Power on
// REASON_WDT_RST = 1 - Hardware Watchdog
// REASON_EXCEPTION_RST = 2 - Exception
// REASON_SOFT_WDT_RST = 3 - Software Watchdog
// REASON_SOFT_RESTART = 4 - Software restart
// REASON_DEEP_SLEEP_AWAKE = 5 - Deep-Sleep Wake
// REASON_EXT_SYS_RST = 6 - External System
return ESP_ResetInfoReason();
}
Applications:
Advanced thread synchronization for ESP32:
class TasAutoMutex {
SemaphoreHandle_t mutex;
bool taken;
int maxWait;
const char *name;
public:
TasAutoMutex(SemaphoreHandle_t* mutex, const char *name = "",
int maxWait = 40, bool take = true);
~TasAutoMutex();
void give();
void take();
};
Features:
settings.ino)Tasmota uses RTC (Real-Time Clock) memory for persistent storage of critical system state that survives reboots but not power cycles:
typedef struct {
uint16_t valid; // Validation marker
uint8_t oswatch_blocked_loop; // Watchdog blocked loop flag
uint8_t ota_loader; // OTA loader state
uint32_t ex_energy_kWhtoday; // Energy today
uint32_t ex_energy_kWhtotal; // Total energy
volatile uint32_t pulse_counter[MAX_COUNTERS]; // Pulse counter values
power_t power; // Current relay states
EnergyUsage energy_usage; // Energy usage statistics
uint32_t nextwakeup; // Next wakeup time
uint32_t baudrate; // Serial communication speed
uint32_t ultradeepsleep; // Ultra deep sleep duration
uint16_t deepsleep_slip; // Deep sleep slip
uint8_t improv_state; // Improv state
int32_t energy_kWhtoday_ph[3]; // Daily energy per phase
int32_t energy_kWhtotal_ph[3]; // Total energy per phase
int32_t energy_kWhexport_ph[3]; // Exported energy per phase
uint32_t utc_time; // Current UTC timestamp
} TRtcSettings;
uint32_t GetRtcSettingsCrc(void) {
uint32_t crc = 0;
uint8_t *bytes = (uint8_t*)&RtcSettings;
for (uint32_t i = 0; i < sizeof(RtcSettings); i++) {
crc += bytes[i] * (i + 1); // Position-weighted checksum
}
return crc;
}
void RtcSettingsSave(void) {
if (GetRtcSettingsCrc() != rtc_settings_crc) {
// Only save if data has changed
ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RtcSettings));
rtc_settings_crc = GetRtcSettingsCrc();
}
}
Key Features:
The main settings structure is stored in flash memory and survives power cycles:
typedef struct {
unsigned long cfg_holder; // Configuration validation
unsigned long save_flag; // Save operation flag
unsigned long version; // Settings version number
unsigned short flag; // Feature flags
unsigned short save_data; // Save data interval
// Network Configuration
char sta_ssid[2][33]; // WiFi SSID (primary/backup)
char sta_pwd[2][65]; // WiFi passwords
char hostname[33]; // Device hostname
// MQTT Configuration
char mqtt_host[33]; // MQTT broker address
uint16_t mqtt_port; // MQTT broker port
char mqtt_client[33]; // MQTT client ID
char mqtt_user[33]; // MQTT username
char mqtt_pwd[33]; // MQTT password
char mqtt_topic[33]; // MQTT topic
// Hardware Configuration
uint8_t display_model; // Display type
uint8_t display_mode; // Display mode
uint16_t pwm_frequency; // PWM frequency
uint16_t pwm_value[MAX_PWMS]; // PWM channel values
// Sensor Configuration
int16_t altitude; // Altitude for pressure correction
uint16_t tele_period; // Telemetry period in seconds
// ... hundreds of additional settings
} Settings;
Tasmota includes a sophisticated settings migration system to handle firmware upgrades:
void SettingsUpdateText(uint32_t index, const char* replace_me) {
if (index < MAX_TEXTS) {
char* setting = SettingsText(index);
if (strcmp(setting, replace_me) != 0) {
strlcpy(setting, replace_me, SETTINGS_TEXT_SIZE);
}
}
}
void SettingsMigrate(void) {
if (Settings->version != VERSION) {
// Version-specific migration logic
if (Settings->version < 0x06000000) {
// Migrate from version < 6.0.0.0
// ... migration code
}
Settings->version = VERSION;
}
}
Migration Features:
Tasmota uses a multi-layered approach to configuration persistence:
void SettingsSave(uint8_t rotate) {
if (Settings->flag.save_state) {
// Immediate save for critical changes
SettingsSaveAll();
} else {
// Delayed save to reduce flash wear
Settings->save_flag++;
}
}
void SettingsBufferFree(void) {
if (settings_buffer != nullptr) {
free(settings_buffer);
settings_buffer = nullptr;
}
}
Optimization Techniques:
support_command.ino)Tasmota implements a sophisticated command system that provides unified access to all device functionality through multiple interfaces (MQTT, HTTP, Serial, WebSocket).
const char kTasmotaCommands[] PROGMEM = "|"
D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|"
D_CMND_SERIALLOG "|" D_CMND_RESTART "|" D_CMND_BACKLOG "|"
D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|"
// ... 200+ commands
;
void (* const TasmotaCommand[])(void) PROGMEM = {
&CmndUpgrade, &CmndUpgrade, &CmndOtaUrl,
&CmndSeriallog, &CmndRestart, &CmndBacklog,
&CmndDelay, &CmndPower, &CmndStatus,
// ... corresponding function pointers
};
bool ExecuteCommand(const char* cmnd, uint32_t source) {
// 1. Parse command and parameters
char* command = strtok(cmnd_buffer, " ");
char* parameters = strtok(nullptr, "");
// 2. Find command in registered tables
int command_code = GetCommandCode(command);
// 3. Execute command with proper context
if (command_code >= 0) {
XdrvMailbox.command = command;
XdrvMailbox.data = parameters;
XdrvMailbox.data_len = strlen(parameters);
XdrvMailbox.payload = atoi(parameters);
// Call registered command handler
TasmotaCommand[command_code]();
return true;
}
// 4. Try driver-specific commands
return XdrvCall(FUNC_COMMAND);
}
struct XDRVMAILBOX {
bool grpflg; // Group flag
bool usridx; // User index flag
uint16_t command_code; // Command code
uint32_t index; // Command index
uint32_t data_len; // Parameter length
int32_t payload; // Numeric parameter
char *topic; // MQTT topic
char *data; // Command parameters
char *command; // Command name
} XdrvMailbox;
Tasmota uses a sophisticated SetOption system for boolean configuration flags:
typedef union {
uint32_t data; // Raw 32-bit access
struct {
uint32_t save_state : 1; // SetOption0 - Save power state
uint32_t button_restrict : 1; // SetOption1 - Button multipress
uint32_t mqtt_add_global_info : 1; // SetOption2 - Global sensor info
uint32_t mqtt_enabled : 1; // SetOption3 - MQTT enable
// ... 28 more options in first group
};
} SOBitfield;
typedef union {
uint32_t data;
struct {
uint32_t timers_enable : 1; // SetOption50 - Timers
uint32_t user_esp8285_enable : 1; // SetOption51 - ESP8285 GPIO
// ... 32 options in second group
};
} SOBitfield3;
bool SetoptionDecode(uint32_t index, uint32_t *ptype, uint32_t *pindex) {
if (index < 178) {
if (index <= 31) { // SetOption0 .. 31 = Settings->flag
*ptype = 2;
*pindex = index; // 0 .. 31
}
else if (index <= 49) { // SetOption32 .. 49 = Settings->param
*ptype = 1;
*pindex = index -32; // 0 .. 17
}
else if (index <= 81) { // SetOption50 .. 81 = Settings->flag3
*ptype = 3;
*pindex = index -50; // 0 .. 31
}
else if (index <= 113) { // SetOption82 .. 113 = Settings->flag4
*ptype = 4;
*pindex = index -82; // 0 .. 31
}
else if (index <= 145) { // SetOption114 .. 145 = Settings->flag5
*ptype = 5;
*pindex = index -114; // 0 .. 31
}
else { // SetOption146 .. 177 = Settings->flag6
*ptype = 6;
*pindex = index -146; // 0 .. 31
}
return true;
}
return false;
}
SetOption Range: Tasmota supports SetOption0 through SetOption177, organized across 6 flag groups:
All commands use a standardized response format:
void Response_P(const char* format, ...) {
va_list args;
va_start(args, format);
vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), format, args);
va_end(args);
}
void ResponseCmndDone(void) {
Response_P(S_JSON_COMMAND_SVALUE, XdrvMailbox.command, D_JSON_DONE);
}
void ResponseCmndNumber(int value) {
Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, value);
}
void ResponseCmndChar(const char* value) {
Response_P(S_JSON_COMMAND_SVALUE, XdrvMailbox.command, value);
}
// Successful command
{"Power1":"ON"}
// Numeric response
{"Dimmer":75}
// Status response
{"Status":{"Module":1,"DeviceName":"Tasmota","FriendlyName":["Tasmota"]}}
// Error response
{"Command":"Error"}
support_wifi.ino)Tasmota implements a sophisticated WiFi management system with automatic reconnection, network scanning, and fallback mechanisms.
enum WifiConfigModes {
WIFI_RESTART, // Restart WiFi
WIFI_SMARTCONFIG, // Smart config mode
WIFI_MANAGER, // WiFi manager mode
WIFI_WPSCONFIG, // WPS configuration
WIFI_RETRY, // Retry connection
WIFI_WAIT, // Wait for connection
WIFI_SERIAL, // Serial configuration
WIFI_MANAGER_RESET_ONLY // Manager reset only
};
int WifiGetRssiAsQuality(int rssi) {
int quality = 0;
if (rssi <= -100) {
quality = 0; // No signal
} else if (rssi >= -50) {
quality = 100; // Excellent signal
} else {
quality = 2 * (rssi + 100); // Linear mapping
}
return quality;
}
void WifiBeginAfterScan(void) {
// Scan for configured networks
int8_t best_network_db = -127;
uint8_t best_network_index = 0;
for (uint32_t i = 0; i < WiFi.scanComplete(); i++) {
String ssid_scan = WiFi.SSID(i);
int32_t rssi_scan = WiFi.RSSI(i);
// Check against configured SSIDs
for (uint32_t j = 0; j < 2; j++) {
if (ssid_scan == SettingsText(SET_STASSID1 + j)) {
if (rssi_scan > best_network_db) {
best_network_db = rssi_scan;
best_network_index = j;
}
}
}
}
// Connect to best available network
WiFi.begin(SettingsText(SET_STASSID1 + best_network_index),
SettingsText(SET_STAPWD1 + best_network_index));
}
void WifiCheck(uint8_t param) {
Wifi.counter--;
switch (WiFi.status()) {
case WL_CONNECTED:
if (Wifi.config_type) {
WifiConfig(WIFI_MANAGER_RESET_ONLY);
}
break;
case WL_NO_SSID_AVAIL:
case WL_CONNECT_FAILED:
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECT_FAILED_NO_IP_ADDRESS));
Wifi.retry_init = (Wifi.retry_init == WIFI_RETRY_OFFSET_SEC) ? 0 : WIFI_RETRY_OFFSET_SEC;
if (Wifi.retry_init) {
Wifi.retry = Settings->sta_active;
WifiConfig(WIFI_RETRY);
} else {
WifiConfig(WIFI_MANAGER);
}
break;
case WL_IDLE_STATUS:
if (!Wifi.counter) {
WiFi.begin();
Wifi.counter = WIFI_CHECK_SEC;
}
break;
}
}
support_network.ino)String NetworkUniqueId(void) {
uint8_t mac[6];
WiFi.macAddress(mac);
char unique_id[13];
snprintf_P(unique_id, sizeof(unique_id), PSTR("%02X%02X%02X%02X%02X%02X"),
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(unique_id);
}
String NetworkHostname(void) {
String hostname = SettingsText(SET_HOSTNAME);
if (hostname.length() == 0) {
hostname = WIFI_HOSTNAME;
hostname.replace("%s", NetworkUniqueId().substring(6));
}
return hostname;
}
void NetworkSetDns(IPAddress dns1, IPAddress dns2) {
if (dns1.isSet()) {
WiFi.config(WiFi.localIP(), WiFi.gatewayIP(), WiFi.subnetMask(), dns1, dns2);
}
}
bool NetworkValidateIP(const char* ip_str) {
IPAddress ip;
return ip.fromString(ip_str) && (ip != IPAddress(0, 0, 0, 0));
}
support_udp.ino)bool UdpConnect(void) {
if (PortUdp.begin(Settings->udp_port)) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
udp_connected = true;
} else {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
udp_connected = false;
}
return udp_connected;
}
void UdpSendPacket(const char* packet, IPAddress ip, uint16_t port) {
if (udp_connected) {
PortUdp.beginPacket(ip, port);
PortUdp.write(packet);
PortUdp.endPacket();
}
}
void UdpDiscovery(void) {
static uint32_t udp_discovery_time = 0;
if (TasmotaGlobal.uptime > 30) { // Wait 30 seconds after boot
if (TimeReached(udp_discovery_time)) {
SetNextTimeInterval(udp_discovery_time, 11 * 60 * 1000); // Every 11 minutes
char discovery_packet[200];
snprintf_P(discovery_packet, sizeof(discovery_packet),
PSTR("{\"ip\":\"%s\",\"hn\":\"%s\",\"mac\":\"%s\",\"md\":\"%s\"}"),
WiFi.localIP().toString().c_str(),
NetworkHostname().c_str(),
NetworkUniqueId().c_str(),
ModuleName().c_str());
UdpSendPacket(discovery_packet, IPAddress(255, 255, 255, 255), UDP_DISCOVERY_PORT);
}
}
}
support_a_i2c.ino)Tasmota provides a comprehensive I2C abstraction layer supporting multiple buses and extensive device management.
struct I2Ct {
uint32_t buffer; // Communication buffer
uint32_t frequency[MAX_I2C]; // Bus frequencies
uint32_t active[MAX_I2C][4]; // Active device tracking (128 devices per bus)
} I2C;
bool I2cBegin(int sda, int scl, uint32_t bus = 0, uint32_t frequency = 100000) {
I2C.frequency[bus] = frequency;
bool result = true;
#if MAX_I2C > 1
TwoWire& myWire = (0 == bus) ? Wire : Wire1;
#else
if (bus > 0) { return false; }
TwoWire& myWire = Wire;
#endif // MAX_I2C
#ifdef ESP8266
myWire.begin(sda, scl);
myWire.setClock(frequency);
#endif // ESP8266
#ifdef ESP32
static bool reinit = false;
if (reinit) { myWire.end(); }
result = myWire.begin(sda, scl, frequency);
reinit = result;
#endif // ESP32
return result;
}
I2C Bus Support: Both ESP8266 and ESP32 support multiple I2C buses (MAX_I2C = 2 for ESP8266, SOC_HP_I2C_NUM for ESP32)
TwoWire& I2cGetWire(uint8_t bus = 0) {
if ((0 == bus) && TasmotaGlobal.i2c_enabled[0]) {
return Wire;
}
#if MAX_I2C > 1
else if ((1 == bus) && TasmotaGlobal.i2c_enabled[1]) {
return Wire1;
}
#endif
return Wire; // Fallback
}
bool I2cActive(uint8_t address, uint8_t bus = 0) {
if (address > 127) { return false; }
return bitRead(I2C.active[bus][address >> 5], address & 0x1F);
}
void I2cSetActive(uint8_t address, uint8_t count = 1, uint8_t bus = 0) {
for (uint8_t i = 0; i < count; i++) {
if ((address + i) <= 127) {
bitSet(I2C.active[bus][(address + i) >> 5], (address + i) & 0x1F);
}
}
}
void I2cSetActiveFound(uint8_t address, const char* types, uint8_t bus = 0) {
I2cSetActive(address, 1, bus);
AddLog(LOG_LEVEL_INFO, PSTR("I2C: %s found at 0x%02X"), types, address);
}
bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size, uint8_t bus = 0) {
TwoWire& myWire = I2cGetWire(bus);
myWire.beginTransmission(addr);
myWire.write(reg);
if (myWire.endTransmission()) { return false; }
myWire.requestFrom(addr, size);
return (myWire.available() == size);
}
bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg, uint8_t bus = 0) {
if (I2cValidRead(addr, reg, 1, bus)) {
*data = I2cGetWire(bus).read();
return true;
}
return false;
}
bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg, uint8_t bus = 0) {
if (I2cValidRead(addr, reg, 2, bus)) {
TwoWire& myWire = I2cGetWire(bus);
*data = (myWire.read() << 8) | myWire.read(); // Big-endian
return true;
}
return false;
}
void I2cScan(char *devs, unsigned int devs_len, uint8_t bus = 0) {
strcpy(devs, "");
uint8_t error = 0;
uint8_t address = 0;
uint8_t any = 0;
TwoWire& myWire = I2cGetWire(bus);
for (address = 1; address <= 127; address++) {
myWire.beginTransmission(address);
error = myWire.endTransmission();
if (0 == error) {
snprintf(devs, devs_len, "%s0x%02X", (any) ? "," : "", address);
devs_len -= strlen(devs);
devs += strlen(devs);
any = 1;
}
else if (4 == error) {
AddLog(LOG_LEVEL_INFO, PSTR("I2C: Unknown error at 0x%02X"), address);
}
}
}
support_a_spi.ino)#ifdef ESP8266
#define SPI_MISO_PIN 12
#define SPI_MOSI_PIN 13
#define SPI_CLK_PIN 14
#endif
#ifdef ESP32
#define SPI_MISO_PIN 19
#define SPI_MOSI_PIN 23
#define SPI_CLK_PIN 18
#endif
bool SpiBegin(int8_t sclk, int8_t miso, int8_t mosi, int8_t cs) {
if ((sclk < 0) || (mosi < 0) || (cs < 0)) {
return false;
}
#ifdef ESP32
SPI.begin(sclk, miso, mosi, cs);
#else
SPI.begin();
#endif
return true;
}
const char kGpioNames[] PROGMEM =
D_SENSOR_NONE "|"
D_SENSOR_DHT11 "|" D_SENSOR_AM2301 "|" D_SENSOR_SI7021 "|"
D_SENSOR_DS18X20 "|" D_SENSOR_I2C_SCL "|" D_SENSOR_I2C_SDA "|"
D_SENSOR_WS2812 "|" D_SENSOR_IRSEND "|" D_SENSOR_SWITCH1 "|"
// ... 200+ GPIO functions
;
const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_NONE), // Not used
AGPIO(GPIO_KEY1), // Button 1
AGPIO(GPIO_KEY1_NP), // Button 1 (no pullup)
AGPIO(GPIO_KEY1_INV), // Button 1 (inverted)
AGPIO(GPIO_KEY1_INV_NP), // Button 1 (inverted, no pullup)
AGPIO(GPIO_SWT1), // Switch 1
AGPIO(GPIO_SWT1_NP), // Switch 1 (no pullup)
// ... complete GPIO mapping
};
void GpioInit(void) {
if (!ValidTemplate(Settings->user_template.gp.io)) {
uint32_t module = Settings->module;
if (module >= MAXMODULE) { module = SONOFF_BASIC; }
memcpy_P(&Settings->user_template, &kModules[module], sizeof(mytmplt));
}
for (uint32_t i = 0; i < ARRAY_SIZE(Settings->user_template.gp.io); i++) {
uint8_t mpin = Settings->user_template.gp.io[i];
if (mpin) {
if ((mpin >= AGPIO(GPIO_SWT1)) && (mpin < (AGPIO(GPIO_SWT1) + MAX_SWITCHES))) {
SwitchInit();
}
else if ((mpin >= AGPIO(GPIO_KEY1)) && (mpin < (AGPIO(GPIO_KEY1) + MAX_KEYS))) {
ButtonInit();
}
else if ((mpin >= AGPIO(GPIO_REL1)) && (mpin < (AGPIO(GPIO_REL1) + MAX_RELAYS))) {
RelayInit();
}
}
}
}
Tasmota supports 28 languages through a sophisticated compile-time localization system. Each language is implemented as a header file with standardized macro definitions.
language/*.h)Each language file follows a consistent pattern:
#ifndef _LANGUAGE_EN_GB_H_
#define _LANGUAGE_EN_GB_H_
// Language metadata
#define LANGUAGE_LCID 2057 // Windows Language Code Identifier
#define D_HTML_LANGUAGE "en" // HTML language attribute
// Date/Time formatting
#define D_YEAR_MONTH_SEPARATOR "-"
#define D_MONTH_DAY_SEPARATOR "-"
#define D_DATE_TIME_SEPARATOR "T"
#define D_HOUR_MINUTE_SEPARATOR ":"
#define D_MINUTE_SECOND_SEPARATOR ":"
// Calendar data
#define D_DAY3LIST "SunMonTueWedThuFriSat"
#define D_MONTH3LIST "JanFebMarAprMayJunJulAugSepOctNovDec"
// Numeric formatting
#define D_DECIMAL_SEPARATOR "."
// Common terms (500+ definitions)
#define D_ADMIN "Admin"
#define D_PASSWORD "Password"
#define D_HOSTNAME "Hostname"
#define D_MAC_ADDRESS "MAC Address"
// ... hundreds more
| Language | Code | LCID | File | Completeness |
|---|---|---|---|---|
| English (GB) | en-GB | 2057 | en_GB.h | 100% (Reference) |
| German | de-DE | 1031 | de_DE.h | 100% |
| French | fr-FR | 1036 | fr_FR.h | 100% |
| Spanish | es-ES | 1034 | es_ES.h | 100% |
| Italian | it-IT | 1040 | it_IT.h | 100% |
| Portuguese (BR) | pt-BR | 1046 | pt_BR.h | 100% |
| Portuguese | pt-PT | 1031 | pt_PT.h | 100% |
| Russian | ru-RU | 1049 | ru_RU.h | 100% |
| Chinese (CN) | zh-CN | 2052 | zh_CN.h | 95% |
| Chinese (TW) | zh-TW | 1028 | zh_TW.h | 95% |
| Korean | ko-KO | 1042 | ko_KO.h | 90% |
| Dutch | nl-NL | 1043 | nl_NL.h | 100% |
| Polish | pl-PL | 1045 | pl_PL.h | 100% |
| Czech | cs-CZ | 1029 | cs_CZ.h | 100% |
| Hungarian | hu-HU | 1038 | hu_HU.h | 100% |
| Romanian | ro-RO | 1048 | ro_RO.h | 100% |
| Bulgarian | bg-BG | 1026 | bg_BG.h | 100% |
| Greek | el-GR | 1032 | el_GR.h | 100% |
| Turkish | tr-TR | 1055 | tr_TR.h | 100% |
| Hebrew | he-HE | 1037 | he_HE.h | 95% |
| Vietnamese | vi-VN | 1066 | vi_VN.h | 95% |
| Ukrainian | uk-UA | 1058 | uk_UA.h | 100% |
| Lithuanian | lt-LT | 1063 | lt_LT.h | 95% |
| Catalan | ca-AD | 1027 | ca_AD.h | 95% |
| Slovak | sk-SK | 1051 | sk_SK.h | 95% |
| Swedish | sv-SE | 1053 | sv_SE.h | 95% |
| Frisian | fy-NL | 1122 | fy_NL.h | 90% |
| Afrikaans | af-AF | 1078 | af_AF.h | 90% |
The localization system covers multiple categories:
Common Terms (~200 definitions)
Command Names (~150 definitions)
Status Messages (~100 definitions)
Web Interface (~300 definitions)
Log Messages (~50 definitions)
Language selection is handled at compile time through preprocessor directives:
// In user_config_override.h or build flags
#define MY_LANGUAGE en_GB // Select English (GB)
// #define MY_LANGUAGE de_DE // Select German
// #define MY_LANGUAGE fr_FR // Select French
// Language file inclusion
#ifdef MY_LANGUAGE
#include "language/MY_LANGUAGE.h"
#else
#include "language/en_GB.h" // Default fallback
#endif
The localization system uses several techniques to minimize memory usage:
const char D_SAVE_CONFIGURATION[] PROGMEM = "Save configuration";
const char D_RESTART_DEVICE[] PROGMEM = "Restart device";
const char kWifiEncryptionTypes[] PROGMEM = "OPEN|WEP|WPA/PSK|WPA2/PSK|WPA/WPA2/PSK";
#ifdef USE_WEBSERVER
#define D_CONFIGURE_WIFI "Configure WiFi"
#else
#define D_CONFIGURE_WIFI ""
#endif
The system provides utility functions for accessing localized text:
char* GetTextIndexed(char* destination, size_t destination_size,
uint32_t index, const char* haystack) {
// Extract indexed string from concatenated list
char* write = destination;
const char* read = haystack;
index++;
while (index--) {
size_t size = destination_size - 1;
write = destination;
char ch = '.';
while ((ch != '\0') && (ch != '|')) {
ch = pgm_read_byte(read++);
if (size && (ch != '|')) {
*write++ = ch;
size--;
}
}
if (0 == index) {
if (ch == '|') { write--; }
break;
}
}
*write = '\0';
return destination;
}
For languages like Hebrew and Arabic, special handling is implemented:
// Hebrew language file (he_HE.h)
#define D_HTML_LANGUAGE "he"
#define D_HTML_DIRECTION "rtl" // Right-to-left text direction
// CSS class for RTL support
#ifdef D_HTML_DIRECTION
const char HTTP_HEAD_STYLE_RTL[] PROGMEM =
"<style>"
".rtl{direction:rtl;text-align:right;}"
"</style>";
#endif
tasmota_types.h)Tasmota implements a comprehensive type system designed for embedded systems with careful attention to memory alignment and size optimization.
// Power control type - supports up to 32 relays
typedef uint32_t power_t; // Power (Relay) type
const uint32_t POWER_MASK = 0xFFFFFFFFUL; // Power (Relay) full mask
const uint32_t POWER_SIZE = 32; // Power (relay) bit count
// Platform-specific constants (from tasmota.h)
#ifdef ESP8266
const uint8_t MAX_RELAYS = 8; // Max number of relays selectable on GPIO
const uint8_t MAX_INTERLOCKS = 16; // Max number of interlock groups (up to MAX_INTERLOCKS_SET)
const uint8_t MAX_SWITCHES = 8; // Max number of switches selectable on GPIO
const uint8_t MAX_KEYS = 8; // Max number of keys or buttons selectable on GPIO
#endif // ESP8266
#ifdef ESP32
const uint8_t MAX_RELAYS = 32; // Max number of relays selectable on GPIO
const uint8_t MAX_INTERLOCKS = 16; // Max number of interlock groups (up to MAX_INTERLOCKS_SET)
const uint8_t MAX_SWITCHES = 32; // Max number of switches selectable on GPIO
const uint8_t MAX_KEYS = 32; // Max number of keys or buttons selectable on GPIO
#endif // ESP32
// Additional constants for settings layout
const uint8_t MAX_RELAYS_SET = 32; // Max number of relays
const uint8_t MAX_KEYS_SET = 32; // Max number of keys
const uint8_t MAX_INTERLOCKS_SET = 16; // Max number of interlock groups (MAX_RELAYS_SET / 2)
const uint8_t MAX_SWITCHES_SET = 32; // Max number of switches
The SetOption system uses sophisticated bitfield unions for memory-efficient boolean storage:
// SetOption0-31 (32 bits)
typedef union {
uint32_t data; // Raw access for bulk operations
struct {
uint32_t save_state : 1; // bit 0 - Save power state on restart
uint32_t button_restrict : 1; // bit 1 - Restrict button multipress
uint32_t mqtt_add_global_info : 1; // bit 2 - Add global sensor info
uint32_t mqtt_enabled : 1; // bit 3 - MQTT functionality
uint32_t mqtt_response : 1; // bit 4 - MQTT response format
uint32_t mqtt_power_retain : 1; // bit 5 - Retain power messages
uint32_t mqtt_button_retain : 1; // bit 6 - Retain button messages
uint32_t mqtt_switch_retain : 1; // bit 7 - Retain switch messages
uint32_t temperature_conversion : 1; // bit 8 - Celsius/Fahrenheit
uint32_t mqtt_sensor_retain : 1; // bit 9 - Retain sensor messages
uint32_t mqtt_offline : 1; // bit 10 - LWT message format
uint32_t button_swap : 1; // bit 11 - Swap button functions
uint32_t stop_flash_rotate : 1; // bit 12 - Fixed flash location
uint32_t button_single : 1; // bit 13 - Single press only
uint32_t interlock : 1; // bit 14 - Relay interlock
uint32_t pwm_control : 1; // bit 15 - PWM vs COLOR control
uint32_t ws_clock_reverse : 1; // bit 16 - WS2812 direction
uint32_t decimal_text : 1; // bit 17 - Decimal vs hex output
uint32_t light_signal : 1; // bit 18 - Light signal pairing
uint32_t hass_discovery : 1; // bit 19 - Home Assistant discovery
uint32_t not_power_linked : 1; // bit 20 - Power/dimmer linking
uint32_t no_power_on_check : 1; // bit 21 - Skip power state check
uint32_t mqtt_serial : 1; // bit 22 - MQTT serial bridge
uint32_t mqtt_serial_raw : 1; // bit 23 - Raw serial data
uint32_t pressure_conversion : 1; // bit 24 - hPa vs mmHg
uint32_t knx_enabled : 1; // bit 25 - KNX protocol
uint32_t device_index_enable : 1; // bit 26 - POWER vs POWER1
uint32_t knx_enable_enhancement : 1; // bit 27 - KNX enhancements
uint32_t rf_receive_decimal : 1; // bit 28 - RF data format
uint32_t ir_receive_decimal : 1; // bit 29 - IR data format
uint32_t hass_light : 1; // bit 30 - Force light discovery
uint32_t global_state : 1; // bit 31 - Link LED control
};
} SOBitfield;
// SetOption50-81 (32 bits)
typedef union {
uint32_t data;
struct {
uint32_t timers_enable : 1; // bit 0 - Timer functionality
uint32_t user_esp8285_enable : 1; // bit 1 - ESP8285 GPIO access
uint32_t time_append_timezone : 1; // bit 2 - Timezone in JSON
uint32_t gui_hostname_ip : 1; // bit 3 - Show hostname in GUI
uint32_t tuya_apply_o20 : 1; // bit 4 - Tuya SetOption20
uint32_t mdns_enabled : 1; // bit 5 - mDNS service
uint32_t use_wifi_scan : 1; // bit 6 - WiFi scan at restart
uint32_t use_wifi_rescan : 1; // bit 7 - Regular WiFi rescan
uint32_t receive_raw : 1; // bit 8 - IR raw data
uint32_t hass_tele_on_power : 1; // bit 9 - Telemetry on power
uint32_t sleep_normal : 1; // bit 10 - Normal vs dynamic sleep
uint32_t button_switch_force_local : 1; // bit 11 - Force local operation
uint32_t no_hold_retain : 1; // bit 12 - No retain on HOLD
uint32_t no_power_feedback : 1; // bit 13 - Skip power scan
uint32_t use_underscore : 1; // bit 14 - Underscore separator
uint32_t fast_power_cycle_disable : 1; // bit 15 - Disable QPC
uint32_t tuya_serial_mqtt_publish : 1; // bit 16 - Tuya MQTT
uint32_t buzzer_enable : 1; // bit 17 - Buzzer functionality
uint32_t pwm_multi_channels : 1; // bit 18 - Multi-channel PWM
uint32_t sb_receive_invert : 1; // bit 19 - Serial bridge invert
uint32_t energy_weekend : 1; // bit 20 - Weekend energy tariff
uint32_t dds2382_model : 1; // bit 21 - DDS2382 registers
uint32_t hardware_energy_total : 1; // bit 22 - Hardware energy total
uint32_t mqtt_buttons : 1; // bit 23 - Detach buttons from relays
uint32_t ds18x20_internal_pullup : 1; // bit 24 - DS18x20 pullup
uint32_t grouptopic_mode : 1; // bit 25 - GroupTopic format
uint32_t bootcount_update : 1; // bit 26 - Bootcount in deepsleep
uint32_t slider_dimmer_stay_on : 1; // bit 27 - Slider behavior
uint32_t ex_compatibility_check : 1; // bit 28 - (unused)
uint32_t counter_reset_on_tele : 1; // bit 29 - Counter reset
uint32_t shutter_mode : 1; // bit 30 - Shutter support
uint32_t pcf8574_ports_inverted : 1; // bit 31 - PCF8574 inversion
};
} SOBitfield3;
The main global state is organized in the TasmotaGlobal_t structure (defined in tasmota.ino):
struct TasmotaGlobal_t {
// System timing and counters
uint32_t global_update; // Timestamp of last global temperature and humidity update
uint32_t baudrate; // Current Serial baudrate
uint32_t pulse_timer[MAX_PULSETIMERS]; // Power off timer
uint32_t blink_timer; // Power cycle timer
uint32_t backlog_timer; // Timer for next command in backlog
uint32_t loop_load_avg; // Indicative loop load average
uint32_t log_buffer_pointer; // Index in log buffer
uint32_t uptime; // Counting every second until 4294967295 = 130 year
uint32_t zc_time; // Zero-cross moment (microseconds)
uint32_t zc_offset; // Zero cross moment offset due to monitoring chip processing (microseconds)
uint32_t zc_code_offset; // Zero cross moment offset due to executing power code (microseconds)
uint32_t zc_interval; // Zero cross interval around 8333 (60Hz) or 10000 (50Hz) (microseconds)
// Power and relay state
power_t power; // Current copy of Settings->power
power_t power_latching; // Current state of single pin latching power
power_t rel_inverted; // Relay inverted flag (1 = (0 = On, 1 = Off))
power_t rel_bistable; // Relay bistable bitmap
power_t last_power; // Last power set state
power_t blink_power; // Blink power state
power_t blink_powersave; // Blink start power save state
power_t blink_mask; // Blink relay active mask
power_t power_on_delay_state;
// GPIO and hardware configuration
uint16_t gpio_pin[MAX_GPIO_PIN]; // GPIO functions indexed by pin number
myio my_module; // Active copy of Module GPIOs (17 x 16 bits)
// System state flags
RulesBitfield rules_flag; // Rule state flags (16 bits)
StateBitfield global_state; // Global states (currently Wifi and Mqtt) (8 bits)
GpioOptionABits gpio_optiona; // GPIO Option_A flags
// Communication and buffers
int serial_in_byte_counter; // Index in receive buffer
char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer
String mqtt_data; // Buffer filled by Response functions
// Device identification
char version[16]; // Composed version string like 255.255.255.255
char image_name[33]; // Code image and/or commit
char hostname[33]; // Composed Wifi hostname
char mqtt_client[99]; // Composed MQTT Clientname
char mqtt_topic[TOPSZ]; // Composed MQTT topic
// Log buffer (platform-dependent)
#ifdef PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED
char* log_buffer = nullptr; // Log buffer in IRAM
#else
char log_buffer[LOG_BUFFER_SIZE]; // Log buffer in DRAM
#endif
// ... (additional fields for PWM, counters, timers, etc.)
} TasmotaGlobal = { 0 };
The template system uses carefully designed structures for GPIO configuration (from tasmota_template.h):
// GPIO configuration structure (platform-dependent size)
typedef struct MYCFGIO {
uint16_t io[MAX_GPIO_PIN]; // GPIO functions (size varies by platform)
} mycfgio;
// GPIO flag structure
typedef struct GPIO_FLAG {
uint8_t flag1; // Flag byte 1
uint8_t flag2; // Flag byte 2
} gpio_flag;
// Main template structure
typedef struct MYTMPLT {
mycfgio gp; // GPIO configuration (28-72 bytes depending on platform)
gpio_flag flag; // Template flags (2 bytes)
} mytmplt; // Total: 30-74 bytes depending on platform
// GPIO function encoding macros (from tasmota_globals.h)
#define AGPIO(x) ((x)<<5) // Analog GPIO encoding (shift left 5 bits)
#define BGPIO(x) ((x)>>5) // Extract base GPIO from analog encoding
#define AGMAX(x) ((x)?(x-1):0) // Maximum analog GPIO index
// Note: There is no DGPIO macro in the actual code
// Digital GPIOs use direct values (0-31), analog GPIOs use AGPIO(x) encoding
// Template validation example (conceptual)
bool ValidTemplate(mytmplt* tmplt) {
uint8_t pins_used = 0;
for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) {
if (tmplt->gp.io[i] > 0) { pins_used++; }
}
return (pins_used > 0);
}
All structures are carefully designed for optimal memory alignment:
// 32-bit aligned structure
struct ENERGY {
float voltage[3]; // 12 bytes (3 * 4)
float current[3]; // 12 bytes (3 * 4)
float active_power[3]; // 12 bytes (3 * 4)
float apparent_power[3]; // 12 bytes (3 * 4)
float reactive_power[3]; // 12 bytes (3 * 4)
float power_factor[3]; // 12 bytes (3 * 4)
float frequency[3]; // 12 bytes (3 * 4)
uint32_t kWhtoday_delta; // 4 bytes
uint32_t kWhtoday_offset; // 4 bytes
uint32_t kWhtoday; // 4 bytes
uint32_t kWhtotal; // 4 bytes
uint16_t mplh_counter; // 2 bytes
uint16_t mplw_counter; // 2 bytes
uint8_t fifth_second; // 1 byte
uint8_t command_code; // 1 byte
uint8_t data_valid[3]; // 3 bytes
uint8_t phase_count; // 1 byte
// Total: 96 bytes (32-bit aligned)
};
The ESP8266 presents significant memory challenges that drive many architectural decisions:
ESP8266 Memory Map:
┌─────────────────────────────────────┐
│ Flash Memory (1MB-4MB) │
├─────────────────────────────────────┤
│ Program Code (~400-600KB) │
├─────────────────────────────────────┤
│ PROGMEM Constants (~100-200KB) │
├─────────────────────────────────────┤
│ Settings/Config (~16KB) │
├─────────────────────────────────────┤
│ File System (Optional, ~64-256KB) │
└─────────────────────────────────────┘
RAM Memory (80KB total):
┌─────────────────────────────────────┐
│ System/WiFi Stack (~35KB) │
├─────────────────────────────────────┤
│ Arduino Framework (~15KB) │
├─────────────────────────────────────┤
│ Application Heap (~25-30KB) │
└─────────────────────────────────────┘
// Strings stored in flash, not RAM
const char kWifiConfig[] PROGMEM = "WiFi configuration";
const char* const kCommands[] PROGMEM = {
PSTR("Power"), PSTR("Status"), PSTR("Reset")
};
// Access via special functions
char buffer[32];
strcpy_P(buffer, kWifiConfig);
// Instead of separate strings, use concatenated format
const char kSensorTypes[] PROGMEM = "None|DHT11|DHT22|DS18B20|BME280";
// Extract individual strings
char sensor_name[16];
GetTextIndexed(sensor_name, sizeof(sensor_name), sensor_type, kSensorTypes);
// Instead of 32 separate bool variables (32 bytes)
struct {
bool option1, option2, option3, ..., option32;
};
// Use bitfield (4 bytes)
union {
uint32_t data;
struct {
uint32_t option1 : 1;
uint32_t option2 : 1;
// ... up to 32 bits
};
} options;
// Avoid large local arrays
void BadFunction() {
char large_buffer[1024]; // Consumes precious stack space
// ... function code
}
// Use heap allocation or global buffers
void GoodFunction() {
char* buffer = (char*)malloc(1024);
if (buffer) {
// ... function code
free(buffer);
}
}
// Actual Tasmota functions for heap monitoring
uint32_t ESP_getFreeHeap(void) {
#ifdef ESP8266
return ESP.getFreeHeap();
#endif
#ifdef ESP32
return ESP.getFreeHeap();
#endif
}
uint32_t ESP_getMaxAllocHeap(void) {
#ifdef ESP8266
return ESP.getMaxFreeBlockSize();
#endif
#ifdef ESP32
return ESP.getMaxAllocHeap();
#endif
}
// Note: Tasmota doesn't have a dedicated MemoryMonitor() function.
// Memory monitoring is done through periodic logging and watchdog systems.
// Tasmota uses distributed buffers rather than a single TASMOTA_BUFFERS structure
// Key buffer sizes defined in tasmota.h and tasmota_globals.h:
// MESSZ: 1040 bytes - Max characters in JSON message string (defined in tasmota_globals.h)
// MAX_LOGSZ: LOG_BUFFER_SIZE - 96 bytes - Max characters in log line
// LOG_BUFFER_SIZE: 6096 bytes for ESP8266 (defined in tasmota.h)
// INPUT_BUFFER_SIZE: 800 bytes - Max characters in serial command buffer (defined in tasmota.h)
// TOPSZ: 151 bytes - Max characters in topic string (defined in tasmota.h)
// Note: There is no WEB_LOG_SIZE constant or TASMOTA_BUFFERS structure in the actual code.
// Buffers are allocated as needed in different subsystems.
The ESP32 provides significantly more memory, enabling advanced features:
| Resource | ESP8266 | ESP32 | ESP32-S3 |
|---|---|---|---|
| Flash | 1-4MB | 4-16MB | 8-32MB |
| RAM | 80KB | 320KB | 512KB |
| PSRAM | None | Optional 4-8MB | Optional 8-32MB |
| Stack | 4KB | 8KB+ | 8KB+ |
#ifdef ESP32
// Use larger buffers on ESP32
#define MESSZ_ESP32 2048
#define WEB_LOG_SIZE_ESP32 8000
// PSRAM utilization
void* ps_malloc(size_t size) {
if (psramFound()) {
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
}
return malloc(size);
}
// Multi-core task distribution
void CoreTaskCreate(void) {
xTaskCreatePinnedToCore(
NetworkTask, // Task function
"NetworkTask", // Task name
4096, // Stack size
NULL, // Parameters
1, // Priority
NULL, // Task handle
0 // Core ID (0 or 1)
);
}
#endif
support_crash_recorder.ino)Tasmota implements comprehensive crash detection and recovery mechanisms with different implementations for ESP8266 and ESP32:
// Note: The following is a simplified representation. Actual implementation differs
// significantly between ESP8266 and ESP32 variants.
struct CRASH_RECORDER {
uint32_t magic; // Validation magic number (0x53415400 = "TAS")
uint32_t crash_counter; // Number of crashes
uint32_t crash_time; // Last crash timestamp
uint32_t crash_restart; // Restart reason
char crash_dump[CRASH_DUMP_SIZE]; // Stack trace
} CrashRecorder;
// Actual implementation details:
// - ESP8266: Uses RTC memory (32-byte offset) to store crash dumps
// - ESP32: Uses RTC_NOINIT_ATTR memory that survives reboots
// - Different stack trace capture for ESP32 (Xtensa) vs ESP32-C3 (RISC-V)
// - Crash detection via custom panic handlers and exception frame analysis
// ESP8266 implementation (simplified):
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end) {
// Stores valid code addresses from stack to RTC memory
// Only stores addresses in code area (0x40000000-0x40300000)
// Limited to crash_dump_max_len (31) addresses
}
// ESP32 implementation (simplified):
extern "C" IRAM_ATTR void custom_crash_recorder(XtExcFrame *exc_frame) {
// Captures program counter, exception cause, exception address
// Performs backtrace using esp_backtrace_get_next_frame()
// Stores up to crash_dump_max_len (48) return addresses
}
// Note: The CrashDumpSave() function shown in the simplified example
// doesn't exist in the actual code. Stack trace capture is implemented
// differently for each platform and architecture.
// Level 1: Software watchdog (main loop monitoring)
void SoftwareWatchdog(void) {
static uint32_t last_loop_time = 0;
uint32_t current_time = millis();
if ((current_time - last_loop_time) > 30000) { // 30 second timeout
AddLog(LOG_LEVEL_ERROR, PSTR("Software watchdog timeout"));
ESP.restart();
}
last_loop_time = current_time;
}
// Level 2: Hardware watchdog (ESP32 built-in)
#ifdef ESP32
void HardwareWatchdogFeed(void) {
esp_task_wdt_reset(); // Reset hardware watchdog
}
#endif
// Level 3: External watchdog (optional hardware)
void ExternalWatchdogToggle(void) {
static bool watchdog_state = false;
if (Pin(GPIO_WATCHDOG) < 99) {
digitalWrite(Pin(GPIO_WATCHDOG), watchdog_state);
watchdog_state = !watchdog_state;
}
}
bool ValidateNumericRange(int value, int min_val, int max_val) {
return (value >= min_val) && (value <= max_val);
}
bool ValidateStringLength(const char* str, uint32_t max_len) {
return (str != nullptr) && (strlen(str) <= max_len);
}
bool ValidateIPAddress(const char* ip_str) {
IPAddress ip;
return ip.fromString(ip_str) && (ip != IPAddress(0, 0, 0, 0));
}
// Command parameter validation example
void CmndTeleperiod(void) {
if (XdrvMailbox.data_len > 0) {
if (ValidateNumericRange(XdrvMailbox.payload, 10, 3600)) {
Settings->tele_period = XdrvMailbox.payload;
} else {
ResponseCmndError();
return;
}
}
ResponseCmndNumber(Settings->tele_period);
}
// Safe string operations
void SafeStringCopy(char* dest, const char* src, size_t dest_size) {
if (dest && src && dest_size > 0) {
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
}
}
// Safe JSON parsing
bool SafeJsonParse(const char* json_str, size_t max_len) {
if (!json_str || strlen(json_str) > max_len) {
return false;
}
// Additional JSON validation
int brace_count = 0;
for (const char* p = json_str; *p; p++) {
if (*p == '{') brace_count++;
else if (*p == '}') brace_count--;
if (brace_count < 0) return false; // Malformed JSON
}
return (brace_count == 0);
}
// TLS/SSL support for MQTT (ESP32)
#ifdef ESP32
bool MqttConnectTLS(void) {
if (Settings->flag4.mqtt_tls) {
WiFiClientSecure* client = new WiFiClientSecure;
if (Settings->flag4.mqtt_no_cert_verify) {
client->setInsecure(); // Skip certificate verification
} else {
client->setCACert(ca_cert); // Use CA certificate
}
MqttClient.setClient(*client);
}
return MqttClient.connect(mqtt_client, mqtt_user, mqtt_password);
}
#endif
// MQTT topic validation
bool ValidateMqttTopic(const char* topic) {
if (!topic || strlen(topic) == 0) return false;
// Check for invalid characters
const char* invalid_chars = "+#\0";
for (const char* p = topic; *p; p++) {
if (strchr(invalid_chars, *p)) return false;
}
return true;
}
// Basic authentication for web interface
bool WebAuthenticate(void) {
if (strlen(SettingsText(SET_WEBPWD)) > 0) {
if (!WebServer->authenticate("admin", SettingsText(SET_WEBPWD))) {
WebServer->requestAuthentication();
return false;
}
}
return true;
}
// CORS header management
void WebSetCorsHeaders(void) {
if (strlen(SettingsText(SET_CORS)) > 0) {
WebServer->sendHeader("Access-Control-Allow-Origin", SettingsText(SET_CORS));
WebServer->sendHeader("Access-Control-Allow-Methods", "GET, POST");
WebServer->sendHeader("Access-Control-Allow-Headers", "Content-Type");
}
}
support_profiling.ino)Tasmota includes performance monitoring capabilities focused on driver and function execution times:
// Actual Tasmota profiling implementation (simplified)
#ifdef USE_PROFILE_DRIVER
void AddLogDriver(const char *driver, uint8_t function, uint32_t start) {
uint32_t profile_millis = millis() - start;
if (profile_millis > PROFILE_THRESHOLD) { // Default: 70ms
char stemp1[20];
AddLog(LOG_LEVEL_DEBUG, PSTR("PRF: *** x%s FUNC_%s (%d ms)"),
driver, GetTextIndexed(stemp1, sizeof(stemp1), function, kXSnsFunctions),
profile_millis);
}
}
#endif // USE_PROFILE_DRIVER
#ifdef USE_PROFILE_FUNCTION
void AddLogFunction(const char *driver, uint8_t index, uint8_t function, uint32_t start) {
uint32_t profile_millis = millis() - start;
if (profile_millis > PROFILE_THRESHOLD) { // Default: 70ms
char stemp1[20];
AddLog(LOG_LEVEL_DEBUG, PSTR("PRF: *** x%s_%02d FUNC_%s (%d ms)"),
driver, index, GetTextIndexed(stemp1, sizeof(stemp1), function, kXSnsFunctions),
profile_millis);
}
}
#endif // USE_PROFILE_DRIVER
// Note: Tasmota does NOT have the comprehensive PROFILING structure with
// loop time averaging, per-function call counts, or detailed timing shown
// in the simplified example. The actual profiling logs only when execution
// time exceeds PROFILE_THRESHOLD (default 70ms).
support_statistics.ino)// Actual Tasmota statistics implementation (simplified)
String GetStatistics(void) {
char data[40];
// CR: Character Usage Ratio - used characters / total settings text size
snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""),
GetSettingsTextLen(), settings_text_size);
return String(data);
}
// Note: Tasmota does NOT have comprehensive heap fragmentation analysis,
// allocation tracking, or detailed memory statistics as shown in the
// simplified example above. The actual statistics support is minimal
// and focuses on character usage ratio for settings storage.
void WifiPerformanceMonitor(void) {
static uint32_t last_check = 0;
if (TimeReached(last_check)) {
SetNextTimeInterval(last_check, 60000); // Check every minute
int32_t rssi = WiFi.RSSI();
uint8_t quality = WifiGetRssiAsQuality(rssi);
// Log poor signal quality
if (quality < 25) {
AddLog(LOG_LEVEL_WARNING, PSTR("Poor WiFi signal: %d%% (%d dBm)"),
quality, rssi);
}
// Trigger rescan if signal is very poor
if (quality < 10 && Settings->flag3.use_wifi_rescan) {
WifiConfig(WIFI_RETRY);
}
}
}
// Message queuing for high-frequency updates
struct MQTT_QUEUE {
char topic[64];
char payload[256];
bool retain;
uint32_t timestamp;
} mqtt_queue[MQTT_QUEUE_SIZE];
void MqttQueueMessage(const char* topic, const char* payload, bool retain) {
static uint8_t queue_index = 0;
// Add to queue
strlcpy(mqtt_queue[queue_index].topic, topic, sizeof(mqtt_queue[0].topic));
strlcpy(mqtt_queue[queue_index].payload, payload, sizeof(mqtt_queue[0].payload));
mqtt_queue[queue_index].retain = retain;
mqtt_queue[queue_index].timestamp = millis();
queue_index = (queue_index + 1) % MQTT_QUEUE_SIZE;
}
void MqttProcessQueue(void) {
static uint8_t process_index = 0;
if (MqttIsConnected() && mqtt_queue[process_index].timestamp > 0) {
MqttPublish(mqtt_queue[process_index].topic,
mqtt_queue[process_index].payload,
mqtt_queue[process_index].retain);
mqtt_queue[process_index].timestamp = 0; // Mark as processed
process_index = (process_index + 1) % MQTT_QUEUE_SIZE;
}
}
Support Files:
- support.ino - Core system functions
- support_*.ino - Specific subsystem support
- settings.ino - Configuration management
Driver Files:
- xdrv_##_name.ino - Driver modules (##: 01-99)
- xsns_##_name.ino - Sensor modules (##: 01-99)
- xlgt_##_name.ino - Light driver modules
- xnrg_##_name.ino - Energy monitoring modules
Include Files:
- tasmota.h - Main header
- tasmota_types.h - Type definitions
- tasmota_globals.h - Global variables
- tasmota_template.h - Device templates
// Public API functions - CamelCase
void ButtonInit(void);
bool WifiConnect(void);
uint32_t GetUptime(void);
// Internal functions - lowercase with underscores
static void button_handler(void);
static bool wifi_scan_networks(void);
static uint32_t calculate_checksum(void);
// Command handlers - CmndXxxxx
void CmndPower(void);
void CmndStatus(void);
void CmndRestart(void);
// Callback functions - XxxxCallback
bool ButtonCallback(uint8_t function);
bool SensorCallback(uint8_t function);
// Avoid frequent malloc/free
char* buffer = (char*)malloc(1024); // Bad in main loop
free(buffer);
// Use static buffers or global pools
static char static_buffer[1024]; // Good for temporary use
// Store strings in flash memory
const char kErrorMessage[] PROGMEM = "Configuration error";
const uint8_t kDefaultValues[] PROGMEM = {1, 2, 3, 4, 5};
// Access with special functions
char buffer[32];
strcpy_P(buffer, kErrorMessage);
// Bad - wastes memory due to alignment
struct BadStruct {
uint8_t flag; // 1 byte + 3 padding
uint32_t value; // 4 bytes
uint8_t status; // 1 byte + 3 padding
}; // Total: 12 bytes
// Good - optimized alignment
struct GoodStruct {
uint32_t value; // 4 bytes
uint8_t flag; // 1 byte
uint8_t status; // 1 byte
uint16_t padding; // 2 bytes (explicit)
}; // Total: 8 bytes
/*
xdrv_##_mydriver.ino - My custom driver for Tasmota
Copyright (C) 2021 Your Name
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
*/
#ifdef USE_MY_DRIVER
#define XDRV_## ## // Unique driver ID
// Driver-specific constants
const char kMyDriverCommands[] PROGMEM = "|"
"MyCmd1|MyCmd2|MyCmd3";
void (* const MyDriverCommand[])(void) PROGMEM = {
&CmndMyCmd1, &CmndMyCmd2, &CmndMyCmd3
};
// Driver initialization
void MyDriverInit(void) {
// Initialize hardware
// Set up GPIO pins
// Configure peripherals
}
// Main driver callback
bool Xdrv##(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_INIT:
MyDriverInit();
break;
case FUNC_EVERY_SECOND:
MyDriverEverySecond();
break;
case FUNC_COMMAND:
result = DecodeCommand(kMyDriverCommands, MyDriverCommand);
break;
case FUNC_JSON_APPEND:
MyDriverShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
MyDriverShow(0);
break;
#endif // USE_WEBSERVER
}
return result;
}
#endif // USE_MY_DRIVER
#ifdef USE_MY_SENSOR
#define XSNS_## ## // Unique sensor ID
bool MySensorDetected = false;
uint8_t MySensorAddress = 0;
void MySensorDetect(void) {
if (MySensorDetected) return;
for (uint32_t i = 0; i < SENSOR_MAX_ADDR; i++) {
uint8_t addr = SENSOR_BASE_ADDR + i;
if (I2cActive(addr)) continue;
if (I2cValidRead8(&sensor_id, addr, SENSOR_ID_REG)) {
if (sensor_id == EXPECTED_SENSOR_ID) {
I2cSetActiveFound(addr, "MySensor");
MySensorDetected = true;
MySensorAddress = addr;
break;
}
}
}
}
void MySensorEverySecond(void) {
if (!MySensorDetected) return;
// Read sensor data
float temperature, humidity;
if (MySensorRead(&temperature, &humidity)) {
// Process readings
}
}
void MySensorShow(bool json) {
if (!MySensorDetected) return;
if (json) {
ResponseAppend_P(PSTR(",\"MySensor\":{\"Temperature\":%1_f,\"Humidity\":%1_f}"),
&temperature, &humidity);
}
#ifdef USE_WEBSERVER
else {
WSContentSend_PD(HTTP_SNS_TEMP, "MySensor", temperature);
WSContentSend_PD(HTTP_SNS_HUM, "MySensor", humidity);
}
#endif // USE_WEBSERVER
}
bool Xsns##(uint8_t function) {
bool result = false;
if (FUNC_INIT == function) {
MySensorDetect();
}
else if (MySensorDetected) {
switch (function) {
case FUNC_EVERY_SECOND:
MySensorEverySecond();
break;
case FUNC_JSON_APPEND:
MySensorShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
MySensorShow(0);
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_MY_SENSOR
// Log levels in order of verbosity
#define LOG_LEVEL_NONE 0 // No logging
#define LOG_LEVEL_ERROR 1 // Critical errors only
#define LOG_LEVEL_INFO 2 // Errors and important info
#define LOG_LEVEL_DEBUG 3 // Detailed debugging info
#define LOG_LEVEL_DEBUG_MORE 4 // Verbose debugging
// Usage examples
AddLog(LOG_LEVEL_ERROR, PSTR("Critical error: %s"), error_msg);
AddLog(LOG_LEVEL_INFO, PSTR("Sensor initialized: %s"), sensor_name);
AddLog(LOG_LEVEL_DEBUG, PSTR("Reading value: %d"), sensor_value);
#ifdef DEBUG_TASMOTA_CORE
void DebugMemoryInfo(const char* location) {
AddLog(LOG_LEVEL_DEBUG, PSTR("MEM: %s - Free: %d, Max: %d, Frag: %d%%"),
location, ESP_getFreeHeap(), ESP_getMaxAllocHeap(),
ESP_getHeapFragmentation());
}
#else
#define DebugMemoryInfo(x)
#endif
This comprehensive analysis provides a deep understanding of Tasmota's support infrastructure, covering all major subsystems from core functionality to development guidelines. The modular architecture, memory optimization techniques, and extensive safety features make Tasmota a robust platform for IoT device development.
This document has been verified against the actual Tasmota source code (as of May 2026). Key findings:
support.ino) - Watchdog, RTC settings, command processingsettings.ino) - TRtcSettings structure, SetOption rangessupport_a_i2c.ino) - I2C device management, MAX_I2C definitionstasmota_types.h) - SOBitfield structures, platform constants/tasmota/language/support_crash_recorder.ino) - Crash detection, stack trace capturesupport_profiling.ino) - Driver/function performance monitoringsupport_wifi.ino, support_network.ino) - WiFi management, MDNSSome sections show simplified code examples that capture the conceptual approach but differ from the actual implementation. These are noted with comments.
Tasmota's support infrastructure represents a sophisticated embedded systems architecture that successfully balances functionality, performance, and resource constraints. The system's key strengths include:
The analysis reveals a mature codebase with consistent patterns, robust error handling, and thoughtful architectural decisions that enable Tasmota to run effectively on resource-constrained microcontrollers while providing rich functionality for IoT applications.