diff --git a/apps/aqhome-react/0BUILD b/apps/aqhome-react/0BUILD
index 415ba5f..0d3de92 100644
--- a/apps/aqhome-react/0BUILD
+++ b/apps/aqhome-react/0BUILD
@@ -42,6 +42,7 @@
init.h
fini.h
loop.h
+ net_read.h
@@ -50,6 +51,7 @@
init.c
fini.c
loop.c
+ net_read.c
main.c
diff --git a/apps/aqhome-react/init.c b/apps/aqhome-react/init.c
index 8fb40fd..541a7b2 100644
--- a/apps/aqhome-react/init.c
+++ b/apps/aqhome-react/init.c
@@ -11,6 +11,7 @@
#endif
#include "./init.h"
+#include "./net_read.h"
#include "./aqhome_react_p.h"
#include "aqhome-react/units/u_timer.h"
#include "aqhome-react/units/u_varchanges.h"
@@ -95,7 +96,7 @@ int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv)
}
}
- _setupBuiltinUnits(aqh);
+ AqHomeReact_ReloadUnitNets(aqh);
rv=_setupBroker(aqh, dbArgs);
if (rv<0) {
@@ -108,6 +109,29 @@ int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv)
+void AqHomeReact_ReloadUnitNets(AQHOME_REACT *aqh)
+{
+ AQHREACT_UNIT_NET_LIST *unitNetList;
+
+ AQHREACT_UnitNet_List_Clear(aqh->unitNetList);
+ AQHREACT_Unit_List_Clear(aqh->unitList);
+ aqh->timerUnit=NULL;
+ aqh->varChangeUnit=NULL;
+
+ _setupBuiltinUnits(aqh);
+
+ unitNetList=AQHREACT_ReadUnitNetFiles(aqh);
+ if (unitNetList) {
+ AQHREACT_UnitNet_List_free(aqh->unitNetList);
+ aqh->unitNetList=unitNetList;
+ }
+ else {
+ DBG_INFO(NULL, "No unit nets read");
+ }
+}
+
+
+
int _createPidFile(const char *pidFilename)
{
FILE *f;
diff --git a/apps/aqhome-react/init.h b/apps/aqhome-react/init.h
index b520658..800a83b 100644
--- a/apps/aqhome-react/init.h
+++ b/apps/aqhome-react/init.h
@@ -15,6 +15,8 @@
int AqHomeReact_Init(AQHOME_REACT *aqh, int argc, char **argv);
+void AqHomeReact_ReloadUnitNets(AQHOME_REACT *aqh);
+
#endif
diff --git a/apps/aqhome-react/net_read.c b/apps/aqhome-react/net_read.c
new file mode 100644
index 0000000..03628fb
--- /dev/null
+++ b/apps/aqhome-react/net_read.c
@@ -0,0 +1,342 @@
+/****************************************************************************
+ * 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
+#endif
+
+#include "./net_read.h"
+#include "aqhome-react/types/unit.h"
+#include "aqhome-react/types/unitnet.h"
+
+#include "aqhome/aqhome.h"
+
+#include
+#include
+#include
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * forward declarations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+AQHREACT_UNIT_NET_LIST *_readUnitNetFiles(AQHOME_REACT *aqh, const GWEN_STRINGLIST *sl);
+static int _readUnitNetFileToList(AQHOME_REACT *aqh, const char *sFilename, AQHREACT_UNIT_NET_LIST *unitNetList);
+static AQHREACT_UNIT_NET *_readUnitNetFromXml(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNetNode);
+static AQHREACT_UNIT *_readUnit(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNode);
+static int _readParam(AQHREACT_UNIT *unit, GWEN_XMLNODE *n);
+static int _readLink(AQHOME_REACT *aqh, AQHREACT_UNIT_NET *unitNet, GWEN_XMLNODE *linkNode);
+
+
+
+/* ------------------------------------------------------------------------------------------------
+ * implementations
+ * ------------------------------------------------------------------------------------------------
+ */
+
+AQHREACT_UNIT_NET_LIST *AQHREACT_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;
+ }
+}
+
+
+
+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=_readUnitNetFileToList(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 _readUnitNetFileToList(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;
+ GWEN_XMLNODE *nGroup;
+
+ unitNet=AQHREACT_UnitNet_new();
+ s=GWEN_XMLNode_GetProperty(unitNetNode, "id", NULL);
+ AQHREACT_UnitNet_SetName(unitNet, s);
+
+ 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));
+ AQHREACT_UnitNet_free(unitNet);
+ return NULL;
+ }
+ n=GWEN_XMLNode_FindNextTag(n, "unit", NULL, NULL);
+ }
+ }
+
+ 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);
+ AQHREACT_UnitNet_free(unitNet);
+ return NULL;
+ }
+ n=GWEN_XMLNode_FindNextTag(n, "link", NULL, NULL);
+ }
+ }
+
+ return unitNet;
+}
+
+
+
+
+AQHREACT_UNIT *_readUnit(AQHOME_REACT *aqh, GWEN_XMLNODE *unitNode)
+{
+ AQHREACT_UNIT *unit;
+ const char *unitType;
+ const char *unitId;
+ GWEN_XMLNODE *nGroup;
+
+ 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);
+
+ nGroup=GWEN_XMLNode_FindFirstTag(unitNode, "params", NULL, NULL);
+ if (nGroup) {
+ GWEN_XMLNODE *n;
+
+ n=GWEN_XMLNode_FindFirstTag(nGroup, "param", NULL, NULL);
+ while(n) {
+ int rv;
+
+ rv=_readParam(unit, n);
+ if (rv<0) {
+ DBG_INFO(NULL, "here (%d)", rv);
+ AQHREACT_Unit_free(unit);
+ return NULL;
+ }
+ n=GWEN_XMLNode_FindNextTag(n, "param", NULL, NULL);
+ }
+ }
+
+ return unit;
+}
+
+
+
+int _readParam(AQHREACT_UNIT *unit, GWEN_XMLNODE *paramNode)
+{
+ const char *paramName;
+
+ paramName=GWEN_XMLNode_GetProperty(paramNode, "name", NULL);
+ if (paramName && *paramName) {
+ AQHREACT_PARAM *param;
+
+ param=AQHREACT_Unit_GetParamByName(unit, 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 unit %s [%s]", paramName, AQHREACT_Unit_GetId(unit), 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 unit %s", paramName, AQHREACT_Unit_GetId(unit));
+ return GWEN_ERROR_BAD_DATA;
+ }
+ }
+
+ 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;
+}
+
+
+
+
diff --git a/apps/aqhome-react/net_read.h b/apps/aqhome-react/net_read.h
new file mode 100644
index 0000000..de78638
--- /dev/null
+++ b/apps/aqhome-react/net_read.h
@@ -0,0 +1,21 @@
+/****************************************************************************
+ * 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.
+ ****************************************************************************/
+
+#ifndef AQHOMEREACT_NET_READ_H
+#define AQHOMEREACT_NET_READ_H
+
+
+#include "./aqhome_react.h"
+
+
+AQHREACT_UNIT_NET_LIST *AQHREACT_ReadUnitNetFiles(AQHOME_REACT *aqh);
+
+
+#endif
+
+
diff --git a/apps/aqhome-react/types/unit.h b/apps/aqhome-react/types/unit.h
index 0225d69..626fbf7 100644
--- a/apps/aqhome-react/types/unit.h
+++ b/apps/aqhome-react/types/unit.h
@@ -65,6 +65,7 @@ AQHREACT_INPUT_SLOT_LIST *AQHREACT_Unit_GetInputSlots(const AQHREACT_UNIT *unit)
void AQHREACT_Unit_AddInputSlot(AQHREACT_UNIT *unit, AQHREACT_INPUT_SLOT *inSlot);
AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByIdForUnit(const AQHREACT_UNIT *unit, int id);
AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetInputSlotByName(const AQHREACT_UNIT *unit, const char *s);
+AQHREACT_INPUT_SLOT *AQHREACT_Unit_GetOrCreateUnusedInputSlotByName(AQHREACT_UNIT *unit, const char *s);
AQHREACT_OUTPUT_SLOT_LIST *AQHREACT_Unit_GetOutputSlots(const AQHREACT_UNIT *unit);