/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #define I18N(msg) msg #define I18S(msg) msg static int _serve(GWEN_DB_NODE *dbArgs); static GWEN_MSG_ENDPOINT_MGR *_setupService(GWEN_DB_NODE *dbArgs); static int _setupTty(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs); static int _setupLog(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs); static int _setupIpc(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs); static int _setupMqtt(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs); static int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs); static int _createPidFile(const char *pidFilename); #ifdef HAVE_SIGNAL_H static int _setSignalHandlers(void); static int _setupSigAction(struct sigaction *sa, int sig); static void _signalHandler(int s); static struct sigaction saINT,saTERM, saHUP, saTSTP, saCONT; #endif static int stopService=0; int main(int argc, char **argv) { GWEN_DB_NODE *dbArgs; int rv; GWEN_GUI *gui; const char *s; rv=GWEN_Init(); if (rv) { fprintf(stderr, "ERROR: Unable to init Gwen.\n"); return 2; } GWEN_Logger_Open(0, "aqhomed", 0, GWEN_LoggerType_Console, GWEN_LoggerFacility_User); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Info); rv=AQH_Init(); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); return 2; } dbArgs=GWEN_DB_Group_new("arguments"); rv=_readArgs(argc, argv, dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); return 2; } else if (rv==1) { DBG_INFO(NULL, "Help printed, done"); return 0; } gui=GWEN_Gui_CGui_new(); s=GWEN_DB_GetCharValue(dbArgs, "charset", 0, NULL); if (s && *s) GWEN_Gui_SetCharSet(gui, s); GWEN_Gui_SetGui(gui); rv=_serve(dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); return 2; } return 0; } int _serve(GWEN_DB_NODE *dbArgs) { const char *pidFile; GWEN_MSG_ENDPOINT_MGR *emgr; int rv; rv=_setSignalHandlers(); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); return rv; } pidFile=GWEN_DB_GetCharValue(dbArgs, "pidfile", 0, "aqhomed.pid"); if (pidFile && *pidFile) { rv=_createPidFile(pidFile); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); return rv; } } emgr=_setupService(dbArgs); if (emgr==NULL) { DBG_INFO(NULL, "Error setting up service"); return GWEN_ERROR_GENERIC; } while(!stopService) { DBG_DEBUG(NULL, "Next loop"); AQH_MsgManager_LoopOnce(emgr); } if (pidFile && *pidFile) remove(pidFile); return 0; } GWEN_MSG_ENDPOINT_MGR *_setupService(GWEN_DB_NODE *dbArgs) { GWEN_MSG_ENDPOINT_MGR *emgr; int nodeAddress; const char *dbfile; int rv; nodeAddress=GWEN_DB_GetIntValue(dbArgs, "nodeAddress", 0, 240); dbfile=GWEN_DB_GetCharValue(dbArgs, "dbfile", 0, "aqhome.db"); emgr=AQH_MsgManager_new(nodeAddress & 0xff); if (dbfile && *dbfile) AQH_MsgManager_SetDbFilename(emgr, dbfile); rv=_setupTty(emgr, dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); GWEN_MsgEndpointMgr_free(emgr); return NULL; } rv=_setupLog(emgr, dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); GWEN_MsgEndpointMgr_free(emgr); return NULL; } rv=_setupIpc(emgr, dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); GWEN_MsgEndpointMgr_free(emgr); return NULL; } rv=_setupMqtt(emgr, dbArgs); if (rv<0) { DBG_INFO(NULL, "here (%d)", rv); GWEN_MsgEndpointMgr_free(emgr); return NULL; } return emgr; } int _setupTty(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs) { const char *devicePath; devicePath=GWEN_DB_GetCharValue(dbArgs, "device", 0, "/dev/ttyUSB0"); if (devicePath && *devicePath) { GWEN_MSG_ENDPOINT *epTty; epTty=AQH_TtyNodeEndpoint_new(devicePath, AQH_MSGMGR_ENDPOINTGROUP_NODE); if (epTty==NULL) { DBG_ERROR(NULL, "Error creating endpoint TTY"); return GWEN_ERROR_GENERIC; } GWEN_MsgEndpoint_SetAcceptedGroupIds(epTty, AQH_MSGMGR_ENDPOINTGROUP_NODE); GWEN_MsgEndpointMgr_AddEndpoint(emgr, epTty); } else { DBG_ERROR(NULL, "Missing device path"); return GWEN_ERROR_GENERIC; } return 0; } int _setupLog(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs) { const char *logFile; logFile=GWEN_DB_GetCharValue(dbArgs, "logfile", 0, NULL); if (logFile && *logFile) { GWEN_MSG_ENDPOINT *epLog; epLog=AQH_LogEndpoint_new(logFile, AQH_MSGMGR_ENDPOINTGROUP_NODE); if (epLog==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Error creating endpoint LOG"); return GWEN_ERROR_GENERIC; } GWEN_MsgEndpoint_SetAcceptedGroupIds(epLog, AQH_MSGMGR_ENDPOINTGROUP_NODE); GWEN_MsgEndpointMgr_AddEndpoint(emgr, epLog); } return 0; } int _setupIpc(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs) { const char *tcpAddress; int tcpPort; tcpAddress=GWEN_DB_GetCharValue(dbArgs, "tcpAddress", 0, NULL); tcpPort=GWEN_DB_GetIntValue(dbArgs, "tcpPort", 0, 45454); if (tcpAddress && *tcpAddress && tcpPort) { GWEN_MSG_ENDPOINT *epTcp; epTcp=AQH_TcpdIpcNodeEndpoint_new(tcpAddress, tcpPort, NULL, AQH_MSGMGR_ENDPOINTGROUP_NODE|AQH_MSGMGR_ENDPOINTGROUP_IPC); if (epTcp==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Error creating endpoint TCP"); return GWEN_ERROR_GENERIC; } GWEN_MsgEndpoint_SetAcceptedGroupIds(epTcp, AQH_MSGMGR_ENDPOINTGROUP_NODE | AQH_MSGMGR_ENDPOINTGROUP_IPC); GWEN_MsgEndpointMgr_AddEndpoint(emgr, epTcp); } return 0; } int _setupMqtt(GWEN_MSG_ENDPOINT_MGR *emgr, GWEN_DB_NODE *dbArgs) { const char *mqttAddress; int mqttPort; const char *mqttClientId; const char *mqttTopicPrefix; int mqttKeepAlive; mqttAddress=GWEN_DB_GetCharValue(dbArgs, "mqttAddress", 0, NULL); mqttPort=GWEN_DB_GetIntValue(dbArgs, "mqttPort", 0, 1883); mqttClientId=GWEN_DB_GetCharValue(dbArgs, "mqttClientId", 0, "aqhomed"); mqttTopicPrefix=GWEN_DB_GetCharValue(dbArgs, "mqttTopicPrefix", 0, "aqhome/sensors"); mqttKeepAlive=GWEN_DB_GetIntValue(dbArgs, "mqttKeepAlive", 0, 600); if (mqttAddress && *mqttAddress && mqttPort) { GWEN_MSG_ENDPOINT *epMqtt; DBG_INFO(AQH_LOGDOMAIN, "Connecting to %s (port %d)", mqttAddress, mqttPort); epMqtt=AQH_MqttClientEndpoint_new(mqttAddress, mqttPort, NULL, AQH_MSGMGR_ENDPOINTGROUP_MQTT); if (epMqtt==NULL) { DBG_ERROR(AQH_LOGDOMAIN, "Error creating endpoint TCP"); return GWEN_ERROR_GENERIC; } GWEN_MsgEndpoint_SetAcceptedGroupIds(epMqtt, AQH_MSGMGR_ENDPOINTGROUP_NODE | AQH_MSGMGR_ENDPOINTGROUP_MQTT); if (mqttClientId && *mqttClientId) AQH_MqttClientEndpoint_SetClientId(epMqtt, mqttClientId); if (mqttTopicPrefix && *mqttTopicPrefix) AQH_MqttClientEndpoint_SetTopicPrefix(epMqtt, mqttTopicPrefix); AQH_MqttClientEndpoint_SetKeepAliveTime(epMqtt, mqttKeepAlive); GWEN_MsgEndpointMgr_AddEndpoint(emgr, epMqtt); } return 0; } int _readArgs(int argc, char **argv, GWEN_DB_NODE *dbArgs) { int rv; const GWEN_ARGS args[]= { { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "cfgdir", /* name */ 0, /* minnum */ 1, /* maxnum */ "D", /* short option */ "cfgdir", /* long option */ I18S("Specify the configuration folder"), I18S("Specify the configuration folder") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "charset", /* name */ 0, /* minnum */ 1, /* maxnum */ 0, /* short option */ "charset", /* long option */ I18S("Specify the output character set"), /* short description */ I18S("Specify the output character set") /* long description */ }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "device", /* name */ 0, /* minnum */ 1, /* maxnum */ "d", /* short option */ "device", /* long option */ I18S("Specify the device to communicate with (e.g. /dev/ttyUSB0)"), I18S("Specify the device to communicate with (e.g. /dev/ttyUSB0)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Int, /* type */ "nodeAddress", /* name */ 0, /* minnum */ 1, /* maxnum */ "n", /* short option */ "node", /* long option */ I18S("Specify the node address for the AqHome node adaptor (default 240)"), I18S("Specify the node address for the AqHome node adaptor (default 240)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "logFile", /* name */ 0, /* minnum */ 1, /* maxnum */ "l", /* short option */ "logfile", /* long option */ I18S("Specify a logfile to log received messages to"), I18S("Specify a logfile to log received messages to") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "tcpAddress", /* name */ 0, /* minnum */ 1, /* maxnum */ "t", /* short option */ "tcpaddress", /* long option */ I18S("Specify the TCP address to listen on (disabled if missing)"), I18S("Specify the TCP address to listen on (disabled if missing)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Int, /* type */ "tcpPort", /* 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_Char, /* type */ "mqttAddress", /* name */ 0, /* minnum */ 1, /* maxnum */ "ma", /* short option */ "mqttaddress", /* long option */ I18S("Specify the address of the MQTT server to connect to (disabled if missing)"), I18S("Specify the address of the MQTT server to connect to (disabled if missing)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Int, /* type */ "mqttPort", /* name */ 0, /* minnum */ 1, /* maxnum */ "mp", /* short option */ "mqttport", /* long option */ I18S("Specify the port of the MQTT server (default: 1883)"), I18S("Specify the port of the MQTT server (default: 1883)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "mqttClientId", /* name */ 0, /* minnum */ 1, /* maxnum */ NULL, /* short option */ "mqttclientid", /* long option */ I18S("Specify client id for the MQTT server (default: \"aqhomed\")"), I18S("Specify client id for the MQTT server (default: \"aqhomed\")") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "mqttTopicPrefix", /* name */ 0, /* minnum */ 1, /* maxnum */ "mt", /* short option */ "mqtttopicprefix", /* long option */ I18S("Specify prefix of MQTT topics when publishing (defaults to \"aqhome/sensors\")"), I18S("Specify prefix of MQTT topics when publishing (defaults to \"aqhome/sensors\")") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Int, /* type */ "mqttKeepAlive", /* name */ 0, /* minnum */ 1, /* maxnum */ "mk", /* short option */ "mqttkeepalive", /* long option */ I18S("Specify keepalive time in seconds (defaults: 600)"), I18S("Specify keepalive time in seconds (defaults: 600)") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "dbfile", /* name */ 0, /* minnum */ 1, /* maxnum */ "db", /* short option */ "dbfile", /* long option */ I18S("Specify DB file to read/write node database"), I18S("Specify DB file to read/write node database") }, { GWEN_ARGS_FLAGS_HAS_ARGUMENT, /* flags */ GWEN_ArgsType_Char, /* type */ "pidfile", /* name */ 0, /* minnum */ 1, /* maxnum */ "p", /* short option */ "pidfile", /* long option */ I18S("Specify the PID file"), I18S("Specify the PID file") }, { GWEN_ARGS_FLAGS_HELP | GWEN_ARGS_FLAGS_LAST, /* flags */ GWEN_ArgsType_Int, /* type */ "help", /* name */ 0, /* minnum */ 0, /* maxnum */ "h", /* short option */ "help", I18S("Show this help screen."), I18S("Show this help screen.") } }; rv=GWEN_Args_Check(argc, argv, 1, 0, args, dbArgs); if (rv==GWEN_ARGS_RESULT_ERROR) { fprintf(stderr, "ERROR: Could not parse arguments main\n"); return GWEN_ERROR_INVALID; } else if (rv==GWEN_ARGS_RESULT_HELP) { GWEN_BUFFER *ubuf; ubuf=GWEN_Buffer_new(0, 1024, 0, 1); GWEN_Buffer_AppendArgs(ubuf, I18N("This is version %s.\nUsage: %s [OPTIONS]\n\nOptions:\n"), AQHOME_VERSION_STRING, argv[0]); if (GWEN_Args_Usage(args, ubuf, GWEN_ArgsOutType_Txt)) { fprintf(stderr, "ERROR: Could not create help string\n"); return 1; } GWEN_Buffer_AppendString(ubuf, "\n"); fprintf(stdout, "%s\n", GWEN_Buffer_GetStart(ubuf)); GWEN_Buffer_free(ubuf); return GWEN_ERROR_CLOSE; } return 0; } int _setSignalHandlers(void) { #ifdef HAVE_SIGNAL_H int rv; rv=_setupSigAction(&saINT, SIGINT); if (rv) return rv; rv=_setupSigAction(&saTERM, SIGTERM); if (rv) return rv; rv=_setupSigAction(&saHUP, SIGHUP); if (rv) return rv; # ifdef SIGTSTP rv=_setupSigAction(&saTSTP, SIGTSTP); if (rv) return rv; # endif # ifdef SIGCONT rv=_setupSigAction(&saCONT, SIGCONT); if (rv) return rv; # endif #endif return 0; } int _setupSigAction(struct sigaction *sa, int sig) { sa->sa_handler=_signalHandler; sigemptyset(&sa->sa_mask); sa->sa_flags=0; if (sigaction(sig, sa, 0)) { DBG_ERROR(NULL, "Could not setup signal handler for signal %d", sig); return GWEN_ERROR_IO; } return 0; } void _signalHandler(int s) { switch(s) { case SIGINT: case SIGTERM: case SIGHUP: DBG_WARN(0, "Received signal %d, stopping service in next loop.",s); stopService=1; break; default: DBG_WARN(0, "Unknown signal %d",s); break; } } int _createPidFile(const char *pidFilename) { FILE *f; int pidfd; if (remove(pidFilename)==0) { DBG_ERROR(0, "Old PID file existed, removed. (Unclean shutdown?)"); } #ifdef HAVE_SYS_STAT_H pidfd = open(pidFilename, O_EXCL|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (pidfd < 0) { DBG_ERROR(NULL, "Could not create PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno)); return GWEN_ERROR_IO; } f = fdopen(pidfd, "w"); #else /* HAVE_STAT_H */ f=fopen(pidFilename,"w+"); #endif /* HAVE_STAT_H */ /* write pid */ #ifdef HAVE_GETPID fprintf(f,"%d\n",getpid()); #else fprintf(f,"-1\n"); #endif if (fclose(f)) { DBG_ERROR(0, "Could not close PID file \"%s\" (%s), aborting.", pidFilename, strerror(errno)); return GWEN_ERROR_IO; } return 0; }