/**************************************************************************** * 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 #endif #include "./u_timeprogram_p.h" #include "aqhome-react/types/prgrule.h" #include #include #include #include #include /* ------------------------------------------------------------------------------------------------ * 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->triggerTimevalue); 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; inextAddTime)==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; } }