From 43b23b2636aa9e90b07c437fcb49604a9e5af0e7 Mon Sep 17 00:00:00 2001 From: Martin Preuss Date: Mon, 10 Jul 2023 21:38:22 +0200 Subject: [PATCH] msg: added endpoint for tty. --- aqhome/msg/0BUILD | 3 + aqhome/msg/endpoint2_tty.c | 397 +++++++++++++++++++++++++++++++++++ aqhome/msg/endpoint2_tty.h | 26 +++ aqhome/msg/endpoint2_tty_p.h | 34 +++ 4 files changed, 460 insertions(+) create mode 100644 aqhome/msg/endpoint2_tty.c create mode 100644 aqhome/msg/endpoint2_tty.h create mode 100644 aqhome/msg/endpoint2_tty_p.h diff --git a/aqhome/msg/0BUILD b/aqhome/msg/0BUILD index 39d361c..00f5da1 100644 --- a/aqhome/msg/0BUILD +++ b/aqhome/msg/0BUILD @@ -50,6 +50,7 @@ endpoint_log.h endpoint_write.h endpoint_tty.h + endpoint2_tty.h msg_node.h msg_ping.h msg_pong.h @@ -78,6 +79,7 @@ endpoint_node_p.h endpoint_log_p.h endpoint_tty_p.h + endpoint2_tty_p.h endpoint_write_p.h @@ -89,6 +91,7 @@ endpoint_node.c endpoint_log.c endpoint_tty.c + endpoint2_tty.c endpoint_write.c msg_node.c msg_ping.c diff --git a/aqhome/msg/endpoint2_tty.c b/aqhome/msg/endpoint2_tty.c new file mode 100644 index 0000000..5a9764c --- /dev/null +++ b/aqhome/msg/endpoint2_tty.c @@ -0,0 +1,397 @@ +/**************************************************************************** + * 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 "aqhome/msg/endpoint2_tty_p.h" + +#include "aqhome/msg/msg_node.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + + +#define AQH_MSG_ENDPOINT2_TTY_BAUDRATE B19200 +#define AQH_MSG_ENDPOINT2_TTY_BYTE_MICROSECS 520 +#define GWEN_ENDPOINT2_TTY_RECONNECT_TIME 10 + + + +GWEN_INHERIT(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY) + + + + +/* ------------------------------------------------------------------------------------------------ + * forward declarations + * ------------------------------------------------------------------------------------------------ + */ + +static void GWENHYWFAR_CB _freeData(void *bp, void *p); + +/* virtual fn for GWEN_MSG_ENDPOINT2 */ +static void _addSockets(GWEN_MSG_ENDPOINT2 *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_SOCKETSET *xSet); + +/* virtual fns for GWEN_MSG_ENDPOINT2_MSGIO */ +static int _getBytesNeededForMessage(GWEN_MSG_ENDPOINT2 *ep, GWEN_MSG *msg); +static int _startMsg(GWEN_MSG_ENDPOINT2 *ep, GWEN_MSG *msg); +static void _endMsg(GWEN_MSG_ENDPOINT2 *ep, GWEN_MSG *msg); + +/* private fns */ +static int _getSocketFd(GWEN_MSG_ENDPOINT2 *ep); +static int _openDevice(GWEN_MSG_ENDPOINT2 *ep); +static int _attnLow(GWEN_MSG_ENDPOINT2 *ep); +static int _attnHigh(GWEN_MSG_ENDPOINT2 *ep); +static int _isAttnLow(GWEN_MSG_ENDPOINT2 *ep); + + + +/* ------------------------------------------------------------------------------------------------ + * implementations + * ------------------------------------------------------------------------------------------------ + */ + + +GWEN_MSG_ENDPOINT2 *AQH_TtyEndpoint_new(const char *devicePath, int groupId) +{ + GWEN_MSG_ENDPOINT2 *ep; + AQH_MSG_ENDPOINT2_TTY *xep; + + ep=GWEN_MsgEndpoint2_new(AQH_MSG_ENDPOINT2_TTY_NAME, groupId); + GWEN_MsgEndpoint2_SetDefaultMessageSize(ep, AQH_MAXMSGSIZE); + + GWEN_NEW_OBJECT(AQH_MSG_ENDPOINT2_TTY, xep); + GWEN_INHERIT_SETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep, xep, _freeData); + + xep->deviceName=strdup(devicePath); + + GWEN_MsgEndpoint2_SetAddSocketsFn(ep, _addSockets); + + /* extend with msg handling code */ + GWEN_MsgIoEndpoint2_Extend(ep); + GWEN_MsgIoEndpoint2_SetGetNeededBytesFn(ep, _getBytesNeededForMessage); + GWEN_MsgIoEndpoint2_SetSendMsgStartFn(ep, _startMsg); + GWEN_MsgIoEndpoint2_SetSendMsgFinishFn(ep, _endMsg); + + return ep; +} + + + +void _freeData(void *bp, void *p) +{ + AQH_MSG_ENDPOINT2_TTY *xep; + + xep=(AQH_MSG_ENDPOINT2_TTY*) p; + free(xep->deviceName); + GWEN_FREE_OBJECT(xep); +} + + + +void _addSockets(GWEN_MSG_ENDPOINT2 *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_UNUSED GWEN_SOCKETSET *xSet) +{ + if (ep) { + if (GWEN_MsgEndpoint2_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_UNCONNECTED) { + time_t now; + + now=time(NULL); + if ((now-GWEN_MsgEndpoint2_GetTimeOfLastStateChange(ep))>=GWEN_ENDPOINT2_TTY_RECONNECT_TIME) { + int rv; + + /* (re)connect, set state */ + DBG_INFO(GWEN_LOGDOMAIN, "Starting to (re-)connect"); + rv=GWEN_TtyEndpoint2_Connect(ep); + if (rv<0) { + DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv); + } + } + } + + if (GWEN_MsgEndpoint2_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_CONNECTED) { + GWEN_SocketSet_AddSocket(readSet, GWEN_MsgEndpoint2_GetSocket(ep)); + if (GWEN_MsgEndpoint2_HaveMessageToSend(ep)) + GWEN_SocketSet_AddSocket(writeSet, GWEN_MsgEndpoint2_GetSocket(ep)); + } + } /* if (ep) */ +} + + + +int GWEN_TtyEndpoint2_Connect(GWEN_MSG_ENDPOINT2 *ep) +{ + if (ep) { + if (GWEN_MsgEndpoint2_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_UNCONNECTED) { + int fd; + + fd=_openDevice(ep); + if (fd<0) { + DBG_INFO(AQH_LOGDOMAIN, "here (%d)", fd); + return fd; + } + else { + GWEN_SOCKET *sk; + + sk=GWEN_Socket_fromFile(fd); + GWEN_MsgEndpoint2_SetSocket(ep, sk); + GWEN_MsgEndpoint2_SetState(ep, GWEN_MSG_ENDPOINT_STATE_CONNECTED); + return 0; + } + } + else { + DBG_ERROR(AQH_LOGDOMAIN, "Endpoint %s: Not unconnected", GWEN_MsgEndpoint2_GetName(ep)); + return GWEN_ERROR_INVALID; + } + } + return GWEN_ERROR_GENERIC; +} + + + +int _getSocketFd(GWEN_MSG_ENDPOINT2 *ep) +{ + if (ep) { + GWEN_SOCKET *sk; + + sk=GWEN_MsgEndpoint2_GetSocket(ep); + if (sk) + return GWEN_Socket_GetSocketInt(sk); + } + return GWEN_ERROR_GENERIC; +} + + + +int _openDevice(GWEN_MSG_ENDPOINT2 *ep) +{ + AQH_MSG_ENDPOINT2_TTY *xep; + int fd; + struct termios options; + int rv; + + xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep); + assert(xep); + fd=open(xep->deviceName, O_NOCTTY | O_NDELAY | O_RDWR); + if (fd<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on open(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + rv=tcgetattr(fd, &(xep->previousOptions)); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on tcgetattr(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + memset(&options, 0, sizeof(options)); /* preset */ + + options.c_cflag=CLOCAL | CREAD | CS8; + options.c_iflag=IGNPAR | IGNBRK; + options.c_oflag=0; + options.c_lflag=0; + cfmakeraw(&options); + options.c_cc[VTIME]=0; /* read timeout in deciseconds */ + options.c_cc[VMIN]=0; /* no minimum number of receive bytes */ + + rv=cfsetispeed(&options, AQH_MSG_ENDPOINT2_TTY_BAUDRATE); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetispeed(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + rv=cfsetospeed(&options, AQH_MSG_ENDPOINT2_TTY_BAUDRATE); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetospeed(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + + rv=tcflush(fd, TCIOFLUSH); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on tcflush(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + + rv=tcsetattr(fd, TCSANOW, &options); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on tcsetattr(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + + return fd; +} + + + +int _startMsg(GWEN_MSG_ENDPOINT2 *ep, GWEN_UNUSED GWEN_MSG *msg) +{ + if (ep) { + AQH_MSG_ENDPOINT2_TTY *xep; + + xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep); + assert(xep); + if (xep->intendedAttnState==1) { + int rv; + + rv=_isAttnLow(ep); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + if (rv>0) { + DBG_INFO(AQH_LOGDOMAIN, "Line busy"); + usleep(AQH_MSG_ENDPOINT2_TTY_BYTE_MICROSECS); + return GWEN_ERROR_TIMEOUT; + } + + rv=_attnLow(ep); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "here (%d)", rv); + return rv; + } + usleep(AQH_MSG_ENDPOINT2_TTY_BYTE_MICROSECS/5); + } + } + return 0; +} + + + +void _endMsg(GWEN_MSG_ENDPOINT2 *ep, GWEN_UNUSED GWEN_MSG *msg) +{ + /* TODO: flush before releasing ATTN */ + _attnHigh(ep); +} + + + +int _getBytesNeededForMessage(GWEN_UNUSED GWEN_MSG_ENDPOINT2 *ep, GWEN_MSG *msg) +{ + uint32_t bytesInMsg; + + bytesInMsg=GWEN_Msg_GetBytesInBuffer(msg); + if (bytesInMsgGWEN_Msg_GetMaxSize(msg)) { + DBG_INFO(AQH_LOGDOMAIN, "Message too long for msg (%d > %d)", msgSize, GWEN_Msg_GetMaxSize(msg)); + return GWEN_ERROR_GENERIC; + } + return (int) (msgSize-bytesInMsg); + } +} + + + +int _attnLow(GWEN_MSG_ENDPOINT2 *ep) +{ + AQH_MSG_ENDPOINT2_TTY *xep; + int status; + int rv; + int fd; + + xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep); + assert(xep); + fd=_getSocketFd(ep); + if (fd<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Not connected (%s)", xep->deviceName); + return GWEN_ERROR_IO; + } + rv=ioctl(fd, TIOCMGET, &status); /* GET the State of MODEM bits in Status */ + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + status |= TIOCM_DTR | TIOCM_RTS; /* clear the DTR pin (cave: signals inverted!) */ + rv=ioctl(fd, TIOCMSET, &status); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + xep->intendedAttnState=0; + return 0; +} + + + +int _attnHigh(GWEN_MSG_ENDPOINT2 *ep) +{ + AQH_MSG_ENDPOINT2_TTY *xep; + int status; + int rv; + int fd; + + xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep); + assert(xep); + fd=_getSocketFd(ep); + if (fd<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Not connected (%s)", xep->deviceName); + return GWEN_ERROR_IO; + } + rv=ioctl(fd, TIOCMGET, &status); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + status |= TIOCM_DTR; /* Set the DTR pin */ + status &= ~ (TIOCM_DTR | TIOCM_RTS); /* clear the DTR pin (cave: signals inverted!) */ + rv=ioctl(fd, TIOCMSET, &status); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + + xep->intendedAttnState=1; + return 0; +} + + + +int _isAttnLow(GWEN_MSG_ENDPOINT2 *ep) +{ + AQH_MSG_ENDPOINT2_TTY *xep; + int status; + int rv; + int fd; + + xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT2, AQH_MSG_ENDPOINT2_TTY, ep); + assert(xep); + fd=_getSocketFd(ep); + if (fd<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Not connected (%s)", xep->deviceName); + return GWEN_ERROR_IO; + } + rv=ioctl(fd, TIOCMGET, &status); + if (rv<0) { + DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl(%s): %s (%d)", xep->deviceName, strerror(errno), errno); + return GWEN_ERROR_IO; + } + return (status & TIOCM_CTS)?1:0; +} + + + + + + + diff --git a/aqhome/msg/endpoint2_tty.h b/aqhome/msg/endpoint2_tty.h new file mode 100644 index 0000000..2c39345 --- /dev/null +++ b/aqhome/msg/endpoint2_tty.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + +#ifndef AQH_MSGENDPOINT2_TTY_H +#define AQH_MSGENDPOINT2_TTY_H + + +#include + +#include + + +#define AQH_MSG_ENDPOINT2_TTY_NAME "tty" + + +AQHOME_API GWEN_MSG_ENDPOINT *AQH_TtyNodeEndpoint2_new(const char *devicePath, int groupId); + +AQHOME_API int GWEN_TtyEndpoint2_Connect(GWEN_MSG_ENDPOINT2 *ep); + + +#endif diff --git a/aqhome/msg/endpoint2_tty_p.h b/aqhome/msg/endpoint2_tty_p.h new file mode 100644 index 0000000..a8c4354 --- /dev/null +++ b/aqhome/msg/endpoint2_tty_p.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * 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. + ****************************************************************************/ + +#ifndef AQH_MSGENDPOINT2_TTY_P_H +#define AQH_MSGENDPOINT2_TTY_P_H + + + +#include + +#include "aqhome/msg/endpoint2_tty.h" + +#include + + + + +typedef struct AQH_MSG_ENDPOINT2_TTY AQH_MSG_ENDPOINT2_TTY; +struct AQH_MSG_ENDPOINT2_TTY { + char *deviceName; + struct termios previousOptions; + int intendedAttnState; +}; + + + + + +#endif