Merge branch 'development' into prerelease-14.4.1

This commit is contained in:
Theo Arends 2024-12-14 16:59:31 +01:00
commit 4dfac9b414
20 changed files with 1447 additions and 1215 deletions

View File

@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file.
## [Released]
## [14.4.1] 20241215
- Release Rudolph
## [14.4.0.1] 20241215
### Added
- MCP23XXX_DRV control register IOCON in template (#22622)
### Changed
- Berry make Leds animate calls reentrant (#22643)
- SSL clean up remnants of old fingerprint algorithm (#22645)
### Fixed
- ESP32 rules operation priority regression from v13.3.0.4 (#22636)
- GUI display power button regression from v14.3.0.5 (#15788)
- MCP23xxx, PCF8574 and Shift595 power control when a display is configured regression from v14.3.0.7
## [14.4.0] 20241211
- Release Rudolph
@ -47,8 +63,6 @@ All notable changes to this project will be documented in this file.
- Add GUI submenu headers and refresh configuration button text (#22592)
- ESP8266 Device Group exception due to lack of stack space (#22271)
### Removed
## [14.3.0.6] 20241116
### Added
- Add command ``WebColor20`` to control color of Button when Off

View File

@ -75,7 +75,7 @@ Latest released binaries can be downloaded from
- http://ota.tasmota.com/tasmota/release
Historical binaries can be downloaded from
- http://ota.tasmota.com/tasmota/release-14.4.0
- http://ota.tasmota.com/tasmota/release-14.4.1
The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin.gz``
@ -104,7 +104,7 @@ Latest released binaries can be downloaded from
- https://ota.tasmota.com/tasmota32/release
Historical binaries can be downloaded from
- https://ota.tasmota.com/tasmota32/release-14.4.0
- https://ota.tasmota.com/tasmota32/release-14.4.1
The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasmota.com/tasmota32/release/tasmota32.bin``
@ -114,7 +114,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
[Complete list](BUILDS.md) of available feature and sensors.
## Changelog v14.4.0 Rudolph
## Changelog v14.4.1 Rudolph
### Added
- Command `WebColor20` to control color of Button when Off
- Command `SetOption161 1` to disable display of state text [#22515](https://github.com/arendst/Tasmota/issues/22515)
@ -139,6 +139,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- Mitsubishi Electric HVAC Outdoor Temperature for MiElHVAC [#22345](https://github.com/arendst/Tasmota/issues/22345)
- Mitsubishi Electric HVAC Compressor Frequency for MiElHVAC [#22347](https://github.com/arendst/Tasmota/issues/22347)
- Mitsubishi Electric HVAC Auto Clear Remote Temp for MiElHVAC [#22370](https://github.com/arendst/Tasmota/issues/22370)
- MCP23XXX_DRV control register IOCON in template [#22622](https://github.com/arendst/Tasmota/issues/22622)
- SolaxX1 Meter mode [#22330](https://github.com/arendst/Tasmota/issues/22330)
- Show Active Power Total with any multi-phase energy monitoring [#22579](https://github.com/arendst/Tasmota/issues/22579)
- ESP32 support for WPA2/3 Enterprise conditional in core v3.1.0.241206 [#22600](https://github.com/arendst/Tasmota/issues/22600)
@ -172,8 +173,10 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- DALI set Tasmota light control as default
- Shutter optimized behavior to publish shutter data with sensor request [#22353](https://github.com/arendst/Tasmota/issues/22353)
- Prevent active BLE operations with unencrypted MI-format beacons [#22453](https://github.com/arendst/Tasmota/issues/22453)
- SSL clean up remnants of old fingerprint algorithm [#22645](https://github.com/arendst/Tasmota/issues/22645)
- ESP32 max number of supported switches/buttons/relays from 28 to 32
- ESP32 max number of interlocks from 14 to 16
- Berry make Leds animate calls reentrant [#22643](https://github.com/arendst/Tasmota/issues/22643)
- HASPmota support for page delete and object updates [#22311](https://github.com/arendst/Tasmota/issues/22311)
### Fixed
@ -188,11 +191,12 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
- Mitsubishi Electric HVAC Standby Stage for MiElHVAC [#22430](https://github.com/arendst/Tasmota/issues/22430)
- EQ3 TRV firmware version 1.46 fails if the default true is used in subscribe on the notify characteristic [#22328](https://github.com/arendst/Tasmota/issues/22328)
- Ethernet on -DFRAMEWORK_ARDUINO_ITEAD framework regression from v14.3.0 [#22367](https://github.com/arendst/Tasmota/issues/22367)
- ESP8266 Device Group exception due to lack of stack space (#22271)[#22271](https://github.com/arendst/Tasmota/issues/22271)
- MCP23xxx, PCF8574 and Shift595 power control when a display is configured regression from v14.3.0.7
- GUI display power button regression from v14.3.0.5 [#15788](https://github.com/arendst/Tasmota/issues/15788)
- ESP8266 Device Group exception due to lack of stack space [#22271](https://github.com/arendst/Tasmota/issues/22271)
- ESP32 rules operation priority regression from v13.3.0.4 [#22636](https://github.com/arendst/Tasmota/issues/22636)
- ESP32 Upgrade by file upload response based on file size [#22500](https://github.com/arendst/Tasmota/issues/22500)
- ESP32 Arduino Core IPv6 zones used by Matter [#22378](https://github.com/arendst/Tasmota/issues/22378)
- ESP32, ESP32-S2 and ESP32-S3 re-enable touch buttons [#22446](https://github.com/arendst/Tasmota/issues/22446)
- ESP32-S3 UART output mode for Tx [#22426](https://github.com/arendst/Tasmota/issues/22426)
- Matter provisioning with matter.js controller [#22470](https://github.com/arendst/Tasmota/issues/22470)
### Removed

View File

@ -767,59 +767,47 @@ extern "C" {
xc->done_cert = true; // first cert already processed
}
// **** Start patch Castellucci
/*
static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) {
br_sha1_init(shactx);
br_sha1_update(shactx, "ssh-rsa", 7); // tag
br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent
br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus
}
*/
// If `compat` id false, adds a u32be length prefixed value to the sha1 state.
// If `compat` is true, the length will be omitted for compatibility with
// data from older versions of Tasmota.
static void sha1_update_len(br_sha1_context *shactx, const void *msg, uint32_t len, bool compat) {
// Add a data element with a u32be length prefix to the sha1 state.
static void sha1_update_len(br_sha1_context *shactx, const void *msg, uint32_t len) {
uint8_t buf[] = {0, 0, 0, 0};
if (!compat) {
buf[0] = (len >> 24) & 0xff;
buf[1] = (len >> 16) & 0xff;
buf[2] = (len >> 8) & 0xff;
buf[3] = (len >> 0) & 0xff;
br_sha1_update(shactx, buf, 4); // length
}
br_sha1_update(shactx, msg, len); // message
}
// Update the received fingerprint based on the certificate's public key.
// If `compat` is true, an insecure version of the fingerprint will be
// calcualted for compatibility with older versions of Tasmota. Normally,
// `compat` should be false.
static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc, bool compat) {
// Calculate the received fingerprint based on the certificate's public key.
// The public exponent and modulus are length prefixed to avoid security
// vulnerabilities related to ambiguous serialization. Without this, an
// attacker can generate alternative public keys which result in the same
// fingerprint, but are trivial to crack. This works because RSA keys can be
// created with more than two primes, and most numbers, even large ones, can
// be easily factored.
static void pubkeyfingerprint_pubkey_fingerprint(br_x509_pubkeyfingerprint_context *xc) {
br_rsa_public_key rsakey = xc->ctx.pkey.key.rsa;
br_sha1_context shactx;
br_sha1_init(&shactx);
sha1_update_len(&shactx, "ssh-rsa", 7, compat); // tag
sha1_update_len(&shactx, rsakey.e, rsakey.elen, compat); // exponent
sha1_update_len(&shactx, rsakey.n, rsakey.nlen, compat); // modulus
// The tag string doesn't really matter, but it should differ depending on
// key type. Since we only support RSA for now, it's a fixed string.
sha1_update_len(&shactx, "ssh-rsa", 7); // tag
sha1_update_len(&shactx, rsakey.e, rsakey.elen); // exponent
sha1_update_len(&shactx, rsakey.n, rsakey.nlen); // modulus
br_sha1_out(&shactx, xc->pubkey_recv_fingerprint); // copy to fingerprint
}
// **** End patch Castellucci
// Callback when complete chain has been parsed.
// Return 0 on validation success, !0 on validation error
static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) {
br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx;
// set fingerprint status byte to zero
// FIXME: find a better way to pass this information
xc->pubkey_recv_fingerprint[20] = 0;
// Try matching using the the new fingerprint algorithm
pubkeyfingerprint_pubkey_fingerprint(xc, false);
pubkeyfingerprint_pubkey_fingerprint(xc);
if (!xc->fingerprint_all) {
if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint1, 20)) {
return 0;
@ -832,7 +820,6 @@ extern "C" {
// Default (no validation at all) or no errors in prior checks = success.
return 0;
}
// **** End patch Castellucci
}
// Return the public key from the validator (set by x509_minimal)

View File

@ -152,12 +152,7 @@ class WiFiClientSecure_light : public WiFiClient {
bool _insecure; // force fingerprint
const uint8_t *_fingerprint1; // fingerprint1 to be checked against
const uint8_t *_fingerprint2; // fingerprint2 to be checked against
// **** Start patch Castellucci
/*
uint8_t _recv_fingerprint[20]; // fingerprint received
*/
uint8_t _recv_fingerprint[21]; // fingerprint received
// **** End patch Castellucci
unsigned char *_recvapp_buf;
size_t _recvapp_len;

View File

@ -110,6 +110,14 @@ class Animate_core
self.strip.clear()
end
def start()
# check if the strip had a different animate object, stop it
var prev_animate = self.strip.get_animate()
import introspect
if (prev_animate != nil) && (type(prev_animate) == 'instance') && (prev_animate != self)
prev_animate.stop()
end
self.strip.set_animate(self)
self.running = true
var animators = self.animators
var idx = 0

View File

@ -3,8 +3,8 @@
* Generated code, don't edit *
\********************************************************************/
#include "be_constobj.h"
// compact class 'Animate_core' ktab size: 48, total: 98 (saved 400 bytes)
static const bvalue be_ktab_class_Animate_core[48] = {
// compact class 'Animate_core' ktab size: 52, total: 104 (saved 416 bytes)
static const bvalue be_ktab_class_Animate_core[52] = {
/* K0 */ be_nested_str_weak(stop),
/* K1 */ be_nested_str_weak(strip),
/* K2 */ be_nested_str_weak(clear),
@ -51,8 +51,12 @@ static const bvalue be_ktab_class_Animate_core[48] = {
/* K43 */ be_nested_str_weak(set_cb),
/* K44 */ be_nested_str_weak(set_back_color),
/* K45 */ be_nested_str_weak(add_animator),
/* K46 */ be_nested_str_weak(start),
/* K47 */ be_nested_str_weak(add_fast_loop),
/* K46 */ be_nested_str_weak(get_animate),
/* K47 */ be_nested_str_weak(introspect),
/* K48 */ be_nested_str_weak(instance),
/* K49 */ be_nested_str_weak(set_animate),
/* K50 */ be_nested_str_weak(start),
/* K51 */ be_nested_str_weak(add_fast_loop),
};
@ -716,7 +720,7 @@ be_local_closure(class_Animate_core_remove, /* name */
********************************************************************/
be_local_closure(class_Animate_core_start, /* name */
be_nested_proto(
6, /* nstack */
8, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
@ -727,27 +731,47 @@ be_local_closure(class_Animate_core_start, /* name */
&be_ktab_class_Animate_core, /* shared constants */
be_str_weak(start),
&be_const_str_solidified,
( &(const binstruction[20]) { /* code */
0x50040200, // 0000 LDBOOL R1 1 0
0x90021601, // 0001 SETMBR R0 K11 R1
0x8804010C, // 0002 GETMBR R1 R0 K12
0x58080007, // 0003 LDCONST R2 K7
0x600C000C, // 0004 GETGBL R3 G12
0x5C100200, // 0005 MOVE R4 R1
0x7C0C0200, // 0006 CALL R3 1
0x140C0403, // 0007 LT R3 R2 R3
0x780E0004, // 0008 JMPF R3 #000E
0x940C0202, // 0009 GETIDX R3 R1 R2
0x8C0C072E, // 000A GETMET R3 R3 K46
0x7C0C0200, // 000B CALL R3 1
0x0008050D, // 000C ADD R2 R2 K13
0x7001FFF5, // 000D JMP #0004
0x90022707, // 000E SETMBR R0 K19 K7
0xB80E0800, // 000F GETNGBL R3 K4
0x8C0C072F, // 0010 GETMET R3 R3 K47
0x8814010F, // 0011 GETMBR R5 R0 K15
0x7C0C0400, // 0012 CALL R3 2
0x80000000, // 0013 RET 0
( &(const binstruction[40]) { /* code */
0x88040101, // 0000 GETMBR R1 R0 K1
0x8C04032E, // 0001 GETMET R1 R1 K46
0x7C040200, // 0002 CALL R1 1
0xA40A5E00, // 0003 IMPORT R2 K47
0x4C0C0000, // 0004 LDNIL R3
0x200C0203, // 0005 NE R3 R1 R3
0x780E0008, // 0006 JMPF R3 #0010
0x600C0004, // 0007 GETGBL R3 G4
0x5C100200, // 0008 MOVE R4 R1
0x7C0C0200, // 0009 CALL R3 1
0x1C0C0730, // 000A EQ R3 R3 K48
0x780E0003, // 000B JMPF R3 #0010
0x200C0200, // 000C NE R3 R1 R0
0x780E0001, // 000D JMPF R3 #0010
0x8C0C0300, // 000E GETMET R3 R1 K0
0x7C0C0200, // 000F CALL R3 1
0x880C0101, // 0010 GETMBR R3 R0 K1
0x8C0C0731, // 0011 GETMET R3 R3 K49
0x5C140000, // 0012 MOVE R5 R0
0x7C0C0400, // 0013 CALL R3 2
0x500C0200, // 0014 LDBOOL R3 1 0
0x90021603, // 0015 SETMBR R0 K11 R3
0x880C010C, // 0016 GETMBR R3 R0 K12
0x58100007, // 0017 LDCONST R4 K7
0x6014000C, // 0018 GETGBL R5 G12
0x5C180600, // 0019 MOVE R6 R3
0x7C140200, // 001A CALL R5 1
0x14140805, // 001B LT R5 R4 R5
0x78160004, // 001C JMPF R5 #0022
0x94140604, // 001D GETIDX R5 R3 R4
0x8C140B32, // 001E GETMET R5 R5 K50
0x7C140200, // 001F CALL R5 1
0x0010090D, // 0020 ADD R4 R4 K13
0x7001FFF5, // 0021 JMP #0018
0x90022707, // 0022 SETMBR R0 K19 K7
0xB8160800, // 0023 GETNGBL R5 K4
0x8C140B33, // 0024 GETMET R5 R5 K51
0x881C010F, // 0025 GETMBR R7 R0 K15
0x7C140400, // 0026 CALL R5 2
0x80000000, // 0027 RET 0
})
)
);

View File

@ -16,7 +16,6 @@ extern int be_leds_apply_bri_gamma(bvm *vm);
/* @const_object_info_begin
class be_class_Leds_ntv (scope: global, name: Leds_ntv, strings: weak) {
_p, var
_t, var
WS2812_GRB, int(ws2812_grb)
SK6812_GRBW, int(sk6812_grbw)

View File

@ -28,6 +28,7 @@ class Leds : Leds_ntv
var gamma # if true, apply gamma (true is default)
var leds # number of leds
var bri # implicit brightness for this led strip (0..255, default is 50% = 127)
var animate # attached animate object or nil - this allows to stop any existing animation for this strip if we add a new animate
# leds:int = number of leds of the strip
# gpio:int (optional) = GPIO for NeoPixel. If not specified, takes the WS2812 gpio
# typ:int (optional) = Type of LED, defaults to WS2812 RGB
@ -35,25 +36,43 @@ class Leds : Leds_ntv
def init(leds, gpio_phy, typ, hardware)
import gpio
self.gamma = true # gamma is enabled by default, it should be disabled explicitly if needed
if (gpio_phy == nil) || (gpio_phy == gpio.pin(gpio.WS2812, 0))
if (leds == nil ) || (gpio_phy == nil) || (gpio_phy == gpio.pin(gpio.WS2812, 0))
# use native driver
self.ctor() # no parameters
# in such case, `self._p` is equal to `0`
self.leds = self.pixel_count()
import light
self.bri = light.get()['bri']
else
# use pure Berry driver
self.leds = int(leds)
leds = int(leds)
self.leds = leds
self.bri = 127 # 50% brightness by default
# initialize the structure
self.ctor(self.leds, gpio_phy, typ, hardware)
# check if already in global `_lhw`
if !global.contains('_lhw')
global._lhw = {}
end
if global._lhw.find(leds) != nil
# an object already exists, attach it
var prov_led = global._lhw.find(leds) # already provisioned leds instance
if self.leds != prov_led.leds
raise "value_error", f"number of leds do not match with previous instanciation {self.leds} vs {prov_led.leds}"
end
self._p = prov_led._p
self.animate = prov_led.animate
global._lhw[leds] = self # put the most recent as current
else
self.ctor(leds, gpio_phy, typ, hardware)
global._lhw[leds] = self
# call begin
self.begin()
end
end
if self._p == nil raise "internal_error", "couldn't not initialize noepixelbus" end
# call begin
self.begin()
end
def clear()
@ -71,6 +90,13 @@ class Leds : Leds_ntv
return self.bri
end
def set_animate(animate)
self.animate = animate
end
def get_animate()
return self.animate
end
def ctor(leds, gpio_phy, typ, hardware)
if gpio_phy == nil
self.call_native(0) # native driver

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@ def addEntryToModtab(source):
is_module = False
pattern = (r'''(?<=module\()[^"].*''') # module??
pattern = (r'''(?<=module\([\"\']).*[\"\']''') # module??
result = re.findall(pattern,code)
if len(result) > 0:
class_name = result[0].replace("'","").replace('"','').replace(")","")

View File

@ -0,0 +1,68 @@
:H,ST7701,480,480,16,RGB,40,39,38,41,-1,5,45,48,47,21,14,13,12,11,10,9,46,3,8,18,17,6
:V,1,10,8,50,1,10,8,20,0
:S,2,1,1,0,40,20
:IS,2,1,42,-1
11,80
FF,5,77,01,00,00,10
C0,2,3B,00
C1,2,0D,02
C2,2,21,08
CD,1,08
B0,10,00,11,18,0E,11,06,07,08,07,22,04,12,0F,AA,31,18
B1,10,00,11,19,0E,12,07,08,08,08,22,04,11,11,A9,32,18
FF,5,77,01,00,00,11
B0,1,60
B1,1,30
B2,1,87
B3,1,80
B5,1,49
B7,1,85
B8,1,21
C1,1,78
C2,1,78,20
E0,3,00,1B,02
E1,B,08,A0,00,00,07,A0,00,00,00,44,44
E2,C,11,11,44,44,ED,A0,00,00,EC,A0,00,00
E3,4,00,00,11,11
E4,2,44,44
E5,10,0A,E9,D8,A0,0C,EB,D8,A0,0E,ED,D8,A0,10,EF,D8,A0
E6,4,00,00,11,11
E7,2,44,44
E8,10,09,E8,D8,A0,0B,EA,D8,A0,0D,EC,D8,A0,0F,EE,D8,A0
EB,7,02,00,E4,E4,88,00,40
EC,2,3C,00
ED,10,AB,89,76,54,02,FF,FF,FF,FF,FF,FF,20,45,67,98,BA
FF,5,77,01,00,00,00
36,1,04
3A,1,66
21,80
29,0
:B,100,02
:UTI,GT911,I1,5d,-1,-1
RDWM 8140 4
MV 0 1
CPR 39
RTF
MV 1 1
CPR 31
RTF
MV 2 1
CPR 31
RTF
RT
:UTT
RDW 814E
MV 0 1
AND 80
CPR 80
RTF
RDWM 8150 8
WRW 814E 00
RT
:UTX
MV 0 3
RT
:UTY
MV 2 3
RT
#

View File

@ -968,8 +968,8 @@ const char HTTP_SNS_F_VOLTAGE[] PROGMEM = "{s}%s " D_VOLTAGE "{
const char HTTP_SNS_F_CURRENT[] PROGMEM = "{s}%s " D_CURRENT "{m}%*_f " D_UNIT_AMPERE "{e}";
const char HTTP_SNS_F_CURRENT_MA[] PROGMEM = "{s}%s " D_CURRENT "{m}%*_f " D_UNIT_MILLIAMPERE "{e}";
const char HTTP_SNS_F_DISTANCE_CM[] PROGMEM = "{s}%s " D_DISTANCE "{m}%1_f " D_UNIT_CENTIMETER "{e}";
const char HTTP_SNS_F_NOX[] PROGMEM = "{s}%s " D_NOX "{m}%*_f " "{e}";
const char HTTP_SNS_F_VOC[] PROGMEM = "{s}%s " D_VOC "{m}%*_f " "{e}";
const char HTTP_SNS_F_NOX[] PROGMEM = "{s}%s " D_NOX "{m}%*_f" "{e}";
const char HTTP_SNS_F_VOC[] PROGMEM = "{s}%s " D_VOC "{m}%*_f" "{e}";
const char HTTP_SNS_F_ABS_HUM[] PROGMEM = "{s}%s " D_ABSOLUTE_HUMIDITY "{m}%*_f " D_UNIT_GRAM_PER_CUBIC_METER "{e}";
const char HTTP_SNS_HUM[] PROGMEM = "{s}%s " D_HUMIDITY "{m}%s " D_UNIT_PERCENT "{e}";
@ -987,17 +987,17 @@ const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{
const char HTTP_SNS_RANGE_CHR[] PROGMEM = "{s}%s " D_RANGE "{m}%s" "{e}";
const char HTTP_SNS_RANGE[] PROGMEM = "{s}%s " D_RANGE "{m}%d" "{e}";
const char HTTP_SNS_HALL_EFFECT[] PROGMEM = "{s}%s " D_HALL_EFFECT "{m}%d" "{e}";
const char HTTP_SNS_PH[] PROGMEM = "{s}%s " D_PH "{m}%s " "{e}";
const char HTTP_SNS_MQ[] PROGMEM = "{s}" D_MQ"-%s" "{m}%s " D_UNIT_PARTS_PER_MILLION "{e}";
const char HTTP_SNS_PH[] PROGMEM = "{s}%s " D_PH "{m}%s" "{e}";
const char HTTP_SNS_MQ[] PROGMEM = "{s}" D_MQ "-%s" "{m}%s " D_UNIT_PARTS_PER_MILLION "{e}";
const char HTTP_SNS_ORP[] PROGMEM = "{s}%s " D_ORP "{m}%s " D_UNIT_MILLIVOLT "{e}";
const char HTTP_SNS_EC[] PROGMEM = "{s}%s " D_EC "{m}%s " D_UNIT_MICROSIEMENS_PER_CM "{e}";
const char HTTP_SNS_O2[] PROGMEM = "{s}%s " D_O2 "{m}%s " D_UNIT_PERCENT "{e}";
const char HTTP_SNS_LITERS[] PROGMEM = "{s}%s " D_VOLUME "{m}%s " D_UNIT_LITERS "{e}";
const char HTTP_SNS_LPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_LITERS_PER_MIN "{e}";
const char HTTP_SNS_DO[] PROGMEM = "{s}%s " D_DO "{m}%s " D_UNIT_MILIGRAMS_PER_LITER "{e}";
const char HTTP_SNS_COLOR_RED[] PROGMEM = "{s}%s " D_COLOR_RED "{m}%u " "{e}";
const char HTTP_SNS_COLOR_GREEN[] PROGMEM = "{s}%s " D_COLOR_GREEN "{m}%u " "{e}";
const char HTTP_SNS_COLOR_BLUE[] PROGMEM = "{s}%s " D_COLOR_BLUE "{m}%u " "{e}";
const char HTTP_SNS_COLOR_RED[] PROGMEM = "{s}%s " D_COLOR_RED "{m}%u" "{e}";
const char HTTP_SNS_COLOR_GREEN[] PROGMEM = "{s}%s " D_COLOR_GREEN "{m}%u" "{e}";
const char HTTP_SNS_COLOR_BLUE[] PROGMEM = "{s}%s " D_COLOR_BLUE "{m}%u" "{e}";
const char HTTP_SNS_MILLILITERS[] PROGMEM = "{s}%s " D_VOLUME "{m}%s " D_UNIT_MILLILITERS "{e}";
const char HTTP_SNS_GAS[] PROGMEM = "{s}%s " D_GAS "{m}%d " D_UNIT_PERCENT "LEL{e}";
const char HTTP_SNS_SOC[] PROGMEM = "{s}%s " D_SOC "{m}%d " D_UNIT_PERCENT "{e}";
@ -1022,7 +1022,7 @@ const char HTTP_SNS_MAX_POWER[] PROGMEM = "{s}" D_MAX_POWER
const char HTTP_SNS_POWER_TOTAL[] PROGMEM = "{s}" D_POWERUSAGE_ACTIVE_TOTAL "{m}%s " D_UNIT_WATT "{e}";
const char HTTP_SNS_POWERUSAGE_APPARENT[] PROGMEM = "{s}" D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}";
const char HTTP_SNS_POWERUSAGE_REACTIVE[] PROGMEM = "{s}" D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}";
const char HTTP_SNS_POWER_FACTOR[] PROGMEM = "{s}" D_POWER_FACTOR "{m}%s {e}";
const char HTTP_SNS_POWER_FACTOR[] PROGMEM = "{s}" D_POWER_FACTOR "{m}%s" "{e}";
const char HTTP_SNS_ENERGY_TODAY[] PROGMEM = "{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
const char HTTP_SNS_ENERGY_YESTERDAY[] PROGMEM = "{s}" D_ENERGY_YESTERDAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
const char HTTP_SNS_ENERGY_TOTAL[] PROGMEM = "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}";

View File

@ -22,6 +22,6 @@
#define TASMOTA_SHA_SHORT // Filled by Github sed
const uint32_t TASMOTA_VERSION = 0x0E040000; // 14.4.0.0
const uint32_t TASMOTA_VERSION = 0x0E040100; // 14.4.1.0
#endif // _TASMOTA_VERSION_H_

View File

@ -211,7 +211,7 @@
#define COLOR_TIMER_TAB_TEXT "#fff" // [WebColor17] Config timer tab text color - White
#define COLOR_TIMER_TAB_BACKGROUND "#999" // [WebColor18] Config timer tab background color - Dark gray
#define COLOR_TITLE_TEXT "#000" // [WebColor19] Title text color - Whiteish
#define COLOR_BUTTON_OFF "#08405e" // [WebColor20] Button color when off - Darkest blueish
#define COLOR_BUTTON_OFF "#a1d9f7" // [WebColor20] Button color when off - Light blue
*/
// Dark theme
// WebColor {"WebColor":["#eaeaea","#252525","#4f4f4f","#000","#ddd","#65c115","#1f1f1f","#ff5661","#008000","#faffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#faffff","#999","#eaeaea","#08405e"]}

View File

@ -484,6 +484,8 @@ ESP8266WebServer *Webserver;
struct WEB {
String chunk_buffer = ""; // Could be max 2 * CHUNKED_BUFFER_SIZE
uint32_t upload_size = 0;
uint32_t light_shutter_button_mask;
uint32_t buttons_non_light_non_shutter;
uint32_t slider_update_time = 0;
int slider[LST_MAX];
int8_t shutter_slider[16]; // MAX_SHUTTERS_ESP32
@ -1257,33 +1259,37 @@ int32_t IsShutterWebButton(uint32_t idx) {
/*-------------------------------------------------------------------------------------------*/
void WebGetDeviceCounts(uint32_t &buttons_non_light, uint32_t &buttons_non_light_non_shutter, uint32_t &shutter_button_mask) {
buttons_non_light = TasmotaGlobal.devices_present;
void WebGetDeviceCounts(void) {
Web.buttons_non_light_non_shutter = TasmotaGlobal.devices_present;
Web.light_shutter_button_mask = 0; // Bitmask for each light and/or shutter button
#ifdef USE_LIGHT
// Chk for reduced toggle buttons used by lights
if (TasmotaGlobal.light_type) {
// Find and skip light buttons (Lights are controlled by the last TasmotaGlobal.devices_present (or 2))
buttons_non_light = LightDevice() -1;
// Find and skip light buttons
uint32_t light_device = LightDevice();
uint32_t light_devices = LightDevices();
for (uint32_t button_idx = light_device; button_idx < (light_device + light_devices); button_idx++) {
Web.buttons_non_light_non_shutter--;
Web.light_shutter_button_mask |= (1 << (button_idx -1)); // Set button bit in bitmask
}
}
#endif // USE_LIGHT
buttons_non_light_non_shutter = buttons_non_light;
shutter_button_mask = 0; // Bitmask for each button
#ifdef USE_SHUTTER
// Chk for reduced toggle buttons used by shutters
if (Settings->flag3.shutter_mode) { // SetOption80 - Enable shutter support
// Find and skip dedicated shutter buttons
if (buttons_non_light && Settings->flag3.shutter_mode) { // SetOption80 - Enable shutter support
for (uint32_t button_idx = 1; button_idx <= buttons_non_light; button_idx++) {
for (uint32_t button_idx = 1; button_idx <= TasmotaGlobal.devices_present; button_idx++) {
if (IsShutterWebButton(button_idx) != 0) {
buttons_non_light_non_shutter--;
shutter_button_mask |= (1 << (button_idx -1)); // Set button bit in bitmask
Web.buttons_non_light_non_shutter--;
Web.light_shutter_button_mask |= (1 << (button_idx -1)); // Set button bit in bitmask
}
}
}
#endif // USE_SHUTTER
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HTP: DP %d, BNL %d, BNLNS %d, SB %08X"), TasmotaGlobal.devices_present, buttons_non_light, buttons_non_light_non_shutter, shutter_button);
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HTP: DP %d, BNLNS %d, SB %08X"), TasmotaGlobal.devices_present, Web.buttons_non_light_non_shutter, Web.light_shutter_button_mask);
}
#ifdef USE_LIGHT
@ -1366,13 +1372,10 @@ void HandleRoot(void) {
#ifndef FIRMWARE_MINIMAL
if (TasmotaGlobal.devices_present) {
uint32_t buttons_non_light;
uint32_t buttons_non_light_non_shutter;
uint32_t shutter_button_mask;
WebGetDeviceCounts(buttons_non_light, buttons_non_light_non_shutter, shutter_button_mask);
uint32_t button_idx = 1;
WebGetDeviceCounts();
if (buttons_non_light_non_shutter) { // Any non light AND non shutter button - Show toggle buttons
uint32_t button_idx = 1;
if (Web.buttons_non_light_non_shutter) { // Any non light AND non shutter button - Show toggle buttons
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
WSContentSend_P(PSTR("<tr>"));
@ -1391,18 +1394,14 @@ void HandleRoot(void) {
#endif // USE_SONOFF_IFAN
const uint32_t max_columns = 8;
uint32_t rows = buttons_non_light_non_shutter / max_columns;
if (buttons_non_light_non_shutter % max_columns) { rows++; }
uint32_t cols = buttons_non_light_non_shutter / rows;
if (buttons_non_light_non_shutter % rows) { cols++; }
uint32_t rows = Web.buttons_non_light_non_shutter / max_columns;
if (Web.buttons_non_light_non_shutter % max_columns) { rows++; }
uint32_t cols = Web.buttons_non_light_non_shutter / rows;
if (Web.buttons_non_light_non_shutter % rows) { cols++; }
uint32_t button_ptr = 0;
for (button_idx = 1; button_idx <= buttons_non_light; button_idx++) {
#ifdef USE_SHUTTER
if (bitRead(shutter_button_mask, button_idx -1)) { continue; } // Skip non-sequential shutter button
#endif // USE_SHUTTER
for (button_idx = 1; button_idx <= TasmotaGlobal.devices_present; button_idx++) {
if (bitRead(Web.light_shutter_button_mask, button_idx -1)) { continue; } // Skip non-sequential light and/or shutter button
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), button_idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / cols, button_idx, button_idx,
@ -1451,20 +1450,20 @@ void HandleRoot(void) {
WSContentSend_P(PSTR("</tr>"));
}
WSContentSend_P(PSTR("</table>"));
if (1 == button_idx) { // No power/display button
button_idx = shutter_button_idx +2;
}
}
#endif // USE_SHUTTER
#ifdef USE_LIGHT
if (TasmotaGlobal.light_type) { // Any light - Show light button and slider(s)
uint32_t light_device = LightDevice();
uint32_t light_devices = LightDevices();
button_idx = light_device;
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
uint8_t light_subtype = TasmotaGlobal.light_type &7;
if (!Settings->flag3.pwm_multi_channels) { // SetOption68 0 - Enable multi-channels PWM instead of Color PWM
bool split_white = ((LST_RGBW <= light_subtype) && (TasmotaGlobal.devices_present > 1) && (Settings->param[P_RGB_REMAP] & 128)); // Only on RGBW or RGBCW and SetOption37 128
bool split_white = ((LST_RGBW <= light_subtype) && (light_devices > 1) && (Settings->param[P_RGB_REMAP] & 128)); // Only on RGBW or RGBCW and SetOption37 128
if ((LST_COLDWARM == light_subtype) || ((LST_RGBCW == light_subtype) && !split_white)) {
WebSliderColdWarm();
@ -1538,7 +1537,7 @@ void HandleRoot(void) {
uint32_t width = 100;
WSContentSend_P(PSTR("<tr>"));
if (button_idx <= TasmotaGlobal.devices_present) {
if (button_idx < (light_device + light_devices)) {
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
char first[2];
snprintf_P(first, sizeof(first), PSTR("%s"), PSTR(D_BUTTON_TOGGLE));
@ -1564,9 +1563,8 @@ void HandleRoot(void) {
WSContentSend_P(PSTR("</tr>"));
}
} else { // Settings->flag3.pwm_multi_channels - SetOption68 1 - Enable multi-channels PWM instead of Color PWM
uint32_t pwm_channels = TasmotaGlobal.devices_present - buttons_non_light;
stemp[0] = 'e'; stemp[1] = '0'; stemp[2] = '\0'; // d0
for (uint32_t i = 0; i < pwm_channels; i++) {
stemp[0] = 'e'; stemp[1] = '0'; stemp[2] = '\0'; // e0
for (uint32_t i = 0; i < light_devices; i++) {
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
char first[2];
snprintf_P(first, sizeof(first), PSTR("%s"), PSTR(D_BUTTON_TOGGLE));
@ -1686,7 +1684,7 @@ bool HandleRootStatusRefresh(void) {
char svalue[32]; // Command and number parameter
char webindex[5]; // WebGetArg name
WebGetArg(PSTR("o"), tmp, sizeof(tmp)); // 1 - 16 Device number for button Toggle or Fanspeed
WebGetArg(PSTR("o"), tmp, sizeof(tmp)); // 1 - 32 Device number for button Toggle or Fanspeed
if (strlen(tmp)) {
ShowWebSource(SRC_WEBGUI);
uint32_t device = atoi(tmp);
@ -1727,8 +1725,8 @@ bool HandleRootStatusRefresh(void) {
ExecuteWebCommand(svalue);
}
uint32_t light_device = LightDevice(); // Channel number offset
uint32_t pwm_channels = (TasmotaGlobal.light_type & 7) > LST_MAX ? LST_MAX : (TasmotaGlobal.light_type & 7);
for (uint32_t j = 0; j < pwm_channels; j++) {
uint32_t light_devices = LightDevices(); // Number of channels
for (uint32_t j = 0; j < light_devices; j++) {
snprintf_P(webindex, sizeof(webindex), PSTR("e%d"), j +1);
WebGetArg(webindex, tmp, sizeof(tmp)); // 0 - 100 percent
if (strlen(tmp)) {
@ -1902,21 +1900,12 @@ bool HandleRootStatusRefresh(void) {
} else {
#endif // USE_SONOFF_IFAN
uint32_t buttons_non_light;
uint32_t buttons_non_light_non_shutter;
uint32_t shutter_button_mask;
WebGetDeviceCounts(buttons_non_light, buttons_non_light_non_shutter, shutter_button_mask);
if (buttons_non_light_non_shutter <= 8) { // Any non light AND non shutter button
if (Web.buttons_non_light_non_shutter <= 8) { // Any non light AND non shutter button
WSContentSend_P(PSTR("{t}<tr>"));
uint32_t cols = buttons_non_light_non_shutter;
uint32_t cols = Web.buttons_non_light_non_shutter;
uint32_t fontsize = (cols < 5) ? 70 - (cols * 8) : 32;
for (uint32_t idx = 1; idx <= buttons_non_light; idx++) {
#ifdef USE_SHUTTER
if (bitRead(shutter_button_mask, idx -1)) { continue; } // Skip non-sequential shutter button
#endif // USE_SHUTTER
for (uint32_t idx = 1; idx <= Web.buttons_non_light_non_shutter; idx++) {
if (bitRead(Web.light_shutter_button_mask, idx -1)) { continue; } // Skip non-sequential shutter button
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(TasmotaGlobal.power, idx -1));
WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (bitRead(TasmotaGlobal.power, idx -1)) ? PSTR("bold") : PSTR("normal"), fontsize,
(cols < 5) ? GetStateText(bitRead(TasmotaGlobal.power, idx -1)) : svalue);

View File

@ -236,6 +236,7 @@ struct LIGHT {
uint8_t random = 0;
uint8_t subtype = 0; // LST_ subtype
uint8_t device = 0;
uint8_t devices = 0;
uint8_t old_power = 1;
uint8_t wakeup_active = 0; // 0=inctive, 1=on-going, 2=about to start, 3=will be triggered next cycle
uint8_t fixed_color_index = 1;
@ -294,10 +295,7 @@ uint8_t LightDevice(void)
}
uint32_t LightDevices(void) {
if (0 == Light.device) {
return 0;
}
return TasmotaGlobal.devices_present - Light.device +1; // Make external
return Light.devices; // Make external
}
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c) {
@ -1256,6 +1254,8 @@ void LightInit(void)
Light.fade_initialized = true; // consider fade intialized starting from black
}
Light.devices = TasmotaGlobal.devices_present - Light.device +1; // Last time that devices_present is not increments by display
LightUpdateColorMapping();
}

View File

@ -1687,10 +1687,15 @@ float evaluateExpression(const char * expression, unsigned int len) {
while (index < operators_size) {
if (priority == pgm_read_byte(kExpressionOperatorsPriorities + operators[index])) { // Need to calculate the operator first
// Get current object value and remove the next object with current operator
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: index %d, v1 '%4_f', v2 '%4_f', op %d"), index, &object_values[index], &object_values[index + 1], operators[index]);
va = calculateTwoValues(object_values[index], object_values[index + 1], operators[index]);
uint32_t i = index;
while (i <= operators_size) {
operators[i++] = operators[i]; // operators.remove(index)
// operators[i++] = operators[i]; // operators.remove(index) - Fails on ESP32 (#22636)
operators[i] = operators[i +1]; // operators.remove(index)
i++;
object_values[i] = object_values[i +1]; // object_values.remove(index + 1)
}
operators_size--;

View File

@ -270,7 +270,7 @@ bool disp_subscribed = false;
/*********************************************************************************************/
uint32_t DisplayDevices(void) {
return (disp_device);
return (disp_device) ? 1 : 0;
}
/*********************************************************************************************/

View File

@ -76,20 +76,8 @@ extern "C" {
be_getmember(vm, 1, "_p");
void * strip = (void*) be_tocomptr(vm, -1);
be_pop(vm, 1);
if (strip == nullptr) {
be_raise(vm, "internal_error", "tasmotaled object not initialized");
}
return strip;
}
int32_t be_get_leds_type(bvm *vm) {
be_getmember(vm, 1, "_t");
int32_t type = be_toint(vm, -1);
be_pop(vm, 1);
if (type < 0) {
be_raise(vm, "internal_error", "invalid leds type");
}
return type;
}
int be_tasmotaled_call_native(bvm *vm);
int be_tasmotaled_call_native(bvm *vm) {
@ -119,7 +107,6 @@ extern "C" {
// if GPIO is '-1'
led_type = 0;
Ws2812InitStrip(); // ensure the tasmotaled object is initialized, because Berry code runs before the driver is initialized
// strip = Ws2812GetStrip(); TODO
} else {
if (led_type < 1) { led_type = 1; }
TasmotaLEDPusher * pusher = TasmotaLEDPusher::Create(hardware, gpio);
@ -130,29 +117,20 @@ extern "C" {
strip->SetPusher(pusher);
}
// AddLog(LOG_LEVEL_DEBUG, "LED: leds %i gpio %i type %i", leds, gpio, led_type);
// store type in attribute `_t`
be_pushint(vm, led_type);
be_setmember(vm, 1, "_t");
be_pop(vm, 1);
be_pushcomptr(vm, (void*) strip); // if native driver, it is NULL
be_setmember(vm, 1, "_p");
be_pop(vm, 1);
be_pushnil(vm);
} else {
// all other commands need a valid tasmotaled pointer
int32_t leds_type = be_get_leds_type(vm);
TasmotaLED * strip = (TasmotaLED*) be_get_tasmotaled(vm); // raises an exception if pointer is invalid
// detect native driver
bool native = (leds_type == 0);
// detect native driver means strip == nullptr
be_pushnil(vm); // push a default `nil` return value
switch (cmd) {
case 1: // # 01 : begin void -> void
if (native) Ws2812Begin();
else if (strip) strip->Begin();
if (strip) strip->Begin();
else Ws2812Begin();
break;
case 2: // # 02 : show void -> void
{
@ -176,43 +154,40 @@ extern "C" {
}
uint32_t pixels_size; // number of bytes to push
bool update_completed = false;
if (native) {
if (strip) {
strip->Show();
pixels_size = strip->PixelCount() * strip->PixelSize();
update_completed = strip->CanShow();
} else {
Ws2812Show();
pixels_size = Ws2812PixelsSize();
update_completed =Ws2812CanShow();
}
else if (strip) {
strip->Show();
pixels_size = strip->PixelCount() * strip->PixelSize();
update_completed = strip->CanShow();
}
}
break;
case 3: // # 03 : CanShow void -> bool
if (native) be_pushbool(vm, Ws2812CanShow());
else if (strip) be_pushbool(vm, strip->CanShow());
if (strip) be_pushbool(vm, strip->CanShow());
else be_pushbool(vm, Ws2812CanShow());
break;
case 4: // # 04 : IsDirty void -> bool
if (native) be_pushbool(vm, Ws2812IsDirty());
else if (strip) be_pushbool(vm, strip->IsDirty());
if (strip) be_pushbool(vm, strip->IsDirty());
else be_pushbool(vm, Ws2812IsDirty());
break;
case 5: // # 05 : Dirty void -> void
if (native) Ws2812Dirty();
else if (strip) strip->Dirty();
if (strip) strip->Dirty();
else Ws2812Dirty();
break;
case 6: // # 06 : Pixels void -> bytes() (mapped to the buffer)
{
if (native) be_pushcomptr(vm, Ws2812Pixels());
else if (strip) be_pushcomptr(vm, strip->Pixels());
}
if (strip) be_pushcomptr(vm, strip->Pixels());
else be_pushcomptr(vm, Ws2812Pixels());
break;
case 7: // # 07 : PixelSize void -> int
if (native) be_pushint(vm, Ws2812PixelSize());
else if (strip) be_pushint(vm, strip->PixelSize());
if (strip) be_pushint(vm, strip->PixelSize());
else be_pushint(vm, Ws2812PixelSize());
break;
case 8: // # 08 : PixelCount void -> int
if (native) be_pushint(vm, Ws2812PixelCount());
else if (strip) be_pushint(vm, strip->PixelCount());
if (strip) be_pushint(vm, strip->PixelCount());
else be_pushint(vm, Ws2812PixelCount());
break;
case 9: // # 09 : ClearTo (color:??) -> void
{
@ -227,11 +202,11 @@ extern "C" {
if (from < 0) { from = 0; }
if (len < 0) { len = 0; }
if (native) Ws2812ClearTo(r, g, b, w, from, from + len - 1);
else if (strip) strip->ClearTo(rgbw, from, from + len - 1);
if (strip) strip->ClearTo(rgbw, from, from + len - 1);
else Ws2812ClearTo(r, g, b, w, from, from + len - 1);
} else {
if (native) Ws2812ClearTo(r, g, b, w, -1, -1);
else if (strip) strip->ClearTo(rgbw);
if (strip) strip->ClearTo(rgbw);
else Ws2812ClearTo(r, g, b, w, -1, -1);
}
}
break;
@ -239,24 +214,24 @@ extern "C" {
{
int32_t idx = be_toint(vm, 3);
uint32_t wrgb = be_toint(vm, 4);
if (native) {
if (strip) {
strip->SetPixelColor(idx, wrgb);
} else {
uint8_t w = (wrgb >> 24) & 0xFF;
uint8_t r = (wrgb >> 16) & 0xFF;
uint8_t g = (wrgb >> 8) & 0xFF;
uint8_t b = (wrgb ) & 0xFF;
Ws2812SetPixelColor(idx, r, g, b, w);
} else if (strip) {
strip->SetPixelColor(idx, wrgb);
}
}
break;
case 11: // # 11 : GetPixelColor (idx:int) -> color:int wrgb
{
int32_t idx = be_toint(vm, 3);
if (native) {
be_pushint(vm, Ws2812GetPixelColor(idx));
} else if (strip) {
if (strip) {
be_pushint(vm, strip->GetPixelColor(idx));
} else {
be_pushint(vm, Ws2812GetPixelColor(idx));
}
}
break;

View File

@ -26,6 +26,7 @@
* Supported template fields:
* NAME - Template name
* BASE - Optional. 0 = use relative buttons and switches (default), 1 = use absolute buttons and switches
* IOCON - Optional. IOCON I/O Expander configuration register (bitmap: 0 MIRROR 0 DISSLW HAEN ODR INTPOL 0. Default 0b01011000 = 0x58)
* GPIO - Sequential list of pin 1 and up with configured GPIO function
* Function Code Description
* ------------------- -------- ----------------------------------------
@ -61,6 +62,9 @@
* Buttons and relays B1 B2 B3 B4 B5 B6 B7 B8 R1 R2 R3 R4 R5 R6 R7 R8
* {"NAME":"MCP23017 A=B1-8, B=R1-8","GPIO":[32,33,34,35,36,37,38,39,224,225,226,227,228,229,230,231]}
*
* Buttons and relays with open-drain INT B1 B2 B3 B4 B5 B6 B7 B8 R1 R2 R3 R4 R5 R6 R7 R8
* {"NAME":"MCP23017 A=B1-8, B=R1-8","GPIO":[32,33,34,35,36,37,38,39,224,225,226,227,228,229,230,231],"IOCON":0x5C}
*
* Buttons, relays, buttons and relays B1 B2 B3 B4 B5 B6 B7 B8 R1 R2 R3 R4 R5 R6 R7 R8 B9 B10B11B12B13B14B15B16R9 R10 R11 R12 R13 R14 R15 R16
* {"NAME":"MCP23017 A=B1-8, B=R1-8, C=B9-16, D=R9-16","GPIO":[32,33,34,35,36,37,38,39,224,225,226,227,228,229,230,231,40,41,42,43,44,45,46,47,232,233,234,235,236,237,238,239]}
*
@ -90,6 +94,8 @@
* MCP23017 support
\*********************************************************************************************/
#define D_JSON_IOCON "IOCON"
enum MCP23S08GPIORegisters {
MCP23X08_IODIR = 0x00,
MCP23X08_IPOL = 0x01,
@ -145,6 +151,20 @@ typedef struct {
int8_t pin_int;
} tMcp23xDevice;
typedef union { // Restricted by MISRA-C Rule 18.4 but so useful...
uint8_t reg; // Allow bit manipulation using template IOCON
struct {
uint8_t spare0 : 1; // 0 Unimplemented
uint8_t INTPOL : 1; // (0) INT pin active-low. (1) active-high
uint8_t ODR : 1; // (0) INT pin active driver output. (1) Open-drain output (overrides INTPOL)
uint8_t HAEN : 1; // (1) Hardware Address enabled (MCS23S17 only)
uint8_t DISSLW : 1; // (0) SDA output slew rate disabled
uint8_t SEQOP : 1; // 0 Sequential operation enabled, address pointer increments
uint8_t MIRROR : 1; // (1) INT pins are internally connected
uint8_t BANK : 1; // 0 Registers are in the same bank (addresses are sequential) (MCS23x17 only)
};
} tIOCON;
struct MCP230 {
tMcp23xDevice device[MCP23XXX_MAX_DEVICES];
uint32_t relay_inverted;
@ -156,6 +176,7 @@ struct MCP230 {
uint8_t relay_offset;
uint8_t button_max;
uint8_t switch_max;
tIOCON iocon;
int8_t button_offset;
int8_t switch_offset;
bool base;
@ -339,15 +360,15 @@ void MCP23xPinMode(uint8_t pin, uint8_t flags) {
}
switch (flags) {
case INPUT:
MCP23xUpdate(pin, true, iodir);
MCP23xUpdate(pin, false, gppu);
MCP23xUpdate(pin, true, iodir); // Pin is configured as an input
MCP23xUpdate(pin, false, gppu); // Pull-up disabled
break;
case INPUT_PULLUP:
MCP23xUpdate(pin, true, iodir);
MCP23xUpdate(pin, true, gppu);
MCP23xUpdate(pin, true, iodir); // Pin is configured as an input
MCP23xUpdate(pin, true, gppu); // Pull-up enabled
break;
case OUTPUT:
MCP23xUpdate(pin, false, iodir);
MCP23xUpdate(pin, false, iodir); // Pin is configured as an output
break;
}
@ -371,21 +392,21 @@ void MCP23xPinInterruptMode(uint8_t pin, uint8_t interrupt_mode) {
}
switch (interrupt_mode) {
case MCP23XXX_CHANGE:
MCP23xUpdate(pin, true, gpinten);
MCP23xUpdate(pin, false, intcon);
MCP23xUpdate(pin, true, gpinten); // Enable GPIO input pin for interrupt-on-change event
MCP23xUpdate(pin, false, intcon); // Pin value is compared against the previous pin value
break;
case MCP23XXX_RISING:
MCP23xUpdate(pin, true, gpinten);
MCP23xUpdate(pin, true, intcon);
MCP23xUpdate(pin, true, defval);
MCP23xUpdate(pin, true, gpinten); // Enable GPIO input pin for interrupt-on-change event
MCP23xUpdate(pin, true, intcon); // Controls how the associated pin value is compared for interrupt-on-change
MCP23xUpdate(pin, false, defval); // If the associated pin level is the opposite from the register bit, an interrupt occurs.
break;
case MCP23XXX_FALLING:
MCP23xUpdate(pin, true, gpinten);
MCP23xUpdate(pin, true, intcon);
MCP23xUpdate(pin, false, defval);
MCP23xUpdate(pin, true, gpinten); // Enable GPIO input pin for interrupt-on-change event
MCP23xUpdate(pin, true, intcon); // Controls how the associated pin value is compared for interrupt-on-change
MCP23xUpdate(pin, true, defval); // If the associated pin level is the opposite from the register bit, an interrupt occurs.
break;
case MCP23XXX_NO_INTERRUPT:
MCP23xUpdate(pin, false, gpinten);
MCP23xUpdate(pin, false, gpinten); // Disable GPIO input pin for interrupt-on-change event
break;
}
}
@ -394,7 +415,7 @@ void MCP23xSetPinModes(uint8_t pin, uint8_t flags) {
// pin 0 - 63
MCP23xPinMode(pin, flags);
if (Mcp23x.device[Mcp23x.chip].pin_int > -1) { // Mcp23x.chip is updated by call to MCP23xPinMode
MCP23xPinInterruptMode(pin, MCP23XXX_CHANGE);
MCP23xPinInterruptMode(pin, (Mcp23x.iocon.ODR) ? MCP23XXX_FALLING : MCP23XXX_CHANGE);
}
}
@ -457,6 +478,15 @@ uint32_t MCP23xGetPin(uint32_t lpin) {
/*********************************************************************************************/
bool MCP23xAddItem(uint8_t &item) {
if (item >= MAX_RELAYS_SET) { // MAX_RELAYS_SET = MAX_SWITCHES_SET = MAX_KEYS_SET = 32
AddLog(LOG_LEVEL_INFO, PSTR("MCP: Max reached"));
return false;
}
item++;
return true;
}
String MCP23xTemplateLoadFile(void) {
String mcptmplt = "";
#ifdef USE_UFILESYS
@ -497,7 +527,7 @@ bool MCP23xLoadTemplate(void) {
}
val = root[PSTR(D_JSON_NAME)];
if (val) {
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: Base %d, Template '%s'"), Mcp23x.base, val.getStr());
AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: IOCON 0x%02X, Base %d, Template '%s'"), Mcp23x.iocon, Mcp23x.base, val.getStr());
}
JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
if (arr) {
@ -507,44 +537,36 @@ bool MCP23xLoadTemplate(void) {
if (!val) { break; }
uint16_t mpin = val.getUInt();
if (mpin) { // Above GPIO_NONE
if ((mpin >= AGPIO(GPIO_SWT1)) && (mpin < (AGPIO(GPIO_SWT1) + MAX_SWITCHES_SET))) {
Mcp23x.switch_max++;
if ((mpin >= AGPIO(GPIO_SWT1)) && (mpin < (AGPIO(GPIO_SWT1) + MAX_SWITCHES_SET)) && MCP23xAddItem(Mcp23x.switch_max)) {
MCP23xSetPinModes(pin, INPUT_PULLUP);
}
else if ((mpin >= AGPIO(GPIO_SWT1_NP)) && (mpin < (AGPIO(GPIO_SWT1_NP) + MAX_SWITCHES_SET))) {
else if ((mpin >= AGPIO(GPIO_SWT1_NP)) && (mpin < (AGPIO(GPIO_SWT1_NP) + MAX_SWITCHES_SET)) && MCP23xAddItem(Mcp23x.switch_max)) {
mpin -= (AGPIO(GPIO_SWT1_NP) - AGPIO(GPIO_SWT1));
Mcp23x.switch_max++;
MCP23xSetPinModes(pin, INPUT);
}
else if ((mpin >= AGPIO(GPIO_KEY1)) && (mpin < (AGPIO(GPIO_KEY1) + MAX_KEYS_SET))) {
Mcp23x.button_max++;
else if ((mpin >= AGPIO(GPIO_KEY1)) && (mpin < (AGPIO(GPIO_KEY1) + MAX_KEYS_SET)) && MCP23xAddItem(Mcp23x.button_max)) {
MCP23xSetPinModes(pin, INPUT_PULLUP);
}
else if ((mpin >= AGPIO(GPIO_KEY1_NP)) && (mpin < (AGPIO(GPIO_KEY1_NP) + MAX_KEYS_SET))) {
else if ((mpin >= AGPIO(GPIO_KEY1_NP)) && (mpin < (AGPIO(GPIO_KEY1_NP) + MAX_KEYS_SET)) && MCP23xAddItem(Mcp23x.button_max)) {
mpin -= (AGPIO(GPIO_KEY1_NP) - AGPIO(GPIO_KEY1));
Mcp23x.button_max++;
MCP23xSetPinModes(pin, INPUT);
}
else if ((mpin >= AGPIO(GPIO_KEY1_INV)) && (mpin < (AGPIO(GPIO_KEY1_INV) + MAX_KEYS_SET))) {
else if ((mpin >= AGPIO(GPIO_KEY1_INV)) && (mpin < (AGPIO(GPIO_KEY1_INV) + MAX_KEYS_SET)) && MCP23xAddItem(Mcp23x.button_max)) {
bitSet(Mcp23x.button_inverted, mpin - AGPIO(GPIO_KEY1_INV));
mpin -= (AGPIO(GPIO_KEY1_INV) - AGPIO(GPIO_KEY1));
Mcp23x.button_max++;
MCP23xSetPinModes(pin, INPUT_PULLUP);
}
else if ((mpin >= AGPIO(GPIO_KEY1_INV_NP)) && (mpin < (AGPIO(GPIO_KEY1_INV_NP) + MAX_KEYS_SET))) {
else if ((mpin >= AGPIO(GPIO_KEY1_INV_NP)) && (mpin < (AGPIO(GPIO_KEY1_INV_NP) + MAX_KEYS_SET)) && MCP23xAddItem(Mcp23x.button_max)) {
bitSet(Mcp23x.button_inverted, mpin - AGPIO(GPIO_KEY1_INV_NP));
mpin -= (AGPIO(GPIO_KEY1_INV_NP) - AGPIO(GPIO_KEY1));
Mcp23x.button_max++;
MCP23xSetPinModes(pin, INPUT);
}
else if ((mpin >= AGPIO(GPIO_REL1)) && (mpin < (AGPIO(GPIO_REL1) + MAX_RELAYS_SET))) {
Mcp23x.relay_max++;
else if ((mpin >= AGPIO(GPIO_REL1)) && (mpin < (AGPIO(GPIO_REL1) + MAX_RELAYS_SET)) && MCP23xAddItem(Mcp23x.relay_max)) {
MCP23xPinMode(pin, OUTPUT);
}
else if ((mpin >= AGPIO(GPIO_REL1_INV)) && (mpin < (AGPIO(GPIO_REL1_INV) + MAX_RELAYS_SET))) {
else if ((mpin >= AGPIO(GPIO_REL1_INV)) && (mpin < (AGPIO(GPIO_REL1_INV) + MAX_RELAYS_SET)) && MCP23xAddItem(Mcp23x.relay_max)) {
bitSet(Mcp23x.relay_inverted, mpin - AGPIO(GPIO_REL1_INV));
mpin -= (AGPIO(GPIO_REL1_INV) - AGPIO(GPIO_REL1));
Mcp23x.relay_max++;
MCP23xPinMode(pin, OUTPUT);
}
else if (mpin == AGPIO(GPIO_OUTPUT_HI)) {
@ -558,14 +580,9 @@ bool MCP23xLoadTemplate(void) {
else { mpin = 0; }
Mcp23x_gpio_pin[pin] = mpin;
}
if ((Mcp23x.switch_max >= MAX_SWITCHES_SET) ||
(Mcp23x.button_max >= MAX_KEYS_SET) ||
(Mcp23x.relay_max >= MAX_RELAYS_SET)) {
AddLog(LOG_LEVEL_INFO, PSTR("MCP: Max reached (S%d/B%d/R%d)"), Mcp23x.switch_max, Mcp23x.button_max, Mcp23x.relay_max);
break;
}
}
Mcp23x.max_pins = pin; // Max number of configured pins
AddLog(LOG_LEVEL_INFO, PSTR("MCP: Pins %d (S%d/B%d/R%d)"), Mcp23x.max_pins, Mcp23x.switch_max, Mcp23x.button_max, Mcp23x.relay_max);
}
// AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: Pins %d, Mcp23x_gpio_pin %*_V"), Mcp23x.max_pins, Mcp23x.max_pins, (uint8_t*)Mcp23x_gpio_pin);
@ -582,6 +599,10 @@ uint32_t MCP23xTemplateGpio(void) {
JsonParserObject root = parser.getRootObject();
if (!root) { return 0; }
JsonParserToken val = root[PSTR(D_JSON_IOCON)];
if (val) {
Mcp23x.iocon.reg = val.getUInt() & 0x5E; // Only allow 0 MIRROR 0 DISSLW HAEN ODR INTPOL 0
}
JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
if (arr.isArray()) {
return arr.size(); // Number of requested pins
@ -590,6 +611,7 @@ uint32_t MCP23xTemplateGpio(void) {
}
void MCP23xModuleInit(void) {
Mcp23x.iocon.reg = 0b01011000; // Default 0x58 = Enable INT mirror, Disable Slew rate, HAEN pins for addressing
int32_t pins_needed = MCP23xTemplateGpio();
if (!pins_needed) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("MCP: Invalid template"));
@ -606,7 +628,8 @@ void MCP23xModuleInit(void) {
#endif
while ((Mcp23x.max_devices < MCP23XXX_MAX_DEVICES) && PinUsed(GPIO_MCP23SXX_CS, Mcp23x.max_devices)) {
Mcp23x.chip = Mcp23x.max_devices;
Mcp23x.device[Mcp23x.chip].pin_int = (PinUsed(GPIO_MCP23XXX_INT, Mcp23x.chip)) ? Pin(GPIO_MCP23XXX_INT, Mcp23x.chip) : -1;
uint32_t pin_int = (Mcp23x.iocon.ODR) ? 0 : Mcp23x.chip; // INT ODR pins are open-drain outputs and supposedly connected together to one GPIO
Mcp23x.device[Mcp23x.chip].pin_int = (PinUsed(GPIO_MCP23XXX_INT, pin_int)) ? Pin(GPIO_MCP23XXX_INT, pin_int) : -1;
Mcp23x.device[Mcp23x.chip].pin_cs = Pin(GPIO_MCP23SXX_CS, Mcp23x.max_devices);
digitalWrite(Mcp23x.device[Mcp23x.chip].pin_cs, 1);
pinMode(Mcp23x.device[Mcp23x.chip].pin_cs, OUTPUT);
@ -619,12 +642,14 @@ void MCP23xModuleInit(void) {
if (0x00 == buffer) { // MCP23S08
AddLog(LOG_LEVEL_INFO, PSTR("SPI: MCP23S08 found at CS%d"), Mcp23x.chip +1);
Mcp23x.device[Mcp23x.chip].pins = 8;
MCP23xWrite(MCP23X08_IOCON, 0b00011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing
// MCP23xWrite(MCP23X08_IOCON, 0b00011000); // Slew rate disabled, HAEN pins for addressing
MCP23xWrite(MCP23X08_IOCON, Mcp23x.iocon.reg & 0x3E);
Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X08_OLAT);
} else if (0x80 == buffer) { // MCP23S17
AddLog(LOG_LEVEL_INFO, PSTR("SPI: MCP23S17 found at CS%d"), Mcp23x.chip +1);
Mcp23x.device[Mcp23x.chip].pins = 16;
MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing
// MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing
MCP23xWrite(MCP23X17_IOCONA, Mcp23x.iocon.reg);
Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X17_OLATA);
Mcp23x.device[Mcp23x.chip].olatb = MCP23xRead(MCP23X17_OLATB);
}
@ -641,8 +666,9 @@ void MCP23xModuleInit(void) {
uint8_t mcp23xxx_address = MCP23XXX_ADDR_START;
while ((Mcp23x.max_devices < MCP23XXX_MAX_DEVICES) && (mcp23xxx_address < MCP23XXX_ADDR_END)) {
Mcp23x.chip = Mcp23x.max_devices;
uint32_t pin_int = (Mcp23x.iocon.ODR) ? 0 : Mcp23x.chip; // INT pins are open-drain outputs and supposedly connected together to one GPIO
if (I2cSetDevice(mcp23xxx_address)) {
Mcp23x.device[Mcp23x.chip].pin_int = (PinUsed(GPIO_MCP23XXX_INT, Mcp23x.chip)) ? Pin(GPIO_MCP23XXX_INT, Mcp23x.chip) : -1;
Mcp23x.device[Mcp23x.chip].pin_int = (PinUsed(GPIO_MCP23XXX_INT, pin_int)) ? Pin(GPIO_MCP23XXX_INT, pin_int) : -1;
Mcp23x.device[Mcp23x.chip].interface = MCP23X_I2C;
Mcp23x.device[Mcp23x.chip].address = mcp23xxx_address;
@ -652,7 +678,8 @@ void MCP23xModuleInit(void) {
if (0x00 == buffer) {
I2cSetActiveFound(mcp23xxx_address, "MCP23008");
Mcp23x.device[Mcp23x.chip].pins = 8;
MCP23xWrite(MCP23X08_IOCON, 0b00011000); // Slew rate disabled, HAEN pins for addressing
// MCP23xWrite(MCP23X08_IOCON, 0b00011000); // Slew rate disabled, HAEN pins for addressing
MCP23xWrite(MCP23X08_IOCON, Mcp23x.iocon.reg & 0x3E);
Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X08_OLAT);
Mcp23x.max_devices++;
}
@ -660,7 +687,8 @@ void MCP23xModuleInit(void) {
I2cSetActiveFound(mcp23xxx_address, "MCP23017");
Mcp23x.device[Mcp23x.chip].pins = 16;
MCP23xWrite(MCP23X08_IOCON, 0x00); // Reset bank mode to 0 (MCP23X17_GPINTENB)
MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing
// MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing
MCP23xWrite(MCP23X17_IOCONA, Mcp23x.iocon.reg);
Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X17_OLATA);
Mcp23x.device[Mcp23x.chip].olatb = MCP23xRead(MCP23X17_OLATB);
Mcp23x.max_devices++;
@ -691,6 +719,8 @@ void MCP23xModuleInit(void) {
return;
}
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("MCP: INT open-drain %d"), Mcp23x.iocon.ODR);
Mcp23x.relay_offset = TasmotaGlobal.devices_present;
Mcp23x.relay_max -= UpdateDevicesPresent(Mcp23x.relay_max);
@ -745,8 +775,10 @@ void MCP23xInit(void) {
} else {
gpio = MCP23xRead16(MCP23X17_GPIOA); // Clear MCP23x17 interrupt
}
if (Mcp23x.iocon.ODR && Mcp23x.chip) { continue; }
// pinMode(Mcp23x.device[Mcp23x.chip].pin_int, (Mcp23x.iocon.ODR) ? INPUT_PULLUP : INPUT);
pinMode(Mcp23x.device[Mcp23x.chip].pin_int, INPUT_PULLUP);
attachInterrupt(Mcp23x.device[Mcp23x.chip].pin_int, MCP23xInputIsr, CHANGE);
attachInterrupt(Mcp23x.device[Mcp23x.chip].pin_int, MCP23xInputIsr, (Mcp23x.iocon.ODR) ? FALLING : CHANGE);
}
}
}