6.4.1.11 Rewrite interlock

6.4.1.11 20190124
 * Remove command SetOption14 as it has been superseded by command Interlock
 * Remove command SetOption63 as it has been superseded by command Interlock
 * Add command Interlock 0 / 1 / 1,2 3,4 .. to control interlock ON/OFF and add up to 8 relays in 1 to 4 interlock groups (#5014)
This commit is contained in:
Theo Arends 2019-01-25 17:46:27 +01:00
parent eab6be8bcb
commit 505c4794d1
7 changed files with 108 additions and 56 deletions

View File

@ -1,4 +1,9 @@
/* 6.4.1.10 20190121
/* 6.4.1.11 20190124
* Remove command SetOption14 as it has been superseded by command Interlock
* Remove command SetOption63 as it has been superseded by command Interlock
* Add command Interlock 0 / 1 / 1,2 3,4 .. to control interlock ON/OFF and add up to 8 relays in 1 to 4 interlock groups (#5014)
*
* 6.4.1.10 20190121
* Fix Hass discovery of MHZ19(B) sensors (#4992)
* Fix Hass Software Watchdog exception during discovery (#4988)
* Add support for MAX44009 Ambient Light sensor (#4907)

View File

@ -72,6 +72,7 @@
#define D_JSON_FROM "from"
#define D_JSON_GAS "Gas"
#define D_JSON_GATEWAY "Gateway"
#define D_JSON_GROUPS "Groups"
#define D_JSON_HEAPSIZE "Heap"
#define D_JSON_HIGH "High"
#define D_JSON_HOST_NOT_FOUND "Host not found"
@ -241,6 +242,7 @@
#define D_WCFG_6_SERIAL "Serial"
#define D_CMND_FRIENDLYNAME "FriendlyName"
#define D_CMND_SWITCHMODE "SwitchMode"
#define D_CMND_INTERLOCK "Interlock"
#define D_CMND_TELEPERIOD "TelePeriod"
#define D_CMND_RESTART "Restart"
#define D_JSON_ONE_TO_RESTART "1 to restart"

View File

@ -76,7 +76,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t sleep_normal : 1; // bit 10 (v6.3.0.15) - SetOption60 - Enable normal sleep instead of dynamic sleep
uint32_t button_switch_force_local : 1;// bit 11 (v6.3.0.16) - SetOption61 - Force local operation when button/switch topic is set
uint32_t no_pullup : 1; // bit 12 (v6.4.1.7) - SetOption62 - Force no pull-up (0 = (no)pull-up, 1 = no pull-up)
uint32_t split_interlock : 1; // bit 13 (v6.4.1.8) - SetOption63 - Split interlock on CH4
uint32_t spare13 : 1;
uint32_t spare14 : 1;
uint32_t spare15 : 1;
uint32_t spare16 : 1;
@ -286,9 +286,7 @@ struct SYSCFG {
uint16_t light_wakeup; // 4A6
byte knx_CB_registered; // 4A8 Number of Group Address to write
char web_password[33]; // 4A9
uint8_t ex_switchmode[4]; // 4CA Free since 6.0.0a
uint8_t interlock[MAX_INTERLOCKS]; // 4CA
char ntp_server[3][33]; // 4CE
byte ina219_mode; // 531
uint16_t pulse_timer[MAX_PULSETIMERS]; // 532

View File

@ -976,7 +976,7 @@ void SettingsDelta(void)
if (Settings.version < 0x06000002) {
for (byte i = 0; i < MAX_SWITCHES; i++) {
if (i < 4) {
Settings.switchmode[i] = Settings.ex_switchmode[i];
Settings.switchmode[i] = Settings.interlock[i];
} else {
Settings.switchmode[i] = SWITCH_MODE;
}
@ -1022,6 +1022,9 @@ void SettingsDelta(void)
Settings.flag3.mdns_enabled = MDNS_ENABLED;
Settings.param[P_MDNS_DELAYED_START] = 0;
}
if (Settings.version < 0x0604010B) {
for (byte i = 0; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; }
}
Settings.version = VERSION;
SettingsSave(1);

View File

@ -51,6 +51,7 @@ typedef unsigned long power_t; // Power (Relay) type
// Changes to the following MAX_ defines will impact settings layout
#define MAX_SWITCHES 8 // Max number of switches
#define MAX_RELAYS 8 // Max number of relays
#define MAX_INTERLOCKS 4 // Max number of interlock groups (MAX_RELAYS / 2)
#define MAX_LEDS 4 // Max number of leds
#define MAX_KEYS 4 // Max number of keys or buttons
#define MAX_PWMS 5 // Max number of PWM channels

View File

@ -75,7 +75,7 @@ enum TasmotaCommands {
CMND_MODULE, CMND_MODULES, CMND_GPIO, CMND_GPIOS, CMND_PWM, CMND_PWMFREQUENCY, CMND_PWMRANGE, CMND_COUNTER, CMND_COUNTERTYPE,
CMND_COUNTERDEBOUNCE, CMND_BUTTONDEBOUNCE, CMND_SWITCHDEBOUNCE, CMND_SLEEP, CMND_UPGRADE, CMND_UPLOAD, CMND_OTAURL, CMND_SERIALLOG, CMND_SYSLOG,
CMND_LOGHOST, CMND_LOGPORT, CMND_IPADDRESS, CMND_NTPSERVER, CMND_AP, CMND_SSID, CMND_PASSWORD, CMND_HOSTNAME,
CMND_WIFICONFIG, CMND_FRIENDLYNAME, CMND_SWITCHMODE,
CMND_WIFICONFIG, CMND_FRIENDLYNAME, CMND_SWITCHMODE, CMND_INTERLOCK,
CMND_TELEPERIOD, CMND_RESTART, CMND_RESET, CMND_TIMEZONE, CMND_TIMESTD, CMND_TIMEDST, CMND_ALTITUDE, CMND_LEDPOWER, CMND_LEDSTATE,
CMND_I2CSCAN, CMND_SERIALSEND, CMND_BAUDRATE, CMND_SERIALDELIMITER, CMND_DRIVER };
const char kTasmotaCommands[] PROGMEM =
@ -85,7 +85,7 @@ const char kTasmotaCommands[] PROGMEM =
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_COUNTER "|" D_CMND_COUNTERTYPE "|"
D_CMND_COUNTERDEBOUNCE "|" D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SLEEP "|" D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|" D_CMND_SERIALLOG "|" D_CMND_SYSLOG "|"
D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" 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_WIFICONFIG "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|"
D_CMND_TELEPERIOD "|" D_CMND_RESTART "|" D_CMND_RESET "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|"
D_CMND_I2CSCAN "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALDELIMITER "|" D_CMND_DRIVER;
@ -318,33 +318,19 @@ void SetDevicePower(power_t rpower, int source)
power = (1 << devices_present) -1;
rpower = power;
}
if (Settings.flag3.split_interlock) {
Settings.flag.interlock = 1; // prevent the situation where interlock is off and split-interlock is on
uint8_t mask = 0x01;
uint8_t count = 0;
byte result1 = 0;
byte result2 = 0;
for (byte i = 0; i < devices_present; i++) {
if (rpower & mask) {
if (i <2) { result1++;}//increment if low part is ON
if (i >1) { result2++;}//increment if high part is ON
}
mask <<= 1; // shift the bitmask one left (1,2,4,8) to find out what is on
}
if ((result1) >1 && (result2 >1)) {power = 0; rpower = 0;} // all 4 switch are on, something is wrong, so we turn all off
if ((result1) >1 && (result2 <2)) {power = power & 0x0C; rpower = power;} // 1/2 are both on and 3/4 max one is on
if ((result1) <2 && (result2 >1)) {power = power & 0x03; rpower = power;} // 1/2 max one is on and 3/4 both are on
} else {
if (Settings.flag.interlock) { // Allow only one or no relay set
for (byte i = 0; i < MAX_INTERLOCKS; i++) {
power_t mask = 1;
uint8_t count = 0;
for (byte i = 0; i < devices_present; i++) {
if (rpower & mask) count++;
for (byte j = 0; j < devices_present; j++) {
if ((Settings.interlock[i] & mask) && (rpower & mask)) { count++; }
mask <<= 1;
}
if (count > 1) {
power = 0;
rpower = 0;
mask = ~Settings.interlock[i]; // Turn interlocked group off as there would be multiple relays on
power &= mask;
rpower &= mask;
}
}
}
@ -754,6 +740,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len)
case 6: // mqtt_button_retain (CMND_BUTTONRETAIN)
case 7: // mqtt_switch_retain (CMND_SWITCHRETAIN)
case 9: // mqtt_sensor_retain (CMND_SENSORRETAIN)
case 14: // interlock (CMND_INTERLOCK)
case 22: // mqtt_serial (SerialSend and SerialLog)
case 23: // mqtt_serial_raw (SerialSend)
case 25: // knx_enabled (Web config)
@ -1181,6 +1168,74 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len)
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, Settings.switchmode[index-1]);
}
else if (CMND_INTERLOCK == command_code) { // Interlock 0 - Off, Interlock 1 - On, Interlock 1,2 3,4 5,6,7
uint8_t max_relays = devices_present;
if (light_type) { max_relays--; }
if (max_relays > sizeof(Settings.interlock[0]) * 8) { max_relays = sizeof(Settings.interlock[0]) * 8; }
if (max_relays > 1) { // Only interlock with more than 1 relay
if (data_len > 0) {
if (strstr(dataBuf, ",")) { // Interlock entry
for (byte i = 0; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; } // Reset current interlocks
char *group;
char *q;
uint8_t group_index = 0;
power_t relay_mask = 0;
for (group = strtok_r(dataBuf, " ", &q); group && group_index < MAX_INTERLOCKS; group = strtok_r(NULL, " ", &q)) {
char *str;
for (str = strtok_r(group, ",", &p); str; str = strtok_r(NULL, ",", &p)) {
int pbit = atoi(str);
if ((pbit > 0) && (pbit <= max_relays)) { // Only valid relays
pbit--;
if (!bitRead(relay_mask, pbit)) { // Only relay once
bitSet(relay_mask, pbit);
bitSet(Settings.interlock[group_index], pbit);
}
}
}
group_index++;
}
for (byte i = 0; i < group_index; i++) {
uint8_t minimal_bits = 0;
for (byte j = 0; j < max_relays; j++) {
if (bitRead(Settings.interlock[i], j)) { minimal_bits++; }
}
if (minimal_bits < 2) { Settings.interlock[i] = 0; } // Discard single relay as interlock
}
} else {
Settings.flag.interlock = payload &1; // Enable/disable interlock
if (Settings.flag.interlock) {
SetDevicePower(power, SRC_IGNORE); // Remove multiple relays if set
}
}
}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_INTERLOCK "\":\"%s\",\"" D_JSON_GROUPS "\":\""), GetStateText(Settings.flag.interlock));
uint8_t anygroup = 0;
for (byte i = 0; i < MAX_INTERLOCKS; i++) {
if (Settings.interlock[i]) {
anygroup++;
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s"), mqtt_data, (anygroup > 1) ? " " : "");
uint8_t anybit = 0;
power_t mask = 1;
for (byte j = 0; j < max_relays; j++) {
if (Settings.interlock[i] & mask) {
anybit++;
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s%d"), mqtt_data, (anybit > 1) ? "," : "", j +1);
}
mask <<= 1;
}
}
}
if (!anygroup) {
for (byte j = 1; j <= max_relays; j++) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s%d"), mqtt_data, (j > 1) ? "," : "", j);
}
}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"}"), mqtt_data);
} else {
Settings.flag.interlock = 0;
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.interlock));
}
}
else if (CMND_TELEPERIOD == command_code) {
if ((payload >= 0) && (payload < 3601)) {
Settings.tele_period = (1 == payload) ? TELE_PERIOD : payload;
@ -1400,41 +1455,29 @@ void ExecuteCommandPower(byte device, byte state, int source)
}
if ((device < 1) || (device > devices_present)) device = 1;
if (device <= MAX_PULSETIMERS) { SetPulseTimer(device -1, 0); }
power_t mask = 1 << (device -1);
power_t mask = 1 << (device -1); // Device to control
if (state <= POWER_TOGGLE) {
if ((blink_mask & mask)) {
blink_mask &= (POWER_MASK ^ mask); // Clear device mask
MqttPublishPowerBlinkState(device);
}
if (Settings.flag3.split_interlock && !Settings.flag.interlock ) Settings.flag.interlock=1; // ensure interlock is on, in case split_interlock is on
// check if channel 1/2 or 3/4 are to be changed
if (device <= 2 && Settings.flag3.split_interlock ) { // channel 1/2 are changed
if (Settings.flag3.split_interlock && !interlock_mutex) { // Clear all but masked relay, but only if we are not already doing something
if (Settings.flag.interlock && !interlock_mutex) { // Clear all but masked relay in interlock group
interlock_mutex = 1;
for (byte i = 0; i < 2; i++) {
byte imask = 0x01 << i;
if ((power & imask) && (mask != imask)) { ExecuteCommandPower(i +1, POWER_OFF, SRC_IGNORE); delay(50); }// example, first power is ON but the pushed button is not the first, then powerOFF the first one
for (byte i = 0; i < MAX_INTERLOCKS; i++) {
if (Settings.interlock[i] & mask) { // Find interlock group
for (byte j = 0; j < devices_present; j++) {
power_t imask = 1 << j;
if ((Settings.interlock[i] & imask) && (power & imask) && (mask != imask)) {
ExecuteCommandPower(j +1, POWER_OFF, SRC_IGNORE);
}
interlock_mutex = 0; // avoid infinite loop due to recursive requests
}
} else { // channel 3/4 are changed
if (Settings.flag3.split_interlock && !interlock_mutex) { // only start if we are on interlock split and have no re-call
interlock_mutex = 1;
for (byte i = 2; i < devices_present; i++) {
byte imask = 0x01 << i;
if ((power & imask) && (mask != imask)) ExecuteCommandPower(i +1, POWER_OFF, SRC_IGNORE);
}
interlock_mutex = 0;
}
}
if ( Settings.flag.interlock && !interlock_mutex && !Settings.flag3.split_interlock) { //execute regular interlock-mode as interlock-split is off
interlock_mutex = 1;
for (byte i = 0; i < devices_present; i++) {
power_t imask = 1 << i;
if ((power & imask) && (mask != imask)) ExecuteCommandPower(i +1, POWER_OFF, SRC_IGNORE);
break; // An interlocked relay is only present in one group so quit
}
}
interlock_mutex = 0;
}
switch (state) {
case POWER_OFF: {
power &= (POWER_MASK ^ mask);

View File

@ -20,7 +20,7 @@
#ifndef _SONOFF_VERSION_H_
#define _SONOFF_VERSION_H_
#define VERSION 0x0604010A
#define VERSION 0x0604010B
#define D_PROGRAMNAME "Sonoff-Tasmota"
#define D_AUTHOR "Theo Arends"