/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see .
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Below is needed to set low latency.
#include
#include "aq_serial.h"
#include "utils.h"
#include "config.h"
#include "packetLogger.h"
#include "timespec_subtract.h"
/*
Notes for serial usb speed
File should exist if using ftdi chip, ie ftdi_sio driver.
/sys/bus/usb-serial/devices/ttyUSB0/latency_timer
Set to 1 for fastest latency.
Can also be set in code
ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);
*/
//#define BLOCKING_MODE
static bool _blocking_mode = false;
int _blocking_fds = -1;
static struct termios _oldtio;
static struct timespec last_serial_read_time;
void send_packet(int fd, unsigned char *packet, int length);
//unsigned char getProtocolType(unsigned char* packet);
const char* get_packet_type(unsigned char* packet , int length)
{
static char buf[15];
if (length <= 0 )
return "";
switch (packet[PKT_CMD]) {
case CMD_ACK:
if (packet[5] == NUL)
return "Ack";
else
return "Ack w/ Command";
break;
case CMD_STATUS:
return "Status";
break;
case CMD_MSG:
return "Message";
break;
case CMD_MSG_LONG:
return "Lng Message";
break;
case CMD_PROBE:
return "Probe";
break;
case CMD_GETID:
return "GetID";
break;
case CMD_PERCENT:
return "AR %%";
break;
case CMD_PPM:
return "AR PPM";
break;
case CMD_PDA_0x05:
return "PDA Unknown";
break;
case CMD_PDA_0x1B:
return "PDA Init (*guess*)";
break;
case CMD_PDA_HIGHLIGHT:
return "PDA Hlight";
break;
case CMD_PDA_CLEAR:
return "PDA Clear";
break;
case CMD_PDA_SHIFTLINES:
return "PDA Shiftlines";
break;
case CMD_PDA_HIGHLIGHTCHARS:
return "PDA HlightChars";
break;
case CMD_IAQ_PAGE_MSG:
if (packet[4] == 0x31)
return "iAq setDevice";
else
return "iAq pMessage";
break;
case CMD_IAQ_PAGE_BUTTON:
return "iAq pButton";
break;
case CMD_IAQ_POLL:
return "iAq Poll";
break;
case CMD_IAQ_PAGE_END:
return "iAq PageEnd";
break;
case CMD_IAQ_PAGE_START:
return "iAq PageStart";
break;
case CMD_IAQ_TABLE_MSG:
return "iAq TableMsg";
break;
case CMD_IAQ_PAGE_CONTINUE:
return "iAq PageContinue";
break;
case CMD_IAQ_STARTUP:
return "iAq init";
break;
case CMD_IAQ_TITLE_MESSAGE:
return "iAq ProductName";
break;
case CMD_IAQ_MSG_LONG:
return "iAq Popup message";
break;
case RSSA_DEV_STATUS:
// This is a fail reply 0x10|0x02|0x48|0x13|0x02|0x00|0x10|0x00|0x7f|0x10|0x03|
// Rather than check all, just check 0x02 and checksum sin't I'm not sure 0x10 means faiure without 0x00 around it.
if (packet[4] == 0x02 && packet[8] == 0x7f)
return "RSSA Cmd Error";
else
return "RSSA DevStatus";
break;
case RSSA_DEV_READY:
return "RSSA SendCommand";
break;
case CMD_EPUMP_STATUS:
if (packet[4] == CMD_EPUMP_RPM)
return "ePump RPM";
else if (packet[4] == CMD_EPUMP_WATTS)
return "ePump Watts";
else
return "ePump (unknown)";
break;
case CMD_EPUMP_RPM:
return "ePump set RPM";
break;
case CMD_EPUMP_WATTS:
return "ePump get Watts";
break;
case CMD_JXI_PING:
if (packet[4] == 0x19)
return "LXi heater on";
else // 0x11 is normal off
return "LXi heater ping";
break;
case CMD_JXI_STATUS:
if (packet[6] == 0x10)
return "LXi error";
else
return "LXi status";
break;
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]);
return buf;
break;
}
}
// Generate and return checksum of packet.
int generate_checksum(unsigned char* packet, int length)
{
int i, sum, n;
n = length - 3;
sum = 0;
for (i = 0; i < n; i++)
sum += (int) packet[i];
return(sum & 0x0ff);
}
bool check_jandy_checksum(unsigned char* packet, int length)
{
//printf("Checking 0x%02hhx against 0x%02hhx\n",generate_checksum(packet, length), packet[length-3]);
if (generate_checksum(packet, length) == packet[length-3])
return true;
// There seems to be a bug with jandy one touch protocol where on a long msg to one touch you get
// a bad checksum but everything is actually accurate, so forcing a good return on this.
// Example message (always one touch / status message / line 3 and always 0x0a checksum)
// 0x10|0x02|0x43|0x04|0x03|0x20|0x20|0x20|0x20|0x35|0x3a|0x30|0x35|0x20|0x50|0x4d|0x20|0x20|0x20|0x20|0x20|0x0a|0x10|0x03|
if (packet[3] == 0x04 && packet[4] == 0x03 && packet[length-3] == 0x0a) {
LOG(RSSD_LOG,LOG_INFO, "Ignoring bad checksum, seems to be bug in Jandy protocol\n");
if (getLogLevel(RSSD_LOG) >= LOG_DEBUG) {
static char buf[1000];
beautifyPacket(buf,packet,length,true);
LOG(RSSD_LOG,LOG_DEBUG, "Packetin question %s\n",buf);
}
return true;
}
return false;
}
bool check_pentair_checksum(unsigned char* packet, int length)
{
//printf("check_pentair_checksum \n");
int i, sum, n;
n = packet[8] + 9;
//n = packet[8] + 8;
sum = 0;
for (i = 3; i < n; i++) {
//printf("Sum 0x%02hhx\n",packet[i]);
sum += (int) packet[i];
}
//printf("Check High 0x%02hhx = 0x%02hhx = 0x%02hhx\n",packet[n], packet[length-2],((sum >> 8) & 0xFF) );
//printf("Check Low 0x%02hhx = 0x%02hhx = 0x%02hhx\n",packet[n + 1], packet[length-1], (sum & 0xFF) );
// Check against caculated length
if (sum == (packet[length-2] * 256 + packet[length-1]))
return true;
// Check against actual # length
if (sum == (packet[n] * 256 + packet[n+1])) {
LOG(RSSD_LOG,LOG_ERR, "Pentair checksum is accurate but length is not\n");
return true;
}
return false;
}
void generate_pentair_checksum(unsigned char* packet, int length)
{
int i, sum, n;
n = packet[8] + 9;
//n = packet[8] + 6;
sum = 0;
for (i = 3; i < n; i++) {
//printf("Sum 0x%02hhx\n",packet[i]);
sum += (int) packet[i];
}
packet[n+1] = (unsigned char) (sum & 0xFF); // Low Byte
packet[n] = (unsigned char) ((sum >> 8) & 0xFF); // High Byte
}
protocolType getProtocolType(unsigned char* packet) {
if (packet[0] == DLE)
return JANDY;
else if (packet[0] == PP1)
return PENTAIR;
return P_UNKNOWN;
}
/*
unsigned char getProtocolType(unsigned char* packet) {
if (packet[0] == DLE)
return PCOL_JANDY;
else if (packet[0] == PP1)
return PCOL_PENTAIR;
return PCOL_UNKNOWN;
}
*/
#ifndef PLAYBACK_MODE
/*
Open and Initialize the serial communications port to the Aqualink RS8 device.
Arg is tty or port designation string
returns the file descriptor
*/
//#define TXDEN_DUMMY_RS485_MODE
#ifdef TXDEN_DUMMY_RS485_MODE
#include
/* RS485 ioctls: */
#define TIOCGRS485 0x542E
#define TIOCSRS485 0x542F
int init_serial_port_Pi(const char* tty)
{
struct serial_rs485 rs485conf = {0};
//int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
int fd = open(tty, O_RDWR);
if (fd < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to open port: %s\n", tty);
return -1;
}
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Openeded serial port %s\n",tty);
if (ioctl (fd, TIOCGRS485, &rs485conf) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Error reading ioctl port (%d): %s\n", errno, strerror( errno ));
return -1;
}
LOG(RSSD_LOG,LOG_DEBUG, "Port currently RS485 mode is %s\n", (rs485conf.flags & SER_RS485_ENABLED) ? "set" : "NOT set");
/* Enable RS485 mode: */
rs485conf.flags |= SER_RS485_ENABLED;
/* Set logical level for RTS pin equal to 1 when sending: */
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
/* or, set logical level for RTS pin equal to 0 when sending: */
//rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);
/* Set logical level for RTS pin equal to 1 after sending: */
rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
/* or, set logical level for RTS pin equal to 0 after sending: */
//rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);
/* Set this flag if you want to receive data even whilst sending data */
//rs485conf.flags |= SER_RS485_RX_DURING_TX;
if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to set port to RS485 %s (%d): %s\n", tty, errno, strerror( errno ));
return -1;
}
return fd;
}
#endif // TXDEN_DUMMY_RS485_MODE
int _init_serial_port(const char* tty, bool blocking, bool readahead);
int init_serial_port(const char* tty)
{
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0) {
return init_blocking_serial_port(_aqconfig_.serial_port);
}
#else
return init_blocking_serial_port(_aqconfig_.serial_port);
#endif
}
int init_blocking_serial_port(const char* tty)
{
_blocking_fds = _init_serial_port(tty, true, false);
return _blocking_fds;
}
int set_port_low_latency(int fd, const char* tty)
{
struct serial_struct serial;
if (ioctl (fd, TIOCGSERIAL, &serial) < 0) {
LOG(RSSD_LOG,LOG_WARNING, "Doesn't look like your USB2RS485 device (%s) supports low latency, this might cause problems on a busy RS485 bus (%d): %s\n ", tty,errno, strerror( errno ));
//LOG(RSSD_LOG,LOG_WARNING, "Error reading low latency mode for port %s (%d): %s\n", tty, errno, strerror( errno ));
return -1;
}
LOG(RSSD_LOG,LOG_NOTICE, "Port %s low latency mode is %s\n", tty, (serial.flags & ASYNC_LOW_LATENCY) ? "set" : "NOT set, resetting to low latency!");
serial.flags |= ASYNC_LOW_LATENCY;
if (ioctl (fd, TIOCSSERIAL, &serial) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to set port %s to low latency mode (%d): %s\n", tty, errno, strerror( errno ));
return -1;
}
return 0;
}
#include
int lock_port(int fd, const char* tty)
{
if (ioctl (fd, TIOCEXCL) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Can't put (%s) into exclusive mode (%d): %s\n", tty,errno, strerror( errno ));
return -1;
}
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Can't lock (%s) (%d): %s\n", tty,errno, strerror( errno ));
return -1;
}
return 0;
}
int unlock_port(int fd)
{
if (flock(fd, LOCK_UN) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Can't unlock serial port (%d): %s\n",errno, strerror( errno ));
return -1;
}
return 0;
}
// https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2
// http://unixwiz.net/techtips/termios-vmin-vtime.html
//#define OLD_SERIAL_INIT
#ifndef OLD_SERIAL_INIT
// Unless AQ_RS_EXTRA_OPTS is defined, blocking will always be true
int _init_serial_port(const char* tty, bool blocking, bool readahead)
{
//B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400
const int BAUD = B9600;
const int PARITY = 0;
struct termios newtio;
_blocking_mode = blocking;
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
//int fd = open(tty, O_RDWR | O_NOCTTY | O_SYNC); // This is way to slow at reading
if (fd < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to open port: %s, error %d\n", tty, errno);
return -1;
}
LOG(RSSD_LOG,LOG_DEBUG, "Openeded serial port %s\n",tty);
if (tcgetattr(fd, &newtio) != 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to get port attributes: %s, error %d\n", tty,errno);
return -1;
}
if ( lock_port(fd, tty) < 0) {
//LOG(RSSD_LOG,LOG_ERR, "Unable to lock port: %s, error %d\n", tty, errno);
return -1;
}
if (_aqconfig_.ftdi_low_latency)
set_port_low_latency(fd, tty);
memcpy(&_oldtio, &newtio, sizeof(struct termios));
cfsetospeed(&newtio, BAUD);
cfsetispeed(&newtio, BAUD);
newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
//newtio.c_iflag &= ~IGNBRK; // disable break processing
newtio.c_iflag = 0; // raw input
newtio.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
newtio.c_oflag = 0; // no remapping, no delays, raw output
if (_blocking_mode) {
fcntl(fd, F_SETFL, 0); //efficient blocking for the read
//newtio.c_cc[VMIN] = 1; // read blocks for 1 character or timeout below
//newtio.c_cc[VTIME] = 0; // 0.5 seconds read timeout
//newtio.c_cc[VTIME] = 255; // 25 seconds read timeout
//newtio.c_cc[VTIME] = 10; // (1 to 255) 1 = 0.1 sec, 255 = 25.5 sec
newtio.c_cc[VTIME] = SERIAL_BLOCKING_TIME;
newtio.c_cc[VMIN] = 0;
} else {
newtio.c_cc[VMIN]= 0; // read doesn't block
//newtio.c_cc[VTIME]= 1;
newtio.c_cc[VTIME]= (readahead?0:1);
}
/*
Raw output is selected by resetting the OPOST option in the c_oflag member:
newtio.c_oflag &= ~OPOST;
When the OPOST option is disabled, all other option bits in c_oflag are ignored.
*/
//newtio.c_oflag &= ~OPOST; // Raw output
newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
newtio.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
// enable reading
newtio.c_cflag &= ~(PARENB | PARODD); // shut off parity
newtio.c_cflag |= PARITY;
newtio.c_cflag &= ~CSTOPB;
newtio.c_cflag &= ~CRTSCTS;
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &newtio) != 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to set port attributes: %s, error %d\n", tty,errno);
return -1;
}
LOG(RSSD_LOG,LOG_INFO, "Port %s set I/O %s attributes\n",tty,_blocking_mode?"blocking":"non blocking");
return fd;
}
#else //OLD_SERIAL_INIT
int _init_serial_port(const char* tty, bool blocking, bool readahead) // readahead ignored in this implimentation
{
long BAUD = B9600;
long DATABITS = CS8;
long STOPBITS = 0;
long PARITYON = 0;
long PARITY = 0;
struct termios newtio; //place for old and new port settings for serial port
_blocking_mode = blocking;
//int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
if (fd < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to open port: %s\n", tty);
return -1;
}
LOG(RSSD_LOG,LOG_DEBUG, "Openeded serial port %s\n",tty);
if (_blocking_mode) {
// http://unixwiz.net/techtips/termios-vmin-vtime.html
// Not designed behaviour, but it's what we need.
fcntl(fd, F_SETFL, 0);
newtio.c_cc[VMIN]= 1;
//newtio.c_cc[VTIME]= 0; // This will wait indefinatly. (not the best if panel / rs485 adapter is down)
newtio.c_cc[VTIME]= 50; // (1 to 255) 1 = 0.1 sec, 255 = 25.5 sec
LOG(RSSD_LOG,LOG_INFO, "Set serial port %s to blocking mode\n",tty);
} else {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_NDELAY);
newtio.c_cc[VMIN]= 0;
newtio.c_cc[VTIME]= 1; // This should be 0 if we have readahead before write
LOG(RSSD_LOG,LOG_INFO, "Set serial port %s to non blocking mode\n",tty);
}
tcgetattr(fd, &_oldtio); // save current port settings
// set new port settings for canonical input processing
newtio.c_cflag = BAUD | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR;
newtio.c_lflag = 0; // ICANON;
newtio.c_oflag = 0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &newtio);
LOG(RSSD_LOG,LOG_DEBUG, "Set serial port %s io attributes\n",tty);
return fd;
}
#endif
void close_blocking_serial_port()
{
if (_blocking_fds > 0) {
LOG(RSSD_LOG,LOG_INFO, "Forcing close of blocking serial port, ignore following read errors\n");
close_serial_port(_blocking_fds);
} else {
LOG(RSSD_LOG,LOG_ERR, "Didn't find valid blocking serial port file descripter\n");
}
}
/* close tty port */
void close_serial_port(int fd)
{
unlock_port(fd);
tcsetattr(fd, TCSANOW, &_oldtio);
close(fd);
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Closed serial port\n");
}
bool serial_blockingmode()
{
return _blocking_mode;
}
// Send an ack packet to the Aqualink RS8 master device.
// fd: the file descriptor of the serial port connected to the device
// command: the command byte to send to the master device, NUL if no command
//
// NUL = '\x00'
// DLE = '\x10'
// STX = '\x02'
// ETX = '\x03'
//
// masterAddr = '\x00' # address of Aqualink controller
//
//msg = DLE+STX+dest+cmd+args
//msg = msg+self.checksum(msg)+DLE+ETX
// DLE+STX+DEST+CMD+ARGS+CHECKSUM+DLE+ETX
/*
void print_hex(char *pk, int length)
{
int i=0;
for (i=0;i <-- type to from type-> <------------------------------ data ---------------------------------------->
*/
//unsigned char packet_buffer[] = {PCOL_PENTAIR, 0x07, 0x0F, 0x10, 0x08, 0x0D, 0x55, 0x55, 0x5B, 0x2A, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00};
//unsigned char packet_buffer[] = {PCOL_JANDY, 0x07, 0x0F, 0x00, 0x00};
void send_command(int fd, unsigned char *packet_buffer, int size)
{
//unsigned char packet[AQ_MAXPKTLEN];
//int i=0;
if (packet_buffer[0] == PCOL_JANDY) {
//LOG(RSSD_LOG,LOG_ERR, "Only Jandy protocol supported at present!\n");
send_jandy_command(fd, &packet_buffer[1], size-1);
return;
}
if (packet_buffer[0] == PCOL_PENTAIR) {
//LOG(RSSD_LOG,LOG_ERR, "Only Jandy protocol supported at present!\n");
send_pentair_command(fd, &packet_buffer[1], size-1);
return;
}
}
/* Clean out any NULL in serial line.
* Return 0x00 if nothing or only nuls read, or return byte if something else was read
* leave fullclean as option as may want to use this to cleanout end of message on get_packet function
*/
unsigned char cleanOutSerial(int fd, bool fullClean)
{
unsigned char byte = 0x00;
while ( (read(fd, &byte, 1) == 1) && (byte == 0x00 || fullClean) ) {
//printf("*** Peek Read 0x%02hhx ***\n",byte);
}
return byte;
}
/*
NEWPACKETADDRESSSPACE
is test to copy packet to unused address space before send, just incase tc_drain doesn't work correctly
and we change buffer after function call. (shouldn;t even happen / be needed buyt good for testing)
*/
#ifdef NEWPACKETADDRESSSPACE
void send_packet(int fd, unsigned char *packet_buffer, int length)
{
static unsigned char packet[AQ_MAXPKTLEN];
memcpy(packet, packet_buffer, length);
#else
void send_packet(int fd, unsigned char *packet, int length)
{
#endif
struct timespec elapsed_time;
struct timespec now;
if (_aqconfig_.frame_delay > 0) {
struct timespec min_frame_wait_time;
struct timespec frame_wait_time;
struct timespec remainder_time;
min_frame_wait_time.tv_sec = 0;
min_frame_wait_time.tv_nsec = _aqconfig_.frame_delay * 1000000;
do {
clock_gettime(CLOCK_REALTIME, &now);
timespec_subtract(&elapsed_time, &now, &last_serial_read_time);
if (timespec_subtract(&frame_wait_time, &min_frame_wait_time, &elapsed_time)) {
break;
}
} while (nanosleep(&frame_wait_time, &remainder_time) != 0);
}
clock_gettime(CLOCK_REALTIME, &now);
if (_blocking_mode) {
//int nwrite = write(fd, packet, length);
//LOG(RSSD_LOG,LOG_DEBUG, "Serial write %d bytes of %d\n",nwrite,length);
int nwrite = write(fd, packet, length);
//if (nwrite < 0)
if (nwrite != length)
LOG(RSSD_LOG, LOG_ERR, "write to serial port failed\n");
} else {
int nwrite, i;
for (i = 0; i < length; i += nwrite) {
nwrite = write(fd, packet + i, length - i);
if (nwrite < 0)
LOG(RSSD_LOG, LOG_ERR, "write to serial port failed\n");
//else
// LOG(RSSD_LOG,LOG_DEBUG, "Serial write %d bytes of %d\n",nwrite,length-1);
}
} // _blockine_mode
/*
#ifdef AQ_DEBUG
// Need to take this out for release
if ( getLogLevel(RSSD_LOG) >= LOG_DEBUG) {
debuglogPacket(&packet[1], length-2);
}
#endif
*/
// MAYBE Change this back to debug serial
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial write %d bytes\n",length-2);
//LOG(RSSD_LOG,LOG_DEBUG, "Serial write %d bytes, type 0x%02hhx cmd 0x%02hhx\n",length-2,packet[5],packet[6]);
logPacketWrite(&packet[1], length-2);
/*
if (getLogLevel(PDA_LOG) == LOG_DEBUG) {
char buff[1024];
beautifyPacket(buff, &packet[1], length, false);
LOG(PDA_LOG,LOG_DEBUG, "%s", buff);
}
*/
/*
if ( getLogLevel(RSSD_LOG) >= LOG_DEBUG_SERIAL || ) {
// Packet is padded with 0x00, so discard for logging
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial write %d bytes\n",length-2);
logPacket(&packet[1], length-2);
}*/ /*else if (getLogLevel(RSSD_LOG) >= LOG_DEBUG) {
static char buf[1000];
beautifyPacket(buf,&packet[1],length-2);
LOG(RSSD_LOG,LOG_DEBUG, "Serial write %s\n",buf);
}*/
tcdrain(fd); // Make sure buffer has been sent.
//if (_aqconfig_.frame_delay > 0) {
timespec_subtract(&elapsed_time, &now, &last_serial_read_time);
LOG(RSSD_LOG, LOG_DEBUG, "Time from recv to %s send is %ld.%09ld sec\n",
(_blocking_mode?"blocking":"non-blocking"), elapsed_time.tv_sec,
elapsed_time.tv_nsec);
//}
}
void _send_ack(int fd, unsigned char ack_type, unsigned char command)
{
const int length = 11;
// Default null ack with checksum generated, don't mess with it, just over right
unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
// Update the packet and checksum if command argument is not NUL.
if(command != NUL || ack_type != NUL) {
//ackPacket[5] = 0x00 normal, 0x03 some pause, 0x01 some pause ending (0x01 = Screen Busy (also return from logn message))
ackPacket[5] = ack_type;
ackPacket[6] = command;
ackPacket[7] = generate_checksum(ackPacket, length-1);
if (command == DLE) { // We shuld probably also check the ack type as well, just for future proofing.
// 0x10(DLE) that's not part of the headder or footer needs to be escaped AFTER with NUL, so shif everyting uo one
ackPacket[8] = ackPacket[7]; // move the caculated checksum
ackPacket[7] = NUL; // escape the DLE
ackPacket[9] = DLE; // add new end sequence
ackPacket[10] = ETX; // add new end sequence
}
}
//printf("***Send ACK (%s) ***\n",(ack_type==ACK_NORMAL?"Normal":(ack_type==ACK_SCREEN_BUSY?"ScreenBusy":"ScreenBusyDisplay")) );
send_packet(fd, ackPacket, length);
}
void send_ack(int fd, unsigned char command)
{
_send_ack(fd, ACK_NORMAL, command);
}
// ack_typ should only be ACK_PDA, ACK_NORMAL, ACK_SCREEN_BUSY, ACK_SCREEN_BUSY_DISPLAY
void send_extended_ack(int fd, unsigned char ack_type, unsigned char command)
{
_send_ack(fd, ack_type, command);
}
/*
int _get_packet(int fd, unsigned char* packet, bool rawlog);
int get_packet(int fd, unsigned char* packet)
{
return _get_packet(fd, packet, false);
}
int get_packet_lograw(int fd, unsigned char* packet)
{
return _get_packet(fd, packet, true);
}
int _get_packet(int fd, unsigned char* packet, bool rawlog)
*/
int get_packet(int fd, unsigned char* packet)
{
unsigned char byte = 0x00;
int bytesRead;
int index = 0;
bool endOfPacket = false;
//bool packetStarted = FALSE;
bool lastByteDLE = false;
int retry = 0;
bool jandyPacketStarted = false;
bool pentairPacketStarted = false;
//bool lastByteDLE = false;
int PentairPreCnt = 0;
int PentairDataCnt = -1;
memset(packet, 0, AQ_MAXPKTLEN);
// Read packet in byte order below
// DLE STX ........ ETX DLE
// sometimes we get ETX DLE and no start, so for now just ignoring that. Seem to be more applicable when busy RS485 traffic
//#ifndef OLD_SERIAL_INIT .. Need to re-do ERROR like EAGAIN with new init
while (!endOfPacket) {
//printf("READ SERIAL\n");
bytesRead = read(fd, &byte, 1);
//printf("Read %d 0x%02hhx err=%d fd=%d\n",bytesRead,byte,errno,fd);
//if (bytesRead < 0 && errno == EAGAIN && packetStarted == FALSE && lastByteDLE == FALSE) {
//if (bytesRead < 0 && (errno == EAGAIN || errno == 0) &&
if (bytesRead <= 0 && (errno == EAGAIN || errno == 0 || errno == ENOTTY) ) { // We also get ENOTTY on some non FTDI adapters
if (_blocking_mode) {
// Something is wrong wrong
return AQSERR_TIMEOUT;
} else if (jandyPacketStarted == false && pentairPacketStarted == false && lastByteDLE == false) {
// We just have nothing else to read
return 0;
} else if (++retry > 120 ) {
LOG(RSSD_LOG,LOG_WARNING, "Serial read timeout\n");
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
if (index > 0) { logPacketError(packet, index); }
return AQSERR_TIMEOUT;
}
delay(1);
} else if (bytesRead == 1) {
retry = 0;
if (_aqconfig_.log_raw_bytes)
logPacketByte(&byte);
if (lastByteDLE == true && byte == NUL)
{
// Check for DLE | NULL (that's escape DLE so delete the NULL)
//printf("IGNORE THIS PACKET\n");
lastByteDLE = false;
}
else if (lastByteDLE == true)
{
if (index == 0)
index++;
packet[index] = byte;
index++;
if (byte == STX && jandyPacketStarted == false)
{
jandyPacketStarted = true;
pentairPacketStarted = false;
}
else if (byte == ETX && jandyPacketStarted == true)
{
endOfPacket = true;
}
}
else if (jandyPacketStarted || pentairPacketStarted)
{
packet[index] = byte;
index++;
if (pentairPacketStarted == true && index == 9)
{
//printf("Read 0x%02hhx %d pentair\n", byte, byte);
PentairDataCnt = byte;
}
if (PentairDataCnt >= 0 && index - 11 >= PentairDataCnt && pentairPacketStarted == true)
{
endOfPacket = true;
PentairPreCnt = -1;
}
}
else if (byte == DLE && jandyPacketStarted == false)
{
packet[index] = byte;
}
// // reset index incase we have EOP before start
if (jandyPacketStarted == false && pentairPacketStarted == false)
{
index = 0;
}
if (byte == DLE && pentairPacketStarted == false)
{
lastByteDLE = true;
PentairPreCnt = -1;
}
else
{
lastByteDLE = false;
if (byte == PP1 && PentairPreCnt == 0)
PentairPreCnt = 1;
else if (byte == PP2 && PentairPreCnt == 1)
PentairPreCnt = 2;
else if (byte == PP3 && PentairPreCnt == 2)
PentairPreCnt = 3;
else if (byte == PP4 && PentairPreCnt == 3)
{
pentairPacketStarted = true;
jandyPacketStarted = false;
PentairDataCnt = -1;
packet[0] = PP1;
packet[1] = PP2;
packet[2] = PP3;
packet[3] = byte;
index = 4;
}
else if (byte != PP1) // Don't reset counter if multiple PP1's
PentairPreCnt = 0;
}
} else if(bytesRead < 0) {
// Got a read error. Wait one millisecond for the next byte to
// arrive.
LOG(RSSD_LOG,LOG_WARNING, "Read error: %d - %s\n", errno, strerror(errno));
if(errno == 9) {
// Bad file descriptor. Port has been disconnected for some reason.
// Return a -1.
return AQSERR_READ;
}
delay(100);
}
// Break out of the loop if we exceed maximum packet
// length.
if (index >= AQ_MAXPKTLEN) {
LOG(RSSD_LOG,LOG_WARNING, "Serial packet too large\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_2LARGE;
break;
}
}
// Clean out rest of buffer, make sure their is nothing else
/* Doesn't work for shit due to probe message speed, need to come back and re-think
*/
//LOG(RSSD_LOG,LOG_DEBUG, "Serial checksum, length %d got 0x%02hhx expected 0x%02hhx\n", index, packet[index-3], generate_checksum(packet, index));
if (jandyPacketStarted) {
if (check_jandy_checksum(packet, index) != true){
LOG(RSSD_LOG,LOG_WARNING, "Serial read bad Jandy checksum, ignoring\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_CHKSUM;
}
} else if (pentairPacketStarted) {
if (check_pentair_checksum(packet, index) != true){
LOG(RSSD_LOG,LOG_WARNING, "Serial read bad Pentair checksum, ignoring\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_CHKSUM;
}
}
/*
if (generate_checksum(packet, index) != packet[index-3]){
LOG(RSSD_LOG,LOG_WARNING, "Serial read bad checksum, ignoring\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
} else*/ if (index < AQ_MINPKTLEN && (jandyPacketStarted || pentairPacketStarted) ) { //NSF. Sometimes we get END sequence only, so just ignore.
LOG(RSSD_LOG,LOG_WARNING, "Serial read too small\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_2SMALL;
}
//if (_aqconfig_.frame_delay > 0) {
clock_gettime(CLOCK_REALTIME, &last_serial_read_time);
//}
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial read %d bytes\n",index);
if (_aqconfig_.log_protocol_packets || getLogLevel(RSSD_LOG) >= LOG_DEBUG_SERIAL)
logPacketRead(packet, index);
// Return the packet length.
return index;
}
#else // PLAYBACKMODE
// Need to re-write this if we ever use playback mode again. Pull info from aq_serial.old.c
#endif