Files
aqhomecontrol/apps/aqhome-react/net_read.c
2024-04-03 21:58:34 +02:00

651 lines
18 KiB
C

/****************************************************************************
* This file is part of the project AqHome.
* AqHome (c) by 2024 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./net_read.h"
#include "aqhome-react/types/unit.h"
#include "aqhome-react/types/unitnet.h"
#include "aqhome/aqhome.h"
#include <gwenhywfar/xml.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/debug.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static time_t _getNewestFiletimeFromFileList(const AQHOME_REACT *aqh, const GWEN_STRINGLIST *sl);
static AQHREACT_UNIT_NET_LIST *_readUnitNetFiles(AQHOME_REACT *aqh, const GWEN_STRINGLIST *sl);
static int _readAllNetworksFromFileIntoList(AQHOME_REACT *aqh, const char *sFilename, AQHREACT_UNIT_NET_LIST *unitNetList);
static GWEN_XMLNODE *_readNetworkFromSysconfIntoXml(AQHOME_REACT *aqh, const char *networkName);
static AQHREACT_UNIT_NET *_readUnitNetFromXml(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNetNode);
static GWEN_XMLNODE *_readUnitNetFileToXml(AQHOME_REACT *aqh, const char *sFilename);
static void _readNetParamDefsWithList(AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *unitNetNode);
static int _readUnits(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *unitNetNode);
static int _readLinks(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *unitNetNode);
static AQHREACT_UNIT *_readUnit(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNode);
static int _readParamValuesForList(const AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *parentNode);
static int _readParamValueForList(const AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *paramNode);
static AQHREACT_PARAM *_readNetParamDef(GWEN_XMLNODE *paramNode);
static int _readLink(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *linkNode);
static int _setParamDoubleValueFromString(AQHREACT_PARAM *param, const char *s);
/* ------------------------------------------------------------------------------------------------
* implementations
* ------------------------------------------------------------------------------------------------
*/
time_t AQHomeReact_GetNewestUnitNetFiletime(const AQHOME_REACT *aqh)
{
GWEN_STRINGLIST *sl;
time_t t1;
time_t t2;
sl=AQH_GetListOfMatchingDataFiles("aqhome/react/networks", "*.xml");
t1=_getNewestFiletimeFromFileList(aqh, sl);
GWEN_StringList_free(sl);
sl=AQH_GetListOfMatchingSysconfFiles("aqhome/react/networks", "*.xml");
t2=_getNewestFiletimeFromFileList(aqh, sl);
GWEN_StringList_free(sl);
return (t1>t2)?t1:t2;
}
AQHREACT_UNIT_NET_LIST *AQHomeReact_ReadUnitNetFiles(AQHOME_REACT *aqh)
{
GWEN_STRINGLIST *sl;
sl=AQH_GetListOfMatchingDataFiles("aqhome/react/networks", "*.xml");
if (sl) {
AQHREACT_UNIT_NET_LIST *unitNetList;
unitNetList=_readUnitNetFiles(aqh, sl);
GWEN_StringList_free(sl);
if (unitNetList==NULL) {
DBG_INFO(NULL, "Error reading unit network files");
return NULL;
}
return unitNetList;
}
else {
DBG_ERROR(NULL, "No unit network files");
return NULL;
}
}
time_t _getNewestFiletimeFromFileList(const AQHOME_REACT *aqh, const GWEN_STRINGLIST *sl)
{
time_t resultTime=0;
if (sl) {
GWEN_STRINGLISTENTRY *se;
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *s;
s=GWEN_StringListEntry_Data(se);
if (s && *s) {
struct stat sb;
if (stat(s, &sb)==0) {
time_t t;
t=sb.st_mtim.tv_sec;
if (t>resultTime)
resultTime=t;
}
else {
DBG_WARN(NULL, "Error on stat(%s): %s (%d)", s, strerror(errno), errno);
}
}
se=GWEN_StringListEntry_Next(se);
}
}
return resultTime;
}
GWEN_XMLNODE *_readNetworkFromSysconfIntoXml(AQHOME_REACT *aqh, const char *networkName)
{
GWEN_XMLNODE *n;
GWEN_BUFFER *bufFilename;
GWEN_BUFFER *bufPath;
bufFilename=GWEN_Buffer_new(0, 256, 0, 1);
GWEN_Buffer_AppendString(bufFilename, "aqhome/react/networks/");
GWEN_Buffer_AppendString(bufFilename, networkName);
GWEN_Buffer_AppendString(bufFilename, ".xml");
bufPath=AQH_FindPathOfSysconfFile(GWEN_Buffer_GetStart(bufFilename));
if (bufPath==NULL) {
DBG_ERROR(NULL, "Network file \"%s\" not found in sysconf folders", GWEN_Buffer_GetStart(bufFilename));
GWEN_Buffer_free(bufFilename);
return NULL;
}
GWEN_Buffer_free(bufFilename);
n=_readUnitNetFileToXml(aqh, GWEN_Buffer_GetStart(bufPath));
if (n==NULL) {
DBG_ERROR(NULL, "Error reading network file \"%s\" from sysconf dir", GWEN_Buffer_GetStart(bufPath));
GWEN_Buffer_free(bufPath);
return NULL;
}
GWEN_Buffer_free(bufPath);
return n;
}
AQHREACT_UNIT_NET_LIST *_readUnitNetFiles(AQHOME_REACT *aqh, const GWEN_STRINGLIST *sl)
{
GWEN_STRINGLISTENTRY *se;
AQHREACT_UNIT_NET_LIST *unitNetList;
unitNetList=AQHREACT_UnitNet_List_new();
se=GWEN_StringList_FirstEntry(sl);
while(se) {
const char *s;
s=GWEN_StringListEntry_Data(se);
if (s && *s) {
int rv;
DBG_INFO(NULL, "Reading unit network file \"%s\"", s);
rv=_readAllNetworksFromFileIntoList(aqh, s, unitNetList);
if (rv<0 && rv!=GWEN_ERROR_NO_DATA) {
DBG_WARN(NULL, "Error reading unit network file \"%s\" (%d), ignoring", s, rv);
}
}
se=GWEN_StringListEntry_Next(se);
}
if (AQHREACT_UnitNet_List_GetCount(unitNetList)<1) {
AQHREACT_UnitNet_List_free(unitNetList);
return NULL;
}
return unitNetList;
}
int _readAllNetworksFromFileIntoList(AQHOME_REACT *aqh, const char *sFilename, AQHREACT_UNIT_NET_LIST *unitNetList)
{
GWEN_XMLNODE *rootNode;
GWEN_XMLNODE *netListNode;
GWEN_XMLNODE *netNode;
int rv;
rootNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, NULL);
rv=GWEN_XML_ReadFile(rootNode, sFilename, GWEN_XML_FLAGS_DEFAULT);
if (rv<0) {
DBG_ERROR(NULL, "Error reading XML file \"%s\": %d", sFilename, rv);
GWEN_XMLNode_free(rootNode);
return rv;
}
netListNode=GWEN_XMLNode_FindFirstTag(rootNode, "networks", NULL, NULL);
if (netListNode==NULL)
netListNode=rootNode;
netNode=GWEN_XMLNode_FindFirstTag(netListNode, "network", NULL, NULL);
while(netNode) {
AQHREACT_UNIT_NET *unitNet;
unitNet=_readUnitNetFromXml(aqh, netNode);
if (unitNet)
AQHREACT_UnitNet_List_Add(unitNet, unitNetList);
else {
DBG_ERROR(NULL, "Error loading network from file \"%s\", ignoring.", sFilename);
}
netNode=GWEN_XMLNode_FindNextTag(netNode, "network", NULL, NULL);
}
return 0;
}
AQHREACT_UNIT_NET *_readUnitNetFromXml(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNetNode)
{
AQHREACT_UNIT_NET *unitNet;
const char *s;
int rv;
unitNet=AQHREACT_UnitNet_new();
s=GWEN_XMLNode_GetProperty(unitNetNode, "id", NULL);
AQHREACT_UnitNet_SetName(unitNet, s);
s=GWEN_XMLNode_GetProperty(unitNetNode, "type", NULL);
if (s && *s) {
GWEN_XMLNODE *baseNetXml;
/* uses a template file, load that and only set params from non-template file */
DBG_INFO(NULL, "Loading base network \"%s\"", s);
baseNetXml=_readNetworkFromSysconfIntoXml(aqh, s);
if (baseNetXml==NULL) {
DBG_ERROR(NULL, "Base network \"%s\" not available (error or missing)", s);
AQHREACT_UnitNet_free(unitNet);
return NULL;
}
_readNetParamDefsWithList(AQHREACT_UnitNet_GetParamList(unitNet), baseNetXml);
/* also read netParams from this file (after reading from template) */
rv=_readParamValuesForList(AQHREACT_UnitNet_GetParamList(unitNet), unitNetNode);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_UnitNet_free(unitNet);
GWEN_XMLNode_free(baseNetXml);
return NULL;
}
rv=_readUnits(aqh, unitNet, baseNetXml);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_UnitNet_free(unitNet);
GWEN_XMLNode_free(baseNetXml);
return NULL;
}
rv=_readLinks(aqh, unitNet, baseNetXml);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_UnitNet_free(unitNet);
GWEN_XMLNode_free(baseNetXml);
return NULL;
}
GWEN_XMLNode_free(baseNetXml);
}
else {
/* just directly read network from given XML node (no need to load a template file) */
_readNetParamDefsWithList(AQHREACT_UnitNet_GetParamList(unitNet), unitNetNode);
rv=_readUnits(aqh, unitNet, unitNetNode);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_UnitNet_free(unitNet);
return NULL;
}
rv=_readLinks(aqh, unitNet, unitNetNode);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_UnitNet_free(unitNet);
return NULL;
}
}
return unitNet;
}
GWEN_XMLNODE *_readUnitNetFileToXml(AQHOME_REACT *aqh, const char *sFilename)
{
GWEN_XMLNODE *rootNode;
GWEN_XMLNODE *netNode;
int rv;
rootNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, NULL);
rv=GWEN_XML_ReadFile(rootNode, sFilename, GWEN_XML_FLAGS_DEFAULT);
if (rv<0) {
DBG_ERROR(NULL, "Error reading XML file \"%s\": %d", sFilename, rv);
GWEN_XMLNode_free(rootNode);
return NULL;
}
netNode=GWEN_XMLNode_FindFirstTag(rootNode, "network", NULL, NULL);
if (netNode) {
GWEN_XMLNode_UnlinkChild(rootNode, netNode);
GWEN_XMLNode_free(rootNode);
return netNode;
}
else {
DBG_ERROR(NULL, "No \"network\" element in network file \"%s\"", sFilename);
return NULL;
}
}
int _readUnits(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *unitNetNode)
{
GWEN_XMLNODE *nGroup;
nGroup=GWEN_XMLNode_FindFirstTag(unitNetNode, "units", NULL, NULL);
if (nGroup) {
GWEN_XMLNODE *n;
n=GWEN_XMLNode_FindFirstTag(nGroup, "unit", NULL, NULL);
while(n) {
AQHREACT_UNIT *unit;
unit=_readUnit(aqh, n);
if (unit)
AQHREACT_UnitNet_AddUnit(unitNet, unit);
else {
DBG_ERROR(NULL, "Error reading unit in net \"%s\"", AQHREACT_UnitNet_GetName(unitNet));
return GWEN_ERROR_BAD_DATA;
}
n=GWEN_XMLNode_FindNextTag(n, "unit", NULL, NULL);
}
}
return 0;
}
int _readLinks(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *unitNetNode)
{
GWEN_XMLNODE *nGroup;
nGroup=GWEN_XMLNode_FindFirstTag(unitNetNode, "links", NULL, NULL);
if (nGroup) {
GWEN_XMLNODE *n;
n=GWEN_XMLNode_FindFirstTag(nGroup, "link", NULL, NULL);
while(n) {
int rv;
rv=_readLink(aqh, unitNet, n);
if (rv<0) {
DBG_ERROR(NULL, "Error reading link in net \"%s\" (%d)", AQHREACT_UnitNet_GetName(unitNet), rv);
return GWEN_ERROR_BAD_DATA;
}
n=GWEN_XMLNode_FindNextTag(n, "link", NULL, NULL);
}
}
return 0;
}
AQHREACT_UNIT *_readUnit(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNode)
{
AQHREACT_UNIT *unit;
const char *unitType;
const char *unitId;
int rv;
unitType=GWEN_XMLNode_GetProperty(unitNode, "type", NULL);
if (!(unitType && *unitType)) {
DBG_ERROR(NULL, "No type name in unit node");
return NULL;
}
unit=AqHomeReact_CreateUnitByName(aqh, unitType);
unitId=GWEN_XMLNode_GetProperty(unitNode, "id", NULL);
AQHREACT_Unit_SetId(unit, unitId);
rv=_readParamValuesForList(AQHREACT_Unit_GetParamList(unit), unitNode);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_Unit_free(unit);
return NULL;
}
return unit;
}
int _readParamValuesForList(const AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *parentNode)
{
GWEN_XMLNODE *nGroup;
nGroup=GWEN_XMLNode_FindFirstTag(parentNode, "params", NULL, NULL);
if (nGroup) {
GWEN_XMLNODE *n;
n=GWEN_XMLNode_FindFirstTag(nGroup, "param", NULL, NULL);
while(n) {
int rv;
rv=_readParamValueForList(paramList, n);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
n=GWEN_XMLNode_FindNextTag(n, "param", NULL, NULL);
}
}
return 0;
}
int _readParamValueForList(const AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *paramNode)
{
const char *paramName;
paramName=GWEN_XMLNode_GetProperty(paramNode, "name", NULL);
if (paramName && *paramName) {
AQHREACT_PARAM *param;
param=AQHREACT_Param_List_GetParamByName(paramList, paramName);
if (param) {
const char *value;
value=GWEN_XMLNode_GetCharValue(paramNode, NULL, NULL);
if (value && *value) {
int dataType;
dataType=AQHREACT_Param_GetDataType(param);
switch(dataType) {
case AQHREACT_DATAOBJECTTYPE_DOUBLE:
{
int rv;
double valueAsDouble;
rv=GWEN_Text_StringToDouble(value, &valueAsDouble);
if (rv<0) {
DBG_ERROR(NULL, "Not a DOUBLE value for param %s in list [%s]", paramName, value);
return rv;
}
AQHREACT_Param_SetDoubleValue(param, valueAsDouble);
break;
}
case AQHREACT_DATAOBJECTTYPE_STRING:
default:
AQHREACT_Param_SetStringValue(param, value);
break;
}
}
}
else {
DBG_ERROR(NULL, "No param name \"%s\" in list", paramName);
return GWEN_ERROR_BAD_DATA;
}
}
return 0;
}
void _readNetParamDefsWithList(AQHREACT_PARAM_LIST *paramList, GWEN_XMLNODE *unitNetNode)
{
GWEN_XMLNODE *nGroup;
nGroup=GWEN_XMLNode_FindFirstTag(unitNetNode, "params", NULL, NULL);
if (nGroup) {
GWEN_XMLNODE *n;
n=GWEN_XMLNode_FindFirstTag(nGroup, "param", NULL, NULL);
while(n) {
AQHREACT_PARAM *param;
param=_readNetParamDef(n);
if (param)
AQHREACT_Param_List_Add(param, paramList);
n=GWEN_XMLNode_FindNextTag(n, "param", NULL, NULL);
}
}
}
AQHREACT_PARAM *_readNetParamDef(GWEN_XMLNODE *paramNode)
{
const char *paramName;
const char *paramType;
const char *paramValue;
paramName=GWEN_XMLNode_GetProperty(paramNode, "name", NULL);
paramType=GWEN_XMLNode_GetProperty(paramNode, "type", "string");
paramValue=GWEN_XMLNode_GetCharValue(paramNode, NULL, NULL);
if (paramName && *paramName) {
int t=AQHREACT_DATAOBJECTTYPE_STRING;
AQHREACT_PARAM *param;
if (paramType) {
if (strcasecmp(paramType, "string")==0)
t=AQHREACT_DATAOBJECTTYPE_STRING;
else if (strcasecmp(paramType, "double")==0)
t=AQHREACT_DATAOBJECTTYPE_DOUBLE;
else {
DBG_ERROR(NULL, "Invalid data type in parameter (%s), assuming string", paramType);
t=AQHREACT_DATAOBJECTTYPE_STRING;
}
}
param=AQHREACT_Param_new();
AQHREACT_Param_SetName(param, paramName);
AQHREACT_Param_SetDataType(param, t);
if (paramValue && *paramValue) {
int rv;
switch(t) {
case AQHREACT_DATAOBJECTTYPE_STRING:
AQHREACT_Param_SetStringValue(param, paramValue);
break;
case AQHREACT_DATAOBJECTTYPE_DOUBLE:
rv=_setParamDoubleValueFromString(param, paramValue);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
AQHREACT_Param_free(param);
return NULL;
}
break;
default:
break;
}
}
return param;
}
return NULL;
}
int _setParamDoubleValueFromString(AQHREACT_PARAM *param, const char *s)
{
int rv;
double value;
rv=GWEN_Text_StringToDouble(s, &value);
if (rv<0) {
DBG_INFO(NULL, "here (%d)", rv);
return rv;
}
AQHREACT_Param_SetDoubleValue(param, value);
return 0;
}
int _readLink(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *linkNode)
{
const char *sourceUnitName;
const char *sourceSlotName;
const char *targetUnitName;
const char *targetSlotName;
AQHREACT_UNIT *sourceUnit;
AQHREACT_UNIT *targetUnit;
AQHREACT_INPUT_SLOT *inputSlot;
AQHREACT_OUTPUT_SLOT *outputSlot;
AQHREACT_LINK *link;
sourceUnitName=GWEN_XMLNode_GetProperty(linkNode, "sourceUnit", NULL);
sourceSlotName=GWEN_XMLNode_GetProperty(linkNode, "sourceSlot", NULL);
targetUnitName=GWEN_XMLNode_GetProperty(linkNode, "targetUnit", NULL);
targetSlotName=GWEN_XMLNode_GetProperty(linkNode, "targetSlot", NULL);
if (!(sourceUnitName && *sourceUnitName && sourceSlotName && *sourceSlotName &&
targetUnitName && *targetUnitName && targetSlotName && *targetSlotName)) {
DBG_ERROR(NULL,
"Link in net \"%s\" needs properties sourceUnit, sourceSlot, targetUnit and targetSlot",
AQHREACT_UnitNet_GetName(unitNet));
return GWEN_ERROR_BAD_DATA;
}
sourceUnit=AQHREACT_UnitNet_GetUnitById(unitNet, sourceUnitName);
if (sourceUnit==NULL)
sourceUnit=AqHomeReact_FindUnitByNetNameAndUnitId(aqh, NULL, sourceUnitName);
if (sourceUnit==NULL) {
DBG_ERROR(NULL, "Source unit \"%s\" not found", sourceUnitName);
return GWEN_ERROR_NOT_FOUND;
}
outputSlot=AQHREACT_Unit_GetOutputSlotByName(sourceUnit, sourceSlotName);
if (outputSlot==NULL) {
DBG_ERROR(NULL, "Output slot \"%s\" not found for source unit \"%s\"", sourceSlotName, sourceUnitName);
return GWEN_ERROR_NOT_FOUND;
}
targetUnit=AQHREACT_UnitNet_GetUnitById(unitNet, targetUnitName);
if (targetUnit==NULL)
targetUnit=AqHomeReact_FindUnitByNetNameAndUnitId(aqh, NULL, targetUnitName);
if (targetUnit==NULL) {
DBG_ERROR(NULL, "Target unit \"%s\" not found", targetUnitName);
return GWEN_ERROR_NOT_FOUND;
}
inputSlot=AQHREACT_Unit_GetOrCreateUnusedInputSlotByName(targetUnit, targetSlotName);
if (inputSlot==NULL) {
DBG_ERROR(NULL, "Input slot \"%s\" not found for target unit \"%s\"", targetSlotName, targetUnitName);
return GWEN_ERROR_NOT_FOUND;
}
link=AQHREACT_Link_new();
AQHREACT_Link_SetTargetUnitId(link, targetUnitName);
AQHREACT_Link_SetTargetUnit(link, targetUnit);
AQHREACT_Link_SetTargetInputSlotIdForUnit(link, AQHREACT_InputSlot_GetIdForUnit(inputSlot));
AQHREACT_OutputSlot_AddLink(outputSlot, link);
return 0;
}