Skip to content

Commit

Permalink
6.4.1.11 Rewrite interlock
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
arendst committed Jan 25, 2019
1 parent eab6be8 commit 505c479
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 56 deletions.
7 changes: 6 additions & 1 deletion sonoff/_changelog.ino
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 2 additions & 0 deletions sonoff/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 2 additions & 4 deletions sonoff/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion sonoff/settings.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions sonoff/sonoff.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
141 changes: 92 additions & 49 deletions sonoff/sonoff.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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;

Expand Down Expand Up @@ -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

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;
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
break; // An interlocked relay is only present in one group so quit
}
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);
}
interlock_mutex = 0;
}

switch (state) {
case POWER_OFF: {
power &= (POWER_MASK ^ mask);
Expand Down
2 changes: 1 addition & 1 deletion sonoff/sonoff_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

7 comments on commit 505c479

@localhost61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand the Interlock bit take the place of SetOption14, so an upgrade to 6.4.11 should not affect the settings of a Sonoff Dual used to control a roller shutter and that rely on this option. Correct ?

@arendst
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to execute interlock 1,2 once

@localhost61
Copy link
Contributor

@localhost61 localhost61 commented on 505c479 Jan 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, a chance I only have 13 roller shutters to upgrade.
Then, I guess it deserves a specific warning.
Or can it be resolved in settings.ino at little expense?

@meingraham
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xavier,

I tried to highlight Theo's wiki entry by making "deprecated" bold font. I just now added additional text to highlight the new alternative command syntax.

I agree that if a user is just upgrading the firmware and does not read the changelog or the wiki, they may be caught unaware as to why their interlock configuration no longer functions. Hopefully this will direct them to the wiki to confirm their understanding of the feature and there they will see the new information. I'm afraid that is the most that can be done. I have gotten into the habit of checking the changelog and wiki just to be sure before I upgrade. Of course, I always miss something ;-) But then that's on me.

Mike

@localhost61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael
In settings.ino, there is a specific mechanism that take into account the changes between versions and can help to solve those regressions. That code is used only once and should remain as small as possible, as always it's a balance cost/benefits.

@arendst
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll change the first byte of the interlock group in settings.ino to 0xFF to simulate the previous functionality.

Tomorrow.

@localhost61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wahoo, I can't imagine more efficient! Thanks!

Please sign in to comment.