mirror of https://github.com/arendst/Tasmota.git
Merge branch 'development' into pre-release
This commit is contained in:
commit
2055d06825
|
@ -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 |
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "Приятелско име"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]"
|
||||
|
|
|
@ -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 "Φιλική ονομασία"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "שם ידידותי"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "Дружественное Имя"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "Дружня назва"
|
||||
|
|
|
@ -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 "昵称"
|
||||
|
|
|
@ -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 "昵稱"
|
||||
|
|
|
@ -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 -------------------
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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("}"));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue