Files
aqhomecontrol/aqhome/ipc2/ttyobject.c

263 lines
5.8 KiB
C

/****************************************************************************
* 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 <config.h>
#endif
#include "./ttyobject.h"
#include <aqhome/events2/fdobject.h>
#include <gwenhywfar/debug.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>
#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) {
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) {
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