263 lines
5.8 KiB
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 "./tty_object.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
|
|
|
|
|