Merge branch 'development' into pre-release

This commit is contained in:
Theo Arends 2020-05-18 10:06:12 +02:00
commit 2055d06825
56 changed files with 1617 additions and 568 deletions

View File

@ -75,6 +75,7 @@
| USE_DHT | - | - | x | x | x | x | x |
| USE_MAX31855 | - | - | - | - | x | - | - |
| USE_MAX31865 | - | - | - | - | - | - | - |
| USE_THERMOSTAT | - | - | - | - | - | - | - |
| | | | | | | | |
| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
| USE_I2C | - | - | x | x | x | - | x |

View File

@ -52,6 +52,15 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog
### Version 8.3.1 Fred
- Change KNX pow function to approximative pow saving 5k of code space
- Change Mutichannel Gas sensor pow function to approximative pow saving 5k of code space
- Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066)
- Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages
- Fix HAss discovery
- Add command ``DeviceName`` defaults to FriendlyName1 and replaces FriendlyName1 in GUI
### Version 8.3.0 Fred
- Breaking Change Device Groups multicast address and port (#8270)

View File

@ -327,6 +327,30 @@ int16_t MutichannelGasSensor::readR(void)
** Returns:
float value - concentration of the gas
*********************************************************************************************************/
float MutichannelGasSensor_pow(float a, float b)
{
// https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
// calculate approximation with fraction of the exponent
int e = abs((int)b);
union {
double d;
int x[2];
} u = { a };
u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
// exponentiation by squaring with the exponent's integer part
// double r = u.d makes everything much slower, not sure why
double r = 1.0;
while (e) {
if (e & 1) {
r *= a;
}
a *= a;
e >>= 1;
}
return r * u.d;
}
float MutichannelGasSensor::calcGas(int gas)
{
@ -370,42 +394,42 @@ float MutichannelGasSensor::calcGas(int gas)
{
case CO:
{
c = pow(ratio1, -1.179)*4.385; //mod by jack
c = MutichannelGasSensor_pow(ratio1, -1.179)*4.385; //mod by jack
break;
}
case NO2:
{
c = pow(ratio2, 1.007)/6.855; //mod by jack
c = MutichannelGasSensor_pow(ratio2, 1.007)/6.855; //mod by jack
break;
}
case NH3:
{
c = pow(ratio0, -1.67)/1.47; //modi by jack
c = MutichannelGasSensor_pow(ratio0, -1.67)/1.47; //modi by jack
break;
}
case C3H8: //add by jack
{
c = pow(ratio0, -2.518)*570.164;
c = MutichannelGasSensor_pow(ratio0, -2.518)*570.164;
break;
}
case C4H10: //add by jack
{
c = pow(ratio0, -2.138)*398.107;
c = MutichannelGasSensor_pow(ratio0, -2.138)*398.107;
break;
}
case GAS_CH4: //add by jack
{
c = pow(ratio1, -4.363)*630.957;
c = MutichannelGasSensor_pow(ratio1, -4.363)*630.957;
break;
}
case H2: //add by jack
{
c = pow(ratio1, -1.8)*0.73;
c = MutichannelGasSensor_pow(ratio1, -1.8)*0.73;
break;
}
case C2H5OH: //add by jack
{
c = pow(ratio1, -1.552)*1.622;
c = MutichannelGasSensor_pow(ratio1, -1.552)*1.622;
break;
}
default:

View File

@ -0,0 +1,513 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Python Class for compressing short strings.
This class contains a highly modified and optimized version of Unishox
for Tasmota converted in C ported to Pyhton3.
It was basically developed to individually compress and decompress small strings
(see https://github.com/siara-cc/Unishox)
In general compression utilities such as zip, gzip do not compress short strings
well and often expand them. They also use lots of memory which makes them unusable
in constrained environments like Arduino.
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.
"""
class Unishox:
"""
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.
@author Stephan Hadinger
@revised Norbert Richter
"""
# pylint: disable=bad-continuation,bad-whitespace,line-too-long
cl_95 = [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]
# enum {SHX_STATE_1 = 1, SHX_STATE_2}; // removed Unicode state
SHX_STATE_1 = 1
SHX_STATE_2 = 2
SHX_SET1 = 0
SHX_SET1A = 1
SHX_SET1B = 2
SHX_SET2 = 3
sets = [[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'],
['.', ',', '-', '/', '?', '+', ' ', '(', ')', '$', '@'],
[';', '#', ':', '<', '^', '*', '"', '{', '}', '[', ']'],
['=', '%', '\'', '>', '&', '_', '!', '\\', '|', '~', '`']]
us_vcode = [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,
us_hcode = [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) ]
# pylint: enable=bad-continuation,bad-whitespace
ESCAPE_MARKER = 0x2A
TERM_CODE = 0x37C0
# TERM_CODE_LEN = 10
DICT_CODE = 0x0000
DICT_CODE_LEN = 5
#DICT_OTHER_CODE = 0x0000
#DICT_OTHER_CODE_LEN = 6
RPT_CODE_TASMOTA = 0x3780
RPT_CODE_TASMOTA_LEN = 10
BACK2_STATE1_CODE = 0x2000
BACK2_STATE1_CODE_LEN = 4
#BACK_FROM_UNI_CODE = 0xFE00
#BACK_FROM_UNI_CODE_LEN = 8
LF_CODE = 0x3700
LF_CODE_LEN = 9
TAB_CODE = 0x2400
TAB_CODE_LEN = 7
ALL_UPPER_CODE = 0x2200
ALL_UPPER_CODE_LEN = 8
SW2_STATE2_CODE = 0x3800
SW2_STATE2_CODE_LEN = 7
ST2_SPC_CODE = 0x3B80
ST2_SPC_CODE_LEN = 11
BIN_CODE_TASMOTA = 0x8000
BIN_CODE_TASMOTA_LEN = 3
NICE_LEN = 5
mask = [0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF]
# pylint: disable=missing-function-docstring,invalid-name
# Input
# out = bytearray
def append_bits(self, out, ol, code, clen, state):
#print("Append bits {ol} {code} {clen} {state}".format(ol=ol, code=code, clen=clen, state=state))
if state == self.SHX_STATE_2:
# remove change state prefix
if (code >> 9) == 0x1C:
code <<= 7
clen -= 7
while clen > 0:
cur_bit = ol % 8
blen = 8 if (clen > 8) else clen
a_byte = (code >> 8) & self.mask[blen - 1]
#print("append_bits a_byte {ab} blen {blen}".format(ab=a_byte,blen=blen))
a_byte >>= cur_bit
if blen + cur_bit > 8:
blen = (8 - cur_bit)
if cur_bit == 0:
out[ol // 8] = a_byte
else:
out[ol // 8] |= a_byte
code <<= blen
ol += blen
if 0 == ol % 8: # pylint: disable=misplaced-comparison-constant
# we completed a full byte
last_c = out[(ol // 8) - 1]
if last_c in (0, self.ESCAPE_MARKER):
out[ol // 8] = 1 + last_c # increment to 0x01 or 0x2B
out[(ol // 8) -1] = self.ESCAPE_MARKER # replace old value with marker
ol += 8 # add one full byte
clen -= blen
return ol
codes = [0x82, 0xC3, 0xE5, 0xED, 0xF5] # pylint: disable=bad-whitespace
bit_len = [ 5, 7, 9, 12, 16] # pylint: disable=bad-whitespace
def encodeCount(self, out, ol, count):
#print("encodeCount ol = {ol}, count = {count}".format(ol=ol, count=count))
till = 0
base = 0
for i in range(len(self.bit_len)):
bit_len_i = self.bit_len[i]
till += (1 << bit_len_i)
if count < till:
codes_i = self.codes[i]
ol = self.append_bits(out, ol, (codes_i & 0xF8) << 8, codes_i & 0x07, 1)
#print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(codes_i & 0xF8) << 8,len=codes_i & 0x07))
ol = self.append_bits(out, ol, (count - base) << (16 - bit_len_i), bit_len_i, 1)
#print("encodeCount append_bits ol = {ol}, code = {code}, len = {len}".format(ol=ol,code=(count - base) << (16 - bit_len_i),len=bit_len_i))
return ol
base = till
return ol
# Returns (int, ol, state, is_all_upper)
def matchOccurance(self, inn, len_, l_, out, ol, state, is_all_upper):
# int j, k;
longest_dist = 0
longest_len = 0
#for (j = l_ - self.NICE_LEN; j >= 0; j--) {
j = l_ - self.NICE_LEN
while j >= 0:
k = l_
#for (k = l_; k < len && j + k - l_ < l_; k++) {
while k < len_ and j + k - l_ < l_:
if inn[k] != inn[j + k - l_]:
break
k += 1
if k - l_ > self.NICE_LEN - 1:
match_len = k - l_ - self.NICE_LEN
match_dist = l_ - j - self.NICE_LEN + 1
if match_len > longest_len:
longest_len = match_len
longest_dist = match_dist
j -= 1
if longest_len:
#print("longest_len {ll}".format(ll=longest_len))
#ol_save = ol
if state == self.SHX_STATE_2 or is_all_upper:
is_all_upper = 0
state = self.SHX_STATE_1
ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state)
ol = self.append_bits(out, ol, self.DICT_CODE, self.DICT_CODE_LEN, 1)
ol = self.encodeCount(out, ol, longest_len)
ol = self.encodeCount(out, ol, longest_dist)
#print("longest_len {ll} longest_dist {ld} ol {ols}-{ol}".format(ll=longest_len, ld=longest_dist, ol=ol, ols=ol_save))
l_ += longest_len + self.NICE_LEN
l_ -= 1
return l_, ol, state, is_all_upper
return -l_, ol, state, is_all_upper
def compress(self, inn, len_, out, len_out):
ol = 0
state = self.SHX_STATE_1
is_all_upper = 0
l = 0
while l < len_:
# for (l=0; l<len_; l++) {
c_in = inn[l]
if l and l < len_ - 4:
if c_in == inn[l - 1] and c_in == inn[l + 1] and c_in == inn[l + 2] and c_in == inn[l + 3]:
rpt_count = l + 4
while rpt_count < len_ and inn[rpt_count] == c_in:
rpt_count += 1
rpt_count -= l
if state == self.SHX_STATE_2 or is_all_upper:
is_all_upper = 0
state = self.SHX_STATE_1
ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state) # back to lower case and Set1
ol = self.append_bits(out, ol, self.RPT_CODE_TASMOTA, self.RPT_CODE_TASMOTA_LEN, 1) # reusing CRLF for RPT
ol = self.encodeCount(out, ol, rpt_count - 4)
l += rpt_count
#l -= 1
continue
if l < (len_ - self.NICE_LEN + 1):
#l_old = l
(l, ol, state, is_all_upper) = self.matchOccurance(inn, len_, l, out, ol, state, is_all_upper)
if l > 0:
#print("matchOccurance l = {l} l_old = {lo}".format(l=l,lo=l_old))
l += 1 # for loop
continue
l = -l
if state == self.SHX_STATE_2: # if Set2
if ord(' ') <= c_in <= ord('@') or ord('[') <= c_in <= ord('`') or ord('{') <= c_in <= ord('~'):
pass
else:
state = self.SHX_STATE_1 # back to Set1 and lower case
ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state)
is_upper = 0
if ord('A') <= c_in <= ord('Z'):
is_upper = 1
else:
if is_all_upper:
is_all_upper = 0
ol = self.append_bits(out, ol, self.BACK2_STATE1_CODE, self.BACK2_STATE1_CODE_LEN, state)
if 32 <= c_in <= 126:
if is_upper and not is_all_upper:
ll = l+5
# for (ll=l+5; ll>=l && ll<len_; ll--) {
while l <= ll < len_:
if inn[ll] < ord('A') or inn[ll] > ord('Z'):
break
ll -= 1
if ll == l-1:
ol = self.append_bits(out, ol, self.ALL_UPPER_CODE, self.ALL_UPPER_CODE_LEN, state) # CapsLock
is_all_upper = 1
if state == self.SHX_STATE_1 and ord('0') <= c_in <= ord('9'):
ol = self.append_bits(out, ol, self.SW2_STATE2_CODE, self.SW2_STATE2_CODE_LEN, state) # Switch to sticky Set2
state = self.SHX_STATE_2
c_in -= 32
if is_all_upper and is_upper:
c_in += 32
if c_in == 0 and state == self.SHX_STATE_2:
ol = self.append_bits(out, ol, self.ST2_SPC_CODE, self.ST2_SPC_CODE_LEN, state) # space from Set2 ionstead of Set1
else:
# ol = self.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
cl = self.cl_95[c_in]
ol = self.append_bits(out, ol, cl & 0xFFF0, cl & 0x000F, state)
elif c_in == 10:
ol = self.append_bits(out, ol, self.LF_CODE, self.LF_CODE_LEN, state) # LF
elif c_in == '\t':
ol = self.append_bits(out, ol, self.TAB_CODE, self.TAB_CODE_LEN, state) # TAB
else:
ol = self.append_bits(out, ol, self.BIN_CODE_TASMOTA, self.BIN_CODE_TASMOTA_LEN, state) # Binary, we reuse the Unicode marker which 3 bits instead of 9
ol = self.encodeCount(out, ol, (255 - c_in) & 0xFF)
# check that we have some headroom in the output buffer
if ol // 8 >= len_out - 4:
return -1 # we risk overflow and crash
l += 1
bits = ol % 8
if bits:
ol = self.append_bits(out, ol, self.TERM_CODE, 8 - bits, 1) # 0011 0111 1100 0000 TERM = 0011 0111 11
return (ol + 7) // 8
# return ol // 8 + 1 if (ol%8) else 0
def getBitVal(self, inn, bit_no, count):
c_in = inn[bit_no >> 3]
if bit_no >> 3 and self.ESCAPE_MARKER == inn[(bit_no >> 3) - 1]:
c_in -= 1
r = 1 << count if (c_in & (0x80 >> (bit_no % 8))) else 0
#print("getBitVal r={r}".format(r=r))
return r
# Returns:
# 0..11
# or -1 if end of stream
def getCodeIdx(self, code_type, inn, len_, bit_no_p):
code = 0
count = 0
while count < 5:
# detect marker
if self.ESCAPE_MARKER == inn[bit_no_p >> 3]:
bit_no_p += 8 # skip marker
if bit_no_p >= len_:
return -1, bit_no_p
code += self.getBitVal(inn, bit_no_p, count)
bit_no_p += 1
count += 1
code_type_code = code_type[code]
if code_type_code and (code_type_code & 0x07) == count:
#print("getCodeIdx = {r}".format(r=code_type_code >> 3))
return code_type_code >> 3, bit_no_p
#print("getCodeIdx not found = {r}".format(r=1))
return 1, bit_no_p
def getNumFromBits(self, inn, bit_no, count):
ret = 0
while count:
count -= 1
if self.ESCAPE_MARKER == inn[bit_no >> 3]:
bit_no += 8 # skip marker
ret += self.getBitVal(inn, bit_no, count)
bit_no += 1
return ret
def readCount(self, inn, bit_no_p, len_):
(idx, bit_no_p) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no_p)
if idx >= 1:
idx -= 1 # we skip v = 1 (code '0') since we no more accept 2 bits encoding
if idx >= 5 or idx < 0:
return 0, bit_no_p # unsupported or end of stream
till = 0
bit_len_idx = 0
base = 0
#for (uint32_t i = 0; i <= idx; i++) {
i = 0
while i <= idx:
# for i in range(idx):
base = till
bit_len_idx = self.bit_len[i]
till += (1 << bit_len_idx)
i += 1
count = self.getNumFromBits(inn, bit_no_p, bit_len_idx) + base
#print("readCount getNumFromBits = {count} ({bl})".format(count=count,bl=bit_len_idx))
bit_no_p += bit_len_idx
return count, bit_no_p
def decodeRepeat(self, inn, len_, out, ol, bit_no):
#print("decodeRepeat Enter")
(dict_len, bit_no) = self.readCount(inn, bit_no, len_)
dict_len += self.NICE_LEN
(dist, bit_no) = self.readCount(inn, bit_no, len_)
dist += self.NICE_LEN - 1
#memcpy(out + ol, out + ol - dist, dict_len);
i = 0
while i < dict_len:
#for i in range(dict_len):
out[ol + i] = out[ol - dist + i]
i += 1
ol += dict_len
return ol, bit_no
def decompress(self, inn, len_, out, len_out):
ol = 0
bit_no = 0
dstate = self.SHX_SET1
is_all_upper = 0
len_ <<= 3 # *8, len_ in bits
out[ol] = 0
while bit_no < len_:
c = 0
is_upper = is_all_upper
(v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # read vCode
#print("bit_no {b}. v = {v}".format(b=bit_no,v=v))
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, bit_no) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no) # read hCode
#print("bit_no {b}. h = {h}".format(b=bit_no,h=h))
if h < 0:
break # end of stream
if h == self.SHX_SET1: # target is Set1
if dstate == self.SHX_SET1: # Switch from Set1 to Set1 us UpperCase
if is_all_upper: # if CapsLock, then back to LowerCase
is_upper = 0
is_all_upper = 0
continue
(v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # read again vCode
if v < 0:
break # end of stream
if v == 0:
(h, bit_no) = self.getCodeIdx(self.us_hcode, inn, len_, bit_no) # read second hCode
if h < 0:
break # end of stream
if h == self.SHX_SET1: # If double Switch Set1, the CapsLock
is_all_upper = 1
continue
is_upper = 1 # anyways, still uppercase
else:
dstate = self.SHX_SET1 # if Set was not Set1, switch to Set1
continue
elif h == self.SHX_SET2: # If Set2, switch dstate to Set2
if dstate == self.SHX_SET1:
dstate = self.SHX_SET2
continue
if h != self.SHX_SET1: # all other Sets (why not else)
(v, bit_no) = self.getCodeIdx(self.us_vcode, inn, len_, bit_no) # we changed set, now read vCode for char
if v < 0:
break # end of stream
if v == 0 and h == self.SHX_SET1A:
#print("v = 0, h = self.SHX_SET1A")
if is_upper:
(temp, bit_no) = self.readCount(inn, bit_no, len_)
out[ol] = 255 - temp # binary
ol += 1
else:
(ol, bit_no) = self.decodeRepeat(inn, len_, out, ol, bit_no) # dist
continue
if h == self.SHX_SET1 and v == 3:
# was Unicode, will do Binary instead
(temp, bit_no) = self.readCount(inn, bit_no, len_)
out[ol] = 255 - temp # binary
ol += 1
continue
if h < 7 and v < 11:
#print("h {h} v {v}".format(h=h,v=v))
c = ord(self.sets[h][v])
if ord('a') <= c <= ord('z'):
if is_upper:
c -= 32 # go to UpperCase for letters
else: # handle all other cases
if is_upper and dstate == self.SHX_SET1 and v == 1:
c = ord('\t') # If UpperCase Space, change to TAB
if h == self.SHX_SET1B:
if 8 == v: # was LF or RPT, now only LF # pylint: disable=misplaced-comparison-constant
out[ol] = ord('\n')
ol += 1
continue
if 9 == v: # was CRLF, now RPT # pylint: disable=misplaced-comparison-constant
(count, bit_no) = self.readCount(inn, bit_no, len_)
count += 4
if ol + count >= len_out:
return -1 # overflow
rpt_c = out[ol - 1]
while count:
count -= 1
out[ol] = rpt_c
ol += 1
continue
if 10 == v: # pylint: disable=misplaced-comparison-constant
break # TERM, stop decoding
out[ol] = c
ol += 1
if ol >= len_out:
return -1 # overflow
return ol
# pylint: enable=missing-function-docstring
if __name__ == "__main__":
# pylint: disable=line-too-long
UNISHOX = Unishox()
BYTES_ = bytearray(2048)
INN = bytearray(b'ON Switch1#State==1 DO Add1 1 ENDON ON Var1#State==0 DO ShutterStop1 ENDON ON Var1#State==1 DO ShutterClose1 ENDON ON Var1#State>=2 DO Var1 0 ENDON ON Shutter1#Close DO Var1 0 ENDON ON Switch2#State==1 DO Add2 1 ENDON ON Var2#State==0 DO ShutterStop1 ENDON ON Var2#State==1 DO ShutterOpen1 ENDON ON Var2#State>=2 DO Var2 0 ENDON ON Shutter1#Open DO Var2 0 ENDON')
LEN_ = UNISHOX.compress(INN, len(INN), BYTES_, len(BYTES_))
print("Compressed from {fromm} to {to} ({p}%)".format(fromm=len(INN), to=LEN_, p=(100-LEN_/len(INN)*100)))
OUT = bytearray(2048)
LEN_ = UNISHOX.decompress(BYTES_, LEN_, OUT, len(OUT))
print(str(OUT, 'utf-8').split('\x00')[0])

View File

@ -153,7 +153,9 @@ const uint16_t BIN_CODE_TASMOTA_LEN = 3;
// 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) {
void Unishox::append_bits(unsigned int code, int clen) {
byte cur_bit;
byte blen;
@ -165,25 +167,19 @@ int append_bits(char *out, size_t ol, unsigned int code, int clen, byte state) {
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;
out[ol >> 3] = a_byte;
else
out[ol / 8] |= a_byte;
out[ol >> 3] |= a_byte;
}
code <<= blen;
ol += blen;
@ -191,14 +187,13 @@ int append_bits(char *out, size_t ol, unsigned int code, int clen, byte state) {
// 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
out[ol >> 3] = 1 + last_c; // increment to 0x01 or 0x2B
out[(ol >>3) -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
@ -210,40 +205,36 @@ 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) {
void Unishox::encodeCount(int32_t count) {
int till = 0;
int base = 0;
for (int i = 0; i < sizeof(bit_len); i++) {
for (uint32_t 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);
append_bits((codes_i & 0xF8) << 8, codes_i & 0x07);
// 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;
append_bits((count - base) << (16 - bit_len_i), bit_len_i);
return;
}
base = till;
}
return ol;
return;
}
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;
bool Unishox::matchOccurance(void) {
int32_t j, k;
uint32_t longest_dist = 0;
uint32_t 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;
uint32_t match_len = k - l - NICE_LEN;
uint32_t match_dist = l - j - NICE_LEN + 1;
if (match_len > longest_len) {
longest_len = match_len;
longest_dist = match_dist;
@ -251,19 +242,18 @@ int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *sta
}
}
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);
if (state == SHX_STATE_2 || is_all_upper) {
is_all_upper = 0;
state = SHX_STATE_1;
append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN);
}
*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;
append_bits(DICT_CODE, DICT_CODE_LEN);
encodeCount(longest_len);
encodeCount(longest_dist);
l += longest_len + NICE_LEN - 1;
return true;
}
return -l;
return false;
}
// Compress a buffer.
@ -275,15 +265,18 @@ int matchOccurance(const char *in, int len, int l, char *out, int *ol, byte *sta
// 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) {
int32_t Unishox::unishox_compress(const char *p_in, size_t p_len, char *p_out, size_t p_len_out) {
in = p_in;
len = p_len;
out = p_out;
len_out = p_len_out;
char *ptr;
byte bits;
byte state;
int l, ll, ol;
int ll;
char c_in, c_next;
byte is_upper, is_all_upper;
byte is_upper;
ol = 0;
state = SHX_STATE_1;
@ -302,23 +295,20 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out)
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
append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN); // 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--;
append_bits(RPT_CODE_TASMOTA, RPT_CODE_TASMOTA_LEN); // reusing CRLF for RPT
encodeCount(rpt_count - 4);
l += rpt_count - 1;
continue;
}
}
if (l < (len - NICE_LEN + 1)) {
l = matchOccurance(in, len, l, out, &ol, &state, &is_all_upper);
if (l > 0) {
if (matchOccurance()) {
continue;
}
l = -l;
}
if (state == SHX_STATE_2) { // if Set2
if ((c_in >= ' ' && c_in <= '@') ||
@ -326,7 +316,7 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out)
(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);
append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN);
}
}
@ -336,7 +326,7 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out)
else {
if (is_all_upper) {
is_all_upper = 0;
ol = append_bits(out, ol, BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN, state);
append_bits(BACK2_STATE1_CODE, BACK2_STATE1_CODE_LEN);
}
}
@ -351,37 +341,30 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out)
break;
}
if (ll == l-1) {
ol = append_bits(out, ol, ALL_UPPER_CODE, ALL_UPPER_CODE_LEN, state); // CapsLock
append_bits(ALL_UPPER_CODE, ALL_UPPER_CODE_LEN); // 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
append_bits(SW2_STATE2_CODE, SW2_STATE2_CODE_LEN); // 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
append_bits(ST2_SPC_CODE, ST2_SPC_CODE_LEN); // 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);
append_bits(cl & 0xFFF0, cl & 0x000F);
}
} 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 if (c_in == 10) {
append_bits(LF_CODE, LF_CODE_LEN); // LF
} else if (c_in == '\t') {
append_bits(TAB_CODE, TAB_CODE_LEN); // 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);
append_bits(BIN_CODE_TASMOTA, BIN_CODE_TASMOTA_LEN); // Binary, we reuse the Unicode marker which 3 bits instead of 9
encodeCount((unsigned char) 255 - c_in);
}
// check that we have some headroom in the output buffer
@ -392,50 +375,46 @@ int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out)
bits = ol % 8;
if (bits) {
ol = append_bits(out, ol, TERM_CODE, 8 - bits, 1); // 0011 0111 1100 0000 TERM = 0011 0111 11
state = SHX_STATE_1;
append_bits(TERM_CODE, 8 - bits); // 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--;
uint32_t Unishox::getNextBit(void) {
if (8 == bit_no) {
byte_in = in[byte_no++];
if (ESCAPE_MARKER == byte_in) {
byte_in = in[byte_no++] - 1;
}
return (c_in & (0x80 >> (bit_no % 8)) ? 1 << count : 0);
bit_no = 0;
}
return byte_in & (0x80 >> bit_no++) ? 1 : 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;
int32_t Unishox::getCodeIdx(const char *code_type) {
int32_t code = 0;
int32_t count = 0;
do {
// detect marker
if (ESCAPE_MARKER == in[*bit_no_p >> 3]) {
*bit_no_p += 8; // skip marker
}
if (*bit_no_p >= len)
if (bit_no >= len)
return -1; // invalid state
code += getBitVal(in, *bit_no_p, count);
(*bit_no_p)++;
code += getNextBit() << count;
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
return -1; // skip if code not found
}
int getNumFromBits(const char *in, int bit_no, int count) {
int32_t Unishox::getNumFromBits(uint32_t count) {
int ret = 0;
while (count--) {
if (ESCAPE_MARKER == in[bit_no >> 3]) {
bit_no += 8; // skip marker
}
ret += getBitVal(in, bit_no++, count);
ret += getNextBit() << count;
}
return ret;
}
@ -452,8 +431,8 @@ int getNumFromBits(const char *in, int bit_no, int count) {
// 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);
uint32_t Unishox::readCount(void) {
int32_t idx = getCodeIdx(us_hcode);
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
@ -465,44 +444,41 @@ int readCount(const char *in, int *bit_no_p, int len) {
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;
int count = getNumFromBits(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;
void Unishox::decodeRepeat(void) {
uint32_t dict_len = readCount() + NICE_LEN;
uint32_t dist = readCount() + 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) {
int32_t Unishox::unishox_decompress(const char *p_in, size_t p_len, char *p_out, size_t p_len_out) {
in = p_in;
len = p_len;
out = p_out;
len_out = p_len_out;
int dstate;
int bit_no;
byte is_all_upper;
int ol = 0;
bit_no = 0;
ol = 0;
bit_no = 8; // force load of first byte, pretending we expired the last one
byte_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;
int32_t 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
v = getCodeIdx(us_vcode); // 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
h = getCodeIdx(us_hcode); // 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
@ -510,10 +486,10 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out
is_upper = is_all_upper = 0;
continue;
}
v = getCodeIdx(us_vcode, in, len, &bit_no); // read again vCode
v = getCodeIdx(us_vcode); // read again vCode
if (v < 0) break; // end of stream
if (v == 0) {
h = getCodeIdx(us_hcode, in, len, &bit_no); // read second hCode
h = getCodeIdx(us_hcode); // read second hCode
if (h < 0) break; // end of stream
if (h == SHX_SET1) { // If double Switch Set1, the CapsLock
is_all_upper = 1;
@ -532,23 +508,23 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out
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
v = getCodeIdx(us_vcode); // 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
out[ol++] = 255 - readCount(); // binary
} else {
ol = decodeRepeat(in, len, out, ol, &bit_no); // dist
decodeRepeat(); // dist
}
continue;
}
if (h == SHX_SET1 && v == 3) {
// was Unicode, will do Binary instead
out[ol++] = 255 - readCount(in, &bit_no, len); // binary
out[ol++] = 255 - readCount(); // binary
continue;
}
if (h < 7 && v < 11) // TODO: are these the actual limits? Not 11x7 ?
@ -561,22 +537,11 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out
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;
uint32_t count = readCount() + 4;
if (ol + count >= len_out) {
return -1; // overflow
}
@ -598,5 +563,4 @@ int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out
}
return ol;
}

View File

@ -20,7 +20,45 @@
#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);
//extern int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out);
class Unishox {
public:
Unishox() {};
int32_t unishox_decompress(const char *in, size_t len, char *out, size_t len_out);
int32_t unishox_compress(const char *in, size_t len, char *out, size_t len_out);
private:
void append_bits(unsigned int code, int clen);
void encodeCount(int32_t count);
bool matchOccurance(void);
uint32_t getNextBit(void);
int32_t getCodeIdx(const char *code_type);
uint32_t readCount(void);
void decodeRepeat(void);
int32_t getNumFromBits(uint32_t count);
inline void writeOut(char c) { out[ol++] = c; }
int32_t l;
uint32_t ol;
int32_t bit_no;
uint32_t byte_no;
const char * in;
char * out;
size_t len;
size_t len_out;
uint8_t dstate;
unsigned char byte_in;
uint8_t state;
uint8_t is_all_upper;
};
#endif

View File

@ -35,12 +35,36 @@ uint16_t ESPKNXIP::data_to_2byte_uint(uint8_t *data)
return (uint16_t)((data[1] << 8) | data[2]);
}
float esp_knx_pow(float a, float b)
{
// https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
// calculate approximation with fraction of the exponent
int e = abs((int)b);
union {
double d;
int x[2];
} u = { a };
u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
// exponentiation by squaring with the exponent's integer part
// double r = u.d makes everything much slower, not sure why
double r = 1.0;
while (e) {
if (e & 1) {
r *= a;
}
a *= a;
e >>= 1;
}
return r * u.d;
}
float ESPKNXIP::data_to_2byte_float(uint8_t *data)
{
//uint8_t sign = (data[1] & 0b10000000) >> 7;
uint8_t expo = (data[1] & 0b01111000) >> 3;
int16_t mant = ((data[1] & 0b10000111) << 8) | data[2];
return 0.01f * mant * pow(2, expo);
return 0.01f * mant * esp_knx_pow(2, expo);
}
time_of_day_t ESPKNXIP::data_to_3byte_time(uint8_t *data)

View File

@ -188,7 +188,7 @@ build_flags = ${esp_defaults.build_flags}
-D sint16_t=int16_t
-D memcpy_P=memcpy
-D memcmp_P=memcmp
; -D USE_CONFIG_OVERRIDE
-D USE_CONFIG_OVERRIDE
lib_extra_dirs =
libesp32

View File

@ -2,6 +2,24 @@
## Released
### 8.3.1 20200518
- Release Fred
### 8.3.0.2 20200517
- Fix HAss discovery
- Add command ``DeviceName`` defaults to FriendlyName1 and replaces FriendlyName1 in GUI
### 8.3.0.1 20200514
- Change KNX pow function to approximative pow saving 5k of code space
- Change Mutichannel Gas sensor pow function to approximative pow saving 5k of code space
- Change Quick Power Cycle detection from 4 to 7 power interrupts (#4066)
- Fix default state of ``SetOption73 0`` for button decoupling and send multi-press and hold MQTT messages
## Released
### 8.3.0 20200514
- Release Fred

View File

@ -276,6 +276,7 @@
#define D_WCFG_5_WAIT "Wait"
#define D_WCFG_6_SERIAL "Serial"
#define D_WCFG_7_WIFIMANAGER_RESET_ONLY "ManagerRst"
#define D_CMND_DEVICENAME "DeviceName"
#define D_CMND_FRIENDLYNAME "FriendlyName"
#define D_CMND_SWITCHMODE "SwitchMode"
#define D_CMND_INTERLOCK "Interlock"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Други параметри"
#define D_TEMPLATE "Модел"
#define D_ACTIVATE "Активирай"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Парола на уеб администратора"
#define D_MQTT_ENABLE "Активиране на MQTT"
#define D_FRIENDLY_NAME "Приятелско име"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Další nastavení"
#define D_TEMPLATE "Šablona"
#define D_ACTIVATE "Aktivovat"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Heslo Web administrátora"
#define D_MQTT_ENABLE "MQTT aktivní"
#define D_FRIENDLY_NAME "Friendly Name"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Sonstige Einstellungen"
#define D_TEMPLATE "Vorlage"
#define D_ACTIVATE "Aktivieren"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Passwort für Web Oberfläche"
#define D_MQTT_ENABLE "MQTT aktivieren"
#define D_FRIENDLY_NAME "Name [friendly name]"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Άλλες παράμετροι"
#define D_TEMPLATE "Πρότυπο"
#define D_ACTIVATE "Ενεργοποίηση"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Κωδικός διαχειριστή"
#define D_MQTT_ENABLE "Ενεργοποίηση MQTT"
#define D_FRIENDLY_NAME "Φιλική ονομασία"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Other parameters"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Web Admin Password"
#define D_MQTT_ENABLE "MQTT enable"
#define D_FRIENDLY_NAME "Friendly Name"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Otros parámetros"
#define D_TEMPLATE "Plantilla"
#define D_ACTIVATE "Activar"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Clave Administrador Web"
#define D_MQTT_ENABLE "Habilitar MQTT"
#define D_FRIENDLY_NAME "Nombre Amigable"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Autres paramètres"
#define D_TEMPLATE "Modèle"
#define D_ACTIVATE "Activer"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Mot de passe Web Admin"
#define D_MQTT_ENABLE "MQTT activé"
#define D_FRIENDLY_NAME "Surnom"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "פרמטרים שונים"
#define D_TEMPLATE "תבנית"
#define D_ACTIVATE "הפעל"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "סיסמת מנהל"
#define D_MQTT_ENABLE "MQTT אפשר"
#define D_FRIENDLY_NAME "שם ידידותי"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Egyéb beállítások"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Web admin jelszó"
#define D_MQTT_ENABLE "MQTT engedélyezése"
#define D_FRIENDLY_NAME "Név"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Altri parametri"
#define D_TEMPLATE "Modello"
#define D_ACTIVATE "Attiva"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Password amministratore web"
#define D_MQTT_ENABLE "Abilita MQTT"
#define D_FRIENDLY_NAME "Nome amichevole"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "기타 설정"
#define D_TEMPLATE "템플릿"
#define D_ACTIVATE "활성화"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Web Admin 비밀번호"
#define D_MQTT_ENABLE "MQTT 사용"
#define D_FRIENDLY_NAME "Friendly Name"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Overige parameters"
#define D_TEMPLATE "Sjabloon"
#define D_ACTIVATE "Activeer"
#define D_DEVICE_NAME "Apparaatnaam"
#define D_WEB_ADMIN_PASSWORD "Web Admin Wachtwoord"
#define D_MQTT_ENABLE "MQTT ingeschakeld"
#define D_FRIENDLY_NAME "Beschrijvende naam"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Inne parametry"
#define D_TEMPLATE "Szablon"
#define D_ACTIVATE "Aktywuj"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Hasło administratora"
#define D_MQTT_ENABLE "Załącz MQTT"
#define D_FRIENDLY_NAME "Nazwa"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Outros parâmetros"
#define D_TEMPLATE "Modelo"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Senha de WEB Admin"
#define D_MQTT_ENABLE "MQTT habilitado"
#define D_FRIENDLY_NAME "Nome amigável"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Outros parametros"
#define D_TEMPLATE "Modelo"
#define D_ACTIVATE "Ativar"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Palavra Chave do Admin WEB"
#define D_MQTT_ENABLE "MQTT habilitado"
#define D_FRIENDLY_NAME "Nome amigável"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Alți paramatri"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activare"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Parolă Web Admin"
#define D_MQTT_ENABLE "Activare MQTT"
#define D_FRIENDLY_NAME "Friendly Name"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Параметры Прочие"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Пароль Web администратора"
#define D_MQTT_ENABLE "MQTT активен"
#define D_FRIENDLY_NAME "Дружественное Имя"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Ostatné nastavenia"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Heslo Web administrátora"
#define D_MQTT_ENABLE "MQTT aktívne"
#define D_FRIENDLY_NAME "Friendly Name"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Andra parametrar"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Webbadmin-lösenord"
#define D_MQTT_ENABLE "MQTT aktivera"
#define D_FRIENDLY_NAME "Läsbart namn"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Diğer parametreler"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Web Yönetici Şifresi"
#define D_MQTT_ENABLE "MQTT aktif"
#define D_FRIENDLY_NAME "Kullanıcı Dostu İsim"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "Параметри Інше"
#define D_TEMPLATE "Шаблони"
#define D_ACTIVATE "Активований"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "Гасло адміністратора Web"
#define D_MQTT_ENABLE "MQTT активний"
#define D_FRIENDLY_NAME "Дружня назва"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "其他设置"
#define D_TEMPLATE "模板"
#define D_ACTIVATE "启用"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "WEB 管理密码"
#define D_MQTT_ENABLE "启用MQTT"
#define D_FRIENDLY_NAME "昵称"

View File

@ -302,6 +302,7 @@
#define D_OTHER_PARAMETERS "其他設置"
#define D_TEMPLATE "Template"
#define D_ACTIVATE "Activate"
#define D_DEVICE_NAME "Device Name"
#define D_WEB_ADMIN_PASSWORD "WEB管理密碼"
#define D_MQTT_ENABLE "啟用MQTT"
#define D_FRIENDLY_NAME "昵稱"

View File

@ -397,7 +397,7 @@
// -- Rules or Script ----------------------------
// 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_COMPRESSION // Compresses rules in Flash at about ~50% (+3.8k code)
#define USE_RULES_COMPRESSION // Compresses rules in Flash at about ~50% (+3.3k code)
//#define USE_SCRIPT // Add support for script (+17k code)
//#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support
@ -699,6 +699,11 @@
#define THERMOSTAT_TIME_OUTPUT_DELAY 180 // Default output delay between state change and real actuation event (f.i. valve open/closed)
#define THERMOSTAT_TEMP_INIT 180 // Default init target temperature for the thermostat controller
#define THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST 3 // Default maximum time where the input and the outpus shall differ (for diagnostic) in minutes
#define THERMOSTAT_TIME_MAX_AUTOTUNE 21600 // Maximum time for the PI autotune function to complete in seconds
#define THERMOSTAT_DUTYCYCLE_AUTOTUNE 35 // Default duty cycle (in % over PI cycle time) for the step response of the autotune PI function
#define THERMOSTAT_PEAKNUMBER_AUTOTUNE 8 // Default number of peak temperatures (max or min) to be used for the autotune PI function
#define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected
#define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful
// -- End of general directives -------------------

View File

@ -357,9 +357,9 @@ void UpdateQuickPowerCycle(bool update)
#else // ESP32
QPCRead(&pc_register, sizeof(pc_register));
#endif // ESP8266 - ESP32
if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) {
uint32_t counter = ((pc_register & 0xF) << 1) & 0xF;
if (0 == counter) { // 4 power cycles in a row
if (update && ((pc_register & 0xFFFFFF80) == 0xFFA55A80)) {
uint32_t counter = ((pc_register & 0x7F) << 1) & 0x7F;
if (0 == counter) { // 7 power cycles in a row
SettingsErase(3); // Quickly reset all settings including QuickPowerCycle flag
EspRestart(); // And restart
} else {
@ -372,8 +372,8 @@ void UpdateQuickPowerCycle(bool update)
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter);
}
}
else if (pc_register != 0xFFA55ABF) {
pc_register = 0xFFA55ABF;
else if (pc_register != 0xFFA55AFF) {
pc_register = 0xFFA55AFF;
#ifdef ESP8266
// Assume flash is default all ones and setting a bit to zero does not need an erase
if (ESP.flashEraseSector(pc_location)) {
@ -754,6 +754,7 @@ void SettingsDefaultSet2(void)
SettingsUpdateText(SET_FRIENDLYNAME2, PSTR(FRIENDLY_NAME"2"));
SettingsUpdateText(SET_FRIENDLYNAME3, PSTR(FRIENDLY_NAME"3"));
SettingsUpdateText(SET_FRIENDLYNAME4, PSTR(FRIENDLY_NAME"4"));
SettingsUpdateText(SET_DEVICENAME, SettingsText(SET_FRIENDLYNAME1));
SettingsUpdateText(SET_OTAURL, PSTR(OTA_URL));
// Power
@ -1411,6 +1412,10 @@ void SettingsDelta(void)
if (Settings.rules[2][0] == 0) { Settings.rules[2][1] = 0; }
}
if (Settings.version < 0x08030002) {
SettingsUpdateText(SET_DEVICENAME, SettingsText(SET_FRIENDLYNAME1));
}
Settings.version = VERSION;
SettingsSave(1);
}

View File

@ -25,7 +25,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|"
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|"
D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|"
D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
D_CMND_DEVICENAME "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" D_CMND_HUMOFFSET "|"
D_CMND_SPEEDUNIT "|" D_CMND_GLOBAL_TEMP "|" D_CMND_GLOBAL_HUM "|"
#ifdef USE_I2C
@ -48,7 +48,7 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
&CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange,
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig,
&CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig,
&CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndDevicename, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset, &CmndHumOffset,
&CmndSpeedUnit, &CmndGlobalTemp, &CmndGlobalHum,
#ifdef USE_I2C
@ -395,11 +395,11 @@ void CmndStatus(void)
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%d" ), stemp2, (i > 0 ? "," : ""), Settings.switchmode[i]);
}
Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\""
Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_DEVICENAME "\":\"%s\",\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\""
D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\""
D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\""
D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"),
ModuleNr(), stemp, mqtt_topic,
ModuleNr(), SettingsText(SET_DEVICENAME), stemp, mqtt_topic,
SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate,
Settings.ledmask, Settings.save_data,
Settings.flag.save_state, // SetOption0 - Save power state and use after restart
@ -1488,6 +1488,14 @@ void CmndWifiConfig(void)
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, GetTextIndexed(stemp1, sizeof(stemp1), Settings.sta_config, kWifiConfig));
}
void CmndDevicename(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
SettingsUpdateText(SET_DEVICENAME, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? SettingsText(SET_FRIENDLYNAME1) : XdrvMailbox.data);
}
ResponseCmndChar(SettingsText(SET_DEVICENAME));
}
void CmndFriendlyname(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) {
@ -1600,8 +1608,9 @@ void CmndTeleperiod(void)
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.tele_period = (1 == XdrvMailbox.payload) ? TELE_PERIOD : XdrvMailbox.payload;
if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) Settings.tele_period = 10; // Do not allow periods < 10 seconds
tele_period = Settings.tele_period;
// tele_period = Settings.tele_period;
}
tele_period = Settings.tele_period; // Show teleperiod data also on empty command
ResponseCmndNumber(Settings.tele_period);
}

View File

@ -292,6 +292,7 @@ enum SettingsTextIndex { SET_OTAURL,
SET_MQTT_GRP_TOPIC2, SET_MQTT_GRP_TOPIC3, SET_MQTT_GRP_TOPIC4,
SET_TEMPLATE_NAME,
SET_DEV_GROUP_NAME1, SET_DEV_GROUP_NAME2, SET_DEV_GROUP_NAME3, SET_DEV_GROUP_NAME4,
SET_DEVICENAME,
SET_MAX };
enum DevGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYPE_UPDATE_COMMAND };

View File

@ -307,7 +307,7 @@ void setup(void) {
SetPowerOnState();
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE), PROJECT, SettingsText(SET_DEVICENAME), my_version, my_image);
#ifdef FIRMWARE_MINIMAL
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION));
#endif // FIRMWARE_MINIMAL

View File

@ -20,7 +20,7 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x08030000;
const uint32_t VERSION = 0x08030100;
// Lowest compatible version
const uint32_t VERSION_COMPATIBLE = 0x07010006;

View File

@ -455,6 +455,8 @@ const char HTTP_FORM_OTHER[] PROGMEM =
"<label><b>" D_WEB_ADMIN_PASSWORD "</b><input type='checkbox' onclick='sp(\"wp\")'></label><br><input id='wp' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERISK_PWD "'><br>"
"<br>"
"<label><input id='b1' type='checkbox'%s><b>" D_MQTT_ENABLE "</b></label><br>"
"<br>"
"<label><b>" D_DEVICE_NAME "</b> (%s)</label><br><input id='dn' placeholder='' value='%s'><br>"
"<br>";
const char HTTP_FORM_END[] PROGMEM =
@ -849,7 +851,7 @@ void WSContentStart_P(const char* title, bool auth)
WSContentBegin(200, CT_HTML);
if (title != nullptr) {
WSContentSend_P(HTTP_HEADER1, SettingsText(SET_FRIENDLYNAME1), title);
WSContentSend_P(HTTP_HEADER1, SettingsText(SET_DEVICENAME), title);
}
}
@ -893,7 +895,7 @@ void WSContentSendStyle_P(const char* formatP, ...)
WebColor(COL_TEXT_WARNING),
#endif
WebColor(COL_TITLE),
ModuleName().c_str(), SettingsText(SET_FRIENDLYNAME1));
ModuleName().c_str(), SettingsText(SET_DEVICENAME));
if (Settings.flag3.gui_hostname_ip) { // SetOption53 - Show hostanme and IP address in GUI main menu
bool lip = (static_cast<uint32_t>(WiFi.localIP()) != 0);
bool sip = (static_cast<uint32_t>(WiFi.softAPIP()) != 0);
@ -1992,7 +1994,9 @@ void HandleOtherConfiguration(void)
TemplateJson();
char stemp[strlen(mqtt_data) +1];
strlcpy(stemp, mqtt_data, sizeof(stemp)); // Get JSON template
WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : ""); // SetOption3 - Enable MQTT
WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "",
(Settings.flag.mqtt_enabled) ? " checked" : "", // SetOption3 - Enable MQTT
SettingsText(SET_FRIENDLYNAME1), SettingsText(SET_DEVICENAME));
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present;
#ifdef USE_SONOFF_IFAN
@ -2042,6 +2046,8 @@ void OtherSaveSettings(void)
char friendlyname[TOPSZ];
char message[LOGSZ];
WebGetArg("dn", tmp, sizeof(tmp));
SettingsUpdateText(SET_DEVICENAME, (!strlen(tmp)) ? "" : (!strcmp(tmp,"1")) ? SettingsText(SET_FRIENDLYNAME1) : tmp);
WebGetArg("wp", tmp, sizeof(tmp));
SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp);
Settings.flag.mqtt_enabled = Webserver->hasArg("b1"); // SetOption3 - Enable MQTT
@ -2053,7 +2059,8 @@ void OtherSaveSettings(void)
#endif // USE_EMULATION_WEMO || USE_EMULATION_HUE
#endif // USE_EMULATION
snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation);
snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_DEVICENAME " %s, " D_CMND_FRIENDLYNAME),
GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation, SettingsText(SET_DEVICENAME));
for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) {
snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i);
WebGetArg(webindex, tmp, sizeof(tmp));

View File

@ -836,28 +836,44 @@ void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *
#define POW FastPrecisePowf
//
// Matrix 3x3 multiplied to a 3 vector, result in a 3 vector
//
void mat3x3(const float *mat33, const float *vec3, float *res3) {
for (uint32_t i = 0; i < 3; i++) {
const float * v = vec3;
*res3 = 0.0f;
for (uint32_t j = 0; j < 3; j++) {
*res3 += *mat33++ * *v++;
}
res3++;
}
}
void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) {
float x = 0.31271f; // default medium white
float y = 0.32902f;
if (i_r + i_b + i_g > 0) {
float r = (float)i_r / 255.0f;
float g = (float)i_g / 255.0f;
float b = (float)i_b / 255.0f;
float rgb[3] = { (float)i_r, (float)i_g, (float)i_b };
// https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d
// Gamma correction
r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f);
g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f);
b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f);
for (uint32_t i = 0; i < 3; i++) {
rgb[i] = rgb[i] / 255.0f;
rgb[i] = (rgb[i] > 0.04045f) ? POW((rgb[i] + 0.055f) / (1.0f + 0.055f), 2.4f) : (rgb[i] / 12.92f);
}
// conversion to X, Y, Z
// Y is also the Luminance
float X = r * 0.649926f + g * 0.103455f + b * 0.197109f;
float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f;
float Z = r * 0.000000f + g * 0.053077f + b * 1.035763f;
float XYZ[3];
static const float XYZ_factors[] = { 0.649926f, 0.103455f, 0.197109f,
0.234327f, 0.743075f, 0.022598f,
0.000000f, 0.053077f, 1.035763f };
mat3x3(XYZ_factors, rgb, XYZ);
x = X / (X + Y + Z);
y = Y / (X + Y + Z);
float XYZ_sum = XYZ[0] + XYZ[1] + XYZ[2];
x = XYZ[0] / XYZ_sum;
y = XYZ[1] / XYZ_sum;
// we keep the raw gamut, one nice thing could be to convert to a narrower gamut
}
if (r_x) *r_x = x;
@ -866,36 +882,33 @@ void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x,
void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb)
{
float XYZ[3], rgb[3];
x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x));
y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y));
float z = 1.0f - x - y;
//float Y = 1.0f;
float X = x / y;
float Z = z / y;
// float r = X * 1.4628067f - 0.1840623f - Z * 0.2743606f;
// float g = -X * 0.5217933f + 1.4472381f + Z * 0.0677227f;
// float b = X * 0.0349342f - 0.0968930f + Z * 1.2884099f;
float r = X * 3.2406f - 1.5372f - Z * 0.4986f;
float g = -X * 0.9689f + 1.8758f + Z * 0.0415f;
float b = X * 0.0557f - 0.2040f + Z * 1.0570f;
float max = (r > g && r > b) ? r : (g > b) ? g : b;
r = r / max; // normalize to max == 1.0
g = g / max;
b = b / max;
r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f;
g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f;
b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f;
//
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, "XyToRgb XZ (%s %s) rgb (%s %s %s)",
// String(X,5).c_str(), String(Z,5).c_str(),
// String(r,5).c_str(), String(g,5).c_str(),String(b,5).c_str());
XYZ[0] = x / y;
XYZ[1] = 1.0f;
XYZ[2] = z / y;
int32_t ir = r * 255.0f + 0.5f;
int32_t ig = g * 255.0f + 0.5f;
int32_t ib = b * 255.0f + 0.5f;
if (rr) { *rr = (ir > 255 ? 255: (ir < 0 ? 0 : ir)); }
if (rg) { *rg = (ig > 255 ? 255: (ig < 0 ? 0 : ig)); }
if (rb) { *rb = (ib > 255 ? 255: (ib < 0 ? 0 : ib)); }
static const float rgb_factors[] = { 3.2406f, -1.5372f, -0.4986f,
-0.9689f, 1.8758f, 0.0415f,
0.0557f, -0.2040f, 1.0570f };
mat3x3(rgb_factors, XYZ, rgb);
float max = (rgb[0] > rgb[1] && rgb[0] > rgb[2]) ? rgb[0] : (rgb[1] > rgb[2]) ? rgb[1] : rgb[2];
for (uint32_t i = 0; i < 3; i++) {
rgb[i] = rgb[i] / max; // normalize to max == 1.0
rgb[i] = (rgb[i] <= 0.0031308f) ? 12.92f * rgb[i] : 1.055f * POW(rgb[i], (1.0f / 2.4f)) - 0.055f; // gamma
}
int32_t irgb[3];
for (uint32_t i = 0; i < 3; i++) {
irgb[i] = rgb[i] * 255.0f + 0.5f;
}
if (rr) { *rr = (irgb[0] > 255 ? 255: (irgb[0] < 0 ? 0 : irgb[0])); }
if (rg) { *rg = (irgb[1] > 255 ? 255: (irgb[1] < 0 ? 0 : irgb[1])); }
if (rb) { *rb = (irgb[2] > 255 ? 255: (irgb[2] < 0 ? 0 : irgb[2])); }
}
class LightControllerClass {

View File

@ -213,6 +213,7 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#ifdef USE_RULES_COMPRESSION
// Statically allocate one String per rule
String k_rules[MAX_RULE_SETS] = { String(), String(), String() }; // Strings are created empty
Unishox compressor; // singleton
#endif // USE_RULES_COMPRESSION
// Returns whether the rule is uncompressed, which means the first byte is not NULL
@ -256,6 +257,7 @@ size_t GetRuleLenStorage(uint32_t idx) {
#endif
}
#ifdef USE_RULES_COMPRESSION
// 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
@ -268,12 +270,13 @@ void GetRule_decompress(String &rule, const char *rule_head) {
rule.reserve(buf_len);
char* buf = rule.begin();
int32_t len_decompressed = unishox_decompress(rule_head, strlen(rule_head), buf, buf_len);
int32_t len_decompressed = compressor.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)
}
#endif // USE_RULES_COMPRESSION
//
// Read rule in memory, uncompress if needed
@ -308,7 +311,7 @@ String GetRule(uint32_t idx) {
// 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);
len_compressed = compressor.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
@ -357,7 +360,7 @@ int32_t SetRule(uint32_t idx, const char *content, bool append = false) {
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);
len_compressed = compressor.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));
}

View File

@ -641,13 +641,14 @@ char *script;
#ifdef USE_LIGHT
#ifdef USE_WS2812
void ws2812_set_array(float *array ,uint8_t len) {
void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) {
Ws2812ForceSuspend();
for (uint8_t cnt=0;cnt<len;cnt++) {
if (cnt>Settings.light_pixels) break;
for (uint32_t cnt=0;cnt<len;cnt++) {
uint32_t index=cnt+offset;
if (index>Settings.light_pixels) break;
uint32_t col=array[cnt];
Ws2812SetColor(cnt+1,col>>16,col>>8,col,0);
Ws2812SetColor(index+1,col>>16,col>>8,col,0);
}
Ws2812ForceUpdate();
}
@ -2444,7 +2445,7 @@ char *ForceStringVar(char *lp,char *dstr) {
}
// replace vars in cmd %var%
void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
void Replace_Cmd_Vars(char *srcbuf,uint32_t srcsize, char *dstbuf,uint32_t dstsize) {
char *cp;
uint16_t count;
uint8_t vtype;
@ -2455,6 +2456,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
char string[SCRIPT_MAXSSIZE];
dstsize-=2;
for (count=0;count<dstsize;count++) {
if (srcsize && (*cp==SCRIPT_EOL)) break;
if (*cp=='%') {
cp++;
if (*cp=='%') {
@ -2869,7 +2871,11 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
lp=GetNumericResult(lp,OPER_EQU,&cv_inc,0);
//SCRIPT_SKIP_EOL
cv_ptr=lp;
if (*cv_count<cv_max) {
floop=1;
} else {
floop=2;
}
} else {
// error
toLogEOL("for error",lp);
@ -2877,12 +2883,21 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
} else if (!strncmp(lp,"next",4) && floop>0) {
// for next loop
*cv_count+=cv_inc;
if (floop==1) {
if (*cv_count<=cv_max) {
lp=cv_ptr;
} else {
lp+=4;
floop=0;
}
} else {
if (*cv_count>=cv_max) {
lp=cv_ptr;
} else {
lp+=4;
floop=0;
}
}
}
if (!strncmp(lp,"switch",6)) {
@ -3010,6 +3025,12 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
lp+=7;
lp=isvar(lp,&vtype,&ind,0,0,0);
if (vtype!=VAR_NV) {
SCRIPT_SKIP_SPACES
if (*lp!=')') {
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
} else {
fvar=0;
}
// found variable as result
uint8_t index=glob_script_mem.type[ind.index].index;
if ((vtype&STYPE)==0) {
@ -3018,7 +3039,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
uint8_t len=0;
float *fa=Get_MFAddr(index,&len);
//Serial.printf(">> 2 %d\n",(uint32_t)*fa);
if (fa && len) ws2812_set_array(fa,len);
if (fa && len) ws2812_set_array(fa,len,fvar);
}
}
}
@ -3069,7 +3090,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
//AddLog_P(LOG_LEVEL_INFO, tmp);
// replace vars in cmd
char *tmp=cmdmem+SCRIPT_CMDMEM/2;
Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2);
Replace_Cmd_Vars(cmd,0,tmp,SCRIPT_CMDMEM/2);
//toSLog(tmp);
if (!strncmp(tmp,"print",5) || pflg) {
@ -4198,7 +4219,7 @@ void Script_Check_Hue(String *response) {
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
// check for hue defintions
// NAME, TYPE , vars
cp=tmp;
@ -5043,7 +5064,7 @@ void ScriptWebShow(char mc) {
WSContentSend_PD(SCRIPT_MSG_NUMINP,label,minstr,maxstr,stepstr,vstr,vname);
} else {
Replace_Cmd_Vars(lin,tmp,sizeof(tmp));
Replace_Cmd_Vars(lin,0,tmp,sizeof(tmp));
if (optflg) {
WSContentSend_PD(PSTR("<div>%s</div>"),tmp);
} else {
@ -5052,7 +5073,7 @@ void ScriptWebShow(char mc) {
}
} else {
if (*lin==mc) {
Replace_Cmd_Vars(lin+1,tmp,sizeof(tmp));
Replace_Cmd_Vars(lin+1,0,tmp,sizeof(tmp));
WSContentSend_PD(PSTR("%s"),tmp);
}
}
@ -5097,7 +5118,7 @@ uint8_t msect=Run_Scripter(">m",-2,0);
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
//client->println(tmp);
func(tmp);
}
@ -5142,7 +5163,7 @@ void ScriptJsonAppend(void) {
}
cp++;
}
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
ResponseAppend_P(PSTR("%s"),tmp);
}
if (*lp==SCRIPT_EOL) {

View File

@ -233,7 +233,7 @@ void HAssAnnounceRelayLight(void)
} else {
if (Settings.flag.hass_discovery && (RelayX || (Light.device > 0) && (max_lights > 0)) && !err_flag )
{ // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59)
char name[33 + 2]; // friendlyname(33) + " " + index
char name[TOPSZ]; // friendlyname(33) + " " + index
char value_template[33];
char prefix[TOPSZ];
char *command_topic = stemp1;
@ -241,9 +241,9 @@ void HAssAnnounceRelayLight(void)
char *availability_topic = stemp3;
if (i > MAX_FRIENDLYNAMES) {
snprintf_P(name, sizeof(name), PSTR("%s %d"), SettingsText(SET_FRIENDLYNAME1), i-1);
snprintf_P(name, sizeof(name), PSTR("%s %s %d"), SettingsText(SET_DEVICENAME), SettingsText(SET_FRIENDLYNAME1), i-1);
} else {
snprintf_P(name, sizeof(name), SettingsText(SET_FRIENDLYNAME1 + i-1));
snprintf_P(name, sizeof(name), PSTR ("%s %s"), SettingsText(SET_DEVICENAME), SettingsText(SET_FRIENDLYNAME1 + i-1));
}
GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1
@ -339,7 +339,7 @@ void HAssAnnouncerTriggers(uint8_t device, uint8_t present, uint8_t key, uint8_t
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/device_automation/%s/config"), unique_id);
if (Settings.flag.hass_discovery && present) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59)
char name[33 + 6]; // friendlyname(33) + " " + "BTN" + " " + index
char name[TOPSZ]; // friendlyname(33) + " " + "BTN" + " " + index
char value_template[33];
char prefix[TOPSZ];
char *state_topic = stemp1;
@ -391,7 +391,7 @@ void HAssAnnouncerBinSensors(uint8_t device, uint8_t present, uint8_t dual, uint
if (Settings.flag.hass_discovery && present ) { // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59)
if (!toggle || dual) {
char name[33 + 6]; // friendlyname(33) + " " + "BTN" + " " + index
char name[TOPSZ]; // friendlyname(33) + " " + "BTN" + " " + index
char value_template[33];
char prefix[TOPSZ];
char *state_topic = stemp1;
@ -403,7 +403,7 @@ void HAssAnnouncerBinSensors(uint8_t device, uint8_t present, uint8_t dual, uint
GetTopic_P(state_topic, STAT, mqtt_topic, jsoname);
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
snprintf_P(name, sizeof(name), PSTR("%s Switch%d"), ModuleName().c_str(), device + 1);
snprintf_P(name, sizeof(name), PSTR("%s Switch%d"), SettingsText(SET_DEVICENAME), device + 1);
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
if (!pir) {
TryResponseAppend_P(HASS_DISCOVER_BIN_SWITCH, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT2), SettingsText(SET_STATE_TXT1));
@ -553,13 +553,13 @@ void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const
if (Settings.flag.hass_discovery)
{ // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59)
char name[33 + 42]; // friendlyname(33) + " " + sensorname(20?) + " " + sensortype(20?)
char name[TOPSZ]; // friendlyname(33) + " " + sensorname(20?) + " " + sensortype(20?)
char prefix[TOPSZ];
char *state_topic = stemp1;
char *availability_topic = stemp2;
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR));
snprintf_P(name, sizeof(name), PSTR("%s %s %s"), ModuleName().c_str(), sensorname, MultiSubName);
snprintf_P(name, sizeof(name), PSTR("%s %s %s"), SettingsText(SET_DEVICENAME), sensorname, MultiSubName);
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
@ -694,18 +694,18 @@ void HAssAnnounceDeviceInfoAndStatusSensor(void)
if (Settings.flag.hass_discovery)
{ // SetOption19 - Control Home Assistantautomatic discovery (See SetOption59)
char name[33 + 7]; // friendlyname(33) + " " + "status"
char name[TOPSZ]; // friendlyname(33) + " " + "status"
char prefix[TOPSZ];
char *state_topic = stemp1;
char *availability_topic = stemp2;
snprintf_P(name, sizeof(name), PSTR("%s status"), ModuleName().c_str());
snprintf_P(name, sizeof(name), PSTR("%s status"), SettingsText(SET_DEVICENAME));
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE));
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
TryResponseAppend_P(HASS_DISCOVER_SENSOR_HASS_STATUS, state_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP_getChipId(), ModuleName().c_str(),
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP_getChipId(), SettingsText(SET_DEVICENAME),
ModuleName().c_str(), my_version, my_image);
TryResponseAppend_P(PSTR("}"));
}

View File

@ -24,6 +24,8 @@
typedef uint64_t Z_IEEEAddress;
typedef uint16_t Z_ShortAddress;
const uint16_t BAD_SHORTADDR = 0xFFFE;
enum ZnpCommandType {
Z_POLL = 0x00,
Z_SREQ = 0x20,

View File

@ -103,7 +103,7 @@ public:
// Probe the existence of device keys
// Results:
// - 0x0000 = not found
// - 0xFFFF = bad parameter
// - BAD_SHORTADDR = bad parameter
// - 0x<shortaddr> = the device's short address
uint16_t isKnownShortAddr(uint16_t shortaddr) const;
uint16_t isKnownLongAddr(uint64_t longaddr) const;
@ -295,19 +295,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
// Scan all devices to find a corresponding shortaddr
// Looks info device.shortaddr entry
// In:
// shortaddr (non null)
// shortaddr (not BAD_SHORTADDR)
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const {
if (!shortaddr) { return -1; } // does not make sense to look for 0x0000 shortaddr (localhost)
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
int32_t found = 0;
if (shortaddr) {
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return found; }
found++;
}
}
return -1;
}
//
@ -321,12 +319,10 @@ int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const {
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return -1; }
int32_t found = 0;
if (longaddr) {
for (auto &elem : _devices) {
if (elem->longaddr == longaddr) { return found; }
found++;
}
}
return -1;
}
//
@ -358,7 +354,7 @@ uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const {
if (found >= 0) {
return shortaddr;
} else {
return 0; // unknown
return BAD_SHORTADDR; // unknown
}
}
@ -368,7 +364,7 @@ uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
const Z_Device & device = devicesAt(found);
return device.shortaddr; // can be zero, if not yet registered
} else {
return 0;
return BAD_SHORTADDR;
}
}
@ -377,18 +373,18 @@ uint16_t Z_Devices::isKnownIndex(uint32_t index) const {
const Z_Device & device = devicesAt(index);
return device.shortaddr;
} else {
return 0;
return BAD_SHORTADDR;
}
}
uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
if ((!name) || (0 == strlen(name))) { return 0xFFFF; } // Error
if ((!name) || (0 == strlen(name))) { return BAD_SHORTADDR; } // Error
int32_t found = findFriendlyName(name);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
return device.shortaddr; // can be zero, if not yet registered
} else {
return 0;
return BAD_SHORTADDR;
}
}
@ -398,10 +394,10 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
}
//
// We have a seen a shortaddr on the network, get the corresponding
// We have a seen a shortaddr on the network, get the corresponding device object
//
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal
if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
return *(_devices[found]);
@ -411,7 +407,7 @@ Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
}
// Same version but Const
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
if (!shortaddr) { return *(Z_Device*) nullptr; } // this is not legal
if (BAD_SHORTADDR == shortaddr) { return *(Z_Device*) nullptr; } // this is not legal
int32_t found = findShortAddr(shortaddr);
if (found >= 0) {
return *(_devices[found]);
@ -471,7 +467,7 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
dirty();
} else {
// neither short/lonf addr are found.
if (shortaddr || longaddr) {
if ((BAD_SHORTADDR != shortaddr) || longaddr) {
createDeviceEntry(shortaddr, longaddr);
}
}
@ -481,7 +477,6 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
// Clear all endpoints
//
void Z_Devices::clearEndpoints(uint16_t shortaddr) {
if (!shortaddr) { return; }
Z_Device &device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
@ -495,7 +490,6 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) {
// Add an endpoint to a shortaddr
//
void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
if (!shortaddr) { return; }
if (0x00 == endpoint) { return; }
Z_Device &device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
@ -922,7 +916,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know
char dataBuf[param_len + 1];
strcpy(dataBuf, param);
RemoveSpace(dataBuf);
uint16_t shortaddr = 0;
uint16_t shortaddr = BAD_SHORTADDR; // start with unknown
if (strlen(dataBuf) < 4) {
// simple number 0..99
@ -1018,8 +1012,8 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
uint16_t shortaddr = device.shortaddr;
char hex[22];
// ignore non-current device, if specified device is non-zero
if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; }
// ignore non-current device, if device specified
if ((BAD_SHORTADDR != status_shortaddr) && (status_shortaddr != shortaddr)) { continue; }
JsonObject& dev = devices.createNestedObject();

View File

@ -66,41 +66,6 @@ public:
const static uint32_t ZIGB_NAME = 0x3167697A; // 'zig1' little endian
const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); // 2040
// encoding for the most commonly 32 clusters, used for binary encoding
const uint16_t Z_ClusterNumber[] PROGMEM = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0100, 0x0101, 0x0102,
0x0201, 0x0202, 0x0203, 0x0204,
0x0300, 0x0301,
0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406,
0x0500, 0x0501, 0x0502,
0x0700, 0x0701, 0x0702,
0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05,
0x1000,
0xFC0F,
};
// convert a 1 byte cluster code to the actual cluster number
uint16_t fromClusterCode(uint8_t c) {
if (c >= ARRAY_SIZE(Z_ClusterNumber)) {
return 0xFFFF; // invalid
}
return pgm_read_word(&Z_ClusterNumber[c]);
}
// convert a cluster number to 1 byte, or 0xFF if not in table
uint8_t toClusterCode(uint16_t c) {
for (uint32_t i = 0; i < ARRAY_SIZE(Z_ClusterNumber); i++) {
if (c == pgm_read_word(&Z_ClusterNumber[i])) {
return i;
}
}
return 0xFF; // not found
}
class SBuffer hibernateDevice(const struct Z_Device &device) {
SBuffer buf(128);
@ -202,18 +167,8 @@ void hydrateDevices(const SBuffer &buf) {
for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) {
uint32_t dev_record_len = buf.get8(k);
// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Device %d Before Memory = %d // DIFF %d // record_len %d"), i, ESP_getFreeHeap(), before - ESP_getFreeHeap(), dev_record_len);
// before = ESP_getFreeHeap();
SBuffer buf_d = buf.subBuffer(k, dev_record_len);
// char *hex_char = (char*) malloc((dev_record_len * 2) + 2);
// if (hex_char) {
// AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "/// SUB %s"),
// ToHex_P(buf_d.getBuffer(), dev_record_len, hex_char, (dev_record_len * 2) + 2));
// free(hex_char);
// }
uint32_t d = 1; // index in device buffer
uint16_t shortaddr = buf_d.get16(d); d += 2;
uint64_t longaddr = buf_d.get64(d); d += 8;

View File

@ -79,16 +79,6 @@ uint8_t Z_getDatatypeLen(uint8_t t) {
}
}
// typedef struct Z_DataTypeMapping {
// uint8_t datatype;
// uint8_t len; // len in bytes and add 0x80 if DISCRETE
// }
// const Z_DataTypeMapping Z_types[] PROGMEM = {
// { Znodata, 0 },
// { Zdata8, 0 },
// };
typedef union ZCLHeaderFrameControl_t {
struct {
uint8_t frame_type : 2; // 00 = across entire profile, 01 = cluster specific
@ -572,6 +562,9 @@ typedef struct Z_AttributeConverter {
Z_AttrConverter func;
} Z_AttributeConverter;
// Cluster numbers are store in 8 bits format to save space,
// the following tables allows the conversion from 8 bits index Cx...
// to the 16 bits actual cluster number
enum Cx_cluster_short {
Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007,
Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,

View File

@ -177,6 +177,9 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
break;
}
if (attrs) {
if (groupaddr) {
shortaddr = BAD_SHORTADDR; // if group address, don't send to device
}
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
}
}
@ -184,7 +187,7 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
// This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms
int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
if (shortaddr) {
if (BAD_SHORTADDR != shortaddr) {
zigbee_devices.setReachable(shortaddr, false); // mark device as reachable
}
}
@ -208,7 +211,7 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
@ -314,12 +317,12 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
}
if (z_cat >= 0) {
uint8_t endpoint = 0;
if (shortaddr) {
if (BAD_SHORTADDR != shortaddr) {
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
}
if ((!shortaddr) || (endpoint)) { // send if group address or endpoint is known
if ((BAD_SHORTADDR == shortaddr) || (endpoint)) { // send if group address or endpoint is known
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback);
if (shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}

View File

@ -584,18 +584,6 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_WAIT_RECV(1000, ZBS_LOGTYPE_DEVICE) // it should be coordinator
ZI_GOTO(ZIGBEE_LABEL_START_ROUTER)
// Device and Router code is common from now
// ZI_LABEL(ZIGBEE_LABEL_START_DEVICE) // Init as a router
// ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredDevice)
// ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
// ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation
// ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
// ZI_SEND(ZBS_STARTUPFROMAPP) // start router
// ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command
// ZI_WAIT_UNTIL_FUNC(0xFFFF, AREQ_STARTUPFROMAPP, &Z_ReceiveStateChange) // wait forever for async message that coordinator started
// ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo
// ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
// ZI_GOTO(ZIGBEE_LABEL_READY)
ZI_LABEL(ZIGBEE_LABEL_FACT_RESET_DEVICE) // Factory reset for router
ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting)
@ -608,25 +596,12 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_WAIT_RECV(1000, ZBR_W_OK)
ZI_GOTO(ZIGBEE_LABEL_FACT_RESET_ROUTER_DEVICE_POST)
// ZI_SEND(ZBS_W_ALL_PAN) // write universal PAN ID = 0xFFFF
// ZI_WAIT_RECV(1000, ZBR_W_OK)
// ZI_SEND(ZBS_W_ALL_CHANN) // write Allows all CHANNELS = 0x07FFF800, 11-26
// ZI_WAIT_RECV(1000, ZBR_W_OK)
// // Now mark the device as ready, writing 0x55 in memory slot 0x0F00
// ZI_SEND(ZBS_WNV_INITZNPHC) // Init NV ZNP Has Configured
// ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
// ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured
// ZI_WAIT_RECV(1000, ZBR_WNV_OK)
// ZI_GOTO(ZIGBEE_LABEL_START_ROUTER)
// ZI_GOTO(ZIGBEE_LABEL_START_DEVICE)
// Error: version of Z-Stack is not supported
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
ZI_MQTT_STATE(ZIGBEE_STATUS_UNSUPPORTED_VERSION, kZNP12)
ZI_GOTO(ZIGBEE_LABEL_ABORT)
// Abort state machine, general error
ZI_LABEL(ZIGBEE_LABEL_ABORT) // Label 99: abort
ZI_MQTT_STATE(ZIGBEE_STATUS_ABORT, kAbort)
ZI_LOG(LOG_LEVEL_ERROR, kZigbeeAbort)

View File

@ -52,7 +52,7 @@ int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
device_associated);
if (device_associated > 0) {
if (device_associated > 0) { // If there are devices registered in CC2530, print the list
uint idx = 16;
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
for (uint32_t i = 0; i < device_associated; i++) {
@ -87,18 +87,20 @@ int32_t Z_Reboot(int32_t res, class SBuffer &buf) {
// print information about the reboot of device
// 4180.02.02.00.02.06.03
//
static const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
uint8_t reason = buf.get8(2);
uint8_t transport_rev = buf.get8(3);
uint8_t product_id = buf.get8(4);
uint8_t major_rel = buf.get8(5);
uint8_t minor_rel = buf.get8(6);
uint8_t hw_rev = buf.get8(7);
char reason_str[12];
const char *reason_str;
if (reason > 3) { reason = 3; }
GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason);
switch (reason) {
case 0: reason_str = PSTR("Power-up"); break;
case 1: reason_str = PSTR("External"); break;
case 2: reason_str = PSTR("Watchdog"); break;
default: reason_str = PSTR("Unknown"); break;
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Message\":\"CC2530 booted\",\"RestartReason\":\"%s\""
@ -201,7 +203,6 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
return -1;
}
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
// Received ZDO_NODE_DESC_RSP
Z_ShortAddress srcAddr = buf.get16(2);
@ -217,15 +218,22 @@ int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
uint16_t maxOutTransferSize = buf.get16(17);
uint8_t descriptorCapabilities = buf.get8(19);
if (0 == status) {
uint8_t deviceType = logicalType & 0x7; // 0=coordinator, 1=router, 2=end device
if (deviceType > 3) { deviceType = 3; }
const char * deviceTypeStr;
switch (deviceType) {
case 0: deviceTypeStr = PSTR("Coordinator"); break;
case 1: deviceTypeStr = PSTR("Router"); break;
case 2: deviceTypeStr = PSTR("Device"); break;
default: deviceTypeStr = PSTR("Unknown"); break;
}
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
complexDescriptorAvailable ? "true" : "false"
ZIGBEE_STATUS_NODE_DESC, deviceTypeStr,
complexDescriptorAvailable ? PSTR("true") : PSTR("false")
);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
@ -282,15 +290,13 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) {
Uint64toHex(ieeeAddr, hex, 64);
// Ping response
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""), nwkAddr, hex);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, hex, friendlyName);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""
"}}"), nwkAddr, hex);
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR("\"}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
@ -398,9 +404,9 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
"\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\""
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
(capabilities & 0x04) ? "true" : "false",
(capabilities & 0x08) ? "true" : "false",
(capabilities & 0x40) ? "true" : "false"
(capabilities & 0x04) ? PSTR("true") : PSTR("false"),
(capabilities & 0x08) ? PSTR("true") : PSTR("false"),
(capabilities & 0x40) ? PSTR("true") : PSTR("false")
);
// query the state of the bulb (for Alexa)
uint32_t wait_ms = 2000; // wait for 2s
@ -444,18 +450,15 @@ int32_t Z_BindRsp(int32_t res, const class SBuffer &buf) {
uint8_t status = buf.get8(4);
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_BIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str());
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), status, getZigbeeStatusMessage(status).c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
@ -470,18 +473,14 @@ int32_t Z_UnbindRsp(int32_t res, const class SBuffer &buf) {
uint8_t status = buf.get8(4);
const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), nwkAddr);
if (friendlyName) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, friendlyName, status, getZigbeeStatusMessage(status).c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_UNBIND "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""
",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), nwkAddr, status, getZigbeeStatusMessage(status).c_str());
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName);
}
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
"}}"), status, getZigbeeStatusMessage(status).c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
@ -506,7 +505,6 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_STATUS "\":%d"
",\"" D_JSON_ZIGBEE_STATUS_MSG "\":\"%s\""
",\"BindingsTotal\":%d"
//",\"BindingsStart\":%d"
",\"Bindings\":["
), status, getZigbeeStatusMessage(status).c_str(), bind_total);
@ -631,7 +629,7 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4);
Z_ShortAddress srcaddr = buf.get16(6);
uint16_t srcaddr = buf.get16(6);
uint8_t srcendpoint = buf.get8(8);
uint8_t dstendpoint = buf.get8(9);
uint8_t wasbroadcast = buf.get8(10);

View File

@ -147,7 +147,7 @@ void ZigbeeInputLoop(void)
// Initialize internal structures
void ZigbeeInit(void)
{
// Check if settings if Flash are set
// Check if settings in Flash are set
if (0 == Settings.zb_channel) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Initializing Zigbee parameters from defaults"));
Settings.zb_ext_panid = USE_ZIGBEE_EXTPANID;
@ -314,7 +314,7 @@ void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterI
SBuffer buf(32+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (0x0000 == shortaddr) { // if no shortaddr we assume group address
if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
@ -372,7 +372,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
}
}
if ((0 == endpoint) && (shortaddr)) {
if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) {
// endpoint is not specified, let's try to find it from shortAddr, unless it's a group address
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
@ -380,7 +380,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
shortaddr, groupaddr, cluster, endpoint, cmd, param);
if ((0 == endpoint) && (shortaddr)) { // endpoint null is ok for group address
if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { // endpoint null is ok for group address
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
return;
}
@ -415,7 +415,7 @@ void CmndZbSend(void) {
// params
static char delim[] = ", "; // delimiters for parameters
uint16_t device = 0x0000; // 0x0000 is local, so considered invalid
uint16_t device = BAD_SHORTADDR; // 0x0000 is local, so considered invalid
uint16_t groupaddr = 0x0000; // group address
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
uint16_t manuf = 0x0000; // Manuf Id in ZCL frame
@ -430,9 +430,9 @@ void CmndZbSend(void) {
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (0x0000 == device) { // if not found, check if we have a group
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
@ -571,8 +571,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
// params
// static char delim[] = ", "; // delimiters for parameters
uint16_t srcDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid
uint16_t dstDevice = 0xFFFF; // 0xFFFF is broadcast, so considered invalid
uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint64_t dstLongAddr = 0;
uint8_t endpoint = 0x00; // 0x00 is invalid for the src endpoint
uint8_t toendpoint = 0x00; // 0x00 is invalid for the dst endpoint
@ -585,9 +585,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
srcDevice = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == srcDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if ((nullptr == &val_device) || (0x0000 == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
if ((nullptr == &val_device) || (BAD_SHORTADDR == srcDevice)) { ResponseCmndChar_P(PSTR("Unknown source device")); return; }
// check if IEEE address is known
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice);
if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; }
@ -605,7 +604,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
const JsonVariant &dst_device = getCaseInsensitive(json, PSTR("ToDevice"));
if (nullptr != &dst_device) {
dstDevice = zigbee_devices.parseDeviceParam(dst_device.as<char*>());
if (0xFFFF == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == dstDevice) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (0x0000 == dstDevice) {
dstLongAddr = localIEEEAddr;
} else {
@ -622,8 +621,8 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (nullptr != &to_group) { toGroup = strToUInt(to_group); }
// make sure we don't have conflicting parameters
if (toGroup && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
if (!toGroup && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
if (&to_group && dstLongAddr) { ResponseCmndChar_P(PSTR("Cannot have both \"ToDevice\" and \"ToGroup\"")); return; }
if (!&to_group && !dstLongAddr) { ResponseCmndChar_P(PSTR("Missing \"ToDevice\" or \"ToGroup\"")); return; }
SBuffer buf(34);
buf.add8(Z_SREQ | Z_ZDO);
@ -670,8 +669,7 @@ void CmndZbUnbind(void) {
void CmndZbBindState(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
SBuffer buf(10);
buf.add8(Z_SREQ | Z_ZDO); // 25
@ -695,8 +693,7 @@ void CmndZbProbe(void) {
void CmndZbProbeOrPing(boolean probe) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
// everything is good, we can send the command
Z_SendIEEEAddrReq(shortaddr);
@ -731,8 +728,7 @@ void CmndZbName(void) {
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (p == nullptr) {
const char * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
@ -763,8 +759,7 @@ void CmndZbModelId(void) {
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (p == nullptr) {
const char * modelId = zigbee_devices.getModelId(shortaddr);
@ -793,8 +788,7 @@ void CmndZbLight(void) {
// parse first part, <device_id>
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (p) {
int8_t bulbtype = strtol(p, nullptr, 10);
@ -817,8 +811,7 @@ void CmndZbLight(void) {
void CmndZbForget(void) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
// everything is good, we can send the command
if (zigbee_devices.removeDevice(shortaddr)) {
@ -906,7 +899,7 @@ void CmndZbRead(void) {
if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
// params
uint16_t device = 0xFFFF; // 0xFFFF is braodcast, so considered valid
uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid
uint16_t groupaddr = 0x0000; // if 0x0000 ignore group adress
uint16_t cluster = 0x0000; // default to general cluster
uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint
@ -917,9 +910,9 @@ void CmndZbRead(void) {
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
if (nullptr != &val_device) {
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
if (0xFFFF == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
}
if (0x0000 == device) { // if not found, check if we have a group
if (BAD_SHORTADDR == device) { // if not found, check if we have a group
const JsonVariant &val_group = getCaseInsensitive(json, PSTR("Group"));
if (nullptr != &val_group) {
groupaddr = strToUInt(val_group);
@ -960,9 +953,9 @@ void CmndZbRead(void) {
if ((0 == endpoint) && (device)) { // try to compute the endpoint
endpoint = zigbee_devices.findFirstEndpoint(device);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbRead: guessing endpoint 0x%02X"), endpoint);
}
if (0x0000 == device) {
if (BAD_SHORTADDR == device) {
endpoint = 0xFF; // endpoint not used for group addresses
}
@ -1012,9 +1005,8 @@ void CmndZbStatus(void) {
if (ZigbeeSerial) {
if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; }
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
if (0xFFFF == shortaddr) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; }
if (XdrvMailbox.payload > 0) {
if (0x0000 == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; }
}
String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr);

View File

@ -24,6 +24,10 @@
// Enable/disable debugging
//#define DEBUG_THERMOSTAT
// Enable/disable experimental PI auto-tuning inspired by the Arduino Autotune Library by
// Brett Beauregard <br3ttb@gmail.com> brettbeauregard.com
//#define USE_PI_AUTOTUNING // (Ziegler-Nichols closed loop method)
#ifdef DEBUG_THERMOSTAT
#define DOMOTICZ_MAX_IDX 4
#define DOMOTICZ_IDX1 791
@ -56,6 +60,9 @@
#define D_CMND_TIMEPICYCLESET "TimePiCycleSet"
#define D_CMND_TEMPANTIWINDUPRESETSET "TempAntiWindupResetSet"
#define D_CMND_TEMPHYSTSET "TempHystSet"
#ifdef USE_PI_AUTOTUNING
#define D_CMND_PERFLEVELAUTOTUNE "PerfLevelAutotune"
#endif // USE_PI_AUTOTUNING
#define D_CMND_TIMEMAXACTIONSET "TimeMaxActionSet"
#define D_CMND_TIMEMINACTIONSET "TimeMinActionSet"
#define D_CMND_TIMEMINTURNOFFACTIONSET "TimeMinTurnoffActionSet"
@ -70,8 +77,15 @@
#define D_CMND_DIAGNOSTICMODESET "DiagnosticModeSet"
enum ThermostatModes { THERMOSTAT_OFF, THERMOSTAT_AUTOMATIC_OP, THERMOSTAT_MANUAL_OP, THERMOSTAT_MODES_MAX };
#ifdef USE_PI_AUTOTUNING
enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP, CTR_PI_AUTOTUNE, CTR_MODES_MAX };
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI, CTR_HYBRID_PI_AUTOTUNE };
enum AutotuneStates { AUTOTUNE_OFF, AUTOTUNE_ON, AUTOTUNE_MAX };
enum AutotunePerformanceParam { AUTOTUNE_PERF_FAST, AUTOTUNE_PERF_NORMAL, AUTOTUNE_PERF_SLOW, AUTOTUNE_PERF_MAX };
#else
enum ControllerModes { CTR_HYBRID, CTR_PI, CTR_RAMP_UP, CTR_MODES_MAX };
enum ControllerHybridPhases { CTR_HYBRID_RAMP_UP, CTR_HYBRID_PI };
#endif // USE_PI_AUTOTUNING
enum ClimateModes { CLIMATE_HEATING, CLIMATE_COOLING, CLIMATE_MODES_MAX };
enum InterfaceStates { IFACE_OFF, IFACE_ON };
enum InputUsage { INPUT_NOT_USED, INPUT_USED };
@ -113,17 +127,29 @@ typedef union {
uint32_t status_output : 1; // Flag stating state of the output (0 = inactive, 1 = active)
uint32_t status_input : 1; // Flag stating state of the input (0 = inactive, 1 = active)
uint32_t use_input : 1; // Flag stating if the input switch shall be used to switch to manual mode
uint32_t phase_hybrid_ctr : 1; // Phase of the hybrid controller (Ramp-up or PI)
uint32_t phase_hybrid_ctr : 2; // Phase of the hybrid controller (Ramp-up, PI or Autotune)
uint32_t status_cycle_active : 1; // Status showing if cycle is active (Output ON) or not (Output OFF)
uint32_t state_emergency : 1; // State for thermostat emergency
uint32_t counter_seconds : 6; // Second counter used to track minutes
uint32_t output_relay_number : 4; // Output relay number
uint32_t input_switch_number : 3; // Input switch number
uint32_t output_inconsist_ctr : 2; // Counter of the minutes where the output state is inconsistent with the command
uint32_t diagnostic_mode : 1; // Diagnostic mode selected
uint32_t free : 1; // Free bits in Bitfield
#ifdef USE_PI_AUTOTUNING
uint32_t autotune_flag : 1; // Enable/disable autotune
uint32_t autotune_perf_mode : 2; // Autotune performance mode
uint32_t free : 1; // Free bits
#else
uint32_t free : 4; // Free bits
#endif // USE_PI_AUTOTUNING
};
} ThermostatBitfield;
} ThermostatStateBitfield;
typedef union {
uint8_t data;
struct {
uint8_t state_emergency : 1; // State for thermostat emergency
uint8_t diagnostic_mode : 1; // Diagnostic mode selected
uint8_t output_inconsist_ctr : 2; // Counter of the minutes where the output state is inconsistent with the command
};
} ThermostatDiagBitfield;
#ifdef DEBUG_THERMOSTAT
const char DOMOTICZ_MES[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\"}";
@ -135,28 +161,36 @@ const char kThermostatCommands[] PROGMEM = "|" D_CMND_THERMOSTATMODESET "|" D_CM
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_TIMEMANUALTOAUTOSET "|" D_CMND_PROPBANDSET "|" D_CMND_TIMERESETSET "|" D_CMND_TIMEPICYCLESET "|"
D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|" D_CMND_TIMEMINACTIONSET "|"
D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|" D_CMND_TIMERAMPUPMAXSET "|"
D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|" D_CMND_TIMEPIINTEGRREAD "|"
D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET;
#ifdef USE_PI_AUTOTUNING
D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_PERFLEVELAUTOTUNE "|" D_CMND_TIMEMAXACTIONSET "|"
#else
D_CMND_TEMPANTIWINDUPRESETSET "|" D_CMND_TEMPHYSTSET "|" D_CMND_TIMEMAXACTIONSET "|"
#endif // USE_PI_AUTOTUNING
D_CMND_TIMEMINACTIONSET "|" D_CMND_TIMEMINTURNOFFACTIONSET "|" D_CMND_TEMPRUPDELTINSET "|" D_CMND_TEMPRUPDELTOUTSET "|"
D_CMND_TIMERAMPUPMAXSET "|" D_CMND_TIMERAMPUPCYCLESET "|" D_CMND_TEMPRAMPUPPIACCERRSET "|" D_CMND_TIMEPIPROPORTREAD "|"
D_CMND_TIMEPIINTEGRREAD "|" D_CMND_TIMESENSLOSTSET "|" D_CMND_DIAGNOSTICMODESET;
void (* const ThermostatCommand[])(void) PROGMEM = {
&CmndThermostatModeSet, &CmndClimateModeSet, &CmndTempFrostProtectSet, &CmndControllerModeSet, &CmndInputSwitchSet,
&CmndInputSwitchUse, &CmndOutputRelaySet, &CmndTimeAllowRampupSet, &CmndTempFormatSet, &CmndTempMeasuredSet,
&CmndTempTargetSet, &CmndTempMeasuredGrdRead, &CmndSensorInputSet, &CmndStateEmergencySet, &CmndTimeManualToAutoSet,
&CmndPropBandSet, &CmndTimeResetSet, &CmndTimePiCycleSet, &CmndTempAntiWindupResetSet, &CmndTempHystSet,
#ifdef USE_PI_AUTOTUNING
&CmndPerfLevelAutotune, &CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet,
#else
&CmndTimeMaxActionSet, &CmndTimeMinActionSet, &CmndTimeMinTurnoffActionSet, &CmndTempRupDeltInSet,
#endif // USE_PI_AUTOTUNING
&CmndTempRupDeltOutSet, &CmndTimeRampupMaxSet, &CmndTimeRampupCycleSet, &CmndTempRampupPiAccErrSet,
&CmndTimePiProportRead, &CmndTimePiIntegrRead, &CmndTimeSensLostSet, &CmndDiagnosticModeSet };
struct THERMOSTAT {
ThermostatBitfield status; // Bittfield including states as well as several flags
ThermostatStateBitfield status; // Bittfield including states as well as several flags
uint32_t timestamp_temp_measured_update = 0; // Timestamp of latest measurement update
uint32_t timestamp_temp_meas_change_update = 0; // Timestamp of latest measurement value change (> or < to previous)
uint32_t timestamp_output_off = 0; // Timestamp of latest thermostat output Off state
uint32_t timestamp_input_on = 0; // Timestamp of latest input On state
uint32_t time_thermostat_total = 0; // Time thermostat on within a specific timeframe
uint32_t time_ctr_checkpoint = 0; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup
uint32_t time_ctr_checkpoint = 0; // Time to finalize the control cycle within the PI strategy or to switch to PI from Rampup in seconds
uint32_t time_ctr_changepoint = 0; // Time until switching off output within the controller in seconds
int32_t temp_measured_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees per hour
int16_t temp_target_level = THERMOSTAT_TEMP_INIT; // Target level of the thermostat in tenths of degrees
@ -167,7 +201,7 @@ struct THERMOSTAT {
int32_t time_integral_pi; // Time integral part of the PI controller
int32_t time_total_pi; // Time total (proportional + integral) of the PI controller
uint16_t kP_pi = 0; // kP value for the PI controller multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi = 0; // kP value for the PI controller multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi = 0; // kI value for the PI controller multiplied by 100 (to avoid floating point operations)
int32_t temp_rampup_meas_gradient = 0; // Temperature measured gradient from sensor in thousandths of degrees celsius per hour calculated during ramp-up
uint32_t timestamp_rampup_start = 0; // Timestamp where the ramp-up controller mode has been started
uint32_t time_rampup_deadtime = 0; // Time constant of the thermostat system (step response time)
@ -195,6 +229,23 @@ struct THERMOSTAT {
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
uint8_t temp_frost_protect = THERMOSTAT_TEMP_FROST_PROTECT; // Minimum temperature for frost protection, in tenths of degrees celsius
ThermostatDiagBitfield diag; // Bittfield including diagnostic flags
#ifdef USE_PI_AUTOTUNING
uint8_t dutycycle_step_autotune = THERMOSTAT_DUTYCYCLE_AUTOTUNE; // Duty cycle for the step response of the autotune PI function in %
uint8_t peak_ctr = 0; // Peak counter for the autotuning function
uint8_t temp_band_no_peak_det = THERMOSTAT_TEMP_BAND_NO_PEAK_DET; // Temperature band in thenths of degrees celsius within no peak will be detected
uint8_t val_prop_band_atune = 0; // Proportional band calculated from the the PI autotune function in degrees celsius
uint32_t time_reset_atune = 0; // Reset time calculated from the PI autotune function in seconds
uint16_t pU_pi_atune = 0; // pU value ("Ultimate" period) period of self-sustaining oscillations determined when the controller gain was set to Ku in minutes (for PI autotune)
uint16_t kU_pi_atune = 0; // kU value ("Ultimate" gain) determined by increasing controller gain until self-sustaining oscillations are achieved (for PI autotune)
uint16_t kP_pi_atune = 0; // kP value calculated by the autotune PI function multiplied by 100 (to avoid floating point operations)
uint16_t kI_pi_atune = 0; // kI value calulated by the autotune PI function multiplied by 100 (to avoid floating point operations)
int16_t temp_peaks_atune[THERMOSTAT_PEAKNUMBER_AUTOTUNE]; // Array to store temperature peaks to be used by the autotune PI function
int16_t temp_abs_max_atune; // Max temperature reached within autotune
int16_t temp_abs_min_atune; // Min temperature reached within autotune
uint16_t time_peak_timestamps_atune[THERMOSTAT_PEAKNUMBER_AUTOTUNE]; // Array to store timestamps in minutes of the temperature peaks to be used by the autotune PI function
uint16_t time_std_dev_peak_det_ok = THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK; // Standard deviation in minutes of the oscillation periods within the peak detection is successful
#endif // USE_PI_AUTOTUNING
} Thermostat[THERMOSTAT_CONTROLLER_OUTPUTS];
/*********************************************************************************************/
@ -212,13 +263,17 @@ void ThermostatInit(uint8_t ctr_output)
Thermostat[ctr_output].status.status_output = IFACE_OFF;
Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI;
Thermostat[ctr_output].status.status_cycle_active = CYCLE_OFF;
Thermostat[ctr_output].status.state_emergency = EMERGENCY_OFF;
Thermostat[ctr_output].diag.state_emergency = EMERGENCY_OFF;
Thermostat[ctr_output].status.counter_seconds = 0;
Thermostat[ctr_output].status.output_relay_number = (THERMOSTAT_RELAY_NUMBER + ctr_output);
Thermostat[ctr_output].status.input_switch_number = (THERMOSTAT_SWITCH_NUMBER + ctr_output);
Thermostat[ctr_output].status.use_input = INPUT_NOT_USED;
Thermostat[ctr_output].status.output_inconsist_ctr = 0;
Thermostat[ctr_output].status.diagnostic_mode = DIAGNOSTIC_ON;
Thermostat[ctr_output].diag.output_inconsist_ctr = 0;
Thermostat[ctr_output].diag.diagnostic_mode = DIAGNOSTIC_ON;
#ifdef USE_PI_AUTOTUNING
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
Thermostat[ctr_output].status.autotune_perf_mode = AUTOTUNE_PERF_FAST;
#endif // USE_PI_AUTOTUNING
// Make sure the Output is OFF
ExecuteCommandPower(Thermostat[ctr_output].status.output_relay_number, POWER_OFF, SRC_THERMOSTAT);
}
@ -312,10 +367,10 @@ void ThermostatSignalPostProcessingSlow(uint8_t ctr_output)
{
// Increate counter when inconsistent output state exists
if (Thermostat[ctr_output].status.status_output != Thermostat[ctr_output].status.command_output) {
Thermostat[ctr_output].status.output_inconsist_ctr++;
Thermostat[ctr_output].diag.output_inconsist_ctr++;
}
else {
Thermostat[ctr_output].status.output_inconsist_ctr = 0;
Thermostat[ctr_output].diag.output_inconsist_ctr = 0;
}
}
@ -333,6 +388,10 @@ void ThermostatSignalProcessingFast(uint8_t ctr_output)
void ThermostatCtrState(uint8_t ctr_output)
{
#ifdef USE_PI_AUTOTUNING
bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING);
#endif //USE_PI_AUTOTUNING
switch (Thermostat[ctr_output].status.controller_mode) {
// Hybrid controller (Ramp-up + PI)
case CTR_HYBRID:
@ -340,10 +399,35 @@ void ThermostatCtrState(uint8_t ctr_output)
break;
// PI controller
case CTR_PI:
#ifdef USE_PI_AUTOTUNING
// If Autotune has been enabled (via flag)
// AND we have just reached the setpoint temperature
// AND the temperature gradient is negative for heating and positive for cooling
// then switch state to PI autotuning
if ((Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_ON)
&&(Thermostat[ctr_output].temp_measured == Thermostat[ctr_output].temp_target_level)
&& ((flag_heating && (Thermostat[ctr_output].temp_measured_gradient < 0))
||(!flag_heating && (Thermostat[ctr_output].temp_measured_gradient > 0))))
{
Thermostat[ctr_output].status.controller_mode = CTR_PI_AUTOTUNE;
ThermostatPeakDetectorInit(ctr_output);
}
#endif // USE_PI_AUTOTUNING
break;
// Ramp-up controller (predictive)
case CTR_RAMP_UP:
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_PI_AUTOTUNE:
// If autotune finalized (flag Off)
// then go back to the PI controller
if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF)
{
Thermostat[ctr_output].status.controller_mode = CTR_PI;
}
break;
#endif //USE_PI_AUTOTUNING
}
}
@ -387,7 +471,32 @@ void ThermostatHybridCtrPhase(uint8_t ctr_output)
Thermostat[ctr_output].time_ctr_checkpoint = 0;
Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_RAMP_UP;
}
#ifdef USE_PI_AUTOTUNING
// If Autotune has been enabled (via flag)
// AND we have just reached the setpoint temperature
// AND the temperature gradient is negative for heating and positive for cooling
// then switch state to PI autotuning
if ((Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_ON)
&&(Thermostat[ctr_output].temp_measured == Thermostat[ctr_output].temp_target_level)
&& ((flag_heating && (Thermostat[ctr_output].temp_measured_gradient < 0))
||(!flag_heating && (Thermostat[ctr_output].temp_measured_gradient > 0))))
{
Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI_AUTOTUNE;
ThermostatPeakDetectorInit(ctr_output);
}
#endif // USE_PI_AUTOTUNING
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune controller phase
case CTR_HYBRID_PI_AUTOTUNE:
// If autotune finalized (flag Off)
// then go back to the PI controller
if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF)
{
Thermostat[ctr_output].status.phase_hybrid_ctr = CTR_HYBRID_PI;
}
break;
#endif // USE_PI_AUTOTUNING
}
}
#ifdef DEBUG_THERMOSTAT
@ -830,6 +939,198 @@ void ThermostatWorkAutomaticRampUp(uint8_t ctr_output)
}
}
#ifdef USE_PI_AUTOTUNING
void ThermostatPeakDetectorInit(uint8_t ctr_output)
{
for (uint8_t i = 0; i < THERMOSTAT_PEAKNUMBER_AUTOTUNE; i++) {
Thermostat[ctr_output].temp_peaks_atune[i] = 0;
}
Thermostat[ctr_output].pU_pi_atune = 0;
Thermostat[ctr_output].kP_pi_atune = 0;
Thermostat[ctr_output].kI_pi_atune = 0;
Thermostat[ctr_output].kU_pi_atune = 0;
Thermostat[ctr_output].peak_ctr = 0;
Thermostat[ctr_output].temp_abs_max_atune = 0;
Thermostat[ctr_output].temp_abs_min_atune = 100;
Thermostat[ctr_output].time_ctr_checkpoint = uptime + THERMOSTAT_TIME_MAX_AUTOTUNE;
}
void ThermostatPeakDetector(uint8_t ctr_output)
{
uint8_t peak_num = Thermostat[ctr_output].peak_ctr;
int16_t peak_avg = 0;
bool peak_transition = false;
// Update Max/Min Thermostat[ctr_output].temp_abs_max_atune
if (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_abs_max_atune) {
Thermostat[ctr_output].temp_abs_max_atune = Thermostat[ctr_output].temp_measured;
}
if (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_abs_min_atune) {
Thermostat[ctr_output].temp_abs_min_atune = Thermostat[ctr_output].temp_measured;
}
// For heating, even peak numbers look for maxes, odd for minds, the contrary for cooling
// If we did not found all peaks yet
if (peak_num < THERMOSTAT_PEAKNUMBER_AUTOTUNE) {
bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING);
bool cond_peak_1 = ( (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_peaks_atune[peak_num])
&& (flag_heating)
|| (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_peaks_atune[peak_num])
&& (!flag_heating));
bool cond_peak_2 = ( (Thermostat[ctr_output].temp_measured < Thermostat[ctr_output].temp_peaks_atune[peak_num])
&& (flag_heating)
|| (Thermostat[ctr_output].temp_measured > Thermostat[ctr_output].temp_peaks_atune[peak_num])
&& (!flag_heating));
bool cond_gradient_1 = ( (Thermostat[ctr_output].temp_measured_gradient > 0)
&& (flag_heating)
|| (Thermostat[ctr_output].temp_measured_gradient < 0)
&& (!flag_heating));
bool cond_gradient_2 = ( (Thermostat[ctr_output].temp_measured_gradient < 0)
&& (flag_heating)
|| (Thermostat[ctr_output].temp_measured_gradient > 0)
&& (!flag_heating));
// If peak number is even (look for max if heating and min if cooling)
if ((peak_num % 2) == 0) {
// If current temperature higher (heating) or lower (cooling) than registered value for peak
// AND temperature gradient > 0 for heating or < 0 for cooling
// then, update value
if (cond_peak_1 && cond_gradient_1) {
Thermostat[ctr_output].temp_peaks_atune[peak_num] = Thermostat[ctr_output].temp_measured;
}
// Else if current temperature lower (heating) or higher (cooling) then registered value for peak
// AND difference to peak is outside of the peak no detection band
// then the current peak value is the peak (max for heating, min for cooling), switch detection
if ( (cond_peak_2)
&& (abs(Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_peaks_atune[peak_num]) > Thermostat[ctr_output].temp_band_no_peak_det)) {
// Register peak timestamp;
Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60);
Thermostat[ctr_output].peak_ctr++;
peak_transition = true;
}
}
// Peak number is odd (look for min if heating and max if cooling)
else {
// If current temperature lower (heating) or higher (cooling) than registered value for peak
// AND temperature gradient < 0 for heating or > 0 for cooling
// then, update value
if (cond_peak_2 && cond_gradient_2) {
Thermostat[ctr_output].temp_peaks_atune[peak_num] = Thermostat[ctr_output].temp_measured;
}
// Else if current temperature higher (heating) or lower (cooling) then registered value for peak
// AND difference to peak is outside of the peak no detection band
// then the current peak value is the peak (min for heating, max for cooling), switch detection
if ( (cond_peak_1)
&& (abs(Thermostat[ctr_output].temp_measured - Thermostat[ctr_output].temp_peaks_atune[peak_num]) > Thermostat[ctr_output].temp_band_no_peak_det)) {
// Calculate period
// Register peak timestamp;
Thermostat[ctr_output].time_peak_timestamps_atune[peak_num] = (uptime / 60);
Thermostat[ctr_output].peak_ctr++;
peak_transition = true;
}
}
}
else {
// Peak detection done, proceed to evaluate results
ThermostatAutotuneParamCalc(ctr_output);
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
}
// If peak detection not finalized but bigger than 3 and we have just found a peak, check if results can be extracted
if ((Thermostat[ctr_output].peak_ctr > 2) && (peak_transition)) {
//Update peak_num
peak_num = Thermostat[ctr_output].peak_ctr;
// Calculate average value among the last 3 peaks
peak_avg = (abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1]
- Thermostat[ctr_output].temp_peaks_atune[peak_num - 2])
+ abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]
- Thermostat[ctr_output].temp_peaks_atune[peak_num - 3])) / 2;
if ((20 * (int32_t)peak_avg) < (int32_t)(Thermostat[ctr_output].temp_abs_max_atune - Thermostat[ctr_output].temp_abs_min_atune)) {
// Calculate average temperature among all peaks
for (uint8_t i = 0; i < peak_num; i++) {
peak_avg += Thermostat[ctr_output].temp_peaks_atune[i];
}
peak_avg /= peak_num;
// If last period crosses the average value, result valid
if (10 * abs(Thermostat[ctr_output].temp_peaks_atune[peak_num - 1] - Thermostat[ctr_output].temp_peaks_atune[peak_num - 2]) < (Thermostat[ctr_output].temp_abs_max_atune - peak_avg)) {
// Peak detection done, proceed to evaluate results
ThermostatAutotuneParamCalc(ctr_output);
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
}
}
}
peak_transition = false;
}
void ThermostatAutotuneParamCalc(uint8_t ctr_output)
{
uint8_t peak_num = Thermostat[ctr_output].peak_ctr;
// Calculate the tunning parameters
// Resolution increased to avoid float operations
Thermostat[ctr_output].kU_pi_atune = (uint16_t)(100 * ((uint32_t)400000 * (uint32_t)(Thermostat[ctr_output].dutycycle_step_autotune)) / ((uint32_t)(Thermostat[ctr_output].temp_abs_max_atune - Thermostat[ctr_output].temp_abs_min_atune) * (uint32_t)314159));
Thermostat[ctr_output].pU_pi_atune = (Thermostat[ctr_output].time_peak_timestamps_atune[peak_num - 1] - Thermostat[ctr_output].time_peak_timestamps_atune[peak_num - 2]);
switch (Thermostat[ctr_output].status.autotune_perf_mode) {
case AUTOTUNE_PERF_FAST:
// Calculate kP/Ki autotune
Thermostat[ctr_output].kP_pi_atune = (4 * Thermostat[ctr_output].kU_pi_atune) / 10;
break;
case AUTOTUNE_PERF_NORMAL:
// Calculate kP/Ki autotune
Thermostat[ctr_output].kP_pi_atune = (18 * Thermostat[ctr_output].kU_pi_atune) / 100;
break;
case AUTOTUNE_PERF_SLOW:
// Calculate kP/Ki autotune
Thermostat[ctr_output].kP_pi_atune = (13 * Thermostat[ctr_output].kU_pi_atune) / 100;
break;
}
// Resolution increased to avoid float operations
Thermostat[ctr_output].kI_pi_atune = (12 * (6000 * Thermostat[ctr_output].kU_pi_atune / Thermostat[ctr_output].pU_pi_atune)) / 10;
// Calculate PropBand Autotune
Thermostat[ctr_output].val_prop_band_atune = 100 / Thermostat[ctr_output].kP_pi_atune;
// Calculate Reset Time Autotune
Thermostat[ctr_output].time_reset_atune = (uint32_t)((((uint32_t)Thermostat[ctr_output].kP_pi_atune * (uint32_t)Thermostat[ctr_output].time_pi_cycle * 6000)) / (uint32_t)Thermostat[ctr_output].kI_pi_atune);
}
void ThermostatWorkAutomaticPIAutotune(uint8_t ctr_output)
{
bool flag_heating = (Thermostat[ctr_output].status.climate_mode == CLIMATE_HEATING);
// If no timeout of the PI Autotune function
// AND no change in setpoint
if ((uptime < Thermostat[ctr_output].time_ctr_checkpoint)
&&(Thermostat[ctr_output].temp_target_level_ctr == Thermostat[ctr_output].temp_target_level)) {
if (uptime >= Thermostat[ctr_output].time_ctr_checkpoint) {
Thermostat[ctr_output].temp_target_level_ctr = Thermostat[ctr_output].temp_target_level;
// Calculate time_ctr_changepoint
Thermostat[ctr_output].time_ctr_changepoint = uptime + (((uint32_t)Thermostat[ctr_output].time_pi_cycle * (uint32_t)Thermostat[ctr_output].dutycycle_step_autotune) / (uint32_t)100);
// Reset cycle active
Thermostat[ctr_output].status.status_cycle_active = CYCLE_OFF;
}
// Set Output On/Off depending on the changepoint
if (uptime < Thermostat[ctr_output].time_ctr_changepoint) {
Thermostat[ctr_output].status.status_cycle_active = CYCLE_ON;
Thermostat[ctr_output].status.command_output = IFACE_ON;
}
else {
Thermostat[ctr_output].status.command_output = IFACE_OFF;
}
// Update peak values
ThermostatPeakDetector(ctr_output);
}
else {
// Disable Autotune flag
Thermostat[ctr_output].status.autotune_flag = AUTOTUNE_OFF;
}
if (Thermostat[ctr_output].status.autotune_flag == AUTOTUNE_OFF) {
// Set output Off
Thermostat[ctr_output].status.command_output = IFACE_OFF;
}
}
#endif //USE_PI_AUTOTUNING
void ThermostatCtrWork(uint8_t ctr_output)
{
switch (Thermostat[ctr_output].status.controller_mode) {
@ -842,6 +1143,12 @@ void ThermostatCtrWork(uint8_t ctr_output)
case CTR_HYBRID_PI:
ThermostatWorkAutomaticPI(ctr_output);
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_HYBRID_PI_AUTOTUNE:
ThermostatWorkAutomaticPIAutotune(ctr_output);
break;
#endif //USE_PI_AUTOTUNING
}
break;
// PI controller
@ -852,6 +1159,12 @@ void ThermostatCtrWork(uint8_t ctr_output)
case CTR_RAMP_UP:
ThermostatWorkAutomaticRampUp(ctr_output);
break;
#ifdef USE_PI_AUTOTUNING
// PI autotune
case CTR_PI_AUTOTUNE:
ThermostatWorkAutomaticPIAutotune(ctr_output);
break;
#endif //USE_PI_AUTOTUNING
}
}
@ -879,17 +1192,17 @@ void ThermostatWork(uint8_t ctr_output)
void ThermostatDiagnostics(uint8_t ctr_output)
{
// Diagnostic related to the plausibility of the output state
if ((Thermostat[ctr_output].status.diagnostic_mode == DIAGNOSTIC_ON)
&&(Thermostat[ctr_output].status.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) {
if ((Thermostat[ctr_output].diag.diagnostic_mode == DIAGNOSTIC_ON)
&&(Thermostat[ctr_output].diag.output_inconsist_ctr >= THERMOSTAT_TIME_MAX_OUTPUT_INCONSIST)) {
Thermostat[ctr_output].status.thermostat_mode = THERMOSTAT_OFF;
Thermostat[ctr_output].status.state_emergency = EMERGENCY_ON;
Thermostat[ctr_output].diag.state_emergency = EMERGENCY_ON;
}
// Diagnostic related to the plausibility of the output power implemented
// already into the energy driver
// If diagnostics fail, emergency enabled and thermostat shutdown triggered
if (Thermostat[ctr_output].status.state_emergency == EMERGENCY_ON) {
if (Thermostat[ctr_output].diag.state_emergency == EMERGENCY_ON) {
ThermostatEmergencyShutdown(ctr_output);
}
}
@ -947,10 +1260,10 @@ void ThermostatDebug(uint8_t ctr_output)
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.counter_seconds: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].status.thermostat_mode, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.thermostat_mode: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].status.state_emergency, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.state_emergency: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].status.output_inconsist_ctr, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.output_inconsist_ctr: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].diag.state_emergency, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].diag.state_emergency: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].diag.output_inconsist_ctr, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].diag.output_inconsist_ctr: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].status.controller_mode, 0, result_chr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Thermostat[ctr_output].status.controller_mode: %s"), result_chr);
dtostrfd(Thermostat[ctr_output].status.command_output, 0, result_chr);
@ -1049,6 +1362,8 @@ void CmndClimateModeSet(void)
uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data));
if ((value >= CLIMATE_HEATING) && (value < CLIMATE_MODES_MAX)) {
Thermostat[ctr_output].status.climate_mode = value;
// Trigger a restart of the controller
Thermostat[ctr_output].time_ctr_checkpoint = uptime;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.climate_mode);
@ -1090,6 +1405,14 @@ void CmndControllerModeSet(void)
uint8_t value = (uint8_t)(XdrvMailbox.payload);
if ((value >= CTR_HYBRID) && (value < CTR_MODES_MAX)) {
Thermostat[ctr_output].status.controller_mode = value;
// Reset controller variables
Thermostat[ctr_output].timestamp_rampup_start = uptime;
Thermostat[ctr_output].temp_rampup_start = Thermostat[ctr_output].temp_measured;
Thermostat[ctr_output].temp_rampup_meas_gradient = 0;
Thermostat[ctr_output].time_rampup_deadtime = 0;
Thermostat[ctr_output].counter_rampup_cycles = 1;
Thermostat[ctr_output].time_ctr_changepoint = 0;
Thermostat[ctr_output].time_ctr_checkpoint = 0;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.controller_mode);
@ -1156,11 +1479,11 @@ void CmndTimeAllowRampupSet(void)
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_allow_rampup = (uint16_t)(value / 60);
if ((value >= 0) && (value < 1440)) {
Thermostat[ctr_output].time_allow_rampup = (uint16_t)value;
}
}
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup * 60));
ResponseCmndNumber((int)((uint32_t)Thermostat[ctr_output].time_allow_rampup));
}
}
@ -1266,10 +1589,10 @@ void CmndStateEmergencySet(void)
if (XdrvMailbox.data_len > 0) {
uint8_t value = (uint8_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= 1)) {
Thermostat[ctr_output].status.state_emergency = (uint16_t)value;
Thermostat[ctr_output].diag.state_emergency = (uint16_t)value;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.state_emergency);
ResponseCmndNumber((int)Thermostat[ctr_output].diag.state_emergency);
}
}
@ -1399,6 +1722,22 @@ void CmndTempHystSet(void)
}
}
#ifdef USE_PI_AUTOTUNING
void CmndPerfLevelAutotune(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
uint8_t ctr_output = XdrvMailbox.index - 1;
if (XdrvMailbox.data_len > 0) {
uint8_t value = (uint8_t)(XdrvMailbox.payload);
if ((value >= 0) && (value <= AUTOTUNE_PERF_MAX)) {
Thermostat[ctr_output].status.autotune_perf_mode = value;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.autotune_perf_mode);
}
}
#endif // USE_PI_AUTOTUNING
void CmndTimeMaxActionSet(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= THERMOSTAT_CONTROLLER_OUTPUTS)) {
@ -1571,10 +1910,10 @@ void CmndDiagnosticModeSet(void)
if (XdrvMailbox.data_len > 0) {
uint8_t value = (uint8_t)(CharToFloat(XdrvMailbox.data));
if ((value >= DIAGNOSTIC_OFF) && (value <= DIAGNOSTIC_ON)) {
Thermostat[ctr_output].status.diagnostic_mode = value;
Thermostat[ctr_output].diag.diagnostic_mode = value;
}
}
ResponseCmndNumber((int)Thermostat[ctr_output].status.diagnostic_mode);
ResponseCmndNumber((int)Thermostat[ctr_output].diag.diagnostic_mode);
}
}

View File

@ -243,10 +243,10 @@ void CseSnsInit(void)
void CseDrvInit(void)
{
Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE));
if (Cse.rx_buffer != nullptr) {
// if (PinUsed(GPIO_CSE7766_RX) && PinUsed(GPIO_CSE7766_TX)) {
if (PinUsed(GPIO_CSE7766_RX)) {
Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE));
if (Cse.rx_buffer != nullptr) {
energy_flg = XNRG_02;
}
}

View File

@ -306,6 +306,9 @@ void MCP230xx_CheckForInterrupt(void) {
ResponseTime_P(PSTR(",\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}}"),
intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int);
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT"));
if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/SENSOR in addition to stat/%topic%/RESULT
MqttPublishSensor();
}
}
if (int_event) {
char command[19]; // Theoretical max = 'event MCPINT_D16=1' so 18 + 1 (for the \n)

View File

@ -45,7 +45,9 @@
#include <TasmotaSerial.h>
// use special no wait serial driver, should be always on
#ifndef ESP32
#define SPECIAL_SS
#endif
// addresses a bug in meter DWS74
//#define DWS74_BUG
@ -440,7 +442,9 @@ const uint8_t meter[]=
#endif
// max number of meters , may be adjusted
#ifndef MAX_METERS
#define MAX_METERS 5
#endif
double meter_vars[SML_MAX_VARS];
// calulate deltas
#define MAX_DVARS MAX_METERS*2
@ -453,7 +457,11 @@ const uint8_t *meter_p;
uint8_t meter_spos[MAX_METERS];
// software serial pointers
#ifdef ESP32
HardwareSerial *meter_ss[MAX_METERS];
#else
TasmotaSerial *meter_ss[MAX_METERS];
#endif
// serial buffers, may be made larger depending on telegram lenght
#define SML_BSIZ 48
@ -774,18 +782,21 @@ uint8_t dump2log=0;
bool Serial_available() {
uint8_t num=dump2log&7;
if (num<1 || num>meters_used) num=1;
if (!meter_ss[num-1]) return 0;
return meter_ss[num-1]->available();
}
uint8_t Serial_read() {
uint8_t num=dump2log&7;
if (num<1 || num>meters_used) num=1;
if (!meter_ss[num-1]) return 0;
return meter_ss[num-1]->read();
}
uint8_t Serial_peek() {
uint8_t num=dump2log&7;
if (num<1 || num>meters_used) num=1;
if (!meter_ss[num-1]) return 0;
return meter_ss[num-1]->peek();
}
@ -1186,7 +1197,8 @@ void sml_shift_in(uint32_t meters,uint32_t shard) {
} else if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M') {
smltbuf[meters][meter_spos[meters]] = iob;
meter_spos[meters]++;
if (meter_spos[meters]>=9) {
uint32_t mlen=smltbuf[meters][2]+5;
if (meter_spos[meters]>=mlen) {
SML_Decode(meters);
sml_empty_receiver(meters);
meter_spos[meters]=0;
@ -1236,6 +1248,7 @@ uint32_t meters;
for (meters=0; meters<meters_used; meters++) {
if (meter_desc_p[meters].type!='c') {
// poll for serial input
if (!meter_ss[meters]) continue;
while (meter_ss[meters]->available()) {
sml_shift_in(meters,0);
}
@ -1530,9 +1543,10 @@ void SML_Decode(uint8_t index) {
if (mb_index!=meter_desc_p[mindex].index) {
goto nextsect;
}
uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],7);
if (lowByte(crc)!=smltbuf[mindex][7]) goto nextsect;
if (highByte(crc)!=smltbuf[mindex][8]) goto nextsect;
uint16_t pos = smltbuf[mindex][2]+3;
uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],pos);
if (lowByte(crc)!=smltbuf[mindex][pos]) goto nextsect;
if (highByte(crc)!=smltbuf[mindex][pos+1]) goto nextsect;
dval=mbus_dval;
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),mp);
mp++;
@ -1728,7 +1742,7 @@ void SML_Show(boolean json) {
} else {
// web ui export
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s{s}%s %s: {m}%s %s{e}", b_mqtt_data,meter_desc[mindex].prefix,name,tpowstr,unit);
WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit);
if (strcmp(name,"*")) WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit);
}
}
}
@ -1830,6 +1844,52 @@ uint8_t *script_meter;
#define METER_DEF_SIZE 3000
#endif
#ifdef SML_REPLACE_VARS
#define SML_SRCBSIZE 256
uint32_t SML_getlinelen(char *lp) {
uint32_t cnt;
for (cnt=0; cnt<SML_SRCBSIZE-1; cnt++) {
if (lp[cnt]==SCRIPT_EOL) {
break;
}
}
return cnt;
}
uint32_t SML_getscriptsize(char *lp) {
uint32_t mlen=0;
char dstbuf[SML_SRCBSIZE*2];
while (1) {
Replace_Cmd_Vars(lp,1,dstbuf,sizeof(dstbuf));
lp+=SML_getlinelen(lp)+1;
uint32_t slen=strlen(dstbuf);
//AddLog_P2(LOG_LEVEL_INFO, PSTR("%d - %s"),slen,dstbuf);
mlen+=slen+1;
if (*lp=='#') break;
if (*lp=='>') break;
if (*lp==0) break;
}
//AddLog_P2(LOG_LEVEL_INFO, PSTR("len=%d"),mlen);
return mlen+32;
}
#else
uint32_t SML_getscriptsize(char *lp) {
uint32_t mlen=0;
for (uint32_t cnt=0;cnt<METER_DEF_SIZE-1;cnt++) {
if (lp[cnt]=='\n' && lp[cnt+1]=='#') {
mlen=cnt+3;
break;
}
}
//AddLog_P2(LOG_LEVEL_INFO, PSTR("len=%d"),mlen);
return mlen;
}
#endif
bool Gpio_used(uint8_t gpiopin) {
/*
for (uint16_t i=0;i<GPIO_SENSOR_END;i++) {
@ -1865,10 +1925,19 @@ void SML_Init(void) {
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
if (script_meter_desc[cnt].txmem) {
free(script_meter_desc[cnt].txmem);
}
script_meter_desc[cnt].txmem=0;
script_meter_desc[cnt].trxpin=-1;
if (meter_ss[cnt]) {
delete meter_ss[cnt];
meter_ss[cnt]=NULL;
}
}
if (bitRead(Settings.rule_enabled, 0)) {
uint8_t meter_script=Run_Scripter(">M",-2,0);
if (meter_script==99) {
// use script definition
@ -1886,13 +1955,7 @@ void SML_Init(void) {
lp+=2;
meters_used=strtol(lp,0,10);
section=1;
uint32_t mlen=0;
for (uint32_t cnt=0;cnt<METER_DEF_SIZE-1;cnt++) {
if (lp[cnt]=='\n' && lp[cnt+1]=='#') {
mlen=cnt+3;
break;
}
}
uint32_t mlen=SML_getscriptsize(lp);
if (mlen==0) return; // missing end #
script_meter=(uint8_t*)calloc(mlen,1);
if (!script_meter) {
@ -1981,6 +2044,30 @@ dddef_exit:
goto next_line;
}
#ifdef SML_REPLACE_VARS
char dstbuf[SML_SRCBSIZE*2];
Replace_Cmd_Vars(lp,1,dstbuf,sizeof(dstbuf));
lp+=SML_getlinelen(lp);
//AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),dstbuf);
char *lp1=dstbuf;
if (*lp1=='-' || isdigit(*lp1)) {
//toLogEOL(">>",lp);
// add meters line -1,1-0:1.8.0*255(@10000,H2OIN,cbm,COUNTER,4|
if (*lp1=='-') lp1++;
uint8_t mnum=strtol(lp1,0,10);
if (mnum<1 || mnum>meters_used) goto next_line;
while (1) {
if (*lp1==0) {
*tp++='|';
goto next_line;
}
*tp++=*lp1++;
index++;
if (index>=METER_DEF_SIZE) break;
}
}
#else
if (*lp=='-' || isdigit(*lp)) {
//toLogEOL(">>",lp);
// add meters line -1,1-0:1.8.0*255(@10000,H2OIN,cbm,COUNTER,4|
@ -1997,6 +2084,7 @@ dddef_exit:
if (index>=METER_DEF_SIZE) break;
}
}
#endif
}
@ -2013,6 +2101,7 @@ next_line:
meter_desc_p=script_meter_desc;
meter_p=script_meter;
}
}
#endif
init10:
@ -2024,6 +2113,7 @@ init10:
RtcSettings.pulse_counter[i]=Settings.pulse_counter[i];
sml_counters[i].sml_cnt_last_ts=millis();
}
uint32_t uart_index=2;
for (uint8_t meters=0; meters<meters_used; meters++) {
if (meter_desc_p[meters].type=='c') {
if (meter_desc_p[meters].flag&2) {
@ -2058,9 +2148,23 @@ init10:
} else {
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1);
}
#else
#ifdef ESP32
meter_ss[meters] = new HardwareSerial(uart_index);
uart_index--;
if (uart_index<0) uart_index=0;
#else
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1);
#endif
#endif
#ifdef ESP32
if (meter_desc_p[meters].type=='M') {
meter_ss[meters]->begin(meter_desc_p[meters].params, SERIAL_8E1,meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin);
} else {
meter_ss[meters]->begin(meter_desc_p[meters].params,SERIAL_8N1,meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin);
}
#else
if (meter_ss[meters]->begin(meter_desc_p[meters].params)) {
meter_ss[meters]->flush();
}
@ -2069,8 +2173,9 @@ init10:
Serial.begin(meter_desc_p[meters].params, SERIAL_8E1);
}
ClaimSerial();
//Serial.setRxBufferSize(512);
}
#endif
}
}
@ -2262,6 +2367,11 @@ void SML_Send_Seq(uint32_t meter,char *seq) {
uint8_t sbuff[32];
uint8_t *ucp=sbuff,slen=0;
char *cp=seq;
uint8_t rflg = 0;
if (*cp=='r') {
rflg = 1;
cp++;
}
while (*cp) {
if (!*cp || !*(cp+1)) break;
if (*cp==',') break;
@ -2272,8 +2382,10 @@ void SML_Send_Seq(uint32_t meter,char *seq) {
if (slen>=sizeof(sbuff)) break;
}
if (script_meter_desc[meter].type=='m' || script_meter_desc[meter].type=='M') {
if (!rflg) {
*ucp++=0;
*ucp++=2;
}
// append crc
uint16_t crc = MBUS_calculateCRC(sbuff,6);
*ucp++=lowByte(crc);
@ -2412,14 +2524,18 @@ bool Xsns53(byte function) {
break;
case FUNC_LOOP:
SML_Counter_Poll();
break;
case FUNC_EVERY_50_MSECOND:
if (dump2log) Dump2log();
else SML_Poll();
break;
// case FUNC_EVERY_50_MSECOND:
// if (dump2log) Dump2log();
// else SML_Poll();
// break;
#ifdef USE_SCRIPT
case FUNC_EVERY_100_MSECOND:
if (bitRead(Settings.rule_enabled, 0)) {
SML_Check_Send();
}
break;
#endif // USE_SCRIPT
case FUNC_JSON_APPEND: