diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index fb9984430..77fd48a49 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -332,6 +332,7 @@ #define D_CMND_OTAURL "OtaUrl" #define D_CMND_SERIALLOG "SerialLog" #define D_CMND_SYSLOG "SysLog" +#define D_CMND_FILELOG "FileLog" #define D_CMND_LOGHOST "LogHost" #define D_CMND_LOGPORT "LogPort" #define D_CMND_IPADDRESS "IPAddress" diff --git a/tasmota/include/tasmota_globals.h b/tasmota/include/tasmota_globals.h index 7c02d9f7f..f9e85bccf 100644 --- a/tasmota/include/tasmota_globals.h +++ b/tasmota/include/tasmota_globals.h @@ -297,6 +297,7 @@ const char WIFI_HOSTNAME[] = WIFI_DEFAULT_HOSTNAME; // Override by user_confi #define TASM_FILE_ZIGBEE_DATA "/zbdata" // Zigbee last known values of devices #define TASM_FILE_AUTOEXEC "/autoexec.bat" // Commands executed after restart #define TASM_FILE_CONFIG "/config.sys" // Settings executed after restart +#define TASM_FILE_LOG "/log%d" // Log file #ifndef DNS_TIMEOUT #define DNS_TIMEOUT 1000 // Milliseconds diff --git a/tasmota/include/tasmota_types.h b/tasmota/include/tasmota_types.h index 310a9c6fc..93805669e 100644 --- a/tasmota/include/tasmota_types.h +++ b/tasmota/include/tasmota_types.h @@ -241,10 +241,7 @@ typedef union { typedef union { uint32_t data; // Allow bit manipulation struct { - uint32_t spare00 : 1; // bit 0 - uint32_t spare01 : 1; // bit 1 - uint32_t spare02 : 1; // bit 2 - uint32_t spare03 : 1; // bit 3 + uint32_t log_file_idx : 4; // bit 0.3 (v14.4.1.2) - FileLog log rotate index uint32_t spare04 : 1; // bit 4 uint32_t spare05 : 1; // bit 5 uint32_t spare06 : 1; // bit 6 @@ -696,9 +693,7 @@ typedef struct { uint16_t influxdb_period; // 538 520 uint16_t rf_duplicate_time; // 53A 522 uint8_t global_sensor_index[3]; // 53C 4C5 - - uint8_t free_53F[1]; // 53F - + uint8_t filelog_level; // 53F uint16_t tcp_baudrate; // 540 uint16_t button_debounce; // 542 uint32_t ipv4_address[5]; // 544 diff --git a/tasmota/tasmota_support/support.ino b/tasmota/tasmota_support/support.ino index 4caa4bdf4..6a6c0e1d7 100755 --- a/tasmota/tasmota_support/support.ino +++ b/tasmota/tasmota_support/support.ino @@ -2649,6 +2649,9 @@ void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_pa uint32_t highest_loglevel = Settings->weblog_level; if (Settings->mqttlog_level > highest_loglevel) { highest_loglevel = Settings->mqttlog_level; } +#ifdef USE_UFILESYS + if (Settings->filelog_level > highest_loglevel) { highest_loglevel = Settings->filelog_level; } +#endif // USE_UFILESYS if (TasmotaGlobal.syslog_level > highest_loglevel) { highest_loglevel = TasmotaGlobal.syslog_level; } if (TasmotaGlobal.templog_level > highest_loglevel) { highest_loglevel = TasmotaGlobal.templog_level; } if (TasmotaGlobal.uptime < 3) { highest_loglevel = LOG_LEVEL_DEBUG_MORE; } // Log all before setup correct log level @@ -2697,6 +2700,9 @@ uint32_t HighestLogLevel() { uint32_t highest_loglevel = TasmotaGlobal.seriallog_level; if (Settings->weblog_level > highest_loglevel) { highest_loglevel = Settings->weblog_level; } if (Settings->mqttlog_level > highest_loglevel) { highest_loglevel = Settings->mqttlog_level; } +#ifdef USE_UFILESYS + if (Settings->filelog_level > highest_loglevel) { highest_loglevel = Settings->filelog_level; } +#endif // USE_UFILESYS if (TasmotaGlobal.syslog_level > highest_loglevel) { highest_loglevel = TasmotaGlobal.syslog_level; } if (TasmotaGlobal.templog_level > highest_loglevel) { highest_loglevel = TasmotaGlobal.templog_level; } if (TasmotaGlobal.uptime < 3) { highest_loglevel = LOG_LEVEL_DEBUG_MORE; } // Log all before setup correct log level diff --git a/tasmota/tasmota_support/support_command.ino b/tasmota/tasmota_support/support_command.ino index 727da3273..23be5c157 100644 --- a/tasmota/tasmota_support/support_command.ino +++ b/tasmota/tasmota_support/support_command.ino @@ -29,6 +29,9 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|" D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOREAD "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" +#ifdef USE_UFILESYS + D_CMND_FILELOG "|" +#endif // USE_UFILESYS D_CMND_SERIALBUFFER "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_WIFI "|" D_CMND_DNSTIMEOUT "|" D_CMND_DEVICENAME "|" D_CMND_FN "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" @@ -69,6 +72,9 @@ void (* const TasmotaCommand[])(void) PROGMEM = { &CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution, &CmndModule, &CmndModules, &CmndGpio, &CmndGpioRead, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange, &CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, +#ifdef USE_UFILESYS + &CmndFilelog, +#endif // USE_UFILESYS &CmndSerialBuffer, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, &CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, &CmndWifi, &CmndDnsTimeout, &CmndDevicename, &CmndFriendlyname, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd, @@ -922,11 +928,19 @@ void CmndStatus(void) } if ((0 == payload) || (3 == payload)) { - Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_MQTTLOG "\":%d,\"" D_CMND_SYSLOG "\":%d,\"" - D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\"" + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_MQTTLOG "\":%d,\"" +#ifdef USE_UFILESYS + D_CMND_FILELOG "\":%d,\"" +#endif // USE_UFILESYS + D_CMND_SYSLOG "\":%d,\"" D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" + D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\"" D_JSON_RESOLUTION "\":\"%08X\",\"" D_CMND_SETOPTION "\":[\"%08X\",\"%s\",\"%08X\",\"%08X\",\"%08X\",\"%08X\"]}}"), - Settings->seriallog_level, Settings->weblog_level, Settings->mqttlog_level, Settings->syslog_level, - SettingsText(SET_SYSLOG_HOST), Settings->syslog_port, EscapeJSONString(SettingsText(SET_STASSID1)).c_str(), EscapeJSONString(SettingsText(SET_STASSID2)).c_str(), Settings->tele_period, + Settings->seriallog_level, Settings->weblog_level, Settings->mqttlog_level, +#ifdef USE_UFILESYS + Settings->filelog_level, +#endif // USE_UFILESYS + Settings->syslog_level, SettingsText(SET_SYSLOG_HOST), Settings->syslog_port, + EscapeJSONString(SettingsText(SET_STASSID1)).c_str(), EscapeJSONString(SettingsText(SET_STASSID2)).c_str(), Settings->tele_period, Settings->flag2.data, Settings->flag.data, ToHex_P((unsigned char*)Settings->param, PARAM8_SIZE, stemp2, sizeof(stemp2)), Settings->flag3.data, Settings->flag4.data, Settings->flag5.data, Settings->flag6.data); CmndStatusResponse(3); @@ -2147,6 +2161,15 @@ void CmndLogport(void) ResponseCmndNumber(Settings->syslog_port); } +#ifdef USE_UFILESYS +void CmndFilelog(void) { + if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { + Settings->filelog_level = XdrvMailbox.payload; + } + ResponseCmndNumber(Settings->filelog_level); +} +#endif // USE_UFILESYS + void CmndIpAddress(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) { diff --git a/tasmota/tasmota_support/support_tasmota.ino b/tasmota/tasmota_support/support_tasmota.ino index d8cceedc4..b42f79a8f 100644 --- a/tasmota/tasmota_support/support_tasmota.ino +++ b/tasmota/tasmota_support/support_tasmota.ino @@ -1176,6 +1176,9 @@ void PerformEverySecond(void) #ifdef SYSLOG_UPDATE_SECOND SyslogAsync(false); #endif // SYSLOG_UPDATE_SECOND +#ifdef USE_UFILESYS + FileLoggingAsync(false); +#endif // USE_UFILESYS ResetGlobalValues(); @@ -1346,6 +1349,9 @@ void Every250mSeconds(void) // Check if log refresh needed in case of fast buffer fill MqttPublishLoggingAsync(true); SyslogAsync(true); +#ifdef USE_UFILESYS + FileLoggingAsync(true); +#endif // USE_UFILESYS /*-------------------------------------------------------------------------------------------*\ * Every second at 0.25 second interval diff --git a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino index 84203d405..34397faec 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino @@ -494,6 +494,69 @@ bool TfsRenameFile(const char *fname1, const char *fname2) { return true; } +/*********************************************************************************************\ + * Log file + * + * Rotate max 8 x 100kB log files /log1 -> /log2 ... /log8 -> /log1 ... + * Keep at least 100kB free on filesystem + * Filesystem needs to ba larger than 110kB +\*********************************************************************************************/ + +void FileLoggingAsync(bool refresh) { + static uint32_t index = 1; + + if (!ffs_type || !Settings->filelog_level) { return; } + if (refresh && !NeedLogRefresh(Settings->filelog_level, index)) { return; } + if (UfsSize() < 110) { return; } // File system too small + + char fname[14]; + bool file_delete = false; + File file; + while (1) { + snprintf_P(fname, sizeof(fname), PSTR(TASM_FILE_LOG), Settings->mbflag2.log_file_idx +1); // /log1 + if (file_delete) { + ffsp->remove(fname); // Delete previous log file + } + file = ffsp->open(fname, "a"); // Append + if (!file) { + file = ffsp->open(fname, "w"); // Make if not exists + if (!file) { + AddLog(LOG_LEVEL_INFO, PSTR("FLG: Save failed")); + return; // Failed to make file + } + } + if (file.size() > 100000) { // Rotate log file if size over 100k + file.close(); + Settings->mbflag2.log_file_idx++; + if ((8 == Settings->mbflag2.log_file_idx) || // Keep max 100k x 8 log files + (UfsFree() < 110)) { // Keep free space around 100k + Settings->mbflag2.log_file_idx = 0; + file_delete = true; + } + } else { + break; + } + } + +#ifdef USE_WEBCAM + WcInterrupt(0); // Stop stream if active to fix TG1WDT_SYS_RESET +#endif + char* line; + size_t len; + while (GetLog(Settings->filelog_level, &index, &line, &len)) { + // This will timeout on ESP32-webcam + // But now solved with WcInterrupt(0) in support_esp.ino + file.write((uint8_t*)line, len -1); + snprintf_P(fname, sizeof(fname), PSTR("\r\n")); + file.write((uint8_t*)fname, 2); + } +#ifdef USE_WEBCAM + WcInterrupt(1); +#endif + + file.close(); +} + /*********************************************************************************************\ * File command execute support \*********************************************************************************************/