Skip to content

Commit

Permalink
v5.12.0i - Add timers
Browse files Browse the repository at this point in the history
5.12.0i
 * Add 16 timers using commands Timer and Timers (#1091)
  • Loading branch information
arendst committed Mar 23, 2018
1 parent d895341 commit d564615
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Sonoff-Tasmota
Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE.

Current version is **5.12.0h** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.
Current version is **5.12.0i** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.

### ATTENTION All versions

Expand Down
5 changes: 4 additions & 1 deletion sonoff/_releasenotes.ino
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* 5.12.0h
/* 5.12.0i
* Add 16 timers using commands Timer and Timers (#1091)
*
* 5.12.0h
* Add optional Arduino OTA support to be enabled in user_config.h (#1998)
* Add support for Software Serial bridge using commands SerialDelimiter, SBaudrate and SSerialSend. Supports 8N1 and text only (#2190)
* Add support for Hardware Serial bridge using commands SerialDelimiter, Baudrate and SerialSend. Supports 8N1 and text only (#2182)
Expand Down
10 changes: 10 additions & 0 deletions sonoff/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,16 @@
#define D_CMND_SBAUDRATE "SBaudrate"
#define D_JSON_SSERIALRECEIVED "SSerialReceived"

// Commands xdrv_09_timers.ino
#define D_CMND_TIMER "Timer"
#define D_JSON_TIMER_ARM "Arm"
#define D_JSON_TIMER_TIME "Time"
#define D_JSON_TIMER_DAYS "Days"
#define D_JSON_TIMER_REPEAT "Repeat"
#define D_JSON_TIMER_DEVICE "Device"
#define D_JSON_TIMER_POWER "Power"
#define D_CMND_TIMERS "Timers"

/********************************************************************************************/

#ifndef MY_LANGUAGE
Expand Down
19 changes: 19 additions & 0 deletions sonoff/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ typedef union {
};
} SysBitfield2;

typedef union {
uint32_t data;
struct {
uint32_t time : 11; // bits 0 - 10 = minutes in a day
uint32_t mday : 5; // bits 11 - 15 = optional day in a month
uint32_t days : 7; // bits 16 - 22 = week day mask
uint32_t device : 4; // bits 23 - 26 = 16 devices
uint32_t power : 2; // bits 27 - 28 = 4 power states - Off, On, Toggle
uint32_t repeat : 1; // bit 29
uint32_t arm : 1; // bit 30
uint32_t spare : 1; // bit 31
};
} Timer;

struct SYSCFG {
unsigned long cfg_holder; // 000
unsigned long save_flag; // 004
Expand Down Expand Up @@ -236,6 +250,11 @@ struct SYSCFG {
uint16_t pulse_counter_debounce; // 5D2
uint8_t rf_code[17][9]; // 5D4

byte free_66d[3]; // 66D

Timer timer[MAX_TIMERS]; // 670

// 6B0 - FFF free locations
} Settings;

struct RTCMEM {
Expand Down
3 changes: 3 additions & 0 deletions sonoff/settings.ino
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,9 @@ void SettingsDelta()
Settings.sbaudrate = SOFT_BAUDRATE / 1200;
Settings.serial_delimiter = 0xff;
}
if (Settings.version < 0x050C0009) {
memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS);
}

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 @@ -43,6 +43,7 @@ typedef unsigned long power_t; // Power (Relay) type
#define MAX_SWITCHES 4 // Max number of switches
#define MAX_PWMS 5 // Max number of PWM channels
#define MAX_COUNTERS 4 // Max number of counter sensors
#define MAX_TIMERS 16 // Max number of Timers
#define MAX_PULSETIMERS 8 // Max number of supported pulse timers
#define MAX_FRIENDLYNAMES 4 // Max number of Friendly names
#define MAX_DOMOTICZ_IDX 4 // Max number of Domoticz device, key and switch indices
Expand Down
2 changes: 1 addition & 1 deletion sonoff/sonoff.ino
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- Select IDE Tools - Flash Size: "1M (no SPIFFS)"
====================================================*/

#define VERSION 0x050C0008 // 5.12.0h
#define VERSION 0x050C0009 // 5.12.0i

// Location specific includes
#include <core_version.h> // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
Expand Down
26 changes: 26 additions & 0 deletions sonoff/support.ino
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,32 @@ char* Unescape(char* buffer, uint16_t* size)
return buffer;
}

char* UpperCase(char* dest, const char* source)
{
char* write = dest;
const char* read = source;
char ch = '.';

while (ch != '\0') {
ch = *read++;
*write++ = toupper(ch);
}
return dest;
}

char* UpperCase_P(char* dest, const char* source)
{
char* write = dest;
const char* read = source;
char ch = '.';

while (ch != '\0') {
ch = pgm_read_byte(read++);
*write++ = toupper(ch);
}
return dest;
}

boolean ParseIp(uint32_t* addr, const char* str)
{
uint8_t *part = (uint8_t*)addr;
Expand Down
2 changes: 2 additions & 0 deletions sonoff/user_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
#define NTP_SERVER2 "nl.pool.ntp.org" // [NtpServer2] Select second NTP server by name or IP address (5.39.184.5)
#define NTP_SERVER3 "0.nl.pool.ntp.org" // [NtpServer3] Select third NTP server by name or IP address (93.94.224.67)

#define USE_TIMERS // Add support for up to 16 timers (+2k2 code)

// -- Time - Start Daylight Saving Time and timezone offset from UTC in minutes
#define TIME_DST North, Last, Sun, Mar, 2, +120 // Northern Hemisphere, Last sunday in march at 02:00 +120 minutes

Expand Down
205 changes: 205 additions & 0 deletions sonoff/xdrv_09_timers.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
xdrv_09_timers.ino - timer support for Sonoff-Tasmota
Copyright (C) 2018 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef USE_TIMERS
/*********************************************************************************************\
* Timers
*
* Arm a timer using one or all of the following JSON values:
* {"Arm":1,"Time":"09:23","Days":"--TW--S","Repeat":1,"Device":1,"Power":1}
*
* Arm 0 = Off, 1 = On
* Time hours:minutes
* Days 7 day character mask starting with Sunday (SMTWTFS). 0 or - = Off, any other value = On
* Repeat 0 = Execute once, 1 = Execute again
* Device 1..16
* Power 0 = Off, 1 = On, 2 = Toggle, 3 = Blink
*
\*********************************************************************************************/

enum TimerCommands { CMND_TIMER, CMND_TIMERS };
const char kTimerCommands[] PROGMEM = D_CMND_TIMER "|" D_CMND_TIMERS ;

power_t fired = 0;

void TimerEverySecond()
{
if (RtcTime.valid) {
uint16_t time = (RtcTime.hour * 60) + RtcTime.minute;
uint8_t days = 1 << (RtcTime.day_of_week -1);

for (byte i = 0; i < MAX_TIMERS; i++) {
if (Settings.timer[i].arm) {
if (time == Settings.timer[i].time) {
if (!bitRead(fired, i) && (Settings.timer[i].days & days)) {
bitSet(fired, i);
Settings.timer[i].arm = Settings.timer[i].repeat;
ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power);
}
} else {
bitClear(fired, i);
}
}
}
}
}

void PrepShowTimer(uint8_t index)
{
char days[8] = { 0 };

index--;
for (byte i = 0; i < 7; i++) {
uint8_t mask = 1 << i;
snprintf(days, sizeof(days), "%s%d", days, ((Settings.timer[index].days & mask) > 0));
}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_DEVICE "\":%d,\"" D_JSON_TIMER_POWER "\":%d}"),
mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power);
}

/*********************************************************************************************\
* Commands
\*********************************************************************************************/

boolean TimerCommand()
{
char command [CMDSZ];
char dataBufUc[XdrvMailbox.data_len];
boolean serviced = true;
uint8_t index = XdrvMailbox.index;

UpperCase(dataBufUc, XdrvMailbox.data);
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kTimerCommands);
if ((CMND_TIMER == command_code) && (index > 0) && (index <= MAX_TIMERS)) {
uint8_t error = 0;

if (XdrvMailbox.data_len) {
StaticJsonBuffer<128> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(dataBufUc);
if (!root.success()) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_INVALID_JSON "\"}"), index); // JSON decode failed
error = 1;
}
else {
char parm_uc[10];

index--;
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ARM))].success()) {
Settings.timer[index].arm = (root[parm_uc] != 0);
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) {
uint16_t itime = 0;
uint8_t value = 0;
char time_str[10];

snprintf(time_str, sizeof(time_str), root[parm_uc]);
const char *substr = strtok(time_str, ":");
if (substr != NULL) {
value = atoi(substr);
if (value > 23) value = 23;
itime = value * 60;
substr = strtok(NULL, ":");
if (substr != NULL) {
value = atoi(substr);
if (value > 59) value = 59;
itime += value;
}
}
Settings.timer[index].time = itime;
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_DAYS))].success()) {
// SMTWTFS = 1234567 = 0011001 = 00TW00S = --TW--S
Settings.timer[index].days = 0;
const char *tday = root[parm_uc];
char ch = '.';

uint8_t i = 0;
while ((ch != '\0') && (i < 7)) {
ch = *tday++;
if (ch == '-') ch = '0';
uint8_t mask = 1 << i++;
Settings.timer[index].days |= (ch == '0') ? 0 : mask;
}
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_REPEAT))].success()) {
Settings.timer[index].repeat = (root[parm_uc] != 0);
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_DEVICE))].success()) {
Settings.timer[index].device = ((uint8_t)root[parm_uc] -1) & 0x0F;
}
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_POWER))].success()) {
Settings.timer[index].power = (uint8_t)root[parm_uc] & 0x03;
}
if (Settings.timer[index].arm) bitClear(fired, index);

index++;
}
}
if (!error) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{"));
PrepShowTimer(index);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
}
}
else if (CMND_TIMERS == command_code) {
byte jsflg = 0;
byte lines = 1;
for (byte i = 0; i < MAX_TIMERS; i++) {
if (!jsflg) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_TIMERS "%d\":{"), lines++);
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data);
}
jsflg = 1;
PrepShowTimer(i +1);
if ((strlen(mqtt_data) > (LOGSZ - TOPSZ)) || (i == MAX_TIMERS -1)) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}}"), mqtt_data);
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_TIMERS));
jsflg = 0;
}
}
mqtt_data[0] = '\0';
}
else serviced = false;

return serviced;
}

/*********************************************************************************************\
* Interface
\*********************************************************************************************/

#define XDRV_09

boolean Xdrv09(byte function)
{
boolean result = false;

switch (function) {
case FUNC_EVERY_SECOND:
TimerEverySecond();
break;
case FUNC_COMMAND:
result = TimerCommand();
break;
}
return result;
}

#endif // USE_TIMERS

1 comment on commit d564615

@tobox
Copy link

@tobox tobox commented on d564615 Mar 28, 2018

Choose a reason for hiding this comment

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

I cannot compile it because it does not find Ticker.h. Should that be added to lib_deps or did you forget to add that file to github?

EDIT: It works after unccomenting:

platform = espressif8266@1.6.0 ; v2.4.0

Strange, I though it was using that as default...

Please sign in to comment.