/* * 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