/**************************************************************************** * This file is part of the project AqHome. * AqHome (c) by 2025 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 "./tty_object.h" #include #include #include #include #include #include #include #define AQH_TTYOBJECT_BAUDRATE B19200 /* ------------------------------------------------------------------------------------------------ * forward declarations * ------------------------------------------------------------------------------------------------ */ static int _startMsg(AQH_OBJECT *o); static void _endMsg(AQH_OBJECT *o); static int _getAttn(int fd); static int _setAttn(int fd, int val); static int _fdSetBlocking(int sk, int fl); //static int _msleep(long int msec); /* ------------------------------------------------------------------------------------------------ * implementations * ------------------------------------------------------------------------------------------------ */ AQH_OBJECT *AQH_TtyObject_new(AQH_EVENT_LOOP *eventLoop, int fd, int fdMode) { AQH_OBJECT *o; o=AQH_FdObject_new(eventLoop, fd, fdMode); AQH_FdObject_SetStartMsgFn(o, _startMsg); AQH_FdObject_SetEndMsgFn(o, _endMsg); return o; } int _startMsg(AQH_OBJECT *o) { if (o) { if (AQH_Object_GetFlags(o) & AQH_TTYOBJECT_FLAGS_NOATTN) return 0; else { int fd; int rv; fd=AQH_FdObject_GetFd(o); if (fd==-1) return GWEN_ERROR_IO; rv=_getAttn(fd); if (rv<0) { DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv); return rv; } else if (rv==0) { DBG_ERROR(NULL, "Line busy"); return GWEN_ERROR_TRY_AGAIN; /* line busy */ } else { _setAttn(fd, 0); /* set ATTN low */ return 0; } } } else return GWEN_ERROR_INVALID; } void _endMsg(AQH_OBJECT *o) { if (o) { if (!(AQH_Object_GetFlags(o) & AQH_TTYOBJECT_FLAGS_NOATTN)) { int fd; fd=AQH_FdObject_GetFd(o); if (fd>=0) { _setAttn(fd, 1); /* set ATTN high */ } } } } int _getAttn(int fd) { int rv; int status; rv=ioctl(fd, TIOCMGET, &status); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno); return GWEN_ERROR_IO; } return (status & TIOCM_CTS)?0:1; } int _setAttn(int fd, int val) { int rv; int status; rv=ioctl(fd, TIOCMGET, &status); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno); return GWEN_ERROR_IO; } if (val) status &= ~ (TIOCM_DTR | TIOCM_RTS); /* attn high, clear the DTR pin (cave: signals inverted!) */ else status |= TIOCM_DTR | TIOCM_RTS; /* attn low, set the DTR pin (cave: signals inverted!) */ rv=ioctl(fd, TIOCMSET, &status); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno); return GWEN_ERROR_IO; } DBG_ERROR(NULL, "Set ATTN to %d", val); return 0; } int AQH_TtyObject_OpenAndInitDevice(const char *device, struct termios *initialTermiosState) { int fd; struct termios options; int rv; fd=open(device, O_NOCTTY | O_NDELAY | O_RDWR); if (fd<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on open(%s): %s (%d)", device, strerror(errno), errno); return GWEN_ERROR_IO; } DBG_INFO(AQH_LOGDOMAIN, "Device %s open (socket %d)", device, fd); _fdSetBlocking(fd, 0); rv=tcgetattr(fd, &options); if (initialTermiosState) *initialTermiosState=options; options.c_cflag=CLOCAL | CREAD | CS8; options.c_iflag=IGNPAR | IGNBRK; options.c_oflag=0; options.c_lflag&= ~(ICANON | ECHO | ECHOE | ISIG); options.c_cc[VTIME]=0; /* read timeout in deciseconds */ options.c_cc[VMIN]=0; /* no minimum number of receive bytes */ cfmakeraw(&options); rv=cfsetispeed(&options, AQH_TTYOBJECT_BAUDRATE); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetispeed(%s): %s (%d)", device, strerror(errno), errno); return GWEN_ERROR_IO; } rv=cfsetospeed(&options, AQH_TTYOBJECT_BAUDRATE); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetospeed(%s): %s (%d)", device, strerror(errno), errno); return GWEN_ERROR_IO; } rv=tcflush(fd, TCIOFLUSH); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on tcflush(%s): %s (%d)", device, 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)", device, strerror(errno), errno); return GWEN_ERROR_IO; } rv=_setAttn(fd, 1); if (rv<0) { DBG_ERROR(AQH_LOGDOMAIN, "Error on setAttn(%s): %s (%d)", device, strerror(errno), errno); return GWEN_ERROR_IO; } return fd; } int _fdSetBlocking(int fd, int fl) { int prevFlags; int newFlags; /* get current socket flags */ prevFlags=fcntl(fd, F_GETFL); if (prevFlags==-1) { DBG_INFO(AQH_LOGDOMAIN, "fcntl(): %s", strerror(errno)); return GWEN_ERROR_IO; } /* set nonblocking/blocking */ if (fl) newFlags=prevFlags&(~O_NONBLOCK); else newFlags=prevFlags|O_NONBLOCK; if (-1==fcntl(fd, F_SETFL, newFlags)) { DBG_INFO(AQH_LOGDOMAIN, "fcntl(): %s", strerror(errno)); return GWEN_ERROR_IO; } prevFlags=fcntl(fd, F_GETFL); if (prevFlags!=newFlags) { DBG_ERROR(AQH_LOGDOMAIN, "fcntl() did not set flags correctly (%08x!=%08x)", prevFlags, newFlags); return GWEN_ERROR_IO; } return 0; } #if 0 int _msleep(long int msec) { struct timespec ts; int rv; if (msec<0) { errno=EINVAL; return -1; } ts.tv_sec=msec/1000; ts.tv_nsec=(msec%1000)*1000000; do { rv=nanosleep(&ts, &ts); } while (rv && errno==EINTR); return rv; } #endif