/**************************************************************************** * This file is part of the project AqHome. * AqHome (c) by 2025 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 "./mdevices_vgraph.h" #include "aqhome-cgi/service/module.h" #include "aqhome-cgi/modules/mdataclient.h" #include #include #include //#include //#include #include #include #include /* ------------------------------------------------------------------------------------------------ * defs and enums * ------------------------------------------------------------------------------------------------ */ #define GBAS GWEN_Buffer_AppendString #define GBAA GWEN_Buffer_AppendArgs /* ------------------------------------------------------------------------------------------------ * vars * ------------------------------------------------------------------------------------------------ */ typedef struct MY_GRAPH_PARAMS MY_GRAPH_PARAMS; struct MY_GRAPH_PARAMS { const char *name; const char *title; const char *modifiers; int startTimeDiff; int acceptedAgeInSeconds; }; static MY_GRAPH_PARAMS _graphParams[]={ {"4h", "last 4 hours", "Lm5", 4*60*60, 2*60}, {"1d", "last 24 hours", "Lm30", 24*60*60, 5*60}, {"1w", "last 7 days", "Lm240", 7*24*60*60, 15*60}, {"1m", "last 30 days", "Lm480", 30*24*60*60, 60*60}, {"6m", "last 6 months", "Lm720", 182*24*60*60, 60*60}, {"12m","last 12 months", "Lm1440", 365*24*60*60, 60*60}, {NULL, NULL, NULL, 0, 0} }; /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static void _runGraphValueWithArgs(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName, GWEN_BUFFER *dbuf); static void _createGraph(AQH_DATACLIENT *dc, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, const char *graphTitle, int precision, const char *curveLabel, const char *sImgFile, int imgWidth, int imgHeight, uint64_t numDataPoints); static AQDG_GRAPH *_mkGraphObjectWithTitle(const char *graphTitle, const MY_GRAPH_PARAMS *graphParams, int precision); static void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, GWEN_BUFFER *dbuf); static const MY_GRAPH_PARAMS *_getParamsByName(const char *s); /* ------------------------------------------------------------------------------------------------ * code * ------------------------------------------------------------------------------------------------ */ void AQH_ModDevices_RunGraphValue(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf) { GWEN_DB_NODE *dbQuery; const char *sDeviceName; const char *sValueName; DBG_DEBUG(NULL, "GraphValue"); dbQuery=AQCGI_Request_GetDbQuery(rq); sDeviceName=GWEN_DB_GetCharValue(dbQuery, "device", 0, NULL); sValueName=GWEN_DB_GetCharValue(dbQuery, "value", 0, NULL); DBG_DEBUG(NULL, "Device=%s, value=%s", sDeviceName?sDeviceName:"", sValueName?sValueName:""); if (sDeviceName && *sDeviceName && sValueName && *sValueName) { GWEN_BUFFER *bufDeviceName; GWEN_BUFFER *bufValueName; bufDeviceName=GWEN_Buffer_new(0, 64, 0, 1); GWEN_Text_UnescapeToBufferTolerant(sDeviceName, bufDeviceName); bufValueName=GWEN_Buffer_new(0, 64, 0, 1); GWEN_Text_UnescapeToBufferTolerant(sValueName, bufValueName); _runGraphValueWithArgs(m, rq, dc, GWEN_Buffer_GetStart(bufDeviceName), GWEN_Buffer_GetStart(bufValueName), dbuf); GWEN_Buffer_free(bufValueName); GWEN_Buffer_free(bufDeviceName); } } void _runGraphValueWithArgs(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName, GWEN_BUFFER *dbuf) { GWEN_DB_NODE *dbQuery; AQH_VALUE *value; const MY_GRAPH_PARAMS *graphParams; const char *sPeriod; DBG_DEBUG(NULL, "GraphValue with args"); dbQuery=AQCGI_Request_GetDbQuery(rq); sPeriod=GWEN_DB_GetCharValue(dbQuery, "period", 0, NULL); graphParams=_getParamsByName(sPeriod); if (graphParams==NULL) graphParams=&_graphParams[0]; DBG_DEBUG(NULL, "Device=%s, value=%s, period=%s", sDeviceName?sDeviceName:"", sValueName?sValueName:"", sPeriod?sPeriod:""); value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName); if (value) { GWEN_BUFFER *fbuf; fbuf=GWEN_Buffer_new(0, 256, 0, 1); _mkPathForValueAndPeriod(m, value, graphParams, fbuf); if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(fbuf), graphParams->acceptedAgeInSeconds)) { DBG_DEBUG(NULL, "Creating graph"); _createGraph(dc, value, graphParams, sValueName, 2, AQH_ValueModality_toString(AQH_Value_GetModality(value)), GWEN_Buffer_GetStart(fbuf), AQH_MODDEVICES_GRAPH_WIDTH, AQH_MODDEVICES_GRAPH_HEIGHT, 100000); } AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(fbuf), "image/png", dbuf); GWEN_Buffer_free(fbuf); AQH_Value_free(value); } else { DBG_ERROR(NULL, "Could not get value \"%s/%s\"", sDeviceName, sValueName); } } void _createGraph(AQH_DATACLIENT *dc, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, const char *graphTitle, int precision, const char *curveLabel, const char *sImgFile, int imgWidth, int imgHeight, uint64_t numDataPoints) { const char *sValue; AQDG_GRAPH *g; AQDG_DRAW_CONTEXT *drawContext; AQDG_OBJECT *graphObject; uint64_t tsBegin; uint64_t tsEnd; AQDG_GRAPH_DATAPAIR_LIST *dpList; sValue=AQH_Value_GetNameForSystem(v); tsEnd=time(0); tsBegin=time(0)-(graphParams->startTimeDiff); g=_mkGraphObjectWithTitle(graphTitle, graphParams, precision); dpList=AQH_ModDevices_RequestDataPairList(dc, sValue, tsBegin, tsEnd, numDataPoints); if (dpList) { DBG_DEBUG(NULL, "Adding data for %s", sValue); AQDG_TimeGraph_ModifyDataAndAddCurve(g, curveLabel?curveLabel:sValue, graphParams->modifiers, dpList); } else { DBG_ERROR(NULL, "No data for %s", sValue); AQDG_Graph_free(g); return; } AQDG_TimeGraph_SetupTicks(g, 0, 0.0, 0.0); DBG_DEBUG(NULL, "Draw graph for %s", sValue); drawContext=AQDG_Draw_ContextCairo_Png_new(sImgFile, imgWidth, imgHeight); graphObject=AQDG_GraphWidget_new(NULL, AQDG_OBJECT_OPTIONS_STRETCHX | AQDG_OBJECT_OPTIONS_STRETCHY, drawContext); AQDG_Object_SetWidth(graphObject, imgWidth); AQDG_Object_SetHeight(graphObject, imgHeight); AQDG_GraphWidget_SetupDefaultPens(graphObject); AQDG_GraphWidget_SetupDefaultFonts(graphObject); AQDG_GraphWidget_FinishWithGraph(graphObject, g); AQDG_Object_free(graphObject); } AQDG_GRAPH *_mkGraphObjectWithTitle(const char *graphTitle, const MY_GRAPH_PARAMS *graphParams, int precision) { AQDG_GRAPH *g; GWEN_BUFFER *tbuf; tbuf=GWEN_Buffer_new(0, 256, 0, 1); GBAA(tbuf, "%s - %s", graphTitle, graphParams->title); g=AQDG_TimeGraph_new(GWEN_Buffer_GetStart(tbuf), NULL, "Value", NULL, precision); GWEN_Buffer_free(tbuf); return g; } void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_PARAMS *graphParams, GWEN_BUFFER *dbuf) { AQH_SERVICE *sv; const char *s; sv=AQH_ModService_GetService(m); /* cache folder */ s=AQH_Service_GetCacheFolder(sv); GBAA(dbuf, "%s%s", s, GWEN_DIR_SEPARATOR_S); /* var name */ s=AQH_Value_GetNameForSystem(v); AQH_ModService_EscapeToBuffer(s, dbuf); GBAA(dbuf, "-%s.png", graphParams->name); } const MY_GRAPH_PARAMS *_getParamsByName(const char *s) { const MY_GRAPH_PARAMS *p; p=_graphParams; while(p->name) { if (strcasecmp(p->name, s)==0) return p; p++; } return NULL; }