/**************************************************************************** * 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 #include #include #include /* ------------------------------------------------------------------------------------------------ * defs and enums * ------------------------------------------------------------------------------------------------ */ #define GBAS GWEN_Buffer_AppendString #define GBAA GWEN_Buffer_AppendArgs enum { VALUEGRAPH_PERIOD_4H=1, VALUEGRAPH_PERIOD_1D, VALUEGRAPH_PERIOD_1W, VALUEGRAPH_PERIOD_1M }; /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static void _runGraphValueWithArgs(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName, GWEN_BUFFER *dbuf); static void _createGraph(AQH_DATACLIENT *dc, const AQH_VALUE *v, int period, const char *graphTitle, int precision, const char *curveLabel, const char *sImgFile, int imgWidth, int imgHeight, uint64_t numDataPoints); static int _fileIsCurrent(const char *sPath, int seconds); static AQDG_GRAPH *_mkGraphObjectWithTitle(const char *graphTitle, int period, int precision); static int _getPeriodFromString(const char *sPeriod); static int _getAcceptedAgeForPeriod(int period); static void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, int pPeriod, GWEN_BUFFER *dbuf); static uint64_t _getStartTimeForPeriod(int period); static const char *_getModifiersForPeriod(int period); static AQDG_GRAPH_DATAPAIR_LIST *_requestDataPairList(AQH_DATACLIENT *dc, const char *valueName, uint64_t tsBegin, uint64_t tsEnd, uint64_t num); static AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues); /* ------------------------------------------------------------------------------------------------ * 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_ERROR(NULL, "GraphValue"); dbQuery=AQCGI_Request_GetDbQuery(rq); sDeviceName=GWEN_DB_GetCharValue(dbQuery, "device", 0, NULL); sValueName=GWEN_DB_GetCharValue(dbQuery, "value", 0, NULL); DBG_ERROR(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, session, 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_SESSION *session, AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName, GWEN_BUFFER *dbuf) { GWEN_DB_NODE *dbQuery; AQH_VALUE *value; const char *sPeriod; int period; DBG_ERROR(NULL, "GraphValue with args"); dbQuery=AQCGI_Request_GetDbQuery(rq); sPeriod=GWEN_DB_GetCharValue(dbQuery, "period", 0, NULL); period=sPeriod?_getPeriodFromString(sPeriod):VALUEGRAPH_PERIOD_1D; DBG_ERROR(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; int rv; fbuf=GWEN_Buffer_new(0, 256, 0, 1); _mkPathForValueAndPeriod(m, value, period, fbuf); if (!_fileIsCurrent(GWEN_Buffer_GetStart(fbuf), _getAcceptedAgeForPeriod(period))) { DBG_ERROR(NULL, "Creating graph"); _createGraph(dc, value, period, sValueName, 2, AQH_ValueModality_toString(AQH_Value_GetModality(value)), GWEN_Buffer_GetStart(fbuf), AQH_MODDEVICES_GRAPH_WIDTH, AQH_MODDEVICES_GRAPH_HEIGHT, 100000); } if (1) { GWEN_BUFFER *ibuf; ibuf=GWEN_Buffer_new(0, 1024, 0, 1); // return file rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(fbuf), ibuf); if (rv<0) { DBG_ERROR(NULL, "here (%d)", rv); } else { GWEN_Buffer_Reset(dbuf); GWEN_Buffer_AppendBytes(dbuf, GWEN_Buffer_GetStart(ibuf), GWEN_Buffer_GetUsedBytes(ibuf)); AQCGI_Request_AddResponseHeaderData(rq, "Content-type: image/png"); AQCGI_Request_AddFlags(rq, AQH_MODSERVICE_RQFLAGS_RAWFILE); } GWEN_Buffer_free(ibuf); } GWEN_Buffer_free(fbuf); AQH_Value_free(value); } else { DBG_ERROR(NULL, "Could not get value"); } } int _fileIsCurrent(const char *sPath, int seconds) { struct stat sb; time_t t1; if (lstat(sPath, &sb)==-1) { DBG_ERROR(NULL, "Error on lstat(%s): %s (%d)", sPath, strerror(errno), errno); return 0; } t1=time(0); if ((t1-sb.st_mtime)<(time_t) seconds) { DBG_ERROR(NULL, "File %s is current", sPath); return 1; } return 0; } void _createGraph(AQH_DATACLIENT *dc, const AQH_VALUE *v, int period, 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; const char *sModifier; AQDG_GRAPH_DATAPAIR_LIST *dpList; sValue=AQH_Value_GetNameForSystem(v); tsEnd=time(0); tsBegin=_getStartTimeForPeriod(period); g=_mkGraphObjectWithTitle(graphTitle, period, precision); sModifier=_getModifiersForPeriod(period); DBG_ERROR(NULL, "Requesting data for %s", sValue); dpList=_requestDataPairList(dc, sValue, tsBegin, tsEnd, numDataPoints); if (dpList) { DBG_ERROR(NULL, "Adding data for %s", sValue); AQDG_TimeGraph_ModifyDataAndAddCurve(g, curveLabel?curveLabel:sValue, sModifier, dpList); } else { DBG_ERROR(NULL, "No data"); AQDG_Graph_free(g); return; } DBG_ERROR(NULL, "Setup ticks for %s", sValue); AQDG_TimeGraph_SetupTicks(g, 0, 0.0, 0.0); DBG_ERROR(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, int period, int precision) { AQDG_GRAPH *g; GWEN_BUFFER *tbuf; const char *s; tbuf=GWEN_Buffer_new(0, 256, 0, 1); switch(period) { case VALUEGRAPH_PERIOD_4H: s="last 4 hours"; break; case VALUEGRAPH_PERIOD_1D: s="last 24 hours"; break; case VALUEGRAPH_PERIOD_1W: s="last 7 days"; break; case VALUEGRAPH_PERIOD_1M: s="last 30 days"; break; default: s="last 24 hours"; break; } GBAA(tbuf, "%s - %s", graphTitle, s); g=AQDG_TimeGraph_new(GWEN_Buffer_GetStart(tbuf), NULL, "Value", NULL, precision); GWEN_Buffer_free(tbuf); return g; } int _getPeriodFromString(const char *sPeriod) { if (strcasecmp(sPeriod, "4h")==0) return VALUEGRAPH_PERIOD_4H; else if (strcasecmp(sPeriod, "1d")==0) return VALUEGRAPH_PERIOD_1D; else if (strcasecmp(sPeriod, "1w")==0) return VALUEGRAPH_PERIOD_1W; else if (strcasecmp(sPeriod, "1m")==0) return VALUEGRAPH_PERIOD_1M; return VALUEGRAPH_PERIOD_1D; } void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, int period, 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); GWEN_Text_EscapeToBuffer(s, dbuf); /* period */ switch(period) { case VALUEGRAPH_PERIOD_4H: s="4h"; break; case VALUEGRAPH_PERIOD_1D: s="1d"; break; case VALUEGRAPH_PERIOD_1W: s="1w"; break; case VALUEGRAPH_PERIOD_1M: s="1m"; break; default: s="1d"; break; } GBAA(dbuf, "-%s.png", s); } uint64_t _getStartTimeForPeriod(int period) { time_t t; t=time(0); /* period */ switch(period) { case VALUEGRAPH_PERIOD_4H: t-=4*60*60; break; case VALUEGRAPH_PERIOD_1D: t-=24*60*60; break; case VALUEGRAPH_PERIOD_1W: t-=7*24*60*60; break; case VALUEGRAPH_PERIOD_1M: t-=30*24*60*60; break; default: t-=24*60*60; break; } return (uint64_t) t; } const char *_getModifiersForPeriod(int period) { /* period */ switch(period) { case VALUEGRAPH_PERIOD_4H: return "La5"; case VALUEGRAPH_PERIOD_1D: return "La30"; case VALUEGRAPH_PERIOD_1W: return "La240"; case VALUEGRAPH_PERIOD_1M: return "La480"; default: return "La15"; } } int _getAcceptedAgeForPeriod(int period) { /* period */ switch(period) { case VALUEGRAPH_PERIOD_4H: return 2*60; /* 2m */ case VALUEGRAPH_PERIOD_1D: return 5*60; /* 5m */ case VALUEGRAPH_PERIOD_1W: return 15*60; /* 15m */ case VALUEGRAPH_PERIOD_1M: return 60*60; /* 1h */ default: return 5*60; /* 5m */ } } AQDG_GRAPH_DATAPAIR_LIST *_requestDataPairList(AQH_DATACLIENT *dc, const char *valueName, uint64_t tsBegin, uint64_t tsEnd, uint64_t num) { uint64_t *dataPoints; uint64_t recvdNum; dataPoints=malloc(num*sizeof(uint64_t)*2); recvdNum=AQH_DataClient_GetPeriodData(dc, valueName, dataPoints, num, tsBegin, tsEnd); if (recvdNum>0) { AQDG_GRAPH_DATAPAIR_LIST *dpList; dpList=_createDataPairListFromDataPoints(dataPoints, recvdNum); free(dataPoints); return dpList; } else { DBG_ERROR(NULL, "No data received"); free(dataPoints); return NULL; } } AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues) { AQDG_GRAPH_DATAPAIR_LIST *dpList; uint64_t i; DBG_ERROR(NULL, "Got %d datapoints", (int) numValues); dpList=AQDG_Graph_DataPair_List_new(); for(i=0; i