aqhome-react: added program rules with test code.

This commit is contained in:
Martin Preuss
2024-04-26 01:29:27 +02:00
parent 2342dfbe4a
commit c9d82cc88e
8 changed files with 733 additions and 13 deletions

View File

@@ -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;
}

View File

@@ -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 <gwenhywfar/debug.h>
#include <gwenhywfar/cgui.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/testframework.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
@@ -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;
}

View File

@@ -47,6 +47,8 @@
param_p.h
unit.h
unit_p.h
prgrule.h
prgrule_p.h
</headers>
<sources>
@@ -56,6 +58,7 @@
link.c
param.c
unit.c
prgrule.c
</sources>
<useTargets>
@@ -69,6 +72,8 @@
<extradist>
prgrule-t.h
prgrule-t.c
</extradist>

View File

@@ -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 <gwenhywfar/testframework.h>
#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

View File

@@ -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 <gwenhywfar/gwenhywfarapi.h>
#include <gwenhywfar/testframework.h>
/**
* Internal tests for AQHREACT_PrgRule.
*/
int AQHREACT_PrgRule_AddTests(GWEN_TEST_MODULE *mod);
#endif

View File

@@ -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 <config.h>
#endif
#include "./prgrule_p.h"
#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>
#include <ctype.h>
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<<month)) &&
(prgRule->bitfieldDayOfMonth & (1L<<dayOfMonth)) &&
(prgRule->bitfieldDayOfWeek & (1L<<dayOfWeek)) &&
(prgRule->bitfieldHour & (1L<<hour)) &&
(prgRule->bitfieldMinute & (1L<<min)))
return 1;
return 0;
}
AQHREACT_PRGRULE *AQHREACT_PrgRule_List_FindMatchingRule(AQHREACT_PRGRULE_LIST *prgRuleList,
int min, int hour, int dayOfMonth, int month, int dayOfWeek)
{
AQHREACT_PRGRULE *prgRule;
prgRule=AQHREACT_PrgRule_List_First(prgRuleList);
while(prgRule) {
if (AQHREACT_PrgRule_Matches(prgRule, min, hour, dayOfMonth, month, dayOfWeek))
return prgRule;
prgRule=AQHREACT_PrgRule_List_Next(prgRule);
}
return NULL;
}
AQHREACT_PRGRULE_LIST *AQHREACT_PrgRule_ReadRules(const char *s)
{
AQHREACT_PRGRULE_LIST *prgRuleList;
prgRuleList=AQHREACT_PrgRule_List_new();
while(*s) {
AQHREACT_PRGRULE *prgRule;
const char *r;
prgRule=AQHREACT_PrgRule_new();
r=_readRule(prgRule, s);
if (r==NULL){
DBG_INFO(NULL, "Error parsing \"%s\"", s);
AQHREACT_PrgRule_free(prgRule);
AQHREACT_PrgRule_List_free(prgRuleList);
return NULL;
}
AQHREACT_PrgRule_List_Add(prgRule, prgRuleList);
s=r;
while(*s && isspace(*s))
s++;
if (*s==';')
s++;
}
if (AQHREACT_PrgRule_List_GetCount(prgRuleList)<1) {
AQHREACT_PrgRule_List_free(prgRuleList);
return NULL;
}
return prgRuleList;
}
const char *_readRule(AQHREACT_PRGRULE *prgRule, const char *s)
{
uint64_t v;
const char *r;
v=0;
r=_readField(s, &v, 0, 59);
if (r==NULL) {
DBG_ERROR(NULL, "Error reading MIN field at \"%s\"", s);
return NULL;
}
prgRule->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; i<maxValue; i+=v) {
_setBitField(ptrBitField, i, i);
}
return s;
}
const char *_readValueAndProbablyRange(const char *s, uint64_t *ptrBitField, int minValue, int maxValue)
{
int v=0;
while(isdigit(*s)) {
v*=10;
v+=(*s)-'0';
s++;
}
if (!_valueOkay(v, minValue, maxValue)) {
DBG_INFO(NULL, "here");
return NULL;
}
if (*s=='-') {
s=_readRangeOpenBegin(++s, ptrBitField, v, maxValue);
if (s==NULL) {
DBG_INFO(NULL, "here");
return NULL;
}
}
else {
if (!_valueOkay(v, minValue, maxValue)) {
DBG_INFO(NULL, "here");
return NULL;
}
_setBitField(ptrBitField, v, v);
}
return s;
}
const char *_readDouble(const char *s, double *ptrDouble)
{
const char *sStart;
int rv;
while(*s && isspace(*s))
s++;
sStart=s;
while(*s && (isdigit(*s) || *s=='.'))
s++;
rv=GWEN_Text_StringToDouble(sStart, ptrDouble);
if (rv<0) {
DBG_ERROR(NULL, "Error reading double: %d", rv);
return NULL;
}
return s;
}
int _valueOkay(int v, int minValue, int maxValue)
{
if (v<minValue) {
DBG_ERROR(NULL, "Value below minimum value (%d < %d)", v, minValue);
return 0;
}
if (v>maxValue) {
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 (startValue<minValue || startValue>maxValue) {
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<<startValue;
for (i=startValue; i<=endValue; i++) {
v|=m;
m<<=1;
}
*ptrBitField|=v;
}
#include "prgrule-t.c"

View File

@@ -0,0 +1,35 @@
/****************************************************************************
* 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_H
#define AQHOME_REACT_PRGRULE_H
#include "aqhome-react/aqhome_react.h"
#include <gwenhywfar/gwentime.h>
#include <gwenhywfar/list.h>
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

View File

@@ -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