/**************************************************************************** * 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/endpoint_tty_p.h" #include "aqhome/msg/msg_node.h" #include #include #include #include #include #include #include #include #include #define AQH_MSG_ENDPOINT_TTY_BAUDRATE B19200 #define AQH_MSG_ENDPOINT_TTY_BYTE_MICROSECS 520 #define GWEN_ENDPOINT_TTY_RECONNECT_TIME 10 GWEN_INHERIT(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_TTY) /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static void GWENHYWFAR_CB _freeData(void *bp, void *p); /* virtual fn for GWEN_MSG_ENDPOINT */ static void _addSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_SOCKETSET *xSet); /* virtual fns for GWEN_MSG_ENDPOINT_MSGIO */ static int _getBytesNeededForMessage(GWEN_MSG_ENDPOINT *ep, GWEN_MSG *msg); static int _startMsg(GWEN_MSG_ENDPOINT *ep, GWEN_MSG *msg); static void _endMsg(GWEN_MSG_ENDPOINT *ep, GWEN_MSG *msg); /* private fns */ static int _getSocketFd(GWEN_MSG_ENDPOINT *ep); static int _openDevice(GWEN_MSG_ENDPOINT *ep); static int _attnLow(GWEN_MSG_ENDPOINT *ep); static int _attnHigh(GWEN_MSG_ENDPOINT *ep); static int _isAttnLow(GWEN_MSG_ENDPOINT *ep); /* ------------------------------------------------------------------------------------------------ * implementations * ------------------------------------------------------------------------------------------------ */ GWEN_MSG_ENDPOINT *AQH_TtyEndpoint_new(const char *devicePath, int groupId) { GWEN_MSG_ENDPOINT *ep; AQH_MSG_ENDPOINT_TTY *xep; ep=GWEN_MsgEndpoint_new(AQH_MSG_ENDPOINT_TTY_NAME, groupId); GWEN_MsgEndpoint_SetDefaultMessageSize(ep, AQH_MAXMSGSIZE); GWEN_NEW_OBJECT(AQH_MSG_ENDPOINT_TTY, xep); GWEN_INHERIT_SETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_TTY, ep, xep, _freeData); xep->deviceName=strdup(devicePath); GWEN_MsgEndpoint_SetAddSocketsFn(ep, _addSockets); /* extend with msg handling code */ GWEN_MsgIoEndpoint_Extend(ep); GWEN_MsgIoEndpoint_SetGetNeededBytesFn(ep, _getBytesNeededForMessage); GWEN_MsgIoEndpoint_SetSendMsgStartFn(ep, _startMsg); GWEN_MsgIoEndpoint_SetSendMsgFinishFn(ep, _endMsg); return ep; } void _freeData(void *bp, void *p) { AQH_MSG_ENDPOINT_TTY *xep; xep=(AQH_MSG_ENDPOINT_TTY*) p; free(xep->deviceName); GWEN_FREE_OBJECT(xep); } void _addSockets(GWEN_MSG_ENDPOINT *ep, GWEN_SOCKETSET *readSet, GWEN_SOCKETSET *writeSet, GWEN_UNUSED GWEN_SOCKETSET *xSet) { if (ep) { if (GWEN_MsgEndpoint_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_UNCONNECTED) { time_t now; now=time(NULL); if ((now-GWEN_MsgEndpoint_GetTimeOfLastStateChange(ep))>=GWEN_ENDPOINT_TTY_RECONNECT_TIME) { int rv; /* (re)connect, set state */ DBG_INFO(AQH_LOGDOMAIN, "Starting to (re-)connect"); rv=AQH_TtyEndpoint_Connect(ep); if (rv<0) { DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv); } } } if (GWEN_MsgEndpoint_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_CONNECTED) { GWEN_SocketSet_AddSocket(readSet, GWEN_MsgEndpoint_GetSocket(ep)); if (GWEN_MsgEndpoint_HaveMessageToSend(ep)) GWEN_SocketSet_AddSocket(writeSet, GWEN_MsgEndpoint_GetSocket(ep)); } } /* if (ep) */ } int AQH_TtyEndpoint_Connect(GWEN_MSG_ENDPOINT *ep) { if (ep) { if (GWEN_MsgEndpoint_GetState(ep)==GWEN_MSG_ENDPOINT_STATE_UNCONNECTED) { int fd; DBG_INFO(AQH_LOGDOMAIN, "Connecting TTY device"); 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_MsgEndpoint_SetSocket(ep, sk); GWEN_MsgEndpoint_SetState(ep, GWEN_MSG_ENDPOINT_STATE_CONNECTED); GWEN_MsgEndpoint_DiscardInput(ep); _attnHigh(ep); return 0; } } else { DBG_ERROR(AQH_LOGDOMAIN, "Endpoint %s: Not unconnected", GWEN_MsgEndpoint_GetName(ep)); return GWEN_ERROR_INVALID; } } return GWEN_ERROR_GENERIC; } int _getSocketFd(GWEN_MSG_ENDPOINT *ep) { if (ep) { GWEN_SOCKET *sk; sk=GWEN_MsgEndpoint_GetSocket(ep); if (sk) { return GWEN_Socket_GetSocketInt(sk); } } return GWEN_ERROR_GENERIC; } int _openDevice(GWEN_MSG_ENDPOINT *ep) { AQH_MSG_ENDPOINT_TTY *xep; int fd; struct termios options; int rv; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_TTY, ep); assert(xep); DBG_INFO(AQH_LOGDOMAIN, "Opening device %s", xep->deviceName); 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; } DBG_INFO(AQH_LOGDOMAIN, "Device %s open (socket %d)", xep->deviceName, fd); 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_ENDPOINT_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_ENDPOINT_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_ENDPOINT *ep, GWEN_UNUSED GWEN_MSG *msg) { if (ep) { AQH_MSG_ENDPOINT_TTY *xep; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_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_ENDPOINT_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_ENDPOINT_TTY_BYTE_MICROSECS/5); } } return 0; } void _endMsg(GWEN_MSG_ENDPOINT *ep, GWEN_UNUSED GWEN_MSG *msg) { /* TODO: flush before releasing ATTN */ _attnHigh(ep); } int _getBytesNeededForMessage(GWEN_UNUSED GWEN_MSG_ENDPOINT *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_ENDPOINT *ep) { AQH_MSG_ENDPOINT_TTY *xep; int status; int rv; int fd; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_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_ENDPOINT *ep) { AQH_MSG_ENDPOINT_TTY *xep; int status; int rv; int fd; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_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_ENDPOINT *ep) { AQH_MSG_ENDPOINT_TTY *xep; int status; int rv; int fd; xep=GWEN_INHERIT_GETDATA(GWEN_MSG_ENDPOINT, AQH_MSG_ENDPOINT_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; }