Files
aqhomecontrol/apps/aqhome-react/units/u_timeprogram.c
2025-04-21 00:29:53 +02:00

341 lines
9.4 KiB
C

/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2024 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./u_timeprogram_p.h"
#include "aqhome-react/types/prgrule.h"
#include <gwenhywfar/debug.h>
#include <gwenhywfar/xml.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/gwendate.h>
#include <gwenhywfar/gwentime.h>
/* ------------------------------------------------------------------------------------------------
* defines
* ------------------------------------------------------------------------------------------------
*/
#define AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT 0
#define AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEFORNEXT_MINS 60 /* 1h */
#define AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEEVERY_SECS (15*60) /* 15m */
#define AQHOMEREACT_UNIT_TIMEPROGRAM_CHECKINTERVAL_SEC 60 /* 1m */
GWEN_INHERIT(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM);
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static MODULE_TIMER_ACTION *ModuleTimerAction_new(int64_t triggerTime, double value);
static void ModuleTimerAction_free(MODULE_TIMER_ACTION *act);
static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static int _cbProcess(AQHREACT_UNIT *unit);
static int _readRules(AQHREACT_UNIT *unit);
static int _checkAndAddActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit);
static int _addActionsForNextMinutes(AQHREACT_UNIT_TIMEPROGRAM *xunit, int minutes,
const AQHREACT_PRGRULE_LIST *ruleList, MODULE_TIMER_ACTION_LIST *actionList);
static int _handlePendingActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
GWEN_LIST_FUNCTIONS(MODULE_TIMER_ACTION, ModuleTimerAction);
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ModuleTimerAction
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
MODULE_TIMER_ACTION *ModuleTimerAction_new(int64_t triggerTime, double value)
{
MODULE_TIMER_ACTION *act;
GWEN_NEW_OBJECT(MODULE_TIMER_ACTION, act);
GWEN_LIST_INIT(MODULE_TIMER_ACTION, act);
act->triggerTime=triggerTime;
act->value=value;
return act;
}
void ModuleTimerAction_free(MODULE_TIMER_ACTION *act)
{
if (act) {
GWEN_LIST_FINI(MODULE_TIMER_ACTION, act);
GWEN_FREE_OBJECT(act);
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* AqHomeReact_UnitTimeProgram
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
AQHREACT_UNIT *AqHomeReact_UnitTimeProgram_new(AQH_OBJECT *aqh)
{
AQHREACT_UNIT *unit;
AQHREACT_UNIT_TIMEPROGRAM *xunit;
AQHREACT_PORT *port;
AQHREACT_PARAM *param;
unit=AQHREACT_Unit_new(aqh);
AQHREACT_Unit_SetTypeName(unit, "timeraction");
AQHREACT_Unit_SetDescription(unit, "Programmable timer");
GWEN_NEW_OBJECT(AQHREACT_UNIT_TIMEPROGRAM, xunit);
GWEN_INHERIT_SETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit, xunit, _freeData);
xunit->actionList=ModuleTimerAction_List_new();
xunit->lastActionHour=-1;
AQHREACT_Unit_SetProcessFn(unit, _cbProcess);
port=AQHREACT_Port_new();
AQHREACT_Port_SetName(port, "output");
AQHREACT_Port_SetIdForUnit(port, AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT);
AQHREACT_Port_SetDataType(port, AQHREACT_DATAOBJECTTYPE_DOUBLE);
AQHREACT_Unit_AddOutputPort(unit, port);
param=AQHREACT_Param_new();
AQHREACT_Param_SetName(param, AQHOMEREACT_UNIT_TIMEPROGRAM_PARAM_RULES);
AQHREACT_Param_SetDataType(param, AQHREACT_DATAOBJECTTYPE_STRING);
AQHREACT_Unit_AddParam(unit, param);
xunit->nextAddTime=GWEN_Timestamp_NowInLocalTime();
return unit;
}
void GWENHYWFAR_CB _freeData(GWEN_UNUSED void *bp, void *p)
{
AQHREACT_UNIT_TIMEPROGRAM *xunit;
xunit=(AQHREACT_UNIT_TIMEPROGRAM*) p;
ModuleTimerAction_List_free(xunit->actionList);
AQHREACT_PrgRule_List_free(xunit->ruleList);
GWEN_Timestamp_free(xunit->nextAddTime);
GWEN_FREE_OBJECT(xunit);
}
int _cbProcess(AQHREACT_UNIT *unit)
{
int didSomething=0;
if (unit) {
AQHREACT_UNIT_TIMEPROGRAM *xunit;
xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
if (xunit) {
int rv;
if (xunit->ruleList==NULL) {
DBG_INFO(NULL, "Reading rules");
rv=_readRules(unit);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
didSomething=1;
}
rv=_checkAndAddActions(unit, xunit);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
if (rv>0)
didSomething=1;
}
}
return didSomething?1:0;
}
int _checkAndAddActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit)
{
int didSomething=0;
xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
if (xunit) {
time_t now;
int rv;
now=time(NULL);
if ((now-xunit->lastCreateTime)>AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEEVERY_SECS) {
rv=_addActionsForNextMinutes(xunit,
AQHOMEREACT_UNIT_TIMEPROGRAM_CREATEFORNEXT_MINS,
xunit->ruleList,
xunit->actionList);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
if (rv>0)
didSomething=1;
xunit->lastCreateTime=now;
}
if ((now-xunit->lastCheckTime)>AQHOMEREACT_UNIT_TIMEPROGRAM_CHECKINTERVAL_SEC) {
rv=_handlePendingActions(unit, xunit);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
if (rv>0)
didSomething=1;
xunit->lastCheckTime=now;
}
}
return didSomething?1:0;
}
int _handlePendingActions(AQHREACT_UNIT *unit, AQHREACT_UNIT_TIMEPROGRAM *xunit)
{
int valueSent=0;
if (unit && xunit) {
AQHREACT_PORT *outputPort;
outputPort=AQHREACT_Unit_GetOutputPortByIdForUnit(unit, AQHOMEREACT_UNIT_TIMEPROGRAM_OUTSLOT_OUTPUT);
if (outputPort) {
GWEN_TIMESTAMP *ts;
int64_t nowInt64;
MODULE_TIMER_ACTION *action;
ts=GWEN_Timestamp_NowInLocalTime();
nowInt64=ts?GWEN_Timestamp_toInt64(ts):0;
GWEN_Timestamp_free(ts);
xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
action=ModuleTimerAction_List_First(xunit->actionList);
while(action) {
MODULE_TIMER_ACTION *nextAction;
nextAction=ModuleTimerAction_List_Next(action);
if (action->triggerTime<nowInt64) {
ModuleTimerAction_List_Del(action);
DBG_ERROR(NULL, "Sending output value %.2f", action->value);
AQHREACT_Unit_OutputDoubleData(unit, outputPort, action->value);
ModuleTimerAction_free(action);
valueSent=1;
}
action=nextAction;
}
}
}
return valueSent?1:0;
}
int _addActionsForNextMinutes(AQHREACT_UNIT_TIMEPROGRAM *xunit,
int minutes,
const AQHREACT_PRGRULE_LIST *ruleList,
MODULE_TIMER_ACTION_LIST *actionList)
{
int actionsAdded=0;
int i;
GWEN_TIMESTAMP *tsWork;
tsWork=GWEN_Timestamp_NowInLocalTime();
for (i=0; i<minutes; i++) {
if (GWEN_Timestamp_Compare(tsWork, xunit->nextAddTime)==1) {
int64_t triggerTime;
const AQHREACT_PRGRULE *rule;
triggerTime=GWEN_Timestamp_toInt64(tsWork);
rule=AQHREACT_PrgRule_List_First(ruleList);
while(rule) {
if (AQHREACT_PrgRule_Matches(rule,
GWEN_Timestamp_GetMinute(tsWork),
GWEN_Timestamp_GetHour(tsWork),
GWEN_Timestamp_GetDay(tsWork),
GWEN_Timestamp_GetMonth(tsWork),
GWEN_Timestamp_GetWeekDay(tsWork))>0) {
MODULE_TIMER_ACTION *act;
DBG_ERROR(NULL, "- rule matches for %s, creating action", GWEN_Timestamp_GetString(tsWork));
act=ModuleTimerAction_new(triggerTime, AQHREACT_PrgRule_GetValue(rule));
ModuleTimerAction_List_Add(act, actionList);
actionsAdded=1;
}
rule=AQHREACT_PrgRule_List_Next(rule);
} /* while */
}
GWEN_Timestamp_AddSeconds(tsWork, 60); /* next min */
} /* for */
GWEN_Timestamp_free(xunit->nextAddTime);
xunit->nextAddTime=tsWork;
return actionsAdded?1:0;
}
int _readRules(AQHREACT_UNIT *unit)
{
AQHREACT_UNIT_TIMEPROGRAM *xunit;
const char *s;
xunit=GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_TIMEPROGRAM, unit);
AQHREACT_PrgRule_List_free(xunit->ruleList);
xunit->ruleList=NULL;
s=AQHREACT_Unit_GetParamValueString(unit, AQHOMEREACT_UNIT_TIMEPROGRAM_PARAM_RULES, NULL);
if (s) {
xunit->ruleList=AQHREACT_PrgRule_ReadRules(s);
if (xunit->ruleList==NULL) {
DBG_INFO(NULL, "Error reading rules from [%s]", s);
return GWEN_ERROR_BAD_DATA;
}
return 0;
}
else {
DBG_INFO(NULL, "No rules");
return GWEN_ERROR_NO_DATA;
}
}