From dce1b8698a12cf5993de0d4b4a00eb56a9258c6b Mon Sep 17 00:00:00 2001 From: Martin Preuss Date: Sun, 25 Feb 2024 01:21:47 +0100 Subject: [PATCH] aqhome-tool: add command "addjsondata". --- apps/aqhome-tool/data/0BUILD | 2 + apps/aqhome-tool/data/addjsondata.c | 423 ++++++++++++++++++++++++++++ apps/aqhome-tool/data/addjsondata.h | 21 ++ apps/aqhome-tool/main.c | 2 + 4 files changed, 448 insertions(+) create mode 100644 apps/aqhome-tool/data/addjsondata.c create mode 100644 apps/aqhome-tool/data/addjsondata.h diff --git a/apps/aqhome-tool/data/0BUILD b/apps/aqhome-tool/data/0BUILD index cc97afc..25f9605 100644 --- a/apps/aqhome-tool/data/0BUILD +++ b/apps/aqhome-tool/data/0BUILD @@ -36,6 +36,7 @@ getvalues.h getdevices.h adddata.h + addjsondata.h getdatapoints.h getlastdatapoint.h setdata.h @@ -49,6 +50,7 @@ getvalues.c getdevices.c adddata.c + addjsondata.c getdatapoints.c getlastdatapoint.c setdata.c diff --git a/apps/aqhome-tool/data/addjsondata.c b/apps/aqhome-tool/data/addjsondata.c new file mode 100644 index 0000000..a41492b --- /dev/null +++ b/apps/aqhome-tool/data/addjsondata.c @@ -0,0 +1,423 @@ +/**************************************************************************** + * This file is part of the project AqHome. + * AqHome (c) by 2023 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 "./adddata.h" +#include "../utils.h" + +#include "aqhome/aqhome.h" +#include "aqhome/msg/msg_node.h" +#include "aqhome/ipc/msg_ipc_result.h" +#include "aqhome/ipc/data/msg_data_multidata.h" +#include "aqhome/ipc/data/ipc_data.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define I18S(msg) msg +#define I18N(msg) GWEN_I18N_Translate(PACKAGE, msg) + + +static int _doAddData(GWEN_DB_NODE *dbArgs); +static void _sendCommand(GWEN_MSG_ENDPOINT *epTcp, const char *valueName, const char *valueUnits, const char *deviceName, + uint64_t timestampToSend, double dataToSend); +static GWEN_JSON_ELEM *_readJsonFile(const char *filename); + + + + +int AQH_Tool_AddJsonDataPoint(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv) +{ + GWEN_DB_NODE *dbLocalArgs; + int rv; + const GWEN_ARGS args[]= { + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "brokerAddress", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "t", /* short option */ + "tcpaddress", /* long option */ + I18S("Specify TCP address to connect to (defaults to 127.0.0.1)"), + I18S("Specify TCP address to connect to (defaults to 127.0.0.1)") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Int, /* type */ + "brokerPort", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "P", /* short option */ + "tcpport", /* long option */ + I18S("Specify the TCP port to listen on"), + I18S("Specify the TCP port to listen on") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Int, /* type */ + "timeout", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "T", /* short option */ + NULL, /* long option */ + I18S("Specify timeout in seconds for response"), + I18S("Specify timeout in seconds for response") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "jsonFile", /* name */ + 1, /* minnum */ + 1, /* maxnum */ + "J", /* short option */ + "jsonfile", /* long option */ + I18S("Name/path of the JSON file to read"), + I18S("Name/path of the JSON file to read") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "valueName", /* name */ + 1, /* minnum */ + 1, /* maxnum */ + "N", /* short option */ + "valuename", /* long option */ + I18S("Name/path of the value to add (e.g. server/temp/system)"), + I18S("Name/path of the value to add (e.g. server/temp/system)") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "valueUnits", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "U", /* short option */ + "valueunits", /* long option */ + I18S("Units of the value to add (e.g. \"Grad Celsius\")"), + I18S("Units of the value to add (e.g. \"Grad Celsius\")") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "timestamp", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "t", /* short option */ + "timestamp", /* long option */ + I18S("Timestamp of the data (now if omitted)"), + I18S("Timestamp of the data (now if omitted)") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "value", /* name */ + 1, /* minnum */ + 1, /* maxnum */ + "v", /* short option */ + "value", /* long option */ + I18S("Value to write"), + I18S("Value to write") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "device", /* name */ + 1, /* minnum */ + 1, /* maxnum */ + "d", /* short option */ + "device", /* long option */ + I18S("device name"), + I18S("device name") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "brokerClientId", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "c", /* short option */ + "clientid", /* long option */ + I18S("Specify CLIENTID"), + I18S("Specify CLIENTID") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "userId", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "u", /* short option */ + "userid", /* long option */ + I18S("Specify user id"), + I18S("Specify user id") + }, + { + GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ + GWEN_ArgsType_Char, /* type */ + "password", /* name */ + 0, /* minnum */ + 1, /* maxnum */ + "p", /* short option */ + "password", /* long option */ + I18S("Specify service password"), + I18S("Specify service password") + }, + { + GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */ + GWEN_ArgsType_Int, /* type */ + "help", /* name */ + 0, /* minnum */ + 0, /* maxnum */ + "h", /* short option */ + "help", /* long option */ + "Show this help screen", /* short description */ + "Show this help screen" /* long description */ + } + }; + + dbLocalArgs=GWEN_DB_GetGroup(dbGlobalArgs, GWEN_DB_FLAGS_DEFAULT, "local"); + rv=GWEN_Args_Check(argc, argv, 1, + GWEN_ARGS_MODE_ALLOW_FREEPARAM, + args, + dbLocalArgs); + if (rv==GWEN_ARGS_RESULT_ERROR) { + fprintf(stderr, "ERROR: Could not parse arguments\n"); + return 1; + } + else if (rv==GWEN_ARGS_RESULT_HELP) { + GWEN_BUFFER *ubuf; + + ubuf=GWEN_Buffer_new(0, 1024, 0, 1); + if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) { + fprintf(stderr, "ERROR: Could not create help string\n"); + return 1; + } + fprintf(stderr, "%s\n", GWEN_Buffer_GetStart(ubuf)); + GWEN_Buffer_free(ubuf); + return 0; + } + + AQH_MergeConfigFileIntoConfig(dbLocalArgs, "ConfigFile"); + + return _doAddData(dbLocalArgs); +} + + + +int _doAddData(GWEN_DB_NODE *dbArgs) +{ + GWEN_MSG_ENDPOINT *epTcp; + int timeoutInSeconds; + GWEN_JSON_ELEM *jRoot; + const char *jsonFile; + const char *valueName; + const char *deviceName; + const char *valueUnits; + const char *valueTimestamp; + const char *valueData; + uint64_t timestampToSend; + double dataToSend; + const char *s; + GWEN_MSG *msg; + int rv; + + timeoutInSeconds=GWEN_DB_GetIntValue(dbArgs, "timeout", 0, 5); + jsonFile=GWEN_DB_GetCharValue(dbArgs, "jsonFile", 0, NULL); + deviceName=GWEN_DB_GetCharValue(dbArgs, "device", 0, NULL); + valueName=GWEN_DB_GetCharValue(dbArgs, "valueName", 0, NULL); + valueUnits=GWEN_DB_GetCharValue(dbArgs, "valueUnits", 0, NULL); + valueTimestamp=GWEN_DB_GetCharValue(dbArgs, "timestamp", 0, NULL); + valueData=GWEN_DB_GetCharValue(dbArgs, "value", 0, NULL); + + if (!(jsonFile && *jsonFile)) { + DBG_ERROR(NULL, "ERROR: Missing JSON file name"); + return 1; + } + + if (!(valueName && *valueName)) { + DBG_ERROR(NULL, "ERROR: Missing value name"); + return 1; + } + + if (!(valueData && *valueData)) { + DBG_ERROR(NULL, "ERROR: Missing data"); + return 1; + } + + if (valueTimestamp && *valueTimestamp) { + unsigned long int x; + + if (1!=sscanf("%lu", valueTimestamp, &x)) { + DBG_ERROR(NULL, "ERROR: Invalid timestamp"); + return 1; + } + timestampToSend=(uint64_t) x; + } + else + timestampToSend=(uint64_t) time(NULL); + + jRoot=_readJsonFile(jsonFile); + if (jRoot==NULL) { + DBG_ERROR(NULL, "ERROR: Error reading JSON file \"%s\"", jsonFile); + return 2; + } + + if (valueData[0]=='@') { + GWEN_JSON_ELEM *j; + + j=GWEN_JsonElement_GetElementByPath(jRoot, valueData+1, GWEN_PATH_FLAGS_PATHMUSTEXIST); + if (j==NULL) { + DBG_ERROR(NULL, "ERROR: Path \"%s\" not found in JSON file \"%s\"", valueData+1, jsonFile); + GWEN_JsonElement_free(jRoot); + return 1; + } + + s=GWEN_JsonElement_GetData(j); + if (!(s && *s)) { + DBG_ERROR(NULL, "ERROR: Path \"%s\" in JSON file \"%s\" has no data", valueData+1, jsonFile); + GWEN_JsonElement_free(jRoot); + return 1; + } + valueData=s; + } + + if (valueUnits) { + if (valueUnits[0]=='@') { + GWEN_JSON_ELEM *j; + + j=GWEN_JsonElement_GetElementByPath(jRoot, valueUnits+1, GWEN_PATH_FLAGS_PATHMUSTEXIST); + if (j==NULL) { + DBG_ERROR(NULL, "ERROR: Path \"%s\" not found in JSON file \"%s\"", valueUnits+1, jsonFile); + return 1; + } + s=GWEN_JsonElement_GetData(j); + if (!(s && *s)) { + DBG_ERROR(NULL, "ERROR: Path \"%s\" in JSON file \"%s\" has no data", valueUnits+1, jsonFile); + return 1; + } + valueUnits=s; + } + } + + rv=GWEN_Text_StringToDouble(valueData, &dataToSend); + if (rv<0) { + DBG_ERROR(NULL, "ERROR: Invalid data"); + return 1; + } + + /*fprintf(stdout, "Sending AddData request\n");*/ + + epTcp=Utils_SetupBrokerClientEndpoint(dbArgs, 0); + if (epTcp==NULL) { + DBG_ERROR(NULL, "ERROR creating TCP connection"); + return 2; + } + + _sendCommand(epTcp, valueName, valueUnits, deviceName, timestampToSend, dataToSend); + + GWEN_JsonElement_free(jRoot); + + for (;;) { + uint16_t code; + + msg=Utils_WaitForSpecificIpcMessage(epTcp, AQH_MSGTYPE_IPC_DATA_RESULT, timeoutInSeconds); + if (msg==NULL) { + DBG_ERROR(NULL, "No response received"); + return 2; + } + code=GWEN_IpcMsg_GetCode(msg); + if (code==AQH_MSGTYPE_IPC_DATA_RESULT) { + uint32_t resultCode; + + resultCode=AQH_ResultIpcMsg_GetResultCode(msg); + if (resultCode!=AQH_MSG_IPC_SUCCESS) { + fprintf(stderr, "ERROR: %d\n", resultCode); + GWEN_MsgEndpoint_free(epTcp); + return 3; + } + else { + /*fprintf(stdout, "Data added.\n");*/ + break; + } + } + else { + DBG_INFO(NULL, "Unexpected message \"%d\"", code); + GWEN_MsgEndpoint_free(epTcp); + return 3; + } + } /* for */ + + GWEN_MsgEndpoint_free(epTcp); + return 0; +} + + + +void _sendCommand(GWEN_MSG_ENDPOINT *epTcp, const char *valueName, const char *valueUnits, + const char *deviceName, + uint64_t timestampToSend, double dataToSend) +{ + GWEN_MSG *msgOut; + union {double f; uint64_t i;} u; + uint64_t arrayToSend[2]; + AQH_VALUE *value; + + u.f=dataToSend; + arrayToSend[0]=timestampToSend; + arrayToSend[1]=u.i; + + value=AQH_Value_new(); + AQH_Value_SetName(value, valueName); + AQH_Value_SetValueUnits(value, valueUnits); + AQH_Value_SetDeviceName(value, deviceName); + + msgOut=AQH_MultiDataDataIpcMsg_new(AQH_MSGTYPE_IPC_DATA_UPDATEDATA, value, arrayToSend, 1); + AQH_Value_free(value); + GWEN_MsgEndpoint_AddSendMessage(epTcp, msgOut); +} + + + +GWEN_JSON_ELEM *_readJsonFile(const char *filename) +{ + GWEN_BUFFER *buf; + int rv; + GWEN_JSON_ELEM *j; + + buf=GWEN_Buffer_new(0, 256, 0, 1); + rv=GWEN_SyncIo_Helper_ReadFile(filename, buf); + if (rv<0) { + DBG_ERROR(NULL, "Error reading file \"%s\" (%d)", filename, rv); + GWEN_Buffer_free(buf); + return NULL; + } + + j=GWEN_JsonElement_fromString(GWEN_Buffer_GetStart(buf)); + GWEN_Buffer_free(buf); + if (j==NULL) { + DBG_ERROR(NULL, "Invalid or no JSON data in file \"%s\"", filename); + return NULL; + } + + return j; +} + + + diff --git a/apps/aqhome-tool/data/addjsondata.h b/apps/aqhome-tool/data/addjsondata.h new file mode 100644 index 0000000..a2bdf16 --- /dev/null +++ b/apps/aqhome-tool/data/addjsondata.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 AQHOME_TOOL_ADDJSONDATA_H +#define AQHOME_TOOL_ADDJSONDATA_H + + +#include + + + +int AQH_Tool_AddJsonDataPoint(GWEN_DB_NODE *dbGlobalArgs, int argc, char **argv); + + +#endif + diff --git a/apps/aqhome-tool/main.c b/apps/aqhome-tool/main.c index 703a29f..6da94d4 100644 --- a/apps/aqhome-tool/main.c +++ b/apps/aqhome-tool/main.c @@ -16,6 +16,7 @@ #include "./data/getvalues.h" #include "./data/getdevices.h" #include "./data/adddata.h" +#include "./data/addjsondata.h" #include "./data/getdatapoints.h" #include "./data/getlastdatapoint.h" #include "./data/setdata.h" @@ -80,6 +81,7 @@ int main(int argc, char **argv) GWEN_FE_DAH("getvalues", AQH_Tool_GetValues, I18N("Request list of known values on the data server")), GWEN_FE_DAH("getdevices", AQH_Tool_GetDevices, I18N("Request list of known devices on the data server")), GWEN_FE_DAH("adddata", AQH_Tool_AddDataPoint, I18N("Send a datapoint to the data server")), + GWEN_FE_DAH("addjsondata", AQH_Tool_AddJsonDataPoint, I18N("Send a datapoint from a JSON file to the data server")), GWEN_FE_DAH("getdata", AQH_Tool_GetDataPoints, I18N("Request list of datapoints for a value on the data server")), GWEN_FE_DAH("getlastdata", AQH_Tool_GetLastDataPoint, I18N("Request last datapoint for a value on the data server")), GWEN_FE_DAH("setdata", AQH_Tool_SetData, I18N("Set data for a value on the data server (e.g. a switch or thermostat)")),