aqhome-cgi: rewrite of the page module.

This commit is contained in:
Martin Preuss
2026-08-24 23:35:54 +02:00
parent 5587dd5abc
commit 8158bc28fb
26 changed files with 2095 additions and 70 deletions

View File

@@ -62,6 +62,10 @@
mdevices_device.h
mdevices_setdevice.h
mdevices_page.h
mdevices_page2.h
mdevices_page2_graph.h
mdevices_pagectx.h
mdevices_pagectx_p.h
</headers>
@@ -83,6 +87,9 @@
mdevices_device.c
mdevices_setdevice.c
mdevices_page.c
mdevices_page2.c
mdevices_page2_graph.c
mdevices_pagectx.c
</sources>

View File

@@ -11,7 +11,7 @@
#endif
#include "./mdevices.h"
#include "./mdevices_p.h"
#include "aqhome-cgi/modules/devices/mdevices_index.h"
#include "aqhome-cgi/modules/devices/mdevices_valuestable.h"
@@ -22,6 +22,7 @@
#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/modules/devices/mdevices_page2.h"
#include "aqhome-cgi/modules/mdataclient.h"
#include <aqcgi/service/module.h>
@@ -46,12 +47,21 @@
#define P_VALUEWRITE AQH_MODDEVICES_PERMS_VALUEWRITE
/* ------------------------------------------------------------------------------------------------
* global vars
* ------------------------------------------------------------------------------------------------
*/
GWEN_INHERIT(AQCGI_MODULE, AQH_MOD_DEVICES)
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName);
static int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem);
@@ -103,13 +113,94 @@ static AQH_MODSERVICE_HANDLER_ENTRY _requestTable[]={
void AQH_ModDevices_Extend(AQCGI_MODULE *m, AQCGI_SERVICE *sv, const char *baseFolder)
{
AQH_MOD_DEVICES *xm;
AQH_ModService_Extend(m, sv, baseFolder);
GWEN_NEW_OBJECT(AQH_MOD_DEVICES, xm);
GWEN_INHERIT_SETDATA(AQCGI_MODULE, AQH_MOD_DEVICES, m, xm, _freeData);
AQH_ModService_SetHandleRequestFn(m, _handleRequest);
AQH_ModService_SetLoadSubModuleFn(m, _loadSubModule);
}
void _freeData(GWEN_UNUSED void *bp, void *p)
{
AQH_MOD_DEVICES *xm;
xm=(AQH_MOD_DEVICES*) p;
AQH_Value_List_free(xm->valueList);
AQH_Device_List_free(xm->deviceList);
GWEN_FREE_OBJECT(xm);
}
AQH_DEVICE_LIST *AQH_ModDevices_GetDeviceList(const AQCGI_MODULE *m)
{
if (m) {
AQH_MOD_DEVICES *xm;
xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_DEVICES, m);
if (xm) {
return xm->deviceList;
}
}
return NULL;
}
void AQH_ModDevices_SetDeviceList(AQCGI_MODULE *m, AQH_DEVICE_LIST *deviceList)
{
if (m) {
AQH_MOD_DEVICES *xm;
xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_DEVICES, m);
if (xm) {
AQH_Device_List_free(xm->deviceList);
xm->deviceList=deviceList;
}
}
}
AQH_VALUE_LIST *AQH_ModDevices_GetValueList(const AQCGI_MODULE *m)
{
if (m) {
AQH_MOD_DEVICES *xm;
xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_DEVICES, m);
if (xm) {
return xm->valueList;
}
}
return NULL;
}
void AQH_ModDevices_SetValueList(AQCGI_MODULE *m, AQH_VALUE_LIST *valueList)
{
if (m) {
AQH_MOD_DEVICES *xm;
xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_DEVICES, m);
if (xm) {
AQH_Value_List_free(xm->valueList);
xm->valueList=valueList;
}
}
}
AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName)
{
return NULL;
@@ -183,7 +274,7 @@ void _handleRqDevicePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *sess
void _handleRqPageGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGet, dbuf);
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPage2Get, dbuf);
}
@@ -197,7 +288,7 @@ void _handleRqPagePost(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *sessio
void _handleRqPageGraphGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPageGraph, dbuf);
AQH_ModDataClient_HandleRequest(m, rq, session, AQH_ModDevices_RunPage2Graph, dbuf);
}
@@ -583,3 +674,41 @@ AQDG_GRAPH_DATAPAIR_LIST *_createDataPairListFromDataPoints(const uint64_t *data
void AQH_ModDevices_ReadDevicesAndValues(AQCGI_MODULE *m, AQH_DATACLIENT *dc)
{
AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
deviceList=AQH_DataClient_GetDevices(dc, NULL);
AQH_ModDevices_SetDeviceList(m, deviceList);
valueList=AQH_DataClient_GetValues(dc, NULL, 0);
AQH_ModDevices_SetValueList(m, valueList);
if (deviceList && valueList) {
AQH_VALUE *v;
v=AQH_Value_List_First(valueList);
while(v) {
const char *s;
s=AQH_Value_GetDeviceNameForSystem(v);
if (s && *s) {
AQH_DEVICE *device;
device=AQH_Device_List_GetByNameForSystem(deviceList, s);
if (device) {
AQH_Value_SetDevicePtr(v, device);
}
else {
DBG_ERROR(NULL, "Device %s not found in list", s);
}
}
v=AQH_Value_List_Next(v);
}
}
}

View File

@@ -42,6 +42,14 @@
void AQH_ModDevices_Extend(AQCGI_MODULE *m, AQCGI_SERVICE *sv, const char *baseFolder);
int AQH_ModDevices_Create(AQCGI_SERVICE *sv);
AQH_DEVICE_LIST *AQH_ModDevices_GetDeviceList(const AQCGI_MODULE *m);
void AQH_ModDevices_SetDeviceList(AQCGI_MODULE *m, AQH_DEVICE_LIST *deviceList);
AQH_VALUE_LIST *AQH_ModDevices_GetValueList(const AQCGI_MODULE *m);
void AQH_ModDevices_SetValueList(AQCGI_MODULE *m, AQH_VALUE_LIST *valueList);
void AQH_ModDevices_ReadDevicesAndValues(AQCGI_MODULE *m, AQH_DATACLIENT *dc);
uint32_t AQH_ModDevices_ColorFromHexString(const char *s);
uint32_t AQH_ModDevices_HtmlColorToValueRGBW(uint32_t colorIn);

View File

@@ -0,0 +1,23 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_MODULES_DEVICES_P_H
#define AQHOME_CGI_MODULES_DEVICES_P_H
#include "aqhome-cgi/modules/devices/mdevices.h"
struct AQH_MOD_DEVICES {
AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
};
typedef struct AQH_MOD_DEVICES AQH_MOD_DEVICES;
#endif

View File

@@ -67,7 +67,6 @@ static void _handlePageGraph(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT
static void _genPageGraph(AQH_DATACLIENT *dc, 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(AQCGI_MODULE *m, const char *sPageId, const char *sGraphId, GWEN_BUFFER *dbuf);
static void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, GWEN_BUFFER *dbuf);
@@ -286,8 +285,8 @@ void _genPageGraph(AQH_DATACLIENT *dc, const char *sFilename, GWEN_XMLNODE *nGra
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"));
tsBegin=AQH_ParseTime(GWEN_XMLNode_GetProperty(nGraph, "begin", "-4h"));
tsEnd=AQH_ParseTime(GWEN_XMLNode_GetProperty(nGraph, "end", "0"));
s=GWEN_XMLNode_GetProperty(nGraph, "lowerLimit", NULL);
if (s && *s) {
@@ -380,69 +379,6 @@ AQDG_GRAPH_DATAPAIR_LIST *_readCurveData(AQH_DATACLIENT *dc, GWEN_XMLNODE *nCurv
// 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;

View File

@@ -0,0 +1,453 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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 "./mdevices_page2.h"
#include "./mdevices_page2_graph.h"
#include "aqhome-cgi/modules/mdataclient.h"
#include "aqhome-cgi/modules/devices/mdevices_pagectx.h"
#include <aqcgi/service/module.h>
#include <aqdiagram/graph/timegraph.h>
#include <aqdiagram/graph/w_graph.h>
#include <aqdiagram/draw/context_cairo.h>
#include <gwenhywfar/debug.h>
#include <gwenhywfar/timestamp.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/directory.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
/* ------------------------------------------------------------------------------------------------
* defs and enums
* ------------------------------------------------------------------------------------------------
*/
#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs
#define DOC_PAGE2 "page.html"
#define FOLDER_PAGES "pages2"
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _sendPageList(AQCGI_MODULE *m, GWEN_BUFFER *dbuf);
static GWEN_STRINGLIST *_listPageFiles(AQCGI_MODULE *m);
static GWEN_XMLNODE *_readPage(AQCGI_MODULE *m, const char *sPageName);
static GWEN_XMLNODE *_readPageFile(const char *sFilename);
static void _writePage(AQCGI_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf);
static void _writePageChildElements(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *n, GWEN_BUFFER *dbuf);
static void _writeHtmlElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static void _writePageElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *n, GWEN_BUFFER *dbuf);
static void _writePageDataSet(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static void _writePageItem(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
static AQH_MD_PAGECTX *_createInitialCtx(AQCGI_MODULE *m, AQH_DATACLIENT *dc);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
void AQH_ModDevices_RunPage2Get(AQCGI_MODULE *m, AQCGI_REQUEST *rq,
GWEN_UNUSED AQCGI_SESSION *session,
AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf)
{
GWEN_DB_NODE *dbQuery;
const char *sPageId;
DBG_INFO(NULL, "RunPage2Get");
dbQuery=AQCGI_Request_GetDbQuery(rq);
sPageId=dbQuery?GWEN_DB_GetCharValue(dbQuery, "page", 0, NULL):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");
}
}
else {
DBG_ERROR(NULL, "Reading page list");
_sendPageList(m, dbuf);
}
}
void AQH_ModDevices_RunPage2Graph(AQCGI_MODULE *m, AQCGI_REQUEST *rq,
GWEN_UNUSED AQCGI_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)
AQH_ModDevices_HandleGraph(m, rq, dc, sPageId, sGraphId, dbuf);
}
void AQH_ModDevices_SetCharValue(GWEN_DB_NODE *dbDest, uint32_t flags, const char *sVar, const char *sValue, GWEN_DB_NODE *dbContext)
{
if (sValue && *sValue) {
GWEN_BUFFER *tbuf;
int rv;
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
rv=GWEN_DB_ReplaceVars(dbContext, sValue, tbuf);
if (rv<0) {
DBG_ERROR(NULL, "Error replacing vars for %s=[%s]", sVar, sValue);
}
else {
/*DBG_ERROR(NULL, "Setting value [%s]=[%s] (%s)", sVar, GWEN_Buffer_GetStart(tbuf), sValue);*/
GWEN_DB_SetCharValue(dbDest, flags, sVar, GWEN_Buffer_GetStart(tbuf));
}
GWEN_Buffer_free(tbuf);
}
else
GWEN_DB_DeleteVar(dbDest, sVar);
}
void _sendPageList(AQCGI_MODULE *m, GWEN_BUFFER *dbuf)
{
GWEN_STRINGLIST *sl;
GBAS(dbuf, "<h1>Page List</h1>\n");
sl=_listPageFiles(m);
if (sl) {
GWEN_STRINGLISTENTRY *se;
GBAS(dbuf,
"<table class=\"datatable\">\n"
"<thead><tr><th>Page</th><th>Title</th></tr></thead>\n"
"<tbody>\n");
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *filename;
filename=GWEN_StringListEntry_Data(se);
if (filename && *filename) {
GWEN_XMLNODE *node;
DBG_ERROR(NULL, "Reading file \"%s\"", filename);
node=_readPageFile(filename);
if (node) {
GWEN_XMLNODE *nPage;
nPage=GWEN_XMLNode_FindFirstTag(node, "page", NULL, NULL);
if (nPage) {
const char *sId;
const char *sTitle;
sId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
sTitle=GWEN_XMLNode_GetProperty(nPage, "title", sId);
if (sId && *sId)
GBAA(dbuf, "<tr><td><a href=\""DOC_PAGE2"?page=%s\">%s</a></td><td>%s</td></tr>\n", sId, sId, sTitle);
}
GWEN_XMLNode_free(node);
}
}
se=GWEN_StringListEntry_Next(se);
}
GBAS(dbuf, "</tbody></table>");
GWEN_StringList_free(sl);
}
else {
GBAS(dbuf, "No pages.");
}
}
GWEN_STRINGLIST *_listPageFiles(AQCGI_MODULE *m)
{
GWEN_BUFFER *fbuf;
AQCGI_SERVICE *sv;
GWEN_STRINGLIST *sl;
int rv;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%s"FOLDER_PAGES, AQCGI_Service_GetRuntimeFolder(sv), GWEN_DIR_SEPARATOR_S);
sl=GWEN_StringList_new();
rv=GWEN_Directory_GetMatchingFilesRecursively(GWEN_Buffer_GetStart(fbuf), sl, "*.xml");
if (rv<0) {
DBG_INFO(NULL, "Error reading pages (%d)", rv);
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
if (GWEN_StringList_Count(sl)<1) {
GWEN_StringList_free(sl);
GWEN_Buffer_free(fbuf);
return NULL;
}
GWEN_Buffer_free(fbuf);
return sl;
}
GWEN_XMLNODE *_readPage(AQCGI_MODULE *m, const char *sPageName)
{
GWEN_BUFFER *fbuf;
AQCGI_SERVICE *sv;
GWEN_XMLNODE *fileNode;
sv=AQH_ModService_GetService(m);
fbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAA(fbuf, "%s%s"FOLDER_PAGES"%s", AQCGI_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:"<no name>", strerror(errno), errno);
GWEN_XMLNode_free(fileNode);
return NULL;
}
return fileNode;
}
void _writePage(AQCGI_MODULE *m, AQH_DATACLIENT *dc, GWEN_XMLNODE *nPage, GWEN_BUFFER *dbuf)
{
const char *sPageId;
const char *s;
AQH_MD_PAGECTX *ctx;
ctx=_createInitialCtx(m, dc);
sPageId=GWEN_XMLNode_GetProperty(nPage, "id", NULL);
/* title */
s=GWEN_XMLNode_GetProperty(nPage, "title", NULL);
if (s && *s)
GBAA(dbuf, "<h1>%s</h1>\n", s);
_writePageChildElements(ctx, sPageId, nPage, dbuf);
AQH_MDPageCtx_free(ctx);
}
void _writePageChildElements(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
GWEN_XMLNODE *n;
n=GWEN_XMLNode_GetChild(nElement);
while(n) {
_writePageElement(ctx, sPageId, n, dbuf);
n=GWEN_XMLNode_Next(n);
}
}
void _writePageElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
if (GWEN_XMLNode_GetType(nElement)==GWEN_XMLNodeTypeTag) {
const char *sElementName;
sElementName=GWEN_XMLNode_GetData(nElement);
if (sElementName && *sElementName) {
if (strcasecmp(sElementName, "dataset")==0)
_writePageDataSet(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "item")==0)
_writePageItem(ctx, sPageId, nElement, dbuf);
else if (strcasecmp(sElementName, "table")==0 ||
strcasecmp(sElementName, "tr")==0 ||
strcasecmp(sElementName, "td")==0 ||
strcasecmp(sElementName, "p")==0)
_writeHtmlElement(ctx, sPageId, nElement, dbuf);
}
}
else if (GWEN_XMLNode_GetType(nElement)==GWEN_XMLNodeTypeData) {
const char *s;
/* copy data after stripping unnecessary blanks */
s=GWEN_XMLNode_GetData(nElement);
if (s && *s) {
GWEN_BUFFER *tbuf;
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
GBAS(tbuf, s);
GWEN_Text_CondenseBuffer(tbuf);
GBAS(dbuf, GWEN_Buffer_GetStart(tbuf));
GWEN_Buffer_free(tbuf);
}
}
}
void _writeHtmlElement(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sElementName;
const char *sClass;
sElementName=GWEN_XMLNode_GetData(nElement);
sClass=GWEN_XMLNode_GetProperty(nElement, "class", NULL); /* TODO: copy all attributes */
if (sClass)
GBAA(dbuf, "<%s class=\"%s\">", sElementName, sClass);
else
GBAA(dbuf, "<%s>", sElementName);
_writePageChildElements(ctx, sPageId, nElement, dbuf);
GBAA(dbuf, "</%s>", sElementName);
}
void _writePageDataSet(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sRoom;
const char *sVarType;
const char *sModality;
const char *sVarName;
int modality;
int varType;
AQH_MD_PAGECTX *newCtx;
/* prepare new context */
sVarType=GWEN_XMLNode_GetProperty(nElement, "vartype", NULL);
sRoom=GWEN_XMLNode_GetProperty(nElement, "room", NULL);
sModality=GWEN_XMLNode_GetProperty(nElement, "modality", NULL);
sVarName=GWEN_XMLNode_GetProperty(nElement, "varname", NULL);
modality=sModality?AQH_ValueModality_fromString(sModality):AQH_ValueModality_Unknown;
varType=sVarType?AQH_ValueType_fromString(sVarType):AQH_ValueType_Sensor;
newCtx=AQH_MDPageCtx_CopyWithMatchingVars(ctx, varType, sRoom, modality, sVarName);
/* enter context */
_writePageChildElements(newCtx, sPageId, nElement, dbuf);
/* free new context */
AQH_MDPageCtx_free(newCtx);
}
void _writePageItem(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
GWEN_XMLNODE *n;
const char *sTitle;
/* write title */
sTitle=GWEN_XMLNode_GetProperty(nElement, "title", NULL);
if (sTitle && *sTitle)
GBAA(dbuf, "<h2>%s</h2>\n", sTitle);
/* write real element */
n=GWEN_XMLNode_GetChild(nElement);
while(n) {
const char *sName;
sName=GWEN_XMLNode_GetData(n);
if (sName && *sName) {
if (strcasecmp(sName, "graph")==0)
AQH_ModDevices_PageWriteGraph(ctx, sPageId, n, dbuf);
// else if (strcasecmp(sName, "actor")==0)
// _writePageActor(ctx, sPageId, n, dbuf);
// else if (strcasecmp(sName, "valueTable")==0)
// _writePageValueTable(ctx, sPageId, n, dbuf);
else {
/* unhandled */
}
}
n=GWEN_XMLNode_Next(n);
}
}
AQH_MD_PAGECTX *_createInitialCtx(AQCGI_MODULE *m, AQH_DATACLIENT *dc)
{
AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
AQH_MD_PAGECTX *ctx;
AQH_ModDevices_ReadDevicesAndValues(m, dc);
deviceList=AQH_ModDevices_GetDeviceList(m);
valueList=AQH_ModDevices_GetValueList(m);
ctx=AQH_MDPageCtx_new(m, dc);
AQH_MDPageCtx_SetDevices(ctx, deviceList);
if (valueList)
AQH_MDPageCtx_SetValues(ctx, AQH_Value_List_dup(valueList));
return ctx;
}

View File

@@ -0,0 +1,29 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_PAGE2_H
#define AQHOME_CGI_MDEVICES_PAGE2_H
#include "aqhome-cgi/modules/devices/mdevices.h"
#include "aqhome/aqhome.h"
#include "aqhome/dataclient/client.h"
#include <aqcgi/request.h>
#include <gwenhywfar/buffer.h>
void AQH_ModDevices_RunPage2Get(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf);
void AQH_ModDevices_RunPage2Graph(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, AQH_DATACLIENT *dc, GWEN_BUFFER *dbuf);
void AQH_ModDevices_SetCharValue(GWEN_DB_NODE *dbDest, uint32_t flags, const char *sVar, const char *sValue, GWEN_DB_NODE *dbCtx);
#endif

View File

@@ -0,0 +1,448 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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 "./mdevices_page2_graph.h"
#include <aqcgi/service/module.h>
#include <aqdiagram/graph/timegraph.h>
#include <aqdiagram/graph/w_graph.h>
#include <aqdiagram/draw/context_cairo.h>
#include <gwenhywfar/directory.h>
#include <gwenhywfar/debug.h>
/* ------------------------------------------------------------------------------------------------
* defs and enums
* ------------------------------------------------------------------------------------------------
*/
#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs
#define DEFAULT_GRAPH_WIDTH 640
#define DEFAULT_GRAPH_HEIGHT 480
#define DEFAULT_GRAPH_PRECISION 2
#define DEFAULT_GRAPH_REFRESHTIME 120
#define DEFAULT_GRAPHCONF_REFRESH 60
#define DOC_PAGE2_GRAPH "pgraph.html"
#define FOLDER_CACHE "pages2"
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _isGraphConfFileCurrent(AQH_MD_PAGECTX *ctx, const char *sPageId, const char *sGraphId, int refreshTime);
static void _mkGraphConfigForEveryValue(AQH_MD_PAGECTX *ctx, GWEN_DB_NODE *dbGraph, GWEN_XMLNODE *n);
static void _setupDbForDeviceAndValue(const AQH_VALUE *v, GWEN_DB_NODE *dbCtx);
static void _mkGraphConfigCurve(AQH_MD_PAGECTX *ctx, GWEN_DB_NODE *dbGraph, GWEN_XMLNODE *nElement);
static GWEN_DB_NODE *_dbGraphFromXml(GWEN_XMLNODE *nElement, GWEN_DB_NODE *dbCtx);
static GWEN_DB_NODE *_dbCurveFromXml(GWEN_XMLNODE *nElement, GWEN_DB_NODE *dbCtx);
static void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, const char *lnk, GWEN_BUFFER *dbuf);
static void _genPageGraph(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbGraph, const char *sFilename);
static int _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_DB_NODE *dbGraph, uint64_t tsBegin, uint64_t tsEnd);
static void _mkPathForGraph(AQCGI_MODULE *m, const char *sPageId, const char *sGraphId, const char *ext, GWEN_BUFFER *buf);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
void AQH_ModDevices_PageWriteGraph(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf)
{
const char *sGraphId;
const char *sLink;
int w;
int h;
sLink=GWEN_XMLNode_GetProperty(nElement, "link", NULL);
w=GWEN_XMLNode_GetIntProperty(nElement, "width", DEFAULT_GRAPH_WIDTH);
h=GWEN_XMLNode_GetIntProperty(nElement, "height", DEFAULT_GRAPH_HEIGHT);
sGraphId=GWEN_XMLNode_GetProperty(nElement, "id", NULL);
if (!_isGraphConfFileCurrent(ctx, sPageId, sGraphId, DEFAULT_GRAPHCONF_REFRESH)) {
GWEN_DB_NODE *dbGraph;
GWEN_XMLNODE *n;
GWEN_BUFFER *nbuf;
int rv;
dbGraph=_dbGraphFromXml(nElement, AQH_MDPageCtx_GetDbVars(ctx));
/* write real element */
n=GWEN_XMLNode_GetFirstTag(nElement);
while(n) {
const char *sName;
sName=GWEN_XMLNode_GetData(n);
if (sName && *sName) {
if (strcasecmp(sName, "ForEveryValue")==0)
_mkGraphConfigForEveryValue(ctx, dbGraph, n);
else if (strcasecmp(sName, "curve")==0)
_mkGraphConfigCurve(ctx, dbGraph, n);
else {
/* unhandled */
}
}
n=GWEN_XMLNode_GetNextTag(n);
}
/* write config file */
nbuf=GWEN_Buffer_new(0, 256, 0, 1);
_mkPathForGraph(AQH_MDPageCtx_GetModule(ctx), sPageId, sGraphId, "conf", nbuf);
rv=AQH_ModService_WriteDbFileSafely(dbGraph, GWEN_Buffer_GetStart(nbuf));
if (rv<0) {
DBG_ERROR(NULL, "Error writing graph config to \"%s\": %d", GWEN_Buffer_GetStart(nbuf), rv);
}
GWEN_Buffer_free(nbuf);
GWEN_DB_Group_free(dbGraph);
}
/* write graph link */
_addGraphLink(sPageId, sGraphId, w, h, sLink, dbuf);
}
int _isGraphConfFileCurrent(AQH_MD_PAGECTX *ctx, const char *sPageId, const char *sGraphId, int refreshTime)
{
int rv;
GWEN_BUFFER *nbuf;
nbuf=GWEN_Buffer_new(0, 256, 0, 1);
_mkPathForGraph(AQH_MDPageCtx_GetModule(ctx), sPageId, sGraphId, "conf", nbuf);
rv=AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(nbuf), refreshTime);
GWEN_Buffer_free(nbuf);
return rv;
}
void _mkGraphConfigForEveryValue(AQH_MD_PAGECTX *ctx, GWEN_DB_NODE *dbGraph, GWEN_XMLNODE *nElement)
{
AQH_VALUE_LIST *valueList;
valueList=AQH_MDPageCtx_GetValues(ctx);
if (valueList) {
const AQH_VALUE *v;
GWEN_DB_NODE *dbCtx;
dbCtx=AQH_MDPageCtx_GetDbVars(ctx);
v=AQH_Value_List_First(valueList);
while(v) {
GWEN_XMLNODE *n;
_setupDbForDeviceAndValue(v, dbCtx);
n=GWEN_XMLNode_GetFirstTag(nElement);
while(n) {
const char *sName;
sName=GWEN_XMLNode_GetData(n);
if (sName && *sName) {
if (strcasecmp(sName, "curve")==0)
_mkGraphConfigCurve(ctx, dbGraph, n);
else {
/* unhandled */
}
}
n=GWEN_XMLNode_GetNextTag(n);
}
v=AQH_Value_List_Next(v);
}
}
else {
DBG_ERROR(NULL, "No values");
}
}
void _setupDbForDeviceAndValue(const AQH_VALUE *v, GWEN_DB_NODE *dbCtx)
{
GWEN_DB_NODE *db;
AQH_DEVICE *device;
db=GWEN_DB_GetGroup(dbCtx, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "value");
AQH_Value_toDb(v, db);
device=AQH_Value_GetDevicePtr(v);
if (device) {
const char *sNameForSystem;
const char *sLocation;
const char *sDescription;
GWEN_BUFFER *tbuf;
db=GWEN_DB_GetGroup(dbCtx, GWEN_DB_FLAGS_OVERWRITE_GROUPS, "device");
AQH_Device_toDb(device, db);
/* generate descriptive name */
sNameForSystem=AQH_Device_GetNameForSystem(device);
sLocation=AQH_Device_GetLocation(device);
sDescription=AQH_Device_GetDescription(device);
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
if (sLocation && sDescription)
GBAA(tbuf, "%s - %s", sDescription, sLocation);
else if (sLocation)
GBAA(tbuf, "%s", sLocation);
else if (sDescription)
GBAA(tbuf, "%s", sDescription);
else
GBAA(tbuf, "%s", sNameForSystem);
GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "descriptiveName", GWEN_Buffer_GetStart(tbuf));
GWEN_Buffer_free(tbuf);
}
else {
DBG_ERROR(NULL, "No device pointer in device %s", AQH_Value_GetNameForSystem(v));
}
}
void _mkGraphConfigCurve(AQH_MD_PAGECTX *ctx, GWEN_DB_NODE *dbGraph, GWEN_XMLNODE *nElement)
{
GWEN_DB_NODE *dbCurve;
dbCurve=_dbCurveFromXml(nElement, AQH_MDPageCtx_GetDbVars(ctx));
GWEN_DB_AddGroup(dbGraph, dbCurve);
}
GWEN_DB_NODE *_dbGraphFromXml(GWEN_XMLNODE *nElement, GWEN_DB_NODE *dbCtx)
{
GWEN_DB_NODE *dbGraph;
dbGraph=GWEN_DB_Group_new("graph");
AQH_ModDevices_SetCharValue(dbGraph, 0, "id", GWEN_XMLNode_GetProperty(nElement, "id", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbGraph, 0, "title", GWEN_XMLNode_GetProperty(nElement, "title", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbGraph, 0, "begin", GWEN_XMLNode_GetProperty(nElement, "begin", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbGraph, 0, "end", GWEN_XMLNode_GetProperty(nElement, "end", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbGraph, 0, "lowerLimit", GWEN_XMLNode_GetProperty(nElement, "lowerLimit", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbGraph, 0, "upperLimit", GWEN_XMLNode_GetProperty(nElement, "upperLimit", NULL), dbCtx);
GWEN_DB_SetIntValue(dbGraph, 0, "width", GWEN_XMLNode_GetIntProperty(nElement, "width", DEFAULT_GRAPH_WIDTH));
GWEN_DB_SetIntValue(dbGraph, 0, "height", GWEN_XMLNode_GetIntProperty(nElement, "height", DEFAULT_GRAPH_HEIGHT));
GWEN_DB_SetIntValue(dbGraph, 0, "precision", GWEN_XMLNode_GetIntProperty(nElement, "precision", DEFAULT_GRAPH_PRECISION));
GWEN_DB_SetIntValue(dbGraph, 0, "refreshTime", GWEN_XMLNode_GetIntProperty(nElement, "refreshTime", DEFAULT_GRAPH_REFRESHTIME));
return dbGraph;
}
GWEN_DB_NODE *_dbCurveFromXml(GWEN_XMLNODE *nElement, GWEN_DB_NODE *dbCtx)
{
GWEN_DB_NODE *dbCurve;
dbCurve=GWEN_DB_Group_new("curve");
AQH_ModDevices_SetCharValue(dbCurve, 0, "title", GWEN_XMLNode_GetProperty(nElement, "title", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbCurve, 0, "value", GWEN_XMLNode_GetProperty(nElement, "value", NULL), dbCtx);
AQH_ModDevices_SetCharValue(dbCurve, 0, "modifier", GWEN_XMLNode_GetProperty(nElement, "modifier", NULL), dbCtx);
return dbCurve;
}
void _addGraphLink(const char *sPageId, const char *sGraphId, int w, int h, const char *lnk, GWEN_BUFFER *dbuf)
{
if (lnk && *lnk)
GBAA(dbuf, "<a href=\"%s\">", lnk);
GBAS(dbuf, "<img src=\""DOC_PAGE2_GRAPH"?page=");
AQH_ModService_EscapeToBuffer(sPageId, dbuf);
GBAS(dbuf, "&graph=");
AQH_ModService_EscapeToBuffer(sGraphId, dbuf);
GBAA(dbuf, "\" alt=\"%s\" width=\"%d\" height=\"%d\"", sGraphId, w, h);
GBAS(dbuf, "/>");
if (lnk && *lnk)
GBAS(dbuf, "</a>");
}
void AQH_ModDevices_HandleGraph(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc,
const char *sPageId, const char *sGraphId,
GWEN_BUFFER *dbuf)
{
GWEN_BUFFER *nbuf;
GWEN_DB_NODE *dbGraph;
int rv;
nbuf=GWEN_Buffer_new(0, 256, 0, 1);
_mkPathForGraph(m, sPageId, sGraphId, "conf", nbuf);
dbGraph=GWEN_DB_Group_new("graph");
rv=GWEN_DB_ReadFile(dbGraph, GWEN_Buffer_GetStart(nbuf), GWEN_DB_FLAGS_DEFAULT | GWEN_PATH_FLAGS_CREATE_GROUP | GWEN_DB_FLAGS_LOCKFILE);
if (rv<0) {
DBG_ERROR(NULL, "Error reading graph config \"%s\": %d", GWEN_Buffer_GetStart(nbuf), rv);
}
else {
int refreshTime;
refreshTime=GWEN_DB_GetIntValue(dbGraph, "refreshTime", 0, DEFAULT_GRAPH_REFRESHTIME);
GWEN_Buffer_Reset(nbuf);
_mkPathForGraph(m, sPageId, sGraphId, "png", nbuf);
if (!AQH_ModService_FileIsCurrent(GWEN_Buffer_GetStart(nbuf), refreshTime)) {
DBG_INFO(NULL, "Generating graph \"%s\"", GWEN_Buffer_GetStart(nbuf));
_genPageGraph(dc, dbGraph, GWEN_Buffer_GetStart(nbuf));
}
AQH_ModService_RespondWithMimeFile(rq, GWEN_Buffer_GetStart(nbuf), "image/png", dbuf);
}
GWEN_DB_Group_free(dbGraph);
GWEN_Buffer_free(nbuf);
}
void _genPageGraph(AQH_DATACLIENT *dc, GWEN_DB_NODE *dbGraph, const char *sFilename)
{
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_DB_GetCharValue(dbGraph, "title", 0, "untitled");
w=GWEN_DB_GetIntValue(dbGraph, "width", 0, DEFAULT_GRAPH_WIDTH);
h=GWEN_DB_GetIntValue(dbGraph, "height", 0, DEFAULT_GRAPH_HEIGHT);
precision=GWEN_DB_GetIntValue(dbGraph, "precision", 0, DEFAULT_GRAPH_PRECISION);
tsBegin=AQH_ParseTime(GWEN_DB_GetCharValue(dbGraph, "begin", 0, "-4h"));
tsEnd=AQH_ParseTime(GWEN_DB_GetCharValue(dbGraph, "end", 0, "0"));
s=GWEN_DB_GetCharValue(dbGraph, "lowerLimit", 0, 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_DB_GetCharValue(dbGraph, "upperLimit", 0, 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);
if (_addCurves(dc, g, dbGraph, tsBegin, tsEnd)) {
AQDG_TimeGraph_SetupTicks(g, tickFlags, lowerLimit, upperLimit);
DBG_INFO(NULL, "Draw graph %s", sFilename);
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);
}
else {
DBG_ERROR(NULL, "No curves added");
AQDG_Graph_free(g);
}
}
int _addCurves(AQH_DATACLIENT *dc, AQDG_GRAPH *g, GWEN_DB_NODE *dbGraph, uint64_t tsBegin, uint64_t tsEnd)
{
GWEN_DB_NODE *dbCurve;
int curvesAdded=0;
dbCurve=GWEN_DB_FindFirstGroup(dbGraph, "curve");
if (dbCurve==NULL) {
DBG_ERROR(NULL, "No curve found");
}
while(dbCurve) {
const char *sCurveLabel;
const char *sModifier;
const char *sValueName;
sCurveLabel=GWEN_DB_GetCharValue(dbCurve, "title", 0, NULL);
sValueName=GWEN_DB_GetCharValue(dbCurve, "value", 0, NULL);
sModifier=GWEN_DB_GetCharValue(dbCurve, "modifier", 0, NULL);
DBG_INFO(NULL, "Handling curve %s", sCurveLabel?sCurveLabel:"<unnamed>");
if (sValueName && *sValueName && sModifier && *sModifier) {
AQDG_GRAPH_DATAPAIR_LIST *dpList;
dpList=AQH_ModDevices_RequestDataPairList(dc, sValueName, tsBegin, tsEnd, 10000000);
if (dpList) {
DBG_INFO(NULL, "Adding data for %s", sCurveLabel?sCurveLabel:"<no title>");
AQDG_TimeGraph_ModifyDataAndAddCurve(g, sCurveLabel?sCurveLabel:"<no title>", sModifier, dpList);
curvesAdded++;
}
}
else {
DBG_ERROR(NULL, "Incomplete curve data");
}
dbCurve=GWEN_DB_FindNextGroup(dbCurve, "curve");
}
return curvesAdded;
}
void _mkPathForGraph(AQCGI_MODULE *m, const char *sPageId, const char *sGraphId, const char *ext, GWEN_BUFFER *buf)
{
AQCGI_SERVICE *sv;
const char *s;
int rv;
GWEN_BUFFER *tbuf;
sv=AQH_ModService_GetService(m);
/* make sure folder exists */
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
s=AQCGI_Service_GetCacheFolder(sv);
GBAA(tbuf, "%s%s"FOLDER_CACHE, s, GWEN_DIR_SEPARATOR_S);
rv=GWEN_Directory_GetPath(GWEN_Buffer_GetStart(tbuf), 0);
if (rv<0) {
DBG_ERROR(NULL, "Error getting path \"%s\"", GWEN_Buffer_GetStart(tbuf));
}
GWEN_Buffer_free(tbuf);
GBAA(buf, "%s%s"FOLDER_CACHE"%s%s-%s.%s", s, GWEN_DIR_SEPARATOR_S, GWEN_DIR_SEPARATOR_S, sPageId, sGraphId, ext?ext:"");
}

View File

@@ -0,0 +1,25 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_PAGE2_GRAPH_H
#define AQHOME_CGI_MDEVICES_PAGE2_GRAPH_H
#include "aqhome-cgi/modules/devices/mdevices_page2.h"
#include "aqhome-cgi/modules/devices/mdevices_pagectx.h"
void AQH_ModDevices_PageWriteGraph(AQH_MD_PAGECTX *ctx, const char *sPageId, GWEN_XMLNODE *nElement, GWEN_BUFFER *dbuf);
void AQH_ModDevices_HandleGraph(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQH_DATACLIENT *dc,
const char *sPageId, const char *sGraphId,
GWEN_BUFFER *dbuf);
#endif

View File

@@ -0,0 +1,29 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_PAGE2_P_H
#define AQHOME_CGI_MDEVICES_PAGE2_P_H
#include "aqhome-cgi/modules/devices/mdevices_page2.h"
struct AQH_MD_PAGECTX {
GWEN_TREE2_ELEMENT(AQH_MD_PAGECTX);
AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
};
#endif

View File

@@ -0,0 +1,211 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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 "./mdevices_pagectx_p.h"
#include <gwenhywfar/misc.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/debug.h>
/* ------------------------------------------------------------------------------------------------
* global vars
* ------------------------------------------------------------------------------------------------
*/
GWEN_TREE2_FUNCTIONS(AQH_MD_PAGECTX, AQH_MDPageCtx);
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static int _valueMatches(const AQH_VALUE *value, int varType, const char *room, int modality, const char *vname);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
AQH_MD_PAGECTX *AQH_MDPageCtx_new(AQCGI_MODULE *module, AQH_DATACLIENT *dataClient)
{
AQH_MD_PAGECTX *ctx;
GWEN_NEW_OBJECT(AQH_MD_PAGECTX, ctx);
if (ctx) {
GWEN_TREE2_INIT(AQH_MD_PAGECTX, ctx, AQH_MDPageCtx);
ctx->module=module;
ctx->dataClient=dataClient;
ctx->dbVars=GWEN_DB_Group_new("vars");
}
return ctx;
}
void AQH_MDPageCtx_free(AQH_MD_PAGECTX *ctx)
{
if (ctx) {
GWEN_DB_Group_free(ctx->dbVars);
AQH_Value_List_free(ctx->valueList);
GWEN_TREE2_FINI(AQH_MD_PAGECTX, ctx, AQH_MDPageCtx);
GWEN_FREE_OBJECT(ctx);
}
}
AQH_MD_PAGECTX *AQH_MDPageCtx_CopyWithMatchingVars(const AQH_MD_PAGECTX *octx,
int varType, const char *room, int modality, const char *vname)
{
AQH_MD_PAGECTX *ctx=NULL;
if (octx) {
ctx=AQH_MDPageCtx_new(octx->module, octx->dataClient);
if (ctx) {
/* copy dbVars */
GWEN_DB_Group_free(ctx->dbVars);
ctx->dbVars=GWEN_DB_Group_dup(octx->dbVars);
/* copy device list */
ctx->deviceList=octx->deviceList;
/* copy matching values from value list */
if (octx->valueList) {
AQH_VALUE_LIST *valueList;
const AQH_VALUE *vSrc;
DBG_ERROR(NULL, "Copying values");
valueList=AQH_Value_List_new();
vSrc=AQH_Value_List_First(octx->valueList);
while(vSrc) {
if (_valueMatches(vSrc, varType, room, modality, vname)) {
AQH_VALUE *vDest;
DBG_ERROR(NULL, "+ %s", AQH_Value_GetNameForSystem(vSrc));
vDest=AQH_Value_dup(vSrc);
AQH_Value_List_Add(vDest, valueList);
}
vSrc=AQH_Value_List_Next(vSrc);
}
AQH_Value_List_free(ctx->valueList);
if (AQH_Value_List_GetCount(valueList))
ctx->valueList=valueList;
else
AQH_Value_List_free(valueList);
}
}
}
return ctx;
}
int _valueMatches(const AQH_VALUE *value, int varType, const char *room, int modality, const char *vname)
{
int matches=1;
/* fast compares first */
if ((varType!=AQH_ValueType_Unknown) && (AQH_Value_GetValueType(value)!=varType))
matches=0;
if (matches && modality && modality!=AQH_Value_GetModality(value))
matches=0;
/* then slower compares */
if (matches && vname) {
const char *sName;
sName=AQH_Value_GetNameForSystem(value);
if (GWEN_Text_ComparePattern(sName, vname, 0)==-1)
matches=0;
}
if (matches && room) {
const AQH_DEVICE *device;
device=AQH_Value_GetDevicePtr(value);
if (device) {
const char *s;
s=AQH_Device_GetRoomName(device);
if (s && strcasecmp(s, room)!=0)
matches=0;
}
}
return matches?1:0;
}
const AQH_DEVICE_LIST *AQH_MDPageCtx_GetDevices(const AQH_MD_PAGECTX *ctx)
{
return ctx?ctx->deviceList:NULL;
}
void AQH_MDPageCtx_SetDevices(AQH_MD_PAGECTX *ctx, const AQH_DEVICE_LIST *dList)
{
if (ctx)
ctx->deviceList=dList;
}
AQH_VALUE_LIST *AQH_MDPageCtx_GetValues(const AQH_MD_PAGECTX *ctx)
{
return ctx?ctx->valueList:NULL;
}
void AQH_MDPageCtx_SetValues(AQH_MD_PAGECTX *ctx, AQH_VALUE_LIST *vList)
{
if (ctx)
ctx->valueList=vList;
}
GWEN_DB_NODE *AQH_MDPageCtx_GetDbVars(const AQH_MD_PAGECTX *ctx)
{
return ctx?ctx->dbVars:NULL;
}
AQCGI_MODULE *AQH_MDPageCtx_GetModule(const AQH_MD_PAGECTX *ctx)
{
return ctx?ctx->module:NULL;
}
AQH_DATACLIENT *AQH_MDPageCtx_GetDataClient(const AQH_MD_PAGECTX *ctx)
{
return ctx?ctx->dataClient:NULL;
}

View File

@@ -0,0 +1,53 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_PAGECTX_H
#define AQHOME_CGI_MDEVICES_PAGECTX_H
#include "aqhome-cgi/modules/devices/mdevices.h"
#include "aqhome/aqhome.h"
#include "aqhome/dataclient/client.h"
#include "aqhome/data/device.h"
#include "aqhome/data/value.h"
#include <aqcgi/request.h>
#include <gwenhywfar/db.h>
#include <gwenhywfar/buffer.h>
#include <gwenhywfar/tree2.h>
typedef struct AQH_MD_PAGECTX AQH_MD_PAGECTX;
GWEN_TREE2_FUNCTION_LIB_DEFS(AQH_MD_PAGECTX, AQH_MDPageCtx, AQHOME_API);
AQH_MD_PAGECTX *AQH_MDPageCtx_new(AQCGI_MODULE *module, AQH_DATACLIENT *dataClient);
void AQH_MDPageCtx_free(AQH_MD_PAGECTX *ctx);
AQH_MD_PAGECTX *AQH_MDPageCtx_CopyWithMatchingVars(const AQH_MD_PAGECTX *octx,
int varType, const char *room, int modality, const char *vname);
AQCGI_MODULE *AQH_MDPageCtx_GetModule(const AQH_MD_PAGECTX *ctx);
AQH_DATACLIENT *AQH_MDPageCtx_GetDataClient(const AQH_MD_PAGECTX *ctx);
const AQH_DEVICE_LIST *AQH_MDPageCtx_GetDevices(const AQH_MD_PAGECTX *ctx);
void AQH_MDPageCtx_SetDevices(AQH_MD_PAGECTX *ctx, const AQH_DEVICE_LIST *dList);
AQH_VALUE_LIST *AQH_MDPageCtx_GetValues(const AQH_MD_PAGECTX *ctx);
void AQH_MDPageCtx_SetValues(AQH_MD_PAGECTX *ctx, AQH_VALUE_LIST *vList);
GWEN_DB_NODE *AQH_MDPageCtx_GetDbVars(const AQH_MD_PAGECTX *ctx);
#endif

View File

@@ -0,0 +1,33 @@
/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2026 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_PAGECTX_P_H
#define AQHOME_CGI_MDEVICES_PAGECTX_P_H
#include "aqhome-cgi/modules/devices/mdevices_pagectx.h"
struct AQH_MD_PAGECTX {
GWEN_TREE2_ELEMENT(AQH_MD_PAGECTX);
AQCGI_MODULE *module;
AQH_DATACLIENT *dataClient;
const AQH_DEVICE_LIST *deviceList;
AQH_VALUE_LIST *valueList;
GWEN_DB_NODE *dbVars;
};
#endif

View File

@@ -18,9 +18,13 @@
#include <gwenhywfar/debug.h>
#include <gwenhywfar/i18n.h>
#include <gwenhywfar/stringlist.h>
#include <gwenhywfar/timestamp.h>
#include <gwenhywfar/directory.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
@@ -308,6 +312,67 @@ const char *AQH_ValueModality_toString(int i)
uint64_t AQH_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;
}

View File

@@ -87,6 +87,8 @@ AQHOME_API const char *AQH_ValueModality_toString(int i);
AQHOME_API int AQH_ReadDataFromString(int dataType, const char *s, uint16_t *pDataVal, uint16_t *pDataDenom);
AQHOME_API int AQH_ReadDataFromDouble(int dataType, double d, uint16_t *pDataVal, uint16_t *pDataDenom);
AQHOME_API uint64_t AQH_ParseTime(const char *s);
#endif

View File

@@ -18,6 +18,7 @@
<headers>
<header type="sys" loc="pre">aqhome/api.h</header>
<header type="sys" loc="post">aqhome/data/device.h</header>
</headers>
<inlines>
@@ -123,6 +124,18 @@
</member>
<member name="devicePtr" type="AQH_DEVICE" >
<access>public</access>
<flags>volatile</flags>
<dupflags>assign</dupflags>
<copyflags>assign</copyflags>
<getflags>none</getflags>
<setflags>none</setflags>
<default>NULL</default>
<preset>NULL</preset>
</member>
</members>
</type>

View File

@@ -3,6 +3,7 @@
<gwbuild>
<subdirs>
cgi-examples
mcu-examples
</subdirs>

20
doc/cgi-examples/0BUILD Normal file
View File

@@ -0,0 +1,20 @@
<?xml?>
<gwbuild>
<data dist="true">
buero_climate.xml
buero_hum.xml
buero_light.xml
buero_temp.xml
stube_climate.xml
stube_hum.xml
stube_light.xml
stube_temp.xml
</data>
<subdirs>
</subdirs>
</gwbuild>

View File

@@ -0,0 +1,51 @@
<page id="buero_climate" title="Klima im Buero">
<dataset room="Buero">
<table>
<tr>
<dataset modality="Temperature">
<td>
<item>
<graph id="temp4h" title="Temperature (last 4h)" link="page.html?page=buero_temp"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
<tr>
<dataset modality="Humidity">
<td>
<item >
<graph id="hum4h" title="Humidity (last 4h)" link="page.html?page=buero_hum"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
<tr>
<dataset modality="Light">
<td>
<item >
<graph id="light4h" title="Light (last 4h)" link="page.html?page=buero_light"
width="640" height="480" begin="-4h" end="0" refreshtime="120" lowerLimit="-5.0" upperLimit="1034.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="buero_hum" title="Luftfeuchtigkeit im Buero">
<dataset room="Buero" modality="Humidity">
<table>
<tr>
<td>
<item>
<graph id="hum4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="hum24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="hum30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="buero_light" title="Helligkeit im Buero">
<dataset room="Buero" modality="Light">
<table>
<tr>
<td>
<item>
<graph id="light4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="light24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="light30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="buero_temp" title="Temperaturen im Buero">
<dataset room="Buero" modality="Temperature">
<table>
<tr>
<td>
<item>
<graph id="temp4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="temp24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="temp30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,51 @@
<page id="stube_climate" title="Klima im Wohnzimmer">
<dataset room="Stube">
<table>
<tr>
<dataset modality="Temperature">
<td>
<item>
<graph id="temp4h" title="Temperature (last 4h)" link="page.html?page=stube_temp"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
<tr>
<dataset modality="Humidity">
<td>
<item >
<graph id="hum4h" title="Humidity (last 4h)" link="page.html?page=stube_hum"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
<tr>
<dataset modality="Light">
<td>
<item >
<graph id="light4h" title="Light (last 4h)" link="page.html?page=stube_light"
width="640" height="480" begin="-4h" end="0" refreshtime="120" lowerLimit="-5.0" upperLimit="1034.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
</dataset>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="stube_hum" title="Luftfeuchtigkeit im Wohnzimmer">
<dataset room="Stube" modality="Humidity">
<table>
<tr>
<td>
<item>
<graph id="hum4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="hum24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="hum30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="hum12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="110.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="stube_light" title="Helligkeit im Wohnzimmer">
<dataset room="Stube" modality="Light">
<table>
<tr>
<td>
<item>
<graph id="light4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="light24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="light30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="light12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="1034" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>

View File

@@ -0,0 +1,73 @@
<page id="stube_temp" title="Temperaturen im Wohnzimmer">
<dataset room="Stube" modality="Temperature">
<table>
<tr>
<td>
<item>
<graph id="temp4h" title="Letzten 4 Stunden"
width="640" height="480" begin="-4h" end="0" refreshtime="180" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp12h" title="Letzten 12 Stunden"
width="640" height="480" begin="-12h" end="0" refreshtime="300" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm20" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="temp24h" title="Letzten 24 Stunden"
width="640" height="480" begin="-24h" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm30" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp7d" title="Letzten 7 Tagen"
width="640" height="480" begin="-7d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm210" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
<tr>
<td>
<item>
<graph id="temp30d" title="Letzten 30 Tage"
width="640" height="480" begin="-30d" end="0" refreshtime="600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm840" />
</ForEveryValue>
</graph>
</item>
</td>
<td>
<item>
<graph id="temp12m" title="Letzten 12 Monate"
width="640" height="480" begin="-365d" end="0" refreshtime="3600" lowerLimit="-5.0" upperLimit="30.0" >
<ForEveryValue>
<curve title="$(Device/descriptiveName)" value="$(Value/NameForSystem)" modifier="Lm10080" />
</ForEveryValue>
</graph>
</item>
</td>
</tr>
</table>
</dataset>
</page>