Allows my home wiz lights to be timed on and off according to a schedule.

Dependents:   heating

wiz/wiz-sched.c

Committer:
andrewboyson
Date:
2021-06-09
Revision:
7:3035a540ef65
Parent:
6:09ca95ef1cdd

File content as of revision 7:3035a540ef65:

#include <stdint.h>
#include <string.h>

#include "wiz-list.h"
#include "wiz-sun.h"
#include "wiz-sched.h"
#include "settings.h"
#include "http.h"
#include "clk.h"
#include "log.h"
#include "mac.h"
#include "tm.h"

#define MINIMUM_DURATION_MINUTES 1

#define DEBUG false

struct item
{
    char    name[WIZ_LIST_NAME_LENGTH]; //Name or '*' for broadcast
    char    ondatum;
    int16_t onminutes;
    char    offdatum;
    int16_t offminutes;
};
static struct item items[WIZ_SCHED_MAX_ITEMS];

void WizSchedSetName    (int i, char*             name) { strncpy  ( items[i].name, name, WIZ_LIST_NAME_LENGTH); SetWizSchedName    (i, name ); }
void WizSchedSetOnEvent (int i, char datum, int16_t minutes)
{
    items[i].ondatum   = datum  ;
    items[i].onminutes = minutes;
    SetWizSchedOnDatum  (i, &datum  );
    SetWizSchedOnMinutes(i, &minutes);
}
void WizSchedSetOffEvent(int i, char datum, int16_t minutes)
{
    items[i].offdatum   = datum;
    items[i].offminutes = minutes;
    SetWizSchedOffDatum  (i, &datum  );
    SetWizSchedOffMinutes(i, &minutes);
}

static int minutesTodayUtc;
static int minutesSunriseUtc;
static int minutesSunsetUtc;
static bool isDst;

int WizSchedMinutesUtcToLocal(int minutes)
{
    if (isDst) minutes += TM_DST_OFFSET * 60;
    else       minutes += TM_STD_OFFSET * 60;
    if (minutes >= 1440) minutes -= 1440;
    return minutes;
}
int WizSchedMinutesLocalToUtc(int minutes)
{
    if (isDst) minutes -= TM_DST_OFFSET * 60;
    else       minutes -= TM_STD_OFFSET * 60;
    if (minutes < 0) minutes += 1440;
    return minutes;
}

static int getOnMinuteUtc(int i)
{
    switch (items[i].ondatum)
    {
        case 'U': return items[i].onminutes;
        case 'L': return WizSchedMinutesLocalToUtc(items[i].onminutes);
        case 'R': return items[i].onminutes + minutesSunriseUtc;
        case 'S': return items[i].onminutes + minutesSunsetUtc;
    }
    return -1;
}
static int getOffMinuteUtc(int i)
{
    switch (items[i].offdatum)
    {
        case 'U': return items[i].offminutes;
        case 'L': return WizSchedMinutesLocalToUtc(items[i].offminutes);
        case 'R': return items[i].offminutes + minutesSunriseUtc;
        case 'S': return items[i].offminutes + minutesSunsetUtc;
    }
    return -1;
}
static int subtractMinutes(int a, int b)
{
    int d = a - b;
    if (d >= 1080) d -= 1440; //Positive durations upto and including 18 hours are ok
    if (d <  -360) d += 1440; //Negative durations over 6 hours are assumed to be next day
    return d;
}
static void onOffHttp(char datum, int minutes)
{
    bool isNegative = minutes < 0;
    if (isNegative) minutes = -minutes;
    switch (datum)
    {
        case 'L':
            if (isNegative) HttpAddChar('-') ;
            HttpAddF("%02d", minutes / 60);
            HttpAddChar('h');
            HttpAddF("%02d", minutes % 60);
            break;
        case 'S':
        case 'R':
            HttpAddChar(datum);
            if (minutes)
            {
                HttpAddChar(isNegative ? '-' : '+') ;
                if (minutes < 60)
                {
                    HttpAddF("%d", minutes);
                }
                else
                {
                    HttpAddF("%02d", minutes / 60);
                    HttpAddChar('h');
                    HttpAddF("%02d", minutes % 60);
                }
            }
            break;
        case 'D':
            HttpAddChar(isNegative ? '-' : '+') ;
            HttpAddF("%02d", minutes / 60);
            HttpAddChar('h');
            HttpAddF("%02d", minutes % 60);
            break;
    }
}
void WizSchedParseEvent(char* text, char* pDatum, int16_t* pMinutes)
{
    if (!text || !pDatum || !pMinutes) return;
    
    *pDatum = 'L'; //Default to local
    bool isNegative = false;
    int minutes = 0;
    int hours = 0;
    for (int i = 0; i < 10 && text[i]; i++)
    {
        switch (text[i])
        {
            case 'H':
            case 'h':
                hours = minutes;
                minutes = 0;
                break;
            case 'L':
            case 'l':
                *pDatum = 'L';
                break;
            case 'R':
            case 'r':
                *pDatum = 'R';
                break;
            case 'S':
            case 's':
                *pDatum = 'S';
                break;
            case '0': minutes = minutes * 10 + 0; break;
            case '1': minutes = minutes * 10 + 1; break;
            case '2': minutes = minutes * 10 + 2; break;
            case '3': minutes = minutes * 10 + 3; break;
            case '4': minutes = minutes * 10 + 4; break;
            case '5': minutes = minutes * 10 + 5; break;
            case '6': minutes = minutes * 10 + 6; break;
            case '7': minutes = minutes * 10 + 7; break;
            case '8': minutes = minutes * 10 + 8; break;
            case '9': minutes = minutes * 10 + 9; break;
            case '+': isNegative = false; break;
            case '-': isNegative = true;  break;
        }
    }
    *pMinutes = hours * 60 + minutes;
    if (isNegative) *pMinutes = -*pMinutes;
}

void WizSchedMoveUp(int i)
{
    if (i <= 0) return; //Cannot move further up
    struct item item;
    strncpy  ( item.name, items[i].name, WIZ_LIST_NAME_LENGTH);
    item.ondatum = items[i].ondatum;
    item.onminutes = items[i].onminutes;
    item.offdatum = items[i].offdatum;
    item.offminutes = items[i].offminutes;
    
    WizSchedSetName    (i,   items[i-1].name);
    WizSchedSetOnEvent (i,   items[i-1].ondatum, items[i-1].onminutes);
    WizSchedSetOffEvent(i,   items[i-1].offdatum, items[i-1].offminutes);

    WizSchedSetName    (i-1, item.name      );
    WizSchedSetOnEvent (i-1, item.ondatum,  item.onminutes);
    WizSchedSetOffEvent(i-1, item.offdatum, item.offminutes);
}
void WizSchedMoveDown(int i)
{
    if (i >= WIZ_LIST_MAX_ITEMS - 1) return; //Cannot move further down
    struct item item;
    strncpy  ( item.name, items[i].name, WIZ_LIST_NAME_LENGTH);
    item.ondatum = items[i].ondatum;
    item.onminutes = items[i].onminutes;
    item.offdatum = items[i].offdatum;
    item.offminutes = items[i].offminutes;
    
    WizSchedSetName    (i,   items[i+1].name);
    WizSchedSetOnEvent (i,   items[i+1].ondatum, items[i+1].onminutes);
    WizSchedSetOffEvent(i,   items[i+1].offdatum, items[i+1].offminutes);

    WizSchedSetName    (i+1, item.name      );
    WizSchedSetOnEvent (i+1, item.ondatum, item.onminutes);
    WizSchedSetOffEvent(i+1, item.offdatum, item.offminutes);
}
void WizSchedHttp()
{
    int i = 0;
    while (i < WIZ_SCHED_MAX_ITEMS)
    {
        if (items[i].name[0])
        {
            HttpAddTextN(items[i].name, WIZ_LIST_NAME_LENGTH);
            HttpAddChar('\t');
            
            onOffHttp(items[i].ondatum, items[i].onminutes );
            HttpAddChar('\t');
            
            onOffHttp(items[i].offdatum, items[i].offminutes );
            HttpAddChar('\t');
            
            int  onMinuteUtc =  getOnMinuteUtc(i);
            int offMinuteUtc = getOffMinuteUtc(i);
            int duration = subtractMinutes(offMinuteUtc, onMinuteUtc);
            
            
            onOffHttp('L',  WizSchedMinutesUtcToLocal(onMinuteUtc));
            HttpAddChar('\t');
            
            onOffHttp('L', WizSchedMinutesUtcToLocal(offMinuteUtc));
            HttpAddChar('\t');
            
            onOffHttp('L', duration); 
        }
        HttpAddChar('\n');
        i++;
    }
}
static void doSchedule(int i)
{   
    if (items[i].name[0] == 0) return;
    
    int onMinuteUtc  = getOnMinuteUtc(i);
    int offMinuteUtc = getOffMinuteUtc(i);
    
    //Don't switch on for short or negative durations durations
    int duration = subtractMinutes(offMinuteUtc, onMinuteUtc);
    if (duration < MINIMUM_DURATION_MINUTES) return;
    
    if (onMinuteUtc == minutesTodayUtc)
    {
        if (items[i].name[0] == '*')
        {
            if (DEBUG) LogTimeF("Wiz schedule - turn all on\r\n");
            WizListTurnAllOn();
        }
        else
        {
            if (DEBUG) LogTimeF("Wiz schedule - turn %s on\r\n", items[i].name);
            WizListTurnRoomOn(items[i].name);
        }
    }
    if (offMinuteUtc == minutesTodayUtc)
    {
        if (items[i].name[0] == '*')
        {
            if (DEBUG) LogTimeF("Wiz schedule - turn all off\r\n");
            WizListTurnAllOff();
        }
        else
        {
            if (DEBUG) LogTimeF("Wiz schedule - turn %s off\r\n", items[i].name);
            WizListTurnRoomOff(items[i].name);
        }
    }
}

void WizSchedMain()
{   
    if (!ClkTimeIsSet()) return;
    
    struct tm tmUtc;
    ClkNowTmUtc  (&tmUtc);
    
    minutesTodayUtc   = tmUtc.tm_hour   * 60 + tmUtc.tm_min;
    isDst = tmUtc.tm_isdst;
    
    minutesSunsetUtc  = WizSunSetMinutes();
    minutesSunriseUtc = WizSunRiseMinutes();
    
    static int minutesLastScan = -1;
    if (minutesLastScan == minutesTodayUtc) return; //Start of new minute one-shot
    minutesLastScan = minutesTodayUtc;
    
    for (int i = 0; i < WIZ_SCHED_MAX_ITEMS; i++) doSchedule(i);
}
void WizSchedInit()
{
    for (int i = 0; i < WIZ_SCHED_MAX_ITEMS; i++)
    {
        GetWizSchedName      (i,  items[i].name      );
        GetWizSchedOnDatum   (i, &items[i].ondatum   );
        GetWizSchedOnMinutes (i, &items[i].onminutes );
        GetWizSchedOffDatum  (i, &items[i].offdatum  );
        GetWizSchedOffMinutes(i, &items[i].offminutes);
    }
}