From 9dfc8f8785d04e2f6962cf69fba2b7ba58ce7126 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Thu, 7 May 2020 19:48:43 +0200 Subject: [PATCH 01/29] Shrink the Sunrise/Sunset code --- tasmota/support_float.ino | 23 +++++++ tasmota/xdrv_09_timers.ino | 133 +++++++++++++++---------------------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/tasmota/support_float.ino b/tasmota/support_float.ino index 29613453a..98553d183 100644 --- a/tasmota/support_float.ino +++ b/tasmota/support_float.ino @@ -422,3 +422,26 @@ uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max, } return (uint32_t) (result > to_max ? to_max : (result < to_min ? to_min : result)); } + +// Force a float value between two ranges, and adds or substract the range until we fit +float ModulusRangef(float f, float a, float b) { + if (b <= a) { return a; } // inconsistent, do what we can + float range = b - a; + float x = f - a; // now range of x should be 0..range + x = fmodf(x, range); // actual range is now -range..range + if (x < 0.0f) { x += range; } // actual range is now 0..range + return x + a; // returns range a..b +} + +// Compute a n-degree polynomial for value x and an array of coefficient (by increasing order) +// Ex: +// For factors = { f0, f1, f2, f3 } +// Returns : f0 + f1 x + f2 x^2, + f3 x^3 +// Internally computed as : f0 + x (f1 + x (f2 + x f3)) +float Polynomialf(const float *factors, uint32_t degree, float x) { + float r = 0.0f; + for (uint32_t i = degree - 1; i >= 0; i--) { + r = r * x + factors[i]; + } + return r; +} diff --git a/tasmota/xdrv_09_timers.ino b/tasmota/xdrv_09_timers.ino index a5cb67f41..61d5bc737 100644 --- a/tasmota/xdrv_09_timers.ino +++ b/tasmota/xdrv_09_timers.ino @@ -68,61 +68,61 @@ const float pi2 = TWO_PI; const float pi = PI; const float RAD = DEG_TO_RAD; -float JulianischesDatum(void) -{ - // Gregorianischer Kalender - int Gregor; - int Jahr = RtcTime.year; - int Monat = RtcTime.month; - int Tag = RtcTime.day_of_month; +// Compute the Julian date from the Calendar date, using only unsigned ints for code compactness +// Warning this formula works only from 2000 to 2099, after 2100 we get 1 day off per century. If ever Tasmota survives until then. +uint32_t JulianDate(const struct TIME_T &now) { + // https://en.wikipedia.org/wiki/Julian_day - if (Monat <= 2) { - Monat += 12; - Jahr -= 1; + uint32_t Year = now.year; // Year ex:2020 + uint32_t Month = now.month; // 1..12 + uint32_t Day = now.day_of_month; // 1..31 + uint32_t Julian; // Julian day number + + if (Month <= 2) { + Month += 12; + Year -= 1; } - Gregor = (Jahr / 400) - (Jahr / 100) + (Jahr / 4); // Gregorianischer Kalender - return 2400000.5f + 365.0f*Jahr - 679004.0f + Gregor + (int)(30.6001f * (Monat +1)) + Tag + 0.5f; + // Warning, this formula works only for the 20th century, afterwards be are off by 1 day - which does not impact Sunrise much + // Julian = (1461 * Year + 6884472) / 4 + (153 * Month - 457) / 5 + Day -1 -13; + Julian = (1461 * Year + 6884416) / 4 + (153 * Month - 457) / 5 + Day; // -1 -13 included in 6884472 - 14*4 = 6884416 + return Julian; } +// Force value in the 0..pi2 range float InPi(float x) { - int n = (int)(x / pi2); - x = x - n*pi2; - if (x < 0) x += pi2; - return x; + return ModulusRangef(x, 0.0f, pi2); } -float eps(float T) -{ - // Neigung der Erdachse - return RAD * (23.43929111f + (-46.8150f*T - 0.00059f*T*T + 0.001813f*T*T*T)/3600.0f); -} +// Time formula +// Tdays is the number of days since Jan 1 2000, and replaces T as the Tropical Century. T = Tdays / 36525.0 +float TimeFormula(float *DK, uint32_t Tdays) { + float RA_Mean = 18.71506921f + (2400.0513369f / 36525.0f) * Tdays; // we keep only first order value as T is between 0.20 and 0.30 + float M = InPi( (pi2 * 0.993133f) + (pi2 * 99.997361f / 36525.0f) * Tdays); + float L = InPi( (pi2 * 0.7859453f) + M + (6893.0f * sinf(M) + 72.0f * sinf(M+M) + (6191.2f / 36525.0f) * Tdays) * (pi2 / 1296.0e3f)); -float BerechneZeitgleichung(float *DK,float T) -{ - float RA_Mittel = 18.71506921f + 2400.0513369f*T +(2.5862e-5f - 1.72e-9f*T)*T*T; - float M = InPi(pi2 * (0.993133f + 99.997361f*T)); - float L = InPi(pi2 * (0.7859453f + M/pi2 + (6893.0f*sinf(M)+72.0f*sinf(2.0f*M)+6191.2f*T) / 1296.0e3f)); - float e = eps(T); - float RA = atanf(tanf(L)*cosf(e)); - if (RA < 0.0) RA += pi; + float eps = 0.40904f; // we take this angle as constant over the next decade + float cos_eps = 0.91750f; // precompute cos(eps) + float sin_eps = 0.39773f; // precompute sin(eps) + + float RA = atanf(tanf(L) * cos_eps); + if (RA < 0.0f) RA += pi; if (L > pi) RA += pi; - RA = 24.0*RA/pi2; - *DK = asinf(sinf(e)*sinf(L)); - // Damit 0<=RA_Mittel<24 - RA_Mittel = 24.0f * InPi(pi2*RA_Mittel/24.0f)/pi2; - float dRA = RA_Mittel - RA; - if (dRA < -12.0f) dRA += 24.0f; - if (dRA > 12.0f) dRA -= 24.0f; + RA = RA * (24.0f/pi2); + *DK = asinf(sin_eps * sinf(L)); + RA_Mean = ModulusRangef(RA_Mean, 0.0f, 24.0f); + float dRA = ModulusRangef(RA_Mean - RA, -12.0f, 12.0f); dRA = dRA * 1.0027379f; return dRA; } void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down) { - float JD2000 = 2451545.0f; - float JD = JulianischesDatum(); - float T = (JD - JD2000) / 36525.0f; + const uint32_t JD2000 = 2451545; + uint32_t JD = JulianDate(RtcTime); + uint32_t Tdays = JD - JD2000; // number of days since Jan 1 2000 + + // ex 2458977 (2020 May 7) - 2451545 -> 7432 -> 0,2034 float DK; /* h (D) = -0.8333 normaler SA & SU-Gang @@ -130,56 +130,33 @@ void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8 h (D) = -12.0 nautische Dämmerung h (D) = -18.0 astronomische Dämmerung */ -// double h = -50/60.0*RAD; - float h = SUNRISE_DAWN_ANGLE *RAD; - float B = (((float)Settings.latitude)/1000000) * RAD; // geographische Breite + const float h = SUNRISE_DAWN_ANGLE * RAD; + const float sin_h = sinf(h); // let GCC pre-compute the sin() at compile time + + float B = Settings.latitude / (1000000.0f / RAD); // geographische Breite + //float B = (((float)Settings.latitude)/1000000) * RAD; // geographische Breite float GeographischeLaenge = ((float)Settings.longitude)/1000000; // double Zeitzone = 0; //Weltzeit // double Zeitzone = 1; //Winterzeit // double Zeitzone = 2.0; //Sommerzeit float Zeitzone = ((float)Rtc.time_timezone) / 60; - float Zeitgleichung = BerechneZeitgleichung(&DK, T); - float Zeitdifferenz = 12.0f*acosf((sinf(h) - sinf(B)*sinf(DK)) / (cosf(B)*cosf(DK)))/pi; + float Zeitgleichung = TimeFormula(&DK, Tdays); + float Zeitdifferenz = acosf((sin_h - sinf(B)*sinf(DK)) / (cosf(B)*cosf(DK))) * (12.0f / pi); float AufgangOrtszeit = 12.0f - Zeitdifferenz - Zeitgleichung; float UntergangOrtszeit = 12.0f + Zeitdifferenz - Zeitgleichung; float AufgangWeltzeit = AufgangOrtszeit - GeographischeLaenge / 15.0f; float UntergangWeltzeit = UntergangOrtszeit - GeographischeLaenge / 15.0f; - float Aufgang = AufgangWeltzeit + Zeitzone; // In Stunden - if (Aufgang < 0.0f) { - Aufgang += 24.0f; - } else { - if (Aufgang >= 24.0f) Aufgang -= 24.0f; - } - float Untergang = UntergangWeltzeit + Zeitzone; - if (Untergang < 0.0f) { - Untergang += 24.0f; - } else { - if (Untergang >= 24.0f) Untergang -= 24.0f; - } - int AufgangMinuten = (int)(60.0f*(Aufgang - (int)Aufgang)+0.5f); + float Aufgang = AufgangWeltzeit + Zeitzone + (1/120.0f); // In Stunden, with rounding to nearest minute (1/60 * .5) + + Aufgang = ModulusRangef(Aufgang, 0.0f, 24.0f); // force 0 <= x < 24.0 int AufgangStunden = (int)Aufgang; - if (AufgangMinuten >= 60.0f) { - AufgangMinuten -= 60.0f; - AufgangStunden++; - } else { - if (AufgangMinuten < 0.0f) { - AufgangMinuten += 60.0f; - AufgangStunden--; - if (AufgangStunden < 0.0f) AufgangStunden += 24.0f; - } - } - int UntergangMinuten = (int)(60.0f*(Untergang - (int)Untergang)+0.5f); + int AufgangMinuten = (int)(60.0f * fmodf(Aufgang, 1.0f)); + float Untergang = UntergangWeltzeit + Zeitzone; + + Untergang = ModulusRangef(Untergang, 0.0f, 24.0f); int UntergangStunden = (int)Untergang; - if (UntergangMinuten >= 60.0f) { - UntergangMinuten -= 60.0f; - UntergangStunden++; - } else { - if (UntergangMinuten<0) { - UntergangMinuten += 60.0f; - UntergangStunden--; - if (UntergangStunden < 0.0f) UntergangStunden += 24.0f; - } - } + int UntergangMinuten = (int)(60.0f * fmodf(Untergang, 1.0f)); + *hour_up = AufgangStunden; *minute_up = AufgangMinuten; *hour_down = UntergangStunden; From 1387866397e8973e8a18350f80d4ee8e0b9ef026 Mon Sep 17 00:00:00 2001 From: znanev <20048364+znanev@users.noreply.github.com> Date: Thu, 7 May 2020 22:57:23 +0100 Subject: [PATCH 02/29] Update bg_BG.h --- tasmota/language/bg_BG.h | 122 +++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index 14bf6f698..3797ea4dc 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -28,7 +28,7 @@ * Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command Prefix to translate cmnd, stat and tele. * - * Updated until v7.1.2.4 + * Updated until v8.2.0.6 \*********************************************************************/ //#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) @@ -115,7 +115,7 @@ #define D_LIGHT "Светлина" #define D_LWT "LWT" #define D_MODULE "Модул" -#define D_MOISTURE "Moisture" +#define D_MOISTURE "Влага" #define D_MQTT "MQTT" #define D_MULTI_PRESS "неколкократно натискане" #define D_NOISE "Шум" @@ -138,7 +138,7 @@ #define D_PROGRAM_SIZE "Размер на програмата" #define D_PROJECT "Проект" #define D_RAIN "Дъжд" -#define D_RANGE "Range" +#define D_RANGE "Обхват" #define D_RECEIVED "Получено" #define D_RESTART "Рестарт" #define D_RESTARTING "Рестартиране" @@ -357,7 +357,7 @@ #define D_UPLOAD_ERR_11 "Грешка при изтриване на RF чипа" #define D_UPLOAD_ERR_12 "Грешка при записване в RF чипа" #define D_UPLOAD_ERR_13 "Грешка при декодиране на RF фърмуера" -#define D_UPLOAD_ERR_14 "Not compatible" +#define D_UPLOAD_ERR_14 "Несъвместим" #define D_UPLOAD_ERROR_CODE "Код на грешка при зареждането" #define D_ENTER_COMMAND "Въвеждане на команда" @@ -484,11 +484,11 @@ // xsns_27_apds9960.ino #define D_GESTURE "Жест" -#define D_COLOR_RED "Red" -#define D_COLOR_GREEN "Грийн" -#define D_COLOR_BLUE "син" +#define D_COLOR_RED "Червен" +#define D_COLOR_GREEN "Зелен" +#define D_COLOR_BLUE "Син" #define D_CCT "CCT" -#define D_PROXIMITY "близост" +#define D_PROXIMITY "Близост" // xsns_32_mpu6050.ino #define D_AX_AXIS "Ускорение - ос X" @@ -514,7 +514,7 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Посока на вятъра" #define D_TX20_WIND_SPEED "Скорост на вятъра" -#define D_TX20_WIND_SPEED_MIN "Мини. скорост на вятъра" +#define D_TX20_WIND_SPEED_MIN "Мин. скорост на вятъра" #define D_TX20_WIND_SPEED_MAX "Макс. скорост на вятъра" #define D_TX20_NORTH "С" #define D_TX20_EAST "И" @@ -522,24 +522,24 @@ #define D_TX20_WEST "З" // xsns_53_sml.ino -#define D_TPWRIN "Energy Total-In" -#define D_TPWROUT "Energy Total-Out" -#define D_TPWRCURR "Active Power-In/Out" -#define D_TPWRCURR1 "Active Power-In p1" -#define D_TPWRCURR2 "Active Power-In p2" -#define D_TPWRCURR3 "Active Power-In p3" -#define D_Strom_L1 "Current L1" -#define D_Strom_L2 "Current L2" -#define D_Strom_L3 "Current L3" -#define D_Spannung_L1 "Voltage L1" -#define D_Spannung_L2 "Voltage L2" -#define D_Spannung_L3 "Voltage L3" -#define D_METERNR "Meter_number" -#define D_METERSID "Service ID" -#define D_GasIN "Counter" -#define D_H2oIN "Counter" -#define D_StL1L2L3 "Current L1+L2+L3" -#define D_SpL1L2L3 "Voltage L1+L2+L3/3" +#define D_TPWRIN "Общо енергия - IN" +#define D_TPWROUT "Общо енергия - OUT" +#define D_TPWRCURR "Активна мощност - In/Out" +#define D_TPWRCURR1 "Активна мощност - In p1" +#define D_TPWRCURR2 "Активна мощност - In p2" +#define D_TPWRCURR3 "Активна мощност - In p3" +#define D_Strom_L1 "Ток L1" +#define D_Strom_L2 "Ток L2" +#define D_Strom_L3 "Ток L3" +#define D_Spannung_L1 "Напрежение L1" +#define D_Spannung_L2 "Напрежение L2" +#define D_Spannung_L3 "Напрежение L3" +#define D_METERNR "Номер_електромер" +#define D_METERSID "ID на услугата" +#define D_GasIN "Брояч" +#define D_H2oIN "Брояч" +#define D_StL1L2L3 "Ток L1+L2+L3" +#define D_SpL1L2L3 "Напрежение L1+L2+L3/3" // tasmota_template.h - keep them as short as possible to be able to fit them in GUI drop down box #define D_SENSOR_NONE "Няма" @@ -636,8 +636,8 @@ #define D_SENSOR_HRE_CLOCK "HRE Clock" #define D_SENSOR_HRE_DATA "HRE Data" #define D_SENSOR_ADE7953_IRQ "ADE7953 IRQ" -#define D_SENSOR_BUZZER "Buzzer" -#define D_SENSOR_OLED_RESET "OLED Reset" +#define D_SENSOR_BUZZER "Зумер" +#define D_SENSOR_OLED_RESET "Нулиране OLED" #define D_SENSOR_ZIGBEE_TXD "Zigbee Tx" #define D_SENSOR_ZIGBEE_RXD "Zigbee Rx" #define D_SENSOR_SOLAXX1_TX "SolaxX1 Tx" @@ -732,7 +732,7 @@ #define D_TOTAL_REACTIVE "Общо реактивна мощност" #define D_UNIT_KWARH "kVArh" #define D_UNIT_ANGLE "°" -#define D_TOTAL_ACTIVE "Total Active" +#define D_TOTAL_ACTIVE "Общо активна мощност" //SOLAXX1 #define D_PV1_VOLTAGE "Напрежение на PV1" @@ -759,40 +759,40 @@ #define D_SOLAX_ERROR_8 "Грешка - друго оборудване" //xdrv_10_scripter.ino -#define D_CONFIGURE_SCRIPT "Edit script" -#define D_SCRIPT "edit script" -#define D_SDCARD_UPLOAD "file upload" -#define D_SDCARD_DIR "sd card directory" -#define D_UPL_DONE "Done" -#define D_SCRIPT_CHARS_LEFT "chars left" -#define D_SCRIPT_CHARS_NO_MORE "no more chars" -#define D_SCRIPT_DOWNLOAD "Download" -#define D_SCRIPT_ENABLE "script enable" -#define D_SCRIPT_UPLOAD "Upload" -#define D_SCRIPT_UPLOAD_FILES "Upload files" +#define D_CONFIGURE_SCRIPT "Редакция на скрипт" +#define D_SCRIPT "редактирай скрипт" +#define D_SDCARD_UPLOAD "изпрати файл" +#define D_SDCARD_DIR "директория на SD картата" +#define D_UPL_DONE "Готово" +#define D_SCRIPT_CHARS_LEFT "оставащи символи" +#define D_SCRIPT_CHARS_NO_MORE "няма повече символи" +#define D_SCRIPT_DOWNLOAD "Изтегляне" +#define D_SCRIPT_ENABLE "активирай скрипт" +#define D_SCRIPT_UPLOAD "Изпращане" +#define D_SCRIPT_UPLOAD_FILES "Изпращане на файлове" //xsns_67_as3935.ino -#define D_AS3935_GAIN "gain:" -#define D_AS3935_ENERGY "energy:" -#define D_AS3935_DISTANCE "distance:" -#define D_AS3935_DISTURBER "disturber:" +#define D_AS3935_GAIN "усилване:" +#define D_AS3935_ENERGY "енергия:" +#define D_AS3935_DISTANCE "разстояние:" +#define D_AS3935_DISTURBER "смущение:" #define D_AS3935_VRMS "µVrms:" -#define D_AS3935_APRX "aprx.:" -#define D_AS3935_AWAY "away" -#define D_AS3935_LIGHT "lightning" -#define D_AS3935_OUT "lightning out of range" -#define D_AS3935_NOT "distance not determined" -#define D_AS3935_ABOVE "lightning overhead" -#define D_AS3935_NOISE "noise detected" -#define D_AS3935_DISTDET "disturber detected" -#define D_AS3935_INTNOEV "Interrupt with no Event!" -#define D_AS3935_NOMESS "listening..." -#define D_AS3935_ON "On" -#define D_AS3935_OFF "Off" -#define D_AS3935_INDOORS "Indoors" -#define D_AS3935_OUTDOORS "Outdoors" -#define D_AS3935_CAL_FAIL "calibration failed" -#define D_AS3935_CAL_OK "calibration set to:" +#define D_AS3935_APRX "прибл.:" +#define D_AS3935_AWAY "далече" +#define D_AS3935_LIGHT "осветление" +#define D_AS3935_OUT "осветление извън обхват" +#define D_AS3935_NOT "неопределено разстояние" +#define D_AS3935_ABOVE "околно осветление" +#define D_AS3935_NOISE "открит шум" +#define D_AS3935_DISTDET "открито смущение" +#define D_AS3935_INTNOEV "Прекъсване без Събитие!" +#define D_AS3935_NOMESS "слушане..." +#define D_AS3935_ON "Вкл." +#define D_AS3935_OFF "Изкл." +#define D_AS3935_INDOORS "На закрито" +#define D_AS3935_OUTDOORS "На открито" +#define D_AS3935_CAL_FAIL "калибрирането е неуспешно" +#define D_AS3935_CAL_OK "калибрирането е зададено на:" //xsns_68_opentherm.ino #define D_SENSOR_BOILER_OT_RX "OpenTherm RX" From 2ddfb2da07255aeb6f779ab47add22a4b09ef37f Mon Sep 17 00:00:00 2001 From: Mickael Gaillard Date: Fri, 8 May 2020 00:34:25 +0200 Subject: [PATCH 03/29] Add extra translate Signed-off-by: Mickael Gaillard --- tasmota/i18n.h | 65 ++++++++++++++++++++-------------------- tasmota/language/bg_BG.h | 4 +++ tasmota/language/cs_CZ.h | 4 +++ tasmota/language/de_DE.h | 4 +++ tasmota/language/el_GR.h | 4 +++ tasmota/language/en_GB.h | 5 +++- tasmota/language/es_ES.h | 4 +++ tasmota/language/fr_FR.h | 22 ++++++++------ tasmota/language/he_HE.h | 4 +++ tasmota/language/hu_HU.h | 4 +++ tasmota/language/it_IT.h | 4 +++ tasmota/language/ko_KO.h | 4 +++ tasmota/language/nl_NL.h | 4 +++ tasmota/language/pl_PL.h | 4 +++ tasmota/language/pt_BR.h | 4 +++ tasmota/language/pt_PT.h | 4 +++ tasmota/language/ro_RO.h | 4 +++ tasmota/language/ru_RU.h | 4 +++ tasmota/language/sk_SK.h | 4 +++ tasmota/language/sv_SE.h | 4 +++ tasmota/language/tr_TR.h | 4 +++ tasmota/language/uk_UA.h | 6 +++- tasmota/language/zh_CN.h | 4 +++ tasmota/language/zh_TW.h | 4 +++ 24 files changed, 134 insertions(+), 44 deletions(-) diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 491c0e77c..4fd7fe7d3 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -690,40 +690,39 @@ const float kSpeedConversionFactor[] = {1, // none // xdrv_02_webserver.ino #ifdef USE_WEBSERVER // {s} = , {m} = , {e} = -const char HTTP_SNS_TEMP[] PROGMEM = "{s}%s " D_TEMPERATURE "{m}%s°%c{e}"; -const char HTTP_SNS_HUM[] PROGMEM = "{s}%s " D_HUMIDITY "{m}%s%%{e}"; -const char HTTP_SNS_DEW[] PROGMEM = "{s}%s " D_DEWPOINT "{m}%s°%c{e}"; -const char HTTP_SNS_PRESSURE[] PROGMEM = "{s}%s " D_PRESSURE "{m}%s %s{e}"; -const char HTTP_SNS_SEAPRESSURE[] PROGMEM = "{s}%s " D_PRESSUREATSEALEVEL "{m}%s %s{e}"; -const char HTTP_SNS_ANALOG[] PROGMEM = "{s}%s " D_ANALOG_INPUT "%d{m}%d{e}"; -const char HTTP_SNS_ILLUMINANCE[] PROGMEM = "{s}%s " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}"; -const char HTTP_SNS_CO2[] PROGMEM = "{s}%s " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; -const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; -const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; -const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; -const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{m}%d %%{e}"; -const char HTTP_SNS_RANGE[] PROGMEM = "{s}%s " D_RANGE "{m}%d{e}"; +const char HTTP_SNS_TEMP[] PROGMEM = "{s}%s " D_TEMPERATURE "{m}%s " D_UNIT_DEGREE "%c{e}"; +const char HTTP_SNS_HUM[] PROGMEM = "{s}%s " D_HUMIDITY "{m}%s " D_UNIT_PERCENT "{e}"; +const char HTTP_SNS_DEW[] PROGMEM = "{s}%s " D_DEWPOINT "{m}%s " D_UNIT_DEGREE "%c{e}"; +const char HTTP_SNS_PRESSURE[] PROGMEM = "{s}%s " D_PRESSURE "{m}%s " "%s{e}"; +const char HTTP_SNS_SEAPRESSURE[] PROGMEM = "{s}%s " D_PRESSUREATSEALEVEL "{m}%s " "%s{e}"; +const char HTTP_SNS_ANALOG[] PROGMEM = "{s}%s " D_ANALOG_INPUT "%d{m}%d" "{e}"; +const char HTTP_SNS_ILLUMINANCE[] PROGMEM = "{s}%s " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}"; +const char HTTP_SNS_CO2[] PROGMEM = "{s}%s " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; +const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; +const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS "{e}"; +const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN "{e}"; +const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{m}%d " D_UNIT_PERCENT "{e}"; +const char HTTP_SNS_RANGE[] PROGMEM = "{s}%s " D_RANGE "{m}%d" "{e}"; +const char HTTP_SNS_VOLTAGE[] PROGMEM = "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"; +const char HTTP_SNS_CURRENT[] PROGMEM = "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"; +const char HTTP_SNS_POWER[] PROGMEM = "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"; +const char HTTP_SNS_ENERGY_TOTAL[] PROGMEM = "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; -const char HTTP_SNS_VOLTAGE[] PROGMEM = "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"; -const char HTTP_SNS_CURRENT[] PROGMEM = "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"; -const char HTTP_SNS_POWER[] PROGMEM = "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"; -const char HTTP_SNS_ENERGY_TOTAL[] PROGMEM = "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"; - -const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU; -const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION; -const char S_CONFIGURE_TEMPLATE[] PROGMEM = D_CONFIGURE_TEMPLATE; -const char S_CONFIGURE_MODULE[] PROGMEM = D_CONFIGURE_MODULE; -const char S_CONFIGURE_WIFI[] PROGMEM = D_CONFIGURE_WIFI; -const char S_NO_NETWORKS_FOUND[] PROGMEM = D_NO_NETWORKS_FOUND; -const char S_CONFIGURE_LOGGING[] PROGMEM = D_CONFIGURE_LOGGING; -const char S_CONFIGURE_OTHER[] PROGMEM = D_CONFIGURE_OTHER; -const char S_SAVE_CONFIGURATION[] PROGMEM = D_SAVE_CONFIGURATION; -const char S_RESET_CONFIGURATION[] PROGMEM = D_RESET_CONFIGURATION; -const char S_RESTORE_CONFIGURATION[] PROGMEM = D_RESTORE_CONFIGURATION; -const char S_FIRMWARE_UPGRADE[] PROGMEM = D_FIRMWARE_UPGRADE; -const char S_CONSOLE[] PROGMEM = D_CONSOLE; -const char S_INFORMATION[] PROGMEM = D_INFORMATION; -const char S_RESTART[] PROGMEM = D_RESTART; +const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU; +const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION; +const char S_CONFIGURE_TEMPLATE[] PROGMEM = D_CONFIGURE_TEMPLATE; +const char S_CONFIGURE_MODULE[] PROGMEM = D_CONFIGURE_MODULE; +const char S_CONFIGURE_WIFI[] PROGMEM = D_CONFIGURE_WIFI; +const char S_NO_NETWORKS_FOUND[] PROGMEM = D_NO_NETWORKS_FOUND; +const char S_CONFIGURE_LOGGING[] PROGMEM = D_CONFIGURE_LOGGING; +const char S_CONFIGURE_OTHER[] PROGMEM = D_CONFIGURE_OTHER; +const char S_SAVE_CONFIGURATION[] PROGMEM = D_SAVE_CONFIGURATION; +const char S_RESET_CONFIGURATION[] PROGMEM = D_RESET_CONFIGURATION; +const char S_RESTORE_CONFIGURATION[] PROGMEM = D_RESTORE_CONFIGURATION; +const char S_FIRMWARE_UPGRADE[] PROGMEM = D_FIRMWARE_UPGRADE; +const char S_CONSOLE[] PROGMEM = D_CONSOLE; +const char S_INFORMATION[] PROGMEM = D_INFORMATION; +const char S_RESTART[] PROGMEM = D_RESTART; #endif // USE_WEBSERVER const uint32_t MARKER_START = 0x5AA55AA5; diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index 14bf6f698..dd59cd5ac 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -690,13 +690,16 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "gal/min" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOMETER_PER_HOUR "km/h" #define D_UNIT_KILOOHM "kΩ" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "s" #define D_UNIT_SECTORS "сектори" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 6da814036..4169fbd51 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -690,13 +690,16 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "hod" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" #define D_UNIT_KILOOHM "kΩ" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sektory" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index 63d38dc58..e48fa2bc5 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "s" #define D_UNIT_SECTORS "Sektoren" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index d89a8c5ce..4fca8a1c2 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectors" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index dcc945c80..8e53287a7 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -690,13 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" -#define D_UNIT_KELVIN "°K" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -714,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectors" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 19cbf5e66..a63ca000e 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "seg" #define D_UNIT_SECTORS "sectores" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index 44e1d1020..eca12d8ee 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -79,7 +79,7 @@ #define D_DATA "Donnée" #define D_DARKLIGHT "Sombre" #define D_DEBUG "Debug" -#define D_DEWPOINT "Dew point" +#define D_DEWPOINT "Point de rosée" #define D_DISABLED "Désactivé" #define D_DISTANCE "Distance" #define D_DNS_SERVER "Serveur DNS" @@ -138,7 +138,7 @@ #define D_PROGRAM_SIZE "Taille programme" #define D_PROJECT "Projet" #define D_RAIN "Pluie" -#define D_RANGE "Range" +#define D_RANGE "Intervalle" #define D_RECEIVED "Reçu" #define D_RESTART "Redémarrage" #define D_RESTARTING "Redémarre" @@ -189,8 +189,8 @@ // tasmota.ino #define D_WARNING_MINIMAL_VERSION "ATTENTION Cette version ne supporte pas les réglages persistants" -#define D_LEVEL_10 "level 1-0" -#define D_LEVEL_01 "level 0-1" +#define D_LEVEL_10 "niveau 1-0" +#define D_LEVEL_01 "niveau 0-1" #define D_SERIAL_LOGGING_DISABLED "Journalisation série désactivée" #define D_SYSLOG_LOGGING_REENABLED "Jounalisation SysLog réactivée" @@ -402,7 +402,7 @@ #define D_DOMOTICZ_TEMP_HUM "Temp,Hum" #define D_DOMOTICZ_TEMP_HUM_BARO "Temp,Hum,Baro" #define D_DOMOTICZ_POWER_ENERGY "Puissance,Énergie" - #define D_DOMOTICZ_ILLUMINANCE "Illuminance" + #define D_DOMOTICZ_ILLUMINANCE "Éclairement" #define D_DOMOTICZ_COUNT "Compteur/PM1" #define D_DOMOTICZ_VOLTAGE "Tension/PM2,5" #define D_DOMOTICZ_CURRENT "Courant/PM10" @@ -536,8 +536,8 @@ #define D_Spannung_L3 "Voltage L3" #define D_METERNR "Meter_number" #define D_METERSID "Service ID" -#define D_GasIN "Counter" -#define D_H2oIN "Counter" +#define D_GasIN "Compteur" +#define D_H2oIN "Compteur" #define D_StL1L2L3 "Current L1+L2+L3" #define D_SpL1L2L3 "Voltage L1+L2+L3/3" @@ -663,8 +663,8 @@ #define D_SENSOR_SLAVE_TX "Esclave TX" #define D_SENSOR_SLAVE_RX "Esclave RX" #define D_SENSOR_SLAVE_RESET "Esclave Rst" -#define D_SENSOR_GPS_TX "GPS TX" #define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" #define D_SENSOR_HM10_RX "HM10 RX" #define D_SENSOR_HM10_TX "HM10 TX" #define D_SENSOR_LE01MR_RX "LE-01MR Rx" @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "gal/mn" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" @@ -709,10 +712,11 @@ #define D_UNIT_MILLIMETER "mm" #define D_UNIT_MILLIMETER_MERCURY "mmHg" #define D_UNIT_MILLISECOND "ms" -#define D_UNIT_MINUTE "mn" +#define D_UNIT_MINUTE "min" // https://fr.wikipedia.org/wiki/Minute_(temps)#Symbole%20et%20d%C3%A9finition #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "s" #define D_UNIT_SECTORS "secteurs" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 058789eb3..0495f5e84 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectors" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index cad66ebee..f249dafa6 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "s" #define D_UNIT_SECTORS "szektorok" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 5c6dcae10..4b942015a 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "o" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "settori" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index 7a84823ff..25049050c 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "시" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "초" #define D_UNIT_SECTORS "섹터" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index d5922cb6a..c5afba2bf 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectoren" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index f50d22de4..d12059b6c 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Godz" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sektory" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index dab2a3a54..c2ff38fd4 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "H" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "s" #define D_UNIT_SECTORS "setores" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index e056245c1..af78b34e7 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "setores" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 83bb05575..09479b806 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectors" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 93cbf2c72..1630a813f 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "А" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Ч" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "гПа" #define D_UNIT_SECOND "сек" #define D_UNIT_SECTORS "секторов" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 0a6415c16..45b68a601 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -690,13 +690,16 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "hod" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" #define D_UNIT_KILOOHM "kΩ" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sek" #define D_UNIT_SECTORS "sektory" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index 438bcd3c1..9bca64261 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Tim" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "ink" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sek" #define D_UNIT_SECTORS "sektorer" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index 740b92774..c8ad206ce 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "A" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "ppd" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "hPa" #define D_UNIT_SECOND "sec" #define D_UNIT_SECTORS "sectors" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index bf0377e7b..78972efb5 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -690,13 +690,16 @@ // Units #define D_UNIT_AMPERE "А" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cм" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Гц" #define D_UNIT_HOUR "г" #define D_UNIT_GALLONS "гал" #define D_UNIT_GALLONS_PER_MIN "гал/хв" #define D_UNIT_INCREMENTS "інк" -#define D_UNIT_KILOMETER "km" +#define D_UNIT_KELVIN "K" +#define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "кг" #define D_UNIT_KILOMETER_PER_HOUR "км/г" // or "km/h" #define D_UNIT_KILOOHM "㏀" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "млрд⁻¹" #define D_UNIT_PARTS_PER_DECILITER "децилітр⁻¹" #define D_UNIT_PARTS_PER_MILLION "млн⁻¹" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "гПа" #define D_UNIT_SECOND "сек" #define D_UNIT_SECTORS "секторів" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index 49e7b8592..0eb04e35c 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "安" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "厘米" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "赫兹" #define D_UNIT_HOUR "时" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "千克" #define D_UNIT_KILOMETER_PER_HOUR "公里/时" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "每分升" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "百帕" #define D_UNIT_SECOND "秒" #define D_UNIT_SECTORS "扇区" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index 7f92495a4..4d834862f 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -690,12 +690,15 @@ // Units #define D_UNIT_AMPERE "安" +#define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" +#define D_UNIT_DEGREE "°" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "時" #define D_UNIT_GALLONS "gal" #define D_UNIT_GALLONS_PER_MIN "g/m" #define D_UNIT_INCREMENTS "inc" +#define D_UNIT_KELVIN "K" #define D_UNIT_KILOMETER "km" #define D_UNIT_KILOGRAM "kg" #define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h" @@ -713,6 +716,7 @@ #define D_UNIT_PARTS_PER_BILLION "ppb" #define D_UNIT_PARTS_PER_DECILITER "每分升" #define D_UNIT_PARTS_PER_MILLION "ppm" +#define D_UNIT_PERCENT "%%" #define D_UNIT_PRESSURE "百帕" #define D_UNIT_SECOND "秒" #define D_UNIT_SECTORS "扇區" From b6165d5a18a10a4b9e49eb77a23c817aacf2c768 Mon Sep 17 00:00:00 2001 From: Mickael Gaillard Date: Fri, 8 May 2020 00:53:13 +0200 Subject: [PATCH 04/29] Add Translate Fahrenheit Signed-off-by: Mickael Gaillard --- tasmota/language/bg_BG.h | 1 + tasmota/language/cs_CZ.h | 1 + tasmota/language/de_DE.h | 1 + tasmota/language/el_GR.h | 1 + tasmota/language/en_GB.h | 1 + tasmota/language/es_ES.h | 1 + tasmota/language/fr_FR.h | 1 + tasmota/language/he_HE.h | 1 + tasmota/language/hu_HU.h | 1 + tasmota/language/it_IT.h | 1 + tasmota/language/ko_KO.h | 1 + tasmota/language/nl_NL.h | 1 + tasmota/language/pl_PL.h | 1 + tasmota/language/pt_BR.h | 1 + tasmota/language/pt_PT.h | 1 + tasmota/language/ro_RO.h | 1 + tasmota/language/ru_RU.h | 1 + tasmota/language/sk_SK.h | 1 + tasmota/language/sv_SE.h | 1 + tasmota/language/tr_TR.h | 1 + tasmota/language/uk_UA.h | 1 + tasmota/language/zh_CN.h | 1 + tasmota/language/zh_TW.h | 1 + tasmota/support.ino | 3 ++- 24 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index dd59cd5ac..305f88894 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 4169fbd51..a048c890a 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "hod" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index e48fa2bc5..f1150405e 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index 4fca8a1c2..4d9424b5d 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 8e53287a7..072148a73 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index a63ca000e..2d82250ec 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index eca12d8ee..342dcef80 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 0495f5e84..9402c722b 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index f249dafa6..9ef7d57c2 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 4b942015a..5c5c8a537 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "o" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index 25049050c..18633f373 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "시" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index c5afba2bf..b0ae25e7b 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index d12059b6c..2c6e38e7c 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Godz" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index c2ff38fd4..d1e51a077 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "H" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index af78b34e7..1227a738a 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 09479b806..616e1d9b1 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 1630a813f..7a46a56db 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Ч" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 45b68a601..3bd5e2ff8 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "hod" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index 9bca64261..d5ad82e6f 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "Tim" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index c8ad206ce..abbc68dbf 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "h" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index 78972efb5..915925d1d 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cм" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Гц" #define D_UNIT_HOUR "г" #define D_UNIT_GALLONS "гал" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index 0eb04e35c..5e76e6069 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "厘米" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "赫兹" #define D_UNIT_HOUR "时" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index 4d834862f..e04296c5a 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -693,6 +693,7 @@ #define D_UNIT_CELSIUS "C" #define D_UNIT_CENTIMETER "cm" #define D_UNIT_DEGREE "°" +#define D_UNIT_FAHRENHEIT "F" #define D_UNIT_HERTZ "Hz" #define D_UNIT_HOUR "時" #define D_UNIT_GALLONS "gal" diff --git a/tasmota/support.ino b/tasmota/support.ino index ff767caf6..c2c0819c1 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -620,7 +620,8 @@ float ConvertTempToCelsius(float c) char TempUnit(void) { - return (Settings.flag.temperature_conversion) ? 'F' : 'C'; // SetOption8 - Switch between Celsius or Fahrenheit + // SetOption8 - Switch between Celsius or Fahrenheit + return (Settings.flag.temperature_conversion) ? D_UNIT_FAHRENHEIT : D_UNIT_CELSIUS; } float ConvertHumidity(float h) From 946c9a03937ff88a2f686f2518c473a2b1020083 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 08:49:18 +0200 Subject: [PATCH 05/29] Use extends in Core flavour --- platformio_override_sample.ini | 36 ++++++++-------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 22bd8cf70..f4c1d9ccd 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -83,16 +83,14 @@ extra_scripts = ${scripts_defaults.extra_scripts} [tasmota_stage] ; *** Esp8266 core for Arduino version Tasmota stage -platform = espressif8266@2.5.0 +extends = tasmota_core platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#36e047e908cfa6eafaaf824988070b49f2c2ff2a -build_flags = ${esp82xx_defaults.build_flags} - -DBEARSSL_SSL_BASIC +; +; *********** Alternative Options, enable only if you know exactly what you do ******** ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190313 -; NONOSDK22x_190703 = 2.2.1+100-dev(38a443e) (Tasmota default) (Firmware 2K smaller than NONOSDK22x_191105) - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 ; NONOSDK22x_191024 = 2.2.1+111-dev(5ab15d1) ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_191024 ; NONOSDK22x_191105 = 2.2.1+113-dev(bb83b9b) @@ -109,34 +107,24 @@ build_flags = ${esp82xx_defaults.build_flags} ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH ; lwIP 2 - Higher Bandwidth Low Memory no Features ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY_LOW_FLASH -; lwIP 2 - Higher Bandwidth no Features (Tasmota default) - -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -; VTABLES in Flash (Tasmota default) - -DVTABLES_IN_FLASH ; VTABLES in Heap ; -DVTABLES_IN_DRAM ; VTABLES in IRAM ; -DVTABLES_IN_IRAM -; enable one option set -> No exception recommended -; No exception code in firmware - -fno-exceptions - -lstdc++ -; Exception code in firmware /needs much space! 90k +; Exception code in firmware /needs much space! ; -fexceptions ; -lstdc++-exc [core_stage] ; *** Esp8266 core for Arduino version latest development version -platform = espressif8266@2.5.0 +extends = tasmota_core platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git -build_flags = ${esp82xx_defaults.build_flags} - -DBEARSSL_SSL_BASIC +; +; *********** Alternative Options, enable only if you know exactly what you do ******** ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190313 -; NONOSDK22x_190703 = 2.2.1+100-dev(38a443e) (Tasmota default) (Firmware 2K smaller than NONOSDK22x_191105) - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 ; NONOSDK22x_191024 = 2.2.1+111-dev(5ab15d1) ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_191024 ; NONOSDK22x_191105 = 2.2.1+113-dev(bb83b9b) @@ -153,19 +141,11 @@ build_flags = ${esp82xx_defaults.build_flags} ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH ; lwIP 2 - Higher Bandwidth Low Memory no Features ; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY_LOW_FLASH -; lwIP 2 - Higher Bandwidth no Features (Tasmota default) - -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -; VTABLES in Flash (Tasmota default) - -DVTABLES_IN_FLASH ; VTABLES in Heap ; -DVTABLES_IN_DRAM ; VTABLES in IRAM ; -DVTABLES_IN_IRAM -; enable one option set -> No exception recommended -; No exception code in firmware - -fno-exceptions - -lstdc++ -; Exception code in firmware /needs much space! 90k +; Exception code in firmware /needs much space! ; -fexceptions ; -lstdc++-exc From 41d8b33d1bccec0bace54bc098a6975466da4b3a Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 08:57:00 +0200 Subject: [PATCH 06/29] Use Core 2.7.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 618e7e572..0f2ce41cb 100755 --- a/platformio.ini +++ b/platformio.ini @@ -115,7 +115,7 @@ build_flags = ${tasmota_core.build_flags} [tasmota_core] ; *** Esp8266 Arduino core 2.7.0 platform = espressif8266@2.5.0 -platform_packages = framework-arduinoespressif8266 @ https://github.com/tasmota/Arduino/releases/download/2.7.0/esp8266-2.7.0.zip +platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino/releases/download/2.7.1/esp8266-2.7.1.zip build_flags = ${esp82xx_defaults.build_flags} -DBEARSSL_SSL_BASIC ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) From 38b8742c6f6ad3b0f2ee3bd38ae53821eed45f5f Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 09:03:10 +0200 Subject: [PATCH 07/29] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d8bb487bd..8599df428 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ - [ ] The pull request is done against the latest dev branch - [ ] Only relevant files were touched - [ ] Only one feature/fix was added per PR. - - [ ] The code change is tested and works on core ESP8266 V.2.7.0 + - [ ] The code change is tested and works on core ESP8266 V.2.7.1 - [ ] The code change is tested and works on core ESP32 V.1.12.0 - [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla). From 7e165a03c3b3d662ceb84dfa7faa557044bfd44e Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 09:11:46 +0200 Subject: [PATCH 08/29] Update platformio.ini --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0f2ce41cb..a6f4c8863 100755 --- a/platformio.ini +++ b/platformio.ini @@ -113,9 +113,9 @@ platform_packages = ${tasmota_core.platform_packages} build_flags = ${tasmota_core.build_flags} [tasmota_core] -; *** Esp8266 Arduino core 2.7.0 -platform = espressif8266@2.5.0 -platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino/releases/download/2.7.1/esp8266-2.7.1.zip +; *** Esp8266 Arduino core 2.7.1 +platform = espressif8266@2.5.1 +platform_packages = build_flags = ${esp82xx_defaults.build_flags} -DBEARSSL_SSL_BASIC ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) From 5d413b917ab2148530f661668d19a86d2272b17e Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 09:50:03 +0200 Subject: [PATCH 09/29] Move standard ESP8266 core... build_flags to section `[esp82xx_defaults]` --- platformio.ini | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index a6f4c8863..8a22cf67e 100755 --- a/platformio.ini +++ b/platformio.ini @@ -99,6 +99,16 @@ build_flags = ${esp_defaults.build_flags} -D NDEBUG -mtarget-align -DFP_IN_IROM + -DBEARSSL_SSL_BASIC + ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + ; lwIP 2 - Higher Bandwidth no Features + -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH + ; VTABLES in Flash + -DVTABLES_IN_FLASH + ; No exception code in firmware + -fno-exceptions + -lstdc++ ; the following removes the 4-bytes alignment for PSTR(), waiting for a cleaner flag from Arduino Core -DPSTR\(s\)=\(__extension__\(\{static\ const\ char\ __c\[\]\ __attribute__\(\(__aligned__\(1\)\)\)\ __attribute__\(\(section\(\ \"\\\\\".irom0.pstr.\"\ __FILE__\ \".\"\ __STRINGIZE\(__LINE__\)\ \".\"\ \ __STRINGIZE\(__COUNTER__\)\ \"\\\\\"\,\ \\\\\"aSM\\\\\"\,\ \@progbits\,\ 1\ \#\"\)\)\)\ =\ \(s\)\;\ \&__c\[0\]\;\}\)\) @@ -117,13 +127,3 @@ build_flags = ${tasmota_core.build_flags} platform = espressif8266@2.5.1 platform_packages = build_flags = ${esp82xx_defaults.build_flags} - -DBEARSSL_SSL_BASIC -; NONOSDK22x_190703 = 2.2.2-dev(38a443e) - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 -; lwIP 2 - Higher Bandwidth no Features - -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH -; VTABLES in Flash - -DVTABLES_IN_FLASH -; No exception code in firmware - -fno-exceptions - -lstdc++ From 82ccd7ed378d5459cb1f0a2fe526f80e8be891ad Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 11:17:18 +0200 Subject: [PATCH 10/29] Fix compilation error --- tasmota/support.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index c2c0819c1..45c6b0e7a 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -621,7 +621,7 @@ float ConvertTempToCelsius(float c) char TempUnit(void) { // SetOption8 - Switch between Celsius or Fahrenheit - return (Settings.flag.temperature_conversion) ? D_UNIT_FAHRENHEIT : D_UNIT_CELSIUS; + return (Settings.flag.temperature_conversion) ? D_UNIT_FAHRENHEIT[0] : D_UNIT_CELSIUS[0]; } float ConvertHumidity(float h) From 33acbd1a8b309ac89a86bb0dadf46773cbac7290 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 12:04:52 +0200 Subject: [PATCH 11/29] Fix Domoticz range check --- tasmota/xdrv_07_domoticz.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasmota/xdrv_07_domoticz.ino b/tasmota/xdrv_07_domoticz.ino index 997905eff..556997bb9 100644 --- a/tasmota/xdrv_07_domoticz.ino +++ b/tasmota/xdrv_07_domoticz.ino @@ -110,7 +110,8 @@ void DomoticzUpdateFanState(void) void MqttPublishDomoticzPowerState(uint8_t device) { if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT - if ((device < 1) || (device > devices_present) || (device > MAX_DOMOTICZ_IDX)) { device = 1; } + if (device < 1) { device = 1; } + if ((device > devices_present) || (device > MAX_DOMOTICZ_IDX)) { return; } if (Settings.domoticz_relay_idx[device -1]) { #ifdef USE_SHUTTER if (domoticz_is_shutter) { From 1dbf713e047e28ba8eb2fecfd7bcda8108fc9cd6 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Fri, 8 May 2020 12:24:05 +0200 Subject: [PATCH 12/29] Use for Tasmota_stage commit `a5432625d93f60d7e28cfdc5ed8abb3e0151951d` = release core 2.7.1 too. --- platformio_override_sample.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index f4c1d9ccd..e14e570c8 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -84,8 +84,7 @@ extra_scripts = ${scripts_defaults.extra_scripts} [tasmota_stage] ; *** Esp8266 core for Arduino version Tasmota stage extends = tasmota_core -platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#36e047e908cfa6eafaaf824988070b49f2c2ff2a -; +platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#a5432625d93f60d7e28cfdc5ed8abb3e0151951d ; *********** Alternative Options, enable only if you know exactly what you do ******** ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 From b53dee396ecdde3ee687d426cb1a12ce0f894a9e Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 15:42:44 +0200 Subject: [PATCH 13/29] Lower minimum PWMFrequency to 40Hz --- tasmota/core_esp8266_wiring_pwm.cpp | 4 ++-- tasmota/tasmota.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 90d69b313..984ce92db 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -45,8 +45,8 @@ extern void __analogWriteRange(uint32_t range) { extern void __analogWriteFreq(uint32_t freq) { - if (freq < 100) { - analogFreq = 100; + if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. + analogFreq = 40; } else if (freq > 60000) { analogFreq = 60000; } else { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 77bbb6786..cca1af80f 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -106,7 +106,7 @@ const uint32_t PWM_RANGE = 1023; // 255..1023 needs to be devisible b //const uint16_t PWM_FREQ = 910; // 100..1000 Hz led refresh (iTead value) const uint16_t PWM_FREQ = 223; // 100..4000 Hz led refresh const uint16_t PWM_MAX = 4000; // [PWM_MAX] Maximum frequency - Default: 4000 -const uint16_t PWM_MIN = 100; // [PWM_MIN] Minimum frequency - Default: 100 +const uint16_t PWM_MIN = 40; // [PWM_MIN] Minimum frequency - Default: 40 // For Dimmers use double of your mains AC frequecy (100 for 50Hz and 120 for 60Hz) // For Controlling Servos use 50 and also set PWM_FREQ as 50 (DO NOT USE THESE VALUES FOR DIMMERS) From 0572644c7f0d19a402326e4ca05889f42d612108 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 16:06:02 +0200 Subject: [PATCH 14/29] Change PWM Frequencies - Change default PWM Frequency to 977 Hz from 880 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz --- RELEASENOTES.md | 4 +++- tasmota/CHANGELOG.md | 3 +++ tasmota/tasmota.h | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d880bc05e..e86665a5d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -56,7 +56,8 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Breaking Change Device Groups multicast address and port (#8270) - Change PWM implementation to Arduino #7231 removing support for Core versions before 2.6.3 -- Change default PWM Frequency to 223 Hz instead of 880 Hz for less interrupt pressure +- Change default PWM Frequency to 977 Hz from 880 Hz +- Change minimum PWM Frequency from 100 Hz to 40 Hz - Change flash access removing support for any Core before 2.6.3 - Change HM-10 sensor type detection and add features (#7962) - Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034) @@ -100,4 +101,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add more accuracy to GPS NTP server (#8088) - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) +- Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index cb730a8cb..ebfd0e3ae 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -5,8 +5,11 @@ - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) +- Add support for Thermostat control by arijav (#8212) - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) +- Change default PWM Frequency to 977 Hz from 223 Hz +- Change minimum PWM Frequency from 100 Hz to 40 Hz ### 8.2.0.5 20200425 diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index cca1af80f..ab152f9ec 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -104,7 +104,7 @@ const uint16_t WS2812_MAX_LEDS = 512; // Max number of LEDs const uint32_t PWM_RANGE = 1023; // 255..1023 needs to be devisible by 256 //const uint16_t PWM_FREQ = 1000; // 100..1000 Hz led refresh //const uint16_t PWM_FREQ = 910; // 100..1000 Hz led refresh (iTead value) -const uint16_t PWM_FREQ = 223; // 100..4000 Hz led refresh +const uint16_t PWM_FREQ = 977; // 100..4000 Hz led refresh const uint16_t PWM_MAX = 4000; // [PWM_MAX] Maximum frequency - Default: 4000 const uint16_t PWM_MIN = 40; // [PWM_MIN] Minimum frequency - Default: 40 // For Dimmers use double of your mains AC frequecy (100 for 50Hz and 120 for 60Hz) From f66a0ee561fa794119163aeceeb13660a77d4c4a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 8 May 2020 17:17:09 +0200 Subject: [PATCH 15/29] Add Webcam commands --- tasmota/settings.h | 35 +++++++- tasmota/xdrv_81_webcam.ino | 176 ++++++++++++++++++++++++++----------- 2 files changed, 157 insertions(+), 54 deletions(-) diff --git a/tasmota/settings.h b/tasmota/settings.h index 6b74cb269..3925aa30d 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -182,6 +182,35 @@ typedef union { }; } Timer; +typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... + uint32_t data; + struct { + uint32_t stream : 1; + uint32_t mirror : 1; + uint32_t flip : 1; + uint32_t spare3 : 1; + uint32_t spare4 : 1; + uint32_t spare5 : 1; + uint32_t spare6 : 1; + uint32_t spare7 : 1; + uint32_t spare8 : 1; + uint32_t spare9 : 1; + uint32_t spare10 : 1; + uint32_t spare11 : 1; + uint32_t spare12 : 1; + uint32_t spare13 : 1; + uint32_t spare14 : 1; + uint32_t spare15 : 1; + uint32_t spare16 : 1; + uint32_t spare17 : 1; + uint32_t spare18 : 1; + uint32_t contrast : 3; + uint32_t brightness : 3; + uint32_t saturation : 3; + uint32_t resolution : 4; + }; +} WebCamCfg; + typedef union { uint16_t data; struct { @@ -365,9 +394,11 @@ struct { myio my_gp; // 3AC - 2 x 40 bytes (ESP32) mytmplt user_template; // 3FC - 2 x 37 bytes (ESP32) - uint8_t free_esp32_446[10]; // 446 + uint8_t free_esp32_446[6]; // 446 - uint8_t esp32_webcam_resolution; // 450 + WebCamCfg webcam_config; // 44C + + uint8_t free_esp32_450[1]; // 450 #endif // ESP8266 - ESP32 char serial_delimiter; // 451 diff --git a/tasmota/xdrv_81_webcam.ino b/tasmota/xdrv_81_webcam.ino index a317b1aaa..15c2dd4ba 100644 --- a/tasmota/xdrv_81_webcam.ino +++ b/tasmota/xdrv_81_webcam.ino @@ -230,11 +230,19 @@ uint32_t wc_setup(int32_t fsiz) { sensor_t * wc_s = esp_camera_sensor_get(); // initial sensors are flipped vertically and colors are a bit saturated +/* if (OV3660_PID == wc_s->id.PID) { wc_s->set_vflip(wc_s, 1); // flip it back wc_s->set_brightness(wc_s, 1); // up the brightness just a bit wc_s->set_saturation(wc_s, -2); // lower the saturation } +*/ + wc_s->set_vflip(wc_s, Settings.webcam_config.flip); + wc_s->set_hmirror(wc_s, Settings.webcam_config.mirror); + wc_s->set_brightness(wc_s, Settings.webcam_config.brightness -2); // up the brightness just a bit + wc_s->set_saturation(wc_s, Settings.webcam_config.saturation -2); // lower the saturation + wc_s->set_contrast(wc_s, Settings.webcam_config.contrast -2); // keep contrast + // drop down frame size for higher initial frame rate wc_s->set_framesize(wc_s, (framesize_t)fsiz); @@ -243,7 +251,6 @@ uint32_t wc_setup(int32_t fsiz) { wc_height = wc_fb->height; esp_camera_fb_return(wc_fb); - #ifdef USE_FACE_DETECT fd_init(); #endif @@ -461,11 +468,11 @@ WiFiClient client; void handleMjpeg(void) { AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver")); - //if (!wc_stream_active) { + if (!wc_stream_active) { wc_stream_active = 1; client = CamServer->client(); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client")); - //} + } } #ifdef USE_FACE_DETECT @@ -613,11 +620,9 @@ void handleMjpeg_task(void) { bool jpeg_converted = false; if (!client.connected()) { - wc_stream_active = 0; AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail")); - goto exit; + wc_stream_active = 0; } - if (1 == wc_stream_active) { client.flush(); client.setTimeout(3); @@ -626,15 +631,15 @@ void handleMjpeg_task(void) { "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" "\r\n"); wc_stream_active = 2; - } else { + } + if (2 == wc_stream_active) { wc_fb = esp_camera_fb_get(); if (!wc_fb) { - wc_stream_active = 0; AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame fail")); - goto exit; + wc_stream_active = 0; } - - + } + if (2 == wc_stream_active) { if (wc_fb->format != PIXFORMAT_JPEG) { jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); if (!jpeg_converted){ @@ -673,13 +678,11 @@ void handleMjpeg_task(void) { if (jpeg_converted) { free(_jpg_buf); } esp_camera_fb_return(wc_fb); //AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame")); - -exit: - if (!wc_stream_active) { - AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit")); - client.flush(); - client.stop(); - } + } + if (0 == wc_stream_active) { + AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit")); + client.flush(); + client.stop(); } } @@ -750,13 +753,6 @@ void detect_motion(void) { } } -void wc_show_stream(void) { - if (CamServer) { - WSContentSend_P(PSTR("

Webcam stream

"), - WiFi.localIP().toString().c_str()); - } -} - uint32_t wc_set_streamserver(uint32_t flag) { if (global_state.wifi_down) { return 0; } @@ -783,17 +779,30 @@ uint32_t wc_set_streamserver(uint32_t flag) { return 0; } -void WcStreamControl(uint32_t resolution) { - wc_set_streamserver(resolution); - /*if (0 == resolution) { - resolution=-1; - }*/ +void WcStreamControl() { + wc_set_streamserver(Settings.webcam_config.stream); + int resolution = (!Settings.webcam_config.stream) ? -1 : Settings.webcam_config.resolution; wc_setup(resolution); } +void WcShowStream(void) { + if (Settings.webcam_config.stream) { + if (!CamServer) { + WcStreamControl(); + delay(50); // Give the webcam webserver some time to prepare the stream + } + if (CamServer) { + WSContentSend_P(PSTR("

Webcam stream

"), + WiFi.localIP().toString().c_str()); + } + } +} + void wc_loop(void) { - if (CamServer) { CamServer->handleClient(); } - if (wc_stream_active) { handleMjpeg_task(); } + if (CamServer) { + CamServer->handleClient(); + if (wc_stream_active) { handleMjpeg_task(); } + } if (motion_detect) { detect_motion(); } #ifdef USE_FACE_DETECT if (face_detect_time) { detect_face(); } @@ -833,34 +842,103 @@ red led = gpio 33 */ void WcInit(void) { - if (Settings.esp32_webcam_resolution > 10) { - Settings.esp32_webcam_resolution = 0; + if (!Settings.webcam_config.data) { + Settings.webcam_config.stream = 1; + Settings.webcam_config.resolution = 5; + Settings.webcam_config.flip = 0; + Settings.webcam_config.mirror = 0; + Settings.webcam_config.saturation = 0; // -2 + Settings.webcam_config.brightness = 3; // 1 + Settings.webcam_config.contrast = 2; // 0 } } - /*********************************************************************************************\ * Commands \*********************************************************************************************/ -#define D_CMND_WEBCAM "Webcam" +#define D_PRFX_WEBCAM "WC" +#define D_CMND_WC_STREAM "Stream" +#define D_CMND_WC_RESOLUTION "Resolution" +#define D_CMND_WC_MIRROR "Mirror" +#define D_CMND_WC_FLIP "Flip" +#define D_CMND_WC_SATURATION "Saturation" +#define D_CMND_WC_BRIGHTNESS "Brightness" +#define D_CMND_WC_CONTRAST "Contrast" -const char kWCCommands[] PROGMEM = "|" // no prefix - D_CMND_WEBCAM +const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix + "|" D_CMND_WC_STREAM "|" D_CMND_WC_RESOLUTION "|" D_CMND_WC_MIRROR "|" D_CMND_WC_FLIP "|" + D_CMND_WC_SATURATION "|" D_CMND_WC_BRIGHTNESS "|" D_CMND_WC_CONTRAST ; void (* const WCCommand[])(void) PROGMEM = { - &CmndWebcam, + &CmndWebcam, &CmndWebcamStream, &CmndWebcamResolution, &CmndWebcamMirror, &CmndWebcamFlip, + &CmndWebcamSaturation, &CmndWebcamBrightness, &CmndWebcamContrast }; void CmndWebcam(void) { - uint32_t flag = 0; - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { - Settings.esp32_webcam_resolution=XdrvMailbox.payload; - WcStreamControl(Settings.esp32_webcam_resolution); + Response_P(PSTR("{\"" D_PRFX_WEBCAM "\":{\"" D_CMND_WC_STREAM "\":%d,\"" D_CMND_WC_RESOLUTION "\":%d,\"" D_CMND_WC_MIRROR "\":%d,\"" + D_CMND_WC_FLIP "\":%d,\"" + D_CMND_WC_SATURATION "\":%d,\"" D_CMND_WC_BRIGHTNESS "\":%d,\"" D_CMND_WC_CONTRAST "\":%d}}"), + Settings.webcam_config.stream, Settings.webcam_config.resolution, Settings.webcam_config.mirror, + Settings.webcam_config.flip, + Settings.webcam_config.saturation -2, Settings.webcam_config.brightness -2, Settings.webcam_config.contrast -2); +} + +void CmndWebcamStream(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.stream = XdrvMailbox.payload; + if (!Settings.webcam_config.stream) { WcStreamControl(); } // Stop stream } - if (CamServer) { flag = 1; } - Response_P(PSTR("{\"" D_CMND_WEBCAM "\":{\"Streaming\":\"%s\"}"),GetStateText(flag)); + ResponseCmndStateText(Settings.webcam_config.stream); +} + +void CmndWebcamResolution(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { + Settings.webcam_config.resolution = XdrvMailbox.payload; + wc_set_options(0, Settings.webcam_config.resolution); + } + ResponseCmndNumber(Settings.webcam_config.resolution); +} + +void CmndWebcamMirror(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.mirror = XdrvMailbox.payload; + wc_set_options(3, Settings.webcam_config.mirror); + } + ResponseCmndStateText(Settings.webcam_config.mirror); +} + +void CmndWebcamFlip(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.webcam_config.flip = XdrvMailbox.payload; + wc_set_options(2, Settings.webcam_config.flip); + } + ResponseCmndStateText(Settings.webcam_config.flip); +} + +void CmndWebcamSaturation(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.saturation = XdrvMailbox.payload +2; + wc_set_options(6, Settings.webcam_config.saturation -2); + } + ResponseCmndNumber(Settings.webcam_config.saturation -2); +} + +void CmndWebcamBrightness(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.brightness = XdrvMailbox.payload +2; + wc_set_options(5, Settings.webcam_config.brightness -2); + } + ResponseCmndNumber(Settings.webcam_config.brightness -2); +} + +void CmndWebcamContrast(void) { + if ((XdrvMailbox.payload >= -2) && (XdrvMailbox.payload <= 2)) { + Settings.webcam_config.contrast = XdrvMailbox.payload +2; + wc_set_options(4, Settings.webcam_config.contrast -2); + } + ResponseCmndNumber(Settings.webcam_config.contrast -2); } /*********************************************************************************************\ @@ -878,13 +956,7 @@ bool Xdrv81(uint8_t function) { wc_pic_setup(); break; case FUNC_WEB_ADD_MAIN_BUTTON: - if (Settings.esp32_webcam_resolution) { -//#ifndef USE_SCRIPT - WcStreamControl(Settings.esp32_webcam_resolution); - delay(50); // Give the webcam webserver some time to prepare the stream - wc_show_stream(); -//#endif - } + WcShowStream(); break; case FUNC_COMMAND: result = DecodeCommand(kWCCommands, WCCommand); From 3462c8d05dc541908af265e0008dd733e94427e0 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 17:49:29 +0200 Subject: [PATCH 16/29] Change PWM updated to the latest version of Arduino PR #7231 --- tasmota/CHANGELOG.md | 1 + tasmota/core_esp8266_waveform.cpp | 362 ++++++++++++++-------------- tasmota/core_esp8266_wiring_pwm.cpp | 30 +-- 3 files changed, 192 insertions(+), 201 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index ebfd0e3ae..5a35b9b6d 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -10,6 +10,7 @@ - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz +- Change PWM updated to the latest version of Arduino PR #7231 ### 8.2.0.5 20200425 diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp index c39670686..deca23f54 100644 --- a/tasmota/core_esp8266_waveform.cpp +++ b/tasmota/core_esp8266_waveform.cpp @@ -55,21 +55,15 @@ extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32 // Maximum delay between IRQs #define MAXIRQUS (10000) -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) - // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t timeHighCycles; // Currently running waveform period + uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Currently running waveform period - uint32_t desiredLowCycles; // - uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase - uint32_t gotoTimeLowCycles; // - uint32_t lastEdge; // + uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal + uint32_t desiredLowCycles; // + uint32_t lastEdge; // Cycle when this generator last changed } Waveform; class WVFState { @@ -82,7 +76,7 @@ public: uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation - int32_t waveformToChange = -1; + uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI uint32_t waveformNewHigh = 0; uint32_t waveformNewLow = 0; @@ -91,8 +85,8 @@ public: // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and // we can avoid looking at the other pins. - int startPin = 0; - int endPin = 0; + uint16_t startPin = 0; + uint16_t endPin = 0; }; static WVFState wvfState; @@ -107,7 +101,7 @@ static WVFState wvfState; static ICACHE_RAM_ATTR void timer1Interrupt(); static bool timerRunning = false; -static void initTimer() { +static __attribute__((noinline)) void initTimer() { if (!timerRunning) { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); @@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() { constexpr int maxPWMs = 8; // PWM machine state -typedef struct { +typedef struct PWMState { uint32_t mask; // Bitmask of active pins uint32_t cnt; // How many entries uint32_t idx; // Where the state machine is along the list uint8_t pin[maxPWMs + 1]; uint32_t delta[maxPWMs + 1]; uint32_t nextServiceCycle; // Clock cycle for next step + struct PWMState *pwmUpdate; // Set by main code, cleared by ISR } PWMState; static PWMState pwmState; -static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR -static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; - +static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; +// If there are no more scheduled activities, shut down Timer 1. +// Otherwise, do nothing. static ICACHE_RAM_ATTR void disableIdleTimer() { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); @@ -162,62 +157,78 @@ static ICACHE_RAM_ATTR void disableIdleTimer() { } } +// Notify the NMI that a new PWM state is available through the mailbox. +// Wait for mailbox to be emptied (either busy or delay() as needed) +static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) { + p->pwmUpdate = nullptr; + pwmState.pwmUpdate = p; + MEMBARRIER(); + forceTimerInterrupt(); + while (pwmState.pwmUpdate) { + if (idle) { + delay(0); + } + MEMBARRIER(); + } +} + +static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); // Called when analogWriteFreq() changed to update the PWM total period -void _setPWMPeriodCC(uint32_t cc) { - if (cc == pwmPeriod) { - return; +void _setPWMFreq(uint32_t freq) { + // Convert frequency into clock cycles + uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; + + // Simple static adjustment to bring period closer to requested due to overhead +#if F_CPU == 80000000 + cc -= microsecondsToClockCycles(2); +#else + cc -= microsecondsToClockCycles(1); +#endif + + if (cc == _pwmPeriod) { + return; // No change } + + _pwmPeriod = cc; + if (pwmState.cnt) { - // Adjust any running ones to the best of our abilities by scaling them - // Used FP math for speed and code size - uint64_t oldCC64p0 = ((uint64_t)pwmPeriod); - uint64_t newCC64p16 = ((uint64_t)cc) << 16; - uint64_t ratio64p16 = (newCC64p16 / oldCC64p0); PWMState p; // The working copy since we can't edit the one in use - p = pwmState; - uint32_t ttl = 0; - for (uint32_t i = 0; i < p.cnt; i++) { - uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; - uint64_t newVal64p32 = val64p16 * ratio64p16; - p.delta[i] = newVal64p32 >> 32; - ttl += p.delta[i]; + p.cnt = 0; + for (uint32_t i = 0; i < pwmState.cnt; i++) { + auto pin = pwmState.pin[i]; + _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); } - p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles // Update and wait for mailbox to be emptied - pwmUpdate = &p; - MEMBARRIER(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - // No mem barrier. The external function call guarantees it's re-read - } + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); } - pwmPeriod = cc; } // Helper routine to remove an entry from the state machine -static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { - uint32_t i; - - // Find the pin to pull out... - for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } - auto delta = p->delta[i]; - - // Add the removed previous pin delta to preserve absolute position - p->delta[i+1] += delta; - - // Move everything back one - for (i++; i <= p->cnt; i++) { - p->pin[i-1] = p->pin[i]; - p->delta[i-1] = p->delta[i]; +// and clean up any marked-off entries +static void _cleanAndRemovePWM(PWMState *p, int pin) { + uint32_t leftover = 0; + uint32_t in, out; + for (in = 0, out = 0; in < p->cnt; in++) { + if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { + p->pin[out] = p->pin[in]; + p->delta[out] = p->delta[in] + leftover; + leftover = 0; + out++; + } else { + leftover += p->delta[in]; + p->mask &= ~(1<pin[in]); + } } - // Remove the pin from the active list - p->mask &= ~(1<cnt--; + p->cnt = out; + // Final pin is never used: p->pin[out] = 0xff; + p->delta[out] = p->delta[in] + leftover; } -// Called by analogWrite(0/100%) to disable PWM on a specific pin + +// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) ICACHE_RAM_ATTR bool _stopPWM(int pin) { if (!((1<= _pwmPeriod) { + _stopPWM(pin); + digitalWrite(pin, HIGH); + return; } - // And add it to the list, in order - if (p.cnt >= maxPWMs) { - return false; // No space left - } else if (p.cnt == 0) { + + if (p.cnt == 0) { // Starting up from scratch, special case 1st element and PWM period p.pin[0] = pin; p.delta[0] = cc; - p.pin[1] = 0xff; - p.delta[1] = pwmPeriod - cc; - p.cnt = 1; - p.mask = 1<= (int)i; j--) { + p.pin[j + 1] = p.pin[j]; + p.delta[j + 1] = p.delta[j]; + } int off = cc - ttl; // The delta from the last edge to the one we're inserting p.pin[i] = pin; p.delta[i] = off; // Add the delta to this new pin p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant - p.cnt++; - p.mask |= 1<= maxPWMs) { + return false; // No space left } + _addPWMtoList(p, pin, val, range); + // Set mailbox and wait for ISR to copy it over - pwmUpdate = &p; - MEMBARRIER(); initTimer(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - } + _notifyPWM(&p, true); + disableIdleTimer(); return true; } @@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time uint32_t mask = 1<= 0) { + // Make sure no waveform changes are waiting to be applied + while (wvfState.waveformToChange) { delay(0); // Wait for waveform to update // No mem barrier here, the call to a global function implies global state updated } + wvfState.waveformNewHigh = timeHighCycles; + wvfState.waveformNewLow = timeLowCycles; + MEMBARRIER(); + wvfState.waveformToChange = mask; + // The waveform will be updated some time in the future on the next period for the signal } else { // if (!(wvfState.waveformEnabled & mask)) { wave->timeHighCycles = timeHighCycles; + wave->desiredHighCycles = timeHighCycles; wave->timeLowCycles = timeLowCycles; - wave->desiredHighCycles = wave->timeHighCycles; - wave->desiredLowCycles = wave->timeLowCycles; + wave->desiredLowCycles = timeLowCycles; wave->lastEdge = 0; - wave->gotoTimeHighCycles = wave->timeHighCycles; - wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); wvfState.waveformToEnable |= mask; MEMBARRIER(); @@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) { // Speed critical bits #pragma GCC optimize ("O2") + // Normally would not want two copies like this, but due to different // optimization levels the inline attribute gets lost if we try the // other version. - static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { uint32_t ccount; __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); @@ -380,8 +412,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (wvfState.waveformEnabled & (1UL << pin)) { - wvfState.waveformToDisable = 1UL << pin; + uint32_t mask = 1<> (turbo ? 0 : 1)) #endif -#define ENABLE_ADJUST // Adjust takes 36 bytes -#define ENABLE_FEEDBACK // Feedback costs 68 bytes -#define ENABLE_PWM // PWM takes 160 bytes - -#ifndef ENABLE_ADJUST - #undef adjust - #define adjust(x) (x) -#endif - static ICACHE_RAM_ATTR void timer1Interrupt() { // Flag if the core is at 160 MHz, for use by adjust() @@ -435,19 +463,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); -#ifdef ENABLE_PWM - } else if (!pwmState.cnt && pwmUpdate) { + } else if (!pwmState.cnt && pwmState.pwmUpdate) { // Start up the PWM generator by copying from the mailbox pwmState.cnt = 1; pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! // No need for mem barrier here. Global must be written by IRQ exit -#endif - } else if (wvfState.waveformToChange >= 0) { - wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh; - wvfState.waveform[wvfState.waveformToChange].gotoTimeLowCycles = wvfState.waveformNewLow; - wvfState.waveformToChange = -1; - // No need for memory barrier here. The global has to be written before exit the ISR. } bool done = false; @@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { nextEventCycles = microsecondsToClockCycles(MAXIRQUS); -#ifdef ENABLE_PWM // PWM state machine implementation if (pwmState.cnt) { - uint32_t now = GetCycleCountIRQ(); - int32_t cyclesToGo = pwmState.nextServiceCycle - now; + int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); if (cyclesToGo < 0) { if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmUpdate; - pwmUpdate = nullptr; - } - GPOS = pwmState.mask; // Set all active pins high - // GPIO16 isn't the same as the others - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; + if (pwmState.pwmUpdate) { + // Do the memory copy from temp to global and clear mailbox + pwmState = *(PWMState*)pwmState.pwmUpdate; + } + GPOS = pwmState.mask; // Set all active pins high + if (pwmState.mask & (1<<16)) { + GP16O = 1; + } + pwmState.idx = 0; } else { - do { - // Drop the pin at this edge - GPOC = 1<expiryCycle - now; if (expiryToGo < 0) { // Done, remove! - wvfState.waveformEnabled &= ~mask; if (i == 16) { GP16O = 0; - } else { - ClearGPIO(mask); - } + } + GPOC = mask; + wvfState.waveformEnabled &= ~mask; continue; } } @@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.waveformState ^= mask; if (wvfState.waveformState & mask) { if (i == 16) { - GP16O = 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); + GP16O = 1; } - if (wave->gotoTimeHighCycles) { + GPOS = mask; + + if (wvfState.waveformToChange & mask) { // Copy over next full-cycle timings - wave->timeHighCycles = wave->gotoTimeHighCycles; - wave->desiredHighCycles = wave->gotoTimeHighCycles; - wave->timeLowCycles = wave->gotoTimeLowCycles; - wave->desiredLowCycles = wave->gotoTimeLowCycles; - wave->gotoTimeHighCycles = 0; - } else { -#ifdef ENABLE_FEEDBACK - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } + wave->timeHighCycles = wvfState.waveformNewHigh; + wave->desiredHighCycles = wvfState.waveformNewHigh; + wave->timeLowCycles = wvfState.waveformNewLow; + wave->desiredLowCycles = wvfState.waveformNewLow; + wave->lastEdge = 0; + wvfState.waveformToChange = 0; + } + if (wave->lastEdge) { + desired = wave->desiredLowCycles; + timeToUpdate = &wave->timeLowCycles; } -#endif nextEdgeCycles = wave->timeHighCycles; } else { if (i == 16) { - GP16O = 0; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); + GP16O = 0; } -#ifdef ENABLE_FEEDBACK + GPOC = mask; desired = wave->desiredHighCycles; timeToUpdate = &wave->timeHighCycles; -#endif nextEdgeCycles = wave->timeLowCycles; } -#ifdef ENABLE_FEEDBACK if (desired) { desired = adjust(desired); int32_t err = desired - (now - wave->lastEdge); @@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { *timeToUpdate += err; } } -#endif nextEdgeCycles = adjust(nextEdgeCycles); wave->nextServiceCycle = now + nextEdgeCycles; nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); @@ -599,7 +608,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Do it here instead of global function to save time and because we know it's edge-IRQ T1L = nextEventCycles >> (turbo ? 1 : 0); - TEIE |= TEIE1; // Edge int enable } }; diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 984ce92db..651e3bb11 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -33,9 +33,7 @@ extern void _setPWMPeriodCC(uint32_t cc); extern bool _stopPWM(int pin); extern bool _setPWM(int pin, uint32_t cc); -static uint32_t analogMap = 0; static int32_t analogScale = PWMRANGE; -static uint16_t analogFreq = 1000; extern void __analogWriteRange(uint32_t range) { if (range > 0) { @@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) { } } - extern void __analogWriteFreq(uint32_t freq) { - if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. - analogFreq = 40; + if (freq < 40) { + freq = 40; } else if (freq > 60000) { - analogFreq = 60000; + freq = 60000; } else { - analogFreq = freq; + freq = freq; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); + _setPWMFreq(freq); } extern void __analogWrite(uint8_t pin, int val) { @@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) { return; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); if (val < 0) { val = 0; } else if (val > analogScale) { val = analogScale; } - analogMap &= ~(1 << pin); - uint32_t high = (analogPeriod * val) / analogScale; - uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (low == 0) { - _stopPWM(pin); - digitalWrite(pin, HIGH); - } else if (high == 0) { - _stopPWM(pin); - digitalWrite(pin, LOW); - } else { - _setPWM(pin, high); - analogMap |= (1 << pin); - } + _setPWM(pin, val, analogScale); } extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); From 87a1cd0ea0b0682a600cdb87d554f37a6d59cb06 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 17:52:24 +0200 Subject: [PATCH 17/29] Change PWM updated to the latest version of Arduino PR #7231 --- tasmota/CHANGELOG.md | 1 + tasmota/core_esp8266_waveform.cpp | 366 ++++++++++++------------ tasmota/core_esp8266_wiring_digital.cpp | 4 +- tasmota/core_esp8266_wiring_pwm.cpp | 34 +-- 4 files changed, 198 insertions(+), 207 deletions(-) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index ebfd0e3ae..5a35b9b6d 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -10,6 +10,7 @@ - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz +- Change PWM updated to the latest version of Arduino PR #7231 ### 8.2.0.5 20200425 diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp index c39670686..1ff0a3687 100644 --- a/tasmota/core_esp8266_waveform.cpp +++ b/tasmota/core_esp8266_waveform.cpp @@ -47,29 +47,23 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); // Maximum delay between IRQs #define MAXIRQUS (10000) -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) - // Waveform generator can create tones, PWM, and servos typedef struct { uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t timeHighCycles; // Currently running waveform period + uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Currently running waveform period - uint32_t desiredLowCycles; // - uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase - uint32_t gotoTimeLowCycles; // - uint32_t lastEdge; // + uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal + uint32_t desiredLowCycles; // + uint32_t lastEdge; // Cycle when this generator last changed } Waveform; class WVFState { @@ -82,7 +76,7 @@ public: uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation - int32_t waveformToChange = -1; + uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI uint32_t waveformNewHigh = 0; uint32_t waveformNewLow = 0; @@ -91,8 +85,8 @@ public: // Optimize the NMI inner loop by keeping track of the min and max GPIO that we // are generating. In the common case (1 PWM) these may be the same pin and // we can avoid looking at the other pins. - int startPin = 0; - int endPin = 0; + uint16_t startPin = 0; + uint16_t endPin = 0; }; static WVFState wvfState; @@ -107,7 +101,7 @@ static WVFState wvfState; static ICACHE_RAM_ATTR void timer1Interrupt(); static bool timerRunning = false; -static void initTimer() { +static __attribute__((noinline)) void initTimer() { if (!timerRunning) { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); @@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() { constexpr int maxPWMs = 8; // PWM machine state -typedef struct { +typedef struct PWMState { uint32_t mask; // Bitmask of active pins uint32_t cnt; // How many entries uint32_t idx; // Where the state machine is along the list uint8_t pin[maxPWMs + 1]; uint32_t delta[maxPWMs + 1]; uint32_t nextServiceCycle; // Clock cycle for next step + struct PWMState *pwmUpdate; // Set by main code, cleared by ISR } PWMState; static PWMState pwmState; -static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR -static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; - +static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000; +// If there are no more scheduled activities, shut down Timer 1. +// Otherwise, do nothing. static ICACHE_RAM_ATTR void disableIdleTimer() { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); @@ -162,62 +157,78 @@ static ICACHE_RAM_ATTR void disableIdleTimer() { } } +// Notify the NMI that a new PWM state is available through the mailbox. +// Wait for mailbox to be emptied (either busy or delay() as needed) +static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) { + p->pwmUpdate = nullptr; + pwmState.pwmUpdate = p; + MEMBARRIER(); + forceTimerInterrupt(); + while (pwmState.pwmUpdate) { + if (idle) { + delay(0); + } + MEMBARRIER(); + } +} + +static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); // Called when analogWriteFreq() changed to update the PWM total period -void _setPWMPeriodCC(uint32_t cc) { - if (cc == pwmPeriod) { - return; +void _setPWMFreq(uint32_t freq) { + // Convert frequency into clock cycles + uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; + + // Simple static adjustment to bring period closer to requested due to overhead +#if F_CPU == 80000000 + cc -= microsecondsToClockCycles(2); +#else + cc -= microsecondsToClockCycles(1); +#endif + + if (cc == _pwmPeriod) { + return; // No change } + + _pwmPeriod = cc; + if (pwmState.cnt) { - // Adjust any running ones to the best of our abilities by scaling them - // Used FP math for speed and code size - uint64_t oldCC64p0 = ((uint64_t)pwmPeriod); - uint64_t newCC64p16 = ((uint64_t)cc) << 16; - uint64_t ratio64p16 = (newCC64p16 / oldCC64p0); PWMState p; // The working copy since we can't edit the one in use - p = pwmState; - uint32_t ttl = 0; - for (uint32_t i = 0; i < p.cnt; i++) { - uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; - uint64_t newVal64p32 = val64p16 * ratio64p16; - p.delta[i] = newVal64p32 >> 32; - ttl += p.delta[i]; + p.cnt = 0; + for (uint32_t i = 0; i < pwmState.cnt; i++) { + auto pin = pwmState.pin[i]; + _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); } - p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles // Update and wait for mailbox to be emptied - pwmUpdate = &p; - MEMBARRIER(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - // No mem barrier. The external function call guarantees it's re-read - } + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); } - pwmPeriod = cc; } // Helper routine to remove an entry from the state machine -static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { - uint32_t i; - - // Find the pin to pull out... - for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } - auto delta = p->delta[i]; - - // Add the removed previous pin delta to preserve absolute position - p->delta[i+1] += delta; - - // Move everything back one - for (i++; i <= p->cnt; i++) { - p->pin[i-1] = p->pin[i]; - p->delta[i-1] = p->delta[i]; +// and clean up any marked-off entries +static void _cleanAndRemovePWM(PWMState *p, int pin) { + uint32_t leftover = 0; + uint32_t in, out; + for (in = 0, out = 0; in < p->cnt; in++) { + if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { + p->pin[out] = p->pin[in]; + p->delta[out] = p->delta[in] + leftover; + leftover = 0; + out++; + } else { + leftover += p->delta[in]; + p->mask &= ~(1<pin[in]); + } } - // Remove the pin from the active list - p->mask &= ~(1<cnt--; + p->cnt = out; + // Final pin is never used: p->pin[out] = 0xff; + p->delta[out] = p->delta[in] + leftover; } -// Called by analogWrite(0/100%) to disable PWM on a specific pin + +// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) ICACHE_RAM_ATTR bool _stopPWM(int pin) { if (!((1<= _pwmPeriod) { + _stopPWM(pin); + digitalWrite(pin, HIGH); + return; } - // And add it to the list, in order - if (p.cnt >= maxPWMs) { - return false; // No space left - } else if (p.cnt == 0) { + + if (p.cnt == 0) { // Starting up from scratch, special case 1st element and PWM period p.pin[0] = pin; p.delta[0] = cc; - p.pin[1] = 0xff; - p.delta[1] = pwmPeriod - cc; - p.cnt = 1; - p.mask = 1<= (int)i; j--) { + p.pin[j + 1] = p.pin[j]; + p.delta[j + 1] = p.delta[j]; + } int off = cc - ttl; // The delta from the last edge to the one we're inserting p.pin[i] = pin; p.delta[i] = off; // Add the delta to this new pin p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant - p.cnt++; - p.mask |= 1<= maxPWMs) { + return false; // No space left } + _addPWMtoList(p, pin, val, range); + // Set mailbox and wait for ISR to copy it over - pwmUpdate = &p; - MEMBARRIER(); initTimer(); - forceTimerInterrupt(); - while (pwmUpdate) { - delay(0); - } + _notifyPWM(&p, true); + disableIdleTimer(); return true; } @@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time uint32_t mask = 1<= 0) { + // Make sure no waveform changes are waiting to be applied + while (wvfState.waveformToChange) { delay(0); // Wait for waveform to update // No mem barrier here, the call to a global function implies global state updated } + wvfState.waveformNewHigh = timeHighCycles; + wvfState.waveformNewLow = timeLowCycles; + MEMBARRIER(); + wvfState.waveformToChange = mask; + // The waveform will be updated some time in the future on the next period for the signal } else { // if (!(wvfState.waveformEnabled & mask)) { wave->timeHighCycles = timeHighCycles; + wave->desiredHighCycles = timeHighCycles; wave->timeLowCycles = timeLowCycles; - wave->desiredHighCycles = wave->timeHighCycles; - wave->desiredLowCycles = wave->timeLowCycles; + wave->desiredLowCycles = timeLowCycles; wave->lastEdge = 0; - wave->gotoTimeHighCycles = wave->timeHighCycles; - wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); wvfState.waveformToEnable |= mask; MEMBARRIER(); @@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) { // Speed critical bits #pragma GCC optimize ("O2") + // Normally would not want two copies like this, but due to different // optimization levels the inline attribute gets lost if we try the // other version. - static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { uint32_t ccount; __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); @@ -380,8 +412,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (wvfState.waveformEnabled & (1UL << pin)) { - wvfState.waveformToDisable = 1UL << pin; + uint32_t mask = 1<> (turbo ? 0 : 1)) #endif -#define ENABLE_ADJUST // Adjust takes 36 bytes -#define ENABLE_FEEDBACK // Feedback costs 68 bytes -#define ENABLE_PWM // PWM takes 160 bytes - -#ifndef ENABLE_ADJUST - #undef adjust - #define adjust(x) (x) -#endif - static ICACHE_RAM_ATTR void timer1Interrupt() { // Flag if the core is at 160 MHz, for use by adjust() @@ -435,19 +463,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); -#ifdef ENABLE_PWM - } else if (!pwmState.cnt && pwmUpdate) { + } else if (!pwmState.cnt && pwmState.pwmUpdate) { // Start up the PWM generator by copying from the mailbox pwmState.cnt = 1; pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! // No need for mem barrier here. Global must be written by IRQ exit -#endif - } else if (wvfState.waveformToChange >= 0) { - wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh; - wvfState.waveform[wvfState.waveformToChange].gotoTimeLowCycles = wvfState.waveformNewLow; - wvfState.waveformToChange = -1; - // No need for memory barrier here. The global has to be written before exit the ISR. } bool done = false; @@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { do { nextEventCycles = microsecondsToClockCycles(MAXIRQUS); -#ifdef ENABLE_PWM // PWM state machine implementation if (pwmState.cnt) { - uint32_t now = GetCycleCountIRQ(); - int32_t cyclesToGo = pwmState.nextServiceCycle - now; + int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); if (cyclesToGo < 0) { if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmUpdate; - pwmUpdate = nullptr; - } - GPOS = pwmState.mask; // Set all active pins high - // GPIO16 isn't the same as the others - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; + if (pwmState.pwmUpdate) { + // Do the memory copy from temp to global and clear mailbox + pwmState = *(PWMState*)pwmState.pwmUpdate; + } + GPOS = pwmState.mask; // Set all active pins high + if (pwmState.mask & (1<<16)) { + GP16O = 1; + } + pwmState.idx = 0; } else { - do { - // Drop the pin at this edge - GPOC = 1<expiryCycle - now; if (expiryToGo < 0) { // Done, remove! - wvfState.waveformEnabled &= ~mask; if (i == 16) { GP16O = 0; - } else { - ClearGPIO(mask); - } + } + GPOC = mask; + wvfState.waveformEnabled &= ~mask; continue; } } @@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { wvfState.waveformState ^= mask; if (wvfState.waveformState & mask) { if (i == 16) { - GP16O = 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); + GP16O = 1; } - if (wave->gotoTimeHighCycles) { + GPOS = mask; + + if (wvfState.waveformToChange & mask) { // Copy over next full-cycle timings - wave->timeHighCycles = wave->gotoTimeHighCycles; - wave->desiredHighCycles = wave->gotoTimeHighCycles; - wave->timeLowCycles = wave->gotoTimeLowCycles; - wave->desiredLowCycles = wave->gotoTimeLowCycles; - wave->gotoTimeHighCycles = 0; - } else { -#ifdef ENABLE_FEEDBACK - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } + wave->timeHighCycles = wvfState.waveformNewHigh; + wave->desiredHighCycles = wvfState.waveformNewHigh; + wave->timeLowCycles = wvfState.waveformNewLow; + wave->desiredLowCycles = wvfState.waveformNewLow; + wave->lastEdge = 0; + wvfState.waveformToChange = 0; + } + if (wave->lastEdge) { + desired = wave->desiredLowCycles; + timeToUpdate = &wave->timeLowCycles; } -#endif nextEdgeCycles = wave->timeHighCycles; } else { if (i == 16) { - GP16O = 0; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); + GP16O = 0; } -#ifdef ENABLE_FEEDBACK + GPOC = mask; desired = wave->desiredHighCycles; timeToUpdate = &wave->timeHighCycles; -#endif nextEdgeCycles = wave->timeLowCycles; } -#ifdef ENABLE_FEEDBACK if (desired) { desired = adjust(desired); int32_t err = desired - (now - wave->lastEdge); @@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { *timeToUpdate += err; } } -#endif nextEdgeCycles = adjust(nextEdgeCycles); wave->nextServiceCycle = now + nextEdgeCycles; nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); @@ -599,7 +608,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Do it here instead of global function to save time and because we know it's edge-IRQ T1L = nextEventCycles >> (turbo ? 1 : 0); - TEIE |= TEIE1; // Edge int enable } }; diff --git a/tasmota/core_esp8266_wiring_digital.cpp b/tasmota/core_esp8266_wiring_digital.cpp index d3a8fa737..199fbabd8 100644 --- a/tasmota/core_esp8266_wiring_digital.cpp +++ b/tasmota/core_esp8266_wiring_digital.cpp @@ -34,9 +34,9 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); extern void resetPins(); volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 }; diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 984ce92db..5189fa8f5 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -29,13 +29,11 @@ extern "C" { // Internal-only calls, not for applications -extern void _setPWMPeriodCC(uint32_t cc); +extern void _setPWMFreq(uint32_t freq); extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t cc); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); -static uint32_t analogMap = 0; static int32_t analogScale = PWMRANGE; -static uint16_t analogFreq = 1000; extern void __analogWriteRange(uint32_t range) { if (range > 0) { @@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) { } } - extern void __analogWriteFreq(uint32_t freq) { - if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. - analogFreq = 40; + if (freq < 40) { + freq = 40; } else if (freq > 60000) { - analogFreq = 60000; + freq = 60000; } else { - analogFreq = freq; + freq = freq; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); + _setPWMFreq(freq); } extern void __analogWrite(uint8_t pin, int val) { @@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) { return; } - uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; - _setPWMPeriodCC(analogPeriod); if (val < 0) { val = 0; } else if (val > analogScale) { val = analogScale; } - analogMap &= ~(1 << pin); - uint32_t high = (analogPeriod * val) / analogScale; - uint32_t low = analogPeriod - high; pinMode(pin, OUTPUT); - if (low == 0) { - _stopPWM(pin); - digitalWrite(pin, HIGH); - } else if (high == 0) { - _stopPWM(pin); - digitalWrite(pin, LOW); - } else { - _setPWM(pin, high); - analogMap |= (1 << pin); - } + _setPWM(pin, val, analogScale); } extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); From a2b05399a3c5ad104304b1c357116a7fc1ee9fea Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 19:33:20 +0200 Subject: [PATCH 18/29] Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules --- .../generator/generator.c | 165 +++++ .../generator/remapping.xlsx | Bin 0 -> 12338 bytes .../library.properties.txt | 8 + lib/Unishox-1.0-shadinger/src/unishox.cpp | 601 ++++++++++++++++++ lib/Unishox-1.0-shadinger/src/unishox.h | 26 + tasmota/CHANGELOG.md | 1 + tasmota/my_user_config.h | 1 + tasmota/settings.h | 2 +- tasmota/settings.ino | 4 + tasmota/xdrv_10_rules.ino | 247 ++++++- 10 files changed, 1040 insertions(+), 15 deletions(-) create mode 100644 lib/Unishox-1.0-shadinger/generator/generator.c create mode 100644 lib/Unishox-1.0-shadinger/generator/remapping.xlsx create mode 100644 lib/Unishox-1.0-shadinger/library.properties.txt create mode 100644 lib/Unishox-1.0-shadinger/src/unishox.cpp create mode 100644 lib/Unishox-1.0-shadinger/src/unishox.h diff --git a/lib/Unishox-1.0-shadinger/generator/generator.c b/lib/Unishox-1.0-shadinger/generator/generator.c new file mode 100644 index 000000000..81c46649e --- /dev/null +++ b/lib/Unishox-1.0-shadinger/generator/generator.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Arundale R. + * + */ + +// Pre-compute c_95[] and l_95[] + +#include +#include +#include +#include +#include +#include + +typedef unsigned char byte; + +enum {SHX_SET1 = 0, SHX_SET1A, SHX_SET1B, SHX_SET2, SHX_SET3, SHX_SET4, SHX_SET4A}; +char us_vcodes[] = {0, 2, 3, 4, 10, 11, 12, 13, 14, 30, 31}; +char us_vcode_lens[] = {2, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5}; +char us_sets[][11] = + {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, + { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, + {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, + { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, + {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, + {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, + {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; + // {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, + // { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, + // {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, + // { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, + // {'.', ',', '-', '/', '=', '+', ' ', '(', ')', '$', '%'}, + // {'&', ';', ':', '<', '>', '*', '"', '{', '}', '[', ']'}, + // {'@', '?', '\'', '^', '#', '_', '!', '\\', '|', '~', '`'}}; + +unsigned int c_95[95] ; +unsigned char l_95[95] ; + + +void init_coder() { + for (int i = 0; i < 7; i++) { + for (int j = 0; j < 11; j++) { + char c = us_sets[i][j]; + if (c != 0 && c != 32) { + int ascii = c - 32; + //int prev_code = c_95[ascii]; + //int prev_code_len = l_95[ascii]; + switch (i) { + case SHX_SET1: // just us_vcode + c_95[ascii] = (us_vcodes[j] << (16 - us_vcode_lens[j])); + l_95[ascii] = us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + (us_vcodes[j] << (12 - us_vcode_lens[j])); + l_95[ascii] = 4 + us_vcode_lens[j]; + } + break; + case SHX_SET1A: // 000 + us_vcode + c_95[ascii] = 0 + (us_vcodes[j] << (13 - us_vcode_lens[j])); + l_95[ascii] = 3 + us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + 0 + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 4 + 3 + us_vcode_lens[j]; + } + break; + case SHX_SET1B: // 00110 + us_vcode + c_95[ascii] = (6 << 11) + (us_vcodes[j] << (11 - us_vcode_lens[j])); + l_95[ascii] = 5 + us_vcode_lens[j]; + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + if (c >= 'a' && c <= 'z') { + ascii -= ('a' - 'A'); + //prev_code = c_95[ascii]; + //prev_code_len = l_95[ascii]; + c_95[ascii] = (2 << 12) + (6 << 7) + (us_vcodes[j] << (7 - us_vcode_lens[j])); + l_95[ascii] = 4 + 5 + us_vcode_lens[j]; + } + break; + case SHX_SET2: // 0011100 + us_vcode + c_95[ascii] = (28 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET3: // 0011101 + us_vcode + c_95[ascii] = (29 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET4: // 0011110 + us_vcode + c_95[ascii] = (30 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + break; + case SHX_SET4A: // 0011111 + us_vcode + c_95[ascii] = (31 << 9) + (us_vcodes[j] << (9 - us_vcode_lens[j])); + l_95[ascii] = 7 + us_vcode_lens[j]; + } + //checkPreus_vcodes(c, prev_code, prev_code_len, c_95[ascii], l_95[ascii]); + } + } + } + c_95[0] = 16384; + l_95[0] = 3; + +} + +int main(int argv, char *args[]) { + init_coder(); + + printf("uint16_t c_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("0x%04X", c_95[i]); + } + printf(" };\n"); + + printf("uint8_t l_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%6d", l_95[i]); + } + printf(" };\n"); + + printf("\n\n"); + + printf("uint16_t c_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%5d", c_95[i]); + } + printf(" };\n"); + + printf("uint8_t l_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("%5d", l_95[i]); + } + printf(" };\n"); + + + printf("uint16_t cl_95[95] PROGMEM = {"); + for (uint8_t i = 0; i<95; i++) { + if (i) { printf(", "); } + printf("0x%04X + %2d", c_95[i], l_95[i]); + } + printf(" };\n"); + +} \ No newline at end of file diff --git a/lib/Unishox-1.0-shadinger/generator/remapping.xlsx b/lib/Unishox-1.0-shadinger/generator/remapping.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..94a82ecded44048c4c5a025e887cee91c9aa3115 GIT binary patch literal 12338 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 ZCo(F^z`XV@0D$=V@p~P-GZ}yX`hU>t7()O6 literal 0 HcmV?d00001 diff --git a/lib/Unishox-1.0-shadinger/library.properties.txt b/lib/Unishox-1.0-shadinger/library.properties.txt new file mode 100644 index 000000000..138b2027c --- /dev/null +++ b/lib/Unishox-1.0-shadinger/library.properties.txt @@ -0,0 +1,8 @@ +name=Unishox Compressor Decompressor highly customized and optimized for ESP8266 and Tasmota +version=1.0 +author=Arundale Ramanathan, Stephan Hadinger +maintainer=Arun , Stephan +sentence=Unishox compression for Tasmota Rules +paragraph=It is based on Unishox hybrid encoding technique. This version has specific Unicode code removed for size. +url=https://github.com/siara-cc/Unishox +architectures=esp8266 diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp new file mode 100644 index 000000000..ada408bff --- /dev/null +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Arundale R. + * + */ + +/* + * + * This is a highly modified and optimized version of Unishox + * for Tasmota, aimed at compressing `Rules` which are typically + * short strings from 50 to 500 bytes. + * + * - moved to C++ (but still C-style) + * - c_95[] and l_95[] are pre-computed + * - all arrays in PROGMEM + * - removed all Unicode specific code to get code smaller, Unicode is rare in rules and encoded as pure binary + * - removed prev_lines management to reduce code size, we don't track previous encodings + * - using C++ const instead of #define + * - reusing the Unicode market to encode pure binary, which is 3 bits instead of 9 + * - reverse binary encoding to 255-byte, favoring short encoding for values above 127, typical of Unicode + * - remove 2 bits encoding for Counts, since it could lead to a series of more than 8 consecutive 0-bits and output NULL char. + * Minimum encoding is 5 bits, which means spending 3+1=4 more bits for values in the range 0..3 + * - removed CRLF encoding and reusing entry for RPT, saving 3 bits for repeats. Note: any CR will be binary encded + * - add safeguard to the output size (len_out), note that the compress buffer needs to be 4 bytes larger than actual compressed output. + * This is needed to avoid crash, since output can have ~30 bits + * - combined c_95[] and l_95[] to a single array to save space + * - Changed mapping of some characters in Set3, Set4 and Set4A, favoring frequent characters in rules and javascript + * - Added escape mechanism to ensure we never output NULL char. The marker is 0x2A which looked rare in preliminary tests + * + * @author Stephan Hadinger + * + */ + +#include +#include +#include +#include +#include +#include + +#include "unishox.h" + +typedef unsigned char byte; +// we squeeze both c_95[] and l_95[] in a sinle array. +// c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) +uint16_t cl_95[95] PROGMEM = {0x4000 + 3, 0x3F80 + 11, 0x3D80 + 11, 0x3C80 + 10, 0x3BE0 + 12, 0x3E80 + 10, 0x3F40 + 11, 0x3EC0 + 10, 0x3BA0 + 11, 0x3BC0 + 11, 0x3D60 + 11, 0x3B60 + 11, 0x3A80 + 10, 0x3AC0 + 10, 0x3A00 + 9, 0x3B00 + 10, 0x38C0 + 10, 0x3900 + 10, 0x3940 + 11, 0x3960 + 11, 0x3980 + 11, 0x39A0 + 11, 0x39C0 + 11, 0x39E0 + 12, 0x39F0 + 12, 0x3880 + 10, 0x3CC0 + 10, 0x3C00 + 9, 0x3D00 + 10, 0x3E00 + 9, 0x3F00 + 10, 0x3B40 + 11, 0x3BF0 + 12, 0x2B00 + 8, 0x21C0 + 11, 0x20C0 + 10, 0x2100 + 10, 0x2600 + 7, 0x2300 + 11, 0x21E0 + 12, 0x2140 + 11, 0x2D00 + 8, 0x2358 + 13, 0x2340 + 12, 0x2080 + 10, 0x21A0 + 11, 0x2E00 + 8, 0x2C00 + 8, 0x2180 + 11, 0x2350 + 13, 0x2F80 + 9, 0x2F00 + 9, 0x2A00 + 8, 0x2160 + 11, 0x2330 + 12, 0x21F0 + 12, 0x2360 + 13, 0x2320 + 12, 0x2368 + 13, 0x3DE0 + 12, 0x3FA0 + 11, 0x3DF0 + 12, 0x3D40 + 11, 0x3F60 + 11, 0x3FF0 + 12, 0xB000 + 4, 0x1C00 + 7, 0x0C00 + 6, 0x1000 + 6, 0x6000 + 3, 0x3000 + 7, 0x1E00 + 8, 0x1400 + 7, 0xD000 + 4, 0x3580 + 9, 0x3400 + 8, 0x0800 + 6, 0x1A00 + 7, 0xE000 + 4, 0xC000 + 4, 0x1800 + 7, 0x3500 + 9, 0xF800 + 5, 0xF000 + 5, 0xA000 + 4, 0x1600 + 7, 0x3300 + 8, 0x1F00 + 8, 0x3600 + 9, 0x3200 + 8, 0x3680 + 9, 0x3DA0 + 11, 0x3FC0 + 11, 0x3DC0 + 11, 0x3FE0 + 12 }; +// Original version with c/l separate +// uint16_t c_95[95] PROGMEM = {0x4000, 0x3F80, 0x3D80, 0x3C80, 0x3BE0, 0x3E80, 0x3F40, 0x3EC0, 0x3BA0, 0x3BC0, 0x3D60, 0x3B60, 0x3A80, 0x3AC0, 0x3A00, 0x3B00, 0x38C0, 0x3900, 0x3940, 0x3960, 0x3980, 0x39A0, 0x39C0, 0x39E0, 0x39F0, 0x3880, 0x3CC0, 0x3C00, 0x3D00, 0x3E00, 0x3F00, 0x3B40, 0x3BF0, 0x2B00, 0x21C0, 0x20C0, 0x2100, 0x2600, 0x2300, 0x21E0, 0x2140, 0x2D00, 0x2358, 0x2340, 0x2080, 0x21A0, 0x2E00, 0x2C00, 0x2180, 0x2350, 0x2F80, 0x2F00, 0x2A00, 0x2160, 0x2330, 0x21F0, 0x2360, 0x2320, 0x2368, 0x3DE0, 0x3FA0, 0x3DF0, 0x3D40, 0x3F60, 0x3FF0, 0xB000, 0x1C00, 0x0C00, 0x1000, 0x6000, 0x3000, 0x1E00, 0x1400, 0xD000, 0x3580, 0x3400, 0x0800, 0x1A00, 0xE000, 0xC000, 0x1800, 0x3500, 0xF800, 0xF000, 0xA000, 0x1600, 0x3300, 0x1F00, 0x3600, 0x3200, 0x3680, 0x3DA0, 0x3FC0, 0x3DC0, 0x3FE0 }; +// uint8_t l_95[95] PROGMEM = { 3, 11, 11, 10, 12, 10, 11, 10, 11, 11, 11, 11, 10, 10, 9, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 10, 10, 9, 10, 9, 10, 11, 12, 8, 11, 10, 10, 7, 11, 12, 11, 8, 13, 12, 10, 11, 8, 8, 11, 13, 9, 9, 8, 11, 12, 12, 13, 12, 13, 12, 11, 12, 11, 11, 12, 4, 7, 6, 6, 3, 7, 8, 7, 4, 9, 8, 6, 7, 4, 4, 7, 9, 5, 5, 4, 7, 8, 8, 9, 8, 9, 11, 11, 11, 12 }; + +enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state + +enum {SHX_SET1 = 0, SHX_SET1A, SHX_SET1B, SHX_SET2, SHX_SET3, SHX_SET4, SHX_SET4A}; +// changed mapping in Set3, Set4, Set4A to accomodate frequencies in Rules and Javascript +char sets[][11] PROGMEM = + {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, + { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, + {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, + { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, + {'.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'}, + {';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'}, + {'=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`'}}; + // {{ 0, ' ', 'e', 0, 't', 'a', 'o', 'i', 'n', 's', 'r'}, + // { 0, 'l', 'c', 'd', 'h', 'u', 'p', 'm', 'b', 'g', 'w'}, + // {'f', 'y', 'v', 'k', 'q', 'j', 'x', 'z', 0, 0, 0}, + // { 0, '9', '0', '1', '2', '3', '4', '5', '6', '7', '8'}, + // {'.', ',', '-', '/', '=', '+', ' ', '(', ')', '$', '%'}, + // {'&', ';', ':', '<', '>', '*', '"', '{', '}', '[', ']'}, + // {'@', '?', '\'', '^', '#', '_', '!', '\\', '|', '~', '`'}}; + +// Decoder is designed for using less memory, not speed +// Decode lookup table for code index and length +// First 2 bits 00, Next 3 bits indicate index of code from 0, +// last 3 bits indicate code length in bits +// 0, 1, 2, 3, 4, +char us_vcode[32] PROGMEM = + {2 + (0 << 3), 3 + (3 << 3), 3 + (1 << 3), 4 + (6 << 3), 0, +// 5, 6, 7, 8, 9, 10 + 4 + (4 << 3), 3 + (2 << 3), 4 + (8 << 3), 0, 0, 0, +// 11, 12, 13, 14, 15 + 4 + (7 << 3), 0, 4 + (5 << 3), 0, 5 + (9 << 3), +// 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 0, +// 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (10 << 3)}; +// 0, 1, 2, 3, 4, 5, 6, 7, +char us_hcode[32] PROGMEM = + {1 + (1 << 3), 2 + (0 << 3), 0, 3 + (2 << 3), 0, 0, 0, 5 + (3 << 3), +// 8, 9, 10, 11, 12, 13, 14, 15, + 0, 0, 0, 0, 0, 0, 0, 5 + (5 << 3), +// 16, 17, 18, 19, 20, 21, 22, 23 + 0, 0, 0, 0, 0, 0, 0, 5 + (4 << 3), +// 24, 25, 26, 27, 28, 29, 30, 31 + 0, 0, 0, 0, 0, 0, 0, 5 + (6 << 3)}; + +const char ESCAPE_MARKER = 0x2A; // Escape any null char + +const uint16_t TERM_CODE = 0x37C0; // 0b0011011111000000 +const uint16_t TERM_CODE_LEN = 10; +const uint16_t DICT_CODE = 0x0000; +const uint16_t DICT_CODE_LEN = 5; +const uint16_t DICT_OTHER_CODE = 0x0000; // not used +const uint16_t DICT_OTHER_CODE_LEN = 6; +// const uint16_t RPT_CODE = 0x2370; +// const uint16_t RPT_CODE_LEN = 13; +const uint16_t RPT_CODE_TASMOTA = 0x3780; +const uint16_t RPT_CODE_TASMOTA_LEN = 10; +const uint16_t BACK2_STATE1_CODE = 0x2000; // 0010 = back to lower case +const uint16_t BACK2_STATE1_CODE_LEN = 4; +const uint16_t BACK_FROM_UNI_CODE = 0xFE00; +const uint16_t BACK_FROM_UNI_CODE_LEN = 8; +// const uint16_t CRLF_CODE = 0x3780; +// const uint16_t CRLF_CODE_LEN = 10; +const uint16_t LF_CODE = 0x3700; +const uint16_t LF_CODE_LEN = 9; +const uint16_t TAB_CODE = 0x2400; +const uint16_t TAB_CODE_LEN = 7; +// const uint16_t UNI_CODE = 0x8000; // Unicode disabled +// const uint16_t UNI_CODE_LEN = 3; +// const uint16_t UNI_STATE_SPL_CODE = 0xF800; +// const uint16_t UNI_STATE_SPL_CODE_LEN = 5; +// const uint16_t UNI_STATE_DICT_CODE = 0xFC00; +// const uint16_t UNI_STATE_DICT_CODE_LEN = 7; +// const uint16_t CONT_UNI_CODE = 0x2800; +// const uint16_t CONT_UNI_CODE_LEN = 7; +const uint16_t ALL_UPPER_CODE = 0x2200; +const uint16_t ALL_UPPER_CODE_LEN = 8; +const uint16_t SW2_STATE2_CODE = 0x3800; +const uint16_t SW2_STATE2_CODE_LEN = 7; +const uint16_t ST2_SPC_CODE = 0x3B80; +const uint16_t ST2_SPC_CODE_LEN = 11; +const uint16_t BIN_CODE_TASMOTA = 0x8000; +const uint16_t BIN_CODE_TASMOTA_LEN = 3; +// const uint16_t BIN_CODE = 0x2000; +// const uint16_t BIN_CODE_LEN = 9; + +#define NICE_LEN 5 + +// uint16_t mask[] PROGMEM = {0x8000, 0xC000, 0xE000, 0xF000, 0xF800, 0xFC00, 0xFE00, 0xFF00}; +uint8_t mask[] PROGMEM = {0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF}; + +int append_bits(char *out, size_t ol, unsigned int code, int clen, byte state) { + + byte cur_bit; + byte blen; + unsigned char a_byte; + + if (state == SHX_STATE_2) { + // remove change state prefix + if ((code >> 9) == 0x1C) { + code <<= 7; + clen -= 7; + } + //if (code == 14272 && clen == 10) { + // code = 9084; + // clen = 14; + //} + } + while (clen > 0) { + cur_bit = ol % 8; + blen = (clen > 8 ? 8 : clen); + // a_byte = (code & pgm_read_word(&mask[blen - 1])) >> 8; + // a_byte = (code & (pgm_read_word(&mask[blen - 1]) << 8)) >> 8; + a_byte = (code >> 8) & pgm_read_word(&mask[blen - 1]); + a_byte >>= cur_bit; + if (blen + cur_bit > 8) + blen = (8 - cur_bit); + if (out) { // if out == nullptr, then we are in dry-run mode + if (cur_bit == 0) + out[ol / 8] = a_byte; + else + out[ol / 8] |= a_byte; + } + code <<= blen; + ol += blen; + if ((out) && (0 == ol % 8)) { // if out == nullptr, dry-run mode. We miss the escaping of characters in the length + // we completed a full byte + char last_c = out[(ol / 8) - 1]; + if ((0 == last_c) || (ESCAPE_MARKER == last_c)) { + out[ol / 8] = 1 + last_c; // increment to 0x01 or 0x2B + out[(ol / 8) -1] = ESCAPE_MARKER; // replace old value with marker + ol += 8; // add one full byte + } + } + clen -= blen; + } + return ol; +} + +// First five bits are code and Last three bits of codes represent length +// removing last 2 bytes, unused, we will never have values above 600 bytes +// const byte codes[7] = {0x01, 0x82, 0xC3, 0xE5, 0xED, 0xF5, 0xFD}; +// const byte bit_len[7] = {2, 5, 7, 9, 12, 16, 17}; +// const uint16_t adder[7] = {0, 4, 36, 164, 676, 4772, 0}; +byte codes[] PROGMEM = { 0x82, 0xC3, 0xE5, 0xED, 0xF5 }; +byte bit_len[] PROGMEM = { 5, 7, 9, 12, 16 }; +// uint16_t adder[7] PROGMEM = { 0, 32, 160, 672, 4768 }; // no more used + +int encodeCount(char *out, int ol, int count) { + int till = 0; + int base = 0; + for (int i = 0; i < sizeof(bit_len); i++) { + uint32_t bit_len_i = pgm_read_byte(&bit_len[i]); + till += (1 << bit_len_i); + if (count < till) { + byte codes_i = pgm_read_byte(&codes[i]); + ol = append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1); + // ol = append_bits(out, ol, (count - pgm_read_word(&adder[i])) << (16 - bit_len_i), bit_len_i, 1); + ol = append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1); + return ol; + } + base = till; + } + return ol; +} + +int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *state, byte *is_all_upper) { + int j, k; + int longest_dist = 0; + int longest_len = 0; + for (j = l - NICE_LEN; j >= 0; j--) { + for (k = l; k < len && j + k - l < l; k++) { + if (in[k] != in[j + k - l]) + break; + } + // while ((((unsigned char) in[k]) >> 6) == 2) + // k--; // Skip partial UTF-8 matches + //if ((in[k - 1] >> 3) == 0x1E || (in[k - 1] >> 4) == 0x0E || (in[k - 1] >> 5) == 0x06) + // k--; + if (k - l > NICE_LEN - 1) { + int match_len = k - l - NICE_LEN; + int match_dist = l - j - NICE_LEN + 1; + if (match_len > longest_len) { + longest_len = match_len; + longest_dist = match_dist; + } + } + } + if (longest_len) { + if (*state == SHX_STATE_2 || *is_all_upper) { + *is_all_upper = 0; + *state = SHX_STATE_1; + *ol = append_bits(out, *ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, *state); + } + *ol = append_bits(out, *ol, DICT_CODE, DICT_CODE_LEN, 1); + *ol = encodeCount(out, *ol, longest_len); + *ol = encodeCount(out, *ol, longest_dist); + l += (longest_len + NICE_LEN); + l--; + return l; + } + return -l; +} + +// Compress a buffer. +// Inputs: +// - in: non-null pointer to a buffer of bytes to be compressed. Progmem is not valid. Null bytes are valid. +// - len: size of the input buffer. 0 is valid for empty buffer +// - out: pointer to output buffer. out is nullptr, the compressor does a dry-run and reports the compressed size without writing bytes +// - len_out: length in bytes of the output buffer. +// Output: +// - if >= 0: size of the compressed buffer. The output buffer does not contain NULL bytes, and it is not NULL terminated +// - if < 0: an error occured, most certainly the output buffer was not large enough +int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out) { + + char *ptr; + byte bits; + byte state; + + int l, ll, ol; + char c_in, c_next; + byte is_upper, is_all_upper; + + ol = 0; + state = SHX_STATE_1; + is_all_upper = 0; + for (l=0; l 0) { + continue; + } + l = -l; + } + if (state == SHX_STATE_2) { // if Set2 + if ((c_in >= ' ' && c_in <= '@') || + (c_in >= '[' && c_in <= '`') || + (c_in >= '{' && c_in <= '~')) { + } else { + state = SHX_STATE_1; // back to Set1 and lower case + ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state); + } + } + + is_upper = 0; + if (c_in >= 'A' && c_in <= 'Z') + is_upper = 1; + else { + if (is_all_upper) { + is_all_upper = 0; + ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state); + } + } + + c_next = 0; + if (l+1 < len) + c_next = in[l+1]; + + if (c_in >= 32 && c_in <= 126) { + if (is_upper && !is_all_upper) { + for (ll=l+5; ll>=l && ll 'Z') + break; + } + if (ll == l-1) { + ol = append_bits(out, ol, ALL_UPPER_CODE, ALL_UPPER_CODE_LEN, state); // CapsLock + is_all_upper = 1; + } + } + if (state == SHX_STATE_1 && c_in >= '0' && c_in <= '9') { + ol = append_bits(out, ol, SW2_STATE2_CODE, SW2_STATE2_CODE_LEN, state); // Switch to sticky Set2 + state = SHX_STATE_2; + } + c_in -= 32; + if (is_all_upper && is_upper) + c_in += 32; + if (c_in == 0 && state == SHX_STATE_2) + ol = append_bits(out, ol, ST2_SPC_CODE, ST2_SPC_CODE_LEN, state); // space from Set2 ionstead of Set1 + else { + // ol = append_bits(out, ol, pgm_read_word(&c_95[c_in]), pgm_read_byte(&l_95[c_in]), state); // original version with c/l in split arrays + uint16_t cl = pgm_read_word(&cl_95[c_in]); + ol = append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state); + } + } else + // if (c_in == 13 && c_next == 10) { // CRLF disabled + // ol = append_bits(out, ol, CRLF_CODE, CRLF_CODE_LEN, state); // CRLF + // l++; + // } else + if (c_in == 10) { + ol = append_bits(out, ol, LF_CODE, LF_CODE_LEN, state); // LF + } else + if (c_in == '\t') { + ol = append_bits(out, ol, TAB_CODE, TAB_CODE_LEN, state); // TAB + } else { + ol = append_bits(out, ol, BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN, state); // Binary, we reuse the Unicode marker which 3 bits instead of 9 + ol = encodeCount(out, ol, (unsigned char) 255 - c_in); + } + + // check that we have some headroom in the output buffer + if (ol / 8 >= len_out - 4) { + return -1; // we risk overflow and crash + } + } + + bits = ol % 8; + if (bits) { + ol = append_bits(out, ol, TERM_CODE, 8 - bits, 1); // 0011 0111 1100 0000 TERM = 0011 0111 11 + } + return ol/8+(ol%8?1:0); +} + +int getBitVal(const char *in, int bit_no, int count) { + char c_in = in[bit_no >> 3]; + if ((bit_no >> 3) && (ESCAPE_MARKER == in[(bit_no >> 3) - 1])) { // if previous byte is a marker, decrement + c_in--; + } + return (c_in & (0x80 >> (bit_no % 8)) ? 1 << count : 0); +} + +// Returns: +// 0..11 +// or -1 if end of stream +int getCodeIdx(char *code_type, const char *in, int len, int *bit_no_p) { + int code = 0; + int count = 0; + do { + // detect marker + if (ESCAPE_MARKER == in[*bit_no_p >> 3]) { + *bit_no_p += 8; // skip marker + } + if (*bit_no_p >= len) + return -1; // invalid state + code += getBitVal(in, *bit_no_p, count); + (*bit_no_p)++; + count++; + uint8_t code_type_code = pgm_read_byte(&code_type[code]); + if (code_type_code && (code_type_code & 0x07) == count) { + return code_type_code >> 3; + } + } while (count < 5); + return 1; // skip if code not found +} + +int getNumFromBits(const char *in, int bit_no, int count) { + int ret = 0; + while (count--) { + if (ESCAPE_MARKER == in[bit_no >> 3]) { + bit_no += 8; // skip marker + } + ret += getBitVal(in, bit_no++, count); + } + return ret; +} + +// const byte bit_len[7] = {5, 2, 7, 9, 12, 16, 17}; +// const uint16_t adder[7] = {4, 0, 36, 164, 676, 4772, 0}; + +// byte bit_len[7] PROGMEM = { 5, 7, 9, 12, 16 }; +// byte bit_len_read[7] PROGMEM = {5, 2, 7, 9, 12, 16 }; +// uint16_t adder_read[7] PROGMEM = {4, 0, 36, 164, 676, 4772, 0}; +// uint16_t adder_read[] PROGMEM = {0, 0, 32, 160, 672, 4768 }; + +// byte bit_len[7] PROGMEM = { 5, 7, 9, 12, 16 }; +// uint16_t adder_read[] PROGMEM = {0, 32, 160, 672, 4768 }; + +// Code size optimized, recalculate adder[] like in encodeCount +int readCount(const char *in, int *bit_no_p, int len) { + int idx = getCodeIdx(us_hcode, in, len, bit_no_p); + if (idx >= 1) idx--; // we skip v = 1 (code '0') since we no more accept 2 bits encoding + if ((idx >= sizeof(bit_len)) || (idx < 0)) return 0; // unsupported or end of stream + + int base; + int till = 0; + byte bit_len_idx; // bit_len[0] + for (uint32_t i = 0; i <= idx; i++) { + base = till; + bit_len_idx = pgm_read_byte(&bit_len[i]); + till += (1 << bit_len_idx); + } + int count = getNumFromBits(in, *bit_no_p, bit_len_idx) + base; + + (*bit_no_p) += bit_len_idx; + return count; +} + +int decodeRepeat(const char *in, int len, char *out, int ol, int *bit_no) { + int dict_len = readCount(in, bit_no, len) + NICE_LEN; + int dist = readCount(in, bit_no, len) + NICE_LEN - 1; + memcpy(out + ol, out + ol - dist, dict_len); + ol += dict_len; + + return ol; +} + +int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out) { + + int dstate; + int bit_no; + byte is_all_upper; + + int ol = 0; + bit_no = 0; + dstate = SHX_SET1; + is_all_upper = 0; + + len <<= 3; // *8, len in bits + out[ol] = 0; + while (bit_no < len) { + int h, v; + char c = 0; + byte is_upper = is_all_upper; + int orig_bit_no = bit_no; + v = getCodeIdx(us_vcode, in, len, &bit_no); // read vCode + if (v < 0) break; // end of stream + h = dstate; // Set1 or Set2 + if (v == 0) { // Switch which is common to Set1 and Set2, first entry + h = getCodeIdx(us_hcode, in, len, &bit_no); // read hCode + if (h < 0) break; // end of stream + if (h == SHX_SET1) { // target is Set1 + if (dstate == SHX_SET1) { // Switch from Set1 to Set1 us UpperCase + if (is_all_upper) { // if CapsLock, then back to LowerCase + is_upper = is_all_upper = 0; + continue; + } + v = getCodeIdx(us_vcode, in, len, &bit_no); // read again vCode + if (v < 0) break; // end of stream + if (v == 0) { + h = getCodeIdx(us_hcode, in, len, &bit_no); // read second hCode + if (h < 0) break; // end of stream + if (h == SHX_SET1) { // If double Switch Set1, the CapsLock + is_all_upper = 1; + continue; + } + } + is_upper = 1; // anyways, still uppercase + } else { + dstate = SHX_SET1; // if Set was not Set1, switch to Set1 + continue; + } + } else + if (h == SHX_SET2) { // If Set2, switch dstate to Set2 + if (dstate == SHX_SET1) // TODO: is this test useful, there are only 2 states possible + dstate = SHX_SET2; + continue; + } + if (h != SHX_SET1) { // all other Sets (why not else) + v = getCodeIdx(us_vcode, in, len, &bit_no); // we changed set, now read vCode for char + if (v < 0) break; // end of stream + } + } + + if (v == 0 && h == SHX_SET1A) { + if (is_upper) { + out[ol++] = 255 - readCount(in, &bit_no, len); // binary + } else { + ol = decodeRepeat(in, len, out, ol, &bit_no); // dist + } + continue; + } + + if (h == SHX_SET1 && v == 3) { + // was Unicode, will do Binary instead + out[ol++] = 255 - readCount(in, &bit_no, len); // binary + continue; + } + if (h < 7 && v < 11) // TODO: are these the actual limits? Not 11x7 ? + c = pgm_read_byte(&sets[h][v]); + if (c >= 'a' && c <= 'z') { + if (is_upper) + c -= 32; // go to UpperCase for letters + } else { // handle all other cases + if (is_upper && dstate == SHX_SET1 && v == 1) + c = '\t'; // If UpperCase Space, change to TAB + if (h == SHX_SET1B) { + if (8 == v) { // was LF or RPT, now only LF + // if (is_upper) { // rpt + // int count = readCount(in, &bit_no, len); + // count += 4; + // char rpt_c = out[ol - 1]; + // while (count--) + // out[ol++] = rpt_c; + // } else { + out[ol++] = '\n'; + // } + continue; + } + if (9 == v) { // was CRLF, now RPT + // out[ol++] = '\r'; // CRLF removed + // out[ol++] = '\n'; + int count = readCount(in, &bit_no, len); + count += 4; + if (ol + count >= len_out) { + return -1; // overflow + } + char rpt_c = out[ol - 1]; + while (count--) + out[ol++] = rpt_c; + continue; + } + if (10 == v) { + break; // TERM, stop decoding + } + } + } + out[ol++] = c; + + if (ol >= len_out) { + return -1; // overflow + } + } + + return ol; + +} diff --git a/lib/Unishox-1.0-shadinger/src/unishox.h b/lib/Unishox-1.0-shadinger/src/unishox.h new file mode 100644 index 000000000..4d6b81641 --- /dev/null +++ b/lib/Unishox-1.0-shadinger/src/unishox.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Siara Logics (cc) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Arundale R. + * + */ +#ifndef unishox +#define unishox + +extern int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out); +extern int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out); + +#endif + diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 5a35b9b6d..eeae3def4 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -11,6 +11,7 @@ - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz - Change PWM updated to the latest version of Arduino PR #7231 +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules ### 8.2.0.5 20200425 diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 72a763b3c..ce93a6eed 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -397,6 +397,7 @@ // -- Rules or Script ---------------------------- // Select none or only one of the below defines #define USE_RULES // Add support for rules (+8k code) + // #define USE_RULES_COMPRESSION // Compresses rules in Flash at about ~50% (+3.8k code) //#define USE_SCRIPT // Add support for script (+17k code) //#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support diff --git a/tasmota/settings.h b/tasmota/settings.h index 3925aa30d..edd3de523 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -112,7 +112,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t only_json_message : 1; // bit 8 (v8.2.0.3) - SetOption90 - Disable non-json MQTT response uint32_t fade_at_startup : 1; // bit 9 (v8.2.0.3) - SetOption91 - Enable light fading at start/power on uint32_t pwm_ct_mode : 1; // bit 10 (v8.2.0.4) - SetOption92 - Set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...) - uint32_t spare11 : 1; + uint32_t compress_rules_cpu : 1; // bit 11 (v8.2.0.6) - SetOption93 - Keep uncompressed rules in memory to avoid CPU load of uncompressing at each tick uint32_t spare12 : 1; uint32_t spare13 : 1; uint32_t spare14 : 1; diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 83527ea6f..de890ee41 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -1404,6 +1404,10 @@ void SettingsDelta(void) Settings.module = WEMOS; ModuleDefault(WEMOS); #endif // ESP32 + // make sure the empty rules have two consecutive NULLs, to be compatible with compressed rules + if (Settings.rules[0][0] == 0) { Settings.rules[0][1] = 0; } + if (Settings.rules[1][0] == 0) { Settings.rules[1][1] = 0; } + if (Settings.rules[2][0] == 0) { Settings.rules[2][1] = 0; } } Settings.version = VERSION; diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index fa5841564..c88806ced 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -66,6 +66,8 @@ #define XDRV_10 10 +#include + #define D_CMND_RULE "Rule" #define D_CMND_RULETIMER "RuleTimer" #define D_CMND_EVENT "Event" @@ -178,6 +180,222 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; #error MAX_RULE_MEMS is bigger than 16 #endif + +/*******************************************************************************************/ +/* + * Add Unishox compression to Rules + * + * New compression for Rules, depends on SetOption93 + * + * To avoid memory corruption when downgrading, the format is as follows: + * - If `SetOption93 0` + * Rule[x][] = 511 char max NULL terminated string (512 with trailing NULL) + * Rule[x][0] = 0 if the Rule is empty + * New: in case the string is empty we also enforce: + * Rule[x][1] = 0 (i.e. we have two conseutive NULLs) + * + * - If `SetOption93 1` + * If the rule is smaller than 511, it is stored uncompressed. Rule[x][0] is not null. + * If the rule is empty, Rule[x][0] = 0 and Rule[x][1] = 0; + * If the rule is bigger than 511, it is stored compressed + * The first byte of each Rule is always NULL. + * Rule[x][0] = 0, if firmware is downgraded, the rule will be considered as empty + * + * The second byte contains the size of uncompressed rule in 8-bytes blocks (i.e. (len+7)/8 ) + * Maximum rule size si 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% + * Rule[x][1] = size uncompressed in dwords. If zero, the rule is empty. + * + * The remaining bytes contain the compressed rule, NULL terminated + */ +/*******************************************************************************************/ + +#ifdef USE_RULES_COMPRESSION +// Statically allocate one String per rule +String k_rules[MAX_RULE_SETS] = { String(), String(), String() }; // Strings are created empty +#endif // USE_RULES_COMPRESSION + +// Returns whether the rule is uncompressed, which means the first byte is not NULL +inline bool IsRuleUncompressed(uint32_t idx) { +#ifdef USE_RULES_COMPRESSION + return Settings.rules[idx][0] ? true : false; // first byte not NULL, the rule is not empty and not compressed +#else + return true; +#endif +} + +// Returns whether the rule is empty, which requires two consecutive NULL +inline bool IsRuleEmpty(uint32_t idx) { +#ifdef USE_RULES_COMPRESSION + return (Settings.rules[idx][0] == 0) && (Settings.rules[idx][1] == 0) ? true : false; +#else + return (Settings.rules[idx][0] == 0) ? true : false; +#endif +} + +// Returns the approximate (+3-0) length of the rule, not counting the trailing NULL +size_t GetRuleLen(uint32_t idx) { + // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true + if (IsRuleUncompressed(idx)) { + return strlen(Settings.rules[idx]); + } else { // either empty or compressed + return Settings.rules[idx][1] * 8; // cheap calculation, but not byte accurate (may overshoot by 7) + } +} + +// Returns the actual Flash storage for the Rule, including trailing NULL +size_t GetRuleLenStorage(uint32_t idx) { + // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true + if (IsRuleUncompressed(idx)) { + return 1 + strlen(Settings.rules[idx]); + } else { + return 2 + strlen(&Settings.rules[idx][2]); // skip first byte and get len of the compressed rule + } +} + +// internal function, do the actual decompression +void GetRule_decompress(String &rule, const char *rule_head) { + size_t buf_len = 1 + *rule_head * 8; // the first byte contains size of buffer for uncompressed rule / 8, buf_len may overshoot by 7 + rule_head++; // advance to the actual compressed buffer + + // We use a nasty trick here. To avoid allocating twice the buffer, + // we first extend the buffer of the String object to the target size (maybe overshooting by 7 bytes) + // then we decompress in this buffer, + // and finally assign the raw string to the String, which happens to work: String uses memmove(), so overlapping works + rule.reserve(buf_len); + char* buf = rule.begin(); + + int32_t len_decompressed = unishox_decompress(rule_head, strlen(rule_head), buf, buf_len); + buf[len_decompressed] = 0; // add NULL terminator + + // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Rawdecompressed: %d"), len_decompressed); + rule = buf; // assign the raw string to the String object (in reality re-writing the same data in the same place) +} + +// +// Read rule in memory, uncompress if needed +// +// Returns: String() object containing a copy of the rule (rule processing is destructive and will change the String) +String GetRule(uint32_t idx) { + if (IsRuleUncompressed(idx)) { + return String(Settings.rules[idx]); + } else { +#ifdef USE_RULES_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code + + String rule(""); + if (Settings.rules[idx][2] == 0) { return rule; } // the rule is empty + + // If the cache is empty, we need to decompress from Settings + if (0 == k_rules[idx].length() ) { + GetRule_decompress(rule, &Settings.rules[idx][1]); + if (!Settings.flag4.compress_rules_cpu) { + k_rules[idx] = rule; // keep a copy for next time + } + } else { + // we have a valid copy + rule = k_rules[idx]; + } + return rule; +#endif + } +} + +#ifdef USE_RULES_COMPRESSION +// internal function, comrpess rule and store a cached version uncompressed (except if SetOption94 1) +// If out == nullptr, we are in dry-run mode, so don't keep rule in cache +int32_t SetRule_compress(uint32_t idx, const char *in, size_t in_len, char *out, size_t out_len) { + int32_t len_compressed; + len_compressed = unishox_compress(in, in_len, out, out_len); + + if (len_compressed >= 0) { // negative means compression failed because of buffer too small, we leave the rule untouched + // check if we need to store in cache + k_rules[idx] = (const char*) nullptr; // Assign the String to nullptr, clears previous string and disallocate internal buffers of String object + if ((!Settings.flag4.compress_rules_cpu) && out) { // if out == nullptr, don't store cache + // keep copy in cache + k_rules[idx] = in; + } + } + return len_compressed; +} +#endif // USE_RULES_COMPRESSION + +// Returns: +// >= 0 : the actual stored size +// <0 : not enough space +int32_t SetRule(uint32_t idx, const char *content, bool append = false) { + if (nullptr == content) { content = ""; } // if nullptr, use empty string + size_t len_in = strlen(content); + bool needsCompress = false; + size_t offset = 0; + + if (len_in >= MAX_RULE_SIZE) { // if input is more than 512, it will not fit uncompressed + needsCompress = true; + } + if (append) { + if (IsRuleUncompressed(idx) || IsRuleEmpty(idx)) { // if already uncompressed (so below 512) and append mode, check if it still fits uncompressed + offset = strlen(Settings.rules[idx]); + if (len_in + offset >= MAX_RULE_SIZE) { + needsCompress = true; + } + } else { + needsCompress = true; // we append to a non-empty compressed rule, so it won't fit uncompressed + } + } + + if (!needsCompress) { // the rule fits uncompressed, so just copy it + strlcpy(Settings.rules[idx] + offset, content, sizeof(Settings.rules[idx])); + +#ifdef USE_RULES_COMPRESSION + // do a dry-run compression to display how much it would be compressed + int32_t len_compressed, len_uncompressed; + + len_uncompressed = strlen(Settings.rules[idx]); + len_compressed = unishox_compress(Settings.rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8); + AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Stored uncompressed, would compress from %d to %d (-%d%%)"), len_uncompressed, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_uncompressed, 0, 100)); + +#endif // USE_RULES_COMPRESSION + + return len_in + offset; + } else { +#ifdef USE_RULES_COMPRESSION + int32_t len_compressed; + // allocate temp buffer so we don't nuke the rule if it's too big to fit + char *buf_out = (char*) malloc(MAX_RULE_SIZE + 8); // take some margin + if (!buf_out) { return -1; } // fail if couldn't allocate + + // compress + if (append) { + String content_append = GetRule(idx); // get original Rule and decompress it if needed + content_append += content; // concat new content + len_in = content_append.length(); // adjust length + len_compressed = SetRule_compress(idx, content_append.c_str(), len_in, buf_out, MAX_RULE_SIZE + 8); + } else { + len_compressed = SetRule_compress(idx, content, len_in, buf_out, MAX_RULE_SIZE + 8); + } + + if ((len_compressed >= 0) && (len_compressed < MAX_RULE_SIZE - 2)) { + // size is ok, copy to Settings + Settings.rules[idx][0] = 0; // clear first byte to mark as compressed + Settings.rules[idx][1] = (len_in + 7) / 8; // store original length in first bytes (4 bytes chuks) + memcpy(&Settings.rules[idx][2], buf_out, len_compressed); + Settings.rules[idx][len_compressed + 2] = 0; // add NULL termination + AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Compressed from %d to %d (-%d%%)"), len_in, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_in, 0, 100)); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: First bytes: %02X%02X%02X%02X"), Settings.rules[idx][0], Settings.rules[idx][1], Settings.rules[idx][2], Settings.rules[idx][3]); + // AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: GetRuleLenStorage = %d"), GetRuleLenStorage(idx)); + } else { + len_compressed = -1; // failed + // clear rule cache, so it will be reloaded from Settings + k_rules[idx] = (const char *) nullptr; + } + free(buf_out); + return len_compressed; + +#else // USE_RULES_COMPRESSION + return -1; // the rule does not fit and we can't compress +#endif // USE_RULES_COMPRESSION + } + +} + /*******************************************************************************************/ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) @@ -419,7 +637,7 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved) //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event = %s, Rule = %s"), event_saved.c_str(), Settings.rules[rule_set]); - String rules = Settings.rules[rule_set]; + String rules = GetRule(rule_set); Rules.trigger_count[rule_set] = 0; int plen = 0; @@ -531,7 +749,7 @@ bool RulesProcessEvent(char *json_event) //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str()); for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { - if (strlen(Settings.rules[i]) && bitRead(Settings.rule_enabled, i)) { + if (GetRuleLen(i) && bitRead(Settings.rule_enabled, i)) { if (RuleSetProcess(i, event_saved)) { serviced = true; } } } @@ -547,7 +765,7 @@ void RulesInit(void) { rules_flag.data = 0; for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { - if (Settings.rules[i][0] == '\0') { + if (0 == GetRuleLen(i)) { bitWrite(Settings.rule_enabled, i, 0); bitWrite(Settings.rule_once, i, 0); } @@ -1727,7 +1945,8 @@ void CmndRule(void) { uint8_t index = XdrvMailbox.index; if ((index > 0) && (index <= MAX_RULE_SETS)) { - if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) { + // if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) { // TODO postpone size calculation + if (XdrvMailbox.data_len > 0) { // TODO postpone size calculation if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { switch (XdrvMailbox.payload) { case 0: // Off @@ -1753,24 +1972,24 @@ void CmndRule(void) break; } } else { - int offset = 0; + bool append = false; if ('+' == XdrvMailbox.data[0]) { - offset = strlen(Settings.rules[index -1]); - if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) { // Check free space - XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted - } else { - offset = -1; // Not enough space so skip it - } + XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted + append = true; } - if (offset != -1) { - strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1])); + int32_t res = SetRule(index - 1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, append); + if (res < 0) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR("RUL: not enough space")); } } Rules.triggers[index -1] = 0; // Reset once flag } + // snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), + // XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), + // GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"), XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), - GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]); + GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[0]) - GetRuleLenStorage(index - 1), GetRule(index - 1).c_str()); } } From d9c9eeca78ea74e25a3101c32976c715b689825b Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 8 May 2020 19:45:39 +0200 Subject: [PATCH 19/29] Update Italian language --- tasmota/language/it_IT.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 5c5c8a537..c9710ef01 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -674,7 +674,7 @@ #define D_SENSOR_HRXL_RX "HRXL - RX" #define D_SENSOR_ELECTRIQ_MOODL "MOODL - TX" #define D_SENSOR_AS3935 "AS3935" -#define D_SENSOR_WINDMETER_SPEED "WindMeter Spd" +#define D_SENSOR_WINDMETER_SPEED "Velocità vento" #define D_GPIO_WEBCAM_PWDN "CAM_PWDN" #define D_GPIO_WEBCAM_RESET "CAM_RESET" #define D_GPIO_WEBCAM_XCLK "CAM_XCLK" @@ -800,7 +800,7 @@ #define D_AS3935_CAL_OK "calibrazione impostata a:" //xsns_68_opentherm.ino -#define D_SENSOR_BOILER_OT_RX "OpenTherm RX" -#define D_SENSOR_BOILER_OT_TX "OpenTherm TX" +#define D_SENSOR_BOILER_OT_RX "OpenTherm - RX" +#define D_SENSOR_BOILER_OT_TX "OpenTherm - TX" #endif // _LANGUAGE_IT_IT_H_ From ed33b9c76ba2a8c195abe84ce385033dcd8622a0 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 19:56:27 +0200 Subject: [PATCH 20/29] Quick fix for ESP32 compilation --- lib/Unishox-1.0-shadinger/src/unishox.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp index ada408bff..050d9338c 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.cpp +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -53,6 +53,10 @@ #include "unishox.h" +#ifndef PROGMEM // quick fix for ESP32 compilation +#define PROGMEM +#endif + typedef unsigned char byte; // we squeeze both c_95[] and l_95[] in a sinle array. // c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) From bf829765e4479955abda391265d7ff2371b0b99b Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Fri, 8 May 2020 21:05:37 +0200 Subject: [PATCH 21/29] Fix compilation for ESP32 --- lib/Unishox-1.0-shadinger/src/unishox.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/Unishox-1.0-shadinger/src/unishox.cpp b/lib/Unishox-1.0-shadinger/src/unishox.cpp index 050d9338c..72c9d3915 100644 --- a/lib/Unishox-1.0-shadinger/src/unishox.cpp +++ b/lib/Unishox-1.0-shadinger/src/unishox.cpp @@ -51,12 +51,9 @@ #include #include +#include #include "unishox.h" -#ifndef PROGMEM // quick fix for ESP32 compilation -#define PROGMEM -#endif - typedef unsigned char byte; // we squeeze both c_95[] and l_95[] in a sinle array. // c_95[] uses only the 3 upper nibbles (or 12 most signifcant bits), while the last nibble encodes length (3..13) From 860db1b1d394e8833212e55d145aa42c34c9aecb Mon Sep 17 00:00:00 2001 From: Michael Ingraham <34340210+meingraham@users.noreply.github.com> Date: Sat, 9 May 2020 10:41:56 -0400 Subject: [PATCH 22/29] FIRMWARE_BASIC remnant --- tasmota/tasmota_configurations.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index 3fc3cc0e0..a3494a6c7 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -505,7 +505,7 @@ #undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef DEBUG_THEO // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code -#endif // FIRMWARE_BASIC +#endif // FIRMWARE_LITE /*********************************************************************************************\ * [tasmota-minimal.bin] From 64c71970cbcbfa6d8ffba49145bf5da2d46ab3c2 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sat, 9 May 2020 18:05:13 +0200 Subject: [PATCH 23/29] Fix clearing rules --- tasmota/xdrv_10_rules.ino | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index c88806ced..ba74c08f8 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -244,12 +244,15 @@ size_t GetRuleLen(uint32_t idx) { // Returns the actual Flash storage for the Rule, including trailing NULL size_t GetRuleLenStorage(uint32_t idx) { - // no need to use #ifdef USE_RULES_COMPRESSION, the compiler will optimize since first test is always true - if (IsRuleUncompressed(idx)) { - return 1 + strlen(Settings.rules[idx]); +#ifdef USE_RULES_COMPRESSION + if (Settings.rules[idx][0] || !Settings.rules[idx][1]) { // if first byte is non-NULL it is uncompressed, if second byte is NULL, then it's either uncompressed or empty + return 1 + strlen(Settings.rules[idx]); // uncompressed or empty } else { - return 2 + strlen(&Settings.rules[idx][2]); // skip first byte and get len of the compressed rule + return 2 + strlen(&Settings.rules[idx][1]); // skip first byte and get len of the compressed rule } +#else + return 1 + strlen(Settings.rules[idx]); +#endif } // internal function, do the actual decompression @@ -282,7 +285,7 @@ String GetRule(uint32_t idx) { #ifdef USE_RULES_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code String rule(""); - if (Settings.rules[idx][2] == 0) { return rule; } // the rule is empty + if (Settings.rules[idx][1] == 0) { return rule; } // the rule is empty // If the cache is empty, we need to decompress from Settings if (0 == k_rules[idx].length() ) { @@ -343,14 +346,19 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) { if (!needsCompress) { // the rule fits uncompressed, so just copy it strlcpy(Settings.rules[idx] + offset, content, sizeof(Settings.rules[idx])); + if (0 == Settings.rules[idx][0]) { + Settings.rules[idx][1] = 0; + } #ifdef USE_RULES_COMPRESSION - // do a dry-run compression to display how much it would be compressed - int32_t len_compressed, len_uncompressed; + if (0 != len_in + offset) { + // do a dry-run compression to display how much it would be compressed + int32_t len_compressed, len_uncompressed; - len_uncompressed = strlen(Settings.rules[idx]); - len_compressed = unishox_compress(Settings.rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8); - AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Stored uncompressed, would compress from %d to %d (-%d%%)"), len_uncompressed, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_uncompressed, 0, 100)); + len_uncompressed = strlen(Settings.rules[idx]); + len_compressed = unishox_compress(Settings.rules[idx], len_uncompressed, nullptr /* dry-run */, MAX_RULE_SIZE + 8); + AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: Stored uncompressed, would compress from %d to %d (-%d%%)"), len_uncompressed, len_compressed, 100 - changeUIntScale(len_compressed, 0, len_uncompressed, 0, 100)); + } #endif // USE_RULES_COMPRESSION From 92c05faa8b29b87220b4a4a0593ccd0d618eb2fc Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:22:12 +0200 Subject: [PATCH 24/29] Add root level triggers as discussed on Discord - Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 ++ tasmota/CHANGELOG.md | 3 ++- tasmota/xdrv_10_rules.ino | 23 ++++++++++++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e86665a5d..00e1c4cf3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -103,3 +103,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index eeae3def4..5abf5c781 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -6,12 +6,13 @@ - Add support for analog anemometer by Matteo Albinola (#8283) - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) +- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules +- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz - Change PWM updated to the latest version of Arduino PR #7231 -- Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules ### 8.2.0.5 20200425 diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index ba74c08f8..f83b23bb6 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -47,6 +47,7 @@ * on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon * on switch1#state do power2 %value% endon * on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon + * on root#loadavg<50 do power 2 endon * * Notes: * Spaces after , around and before are mandatory @@ -186,25 +187,25 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; * Add Unishox compression to Rules * * New compression for Rules, depends on SetOption93 - * + * * To avoid memory corruption when downgrading, the format is as follows: * - If `SetOption93 0` * Rule[x][] = 511 char max NULL terminated string (512 with trailing NULL) * Rule[x][0] = 0 if the Rule is empty * New: in case the string is empty we also enforce: * Rule[x][1] = 0 (i.e. we have two conseutive NULLs) - * + * * - If `SetOption93 1` * If the rule is smaller than 511, it is stored uncompressed. Rule[x][0] is not null. * If the rule is empty, Rule[x][0] = 0 and Rule[x][1] = 0; * If the rule is bigger than 511, it is stored compressed * The first byte of each Rule is always NULL. * Rule[x][0] = 0, if firmware is downgraded, the rule will be considered as empty - * + * * The second byte contains the size of uncompressed rule in 8-bytes blocks (i.e. (len+7)/8 ) * Maximum rule size si 2KB (2048 bytes per rule), although there is little chances compression ratio will go down to 75% * Rule[x][1] = size uncompressed in dwords. If zero, the rule is empty. - * + * * The remaining bytes contain the compressed rule, NULL terminated */ /*******************************************************************************************/ @@ -490,13 +491,21 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); + StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data - if (!root[rule_task].success()) { return false; } // No rule_task in JSON data - JsonObject &obj1 = root[rule_task]; - JsonObject *obj = &obj1; + JsonObject *obj; + if (rule_task.startsWith("ROOT")) { // Support root level + obj = &root; + } else { + if (!root[rule_task].success()) { return false; } // No rule_task in JSON data + JsonObject &obj1 = root[rule_task]; + obj = &obj1; + } + String subtype; uint32_t i = 0; while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" From bf215b1b9df24e1fe34745576f2c9d5a58995dc2 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:33:19 +0200 Subject: [PATCH 25/29] Update root level triggers Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 +- tasmota/CHANGELOG.md | 2 +- tasmota/xdrv_10_rules.ino | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 00e1c4cf3..aba4ced89 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -104,4 +104,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 5abf5c781..f02a4c320 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,7 +7,7 @@ - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``root#`` to trigger on any root value like ``on root#loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index f83b23bb6..476a1d392 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -491,14 +491,14 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); +AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data JsonObject *obj; - if (rule_task.startsWith("ROOT")) { // Support root level + if ((rule_task.length() == 0) || rule_task.startsWith("ROOT")) { // Support root level obj = &root; } else { if (!root[rule_task].success()) { return false; } // No rule_task in JSON data From cc2f7cbf5a15c5a9bca4d4a1197bcb20300d0374 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 May 2020 19:35:16 +0200 Subject: [PATCH 26/29] Oops remove debug message --- tasmota/xdrv_10_rules.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 476a1d392..f790768b9 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -491,7 +491,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); From de40260f3eaa3bf1d7ce690af76c963ac8649d67 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 10 May 2020 10:18:23 +0200 Subject: [PATCH 27/29] Fix BatteryPercentage calculation --- tasmota/xdrv_23_zigbee_5_converters.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index a0c59f20b..07b5f5ef1 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -703,7 +703,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Zuint16, Cx0001, 0x0000, Z(MainsVoltage), &Z_Copy }, { Zuint8, Cx0001, 0x0001, Z(MainsFrequency), &Z_Copy }, { Zuint8, Cx0001, 0x0020, Z(BatteryVoltage), &Z_FloatDiv10 }, - { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), &Z_Copy }, + { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), &Z_FloatDiv2 }, // Device Temperature Configuration cluster { Zint16, Cx0002, 0x0000, Z(CurrentTemperature), &Z_Copy }, From 89b130b45a1b1ed6e3f716a10bed64681a83e98b Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 11:09:34 +0200 Subject: [PATCH 28/29] Change root rule trigger Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command --- RELEASENOTES.md | 2 +- tasmota/CHANGELOG.md | 2 +- tasmota/xdrv_10_rules.ino | 39 +++++++++++++++------------------------ 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index aba4ced89..24e65e4b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -104,4 +104,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for Thermostat control by arijav (#8212) - Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index f02a4c320..a54809a68 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,7 +7,7 @@ - Add support for OpenTherm by Yuriy Sannikov (#8373) - Add support for Thermostat control by arijav (#8212) - Add automatic compression of Rules to achieve ~60% compression, added ``SetOption93 1`` to control caching of rules -- Add rule trigger ``#`` to trigger on any root value like ``on #loadavg<50 do power 2 endon`` after ``state`` command +- Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command - Change flash access removing support for any Core before 2.6.3 - Change HAss discovery by Federico Leoni (#8370) - Change default PWM Frequency to 977 Hz from 223 Hz diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index f790768b9..f2f01a8db 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -417,19 +417,20 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) char stemp[10]; // Step1: Analyse rule - int pos = rule.indexOf('#'); - if (pos == -1) { return false; } // No # sign in rule - - String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM" + String rule_expr = rule; // "TELE-INA219#CURRENT>0.100" if (Rules.teleperiod) { - int ppos = rule_task.indexOf("TELE-"); // "TELE-INA219" or "INA219" + int ppos = rule_expr.indexOf("TELE-"); // "TELE-INA219#CURRENT>0.100" or "INA219#CURRENT>0.100" if (ppos == -1) { return false; } // No pre-amble in rule - rule_task = rule.substring(5, pos); // "INA219" or "SYSTEM" + rule_expr = rule.substring(5); // "INA219#CURRENT>0.100" or "SYSTEM#BOOT" } - String rule_expr = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5" String rule_name, rule_param; - int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param); //Parse the compare expression.Return operator and the left, right part of expression + int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param); // Parse the compare expression.Return operator and the left, right part of expression + + // rule_name = "INA219#CURRENT" + // rule_param = "0.100" or "%VAR1%" + +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: expr %s, name %s, param %s"), rule_expr.c_str(), rule_name.c_str(), rule_param.c_str()); char rule_svalue[80] = { 0 }; float rule_value = 0; @@ -477,11 +478,12 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) if (temp_value > -1) { rule_value = temp_value; } else { - rule_value = CharToFloat((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()! + rule_value = CharToFloat((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()! } } - // Step2: Search rule_task and rule_name + // Step2: Search rule_name + int pos; int rule_name_idx = 0; if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]" rule_name_idx = rule_name.substring(pos +1).toInt(); @@ -491,21 +493,10 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Find Task %s, Name %s"), rule_task.c_str(), rule_name.c_str()); - StaticJsonBuffer<1024> jsonBuf; JsonObject &root = jsonBuf.parseObject(event); if (!root.success()) { return false; } // No valid JSON data - - JsonObject *obj; - if ((rule_task.length() == 0) || rule_task.startsWith("ROOT")) { // Support root level - obj = &root; - } else { - if (!root[rule_task].success()) { return false; } // No rule_task in JSON data - JsonObject &obj1 = root[rule_task]; - obj = &obj1; - } - + JsonObject *obj = &root; String subtype; uint32_t i = 0; while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" @@ -524,8 +515,8 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) str_value = (*obj)[rule_name]; // "CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), -// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); +//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), +// rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); Rules.event_value = str_value; // Prepare %value% From 02f61a5259d7418dedc5c6bd0fdb35914d084392 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 10 May 2020 11:10:55 +0200 Subject: [PATCH 29/29] Allow rule processing on single Status command Allow rule processing on single Status command only --- tasmota/support_command.ino | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index d4b4337c9..1bd243754 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -561,6 +561,11 @@ void CmndStatus(void) #ifdef USE_SCRIPT_STATUS if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); #endif + + if (payload) { + XdrvRulesProcess(); // Allow rule processing on single Status command only + } + mqtt_data[0] = '\0'; }