From 7c5abc0f3b39a69dbde6a34e2e84081fb3cd7ea9 Mon Sep 17 00:00:00 2001 From: Martin Preuss Date: Mon, 27 Oct 2025 23:15:28 +0100 Subject: [PATCH] aqhome-cgi: more code sharing, adding page handling allows to define your own pages with graphs, sensors and actors. --- apps/aqhome-cgi/modules/common/mservice.c | 136 +++ apps/aqhome-cgi/modules/common/mservice.h | 6 + apps/aqhome-cgi/modules/devices/0BUILD | 2 + apps/aqhome-cgi/modules/devices/mdevices.c | 143 +++ apps/aqhome-cgi/modules/devices/mdevices.h | 15 + .../modules/devices/mdevices_page.c | 868 ++++++++++++++++++ .../modules/devices/mdevices_page.h | 29 + .../modules/devices/mdevices_setdata.c | 4 +- .../modules/devices/mdevices_vgraph.c | 113 +-- 9 files changed, 1205 insertions(+), 111 deletions(-) create mode 100644 apps/aqhome-cgi/modules/devices/mdevices_page.c create mode 100644 apps/aqhome-cgi/modules/devices/mdevices_page.h diff --git a/apps/aqhome-cgi/modules/common/mservice.c b/apps/aqhome-cgi/modules/common/mservice.c index f4df10f..f3923e0 100644 --- a/apps/aqhome-cgi/modules/common/mservice.c +++ b/apps/aqhome-cgi/modules/common/mservice.c @@ -17,6 +17,14 @@ #include +#include +#include +#include + +#include +#include +#include + /* ------------------------------------------------------------------------------------------------ @@ -680,5 +688,133 @@ AQH_USER_LIST *AQH_ModService_LoadRawUsers(AQH_MODULE *m) +void AQH_ModService_EscapeToBuffer(const char *src, GWEN_BUFFER *buf) +{ + while (*src) { + unsigned char x; + + x=(unsigned char)*src; + if (!( + (x>='A' && x<='Z') || + (x>='a' && x<='z') || + (x>='0' && x<='9') || + NULL!=strchr("-_", x) + )) { + unsigned char c; + + GWEN_Buffer_AppendByte(buf, '%'); + c=(((unsigned char)(*src))>>4)&0xf; + if (c>9) + c+=7; + c+='0'; + GWEN_Buffer_AppendByte(buf, c); + c=((unsigned char)(*src))&0xf; + if (c>9) + c+=7; + c+='0'; + GWEN_Buffer_AppendByte(buf, c); + } + else + GWEN_Buffer_AppendByte(buf, *src); + + src++; + } /* while */ +} + + + +void AQH_ModService_UnescapeToBuffer(const char *src, GWEN_BUFFER *buf) +{ + while (*src) { + int charHandled=0; + + if (*src=='%') { + if (strlen(src)>2) { + unsigned char d1, d2; + unsigned char c; + + if (isxdigit((int)src[1]) && isxdigit((int)src[2])) { + /* skip '%' */ + src++; + /* read first digit */ + d1=(unsigned char)(toupper(*src)); + + /* get second digit */ + src++; + d2=(unsigned char)(toupper(*src)); + /* compute character */ + d1-='0'; + if (d1>9) + d1-=7; + c=(d1<<4)&0xf0; + d2-='0'; + if (d2>9) + d2-=7; + c+=(d2&0xf); + /* store character */ + GWEN_Buffer_AppendByte(buf, (char)c); + charHandled=1; + } + } + } + if (!charHandled) + GWEN_Buffer_AppendByte(buf, *src); + src++; + } /* while */ +} + + + +int AQH_ModService_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_DEBUG(NULL, "File %s is current", sPath); + return 1; + } + + return 0; +} + + + +int AQH_ModService_RespondWithMimeFile(AQCGI_REQUEST *rq, const char *sFilename, const char *sMimeType, GWEN_BUFFER *dbuf) +{ + GWEN_BUFFER *ibuf; + int rv; + + ibuf=GWEN_Buffer_new(0, 1024, 0, 1); + /* read file */ + rv=GWEN_SyncIo_Helper_ReadFile(sFilename, ibuf); + if (rv<0) { + DBG_ERROR(NULL, "Error reading \"%s\" (%d)", sFilename, rv); + return rv; + } + else { + GWEN_Buffer_Reset(dbuf); + GWEN_Buffer_AppendBytes(dbuf, GWEN_Buffer_GetStart(ibuf), GWEN_Buffer_GetUsedBytes(ibuf)); + + if (sMimeType && *sMimeType) { + GWEN_BUFFER *tbuf; + + tbuf=GWEN_Buffer_new(0, 256, 0, 1); + GBAA(tbuf, "Content-type: %s", sMimeType); + AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf)); + GWEN_Buffer_free(tbuf); + } + AQCGI_Request_AddFlags(rq, AQH_MODSERVICE_RQFLAGS_RAWFILE); + } + GWEN_Buffer_free(ibuf); + return 0; +} + + diff --git a/apps/aqhome-cgi/modules/common/mservice.h b/apps/aqhome-cgi/modules/common/mservice.h index 8a578ce..32ce803 100644 --- a/apps/aqhome-cgi/modules/common/mservice.h +++ b/apps/aqhome-cgi/modules/common/mservice.h @@ -80,6 +80,12 @@ void AQH_ModService_SetLoadSubModuleFn(AQH_MODULE *m, AQH_MODSERVICE_LOADSUBMODU void AQH_ModService_SetAddHeaderFn(AQH_MODULE *m, AQH_MODSERVICE_ADDHEADER_FN fn); void AQH_ModService_SetAddFooterFn(AQH_MODULE *m, AQH_MODSERVICE_ADDFOOTER_FN fn); +void AQH_ModService_EscapeToBuffer(const char *src, GWEN_BUFFER *buf); +void AQH_ModService_UnescapeToBuffer(const char *src, GWEN_BUFFER *buf); + +int AQH_ModService_FileIsCurrent(const char *sPath, int seconds); + +int AQH_ModService_RespondWithMimeFile(AQCGI_REQUEST *rq, const char *sFilename, const char *sMimeType, GWEN_BUFFER *dbuf); #endif diff --git a/apps/aqhome-cgi/modules/devices/0BUILD b/apps/aqhome-cgi/modules/devices/0BUILD index 051c8ae..56bf157 100644 --- a/apps/aqhome-cgi/modules/devices/0BUILD +++ b/apps/aqhome-cgi/modules/devices/0BUILD @@ -61,6 +61,7 @@ mdevices_vgraph.h mdevices_device.h mdevices_setdevice.h + mdevices_page.h @@ -81,6 +82,7 @@ mdevices_vgraph.c mdevices_device.c mdevices_setdevice.c + mdevices_page.c diff --git a/apps/aqhome-cgi/modules/devices/mdevices.c b/apps/aqhome-cgi/modules/devices/mdevices.c index 70e1e2d..aef7878 100644 --- a/apps/aqhome-cgi/modules/devices/mdevices.c +++ b/apps/aqhome-cgi/modules/devices/mdevices.c @@ -21,6 +21,7 @@ #include "aqhome-cgi/modules/devices/mdevices_vgraph.h" #include "aqhome-cgi/modules/devices/mdevices_device.h" #include "aqhome-cgi/modules/devices/mdevices_setdevice.h" +#include "aqhome-cgi/modules/devices/mdevices_page.h" #include "aqhome-cgi/service/module.h" #include "aqhome-cgi/modules/mdataclient.h" @@ -61,6 +62,11 @@ static void _handleRqSetDataPost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION * static void _handleRqGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); static void _handleRqDeviceGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); static void _handleRqDevicePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); +static void _handleRqPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); +static void _handleRqPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); +static void _handleRqPageGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf); + +static AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *dataPoints, uint64_t numValues); static void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf); static void _addLastValueToForm(AQH_DATACLIENT *dc, const AQH_VALUE *value, GWEN_BUFFER *dbuf); @@ -81,6 +87,9 @@ static AQH_MODSERVICE_HANDLER_ENTRY _requestTable[]={ {"value.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqValueGet}, {"setdata.html", AQCGI_REQUEST_METHOD_POST, P_VALUEWRITE, _handleRqSetDataPost}, {"graph.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqGraphGet}, + {"page.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqPageGet}, + {"page.html", AQCGI_REQUEST_METHOD_POST, P_VALUEWRITE, _handleRqPagePost}, + {"pgraph.html", AQCGI_REQUEST_METHOD_GET, P_DEVICEREAD | P_VALUEREAD, _handleRqPageGraphGet}, {NULL, 0, 0, NULL} }; @@ -171,6 +180,26 @@ void _handleRqDevicePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, +void _handleRqPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf) +{ + AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGet, dbuf); +} + + + +void _handleRqPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf) +{ + AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPagePost, dbuf); +} + + + +void _handleRqPageGraphGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, GWEN_BUFFER *dbuf) +{ + AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGraph, dbuf); +} + + @@ -227,6 +256,44 @@ uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn) +uint32_t AQH_ModDevices_RgbwFromComponents(int r, int g, int b, int w) +{ + uint32_t colorOut; + + colorOut=((r & 0xff)<<16) | ((g & 0xff)<<24) | (b &0xff) | ((w & 0xff)<<8); + return colorOut; +} + + + +int AQH_ModDevices_RgbwGetR(uint32_t color) +{ + return (color & 0xff0000)>>16; +} + + + +int AQH_ModDevices_RgbwGetG(uint32_t color) +{ + return (color & 0xff000000)>>24; +} + + + +int AQH_ModDevices_RgbwGetB(uint32_t color) +{ + return (color & 0xff); +} + + + +int AQH_ModDevices_RgbwGetW(uint32_t color) +{ + return (color & 0xff00)>>8; +} + + + void _addValueActionToForm(const AQH_VALUE *value, GWEN_BUFFER *dbuf) { const char *sValueName; @@ -392,4 +459,80 @@ AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName +int AQH_ModDevices_ValueGetLastDataAsInt(AQH_DATACLIENT *dc, const AQH_VALUE *value, int defaultValue) +{ + const char *sValueSystemName; + uint64_t dataPoints[2]; + uint64_t recvdNum; + // uint64_t timestamp; + union {double f; uint64_t i;} u; + int intVal; + + sValueSystemName=AQH_Value_GetNameForSystem(value); + recvdNum=AQH_DataClient_GetLastData(dc, sValueSystemName, &dataPoints[0], 1); + if (recvdNum>0) { + // timestamp=dataPoints[0]; + u.i=dataPoints[1]; + intVal=(int) u.f; + } + else { + DBG_INFO(NULL, "No last value for \"%s\"", sValueSystemName); + intVal=defaultValue; + } + + return intVal; +} + + + + +AQDG_GRAPH_DATAPAIR_LIST *AQH_ModDevices_RequestDataPairList(AQH_DATACLIENT *dc, const char *systemValueName, + 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, systemValueName, 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 for %s", systemValueName); + 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_DEBUG(NULL, "Got %d datapoints", (int) numValues); + dpList=AQDG_Graph_DataPair_List_new(); + for(i=0; i +#include + #include @@ -44,9 +46,22 @@ int AQH_ModDevices_Create(AQH_SERVICE *sv); uint32_t AQH_ModDevices_ColorFromHexString(const char *s); uint32_t AQH_ModDevices_HtmlColorToValueRGBW(uint32_t colorIn); uint32_t AQH_ModDevices_RgbwToHtmlColor(uint32_t colorIn); +uint32_t AQH_ModDevices_RgbwFromComponents(int r, int g, int b, int w); + +int AQH_ModDevices_RgbwGetR(uint32_t color); +int AQH_ModDevices_RgbwGetG(uint32_t color); +int AQH_ModDevices_RgbwGetB(uint32_t color); +int AQH_ModDevices_RgbwGetW(uint32_t color); + + + AQH_VALUE *AQH_ModDevices_GetValueForDevice(AQH_DATACLIENT *dc, const char *sDeviceName, const char *sValueName); AQH_DEVICE *AQH_ModDevices_GetDevice(AQH_DATACLIENT *dc, const char *sDeviceName); +int AQH_ModDevices_ValueGetLastDataAsInt(AQH_DATACLIENT *dc, const AQH_VALUE *value, int defaultValue); + +AQDG_GRAPH_DATAPAIR_LIST *AQH_ModDevices_RequestDataPairList(AQH_DATACLIENT *dc, const char *systemValueName, + uint64_t tsBegin, uint64_t tsEnd, uint64_t num); diff --git a/apps/aqhome-cgi/modules/devices/mdevices_page.c b/apps/aqhome-cgi/modules/devices/mdevices_page.c new file mode 100644 index 0000000..60eac5c --- /dev/null +++ b/apps/aqhome-cgi/modules/devices/mdevices_page.c @@ -0,0 +1,868 @@ +/**************************************************************************** + * 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_page.h" + +#include "aqhome-cgi/service/module.h" +#include "aqhome-cgi/modules/mdataclient.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + + + +/* ------------------------------------------------------------------------------------------------ + * defs and enums + * ------------------------------------------------------------------------------------------------ + */ + +#define GBAS GWEN_Buffer_AppendString +#define GBAA GWEN_Buffer_AppendArgs + + +enum { + MY_LAYOUT_NONE=0, + MY_LAYOUT_HORIZONTAL, + MY_LAYOUT_VERTICAL +}; + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static void _writePage(AQH_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf); +static void _writeItem(AQH_MODULE *m, AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *nItem, GWEN_BUFFER *dbuf); +static void _writeActor(AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf); +static void _writeGraph(const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf); + +static void _handlePageActor(AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sActorId, GWEN_XMLNODE *nActor); +static void _handlePageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sGraphId, GWEN_XMLNODE *nGraph, GWEN_BUFFER *dbuf); +static void _genPageGraph(AQH_DATACLIENT *dc, const char *sGraphId, const char *sFilename, GWEN_XMLNODE *nGraph); +static void _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_XMLNODE *nGraph, uint64_t tsBegin, uint64_t tsEnd); +static AQDG_GRAPH_DATAPAIR_LIST *_readCurveData(AQH_DATACLIENT *dc, GWEN_XMLNODE *nCurve, uint64_t tsBegin, uint64_t tsEnd); +static uint64_t _parseTime(const char *s); +static GWEN_XMLNODE *_getSubItemNode(GWEN_XMLNODE *nPage, const char *sId, const char *sElementName); +static void _mkPathForGraph(AQH_MODULE *m, const char *sGraphId, GWEN_BUFFER *dbuf); +static void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, GWEN_BUFFER *dbuf); +static void _writeRgbwToForm(const char *sValueName, uint32_t color, GWEN_BUFFER *dbuf); +static void _writeOnOffToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf); +static void _writeOnOffAutoToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf); +static void _setRgbwData(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbPost, const char *sValueName, const AQH_VALUE *value); +static int _getColorComponent(GWEN_DB_NODE *dbPost, const char *sValueName, const char *sComponent, int defaultValue); +static void _setOnOffData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue); +static void _setOnOffAutoData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue); + +static GWEN_XMLNODE *_readPage(AQH_MODULE *m, const char *sPageName); +static GWEN_XMLNODE *_readPageFile(const char *sFilename); +static int _layoutFromString(const char *s); + + + +/* ------------------------------------------------------------------------------------------------ + * code + * ------------------------------------------------------------------------------------------------ + */ + +void AQH_ModDevices_RunPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf) +{ + GWEN_DB_NODE *dbQuery; + const char *sPageId; + + DBG_INFO(NULL, "RunPageGet"); + dbQuery=AQCGI_Request_GetDbQuery(rq); + sPageId=GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL); + if (sPageId && *sPageId) { + GWEN_XMLNODE *fileNode; + + fileNode=_readPage(m, sPageId); + if (fileNode) { + GWEN_XMLNODE *nPage; + + nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", NULL, NULL); + if (nPage) { + _writePage(m, dc, nPage, dbuf); + AQCGI_Request_AddResponseHeaderData(rq, "Refresh: 120"); + } + else { + DBG_ERROR(NULL, "No page element in file for \"%s\"", sPageId); + } + GWEN_XMLNode_free(fileNode); + } + else { + DBG_INFO(NULL, "here"); + } + } +} + + + +void AQH_ModDevices_RunPageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf) +{ + GWEN_DB_NODE *dbQuery; + const char *sPageId; + const char *sGraphId; + + DBG_INFO(NULL, "RunPageGraphGet"); + dbQuery=AQCGI_Request_GetDbQuery(rq); + sPageId=GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL); + sGraphId=GWEN_DB_GetCharValue(dbQuery, "graph", 0, NULL); + if (sPageId && *sPageId && sGraphId && *sGraphId) { + GWEN_XMLNODE *fileNode; + + fileNode=_readPage(m, sPageId); + if (fileNode) { + GWEN_XMLNODE *nPage; + GWEN_XMLNODE *nGraph; + + nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", "id", sPageId); + nGraph=_getSubItemNode(nPage, sGraphId, "graph"); + if (nPage && nGraph) + _handlePageGraph(m, rq, dc, sGraphId, nGraph, dbuf); + else { + DBG_ERROR(NULL, "Graph %s/%s not found", sPageId, sGraphId); + } + GWEN_XMLNode_free(fileNode); + } + else { + DBG_INFO(NULL, "here"); + } + } +} + + + +void AQH_ModDevices_RunPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf) +{ + GWEN_DB_NODE *dbPost; + const char *sPageId; + const char *sActorId; + + DBG_INFO(NULL, "RunPagePost"); + dbPost=AQCGI_Request_GetDbPostBody(rq); + sPageId=GWEN_DB_GetCharValue(dbPost, "page", 0, NULL); + sActorId=GWEN_DB_GetCharValue(dbPost, "actor", 0, NULL); + if (sPageId && *sPageId && sActorId && *sActorId) { + GWEN_XMLNODE *fileNode; + + fileNode=_readPage(m, sPageId); + if (fileNode) { + GWEN_XMLNODE *nPage; + GWEN_XMLNODE *nActor; + + nPage=GWEN_XMLNode_FindFirstTag(fileNode, "page", "id", sPageId); + nActor=_getSubItemNode(nPage, sActorId, "actor"); + if (nPage && nActor) { + _handlePageActor(rq, dc, sActorId, nActor); + } + else { + DBG_ERROR(NULL, "Actor %s/%s not found", sPageId, sActorId); + } + GWEN_XMLNode_free(fileNode); + } + else { + DBG_INFO(NULL, "here"); + } + } + + if (sPageId && *sPageId) { + GWEN_BUFFER *pbuf; + + pbuf=GWEN_Buffer_new(0, 256, 0, 1); + GBAS(pbuf, "Location: page.html?page="); + GWEN_Text_EscapeToBuffer(sPageId, pbuf); + AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf)); + GWEN_Buffer_free(pbuf); + } + AQCGI_Request_SetResponseCode(rq, 303); + AQCGI_Request_SetResponseText(rq, "See other"); +} + + + +void _handlePageActor(AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sActorId, GWEN_XMLNODE *nActor) +{ + GWEN_DB_NODE *dbPost; + const char *sDeviceName; + const char *sValueName; + + dbPost=AQCGI_Request_GetDbPostBody(rq); + sDeviceName=GWEN_XMLNode_GetProperty(nActor, "device", NULL); + sValueName=GWEN_XMLNode_GetProperty(nActor, "value", NULL); + + if (sDeviceName && *sDeviceName && sValueName && *sValueName) { + AQH_VALUE *value; + + value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName); + if (value) { + const char *sSystemValueName; + + sSystemValueName=AQH_Value_GetNameForSystem(value); + if (sSystemValueName) { + const char *sData; + + sData=GWEN_DB_GetCharValue(dbPost, sActorId, 0, NULL); + DBG_INFO(NULL, "Setting value %s to %s", sSystemValueName, sData?sData:"empty/no value"); + switch(AQH_Value_GetModality(value)) { + case AQH_ValueModality_RGBW: _setRgbwData(dc, dbPost, sActorId, value); break; + case AQH_ValueModality_OnOff: _setOnOffData(dc, value, sData); break; + case AQH_ValueModality_OnOffAuto: _setOnOffAutoData(dc, value, sData); break; + default: + break; + } /* switch */ + } + } + } +} + + + +void _handlePageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc, const char *sGraphId, GWEN_XMLNODE *nGraph, GWEN_BUFFER *dbuf) +{ + GWEN_BUFFER *fbuf; + int refreshTime; + + refreshTime=GWEN_XMLNode_GetIntProperty(nGraph, "refreshTime", 120); + fbuf=GWEN_Buffer_new(0, 256, 0, 1); + _mkPathForGraph(m, sGraphId, fbuf); + if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(fbuf), refreshTime)) { + _genPageGraph(dc, sGraphId, GWEN_Buffer_GetStart(fbuf), nGraph); + } + AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(fbuf), "image/png", dbuf); + GWEN_Buffer_free(fbuf); +} + + + +void _genPageGraph(AQH_DATACLIENT *dc, const char *sGraphId, const char *sFilename, GWEN_XMLNODE *nGraph) +{ + const char *s; + const char *sTitle; + int w; + int h; + int precision; + uint64_t tsBegin; + uint64_t tsEnd; + AQDG_GRAPH *g; + AQDG_DRAW_CONTEXT *drawContext; + AQDG_OBJECT *graphObject; + uint32_t tickFlags=0; + double upperLimit; + double lowerLimit; + + sTitle=GWEN_XMLNode_GetProperty(nGraph, "title", "untitled"); + w=GWEN_XMLNode_GetIntProperty(nGraph, "width", AQH_MODDEVICES_GRAPH_WIDTH); + h=GWEN_XMLNode_GetIntProperty(nGraph, "height", AQH_MODDEVICES_GRAPH_HEIGHT); + precision=GWEN_XMLNode_GetIntProperty(nGraph, "precision", 2); + tsBegin=_parseTime(GWEN_XMLNode_GetProperty(nGraph, "begin", "-4h")); + tsEnd=_parseTime(GWEN_XMLNode_GetProperty(nGraph, "end", "0")); + + s=GWEN_XMLNode_GetProperty(nGraph, "lowerLimit", NULL); + if (s && *s) { + if (1==sscanf(s, "%lf", &lowerLimit)) + tickFlags|=AQDG_TIMEGRAPH_SETUPTICKS_FLAGS_MINY; + else { + DBG_ERROR(NULL, "Ignoring invalid lowerLimit (%s)", s); + } + } + + s=GWEN_XMLNode_GetProperty(nGraph, "upperLimit", NULL); + if (s && *s) { + if (1==sscanf(s, "%lf", &upperLimit)) + tickFlags|=AQDG_TIMEGRAPH_SETUPTICKS_FLAGS_MAXY; + else { + DBG_ERROR(NULL, "Ignoring invalid upperLimit (%s)", s); + } + } + + g=AQDG_TimeGraph_new(sTitle, NULL, "Value", NULL, precision); + _addCurves(dc, g, nGraph, tsBegin, tsEnd); + AQDG_TimeGraph_SetupTicks(g, tickFlags, lowerLimit, upperLimit); + + DBG_DEBUG(NULL, "Draw graph for %s", sGraphId); + drawContext=AQDG_Draw_ContextCairo_Png_new(sFilename, w, h); + graphObject=AQDG_GraphWidget_new(NULL, AQDG_OBJECT_OPTIONS_STRETCHX | AQDG_OBJECT_OPTIONS_STRETCHY, drawContext); + AQDG_Object_SetWidth(graphObject, w); + AQDG_Object_SetHeight(graphObject, h); + + AQDG_GraphWidget_SetupDefaultPens(graphObject); + AQDG_GraphWidget_SetupDefaultFonts(graphObject); + + AQDG_GraphWidget_FinishWithGraph(graphObject, g); + + AQDG_Object_free(graphObject); +} + + + +void _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_XMLNODE *nGraph, uint64_t tsBegin, uint64_t tsEnd) +{ + GWEN_XMLNODE *nCurve; + + nCurve=GWEN_XMLNode_FindFirstTag(nGraph, "curve", NULL, NULL); + while(nCurve) { + const char *sModifier; + + sModifier=GWEN_XMLNode_GetProperty(nCurve, "modifier", NULL); + if (sModifier && *sModifier) { + AQDG_GRAPH_DATAPAIR_LIST *dpList; + + dpList=_readCurveData(dc, nCurve, tsBegin, tsEnd); + if (dpList) { + const char *sCurveLabel; + + sCurveLabel=GWEN_XMLNode_GetProperty(nCurve, "title", NULL); + DBG_DEBUG(NULL, "Adding data for %s", sCurveLabel?sCurveLabel:""); + AQDG_TimeGraph_ModifyDataAndAddCurve(g, sCurveLabel?sCurveLabel:"", sModifier, dpList); + } + } + nCurve=GWEN_XMLNode_FindNextTag(nCurve, "curve", NULL, NULL); + } +} + + + +AQDG_GRAPH_DATAPAIR_LIST *_readCurveData(AQH_DATACLIENT *dc, GWEN_XMLNODE *nCurve, uint64_t tsBegin, uint64_t tsEnd) +{ + const char *sDeviceName; + const char *sValueName; + + sDeviceName=GWEN_XMLNode_GetProperty(nCurve, "device", NULL); + sValueName=GWEN_XMLNode_GetProperty(nCurve, "value", NULL); + + if (sDeviceName && *sDeviceName && sValueName && *sValueName) { + AQDG_GRAPH_DATAPAIR_LIST *dpList; + GWEN_BUFFER *vbuf; + + vbuf=GWEN_Buffer_new(0, 64, 0, 1); + GBAA(vbuf, "%s/%s", sDeviceName, sValueName); + dpList=AQH_ModDevices_RequestDataPairList(dc, GWEN_Buffer_GetStart(vbuf), tsBegin, tsEnd, 100000); + if (dpList) { + GWEN_Buffer_free(vbuf); + return dpList; + } + GWEN_Buffer_free(vbuf); + } + return NULL; +} + + + +// TODO: move to aqhome.{c,h} +uint64_t _parseTime(const char *s) +{ + if (s && *s) { + if (*s=='-') { + uint64_t x=0; + uint64_t now=time(NULL); + + s++; + while(*s && isdigit(*s)) { + unsigned int i; + + i=*(s++)-'0'; + x*=10; + x+=i; + } + if (*s) { + switch(*s) { + case 0: + case 'm': x*=60; break; + case 'h': x*=(60*60); break; + case 'd': x*=(60*60*24); break; + case 'w': x*=(60*60*24*7); break; + case 'M': x*=(60*60*24*30); break; + case 'y': x*=(60*60*24*365); break; + default: break; + } + } + return (now-x); + } + if (*s=='@') { + int y, m, d, H, M, S; + + if (6==sscanf(s+1, "%d/%d/%d-%d:%d:%d", &y, &m, &d, &H, &M, &S)) { + GWEN_TIMESTAMP *ts; + uint64_t x=0; + + ts=GWEN_Timestamp_new(y, m, d, H, M, S); + x=GWEN_Timestamp_toTimeT(ts); + GWEN_Timestamp_free(ts); + return x; + } + else { + DBG_ERROR(NULL, "Invalid timespec [%s], expected: @YYYY/MM/DD-HH:MM:SS", s); + return (uint64_t) (-1); + } + } + else { + unsigned long int x; + + if (1!=sscanf(s, "%lu", &x)) { + DBG_ERROR(NULL, "ERROR: Invalid timestamp"); + return (uint64_t) (-1); + } + return (uint64_t) x; + } + } + return 0; +} + + + + +GWEN_XMLNODE *_getSubItemNode(GWEN_XMLNODE *nPage, const char *sId, const char *sElementName) +{ + GWEN_XMLNODE *nItem; + + nItem=GWEN_XMLNode_FindFirstTag(nPage, "item", NULL, NULL); + while(nItem) { + GWEN_XMLNODE *nGraph; + + nGraph=GWEN_XMLNode_FindFirstTag(nItem, sElementName, "id", sId); + if (nGraph) + return nGraph; + nItem=GWEN_XMLNode_FindNextTag(nItem, "item", NULL, NULL); + } + + return NULL; +} + + + +void _mkPathForGraph(AQH_MODULE *m, const char *sGraphId, GWEN_BUFFER *buf) +{ + AQH_SERVICE *sv; + const char *s; + + sv=AQH_ModService_GetService(m); + s=AQH_Service_GetCacheFolder(sv); + GBAA(buf, "%s%s%s", s, GWEN_DIR_SEPARATOR_S, sGraphId); + AQH_ModService_EscapeToBuffer(s, buf); + GBAS(buf, ".png"); +} + + + +void _writePage(AQH_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf) +{ + const char *sPageId; + GWEN_XMLNODE *nItem; + int layout; + const char *s; + + sPageId=GWEN_XMLNode_GetProperty(nPage, "id", NULL); + + /* title */ + s=GWEN_XMLNode_GetProperty(nPage, "title", NULL); + if (s && *s) + GBAA(dbuf, "

%s

\n", s); + + layout=_layoutFromString(GWEN_XMLNode_GetProperty(nPage, "layout", "none")); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); + if (layout==MY_LAYOUT_HORIZONTAL) + GBAS(dbuf, "\n"); + nItem=GWEN_XMLNode_FindFirstTag(nPage, "item", NULL, NULL); + while(nItem) { + if (layout==MY_LAYOUT_VERTICAL) + GBAS(dbuf, "\n"); + + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, ""); + + if (layout==MY_LAYOUT_VERTICAL) + GBAS(dbuf, "\n"); + nItem=GWEN_XMLNode_FindNextTag(nItem, "item", NULL, NULL); + } /* while */ + if (layout==MY_LAYOUT_HORIZONTAL) + GBAS(dbuf, "\n"); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "
"); + _writeItem(m, dc, sPageId, nItem, dbuf); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "
\n"); +} + + + +void _writeItem(AQH_MODULE *m, AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *nItem, GWEN_BUFFER *dbuf) +{ + GWEN_XMLNODE *n; + uint32_t perms; + int layout; + + perms=AQH_ModService_GetUserPerms(m); + layout=_layoutFromString(GWEN_XMLNode_GetProperty(nItem, "layout", "none")); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); + if (layout==MY_LAYOUT_HORIZONTAL) + GBAS(dbuf, "\n"); + n=GWEN_XMLNode_GetFirstTag(nItem); + while(n) { + const char *sName; + + if (layout==MY_LAYOUT_VERTICAL) + GBAS(dbuf, "\n"); + sName=GWEN_XMLNode_GetData(n); + if (sName && *sName) { + if (strcasecmp(sName, "actor")==0) { + if (perms && AQH_MODDEVICES_PERMS_VALUEWRITE) + _writeActor(dc, sPageId, n, layout, dbuf); + else { + DBG_ERROR(NULL, "No permissions to write values"); + } + + } + else if (strcasecmp(sName, "graph")==0) + _writeGraph(sPageId, n, layout, dbuf); + else { + DBG_ERROR(NULL, "Ignoring element \"%s\"", sName); + } + } + if (layout==MY_LAYOUT_VERTICAL) + GBAS(dbuf, "\n"); + n=GWEN_XMLNode_GetNextTag(n); + } + if (layout==MY_LAYOUT_HORIZONTAL) + GBAS(dbuf, "\n"); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "
\n"); +} + + + +void _writeActor(AQH_DATACLIENT *dc, const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf) +{ + const char *sActorId; + const char *sDeviceName; + const char *sValueName; + const char *sLabel; + + sActorId=GWEN_XMLNode_GetProperty(n, "id", NULL); + sLabel=GWEN_XMLNode_GetProperty(n, "label", NULL); + sDeviceName=GWEN_XMLNode_GetProperty(n, "device", NULL); + sValueName=GWEN_XMLNode_GetProperty(n, "value", NULL); + if (sActorId && *sActorId && sDeviceName && *sDeviceName && sValueName && *sValueName) { + AQH_VALUE *value; + + value=AQH_ModDevices_GetValueForDevice(dc, sDeviceName, sValueName); + if (value) { + int lastData; + + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); + lastData=AQH_ModDevices_ValueGetLastDataAsInt(dc, value, 0); + GBAS(dbuf,"
\n"); + GBAA(dbuf, "\n", sPageId); + GBAA(dbuf, "\n", sActorId); + + DBG_INFO(NULL, "Adding actor"); + if (sLabel && *sLabel) + GBAA(dbuf,"", sActorId, sLabel); + if (layout!=MY_LAYOUT_NONE) { + GBAS(dbuf, "\n"); + GBAS(dbuf, "\n"); + } + switch(AQH_Value_GetModality(value)) { + case AQH_ValueModality_RGBW: _writeRgbwToForm(sActorId, lastData, dbuf); break; + case AQH_ValueModality_OnOff: _writeOnOffToForm(sActorId, lastData, dbuf); break; + case AQH_ValueModality_OnOffAuto: _writeOnOffAutoToForm(sActorId, lastData, dbuf); break; + default: GBAA(dbuf, "%d", lastData); break; + } /* switch */ + if (layout!=MY_LAYOUT_NONE) { + GBAS(dbuf, "\n"); + GBAS(dbuf, "\n"); + } + GBAS(dbuf,""); + GBAS(dbuf, "
\n\n"); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); + } + } +} + + + +void _writeGraph(const char *sPageId, GWEN_XMLNODE *n, int layout, GWEN_BUFFER *dbuf) +{ + const char *sGraphId; + int w; + int h; + + w=GWEN_XMLNode_GetIntProperty(n, "width", AQH_MODDEVICES_GRAPH_WIDTH); + h=GWEN_XMLNode_GetIntProperty(n, "height", AQH_MODDEVICES_GRAPH_HEIGHT); + sGraphId=GWEN_XMLNode_GetProperty(n, "id", NULL); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); + if (sGraphId && *sGraphId) + _addGraphLink(sPageId, sGraphId, w, h, dbuf); + if (layout!=MY_LAYOUT_NONE) + GBAS(dbuf, "\n"); +} + + + + +void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, GWEN_BUFFER *dbuf) +{ + GBAS(dbuf, "\"%s\""); +} + + + + + + + +void _writeRgbwToForm(const char *sValueName, uint32_t color, GWEN_BUFFER *dbuf) +{ +#if 1 + GBAA(dbuf, "", sValueName); + GBAA(dbuf, "", + sValueName, sValueName, AQH_ModDevices_RgbwGetR(color)); + + GBAA(dbuf, "", sValueName); + GBAA(dbuf, "", + sValueName, sValueName, AQH_ModDevices_RgbwGetG(color)); + + GBAA(dbuf, "", sValueName); + GBAA(dbuf, "", + sValueName, sValueName, AQH_ModDevices_RgbwGetB(color)); + + GBAA(dbuf, "", sValueName); + GBAA(dbuf, "", + sValueName, sValueName, AQH_ModDevices_RgbwGetW(color)); + +#else + GBAA(dbuf, "", sValueName, sValueName, color); + // else + GBAA(dbuf, "#%08x (#%08x)", + sValueName, sValueName, + AQH_ModDevices_RgbwToHtmlColor(color), + AQH_ModDevices_RgbwToHtmlColor(color), + color); +#endif +} + + + +void _setRgbwData(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbPost, const char *sValueName, const AQH_VALUE *value) +{ + const char *sValueSystemName; + uint32_t color; + int rv; + + sValueSystemName=AQH_Value_GetNameForSystem(value); + DBG_INFO(NULL, "Set value %s", sValueName); + color=AQH_ModDevices_RgbwFromComponents(_getColorComponent(dbPost, sValueName, "r", 0), + _getColorComponent(dbPost, sValueName, "g", 0), + _getColorComponent(dbPost, sValueName, "b", 0), + _getColorComponent(dbPost, sValueName, "w", 0)); + DBG_INFO(NULL, "Send value [#%08x] to %s", color, sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, (double) color); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } +} + + + +int _getColorComponent(GWEN_DB_NODE *dbPost, const char *sValueName, const char *sComponent, int defaultValue) +{ + GWEN_BUFFER *buf; + const char *sData; + int result; + + buf=GWEN_Buffer_new(0, 64, 0, 1); + GBAA(buf, "%s_%s", sValueName, sComponent); + DBG_INFO(NULL, "Read value %s", GWEN_Buffer_GetStart(buf)); + sData=GWEN_DB_GetCharValue(dbPost, GWEN_Buffer_GetStart(buf), 0, NULL); + GWEN_Buffer_free(buf); + if (sData) { + if (1==sscanf(sData, "%u", &result)) + return result; + } + return defaultValue; +} + + + +void _writeOnOffToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf) +{ + GBAA(dbuf, ""); +} + + + +void _setOnOffData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue) +{ + if (sValue) { + const char *sValueSystemName; + int rv; + + sValueSystemName=AQH_Value_GetNameForSystem(value); + if (strcasecmp(sValue, "unchanged")==0) { + DBG_INFO(NULL, "Value %s unchanged", sValueSystemName); + } + else if (strcasecmp(sValue, "on")==0) { + DBG_INFO(NULL, "Send value 1 to %s", sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, 1.0); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } + } + else if (strcasecmp(sValue, "off")==0) { + DBG_INFO(NULL, "Send value 0 to %s", sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, 0.0); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } + } + else { + } + } +} + + + +void _writeOnOffAutoToForm(const char *sValueName, int intVal, GWEN_BUFFER *dbuf) +{ + GBAA(dbuf, ""); +} + + + +void _setOnOffAutoData(AQH_DATACLIENT *dc, const AQH_VALUE *value, const char *sValue) +{ + if (sValue) { + const char *sValueSystemName; + int rv; + + sValueSystemName=AQH_Value_GetNameForSystem(value); + if (strcasecmp(sValue, "unchanged")==0) { + DBG_INFO(NULL, "Value %s unchanged", sValueSystemName); + } + else if (strcasecmp(sValue, "on")==0) { + DBG_INFO(NULL, "Send value 1 to %s", sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, 1.0); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } + } + else if (strcasecmp(sValue, "off")==0) { + DBG_INFO(NULL, "Send value 0 to %s", sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, 0.0); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } + } + else if (strcasecmp(sValue, "auto")==0) { + DBG_INFO(NULL, "Send value 2 to %s", sValueSystemName); + rv=AQH_DataClient_SetData(dc, value, 2.0); + if (rv<0) { + DBG_INFO(NULL, "Error sending data: %d", rv); + } + } + else { + DBG_INFO(NULL, "Invalid value [%s] for %s", sValue, sValueSystemName); + } + } +} + + + + +GWEN_XMLNODE *_readPage(AQH_MODULE *m, const char *sPageName) +{ + GWEN_BUFFER *fbuf; + AQH_SERVICE *sv; + GWEN_XMLNODE *fileNode; + + sv=AQH_ModService_GetService(m); + fbuf=GWEN_Buffer_new(0, 256, 0, 1); + GBAA(fbuf, "%s%spages%s", AQH_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S, GWEN_DIR_SEPARATOR_S); + AQH_ModService_EscapeToBuffer(sPageName, fbuf); + GBAS(fbuf, ".xml"); + + fileNode=_readPageFile(GWEN_Buffer_GetStart(fbuf)); + if (fileNode==NULL) { + DBG_INFO(NULL, "here"); + GWEN_Buffer_free(fbuf); + return NULL; + } + + GWEN_Buffer_free(fbuf); + return fileNode; +} + + + +GWEN_XMLNODE *_readPageFile(const char *sFilename) +{ + GWEN_XMLNODE *fileNode; + int rv; + + fileNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, sFilename); + rv=GWEN_XML_ReadFile(fileNode, sFilename, GWEN_XML_FLAGS_DEFAULT); + if (rv<0) { + DBG_ERROR(NULL, "Error reading \"%s\": %s (%d)", sFilename?sFilename:"", strerror(errno), errno); + GWEN_XMLNode_free(fileNode); + return NULL; + } + + return fileNode; +} + + + +int _layoutFromString(const char *s) +{ + if (s && *s) { + if (strcasecmp(s, "none")==0) + return MY_LAYOUT_NONE; + else if (strcasecmp(s, "horizontal")==0) + return MY_LAYOUT_HORIZONTAL; + else if (strcasecmp(s, "vertical")==0) + return MY_LAYOUT_VERTICAL; + } + return MY_LAYOUT_NONE; +} + + + + diff --git a/apps/aqhome-cgi/modules/devices/mdevices_page.h b/apps/aqhome-cgi/modules/devices/mdevices_page.h new file mode 100644 index 0000000..546a0b7 --- /dev/null +++ b/apps/aqhome-cgi/modules/devices/mdevices_page.h @@ -0,0 +1,29 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + +#ifndef AQHOME_CGI_MDEVICES_PAGE_H +#define AQHOME_CGI_MDEVICES_PAGE_H + + +#include "aqhome-cgi/modules/devices/mdevices.h" + +#include "aqhome/aqhome.h" +#include "aqhome/dataclient/client.h" + +#include + +#include + + + +void AQH_ModDevices_RunPageGet(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf); +void AQH_ModDevices_RunPagePost(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf); +void AQH_ModDevices_RunPageGraph(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf); + + +#endif diff --git a/apps/aqhome-cgi/modules/devices/mdevices_setdata.c b/apps/aqhome-cgi/modules/devices/mdevices_setdata.c index 5a16bd8..b2a665a 100644 --- a/apps/aqhome-cgi/modules/devices/mdevices_setdata.c +++ b/apps/aqhome-cgi/modules/devices/mdevices_setdata.c @@ -86,13 +86,13 @@ void AQH_ModDevices_RunSetData(AQH_MODULE *m, AQCGI_REQUEST *rq, AQH_SESSION *se pbuf=GWEN_Buffer_new(0, 256, 0, 1); if (sValueName && *sValueName) { - GBAS(pbuf, "Location: /aqbt/devices/value.html?device="); + GBAS(pbuf, "Location: value.html?device="); GWEN_Text_EscapeToBuffer(sDeviceName, pbuf); GBAS(pbuf, "&value="); GWEN_Text_EscapeToBuffer(sValueName, pbuf); } else { - GBAS(pbuf, "Location: /aqbt/devices/values.html?device="); + GBAS(pbuf, "Location: values.html?device="); GWEN_Text_EscapeToBuffer(sDeviceName, pbuf); } AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(pbuf)); diff --git a/apps/aqhome-cgi/modules/devices/mdevices_vgraph.c b/apps/aqhome-cgi/modules/devices/mdevices_vgraph.c index ed3c0ae..0bdeeee 100644 --- a/apps/aqhome-cgi/modules/devices/mdevices_vgraph.c +++ b/apps/aqhome-cgi/modules/devices/mdevices_vgraph.c @@ -26,10 +26,6 @@ #include #include -#include -#include -#include - /* ------------------------------------------------------------------------------------------------ @@ -42,17 +38,6 @@ -enum { - VALUEGRAPH_PERIOD_4H=1, - VALUEGRAPH_PERIOD_1D, - VALUEGRAPH_PERIOD_1W, - VALUEGRAPH_PERIOD_1M, - VALUEGRAPH_PERIOD_6M, - VALUEGRAPH_PERIOD_12M, -}; - - - /* ------------------------------------------------------------------------------------------------ * vars * ------------------------------------------------------------------------------------------------ @@ -97,12 +82,8 @@ static void _createGraph(AQH_DATACLIENT *dc, const MY_GRAPH_PARAMS *graphParams, 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, 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 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); static const MY_GRAPH_PARAMS *_getParamsByName(const char *s); @@ -167,11 +148,10 @@ void _runGraphValueWithArgs(AQH_MODULE *m, 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, graphParams, fbuf); - if (!_fileIsCurrent(GWEN_Buffer_GetStart(fbuf), graphParams->acceptedAgeInSeconds)) { + if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(fbuf), graphParams->acceptedAgeInSeconds)) { DBG_DEBUG(NULL, "Creating graph"); _createGraph(dc, value, @@ -184,23 +164,7 @@ void _runGraphValueWithArgs(AQH_MODULE *m, 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, "Error reading \"%s\" (%d)", GWEN_Buffer_GetStart(fbuf), 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); - } + AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(fbuf), "image/png", dbuf); GWEN_Buffer_free(fbuf); AQH_Value_free(value); @@ -212,25 +176,6 @@ void _runGraphValueWithArgs(AQH_MODULE *m, -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_DEBUG(NULL, "File %s is current", sPath); - return 1; - } - - return 0; -} - - void _createGraph(AQH_DATACLIENT *dc, const AQH_VALUE *v, @@ -252,7 +197,7 @@ void _createGraph(AQH_DATACLIENT *dc, tsBegin=time(0)-(graphParams->startTimeDiff); g=_mkGraphObjectWithTitle(graphTitle, graphParams, precision); - dpList=_requestDataPairList(dc, sValue, tsBegin, tsEnd, numDataPoints); + 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); @@ -309,63 +254,13 @@ void _mkPathForValueAndPeriod(AQH_MODULE *m, const AQH_VALUE *v, const MY_GRAPH_ /* var name */ s=AQH_Value_GetNameForSystem(v); - GWEN_Text_EscapeToBuffer(s, dbuf); + AQH_ModService_EscapeToBuffer(s, dbuf); GBAA(dbuf, "-%s.png", graphParams->name); } -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 for %s", valueName); - 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_DEBUG(NULL, "Got %d datapoints", (int) numValues); - dpList=AQDG_Graph_DataPair_List_new(); - for(i=0; i