Merge pull request #62 from arendst/development

Update
This commit is contained in:
Jason2866 2020-05-11 15:52:08 +02:00 committed by GitHub
commit 99298bdb89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1767 additions and 430 deletions

View File

@ -147,6 +147,7 @@ People helping to keep the show on the road:
- Christian Staars for NRF24L01 and HM-10 Bluetooth sensor support - Christian Staars for NRF24L01 and HM-10 Bluetooth sensor support
- Paul Diem for UDP Group communication support - Paul Diem for UDP Group communication support
- Jörg Schüler-Maroldt for his initial ESP32 port - Jörg Schüler-Maroldt for his initial ESP32 port
- Javier Arigita for his thermostat driver
- Many more providing Tips, Wips, Pocs, PRs and Donations - Many more providing Tips, Wips, Pocs, PRs and Donations
## License ## License

View File

@ -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) - 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 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 flash access removing support for any Core before 2.6.3
- Change HM-10 sensor type detection and add features (#7962) - 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) - Change light scheme 2,3,4 cycle time speed from 24,48,72,... seconds to 4,6,12,24,36,48,... seconds (#8034)
@ -81,6 +82,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- Add command ``SetOption90 1`` to disable non-json MQTT messages (#8044) - Add command ``SetOption90 1`` to disable non-json MQTT messages (#8044)
- Add command ``SetOption91 1`` to enable fading at startup / power on - Add command ``SetOption91 1`` to enable fading at startup / power on
- Add command ``SetOption92 1`` to set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...) - Add command ``SetOption92 1`` to set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...)
- Add command ``SetOption93 1`` to control caching of compressed rules
- Add command ``Sensor10 0/1/2`` to control BH1750 resolution - 0 = High (default), 1 = High2, 2 = Low (#8016) - Add command ``Sensor10 0/1/2`` to control BH1750 resolution - 0 = High (default), 1 = High2, 2 = Low (#8016)
- Add command ``Sensor10 31..254`` to control BH1750 measurement time which defaults to 69 (#8016) - Add command ``Sensor10 31..254`` to control BH1750 measurement time which defaults to 69 (#8016)
- Add command ``Sensor18 0..32000`` to control PMS5003 sensor interval to extend lifetime by Gene Ruebsamen (#8128) - Add command ``Sensor18 0..32000`` to control PMS5003 sensor interval to extend lifetime by Gene Ruebsamen (#8128)
@ -100,4 +102,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- Add more accuracy to GPS NTP server (#8088) - Add more accuracy to GPS NTP server (#8088)
- Add support for analog anemometer by Matteo Albinola (#8283) - Add support for analog anemometer by Matteo Albinola (#8283)
- Add support for OpenTherm by Yuriy Sannikov (#8373) - 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 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 by Stefan Hadinger
- Add rule trigger at root level like ``on loadavg<50 do power 2 endon`` after ``state`` command

View File

@ -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 <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdint.h>
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");
}

Binary file not shown.

View File

@ -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 <arun@siara.cc>, Stephan <stephan.hadinger@gmail.com>
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

View File

@ -0,0 +1,602 @@
/*
* 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 <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdint.h>
#include <pgmspace.h>
#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<len; l++) {
c_in = in[l];
if (l && l < len - 4) {
if (c_in == in[l - 1] && c_in == in[l + 1] && c_in == in[l + 2] && c_in == in[l + 3]) { // check for repeat
int rpt_count = l + 4;
while (rpt_count < len && in[rpt_count] == c_in)
rpt_count++;
rpt_count -= l;
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); // back to lower case and Set1
}
// ol = append_bits(out, ol, RPT_CODE, RPT_CODE_LEN, 1);
ol = append_bits(out, ol, RPT_CODE_TASMOTA, RPT_CODE_TASMOTA_LEN, 1); // reusing CRLF for RPT
ol = encodeCount(out, ol, rpt_count - 4);
l += rpt_count;
l--;
continue;
}
}
if (l < (len - NICE_LEN + 1)) {
l = matchOccurance(in, len, l, out, &ol, &state, &is_all_upper);
if (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<len; ll--) {
if (in[ll] < 'A' || in[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;
}

View File

@ -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

View File

@ -364,8 +364,12 @@ const unsigned char lut_partial_update[] =
#define PIN_OUT_SET 0x60000304 #define PIN_OUT_SET 0x60000304
#define PIN_OUT_CLEAR 0x60000308 #define PIN_OUT_CLEAR 0x60000308
#define PWRITE xdigitalWrite #ifdef ESP32
#define SSPI_USEANYPIN 1
#define PWRITE digitalWrite
#else
#define PWRITE ydigitalWrite
#endif
#ifndef SSPI_USEANYPIN #ifndef SSPI_USEANYPIN
// uses about 2.75 usecs, 365 kb /sec // uses about 2.75 usecs, 365 kb /sec
@ -388,6 +392,7 @@ void ICACHE_RAM_ATTR Epd::fastSPIwrite(uint8_t d,uint8_t dc) {
} }
#else #else
#ifndef ESP32
extern void ICACHE_RAM_ATTR xdigitalWrite(uint8_t pin, uint8_t val) { extern void ICACHE_RAM_ATTR xdigitalWrite(uint8_t pin, uint8_t val) {
//stopWaveform(pin); //stopWaveform(pin);
if(pin < 16){ if(pin < 16){
@ -398,6 +403,7 @@ extern void ICACHE_RAM_ATTR xdigitalWrite(uint8_t pin, uint8_t val) {
else GP16O &= ~1; else GP16O &= ~1;
} }
} }
#endif
// about 13 us => 76 kb / sec // about 13 us => 76 kb / sec
// can use any pin // can use any pin

View File

@ -502,12 +502,15 @@ const unsigned char lut_wb_quick[] PROGMEM =
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };
#define PIN_OUT_SET 0x60000304 #define PIN_OUT_SET 0x60000304
#define PIN_OUT_CLEAR 0x60000308 #define PIN_OUT_CLEAR 0x60000308
#ifdef ESP32
#define SSPI_USEANYPIN 1
#define PWRITE digitalWrite
#else
#define PWRITE ydigitalWrite #define PWRITE ydigitalWrite
#endif
#ifndef SSPI_USEANYPIN #ifndef SSPI_USEANYPIN
// uses about 2.75 usecs, 365 kb /sec // uses about 2.75 usecs, 365 kb /sec
@ -530,6 +533,7 @@ void ICACHE_RAM_ATTR Epd42::fastSPIwrite(uint8_t d,uint8_t dc) {
} }
#else #else
#ifndef ESP32
extern void ICACHE_RAM_ATTR ydigitalWrite(uint8_t pin, uint8_t val) { extern void ICACHE_RAM_ATTR ydigitalWrite(uint8_t pin, uint8_t val) {
//stopWaveform(pin); //stopWaveform(pin);
if(pin < 16){ if(pin < 16){
@ -540,6 +544,7 @@ extern void ICACHE_RAM_ATTR ydigitalWrite(uint8_t pin, uint8_t val) {
else GP16O &= ~1; else GP16O &= ~1;
} }
} }
#endif
// about 13 us => 76 kb / sec // about 13 us => 76 kb / sec
// can use any pin // can use any pin
void Epd42::fastSPIwrite(uint8_t d,uint8_t dc) { void Epd42::fastSPIwrite(uint8_t d,uint8_t dc) {

View File

@ -5,8 +5,16 @@
- Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt - 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 analog anemometer by Matteo Albinola (#8283)
- Add support for OpenTherm by Yuriy Sannikov (#8373) - 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 by Stefan Hadinger
- Add command ``SetOption93 1`` to control caching of compressed rules
- 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 flash access removing support for any Core before 2.6.3
- Change HAss discovery by Federico Leoni (#8370) - 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
- Change Philips Hue emulation now exposes modelId and manufacturerId
### 8.2.0.5 20200425 ### 8.2.0.5 20200425

View File

@ -47,29 +47,23 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // 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 _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); extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
// Maximum delay between IRQs // Maximum delay between IRQs
#define MAXIRQUS (10000) #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 // Waveform generator can create tones, PWM, and servos
typedef struct { typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required 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 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 timeLowCycles; //
uint32_t desiredHighCycles; // Currently running waveform period uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
uint32_t desiredLowCycles; // uint32_t desiredLowCycles; //
uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase uint32_t lastEdge; // Cycle when this generator last changed
uint32_t gotoTimeLowCycles; //
uint32_t lastEdge; //
} Waveform; } Waveform;
class WVFState { 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 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 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 waveformNewHigh = 0;
uint32_t waveformNewLow = 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 // 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 // are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins. // we can avoid looking at the other pins.
int startPin = 0; uint16_t startPin = 0;
int endPin = 0; uint16_t endPin = 0;
}; };
static WVFState wvfState; static WVFState wvfState;
@ -107,7 +101,7 @@ static WVFState wvfState;
static ICACHE_RAM_ATTR void timer1Interrupt(); static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false; static bool timerRunning = false;
static void initTimer() { static __attribute__((noinline)) void initTimer() {
if (!timerRunning) { if (!timerRunning) {
timer1_disable(); timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() {
constexpr int maxPWMs = 8; constexpr int maxPWMs = 8;
// PWM machine state // PWM machine state
typedef struct { typedef struct PWMState {
uint32_t mask; // Bitmask of active pins uint32_t mask; // Bitmask of active pins
uint32_t cnt; // How many entries uint32_t cnt; // How many entries
uint32_t idx; // Where the state machine is along the list uint32_t idx; // Where the state machine is along the list
uint8_t pin[maxPWMs + 1]; uint8_t pin[maxPWMs + 1];
uint32_t delta[maxPWMs + 1]; uint32_t delta[maxPWMs + 1];
uint32_t nextServiceCycle; // Clock cycle for next step uint32_t nextServiceCycle; // Clock cycle for next step
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
} PWMState; } PWMState;
static PWMState 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() { static ICACHE_RAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); 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 // Called when analogWriteFreq() changed to update the PWM total period
void _setPWMPeriodCC(uint32_t cc) { void _setPWMFreq(uint32_t freq) {
if (cc == pwmPeriod) { // Convert frequency into clock cycles
return; 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) { 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 PWMState p; // The working copy since we can't edit the one in use
p = pwmState; p.cnt = 0;
uint32_t ttl = 0; for (uint32_t i = 0; i < pwmState.cnt; i++) {
for (uint32_t i = 0; i < p.cnt; i++) { auto pin = pwmState.pin[i];
uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
uint64_t newVal64p32 = val64p16 * ratio64p16;
p.delta[i] = newVal64p32 >> 32;
ttl += p.delta[i];
} }
p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles
// Update and wait for mailbox to be emptied // Update and wait for mailbox to be emptied
pwmUpdate = &p; initTimer();
MEMBARRIER(); _notifyPWM(&p, true);
forceTimerInterrupt(); disableIdleTimer();
while (pwmUpdate) {
delay(0);
// No mem barrier. The external function call guarantees it's re-read
}
} }
pwmPeriod = cc;
} }
// Helper routine to remove an entry from the state machine // Helper routine to remove an entry from the state machine
static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { // and clean up any marked-off entries
uint32_t i; static void _cleanAndRemovePWM(PWMState *p, int pin) {
uint32_t leftover = 0;
// Find the pin to pull out... uint32_t in, out;
for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } for (in = 0, out = 0; in < p->cnt; in++) {
auto delta = p->delta[i]; if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
p->pin[out] = p->pin[in];
// Add the removed previous pin delta to preserve absolute position p->delta[out] = p->delta[in] + leftover;
p->delta[i+1] += delta; leftover = 0;
out++;
// Move everything back one } else {
for (i++; i <= p->cnt; i++) { leftover += p->delta[in];
p->pin[i-1] = p->pin[i]; p->mask &= ~(1<<p->pin[in]);
p->delta[i-1] = p->delta[i]; }
} }
// Remove the pin from the active list p->cnt = out;
p->mask &= ~(1<<pin); // Final pin is never used: p->pin[out] = 0xff;
p->cnt--; 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) { ICACHE_RAM_ATTR bool _stopPWM(int pin) {
if (!((1<<pin) & pwmState.mask)) { if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active return false; // Pin not actually active
@ -225,67 +236,88 @@ ICACHE_RAM_ATTR bool _stopPWM(int pin) {
PWMState p; // The working copy since we can't edit the one in use PWMState p; // The working copy since we can't edit the one in use
p = pwmState; p = pwmState;
_removePWMEntry(pin, &p);
// Update and wait for mailbox to be emptied // In _stopPWM we just clear the mask but keep everything else
pwmUpdate = &p; // untouched to save IRAM. The main startPWM will handle cleanup.
MEMBARRIER(); p.mask &= ~(1<<pin);
forceTimerInterrupt(); if (!p.mask) {
while (pwmUpdate) { // If all have been stopped, then turn PWM off completely
MEMBARRIER(); p.cnt = 0;
/* Busy wait, could be in ISR */
} }
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
_notifyPWM(&p, false);
// Possibly shut down the timer completely if we're done // Possibly shut down the timer completely if we're done
disableIdleTimer(); disableIdleTimer();
return true; return true;
} }
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
bool _setPWM(int pin, uint32_t cc) { // Stash the val and range so we can re-evaluate the fraction
stopWaveform(pin); // should the user change PWM frequency. This allows us to
PWMState p; // Working copy // give as great a precision as possible. We know by construction
p = pwmState; // that the waveform for this pin will be inactive so we can borrow
// Get rid of any entries for this pin // memory from that structure.
if ((1<<pin) & p.mask) { wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
_removePWMEntry(pin, &p); wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
uint32_t cc = (_pwmPeriod * val) / range;
if (cc == 0) {
_stopPWM(pin);
digitalWrite(pin, LOW);
return;
} else if (cc >= _pwmPeriod) {
_stopPWM(pin);
digitalWrite(pin, HIGH);
return;
} }
// And add it to the list, in order
if (p.cnt >= maxPWMs) { if (p.cnt == 0) {
return false; // No space left
} else if (p.cnt == 0) {
// Starting up from scratch, special case 1st element and PWM period // Starting up from scratch, special case 1st element and PWM period
p.pin[0] = pin; p.pin[0] = pin;
p.delta[0] = cc; p.delta[0] = cc;
p.pin[1] = 0xff; // Final pin is never used: p.pin[1] = 0xff;
p.delta[1] = pwmPeriod - cc; p.delta[1] = _pwmPeriod - cc;
p.cnt = 1;
p.mask = 1<<pin;
} else { } else {
uint32_t ttl=0; uint32_t ttl = 0;
uint32_t i; uint32_t i;
// Skip along until we're at the spot to insert // Skip along until we're at the spot to insert
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
ttl += p.delta[i]; ttl += p.delta[i];
} }
// Shift everything out by one to make space for new edge // Shift everything out by one to make space for new edge
memmove(&p.pin[i + 1], &p.pin[i], (1 + p.cnt - i) * sizeof(p.pin[0])); for (int32_t j = p.cnt; j >= (int)i; j--) {
memmove(&p.delta[i + 1], &p.delta[i], (1 + p.cnt - i) * sizeof(p.delta[0])); 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 int off = cc - ttl; // The delta from the last edge to the one we're inserting
p.pin[i] = pin; p.pin[i] = pin;
p.delta[i] = off; // Add the delta to this new 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.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
p.cnt++; }
p.mask |= 1<<pin; p.cnt++;
p.mask |= 1<<pin;
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
bool _setPWM(int pin, uint32_t val, uint32_t range) {
stopWaveform(pin);
PWMState p; // Working copy
p = pwmState;
// Get rid of any entries for this pin
_cleanAndRemovePWM(&p, pin);
// And add it to the list, in order
if (p.cnt >= maxPWMs) {
return false; // No space left
} }
_addPWMtoList(p, pin, val, range);
// Set mailbox and wait for ISR to copy it over // Set mailbox and wait for ISR to copy it over
pwmUpdate = &p;
MEMBARRIER();
initTimer(); initTimer();
forceTimerInterrupt(); _notifyPWM(&p, true);
while (pwmUpdate) { disableIdleTimer();
delay(0);
}
return true; return true;
} }
@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time
uint32_t mask = 1<<pin; uint32_t mask = 1<<pin;
MEMBARRIER(); MEMBARRIER();
if (wvfState.waveformEnabled & mask) { if (wvfState.waveformEnabled & mask) {
wvfState.waveformNewHigh = timeHighCycles; // Make sure no waveform changes are waiting to be applied
wvfState.waveformNewLow = timeLowCycles; while (wvfState.waveformToChange) {
MEMBARRIER();
wvfState.waveformToChange = pin;
while (wvfState.waveformToChange >= 0) {
delay(0); // Wait for waveform to update delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated // 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)) { } else { // if (!(wvfState.waveformEnabled & mask)) {
wave->timeHighCycles = timeHighCycles; wave->timeHighCycles = timeHighCycles;
wave->desiredHighCycles = timeHighCycles;
wave->timeLowCycles = timeLowCycles; wave->timeLowCycles = timeLowCycles;
wave->desiredHighCycles = wave->timeHighCycles; wave->desiredLowCycles = timeLowCycles;
wave->desiredLowCycles = wave->timeLowCycles;
wave->lastEdge = 0; 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); wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
wvfState.waveformToEnable |= mask; wvfState.waveformToEnable |= mask;
MEMBARRIER(); MEMBARRIER();
@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) {
// Speed critical bits // Speed critical bits
#pragma GCC optimize ("O2") #pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different // Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the // optimization levels the inline attribute gets lost if we try the
// other version. // other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount; uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(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 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 they send >=32, then the shift will result in 0 and it will also return false
if (wvfState.waveformEnabled & (1UL << pin)) { uint32_t mask = 1<<pin;
wvfState.waveformToDisable = 1UL << pin; if (wvfState.waveformEnabled & mask) {
wvfState.waveformToDisable = mask;
// Cancel any pending updates for this waveform, too.
if (wvfState.waveformToChange & mask) {
wvfState.waveformToChange = 0;
}
forceTimerInterrupt(); forceTimerInterrupt();
while (wvfState.waveformToDisable) { while (wvfState.waveformToDisable) {
MEMBARRIER(); // If it wasn't written yet, it has to be by now MEMBARRIER(); // If it wasn't written yet, it has to be by now
@ -407,15 +444,6 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
#define adjust(x) ((x) >> (turbo ? 0 : 1)) #define adjust(x) ((x) >> (turbo ? 0 : 1))
#endif #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() { static ICACHE_RAM_ATTR void timer1Interrupt() {
// Flag if the core is at 160 MHz, for use by adjust() // 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; wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
#ifdef ENABLE_PWM } else if (!pwmState.cnt && pwmState.pwmUpdate) {
} else if (!pwmState.cnt && pwmUpdate) {
// Start up the PWM generator by copying from the mailbox // Start up the PWM generator by copying from the mailbox
pwmState.cnt = 1; pwmState.cnt = 1;
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
// No need for mem barrier here. Global must be written by IRQ exit // 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; bool done = false;
@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
do { do {
nextEventCycles = microsecondsToClockCycles(MAXIRQUS); nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
#ifdef ENABLE_PWM
// PWM state machine implementation // PWM state machine implementation
if (pwmState.cnt) { if (pwmState.cnt) {
uint32_t now = GetCycleCountIRQ(); int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
int32_t cyclesToGo = pwmState.nextServiceCycle - now;
if (cyclesToGo < 0) { if (cyclesToGo < 0) {
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
if (pwmUpdate) { if (pwmState.pwmUpdate) {
// Do the memory copy from temp to global and clear mailbox // Do the memory copy from temp to global and clear mailbox
pwmState = *(PWMState*)pwmUpdate; pwmState = *(PWMState*)pwmState.pwmUpdate;
pwmUpdate = nullptr; }
} GPOS = pwmState.mask; // Set all active pins high
GPOS = pwmState.mask; // Set all active pins high if (pwmState.mask & (1<<16)) {
// GPIO16 isn't the same as the others GP16O = 1;
if (pwmState.mask & (1<<16)) { }
GP16O = 1; pwmState.idx = 0;
}
pwmState.idx = 0;
} else { } else {
do { do {
// Drop the pin at this edge // Drop the pin at this edge
GPOC = 1<<pwmState.pin[pwmState.idx]; if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
// GPIO16 still needs manual work GPOC = 1<<pwmState.pin[pwmState.idx];
if (pwmState.pin[pwmState.idx] == 16) { if (pwmState.pin[pwmState.idx] == 16) {
GP16O = 0; GP16O = 0;
} }
pwmState.idx++; }
// Any other pins at this same PWM value will have delta==0, drop them too. pwmState.idx++;
} while (pwmState.delta[pwmState.idx] == 0); // Any other pins at this same PWM value will have delta==0, drop them too.
} while (pwmState.delta[pwmState.idx] == 0);
} }
// Preserve duty cycle over PWM period by using now+xxx instead of += delta // Preserve duty cycle over PWM period by using now+xxx instead of += delta
cyclesToGo = adjust(pwmState.delta[pwmState.idx]); cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
@ -491,7 +509,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
} }
nextEventCycles = min_u32(nextEventCycles, cyclesToGo); nextEventCycles = min_u32(nextEventCycles, cyclesToGo);
} }
#endif
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) { for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
uint32_t mask = 1<<i; uint32_t mask = 1<<i;
@ -509,12 +526,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
int32_t expiryToGo = wave->expiryCycle - now; int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) { if (expiryToGo < 0) {
// Done, remove! // Done, remove!
wvfState.waveformEnabled &= ~mask;
if (i == 16) { if (i == 16) {
GP16O = 0; GP16O = 0;
} else { }
ClearGPIO(mask); GPOC = mask;
} wvfState.waveformEnabled &= ~mask;
continue; continue;
} }
} }
@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
wvfState.waveformState ^= mask; wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) { if (wvfState.waveformState & mask) {
if (i == 16) { if (i == 16) {
GP16O = 1; // GPIO16 write slow as it's RMW GP16O = 1;
} else {
SetGPIO(mask);
} }
if (wave->gotoTimeHighCycles) { GPOS = mask;
if (wvfState.waveformToChange & mask) {
// Copy over next full-cycle timings // Copy over next full-cycle timings
wave->timeHighCycles = wave->gotoTimeHighCycles; wave->timeHighCycles = wvfState.waveformNewHigh;
wave->desiredHighCycles = wave->gotoTimeHighCycles; wave->desiredHighCycles = wvfState.waveformNewHigh;
wave->timeLowCycles = wave->gotoTimeLowCycles; wave->timeLowCycles = wvfState.waveformNewLow;
wave->desiredLowCycles = wave->gotoTimeLowCycles; wave->desiredLowCycles = wvfState.waveformNewLow;
wave->gotoTimeHighCycles = 0; wave->lastEdge = 0;
} else { wvfState.waveformToChange = 0;
#ifdef ENABLE_FEEDBACK }
if (wave->lastEdge) { if (wave->lastEdge) {
desired = wave->desiredLowCycles; desired = wave->desiredLowCycles;
timeToUpdate = &wave->timeLowCycles; timeToUpdate = &wave->timeLowCycles;
}
} }
#endif
nextEdgeCycles = wave->timeHighCycles; nextEdgeCycles = wave->timeHighCycles;
} else { } else {
if (i == 16) { if (i == 16) {
GP16O = 0; // GPIO16 write slow as it's RMW GP16O = 0;
} else {
ClearGPIO(mask);
} }
#ifdef ENABLE_FEEDBACK GPOC = mask;
desired = wave->desiredHighCycles; desired = wave->desiredHighCycles;
timeToUpdate = &wave->timeHighCycles; timeToUpdate = &wave->timeHighCycles;
#endif
nextEdgeCycles = wave->timeLowCycles; nextEdgeCycles = wave->timeLowCycles;
} }
#ifdef ENABLE_FEEDBACK
if (desired) { if (desired) {
desired = adjust(desired); desired = adjust(desired);
int32_t err = desired - (now - wave->lastEdge); int32_t err = desired - (now - wave->lastEdge);
@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
*timeToUpdate += err; *timeToUpdate += err;
} }
} }
#endif
nextEdgeCycles = adjust(nextEdgeCycles); nextEdgeCycles = adjust(nextEdgeCycles);
wave->nextServiceCycle = now + nextEdgeCycles; wave->nextServiceCycle = now + nextEdgeCycles;
nextEventCycles = min_u32(nextEventCycles, 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 // Do it here instead of global function to save time and because we know it's edge-IRQ
T1L = nextEventCycles >> (turbo ? 1 : 0); T1L = nextEventCycles >> (turbo ? 1 : 0);
TEIE |= TEIE1; // Edge int enable
} }
}; };

View File

@ -34,9 +34,9 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // 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 _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(); 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 }; volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 };

View File

@ -29,13 +29,11 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // 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 _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 int32_t analogScale = PWMRANGE;
static uint16_t analogFreq = 1000;
extern void __analogWriteRange(uint32_t range) { extern void __analogWriteRange(uint32_t range) {
if (range > 0) { if (range > 0) {
@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) {
} }
} }
extern void __analogWriteFreq(uint32_t freq) { extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) { if (freq < 40) {
analogFreq = 100; freq = 40;
} else if (freq > 60000) { } else if (freq > 60000) {
analogFreq = 60000; freq = 60000;
} else { } else {
analogFreq = freq; freq = freq;
} }
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; _setPWMFreq(freq);
_setPWMPeriodCC(analogPeriod);
} }
extern void __analogWrite(uint8_t pin, int val) { extern void __analogWrite(uint8_t pin, int val) {
@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) {
return; return;
} }
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
_setPWMPeriodCC(analogPeriod);
if (val < 0) { if (val < 0) {
val = 0; val = 0;
} else if (val > analogScale) { } else if (val > analogScale) {
val = analogScale; val = analogScale;
} }
analogMap &= ~(1 << pin);
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
if (low == 0) { _setPWM(pin, val, analogScale);
_stopPWM(pin);
digitalWrite(pin, HIGH);
} else if (high == 0) {
_stopPWM(pin);
digitalWrite(pin, LOW);
} else {
_setPWM(pin, high);
analogMap |= (1 << pin);
}
} }
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));

View File

@ -674,7 +674,7 @@
#define D_SENSOR_HRXL_RX "HRXL - RX" #define D_SENSOR_HRXL_RX "HRXL - RX"
#define D_SENSOR_ELECTRIQ_MOODL "MOODL - TX" #define D_SENSOR_ELECTRIQ_MOODL "MOODL - TX"
#define D_SENSOR_AS3935 "AS3935" #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_PWDN "CAM_PWDN"
#define D_GPIO_WEBCAM_RESET "CAM_RESET" #define D_GPIO_WEBCAM_RESET "CAM_RESET"
#define D_GPIO_WEBCAM_XCLK "CAM_XCLK" #define D_GPIO_WEBCAM_XCLK "CAM_XCLK"
@ -800,7 +800,7 @@
#define D_AS3935_CAL_OK "calibrazione impostata a:" #define D_AS3935_CAL_OK "calibrazione impostata a:"
//xsns_68_opentherm.ino //xsns_68_opentherm.ino
#define D_SENSOR_BOILER_OT_RX "OpenTherm RX" #define D_SENSOR_BOILER_OT_RX "OpenTherm - RX"
#define D_SENSOR_BOILER_OT_TX "OpenTherm TX" #define D_SENSOR_BOILER_OT_TX "OpenTherm - TX"
#endif // _LANGUAGE_IT_IT_H_ #endif // _LANGUAGE_IT_IT_H_

View File

@ -395,8 +395,9 @@
// #define USE_PING // Enable Ping command (+2k code) // #define USE_PING // Enable Ping command (+2k code)
// -- Rules or Script ---------------------------- // -- Rules or Script ----------------------------
// Select none or only one of the below defines // Select none or only one of the below defines USE_RULES or USE_SCRIPT
#define USE_RULES // Add support for rules (+8k code) #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 // Add support for script (+17k code)
//#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support //#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support
@ -669,18 +670,17 @@
//#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code)
// -- Thermostat control ---------------------------- // -- Thermostat control ----------------------------
//#define USE_THERMOSTAT // Add support for Thermostat //#define USE_THERMOSTAT // Add support for Thermostat
#define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently #define THERMOSTAT_CONTROLLER_OUTPUTS 1 // Number of outputs to be controlled independently
#define THERMOSTAT_SENSOR_NAME "DS18B20" // Name of the local sensor to be used #define THERMOSTAT_SENSOR_NAME "DS18B20" // Name of the local sensor to be used
#define THERMOSTAT_RELAY_NUMBER 1 // Default output relay number for the first controller (+i for following ones) #define THERMOSTAT_RELAY_NUMBER 1 // Default output relay number for the first controller (+i for following ones)
#define THERMOSTAT_SWITCH_NUMBER 1 // Default input switch number for the first controller (+i for following ones) #define THERMOSTAT_SWITCH_NUMBER 1 // Default input switch number for the first controller (+i for following ones)
#define THERMOSTAT_TIME_ALLOW_RAMPUP 300 // Default time in seconds after last target update to allow ramp-up controller phase in minutes #define THERMOSTAT_TIME_ALLOW_RAMPUP 300 // Default time after last target update to allow ramp-up controller phase in minutes
#define THERMOSTAT_TIME_RAMPUP_MAX 960 // Default time maximum ramp-up controller duration in minutes #define THERMOSTAT_TIME_RAMPUP_MAX 960 // Default time maximum ramp-up controller duration in minutes
#define THERMOSTAT_TIME_RAMPUP_CYCLE 1800 // Default time ramp-up cycle in seconds #define THERMOSTAT_TIME_RAMPUP_CYCLE 30 // Default time ramp-up cycle in minutes
#define THERMOSTAT_TIME_SENS_LOST 30 // Maximum time w/o sensor update to set it as lost in minutes #define THERMOSTAT_TIME_SENS_LOST 30 // Maximum time w/o sensor update to set it as lost in minutes
#define THERMOSTAT_TEMP_SENS_NUMBER 1 // Default temperature sensor number #define THERMOSTAT_TEMP_SENS_NUMBER 1 // Default temperature sensor number
#define THERMOSTAT_TIME_MANUAL_TO_AUTO 60 // Default time without input switch active to change from manual to automatic in minutes #define THERMOSTAT_TIME_MANUAL_TO_AUTO 60 // Default time without input switch active to change from manual to automatic in minutes
#define THERMOSTAT_TIME_ON_LIMIT 120 // Default maximum time with output active in minutes
#define THERMOSTAT_TIME_RESET 12000 // Default reset time of the PI controller in seconds #define THERMOSTAT_TIME_RESET 12000 // Default reset time of the PI controller in seconds
#define THERMOSTAT_TIME_PI_CYCLE 30 // Default cycle time for the thermostat controller in minutes #define THERMOSTAT_TIME_PI_CYCLE 30 // Default cycle time for the thermostat controller in minutes
#define THERMOSTAT_TIME_MAX_ACTION 20 // Default maximum thermostat time per cycle in minutes #define THERMOSTAT_TIME_MAX_ACTION 20 // Default maximum thermostat time per cycle in minutes

View File

@ -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 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 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 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 spare12 : 1;
uint32_t spare13 : 1; uint32_t spare13 : 1;
uint32_t spare14 : 1; uint32_t spare14 : 1;
@ -182,6 +182,35 @@ typedef union {
}; };
} Timer; } 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 { typedef union {
uint16_t data; uint16_t data;
struct { struct {
@ -365,9 +394,11 @@ struct {
myio my_gp; // 3AC - 2 x 40 bytes (ESP32) myio my_gp; // 3AC - 2 x 40 bytes (ESP32)
mytmplt user_template; // 3FC - 2 x 37 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 #endif // ESP8266 - ESP32
char serial_delimiter; // 451 char serial_delimiter; // 451

View File

@ -1404,6 +1404,10 @@ void SettingsDelta(void)
Settings.module = WEMOS; Settings.module = WEMOS;
ModuleDefault(WEMOS); ModuleDefault(WEMOS);
#endif // ESP32 #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; Settings.version = VERSION;

View File

@ -638,7 +638,7 @@ float ConvertHumidity(float h)
float CalcTempHumToDew(float t, float h) float CalcTempHumToDew(float t, float h)
{ {
if (isnan(h) || isnan(t)) { return 0.0; } if (isnan(h) || isnan(t)) { return NAN; }
if (Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit if (Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
t = (t - 32) / 1.8; // Celsius t = (t - 32) / 1.8; // Celsius
@ -1863,3 +1863,62 @@ void AddLogBufferSize(uint32_t loglevel, uint8_t *buffer, uint32_t count, uint32
} }
AddLog(loglevel); AddLog(loglevel);
} }
/*********************************************************************************************\
* JSON parsing
\*********************************************************************************************/
// does the character needs to be escaped, and if so with which character
char escapeJSONChar(char c) {
if ((c == '\"') || (c == '\\')) {
return c;
}
if (c == '\n') { return 'n'; }
if (c == '\t') { return 't'; }
if (c == '\r') { return 'r'; }
if (c == '\f') { return 'f'; }
if (c == '\b') { return 'b'; }
return 0;
}
String escapeJSONString(const char *str) {
String r("");
if (nullptr == str) { return r; }
bool needs_escape = false;
size_t len_out = 1;
const char * c = str;
while (*c) {
if (escapeJSONChar(*c)) {
len_out++;
needs_escape = true;
}
c++;
len_out++;
}
if (needs_escape) {
// we need to escape some chars
// allocate target buffer
r.reserve(len_out);
c = str;
char *d = r.begin();
while (*c) {
char c2 = escapeJSONChar(*c);
if (c2) {
c++;
*d++ = '\\';
*d++ = c2;
} else {
*d++ = *c++;
}
}
*d = 0; // add NULL terminator
r = (char*) r.begin(); // assign the buffer to the string
} else {
r = str;
}
return r;
}

View File

@ -561,6 +561,11 @@ void CmndStatus(void)
#ifdef USE_SCRIPT_STATUS #ifdef USE_SCRIPT_STATUS
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data);
#endif #endif
if (payload) {
XdrvRulesProcess(); // Allow rule processing on single Status command only
}
mqtt_data[0] = '\0'; mqtt_data[0] = '\0';
} }

View File

@ -477,7 +477,7 @@ void GetFeatures(void)
#ifdef USE_EXS_DIMMER #ifdef USE_EXS_DIMMER
feature5 |= 0x00008000; // xdrv_30_exs_dimmer.ino feature5 |= 0x00008000; // xdrv_30_exs_dimmer.ino
#endif #endif
#ifdef USE_ARDUINO_SLAVE #ifdef USE_TASMOTA_SLAVE
feature5 |= 0x00010000; // xdrv_31_arduino_slave.ino feature5 |= 0x00010000; // xdrv_31_arduino_slave.ino
#endif #endif
#ifdef USE_HIH6 #ifdef USE_HIH6

View File

@ -1227,6 +1227,17 @@ void SerialInput(void)
delay(0); delay(0);
serial_in_byte = Serial.read(); serial_in_byte = Serial.read();
if (0 == serial_in_byte_counter) {
serial_buffer_overrun = false;
}
else if ((serial_in_byte_counter == INPUT_BUFFER_SIZE)
#ifdef ESP8266
// || Serial.hasOverrun() // Default ESP8266 Serial buffer size is 256. Tasmota increases to INPUT_BUFFER_SIZE
#endif
) {
serial_buffer_overrun = true;
}
#ifdef ESP8266 #ifdef ESP8266
/*-------------------------------------------------------------------------------------------*\ /*-------------------------------------------------------------------------------------------*\
* Sonoff dual and ch4 19200 baud serial interface * Sonoff dual and ch4 19200 baud serial interface
@ -1255,7 +1266,7 @@ void SerialInput(void)
if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) { // Add char to string if it still fits if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) { // Add char to string if it still fits
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
} else { } else {
serial_in_byte_counter = 0; serial_buffer_overrun = true; // Signal overrun but continue reading input to flush until '\n' (EOL)
} }
} }
} else { } else {
@ -1292,8 +1303,12 @@ void SerialInput(void)
if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) { // CMND_SERIALSEND and CMND_SERIALLOG if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) { // CMND_SERIALSEND and CMND_SERIALLOG
serial_in_buffer[serial_in_byte_counter] = 0; // Serial data completed serial_in_buffer[serial_in_byte_counter] = 0; // Serial data completed
seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level; seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); if (serial_buffer_overrun) {
ExecuteCommand(serial_in_buffer, SRC_SERIAL); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "Serial buffer overrun"));
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer);
ExecuteCommand(serial_in_buffer, SRC_SERIAL);
}
serial_in_byte_counter = 0; serial_in_byte_counter = 0;
serial_polling_window = 0; serial_polling_window = 0;
Serial.flush(); Serial.flush();

View File

@ -104,9 +104,9 @@ 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 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 = 1000; // 100..1000 Hz led refresh
//const uint16_t PWM_FREQ = 910; // 100..1000 Hz led refresh (iTead value) //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_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 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) // For Controlling Servos use 50 and also set PWM_FREQ as 50 (DO NOT USE THESE VALUES FOR DIMMERS)
@ -124,7 +124,7 @@ const uint16_t SYSLOG_TIMER = 600; // Seconds to restore syslog_level
const uint16_t SERIALLOG_TIMER = 600; // Seconds to disable SerialLog const uint16_t SERIALLOG_TIMER = 600; // Seconds to disable SerialLog
const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching the new firmware
const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in serial command buffer
const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32) const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32)
const uint16_t CMDSZ = 24; // Max number of characters in command const uint16_t CMDSZ = 24; // Max number of characters in command
const uint16_t TOPSZ = 151; // Max number of characters in topic string const uint16_t TOPSZ = 151; // Max number of characters in topic string

View File

@ -156,7 +156,8 @@ uint8_t last_source = 0; // Last command source
uint8_t shutters_present = 0; // Number of actual define shutters uint8_t shutters_present = 0; // Number of actual define shutters
uint8_t prepped_loglevel = 0; // Delayed log level message uint8_t prepped_loglevel = 0; // Delayed log level message
//uint8_t mdns_delayed_start = 0; // mDNS delayed start //uint8_t mdns_delayed_start = 0; // mDNS delayed start
bool serial_local = false; // Handle serial locally; bool serial_local = false; // Handle serial locally
bool serial_buffer_overrun = false; // Serial buffer overrun
bool fallback_topic_flag = false; // Use Topic or FallbackTopic bool fallback_topic_flag = false; // Use Topic or FallbackTopic
bool backlog_mutex = false; // Command backlog pending bool backlog_mutex = false; // Command backlog pending
bool interlock_mutex = false; // Interlock power command pending bool interlock_mutex = false; // Interlock power command pending
@ -215,6 +216,7 @@ void setup(void)
RtcRebootSave(); RtcRebootSave();
Serial.begin(APP_BAUDRATE); Serial.begin(APP_BAUDRATE);
Serial.setRxBufferSize(INPUT_BUFFER_SIZE); // Default is 256 chars
seriallog_level = LOG_LEVEL_INFO; // Allow specific serial messages until config loaded seriallog_level = LOG_LEVEL_INFO; // Allow specific serial messages until config loaded
snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); // Release version 6.3.0 snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); // Release version 6.3.0

View File

@ -143,12 +143,15 @@
#define MP3_VOLUME 10 // Set the startup volume on init, the range can be 0..30(max) #define MP3_VOLUME 10 // Set the startup volume on init, the range can be 0..30(max)
//#define USE_AZ7798 // Add support for AZ-Instrument 7798 CO2 datalogger //#define USE_AZ7798 // Add support for AZ-Instrument 7798 CO2 datalogger
#define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP
#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) #define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8)
#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) //#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#define USE_HM10 // (ESP8266 only) Add support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #define USE_HM10 // (ESP8266 only) Add support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code)
//#define USE_MI_ESP32 // (ESP32 only) Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) //#define USE_MI_ESP32 // (ESP32 only) Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
#define USE_HRXL // Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #define USE_HRXL // Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7)
//#define USE_TASMOTA_SLAVE // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem)
//#define USE_OPENTHERM // Add support for OpenTherm (+15k code)
#define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
@ -168,22 +171,20 @@
#define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram) #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram)
#define USE_IR_RECEIVE // Support for IR receiver (+5k5 code, 264 iram) #define USE_IR_RECEIVE // Support for IR receiver (+5k5 code, 264 iram)
//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP
#define USE_SR04 // Add support for HC-SR04 ultrasonic devices (+1k code) #define USE_SR04 // Add support for HC-SR04 ultrasonic devices (+1k code)
#define USE_TM1638 // Add support for TM1638 switches copying Switch1 .. Switch8 (+1k code) #define USE_TM1638 // Add support for TM1638 switches copying Switch1 .. Switch8 (+1k code)
#define USE_HX711 // Add support for HX711 load cell (+1k5 code) #define USE_HX711 // Add support for HX711 load cell (+1k5 code)
//#define USE_HX711_GUI // Add optional web GUI to HX711 as scale (+1k8 code) //#define USE_HX711_GUI // Add optional web GUI to HX711 as scale (+1k8 code)
//#define USE_TX20_WIND_SENSOR // Add support for La Crosse TX20 anemometer (+2k6/0k8 code) //#define USE_TX20_WIND_SENSOR // Add support for La Crosse TX20 anemometer (+2k6/0k8 code)
//#define USE_TX23_WIND_SENSOR // Add support for La Crosse TX23 anemometer (+2k7/1k code) //#define USE_TX23_WIND_SENSOR // Add support for La Crosse TX23 anemometer (+2k7/1k code)
//#define USE_WINDMETER // Add support for analog anemometer (+2k2 code)
#define USE_RC_SWITCH // Add support for RF transceiver using library RcSwitch (+2k7 code, 460 iram) #define USE_RC_SWITCH // Add support for RF transceiver using library RcSwitch (+2k7 code, 460 iram)
#define USE_RF_SENSOR // Add support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #define USE_RF_SENSOR // Add support for RF sensor receiver (434MHz or 868MHz) (+0k8 code)
// #define USE_THEO_V2 // Add support for decoding Theo V2 sensors as documented on https://sidweb.nl using 434MHz RF sensor receiver (+1k4 code) // #define USE_THEO_V2 // Add support for decoding Theo V2 sensors as documented on https://sidweb.nl using 434MHz RF sensor receiver (+1k4 code)
#define USE_ALECTO_V2 // Add support for decoding Alecto V2 sensors like ACH2010, WS3000 and DKW2012 using 868MHz RF sensor receiver (+1k7 code) #define USE_ALECTO_V2 // Add support for decoding Alecto V2 sensors like ACH2010, WS3000 and DKW2012 using 868MHz RF sensor receiver (+1k7 code)
#define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code) #define USE_HRE // Add support for Badger HR-E Water Meter (+1k4 code)
//#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code) //#define USE_A4988_STEPPER // Add support for A4988/DRV8825 stepper-motor-driver-circuit (+10k5 code)
//#define USE_ARDUINO_SLAVE // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) //#define USE_THERMOSTAT // Add support for Thermostat
//#define USE_WINDMETER // Add support for analog anemometer
#undef DEBUG_THEO // Disable debug code #undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_SENSORS #endif // FIRMWARE_SENSORS
@ -366,12 +367,15 @@
#undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop
#undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code)
#undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
#undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7)
#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem)
#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code)
//#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor //#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor
#undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI #undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI
@ -381,11 +385,12 @@
#undef USE_HX711 // Disable support for HX711 load cell #undef USE_HX711 // Disable support for HX711 load cell
#undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer
#undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer
#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code)
#undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch
#undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code)
#undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code)
#undef USE_A4988_STEPPER // Disable support for A4988_Stepper #undef USE_A4988_STEPPER // Disable support for A4988_Stepper
#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_THERMOSTAT // Disable support for Thermostat
#undef DEBUG_THEO // Disable debug code #undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_IR #endif // FIRMWARE_IR
@ -467,12 +472,15 @@
#undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop
#undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code)
#undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
#undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7)
#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem)
#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code)
//#undef USE_ENERGY_SENSOR // Disable energy sensors //#undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #undef USE_PZEM004T // Disable PZEM004T energy sensor
@ -491,21 +499,20 @@
#undef USE_MAX31865 // Disable support for MAX31865 RTD sensors using softSPI #undef USE_MAX31865 // Disable support for MAX31865 RTD sensors using softSPI
#undef USE_IR_REMOTE // Disable IR driver #undef USE_IR_REMOTE // Disable IR driver
#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP
#undef USE_SR04 // Disable support for for HC-SR04 ultrasonic devices #undef USE_SR04 // Disable support for for HC-SR04 ultrasonic devices
#undef USE_TM1638 // Disable support for TM1638 switches copying Switch1 .. Switch8 #undef USE_TM1638 // Disable support for TM1638 switches copying Switch1 .. Switch8
#undef USE_HX711 // Disable support for HX711 load cell #undef USE_HX711 // Disable support for HX711 load cell
#undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer
#undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer
#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code)
#undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch
#undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code)
#undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code)
#undef USE_A4988_STEPPER // Disable support for A4988_Stepper #undef USE_A4988_STEPPER // Disable support for A4988_Stepper
#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_THERMOSTAT // Disable support for Thermostat
#undef DEBUG_THEO // Disable debug code #undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_BASIC #endif // FIRMWARE_LITE
/*********************************************************************************************\ /*********************************************************************************************\
* [tasmota-minimal.bin] * [tasmota-minimal.bin]
@ -590,12 +597,15 @@
#undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop #undef USE_MP3_PLAYER // Disable DFPlayer Mini MP3 Player RB-DFR-562 commands: play, volume and stop
#undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger #undef USE_AZ7798 // Disable support for AZ-Instrument 7798 CO2 datalogger
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_ZIGBEE // Disable serial communication with Zigbee CC2530 flashed with ZNP
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code) #undef USE_HM10 // (ESP8266 only) Disable support for HM-10 as a BLE-bridge for the LYWSD03 (+5k1 code)
#undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
#undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7)
#undef USE_TASMOTA_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem)
#undef USE_OPENTHERM // Disable support for OpenTherm (+15k code)
#undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #undef USE_PZEM004T // Disable PZEM004T energy sensor
@ -618,11 +628,12 @@
#undef USE_HX711 // Disable support for HX711 load cell #undef USE_HX711 // Disable support for HX711 load cell
#undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer #undef USE_TX20_WIND_SENSOR // Disable support for La Crosse TX20 anemometer
#undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer #undef USE_TX23_WIND_SENSOR // Disable support for La Crosse TX23 anemometer
#undef USE_WINDMETER // Disable support for analog anemometer (+2k2 code)
#undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch #undef USE_RC_SWITCH // Disable support for RF transceiver using library RcSwitch
#undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code) #undef USE_RF_SENSOR // Disable support for RF sensor receiver (434MHz or 868MHz) (+0k8 code)
#undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code) #undef USE_HRE // Disable support for Badger HR-E Water Meter (+1k4 code)
#undef USE_A4988_STEPPER // Disable support for A4988_Stepper #undef USE_A4988_STEPPER // Disable support for A4988_Stepper
#undef USE_ARDUINO_SLAVE // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_THERMOSTAT // Disable support for Thermostat
#undef DEBUG_THEO // Disable debug code #undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code #undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_MINIMAL #endif // FIRMWARE_MINIMAL

View File

@ -96,6 +96,7 @@ struct ENERGY {
uint8_t phase_count = 1; // Number of phases active uint8_t phase_count = 1; // Number of phases active
bool voltage_common = false; // Use single voltage and frequency bool voltage_common = false; // Use single voltage and frequency
bool kWhtoday_offset_init = false;
bool voltage_available = true; // Enable if voltage is measured bool voltage_available = true; // Enable if voltage is measured
bool current_available = true; // Enable if current is measured bool current_available = true; // Enable if current is measured
@ -223,6 +224,12 @@ void Energy200ms(void)
XnrgCall(FUNC_ENERGY_EVERY_SECOND); XnrgCall(FUNC_ENERGY_EVERY_SECOND);
if (RtcTime.valid) { if (RtcTime.valid) {
if (!Energy.kWhtoday_offset_init && (RtcTime.day_of_year == Settings.energy_kWhdoy)) {
Energy.kWhtoday_offset = Settings.energy_kWhtoday;
Energy.kWhtoday_offset_init = true;
}
if (LocalTime() == Midnight()) { if (LocalTime() == Midnight()) {
Settings.energy_kWhyesterday = RtcSettings.energy_kWhtoday; Settings.energy_kWhyesterday = RtcSettings.energy_kWhtoday;
@ -827,14 +834,11 @@ void EnergySnsInit(void)
XnrgCall(FUNC_INIT); XnrgCall(FUNC_INIT);
if (energy_flg) { if (energy_flg) {
if (RtcSettingsValid()) { Energy.kWhtoday_offset = 0;
// Do not use at Power On as Rtc was invalid (but has been restored from Settings already)
if ((ResetReason() != REASON_DEFAULT_RST) && RtcSettingsValid()) {
Energy.kWhtoday_offset = RtcSettings.energy_kWhtoday; Energy.kWhtoday_offset = RtcSettings.energy_kWhtoday;
} Energy.kWhtoday_offset_init = true;
else if (RtcTime.day_of_year == Settings.energy_kWhdoy) {
Energy.kWhtoday_offset = Settings.energy_kWhtoday;
}
else {
Energy.kWhtoday_offset = 0;
} }
Energy.kWhtoday = 0; Energy.kWhtoday = 0;
Energy.kWhtoday_delta = 0; Energy.kWhtoday_delta = 0;

View File

@ -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 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 switch1#state do power2 %value% endon
* on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon * on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon
* on loadavg<50 do power 2 endon
* *
* Notes: * Notes:
* Spaces after <on>, around <do> and before <endon> are mandatory * Spaces after <on>, around <do> and before <endon> are mandatory
@ -66,6 +67,8 @@
#define XDRV_10 10 #define XDRV_10 10
#include <unishox.h>
#define D_CMND_RULE "Rule" #define D_CMND_RULE "Rule"
#define D_CMND_RULETIMER "RuleTimer" #define D_CMND_RULETIMER "RuleTimer"
#define D_CMND_EVENT "Event" #define D_CMND_EVENT "Event"
@ -178,6 +181,230 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#error MAX_RULE_MEMS is bigger than 16 #error MAX_RULE_MEMS is bigger than 16
#endif #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<x> 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 is 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) {
#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][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
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][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() ) {
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]));
if (0 == Settings.rules[idx][0]) {
Settings.rules[idx][1] = 0;
}
#ifdef USE_RULES_COMPRESSION
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));
}
#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) bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
@ -190,19 +417,20 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
char stemp[10]; char stemp[10];
// Step1: Analyse rule // Step1: Analyse rule
int pos = rule.indexOf('#'); String rule_expr = rule; // "TELE-INA219#CURRENT>0.100"
if (pos == -1) { return false; } // No # sign in rule
String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM"
if (Rules.teleperiod) { 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 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; 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 }; char rule_svalue[80] = { 0 };
float rule_value = 0; float rule_value = 0;
@ -250,11 +478,12 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
if (temp_value > -1) { if (temp_value > -1) {
rule_value = temp_value; rule_value = temp_value;
} else { } 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; int rule_name_idx = 0;
if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]" if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]"
rule_name_idx = rule_name.substring(pos +1).toInt(); rule_name_idx = rule_name.substring(pos +1).toInt();
@ -267,10 +496,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
StaticJsonBuffer<1024> jsonBuf; StaticJsonBuffer<1024> jsonBuf;
JsonObject &root = jsonBuf.parseObject(event); JsonObject &root = jsonBuf.parseObject(event);
if (!root.success()) { return false; } // No valid JSON data if (!root.success()) { return false; } // No valid JSON data
if (!root[rule_task].success()) { return false; } // No rule_task in JSON data JsonObject *obj = &root;
JsonObject &obj1 = root[rule_task];
JsonObject *obj = &obj1;
String subtype; String subtype;
uint32_t i = 0; uint32_t i = 0;
while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT"
@ -289,8 +515,8 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
str_value = (*obj)[rule_name]; // "CURRENT" 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"), //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: 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"); // 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% Rules.event_value = str_value; // Prepare %value%
@ -419,7 +645,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]); //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; Rules.trigger_count[rule_set] = 0;
int plen = 0; int plen = 0;
@ -531,7 +757,7 @@ bool RulesProcessEvent(char *json_event)
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str()); //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str());
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { 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; } if (RuleSetProcess(i, event_saved)) { serviced = true; }
} }
} }
@ -547,7 +773,7 @@ void RulesInit(void)
{ {
rules_flag.data = 0; rules_flag.data = 0;
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { 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_enabled, i, 0);
bitWrite(Settings.rule_once, i, 0); bitWrite(Settings.rule_once, i, 0);
} }
@ -1727,7 +1953,8 @@ void CmndRule(void)
{ {
uint8_t index = XdrvMailbox.index; uint8_t index = XdrvMailbox.index;
if ((index > 0) && (index <= MAX_RULE_SETS)) { 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)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) {
switch (XdrvMailbox.payload) { switch (XdrvMailbox.payload) {
case 0: // Off case 0: // Off
@ -1753,24 +1980,33 @@ void CmndRule(void)
break; break;
} }
} else { } else {
int offset = 0; bool append = false;
if ('+' == XdrvMailbox.data[0]) { if ('+' == XdrvMailbox.data[0]) {
offset = strlen(Settings.rules[index -1]); XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted
if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) { // Check free space append = true;
XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted
} else {
offset = -1; // Not enough space so skip it
}
} }
if (offset != -1) { int32_t res = SetRule(index - 1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, append);
strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1])); if (res < 0) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR("RUL: Not enough space"));
} }
} }
Rules.triggers[index -1] = 0; // Reset once flag 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\"}"), String rule = GetRule(index - 1);
size_t rule_len = rule.length();
if (rule_len >= MAX_RULE_SIZE) {
// we need to split the rule in chunks
rule = rule.substring(0, MAX_RULE_SIZE);
rule += F("...");
}
// 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\",\"Length\":%d,\"Free\":%d,\"Rules\":\"%s\"}"),
XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)), 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)),
rule_len, MAX_RULE_SIZE - GetRuleLenStorage(index - 1),
escapeJSONString(rule.c_str()).c_str());
} }
} }

View File

@ -1879,6 +1879,19 @@ chknext:
len=0; len=0;
goto strexit; goto strexit;
} }
#ifdef ESP32
if (!strncmp(vname,"sf(",3)) {
lp+=2;
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
if (fvar<80) fvar=80;
if (fvar>240) fvar=240;
setCpuFrequencyMhz(fvar);
fvar=getCpuFrequencyMhz();
lp++;
len=0;
goto exit;
}
#endif
#if defined(USE_TIMERS) && defined(USE_SUNRISE) #if defined(USE_TIMERS) && defined(USE_SUNRISE)
if (!strncmp(vname,"sunrise",7)) { if (!strncmp(vname,"sunrise",7)) {
fvar=SunMinutes(0); fvar=SunMinutes(0);
@ -2093,7 +2106,7 @@ chknext:
len=0; len=0;
goto exit; goto exit;
} }
#endif #endif //ESP32, USE_WEBCAM
if (!strncmp(vname,"wday",4)) { if (!strncmp(vname,"wday",4)) {
fvar=RtcTime.day_of_week; fvar=RtcTime.day_of_week;
goto exit; goto exit;
@ -2692,11 +2705,13 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
return -99; return -99;
} }
DynamicJsonBuffer jsonBuffer; // on heap JsonObject *jo=0;
JsonObject &jobj=jsonBuffer.parseObject(js);
JsonObject *jo; if (js) {
if (js) jo=&jobj; DynamicJsonBuffer jsonBuffer; // on heap
else jo=0; JsonObject &jobj=jsonBuffer.parseObject(js);
jo=&jobj;
}
char *lp=glob_script_mem.scriptptr; char *lp=glob_script_mem.scriptptr;
@ -5119,6 +5134,8 @@ bool RulesProcessEvent(char *json_event) {
#ifdef USE_SCRIPT_TASK #ifdef USE_SCRIPT_TASK
uint16_t task_timer1; uint16_t task_timer1;
uint16_t task_timer2; uint16_t task_timer2;
TaskHandle_t task_t1;
TaskHandle_t task_t2;
void script_task1(void *arg) { void script_task1(void *arg) {
while (1) { while (1) {
@ -5143,14 +5160,16 @@ void script_task2(void *arg) {
uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) { uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) {
//return 0; //return 0;
BaseType_t res=0; BaseType_t res = 0;
if (core>1) {core = 1;} if (core > 1) { core = 1; }
if (num == 1) { if (num == 1) {
res = xTaskCreatePinnedToCore(script_task1, "T 1", STASK_STACK, NULL, STASK_PRIO, NULL, core); if (task_t1) { vTaskDelete(task_t1); }
task_timer1=time; res = xTaskCreatePinnedToCore(script_task1, "T1", STASK_STACK, NULL, STASK_PRIO, &task_t1, core);
task_timer1 = time;
} else { } else {
res = xTaskCreatePinnedToCore(script_task2, "T 2", STASK_STACK, NULL, STASK_PRIO, NULL, core); if (task_t2) { vTaskDelete(task_t2); }
task_timer2=time; res = xTaskCreatePinnedToCore(script_task2, "T2", STASK_STACK, NULL, STASK_PRIO, &task_t2, core);
task_timer2 = time;
} }
return res; return res;
} }

View File

@ -144,9 +144,9 @@ const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM = const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
",\"type\":\"Extended color light\"," ",\"type\":\"Extended color light\","
"\"name\":\"%s\"," "\"name\":\"%s\","
"\"modelid\":\"LCT007\"," "\"modelid\":\"%s\","
"\"uniqueid\":\"%s\"," "\"manufacturername\":\"%s\","
"\"swversion\":\"5.50.1.19085\"}"; "\"uniqueid\":\"%s\"}";
const char HUE_GROUP0_STATUS_JSON[] PROGMEM = const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
"{\"name\":\"Group 0\"," "{\"name\":\"Group 0\","
"\"lights\":[{l1]," "\"lights\":[{l1],"
@ -358,7 +358,7 @@ bool HueActive(uint8_t device) {
void HueLightStatus2(uint8_t device, String *response) void HueLightStatus2(uint8_t device, String *response)
{ {
const size_t buf_size = 192; const size_t buf_size = 300;
char * buf = (char*) malloc(buf_size); char * buf = (char*) malloc(buf_size);
const size_t max_name_len = 32; const size_t max_name_len = 32;
char fname[max_name_len + 1]; char fname[max_name_len + 1];
@ -376,7 +376,11 @@ void HueLightStatus2(uint8_t device, String *response)
} }
fname[fname_len] = 0x00; fname[fname_len] = 0x00;
} }
snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2, fname, GetHueDeviceId(device).c_str()); snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2,
escapeJSONString(fname).c_str(),
escapeJSONString(Settings.user_template_name).c_str(),
PSTR("Tasmota"),
GetHueDeviceId(device).c_str());
*response += buf; *response += buf;
free(buf); free(buf);
} }

View File

@ -127,6 +127,7 @@ public:
void setFriendlyName(uint16_t shortaddr, const char * str); void setFriendlyName(uint16_t shortaddr, const char * str);
const char * getFriendlyName(uint16_t shortaddr) const; const char * getFriendlyName(uint16_t shortaddr) const;
const char * getModelId(uint16_t shortaddr) const; const char * getModelId(uint16_t shortaddr) const;
const char * getManufacturerId(uint16_t shortaddr) const;
void setReachable(uint16_t shortaddr, bool reachable); void setReachable(uint16_t shortaddr, bool reachable);
// get next sequence number for (increment at each all) // get next sequence number for (increment at each all)
@ -589,6 +590,15 @@ const char * Z_Devices::getModelId(uint16_t shortaddr) const {
return nullptr; return nullptr;
} }
const char * Z_Devices::getManufacturerId(uint16_t shortaddr) const {
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
return device.manufacturerId;
}
return nullptr;
}
void Z_Devices::setReachable(uint16_t shortaddr, bool reachable) { void Z_Devices::setReachable(uint16_t shortaddr, bool reachable) {
Z_Device & device = getShortAddr(shortaddr); Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found if (&device == nullptr) { return; } // don't crash if not found

View File

@ -75,16 +75,21 @@ void HueLightStatus1Zigbee(uint16_t shortaddr, uint8_t local_light_subtype, Stri
void HueLightStatus2Zigbee(uint16_t shortaddr, String *response) void HueLightStatus2Zigbee(uint16_t shortaddr, String *response)
{ {
const size_t buf_size = 192; const size_t buf_size = 300;
char * buf = (char*) malloc(buf_size); char * buf = (char*) malloc(buf_size);
const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr); const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
const char * modelId = zigbee_devices.getModelId(shortaddr);
const char * manufacturerId = zigbee_devices.getManufacturerId(shortaddr);
char shortaddrname[8]; char shortaddrname[8];
snprintf_P(shortaddrname, sizeof(shortaddrname), PSTR("0x%04X"), shortaddr); snprintf_P(shortaddrname, sizeof(shortaddrname), PSTR("0x%04X"), shortaddr);
snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2, snprintf_P(buf, buf_size, HUE_LIGHTS_STATUS_JSON2,
(friendlyName) ? friendlyName : shortaddrname, (friendlyName) ? escapeJSONString(friendlyName).c_str() : shortaddrname,
(modelId) ? escapeJSONString(modelId).c_str() : PSTR("Unknown"),
(manufacturerId) ? escapeJSONString(manufacturerId).c_str() : PSTR("Tasmota"),
GetHueDeviceId(shortaddr).c_str()); GetHueDeviceId(shortaddr).c_str());
*response += buf; *response += buf;
free(buf); free(buf);
} }

View File

@ -703,7 +703,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ Zuint16, Cx0001, 0x0000, Z(MainsVoltage), &Z_Copy }, { Zuint16, Cx0001, 0x0000, Z(MainsVoltage), &Z_Copy },
{ Zuint8, Cx0001, 0x0001, Z(MainsFrequency), &Z_Copy }, { Zuint8, Cx0001, 0x0001, Z(MainsFrequency), &Z_Copy },
{ Zuint8, Cx0001, 0x0020, Z(BatteryVoltage), &Z_FloatDiv10 }, { 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 // Device Temperature Configuration cluster
{ Zint16, Cx0002, 0x0000, Z(CurrentTemperature), &Z_Copy }, { Zint16, Cx0002, 0x0000, Z(CurrentTemperature), &Z_Copy },

View File

@ -134,18 +134,18 @@ const char kThermostatCommands[] PROGMEM = "|" D_CMND_THERMOSTATMODESET "|" D_CM
D_CMND_TEMPFROSTPROTECTSET "|" D_CMND_CONTROLLERMODESET "|" D_CMND_INPUTSWITCHSET "|" D_CMND_INPUTSWITCHUSE "|" D_CMND_TEMPFROSTPROTECTSET "|" D_CMND_CONTROLLERMODESET "|" D_CMND_INPUTSWITCHSET "|" D_CMND_INPUTSWITCHUSE "|"
D_CMND_OUTPUTRELAYSET "|" D_CMND_TIMEALLOWRAMPUPSET "|" D_CMND_TEMPFORMATSET "|" D_CMND_TEMPMEASUREDSET "|" D_CMND_OUTPUTRELAYSET "|" D_CMND_TIMEALLOWRAMPUPSET "|" D_CMND_TEMPFORMATSET "|" D_CMND_TEMPMEASUREDSET "|"
D_CMND_TEMPTARGETSET "|" D_CMND_TEMPMEASUREDGRDREAD "|" D_CMND_SENSORINPUTSET "|" D_CMND_STATEEMERGENCYSET "|" D_CMND_TEMPTARGETSET "|" D_CMND_TEMPMEASUREDGRDREAD "|" D_CMND_SENSORINPUTSET "|" D_CMND_STATEEMERGENCYSET "|"
D_CMND_TIMEMANUALTOAUTOSET "|" D_CMND_TIMEONLIMITSET "|" D_CMND_PROPBANDSET "|" D_CMND_TIMERESETSET "|" D_CMND_TIMEMANUALTOAUTOSET "|" D_CMND_PROPBANDSET "|" D_CMND_TIMERESETSET "|" D_CMND_TIMEPICYCLESET "|"
D_CMND_TIMEPICYCLESET "|" D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" D_CMND_TIMEMINACTIONSET "|"
D_CMND_TIMEMINACTIONSET "|" D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" D_CMND_TIMERAMPUPMAXSET "|"
D_CMND_TIMERAMPUPMAXSET "|" D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" D_CMND_TIMEPIINTEGRREAD "|"
D_CMND_TIMEPIINTEGRREAD "|" D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET; D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET;
void (* const ThermostatCommand[])(void) PROGMEM = { void (* const ThermostatCommand[])(void) PROGMEM = {
&CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet, &CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet,
&CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet, &CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet,
&CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet, &CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet,
&CmndTimeOnLimitSet, &CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet,
&CmndTempHystSet, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet,
&CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet, &CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet,
&CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet }; &CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet };
@ -183,16 +183,15 @@ struct THERMOSTAT {
int16_t temp_rampup_start = 0; // Temperature at start of ramp-up controller in tenths of degrees celsius int16_t temp_rampup_start = 0; // Temperature at start of ramp-up controller in tenths of degrees celsius
int16_t temp_rampup_cycle = 0; // Temperature set at the beginning of each ramp-up cycle in tenths of degrees int16_t temp_rampup_cycle = 0; // Temperature set at the beginning of each ramp-up cycle in tenths of degrees
uint16_t time_rampup_max = THERMOSTAT_TIME_RAMPUP_MAX; // Time maximum ramp-up controller duration in minutes uint16_t time_rampup_max = THERMOSTAT_TIME_RAMPUP_MAX; // Time maximum ramp-up controller duration in minutes
uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE; // Time ramp-up cycle in seconds uint16_t time_rampup_cycle = THERMOSTAT_TIME_RAMPUP_CYCLE; // Time ramp-up cycle in minutes
uint16_t time_allow_rampup = THERMOSTAT_TIME_ALLOW_RAMPUP; // Time in minutes after last target update to allow ramp-up controller phase uint16_t time_allow_rampup = THERMOSTAT_TIME_ALLOW_RAMPUP; // Time in minutes after last target update to allow ramp-up controller phase
uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST; // Maximum time w/o sensor update to set it as lost uint16_t time_sens_lost = THERMOSTAT_TIME_SENS_LOST; // Maximum time w/o sensor update to set it as lost in minutes
uint16_t time_manual_to_auto = THERMOSTAT_TIME_MANUAL_TO_AUTO; // Time without input switch active to change from manual to automatic in minutes uint16_t time_manual_to_auto = THERMOSTAT_TIME_MANUAL_TO_AUTO; // Time without input switch active to change from manual to automatic in minutes
uint16_t time_on_limit = THERMOSTAT_TIME_ON_LIMIT; // Maximum time with output active in minutes
uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE; // Cycle time for the thermostat controller in seconds
uint32_t time_reset = THERMOSTAT_TIME_RESET; // Reset time of the PI controller in seconds uint32_t time_reset = THERMOSTAT_TIME_RESET; // Reset time of the PI controller in seconds
uint16_t time_pi_cycle = THERMOSTAT_TIME_PI_CYCLE; // Cycle time for the thermostat controller in minutes
uint16_t time_max_action = THERMOSTAT_TIME_MAX_ACTION; // Maximum thermostat time per cycle in minutes uint16_t time_max_action = THERMOSTAT_TIME_MAX_ACTION; // Maximum thermostat time per cycle in minutes
uint16_t time_min_action = THERMOSTAT_TIME_MIN_ACTION; // Minimum thermostat time per cycle in minutes uint16_t time_min_action = THERMOSTAT_TIME_MIN_ACTION; // Minimum thermostat time per cycle in minutes
uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION; // Minimum turnoff time in minutes, below it the thermostat will be held on uint16_t time_min_turnoff_action = THERMOSTAT_TIME_MIN_TURNOFF_ACTION; // Minimum turnoff time in minutes, below it the thermostat will stay on
uint8_t temp_reset_anti_windup = THERMOSTAT_TEMP_RESET_ANTI_WINDUP; // Range where reset antiwindup is disabled, in tenths of degrees celsius uint8_t temp_reset_anti_windup = THERMOSTAT_TEMP_RESET_ANTI_WINDUP; // Range where reset antiwindup is disabled, in tenths of degrees celsius
int8_t temp_hysteresis = THERMOSTAT_TEMP_HYSTERESIS; // Range hysteresis for temperature PI controller, in tenths of degrees celsius int8_t temp_hysteresis = THERMOSTAT_TEMP_HYSTERESIS; // Range hysteresis for temperature PI controller, in tenths of degrees celsius
uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius
@ -605,7 +604,7 @@ void ThermostatCalculatePI(uint8_t ctr_output)
// Antiwindup of the integrator // Antiwindup of the integrator
// If integral calculation is bigger than cycle time, adjust result // If integral calculation is bigger than cycle time, adjust result
// to the cycle time and error will not be cummulated]] // to the cycle time and error will not be cummulated
if (Thermostat[ctr_output].time_integral_pi > ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60)) { if (Thermostat[ctr_output].time_integral_pi > ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60)) {
Thermostat[ctr_output].time_integral_pi = ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60); Thermostat[ctr_output].time_integral_pi = ((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60);
} }
@ -616,7 +615,7 @@ void ThermostatCalculatePI(uint8_t ctr_output)
// Antiwindup of the output // Antiwindup of the output
// If result is bigger than cycle time, the result will be adjusted // If result is bigger than cycle time, the result will be adjusted
// to the cylce time minus safety time and error will not be cummulated]] // to the cylce time minus safety time and error will not be cummulated
if (Thermostat[ctr_output].time_total_pi >= ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60)) { if (Thermostat[ctr_output].time_total_pi >= ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60)) {
// Limit to cycle time //at least switch down a minimum time // Limit to cycle time //at least switch down a minimum time
Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60); Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_pi_cycle * 60);
@ -651,13 +650,13 @@ void ThermostatCalculatePI(uint8_t ctr_output)
} }
// Minimum action limiter // Minimum action limiter
// If result is less than the minimum action time, adjust to minimum value]] // If result is less than the minimum action time, adjust to minimum value
if ((Thermostat[ctr_output].time_total_pi <= abs(((uint32_t)Thermostat[ctr_output].time_min_action * 60))) if ((Thermostat[ctr_output].time_total_pi <= abs(((uint32_t)Thermostat[ctr_output].time_min_action * 60)))
&& (Thermostat[ctr_output].time_total_pi != 0)) { && (Thermostat[ctr_output].time_total_pi != 0)) {
Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_min_action * 60); Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_min_action * 60);
} }
// Maximum action limiter // Maximum action limiter
// If result is more than the maximum action time, adjust to maximum value]] // If result is more than the maximum action time, adjust to maximum value
else if (Thermostat[ctr_output].time_total_pi > abs(((int32_t)Thermostat[ctr_output].time_max_action * 60))) { else if (Thermostat[ctr_output].time_total_pi > abs(((int32_t)Thermostat[ctr_output].time_max_action * 60))) {
Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_max_action * 60); Thermostat[ctr_output].time_total_pi = ((int32_t)Thermostat[ctr_output].time_max_action * 60);
} }
@ -747,7 +746,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
} }
// Calculate absolute gradient since start of ramp-up (considering deadtime) in thousandths of º/hour // Calculate absolute gradient since start of ramp-up (considering deadtime) in thousandths of º/hour
Thermostat[ctr_output].temp_rampup_meas_gradient = (int32_t)((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_in_rampup); Thermostat[ctr_output].temp_rampup_meas_gradient = (int32_t)((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_in_rampup);
Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60);
// Set auxiliary variables // Set auxiliary variables
Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured; Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured;
Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max); Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max);
@ -758,7 +757,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
// Calculate temp. gradient in º/hour and set again time_rampup_nextcycle and temp_rampup_cycle // Calculate temp. gradient in º/hour and set again time_rampup_nextcycle and temp_rampup_cycle
// temp_rampup_meas_gradient = ((3600 * temp_delta_rampup) / (os.time() - time_rampup_nextcycle)) // temp_rampup_meas_gradient = ((3600 * temp_delta_rampup) / (os.time() - time_rampup_nextcycle))
temp_delta_rampup = Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_rampup_cycle; temp_delta_rampup = Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_rampup_cycle;
uint32_t time_total_rampup = (uint32_t)Thermostat[ctr_output].time_rampup_cycle * Thermostat[ctr_output].counter_rampup_cycles; uint32_t time_total_rampup = (uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60 * Thermostat[ctr_output].counter_rampup_cycles;
// Translate into gradient per hour (thousandths of ° per hour) // Translate into gradient per hour (thousandths of ° per hour)
Thermostat[ctr_output].temp_rampup_meas_gradient = int32_t((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_total_rampup); Thermostat[ctr_output].temp_rampup_meas_gradient = int32_t((360000 * (int32_t)temp_delta_rampup) / (int32_t)time_total_rampup);
if ( ((Thermostat[ctr_output].temp_rampup_meas_gradient > 0) if ( ((Thermostat[ctr_output].temp_rampup_meas_gradient > 0)
@ -776,7 +775,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
// y = (((y2-y1)/(x2-x1))*(x-x1)) + y1 // y = (((y2-y1)/(x2-x1))*(x-x1)) + y1
Thermostat[ctr_output].temp_rampup_output_off = (int16_t)(((int32_t)temp_delta_rampup * (int32_t)(Thermostat[ctr_output].time_ctr_changepoint - (uptime - (time_total_rampup)))) / (int32_t)(time_total_rampup * Thermostat[ctr_output].counter_rampup_cycles)) + Thermostat[ctr_output].temp_rampup_cycle; Thermostat[ctr_output].temp_rampup_output_off = (int16_t)(((int32_t)temp_delta_rampup * (int32_t)(Thermostat[ctr_output].time_ctr_changepoint - (uptime - (time_total_rampup)))) / (int32_t)(time_total_rampup * Thermostat[ctr_output].counter_rampup_cycles)) + Thermostat[ctr_output].temp_rampup_cycle;
// Set auxiliary variables // Set auxiliary variables
Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60);
Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured; Thermostat[ctr_output].temp_rampup_cycle = Thermostat[ctr_output].temp_measured;
// Reset period counter // Reset period counter
Thermostat[ctr_output].counter_rampup_cycles = 1; Thermostat[ctr_output].counter_rampup_cycles = 1;
@ -785,7 +784,7 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
// Increase the period counter // Increase the period counter
Thermostat[ctr_output].counter_rampup_cycles++; Thermostat[ctr_output].counter_rampup_cycles++;
// Set another period // Set another period
Thermostat[ctr_output].time_rampup_nextcycle = uptime + (uint32_t)Thermostat[ctr_output].time_rampup_cycle; Thermostat[ctr_output].time_rampup_nextcycle = uptime + ((uint32_t)Thermostat[ctr_output].time_rampup_cycle * 60);
// Reset time_ctr_changepoint and temp_rampup_output_off // Reset time_ctr_changepoint and temp_rampup_output_off
Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max) - time_in_rampup; Thermostat[ctr_output].time_ctr_changepoint = uptime + (60 * (uint32_t)Thermostat[ctr_output].time_rampup_max) - time_in_rampup;
Thermostat[ctr_output].temp_rampup_output_off = Thermostat[ctr_output].temp_target_level_ctr; Thermostat[ctr_output].temp_rampup_output_off = Thermostat[ctr_output].temp_target_level_ctr;
@ -1256,7 +1255,7 @@ void CmndTempMeasuredGrdRead(void)
else { else {
value = Thermostat[ctr_output].temp_measured_gradient; value = Thermostat[ctr_output].temp_measured_gradient;
} }
ResponseCmndFloat((float)value / 10, 1); ResponseCmndFloat(((float)value) / 1000, 1);
} }
} }
@ -1280,25 +1279,11 @@ void CmndTimeManualToAutoSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_manual_to_auto = (uint16_t)(value / 60); Thermostat[ctr_output].time_manual_to_auto = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_manual_to_auto * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_manual_to_auto));
}
}
void CmndTimeOnLimitSet(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) {
Thermostat[ctr_output].time_on_limit = (uint16_t)(value / 60);
}
}
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_on_limit * 60));
} }
} }
@ -1330,17 +1315,33 @@ void CmndTimeResetSet(void)
} }
} }
void CmndTimePiProportRead(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
ResponseCmndNumber((int)Thermostat[ctr_output].time_proportional_pi);
}
}
void CmndTimePiIntegrRead(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
ResponseCmndNumber((int)Thermostat[ctr_output].time_integral_pi);
}
}
void CmndTimePiCycleSet(void) void CmndTimePiCycleSet(void)
{ {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_pi_cycle = (uint16_t)(value / 60); Thermostat[ctr_output].time_pi_cycle = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_pi_cycle * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_pi_cycle));
} }
} }
@ -1404,11 +1405,11 @@ void CmndTimeMaxActionSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_max_action = (uint16_t)(value / 60); Thermostat[ctr_output].time_max_action = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_max_action * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_max_action));
} }
} }
@ -1418,11 +1419,11 @@ void CmndTimeMinActionSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_min_action = (uint16_t)(value / 60); Thermostat[ctr_output].time_min_action = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_action * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_action));
} }
} }
@ -1432,11 +1433,11 @@ void CmndTimeSensLostSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_sens_lost = (uint16_t)(value / 60); Thermostat[ctr_output].time_sens_lost = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_sens_lost * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_sens_lost));
} }
} }
@ -1446,11 +1447,11 @@ void CmndTimeMinTurnoffActionSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_min_turnoff_action = (uint16_t)(value / 60); Thermostat[ctr_output].time_min_turnoff_action = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_turnoff_action * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_min_turnoff_action));
} }
} }
@ -1514,11 +1515,11 @@ void CmndTimeRampupMaxSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 86400)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_rampup_max = (uint16_t)(value / 60); Thermostat[ctr_output].time_rampup_max = (uint16_t)value;
} }
} }
ResponseCmndNumber((int)(((uint32_t)Thermostat[ctr_output].time_rampup_max) * 60)); ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_rampup_max));
} }
} }
@ -1528,7 +1529,7 @@ void CmndTimeRampupCycleSet(void)
uint8_t ctr_output = XdrvMailbox.index - 1; uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
uint32_t value = (uint32_t)(XdrvMailbox.payload); uint32_t value = (uint32_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 54000)) { if ((value >= 0) && (value <= 1440)) {
Thermostat[ctr_output].time_rampup_cycle = (uint16_t)value; Thermostat[ctr_output].time_rampup_cycle = (uint16_t)value;
} }
} }
@ -1563,22 +1564,6 @@ void CmndTempRampupPiAccErrSet(void)
} }
} }
void CmndTimePiProportRead(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
ResponseCmndNumber((int)Thermostat[ctr_output].time_proportional_pi);
}
}
void CmndTimePiIntegrRead(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
ResponseCmndNumber((int)Thermostat[ctr_output].time_integral_pi);
}
}
void CmndDiagnosticModeSet(void) void CmndDiagnosticModeSet(void)
{ {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {

View File

@ -230,11 +230,19 @@ uint32_t wc_setup(int32_t fsiz) {
sensor_t * wc_s = esp_camera_sensor_get(); sensor_t * wc_s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated // initial sensors are flipped vertically and colors are a bit saturated
/*
if (OV3660_PID == wc_s->id.PID) { if (OV3660_PID == wc_s->id.PID) {
wc_s->set_vflip(wc_s, 1); // flip it back 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_brightness(wc_s, 1); // up the brightness just a bit
wc_s->set_saturation(wc_s, -2); // lower the saturation 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 // drop down frame size for higher initial frame rate
wc_s->set_framesize(wc_s, (framesize_t)fsiz); 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; wc_height = wc_fb->height;
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
#ifdef USE_FACE_DETECT #ifdef USE_FACE_DETECT
fd_init(); fd_init();
#endif #endif
@ -347,8 +354,8 @@ uint32_t wc_get_jpeg(uint8_t **buff) {
_jpg_buf_len = wc_fb->len; _jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf; _jpg_buf = wc_fb->buf;
} }
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb); // This frees the buffer
*buff = _jpg_buf; *buff = _jpg_buf; // Buffer has been freed so this will cause exceptions
return _jpg_buf_len; return _jpg_buf_len;
} }
@ -440,7 +447,7 @@ void HandleImage(void) {
len = wc_get_jpeg(&buff); len = wc_get_jpeg(&buff);
if (len) { if (len) {
client.write(buff,len); client.write(buff,len);
free(buff); free(buff); // Buffer has been freed already in wc_get_jpeg so this will cause exceptions
} }
} else { } else {
bnum--; bnum--;
@ -461,11 +468,55 @@ WiFiClient client;
void handleMjpeg(void) { void handleMjpeg(void) {
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver")); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Handle camserver"));
//if (!wc_stream_active) { if (!wc_stream_active) {
wc_stream_active = 1; wc_stream_active = 1;
client = CamServer->client(); client = CamServer->client();
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client")); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Create client"));
//} }
}
void HandleImageTheo(void) {
if (!HttpCheckPriviledgedAccess(true)) { return; }
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Capture image"));
if (Settings.webcam_config.stream) {
if (!CamServer) {
WcStreamControl();
}
}
camera_fb_t *wc_fb;
wc_fb = esp_camera_fb_get(); // Acquire frame
if (!wc_fb) {
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame buffer could not be acquired"));
return;
}
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
if (wc_fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted) {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
} else {
_jpg_buf_len = wc_fb->len;
_jpg_buf = wc_fb->buf;
}
if (_jpg_buf_len) {
Webserver->client().flush();
WSHeaderSend();
Webserver->sendHeader(F("Content-disposition"), F("inline; filename=cap.jpg"));
Webserver->send_P(200, "image/jpeg", (char *)_jpg_buf, _jpg_buf_len);
Webserver->client().stop();
}
esp_camera_fb_return(wc_fb); // Free frame buffer
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Image sent"));
} }
#ifdef USE_FACE_DETECT #ifdef USE_FACE_DETECT
@ -613,11 +664,9 @@ void handleMjpeg_task(void) {
bool jpeg_converted = false; bool jpeg_converted = false;
if (!client.connected()) { if (!client.connected()) {
wc_stream_active = 0;
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail")); AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Client fail"));
goto exit; wc_stream_active = 0;
} }
if (1 == wc_stream_active) { if (1 == wc_stream_active) {
client.flush(); client.flush();
client.setTimeout(3); client.setTimeout(3);
@ -626,15 +675,15 @@ void handleMjpeg_task(void) {
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
"\r\n"); "\r\n");
wc_stream_active = 2; wc_stream_active = 2;
} else { }
if (2 == wc_stream_active) {
wc_fb = esp_camera_fb_get(); wc_fb = esp_camera_fb_get();
if (!wc_fb) { if (!wc_fb) {
wc_stream_active = 0;
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Frame fail")); 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) { if (wc_fb->format != PIXFORMAT_JPEG) {
jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len); jpeg_converted = frame2jpg(wc_fb, 80, &_jpg_buf, &_jpg_buf_len);
if (!jpeg_converted){ if (!jpeg_converted){
@ -673,13 +722,11 @@ void handleMjpeg_task(void) {
if (jpeg_converted) { free(_jpg_buf); } if (jpeg_converted) { free(_jpg_buf); }
esp_camera_fb_return(wc_fb); esp_camera_fb_return(wc_fb);
//AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame")); //AddLog_P2(WC_LOGLEVEL, PSTR("CAM: send frame"));
}
exit: if (0 == wc_stream_active) {
if (!wc_stream_active) { AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit"));
AddLog_P2(WC_LOGLEVEL, PSTR("CAM: Stream exit")); client.flush();
client.flush(); client.stop();
client.stop();
}
} }
} }
@ -750,13 +797,6 @@ void detect_motion(void) {
} }
} }
void wc_show_stream(void) {
if (CamServer) {
WSContentSend_P(PSTR("<p></p><center><img src='http://%s:81/stream' alt='Webcam stream' style='width:99%%;'></center><p></p>"),
WiFi.localIP().toString().c_str());
}
}
uint32_t wc_set_streamserver(uint32_t flag) { uint32_t wc_set_streamserver(uint32_t flag) {
if (global_state.wifi_down) { return 0; } if (global_state.wifi_down) { return 0; }
@ -783,17 +823,30 @@ uint32_t wc_set_streamserver(uint32_t flag) {
return 0; return 0;
} }
void WcStreamControl(uint32_t resolution) { void WcStreamControl() {
wc_set_streamserver(resolution); wc_set_streamserver(Settings.webcam_config.stream);
/*if (0 == resolution) { int resolution = (!Settings.webcam_config.stream) ? -1 : Settings.webcam_config.resolution;
resolution=-1;
}*/
wc_setup(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("<p></p><center><img src='http://%s:81/stream' alt='Webcam stream' style='width:99%%;'></center><p></p>"),
WiFi.localIP().toString().c_str());
}
}
}
void wc_loop(void) { void wc_loop(void) {
if (CamServer) { CamServer->handleClient(); } if (CamServer) {
if (wc_stream_active) { handleMjpeg_task(); } CamServer->handleClient();
if (wc_stream_active) { handleMjpeg_task(); }
}
if (motion_detect) { detect_motion(); } if (motion_detect) { detect_motion(); }
#ifdef USE_FACE_DETECT #ifdef USE_FACE_DETECT
if (face_detect_time) { detect_face(); } if (face_detect_time) { detect_face(); }
@ -803,6 +856,8 @@ void wc_loop(void) {
void wc_pic_setup(void) { void wc_pic_setup(void) {
Webserver->on("/wc.jpg", HandleImage); Webserver->on("/wc.jpg", HandleImage);
Webserver->on("/wc.mjpeg", HandleImage); Webserver->on("/wc.mjpeg", HandleImage);
Webserver->on("/snapshot.jpg", HandleImageTheo);
} }
/* /*
@ -833,34 +888,103 @@ red led = gpio 33
*/ */
void WcInit(void) { void WcInit(void) {
if (Settings.esp32_webcam_resolution > 10) { if (!Settings.webcam_config.data) {
Settings.esp32_webcam_resolution = 0; 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 * 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 const char kWCCommands[] PROGMEM = D_PRFX_WEBCAM "|" // Prefix
D_CMND_WEBCAM "|" 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 = { void (* const WCCommand[])(void) PROGMEM = {
&CmndWebcam, &CmndWebcam, &CmndWebcamStream, &CmndWebcamResolution, &CmndWebcamMirror, &CmndWebcamFlip,
&CmndWebcamSaturation, &CmndWebcamBrightness, &CmndWebcamContrast
}; };
void CmndWebcam(void) { void CmndWebcam(void) {
uint32_t flag = 0; Response_P(PSTR("{\"" D_PRFX_WEBCAM "\":{\"" D_CMND_WC_STREAM "\":%d,\"" D_CMND_WC_RESOLUTION "\":%d,\"" D_CMND_WC_MIRROR "\":%d,\""
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) { D_CMND_WC_FLIP "\":%d,\""
Settings.esp32_webcam_resolution=XdrvMailbox.payload; D_CMND_WC_SATURATION "\":%d,\"" D_CMND_WC_BRIGHTNESS "\":%d,\"" D_CMND_WC_CONTRAST "\":%d}}"),
WcStreamControl(Settings.esp32_webcam_resolution); 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; } ResponseCmndStateText(Settings.webcam_config.stream);
Response_P(PSTR("{\"" D_CMND_WEBCAM "\":{\"Streaming\":\"%s\"}"),GetStateText(flag)); }
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 +1002,7 @@ bool Xdrv81(uint8_t function) {
wc_pic_setup(); wc_pic_setup();
break; break;
case FUNC_WEB_ADD_MAIN_BUTTON: case FUNC_WEB_ADD_MAIN_BUTTON:
if (Settings.esp32_webcam_resolution) { WcShowStream();
//#ifndef USE_SCRIPT
WcStreamControl(Settings.esp32_webcam_resolution);
delay(50); // Give the webcam webserver some time to prepare the stream
wc_show_stream();
//#endif
}
break; break;
case FUNC_COMMAND: case FUNC_COMMAND:
result = DecodeCommand(kWCCommands, WCCommand); result = DecodeCommand(kWCCommands, WCCommand);

View File

@ -59,13 +59,14 @@
#define D_CMND_I2CREAD "I2CRead" #define D_CMND_I2CREAD "I2CRead"
#define D_CMND_I2CSTRETCH "I2CStretch" #define D_CMND_I2CSTRETCH "I2CStretch"
#define D_CMND_I2CCLOCK "I2CClock" #define D_CMND_I2CCLOCK "I2CClock"
#define D_CMND_SERBUFF "SerBufSize"
const char kDebugCommands[] PROGMEM = "|" // No prefix const char kDebugCommands[] PROGMEM = "|" // No prefix
D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|" D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|"
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
D_CMND_CFGXOR "|" D_CMND_CFGXOR "|"
#endif #endif
D_CMND_CPUCHECK "|" D_CMND_CPUCHECK "|" D_CMND_SERBUFF "|"
#ifdef DEBUG_THEO #ifdef DEBUG_THEO
D_CMND_EXCEPTION "|" D_CMND_EXCEPTION "|"
#endif #endif
@ -80,7 +81,7 @@ void (* const DebugCommand[])(void) PROGMEM = {
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
&CmndCfgXor, &CmndCfgXor,
#endif #endif
&CmndCpuCheck, &CmndCpuCheck, &CmndSerBufSize,
#ifdef DEBUG_THEO #ifdef DEBUG_THEO
&CmndException, &CmndException,
#endif #endif
@ -479,6 +480,18 @@ void CmndCpuCheck(void)
ResponseCmndNumber(CPU_load_check); ResponseCmndNumber(CPU_load_check);
} }
void CmndSerBufSize(void)
{
if (XdrvMailbox.data_len > 0) {
Serial.setRxBufferSize(XdrvMailbox.payload);
}
#ifdef ESP8266
ResponseCmndNumber(Serial.getRxBufferSize());
#else
ResponseCmndDone();
#endif
}
void CmndFreemem(void) void CmndFreemem(void)
{ {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {