From c9d82cc88ea7ee965afb8e3cc1e4c34b551ec028 Mon Sep 17 00:00:00 2001 From: Martin Preuss Date: Fri, 26 Apr 2024 01:29:27 +0200 Subject: [PATCH] aqhome-react: added program rules with test code. --- apps/aqhome-react/init.c | 10 +- apps/aqhome-react/main.c | 66 ++++- apps/aqhome-react/types/0BUILD | 5 + apps/aqhome-react/types/prgrule-t.c | 156 +++++++++++ apps/aqhome-react/types/prgrule-t.h | 26 ++ apps/aqhome-react/types/prgrule.c | 418 ++++++++++++++++++++++++++++ apps/aqhome-react/types/prgrule.h | 35 +++ apps/aqhome-react/types/prgrule_p.h | 30 ++ 8 files changed, 733 insertions(+), 13 deletions(-) create mode 100644 apps/aqhome-react/types/prgrule-t.c create mode 100644 apps/aqhome-react/types/prgrule-t.h create mode 100644 apps/aqhome-react/types/prgrule.c create mode 100644 apps/aqhome-react/types/prgrule.h create mode 100644 apps/aqhome-react/types/prgrule_p.h diff --git a/apps/aqhome-react/init.c b/apps/aqhome-react/init.c index 551dc31..fb63f3c 100644 --- a/apps/aqhome-react/init.c +++ b/apps/aqhome-react/init.c @@ -73,6 +73,11 @@ int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv) DBG_ERROR(NULL, "Error reading args (%d)", rv); return rv; } + + s=GWEN_DB_GetCharValue(dbArgs, "params", 0, NULL); + if (s && *s && strcasecmp(s, "modtest")==0) + return rv; + AQH_MergeConfigFileIntoConfig(dbArgs, "ConfigFile"); aqh->dbArgs=dbArgs; @@ -340,7 +345,6 @@ int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs) I18S("Specify the device file"), I18S("Specify the device file") }, - { GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */ GWEN_ArgsType_Int, /* type */ @@ -354,7 +358,7 @@ int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs) } }; - rv=GWEN_Args_Check(argc, argv, 1, 0, args, dbArgs); + rv=GWEN_Args_Check(argc, argv, 1, GWEN_ARGS_MODE_ALLOW_FREEPARAM, args, dbArgs); if (rv==GWEN_ARGS_RESULT_ERROR) { fprintf(stderr, "ERROR: Could not parse arguments main\n"); return GWEN_ERROR_INVALID; @@ -377,7 +381,7 @@ int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs) GWEN_Buffer_free(ubuf); return GWEN_ERROR_CLOSE; } - return 0; + return rv; } diff --git a/apps/aqhome-react/main.c b/apps/aqhome-react/main.c index 403683a..884e263 100644 --- a/apps/aqhome-react/main.c +++ b/apps/aqhome-react/main.c @@ -15,6 +15,7 @@ #include "./loop.h" #include "./net_read.h" #include "aqhome-react/units/u_timer.h" +#include "aqhome-react/types/prgrule-t.h" #include "aqhome/aqhome.h" @@ -25,6 +26,7 @@ #include #include #include +#include #ifdef HAVE_SIGNAL_H # include @@ -60,6 +62,7 @@ */ static void _serve(AQHOME_REACT *aqh); +static int _testModules(int argc, char **argv); #ifdef HAVE_SIGNAL_H static int _setSignalHandlers(void); @@ -107,24 +110,32 @@ int main(int argc, char **argv) return 2; } + gui=GWEN_Gui_CGui_new(); + aqh=AqHomeReact_new(); rv=AqHomeReact_Init(aqh, argc, argv); + dbArgs=AqHomeReact_GetDbArgs(aqh); + DBG_ERROR(NULL, "RV=%d", rv); if (rv<0) { if (rv==GWEN_ERROR_CLOSE) return 1; DBG_INFO(NULL, "here (%d)", rv); return 2; } + else if (rv>0) { + argc-=rv; + argv+=rv; + rv=_testModules(argc, argv); + } + else { + s=GWEN_DB_GetCharValue(dbArgs, "charset", 0, NULL); + if (s && *s) + GWEN_Gui_SetCharSet(gui, s); + GWEN_Gui_SetGui(gui); - dbArgs=AqHomeReact_GetDbArgs(aqh); - - gui=GWEN_Gui_CGui_new(); - s=GWEN_DB_GetCharValue(dbArgs, "charset", 0, NULL); - if (s && *s) - GWEN_Gui_SetCharSet(gui, s); - GWEN_Gui_SetGui(gui); - - _serve(aqh); + _serve(aqh); + rv=0; + } AqHomeReact_Fini(aqh); AqHomeReact_free(aqh); @@ -132,7 +143,7 @@ int main(int argc, char **argv) GWEN_Gui_SetGui(NULL); GWEN_Gui_free(gui); - return 0; + return rv; } @@ -273,4 +284,39 @@ void _signalHandler(int s) +int _testModules(int argc, char **argv) +{ + GWEN_GUI *gui; + GWEN_TEST_FRAMEWORK *tf; + int rv; + + gui=GWEN_Gui_CGui_new(); + GWEN_Gui_SetGui(gui); + + tf=TestFramework_new(); + + rv=AQHREACT_PrgRule_AddTests(TestFramework_GetModulesRoot(tf)); + if (rv<0) { + fprintf(stderr, "Adding module failed.\n"); + return 2; + } + + argc--; + argv++; + rv=TestFramework_Run(tf, argc, argv); + if (rv) { + fprintf(stderr, "SomeError in tests failed.\n"); + GWEN_Gui_SetGui(NULL); + GWEN_Gui_free(gui); + return 2; + } + TestFramework_free(tf); + + GWEN_Gui_SetGui(NULL); + GWEN_Gui_free(gui); + return 0; +} + + + diff --git a/apps/aqhome-react/types/0BUILD b/apps/aqhome-react/types/0BUILD index e04d968..7fe78e3 100644 --- a/apps/aqhome-react/types/0BUILD +++ b/apps/aqhome-react/types/0BUILD @@ -47,6 +47,8 @@ param_p.h unit.h unit_p.h + prgrule.h + prgrule_p.h @@ -56,6 +58,7 @@ link.c param.c unit.c + prgrule.c @@ -69,6 +72,8 @@ + prgrule-t.h + prgrule-t.c diff --git a/apps/aqhome-react/types/prgrule-t.c b/apps/aqhome-react/types/prgrule-t.c new file mode 100644 index 0000000..e172329 --- /dev/null +++ b/apps/aqhome-react/types/prgrule-t.c @@ -0,0 +1,156 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + + +/* This file is included by "prgrule.c" */ + + +#include +#include "prgrule-t.h" + + +#ifdef AQHOME_ENABLE_TESTCODE + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static int GWENHYWFAR_CB test1(GWEN_TEST_MODULE *mod); +static int GWENHYWFAR_CB test2(GWEN_TEST_MODULE *mod); + +static int testAgainstValues(const char *ruleText, const uint64_t *ptrExpectedFields, double expectedValue); + + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + + +int AQHREACT_PrgRule_AddTests(GWEN_TEST_MODULE *mod) +{ + GWEN_TEST_MODULE *newMod; + + newMod=GWEN_Test_Module_AddModule(mod, "AQHREACT_PrgRule", NULL); + + GWEN_Test_Module_AddTest(newMod, "parse rules 1", test1, NULL); + GWEN_Test_Module_AddTest(newMod, "parse rules 2", test2, NULL); + + return 0; +} + + + +int test1(GWEN_UNUSED GWEN_TEST_MODULE *mod) +{ + static char ruleText[]="* * * * * 10.0"; + uint64_t expectedFields[]={ + 0b111111111111111111111111111111111111111111111111111111111111L, /* minute */ + 0b111111111111111111111111L, /* hour */ + 0b11111111111111111111111111111110L, /* day of month */ + 0b1111111111110L, /* month */ + 0b1111111L /* day of week (0=sunday) */ + }; + + return testAgainstValues(ruleText, expectedFields, 10.0); +} + + + +int test2(GWEN_UNUSED GWEN_TEST_MODULE *mod) +{ + static char ruleText[]="/2 4-7 1-5,8,15,23-25 * 0,3,5 12.3"; + uint64_t expectedFields[]={ + 0b010101010101010101010101010101010101010101010101010101010101L, /* minute */ + 0b000000000000000011110000L, /* hour */ + 0b00000011100000001000000100111110L, /* day of month */ + 0b1111111111110L, /* month */ + 0b0101001L /* day of week (0=sunday) */ + }; + + return testAgainstValues(ruleText, expectedFields, 12.3); +} + + + + +int testAgainstValues(const char *ruleText, const uint64_t *ptrExpectedFields, double expectedValue) +{ + AQHREACT_PRGRULE_LIST *prgRuleList; + AQHREACT_PRGRULE *prgRule; + + prgRuleList=AQHREACT_PrgRule_ReadRules(ruleText); + if (prgRuleList==NULL) { + DBG_ERROR(NULL, "Error reading rules list"); + return GWEN_ERROR_GENERIC; + } + + prgRule=AQHREACT_PrgRule_List_First(prgRuleList); + if (AQHREACT_PrgRule_List_Next(prgRule)) { + DBG_ERROR(NULL, "More than one rule read when only one given"); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->bitfieldMinute!=ptrExpectedFields[0]) { + DBG_ERROR(NULL, "Unexpected value for MINUTE: %016lX [%016lX]", prgRule->bitfieldMinute, ptrExpectedFields[0]); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->bitfieldHour!=ptrExpectedFields[1]) { + DBG_ERROR(NULL, "Unexpected value for HOUR: %016X [%016lX]", prgRule->bitfieldHour, ptrExpectedFields[1]); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->bitfieldDayOfMonth!=ptrExpectedFields[2]) { + DBG_ERROR(NULL, "Unexpected value for DAYOFMONTH: %016X [%016lX]", prgRule->bitfieldDayOfMonth, ptrExpectedFields[2]); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->bitfieldMonth!=ptrExpectedFields[3]) { + DBG_ERROR(NULL, "Unexpected value for MONTH: %016X [%016lX]", prgRule->bitfieldMonth, ptrExpectedFields[3]); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->bitfieldDayOfWeek!=ptrExpectedFields[4]) { + DBG_ERROR(NULL, "Unexpected value for DAYOFWEEK: %08X [%016lX]", prgRule->bitfieldDayOfWeek, ptrExpectedFields[4]); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + if (prgRule->value!=expectedValue) { + DBG_ERROR(NULL, "Unexpected value : %f", prgRule->value); + AQHREACT_PrgRule_List_free(prgRuleList); + return GWEN_ERROR_GENERIC; + } + + return 0; +} + + + + +#else + +int AQHREACT_PrgRule_AddTests(GWEN_TEST_MODULE *mod) +{ + DBG_ERROR(GWEN_LOGDOMAIN, "AqHome was compiled without test code enabled."); + return GWEN_ERROR_GENERIC; +} + + +#endif + + + diff --git a/apps/aqhome-react/types/prgrule-t.h b/apps/aqhome-react/types/prgrule-t.h new file mode 100644 index 0000000..797436b --- /dev/null +++ b/apps/aqhome-react/types/prgrule-t.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + +#ifndef AQHOME_REACT_PRGRULE_T_H +#define AQHOME_REACT_PRGRULE_T_H + + + +#include +#include + + + +/** + * Internal tests for AQHREACT_PrgRule. + */ +int AQHREACT_PrgRule_AddTests(GWEN_TEST_MODULE *mod); + + +#endif + diff --git a/apps/aqhome-react/types/prgrule.c b/apps/aqhome-react/types/prgrule.c new file mode 100644 index 0000000..fdc24cf --- /dev/null +++ b/apps/aqhome-react/types/prgrule.c @@ -0,0 +1,418 @@ +/**************************************************************************** + * 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 "./prgrule_p.h" + +#include +#include + +#include + + +GWEN_LIST_FUNCTIONS(AQHREACT_PRGRULE, AQHREACT_PrgRule) + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +const char *_readRule(AQHREACT_PRGRULE *prgRule, const char *s); + +const char *_readField(const char *s, uint64_t *ptrBitField, int minValue, int maxValue); +const char *_readRangeOpenBegin(const char *s, uint64_t *ptrBitField, int minValue, int maxValue); +const char *_readInterval(const char *s, uint64_t *ptrBitField, int minValue, int maxValue); +const char *_readValueAndProbablyRange(const char *s, uint64_t *ptrBitField, int minValue, int maxValue); +const char *_readDouble(const char *s, double *ptrDouble); + +int _valueOkay(int v, int minValue, int maxValue); +int _rangeOkay(int startValue, int endValue, int minValue, int maxValue); +void _setBitField(uint64_t *ptrBitField, int startValue, int endValue); + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + +AQHREACT_PRGRULE *AQHREACT_PrgRule_new(void) +{ + AQHREACT_PRGRULE *prgRule; + + GWEN_NEW_OBJECT(AQHREACT_PRGRULE, prgRule); + GWEN_LIST_INIT(AQHREACT_PRGRULE, prgRule); + + return prgRule; +} + + + +void AQHREACT_PrgRule_free(AQHREACT_PRGRULE *prgRule) +{ + if (prgRule) { + GWEN_LIST_FINI(AQHREACT_PRGRULE, prgRule); + GWEN_FREE_OBJECT(prgRule); + } +} + + + +int AQHREACT_PrgRule_Matches(const AQHREACT_PRGRULE *prgRule, int min, int hour, int dayOfMonth, int month, int dayOfWeek) +{ + if ((prgRule->bitfieldMonth & (1L<bitfieldDayOfMonth & (1L<bitfieldDayOfWeek & (1L<bitfieldHour & (1L<bitfieldMinute & (1L<bitfieldMinute=v; + + s=r; + v=0; + r=_readField(s, &v, 0, 23); + if (r==NULL) { + DBG_ERROR(NULL, "Error reading HOUR field at \"%s\"", s); + return NULL; + } + prgRule->bitfieldHour=(uint32_t)(v & 0xffffffffl); + + s=r; + v=0; + r=_readField(s, &v, 1, 31); + if (r==NULL) { + DBG_ERROR(NULL, "Error reading DAYOFMONTH field at \"%s\"", s); + return NULL; + } + prgRule->bitfieldDayOfMonth=(uint32_t)(v & 0xffffffffl); + + s=r; + v=0; + r=_readField(s, &v, 1, 12); + if (r==NULL) { + DBG_ERROR(NULL, "Error reading MONTH field at \"%s\"", s); + return NULL; + } + prgRule->bitfieldMonth=(uint16_t)(v & 0xffffl); + + s=r; + v=0; + r=_readField(s, &v, 0, 6); + if (r==NULL) { + DBG_ERROR(NULL, "Error reading DAYOFWEEK field at \"%s\"", s); + return NULL; + } + prgRule->bitfieldDayOfWeek=(uint8_t)(v & 0xffl); + + s=r; + r=_readDouble(s, &(prgRule->value)); + if (r==NULL) { + DBG_ERROR(NULL, "Error reading DOUBLE field at \"%s\"", s); + return NULL; + } + + return r; +} + + + +const char *_readField(const char *s, uint64_t *ptrBitField, int minValue, int maxValue) +{ + while(*s && isspace(*s)) + s++; + + if (*s==0) { + DBG_INFO(NULL, "Premature field end"); + return NULL; + } + + while(s && *s) { + if (*s=='*') { + _setBitField(ptrBitField, minValue, maxValue); + s++; + } + else if (*s=='-') { + s=_readRangeOpenBegin(++s, ptrBitField, minValue, maxValue); + if (s==NULL) { + DBG_INFO(NULL, "here"); + return NULL; + } + } + else if (*s=='/') { + s=_readInterval(++s, ptrBitField, minValue, maxValue); + if (s==NULL) { + DBG_INFO(NULL, "here"); + return NULL; + } + } + else if (isdigit(*s)) { + s=_readValueAndProbablyRange(s, ptrBitField, minValue, maxValue); + if (s==NULL) { + DBG_INFO(NULL, "here"); + return NULL; + } + } + else { + DBG_ERROR(NULL, "Unexpected character at \"%s\"", s); + return NULL; + } + + if (isspace(*s)){ + /* field done */ + return s; + } + else if (*s==',') { + s++; + } + else { + DBG_ERROR(NULL, "Unexpected character at \"%s\"", s); + return NULL; + } + } /* while */ + + return s; +} + + + +const char *_readRangeOpenBegin(const char *s, uint64_t *ptrBitField, int minValue, int maxValue) +{ + int v=0; + + if (!isdigit(*s)) { + DBG_ERROR(NULL, "Need at least start or end value for range"); + return NULL; + } + while(isdigit(*s)) { + v*=10; + v+=(*s)-'0'; + s++; + } + if (!_rangeOkay(minValue, v, minValue, maxValue)) { + DBG_INFO(NULL, "here"); + return NULL; + } + _setBitField(ptrBitField, minValue, v); + return s; +} + + + +const char *_readInterval(const char *s, uint64_t *ptrBitField, int minValue, int maxValue) +{ + int v=0; + int i; + + while(isdigit(*s)) { + v*=10; + v+=(*s)-'0'; + s++; + } + if (v==0) { + DBG_ERROR(NULL, "Bad value 0 for interval (/0)"); + return NULL; + } + + for (i=minValue; imaxValue) { + DBG_ERROR(NULL, "Value above maxmum value (%d > %d)", v, maxValue); + return 0; + } + + return 1; +} + + + +int _rangeOkay(int startValue, int endValue, int minValue, int maxValue) +{ + if (startValue>endValue) { + DBG_ERROR(NULL, "Startvalue (%d) > endvalue (%d)", startValue, endValue); + return 0; + } + + if (startValuemaxValue) { + DBG_ERROR(NULL, "Startvalue (%d) out of range (%d-%d)", startValue, minValue, maxValue); + return 0; + } + + if (endValue>maxValue) { + DBG_ERROR(NULL, "Endvalue (%d) out of range (%d-%d)", endValue, minValue, maxValue); + return 0; + } + + return 1; +} + + + +void _setBitField(uint64_t *ptrBitField, int startValue, int endValue) +{ + int i; + uint64_t m; + uint64_t v=0; + + m=1l< +#include + + +typedef struct AQHREACT_PRGRULE AQHREACT_PRGRULE; +GWEN_LIST_FUNCTION_DEFS(AQHREACT_PRGRULE, AQHREACT_PrgRule) + +AQHREACT_PRGRULE *AQHREACT_PrgRule_new(void); +void AQHREACT_PrgRule_free(AQHREACT_PRGRULE *prgRule); + +int AQHREACT_PrgRule_Matches(const AQHREACT_PRGRULE *prgRule, int min, int hour, int dayOfMonth, int month, int dayOfWeek); + +AQHREACT_PRGRULE *AQHREACT_PrgRule_List_FindMatchingRule(AQHREACT_PRGRULE_LIST *prgRuleList, + int min, int hour, int dayOfMonth, int month, int dayOfWeek); + + +AQHREACT_PRGRULE_LIST *AQHREACT_PrgRule_ReadRules(const char *s); + + +#endif + diff --git a/apps/aqhome-react/types/prgrule_p.h b/apps/aqhome-react/types/prgrule_p.h new file mode 100644 index 0000000..c99f58d --- /dev/null +++ b/apps/aqhome-react/types/prgrule_p.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + +#ifndef AQHOME_REACT_PRGRULE_P_H +#define AQHOME_REACT_PRGRULE_P_H + + +#include "aqhome-react/types/prgrule.h" + + +struct AQHREACT_PRGRULE { + GWEN_LIST_ELEMENT(AQHREACT_PRGRULE) + + uint64_t bitfieldMinute; + uint32_t bitfieldDayOfMonth; + uint32_t bitfieldHour; + uint16_t bitfieldMonth; + uint8_t bitfieldDayOfWeek; + + double value; +}; + + +#endif +