pull/69/head
sfeakes 2023-06-04 16:17:48 -05:00
parent 9f91af03ce
commit 0780f94a2b
23 changed files with 693 additions and 275 deletions

View File

@ -15,6 +15,9 @@ AQ_ONETOUCH = true
AQ_IAQTOUCH = true
#AQ_MEMCMP = true // Not implimented correctly yet.
# Turn off threadded net services
AQ_NO_THREAD_NETSERVICE = false
# Get some system information
PI_OS_VERSION = $(shell cat /etc/os-release | grep VERSION= | cut -d\" -f2)
$(info OS: $(PI_OS_VERSION) )
@ -81,6 +84,11 @@ ifeq ($(AQ_MEMCMP), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_MEMCMP
endif
ifeq ($(AQ_NO_THREAD_NETSERVICE), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_NO_THREAD_NETSERVICE
endif
# Put all flags together.
CFLAGS = $(GCCFLAGS) $(AQ_FLAGS) $(MGFLAGS)
DFLAGS = $(DGCCFLAGS) $(AQ_FLAGS) $(MGFLAGS)

View File

@ -78,22 +78,25 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
* Allow selecting of pre-defined VSP programs (Aqualink Touch & OneTouch protocols.)
* Add set time to OneTouch protocol.
# Update in Release 2.3.0e (pre release)
# Update in Release 2.3.0f (pre release)
* This is pre-release, please treat it as such.
* Proceed with caution on PDA panels <i>I have not been able to test it fully on all variants</i> and you may need to go back to your current (or previous) version of AqualinkD
* Changed a lot of logic around different protocols.
* Added low latency support for ITDI usb driver.
* AqualinkD will find out the fastest way to change something depending on the protocols available.
* Added scheduler (click time in web ui). supports full calendar year (ie seasons), See wiki for details.
* Added timers for devices (ie can turn on Pump for x minutes), Long press on device in WebUI.
* Timers supported in MQTT/API.
* Support for external chem feeders can post to AqualinkD (pH & ORP shown in webUI / homekit etc)
* Add support for dimmers.
* Extended SWG status now in web UI.
* Serial logging / error checking enhancements.
* Added simulator back. (+ Improved UI).
* Fix issue with incorrect device state after duplicate MQTT messages being sent in rapid succession ( < 0.5 second).
* Found workaround for panel firmware bug in iAqualink Touch protocol where VSP updates sometimes got lost.
* Found a workaround for panel firmware bug in iAqualink Touch protocol where VSP updates sometimes got lost.
* Fix bug in IntelliBrite color lights
* Install script checks for cron and it's config (needed for scheduler)
* serial-logger will now give recommended values for aqualinkd.conf
# Update in Release 2.2.2
* Fixed some Web UI bugs

View File

@ -23,12 +23,27 @@
#include <string.h>
#include <sys/ioctl.h>
#include <stdbool.h>
// Below is needed to set low latency.
#include <linux/serial.h>
#include "aq_serial.h"
#include "utils.h"
#include "config.h"
#include "packetLogger.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
@ -303,7 +318,20 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead);
int init_serial_port(const char* tty)
{
return _init_serial_port(tty, false, false);
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0)
return init_blocking_serial_port(_aqconfig_.serial_port);
else if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
return init_serial_port(_aqconfig_.serial_port);
#else
if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
return init_blocking_serial_port(_aqconfig_.serial_port);
#endif
}
int init_readahead_serial_port(const char* tty)
{
@ -315,6 +343,31 @@ int init_blocking_serial_port(const char* tty)
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;
}
// https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2
// http://unixwiz.net/techtips/termios-vmin-vtime.html
//#define OLD_SERIAL_INIT
@ -343,6 +396,9 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead)
return -1;
}
if (_aqconfig_.ftdi_low_latency)
set_port_low_latency(fd, tty);
memcpy(&_oldtio, &newtio, sizeof(struct termios));
cfsetospeed(&newtio, BAUD);
@ -360,8 +416,10 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead)
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] = 5; // 0.5 seconds read timeout
newtio.c_cc[VTIME] = 255; // 25 seconds read timeout
//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
@ -390,7 +448,7 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead)
return -1;
}
LOG(RSSD_LOG,LOG_INFO, "Set serial port %s io %s attributes\n",tty,_blocking_mode?"blocking":"non blocking");
LOG(RSSD_LOG,LOG_INFO, "Set serial port %s I/O %s attributes\n",tty,_blocking_mode?"blocking":"non blocking");
return fd;
}
@ -467,6 +525,11 @@ void close_serial_port(int 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
@ -749,11 +812,12 @@ int get_packet(int fd, unsigned char* packet)
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) ) {
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;

View File

@ -8,6 +8,8 @@
#define CONNECTION_ERROR "ERROR No connection to RS control panel"
#define SERIAL_BLOCKING_TIME 50 // (1 to 255) in 1/10th second so 1 = 0.1 sec, 255 = 25.5 sec
// Protocol types
#define PCOL_JANDY 0xFF
#define PCOL_PENTAIR 0xFE
@ -445,6 +447,8 @@ int init_readahead_serial_port(const char* tty);
void close_serial_port(int file_descriptor);
void close_blocking_serial_port();
bool serial_blockingmode();
//#ifdef AQ_PDA
//void set_pda_mode(bool mode);
//bool pda_mode();

View File

@ -9,8 +9,14 @@
#include "aq_programmer.h"
//#include "aq_panel.h" // Moved to later in file to overcome circular dependancy. (crappy I know)
#define DEFAULT_POLL_SPEED -1
#define DEFAULT_POLL_SPEED_NON_THREADDED 2
#define SIGRESTART SIGUSR1
#ifdef AQ_NO_THREAD_NETSERVICE
#define DEFAULT_POLL_SPEED -1
#define DEFAULT_POLL_SPEED_NON_THREADDED 2
#endif
#define TIME_CHECK_INTERVAL 3600
//#define TIME_CHECK_INTERVAL 100 // DEBUG ONLY
@ -20,7 +26,10 @@
//#define TIME_CHECK_INTERVAL 100
//#define ACCEPTABLE_TIME_DIFF 10
#define MAX_ZERO_READ_BEFORE_RECONNECT 20000 // 2k normally
#define MAX_ZERO_READ_BEFORE_RECONNECT_NONBLOCKING 100000 // 10k normally
#define MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING (25 / (SERIAL_BLOCKING_TIME / 10) ) // Want this to be 25 seconds, so it's depdand on how long the serial blocking is
// Time in ms to delay between read requests in non blocking serial port. Have to set something to stop CPU spiking.
#define NONBLOCKING_SERIAL_DELAY 2
// The below will change state of devices before that are actually set on the control panel, this helps
// with duplicate messages that come in quick succession that can catch the state before it happens.
@ -217,6 +226,7 @@ struct aqualinkdata
unsigned char last_packet_type;
int swg_delayed_percent;
bool simulate_panel;
bool aqManagerActive;
int open_websockets;
struct programmingthread active_thread;
struct action unactioned;

View File

@ -60,9 +60,16 @@
//#define DEFAULT_CONFIG_FILE "./aqualinkd.conf"
static volatile bool _keepRunning = true;
static volatile bool _restart = false;
//char** _argv;
//static struct aqconfig _aqconfig_;
static struct aqualinkdata _aqualink_data;
char *_self;
char *_cfgFile;
int _cmdln_loglevel = -1;
bool _cmdln_debugRS485 = false;
bool _cmdln_lograwRS485 = false;
#ifdef AQ_TM_DEBUG
//struct timespec _rs_packet_readitme;
@ -71,20 +78,34 @@ static struct aqualinkdata _aqualink_data;
void main_loop();
int startup(char *self, char *cfgFile);
void intHandler(int dummy)
void intHandler(int sig_num)
{
LOG(AQUA_LOG,LOG_WARNING, "Stopping!\n");
_keepRunning = false;
LOG(AQUA_LOG,LOG_NOTICE, "Stopping!\n");
if (sig_num == SIGRESTART) {
_restart = true;
}
//LOG(AQUA_LOG,LOG_NOTICE, "Stopping!\n");
//if (dummy){}// stop compile warnings
// In blocking mode, die as cleanly as possible.
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0) {
stopPacketLogger();
// This should force port to close and do somewhat gracefull exit.
close_blocking_serial_port();
//exit(-1);
}
#else
stopPacketLogger();
// This should force port to close and do somewhat gracefull exit.
if (serial_blockingmode())
close_blocking_serial_port();
#endif
}
void processLEDstate()
@ -1008,17 +1029,12 @@ void printHelp()
printf("\t-rsrd (RS485 raw debug)\n");
}
int main(int argc, char *argv[])
{
int i, j;
//char *cfgFile = DEFAULT_CONFIG_FILE;
_restart = false;
char defaultCfg[] = "./aqualinkd.conf";
char *cfgFile;
int cmdln_loglevel = -1;
bool cmdln_debugRS485 = false;
bool cmdln_lograwRS485 = false;
//printf ("TIMER = %d\n",TIMR_LOG);
@ -1065,7 +1081,7 @@ int main(int argc, char *argv[])
init_config();
cfgFile = defaultCfg;
for (i = 1; i < argc; i++)
for (int i = 1; i < argc; i++)
{
if (strcmp(argv[i], "-h") == 0)
{
@ -1082,22 +1098,34 @@ int main(int argc, char *argv[])
}
else if (strcmp(argv[i], "-vv") == 0)
{
cmdln_loglevel = LOG_DEBUG_SERIAL;
_cmdln_loglevel = LOG_DEBUG_SERIAL;
}
else if (strcmp(argv[i], "-v") == 0)
{
cmdln_loglevel = LOG_DEBUG;
_cmdln_loglevel = LOG_DEBUG;
}
else if (strcmp(argv[i], "-rsd") == 0)
{
cmdln_debugRS485 = true;
_cmdln_debugRS485 = true;
}
else if (strcmp(argv[i], "-rsrd") == 0)
{
cmdln_lograwRS485 = true;
_cmdln_lograwRS485 = true;
}
}
// Set this here, so it doesn;t get reset if the manager restarts the AqualinkD process.
_aqualink_data.aqManagerActive = false;
return startup(argv[0], cfgFile);
}
int startup(char *self, char *cfgFile)
{
int i, j;
_self = self;
_cfgFile = cfgFile;
//initButtons(&_aqualink_data);
read_config(&_aqualink_data, cfgFile);
@ -1152,16 +1180,19 @@ int main(int argc, char *argv[])
#endif
_aqualink_data.total_buttons = 12;
*/
if (cmdln_loglevel != -1)
_aqconfig_.log_level = cmdln_loglevel;
if (cmdln_debugRS485)
if (_cmdln_loglevel != -1)
_aqconfig_.log_level = _cmdln_loglevel;
if (_cmdln_debugRS485)
_aqconfig_.log_protocol_packets = true;
if (cmdln_lograwRS485)
if (_cmdln_lograwRS485)
_aqconfig_.log_raw_bytes = true;
if (_aqconfig_.display_warnings_web == true)
setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, _aqconfig_.log_file, _aqualink_data.last_display_message);
else
@ -1224,6 +1255,7 @@ int main(int argc, char *argv[])
}
#endif
LOG(AQUA_LOG,LOG_NOTICE, "Config force SWG = %s\n", bool2text(_aqconfig_.force_swg));
LOG(AQUA_LOG,LOG_NOTICE, "Config force PS setpoint = %s\n", bool2text(_aqconfig_.force_ps_setpoints));
/* removed until domoticz has a better virtual thermostat
LOG(AQUA_LOG,LOG_NOTICE, "Config idx pool thermostat = %d\n", _aqconfig_.dzidx_pool_thermostat);
LOG(AQUA_LOG,LOG_NOTICE, "Config idx spa thermostat = %d\n", _aqconfig_.dzidx_spa_thermostat);
@ -1236,13 +1268,7 @@ int main(int argc, char *argv[])
LOG(AQUA_LOG,LOG_NOTICE, "Debug RS485 protocol = %s\n", bool2text(_aqconfig_.log_protocol_packets));
LOG(AQUA_LOG,LOG_NOTICE, "Debug RS485 protocol raw = %s\n", bool2text(_aqconfig_.log_raw_bytes));
if ( _aqconfig_.RSSD_LOG_filter != NUL)
LOG(AQUA_LOG,LOG_NOTICE, "Log RS485 packets from = 0x%02hhx\n", _aqconfig_.RSSD_LOG_filter
);
LOG(AQUA_LOG,LOG_NOTICE, "Log RS485 packets from = 0x%02hhx\n", _aqconfig_.RSSD_LOG_filter);
//LOG(AQUA_LOG,LOG_NOTICE, "Use PDA 4 auxiliary info = %s\n", bool2text(_aqconfig_.use_PDA_auxiliary));
//LOG(AQUA_LOG,LOG_NOTICE, "Read Pentair Packets = %s\n", bool2text(_aqconfig_.read_pentair_packets));
// logMessage (LOG_NOTICE, "Config serial_port = %s\n", config_parameters->serial_port);
@ -1259,6 +1285,13 @@ int main(int argc, char *argv[])
if (_aqconfig_.readahead_b4_write == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial Read Ahead Write = %s\n", bool2text(_aqconfig_.readahead_b4_write));
if (_aqconfig_.ftdi_low_latency == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial FTDI low latency = %s\n", bool2text(_aqconfig_.ftdi_low_latency));
if (_aqconfig_.prioritize_ack == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial Prioritize Ack = %s\n", bool2text(_aqconfig_.prioritize_ack));
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.thread_netservices)
LOG(AQUA_LOG,LOG_NOTICE, "Thread Network Services = %s\n", bool2text(_aqconfig_.thread_netservices));
@ -1273,6 +1306,7 @@ int main(int argc, char *argv[])
LOG(AQUA_LOG,LOG_WARNING, "Serial Read Ahead Write is not valid when using Negative RS Poll Speed, turning Serial Read Ahead Write off\n");
_aqconfig_.readahead_b4_write = false;
}
#endif
//for (i = 0; i < TOTAL_BUTONS; i++)
for (i = 0; i < _aqualink_data.total_buttons; i++)
@ -1311,7 +1345,8 @@ int main(int argc, char *argv[])
{
char pidfile[256];
// sprintf(pidfile, "%s/%s.pid",PIDLOCATION, basename(argv[0]));
sprintf(pidfile, "%s/%s.pid", "/run", basename(argv[0]));
//sprintf(pidfile, "%s/%s.pid", "/run", basename(argv[0]));
sprintf(pidfile, "%s/%s.pid", "/run", basename(self));
daemonise(pidfile, main_loop);
}
else
@ -1322,10 +1357,10 @@ int main(int argc, char *argv[])
exit(EXIT_SUCCESS);
}
/*
#define MAX_BLOCK_ACK 12
#define MAX_BUSY_ACK (50 + MAX_BLOCK_ACK)
*/
/* Point of this is to sent ack as quickly as possible, all checks should be done prior to calling this.*/
void caculate_ack_packet(int rs_fd, unsigned char *packet_buffer, emulation_type source)
@ -1391,73 +1426,7 @@ void caculate_ack_packet(int rs_fd, unsigned char *packet_buffer, emulation_type
*/
}
/*
void caculate_ack_packet_old(int rs_fd, unsigned char *packet_buffer) {
static int delayAckCnt = 0;
if (packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id) {
if (onetouch_enabled())
send_extended_ack(rs_fd, ACK_ONETOUCH, pop_ot_cmd(packet_buffer[PKT_CMD]));
else if (iaqtouch_enabled()) {
if (packet_buffer[PKT_CMD] != CMD_IAQ_CTRL_READY)
send_extended_ack(rs_fd, ACK_IAQ_TOUCH, pop_iaqt_cmd(packet_buffer[PKT_CMD]));
else {
unsigned char *cmd;
int size = ref_iaqt_control_cmd(&cmd);
send_jandy_command(rs_fd, cmd, size);
rem_iaqt_control_cmd(cmd);
}
}
return;
}
// if PDA mode, should we sleep? if not Can only send command to status message on PDA.
#ifdef AQ_PDA
if (_aqconfig_.pda_mode == true) {
//pda_programming_thread_check(&_aqualink_data);
if (_aqconfig_.pda_sleep_mode && pda_shouldSleep()) {
LOG(AQUA_LOG,LOG_DEBUG, "PDA Aqualink daemon in sleep mode\n");
return;
} else {
send_extended_ack(rs_fd, ACK_PDA, pop_aq_cmd(&_aqualink_data));
}
} else
#endif
if (_aqualink_data.simulate_panel && in_programming_mode(&_aqualink_data) == false) {
// We are in simlator mode, ack get's complicated now.
// If have a command to send, send a normal ack.
// If we last message is waiting for an input "SELECT xxxxx", then sent a pause ack
// pause ack starts with around 12 ACK_SCREEN_BUSY_DISPLAY acks, then 50 ACK_SCREEN_BUSY acks
// if we send a command (ie keypress), the whole count needs to end and go back to sending normal ack.
// In code below, it jumps to sending ACK_SCREEN_BUSY, which still seems to work ok.
if (_aqualink_data.last_packet_type == CMD_MSG_LONG) {
send_extended_ack(rs_fd, ACK_SCREEN_BUSY, pop_aq_cmd(&_aqualink_data));
} if (strncasecmp(_aqualink_data.last_display_message, "SELECT", 6) != 0) { // Nothing to wait for, send normal ack.
send_ack(rs_fd, pop_aq_cmd(&_aqualink_data));
delayAckCnt = 0;
} else if (get_aq_cmd_length() > 0) {
// Send command and jump directly "busy but can receive message"
send_ack(rs_fd, pop_aq_cmd(&_aqualink_data));
delayAckCnt = MAX_BUSY_ACK; // need to test jumping to MAX_BUSY_ACK here
} else {
LOG(AQUA_LOG,LOG_NOTICE, "Sending display busy due to Simulator mode \n");
if (delayAckCnt < MAX_BLOCK_ACK) // block all incomming messages
send_extended_ack(rs_fd, ACK_SCREEN_BUSY_BLOCK, pop_aq_cmd(&_aqualink_data));
else if (delayAckCnt < MAX_BUSY_ACK) // say we are pausing
send_extended_ack(rs_fd, ACK_SCREEN_BUSY, pop_aq_cmd(&_aqualink_data));
else // We timed out pause, send normal ack (This should also reset the display message on next message received)
send_ack(rs_fd, pop_aq_cmd(&_aqualink_data));
delayAckCnt++;
}
} else {
// We are in simulate panel mode, but a thread is active, so ignore simulate panel
send_ack(rs_fd, pop_aq_cmd(&_aqualink_data));
}
}
*/
unsigned char find_unused_address(unsigned char* packet) {
static int ID[4] = {0,0,0,0}; // 0=0x08, 1=0x09, 2=0x0A, 3=0x0B
static unsigned char lastID = 0x00;
@ -1484,24 +1453,17 @@ unsigned char find_unused_address(unsigned char* packet) {
void main_loop()
{
struct mg_mgr mgr;
int rs_fd;
int packet_length;
unsigned char packet_buffer[AQ_MAXPKTLEN+1];
//bool interestedInNextAck = false;
//rsDeviceType interestedInNextAck = DRS_NONE;
//bool changed = false;
//int swg_zero_cnt = 0;
int i;
//int delayAckCnt = 0;
bool got_probe = false;
bool got_probe_extended = false;
bool got_probe_rssa = false;
bool print_once = false;
//unsigned char previous_packet_to = NUL; // bad name, it's not previous, it's previous that we were interested in.
int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING; // Will get reset if non blocking
// NSF need to find a better place to init this.
//_aqualink_data.aq_command = 0x00;
sprintf(_aqualink_data.last_display_message, "%s", "Connecting to Control Panel");
_aqualink_data.simulate_panel = false;
_aqualink_data.active_thread.thread_id = 0;
@ -1518,7 +1480,6 @@ void main_loop()
_aqualink_data.swg_led_state = LED_S_UNKNOWN;
_aqualink_data.swg_delayed_percent = TEMP_UNKNOWN;
_aqualink_data.temp_units = UNKNOWN;
//_aqualink_data.single_device = false;
_aqualink_data.service_mode_state = OFF;
_aqualink_data.frz_protect_state = OFF;
_aqualink_data.battery = OK;
@ -1542,7 +1503,12 @@ void main_loop()
_aqualink_data.swg_ppm = 0;
}
if (!start_net_services(&mgr, &_aqualink_data))
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
signal(SIGQUIT, intHandler);
signal(SIGRESTART, intHandler);
if (!start_net_services(&_aqualink_data))
{
LOG(AQUA_LOG,LOG_ERR, "Can not start webserver on port %s.\n", _aqconfig_.socket_port);
exit(EXIT_FAILURE);
@ -1550,17 +1516,9 @@ void main_loop()
startPacketLogger();
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
signal(SIGQUIT, intHandler);
int blank_read = 0;
if (_aqconfig_.rs_poll_speed < 0)
rs_fd = init_blocking_serial_port(_aqconfig_.serial_port);
else if (_aqconfig_.readahead_b4_write)
rs_fd = init_readahead_serial_port(_aqconfig_.serial_port);
else
rs_fd = init_serial_port(_aqconfig_.serial_port);
rs_fd = init_serial_port(_aqconfig_.serial_port);
if (rs_fd == -1) {
LOG(AQUA_LOG,LOG_ERR, "Error Aqualink setting serial port: %s\n", _aqconfig_.serial_port);
@ -1568,6 +1526,9 @@ void main_loop()
}
LOG(AQUA_LOG,LOG_NOTICE, "Listening to Aqualink RS8 on serial port: %s\n", _aqconfig_.serial_port);
if (!serial_blockingmode())
blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_NONBLOCKING;
#ifdef AQ_PDA
if (isPDA_PANEL) {
init_pda(&_aqualink_data);
@ -1638,12 +1599,13 @@ void main_loop()
// Loop until we get the probe messages, that means we didn;t start too soon after last shutdown.
while ( (got_probe == false || got_probe_rssa == false || got_probe_extended == false ) && _keepRunning == true)
{
if (blank_read == MAX_ZERO_READ_BEFORE_RECONNECT) {
if (blank_read == blank_read_reconnect) {
LOG(AQUA_LOG,LOG_ERR, "Nothing read on '%s', are you sure that's right?\n",_aqconfig_.serial_port);
} else if (blank_read == MAX_ZERO_READ_BEFORE_RECONNECT*2) {
LOG(AQUA_LOG,LOG_ERR, "Nothing read on '%s', are you sure that's right?\n",_aqconfig_.serial_port);
} else if (blank_read == MAX_ZERO_READ_BEFORE_RECONNECT*3) {
} else if (blank_read == blank_read_reconnect*2) {
LOG(AQUA_LOG,LOG_ERR, "I'm done, exiting, please check '%s'\n",_aqconfig_.serial_port);
stopPacketLogger();
close_serial_port(rs_fd);
stop_net_services();
return;
}
/*
@ -1702,11 +1664,15 @@ void main_loop()
#endif
else if (packet_length <= 0) {
blank_read++;
//printf("Blank Reads %d\n",blank_read);
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0)
LOG(AQUA_LOG,LOG_DEBUG, "Blank RS485 read\n");
else
delay(2);
#else
if (serial_blockingmode())
LOG(AQUA_LOG,LOG_DEBUG, "Blank RS485 read\n");
#endif
}
else if (packet_length > 0) {
blank_read = 0;
@ -1724,8 +1690,7 @@ void main_loop()
#endif
stopPacketLogger();
close_serial_port(rs_fd);
//mg_mgr_free(&mgr);
stop_net_services(&mgr);
stop_net_services();
return;
}
}
@ -1747,10 +1712,12 @@ void main_loop()
LOG(AQUA_LOG,LOG_NOTICE, "Starting communication with Control Panel\n");
int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT;
// Not the best way to do this, but ok for moment
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed == 0)
blank_read_reconnect = blank_read_reconnect * 50;
#endif
blank_read = 0;
// OK, Now go into infinate loop
@ -1765,55 +1732,45 @@ void main_loop()
// sleep(1);
sprintf(_aqualink_data.last_display_message, CONNECTION_ERROR);
LOG(AQUA_LOG,LOG_ERR, "Aqualink daemon waiting to connect to master device...\n");
broadcast_aqualinkstate_error(mgr.active_connections, CONNECTION_ERROR);
//mg_mgr_poll(&mgr, 1000); // Sevice messages
//mg_mgr_poll(&mgr, 3000); // should do nothing for 3 seconds.
poll_net_services(&mgr, 1000);
poll_net_services(&mgr, 3000);
_aqualink_data.updated = true;
broadcast_aqualinkstate_error(CONNECTION_ERROR);
#ifdef AQ_NO_THREAD_NETSERVICE
poll_net_services(1000);
poll_net_services(3000);
#endif
// broadcast_aqualinkstate_error(mgr.active_connections, "No connection to RS control panel");
}
else
{
sprintf(_aqualink_data.last_display_message, CONNECTION_ERROR);
LOG(AQUA_LOG,LOG_ERR, "Aqualink daemon looks like serial error, resetting.\n");
_aqualink_data.updated = true;
broadcast_aqualinkstate_error(CONNECTION_ERROR);
close_serial_port(rs_fd);
if (_aqconfig_.rs_poll_speed < 0)
rs_fd = init_blocking_serial_port(_aqconfig_.serial_port);
else if (_aqconfig_.readahead_b4_write)
rs_fd = init_readahead_serial_port(_aqconfig_.serial_port);
else
rs_fd = init_serial_port(_aqconfig_.serial_port);
rs_fd = init_serial_port(_aqconfig_.serial_port);
}
blank_read = 0;
}
packet_length = get_packet(rs_fd, packet_buffer);
/*
if (_aqconfig_.log_raw_RS_bytes)
packet_length = get_packet_lograw(rs_fd, packet_buffer);
else
packet_length = get_packet(rs_fd, packet_buffer);
*/
/*
if (packet_length == AQSERR_READ || packet_length == AQSERR_TIMEOUT)
{
// Unrecoverable read error. Force an attempt to reconnect.
if (_aqconfig_.rs_poll_speed < 0) {
LOG(AQUA_LOG,LOG_ERR, "Bad serial read or connection\n");
blank_read = blank_read_reconnect;
} else {
blank_read++;
}
}
else*/
if (packet_length <= 0)
{
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0) {
#else
//if (!_aqconfig_.readahead_b4_write) {
if (serial_blockingmode()) {
#endif
LOG(AQUA_LOG,LOG_ERR, "Nothing read on blocking serial port\n");
blank_read = blank_read_reconnect;
} else if (packet_length == AQSERR_READ)
} else if (packet_length == AQSERR_READ) {
blank_read = blank_read_reconnect;
} else {
// In non blocking, so sleep for 2 milliseconds
delay(NONBLOCKING_SERIAL_DELAY);
}
//if (blank_read > max_blank_read) {
// LOG(AQUA_LOG,LOG_NOTICE, "Nothing read on serial %d\n",blank_read);
// max_blank_read = blank_read;
@ -1834,32 +1791,51 @@ void main_loop()
logPacketRead(packet_buffer, packet_length);
}
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
if (!_aqconfig_.prioritize_ack)
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
#ifdef AQ_PDA
if (isPDA_PANEL)
caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA);
else
#endif
caculate_ack_packet(rs_fd, packet_buffer, ALLBUTTON);
if (_aqconfig_.prioritize_ack)
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"AllButton Emulation Processed packet in");
}
else if (packet_length > 0 && isRSSA_ENABLED && packet_buffer[PKT_DEST] == _aqconfig_.rssa_device_id && getProtocolType(packet_buffer) == JANDY) {
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
if (_aqconfig_.prioritize_ack) {
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
} else {
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
}
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"SerialAdapter Emulation Processed packet in");
}
#ifdef AQ_ONETOUCH
else if (packet_length > 0 && isONET_ENABLED && packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id && getProtocolType(packet_buffer) == JANDY) {
_aqualink_data.updated = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, ONETOUCH);
if (_aqconfig_.prioritize_ack) {
caculate_ack_packet(rs_fd, packet_buffer, ONETOUCH);
_aqualink_data.updated = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data);
} else {
_aqualink_data.updated = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, ONETOUCH);
}
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"OneTouch Emulation Processed packet in");
}
#endif
#ifdef AQ_IAQTOUCH
else if (packet_length > 0 && isIAQT_ENABLED && packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id && getProtocolType(packet_buffer) == JANDY) {
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
if (_aqconfig_.prioritize_ack) {
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
} else {
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
}
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"AquaTouch Emulation Processed packet in");
}
#endif
@ -1880,19 +1856,17 @@ void main_loop()
DEBUG_TIMER_CLEAR(_rs_packet_timer); // Clear timer, no need to print anything
}
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqualink_data.updated) {
broadcast_aqualinkstate(mgr.active_connections);
//_aqualink_data.updated = false;
broadcast_aqualinkstate();
}
#endif
}
//mg_mgr_poll(&mgr, 10);
//mg_mgr_poll(&mgr, 5);
//mg_mgr_poll(&mgr, packet_length>0?0:_aqconfig_.net_poll_wait); // Don;t wait if we read something.
poll_net_services(&mgr, packet_length>0?0:_aqconfig_.rs_poll_speed); // Don;t wait if we read something.
//tcdrain(rs_fd); // Make sure buffer has been sent.
//mg_mgr_poll(&mgr, 0);
#ifdef AQ_NO_THREAD_NETSERVICE
poll_net_services(packet_length>0?0:_aqconfig_.rs_poll_speed); // Don;t wait if we read something.
#endif
// NSF might want to wait if we are on a non blocking serial port.
// Any unactioned commands
if (_aqualink_data.unactioned.type != NO_ACTION)
@ -1913,17 +1887,28 @@ void main_loop()
//if (_aqconfig_.debug_RSProtocol_packets) stopPacketLogger();
stopPacketLogger();
// Stop network
stop_net_services(&mgr);
if (! _restart) { // Stop network if we are not restarting
stop_net_services();
}
// Reset and close the port.
close_serial_port(rs_fd);
// Clear webbrowser
//mg_mgr_free(&mgr);
if (! _restart) {
// NSF need to run through config memory and clean up.
LOG(AQUA_LOG,LOG_NOTICE, "Exit!\n");
exit(EXIT_FAILURE);
LOG(AQUA_LOG,LOG_NOTICE, "Exit!\n");
exit(EXIT_FAILURE);
} else {
LOG(AQUA_LOG,LOG_WARNING, "Waiting for process to fininish!\n");
delay(5 * 1000);
LOG(AQUA_LOG,LOG_WARNING, "Restarting!\n");
_keepRunning = true;
_restart = false;
startup(_self, _cfgFile);
}
}

View File

@ -123,6 +123,7 @@ void init_parameters (struct aqconfig * parms)
parms->use_panel_aux_labels = false;
parms->force_swg = false;
parms->force_ps_setpoints = false;
//parms->swg_pool_and_spa = false;
parms->swg_zero_ignore = DEFAULT_SWG_ZERO_IGNORE_COUNT;
parms->display_warnings_web = false;
@ -134,10 +135,15 @@ void init_parameters (struct aqconfig * parms)
// Default parameters for threading and USB blocking
parms->readahead_b4_write = false;
#ifdef AQ_NO_THREAD_NETSERVICE
parms->rs_poll_speed = DEFAULT_POLL_SPEED;
parms->thread_netservices = true;
#endif
parms->enable_scheduler = true;
parms->ftdi_low_latency = true;
parms->prioritize_ack = true;
generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN);
}
@ -545,9 +551,12 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "use_panel_aux_labels", 20) == 0) {
_aqconfig_.use_panel_aux_labels = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "force_SWG", 9) == 0) {
} else if (strncasecmp (param, "force_SWG", 9) == 0) {
_aqconfig_.force_swg = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "force_ps_setpoints", 18) == 0) {
_aqconfig_.force_ps_setpoints = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_bytes", 22) == 0) {
_aqconfig_.log_raw_bytes = text2bool(value);
rtn=true;
@ -569,6 +578,7 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "keep_paneltime_synced", 21) == 0) {
_aqconfig_.sync_panel_time = text2bool(value);
rtn=true;
#ifdef AQ_NO_THREAD_NETSERVICE
} else if (strncasecmp (param, "network_poll_speed", 18) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'network_poll_speed' is no longer supported, using value for 'rs_poll_speed'\n");
_aqconfig_.rs_poll_speed = strtoul(value, NULL, 10);
@ -579,12 +589,17 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "thread_netservices", 18) == 0) {
_aqconfig_.thread_netservices = text2bool(value);
rtn=true;
#endif
} else if (strncasecmp (param, "enable_scheduler", 16) == 0) {
_aqconfig_.enable_scheduler = text2bool(value);
rtn=true;
}
} else if (strncasecmp (param, "ftdi_low_latency", 16) == 0) {
_aqconfig_.ftdi_low_latency = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "prioritize_ack", 14) == 0) {
_aqconfig_.prioritize_ack = text2bool(value);
rtn=true;
}
else if (strncasecmp(param, "button_", 7) == 0) {
// Check we have inichalized panel information, if not use any settings we may have
if (_aqconfig_.paneltype_mask == 0)

View File

@ -73,6 +73,7 @@ struct aqconfig
uint8_t read_RS485_devmask;
bool use_panel_aux_labels;
bool force_swg;
bool force_ps_setpoints;
int swg_zero_ignore;
bool display_warnings_web;
bool log_protocol_packets; // Read & Write as packets
@ -82,9 +83,13 @@ struct aqconfig
bool readahead_b4_write;
bool mqtt_timed_update;
bool sync_panel_time;
int rs_poll_speed;
bool thread_netservices;
bool enable_scheduler;
bool ftdi_low_latency;
bool prioritize_ack;
#ifdef AQ_NO_THREAD_NETSERVICE
int rs_poll_speed; // Need to remove
bool thread_netservices; // Need to remove
#endif
};
#ifndef CONFIG_C

26
extras/kill_aqualinkd.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
#
# /*
# * 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 <http://www.gnu.org/licenses/>.
# *
# * 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
# */
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
kill -9 `ps -ef | grep aqualinkd | awk '{printf "%s ",$2}'`

View File

@ -29,3 +29,6 @@ echo "Pss " $Pss
echo "===================";
echo "Mem " $Mem
echo "===================";
ps -p $MYPID -o %cpu,%mem,cmd
echo "===================";

13
extras/show_realtime.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
#
PROCESSNAME=aqualinkd
MYPID=`pidof $PROCESSNAME`
#if [[ $EUID -ne 0 ]]; then
# echo "This script must be run as root"
# exit 1
#fi
top -H -p $MYPID

View File

@ -31,7 +31,7 @@
#include "devices_jandy.h"
#include "version.h"
#include "aq_timer.h"
#include "aq_programmer.h"
//#define test_message "{\"type\": \"status\",\"version\": \"8157 REV MMM\",\"date\": \"09/01/16 THU\",\"time\": \"1:16 PM\",\"temp_units\": \"F\",\"air_temp\": \"96\",\"pool_temp\": \"86\",\"spa_temp\": \" \",\"battery\": \"ok\",\"pool_htr_set_pnt\": \"85\",\"spa_htr_set_pnt\": \"99\",\"freeze_protection\": \"off\",\"frz_protect_set_pnt\": \"0\",\"leds\": {\"pump\": \"on\",\"spa\": \"off\",\"aux1\": \"off\",\"aux2\": \"off\",\"aux3\": \"off\",\"aux4\": \"off\",\"aux5\": \"off\",\"aux6\": \"off\",\"aux7\": \"off\",\"pool_heater\": \"off\",\"spa_heater\": \"off\",\"solar_heater\": \"off\"}}"
//#define test_labels "{\"type\": \"aux_labels\",\"aux1_label\": \"Cleaner\",\"aux2_label\": \"Waterfall\",\"aux3_label\": \"Spa Blower\",\"aux4_label\": \"Pool Light\",\"aux5_label\": \"Spa Light\",\"aux6_label\": \"Unassigned\",\"aux7_label\": \"Unassigned\"}"
@ -41,7 +41,50 @@
//{"type": "aux_labels","Pool Pump": "Pool Pump","Spa Mode": "Spa Mode","Cleaner": "Aux 1","Waterfall": "Aux 2","Spa Blower": "Aux 2","Pool Light": "Aux 4","Spa Light ": "Aux 5","Aux 6": "Aux 6","Aux 7": "Aux 7","Heater": "Heater","Heater": "Heater","Solar Heater": "Solar Heater","(null)": "(null)"}
//SPA WILL TURN OFF AFTER COOL DOWN CYCLE
#include "aq_programmer.h"
int json_chars(char *dest, const char *src, int dest_len, int src_len)
{
int i;
int end = dest_len < src_len ? dest_len:src_len;
for(i=0; i < end; i++) {
if ( (src[i] < 32 || src[i] > 126) ||
src[i] == 123 || // {
src[i] == 125 || // }
src[i] == 34 || // "
src[i] == 92 // backslash
) // only printable chars
dest[i] = ' ';
else
dest[i] = src[i];
}
i--;
while (dest[i] == ' ')
i--;
if (dest[i] != '\0') {
if (i < (dest_len-1))
i++;
dest[i] = '\0';
}
return i;
}
int build_logmsg_JSON(char *dest, const char *src, int dest_len, int src_len)
{
int length = sprintf(dest, "{\"logmsg\":\"");
length += json_chars(dest+length, src, (dest_len-20), src_len);
length += sprintf(dest+length, "\"}");
dest[length] = '\n';
dest[length+1] = '\0';
return length;
}
const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
{
@ -57,6 +100,7 @@ const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
return JSON_TIMEOUT;
}
// NSF should probably use json_chars here.
if (aqdata->last_display_message[0] != '\0') {
int i;
for(i=0; i < strlen(aqdata->last_display_message); i++ ) {
@ -239,7 +283,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
for (i=0; i < aqdata->total_buttons; i++)
{
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && aqdata->pool_htr_set_point != TEMP_UNKNOWN) {
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (_aqconfig_.force_ps_setpoints || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
@ -252,7 +296,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
LED2int(aqdata->aqbuttons[i].led->state),
((aqdata->aqbuttons[i].special_mask & TIMER_ACTIVE) == TIMER_ACTIVE?JSON_ON:JSON_OFF) );
} else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && aqdata->spa_htr_set_point != TEMP_UNKNOWN) {
} else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (_aqconfig_.force_ps_setpoints || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,

View File

@ -48,7 +48,7 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
int build_aux_labels_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
bool parseJSONwebrequest(char *buffer, struct JSONwebrequest *request);
bool parseJSONrequest(char *buffer, struct JSONkvptr *request);
int build_logmsg_JSON(char *dest, const char *src, int dest_len, int src_len);
int build_mqtt_status_JSON(char* buffer, int size, int idx, int nvalue, float setpoint/*char *svalue*/);
bool parseJSONmqttrequest(const char *str, size_t len, int *idx, int *nvalue, char *svalue);
int build_aqualink_error_status_JSON(char* buffer, int size, char *msg);

View File

@ -54,6 +54,9 @@
static struct aqualinkdata *_aqualink_data;
//static char *_web_root;
static pthread_t _net_thread_id = 0;
static bool _keepNetServicesRunning = false;
static struct mg_mgr _mgr;
static int _mqtt_exit_flag = false;
@ -71,21 +74,26 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc);
void reset_last_mqtt_status();
static sig_atomic_t s_signal_received = 0;
//static const char *s_http_port = "8080";
static struct mg_serve_http_opts _http_server_opts;
static void net_signal_handler(int sig_num) {
#ifdef AQ_NO_THREAD_NETSERVICE
static sig_atomic_t s_signal_received = 0;
#endif
static void net_signal_handler(int sig_num) {
//printf("** net_signal_handler **\n");
#ifdef AQ_NO_THREAD_NETSERVICE
if (!_aqconfig_.thread_netservices) {
signal(sig_num, net_signal_handler); // Reinstantiate signal handler to aqualinkd.c
s_signal_received = sig_num;
} else {
intHandler(sig_num); // Force signal handler to aqualinkd.c
}
#else
intHandler(sig_num); // Force signal handler to aqualinkd.c
#endif
}
@ -99,7 +107,12 @@ static void set_websocket_simulator(struct mg_connection *nc) {
static int is_websocket_simulator(const struct mg_connection *nc) {
return nc->flags & MG_F_USER_2;
}
static void set_websocket_aqmanager(struct mg_connection *nc) {
nc->flags |= MG_F_USER_3;
}
static int is_websocket_aqmanager(const struct mg_connection *nc) {
return nc->flags & MG_F_USER_3;
}
static int is_mqtt(const struct mg_connection *nc) {
return nc->flags & MG_F_USER_1;
}
@ -129,6 +142,32 @@ void _broadcast_aqualinkstate_error(struct mg_connection *nc, char *msg)
// Maybe enhacment in future to sent error messages to MQTT
}
// Send log message to any aqManager websocket.
void _broadcast_logs(struct mg_connection *nc, char *msg) {
char message[LOGBUFFER];
struct mg_connection *c;
for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
if (is_websocket(c) && is_websocket_aqmanager(c)) {
build_logmsg_JSON(message, msg, LOGBUFFER, strlen(msg));
ws_send(c, message);
}
}
}
void broadcast_logs(char *msg) {
// NSF This causes mongoose to core dump after a period of time due to number of messages
// so remove until get time to update to new mongoose version.
return;
// See if we have and manager runnig first so we return ASAP.
// Since this get's galled long before net_Services is started, also check we are running.
if (_keepNetServicesRunning && _net_thread_id != 0 && _aqualink_data->aqManagerActive)
_broadcast_logs(_mgr.active_connections, msg);
}
void _broadcast_aqualinkstate(struct mg_connection *nc)
{
static int mqtt_count=0;
@ -661,7 +700,7 @@ void set_light_mode(char *value, int button)
}
*/
typedef enum {uActioned, uBad, uDevices, uStatus, uHomebridge, uDynamicconf, uDebugStatus, uDebugDownload, uSimulator, uSchedules, uSetSchedules} uriAtype;
typedef enum {uActioned, uBad, uDevices, uStatus, uHomebridge, uDynamicconf, uDebugStatus, uDebugDownload, uSimulator, uSchedules, uSetSchedules, uAQmanager} uriAtype;
//typedef enum {NET_MQTT=0, NET_API, NET_WS, DZ_MQTT} netRequest;
const char actionName[][5] = {"MQTT", "API", "WS", "DZ"};
@ -817,6 +856,7 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
char *ri1 = (char *)URI;
char *ri2 = NULL;
char *ri3 = NULL;
//char *ri4 = NULL;
LOG(NET_LOG,LOG_DEBUG, "%s: URI Request '%.*s': value %.2f\n", actionName[from], uri_length, URI, value);
@ -828,7 +868,10 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
} else if (ri3 == NULL) {
ri3 = (char *)&URI[++i];
break;
}
} /*else if (ri4 == NULL) {
ri4 = (char *)&URI[++i];
break;
}*/
}
}
@ -848,6 +891,8 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
return uSchedules;
} else if (strncmp(ri1, "simulator", 9) == 0 && from == NET_WS) { // Only valid from websocket.
return uSimulator;
} else if (strncmp(ri1, "aqmanager", 9) == 0 && from == NET_WS) { // Only valid from websocket.
return uAQmanager;
} else if (strncmp(ri1, "rawcommand", 10) == 0 && from == NET_WS) { // Only valid from websocket.
aq_send_cmd((unsigned char)value);
return uActioned;
@ -868,6 +913,10 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
}
return uDebugStatus;
// couple of debug items for testing
} else if (strncmp(ri1, "restart", 13) == 0) {
LOG(NET_LOG,LOG_NOTICE, "Received restart request!\n");
raise(SIGRESTART);
return uActioned;
} else if (strncmp(ri1, "set_date_time", 13) == 0) {
//aq_programmer(AQ_SET_TIME, NULL, _aqualink_data);
panel_device_request(_aqualink_data, DATE_TIME, 0, 0, from);
@ -1052,6 +1101,21 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
} else {
rtn = uActioned;
}
} else if ((ri3 != NULL && (strncmp(ri1, "CHEM", 4) == 0) && (strncasecmp(ri3, "set", 3) == 0))) {
//aqualinkd/CHEM/pH/set
//aqualinkd/CHEM/ORP/set
if ( strncasecmp(ri2, "ORP", 3) == 0 ) {
_aqualink_data->orp = round(value);
rtn = uActioned;
LOG(NET_LOG,LOG_NOTICE, "%s: request to set ORP to %d\n",actionName[from],_aqualink_data->orp);
} else if ( strncasecmp(ri2, "Ph", 2) == 0 ) {
_aqualink_data->ph = value;
rtn = uActioned;
LOG(NET_LOG,LOG_NOTICE, "%s: request to set Ph to %.2f\n",actionName[from],_aqualink_data->ph);
} else {
LOG(NET_LOG,LOG_WARNING,"%s: ignoring, unknown URI %.*s\n",actionName[from],uri_length,URI);
rtn = uBad;
}
// Action a Turn on / off message
} else if ( (ri2 != NULL && (strncasecmp(ri2, "set", 3) == 0) && (strncasecmp(ri2, "setpoint", 8) != 0)) ||
(ri2 != NULL && ri3 != NULL && (strncasecmp(ri2, "timer", 5) == 0) && (strncasecmp(ri3, "set", 3) == 0)) ) {
@ -1417,6 +1481,18 @@ void action_websocket_request(struct mg_connection *nc, struct websocket_message
ws_send(nc, message);
}
break;
case uAQmanager:
{
LOG(NET_LOG,LOG_DEBUG, "Started AqualinkD Manager\n");
set_websocket_aqmanager(nc);
_aqualink_data->aqManagerActive = true;
DEBUG_TIMER_START(&tid);
char message[JSON_BUFFER_SIZE];
build_aqualink_status_JSON(_aqualink_data, message, JSON_BUFFER_SIZE); // Should change this to simulator.
DEBUG_TIMER_STOP(tid, NET_LOG, "action_websocket_request() build_aqualink_status_JSON took");
ws_send(nc, message);
}
break;
case uSchedules:
{
DEBUG_TIMER_START(&tid);
@ -1537,6 +1613,9 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
if (is_websocket_simulator(nc)) {
_aqualink_data->simulate_panel = false;
LOG(NET_LOG,LOG_DEBUG, "Stoped Simulator Mode\n");
} else if (is_websocket_aqmanager(nc)) {
_aqualink_data->aqManagerActive = false;
LOG(NET_LOG,LOG_DEBUG, "Stoped Aqualink Manager\n");
}
} else if (is_mqtt(nc)) {
LOG(NET_LOG,LOG_WARNING, "MQTT Connection closed\n");
@ -1649,9 +1728,15 @@ void reset_last_mqtt_status()
//_last_mqtt_aqualinkdata.sw .ar_swg_device_status = SWG_STATUS_UNKNOWN;
_last_mqtt_aqualinkdata.battery = -1;
_last_mqtt_aqualinkdata.frz_protect_state = -1;
_last_mqtt_aqualinkdata.boost = -1;
_last_mqtt_aqualinkdata.service_mode_state = -1;
_last_mqtt_aqualinkdata.pool_htr_set_point = TEMP_REFRESH;
_last_mqtt_aqualinkdata.spa_htr_set_point = TEMP_REFRESH;
_last_mqtt_aqualinkdata.ph = -1;
_last_mqtt_aqualinkdata.orp = -1;
_last_mqtt_aqualinkdata.boost = -1;
_last_mqtt_aqualinkdata.swg_percent = -1;
_last_mqtt_aqualinkdata.swg_ppm = -1;
}
void start_mqtt(struct mg_mgr *mgr) {
@ -1681,6 +1766,7 @@ bool _start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata) {
signal(SIGTERM, net_signal_handler);
signal(SIGINT, net_signal_handler);
signal(SIGRESTART, net_signal_handler);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
@ -1722,17 +1808,14 @@ bool _start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata) {
*
*/
pthread_t _net_thread_id;
bool _keepNetServicesRunning = true;
//volatile bool _broadcast = false; // This is redundent when most the fully threadded rather than option.
void *net_services_thread( void *ptr )
{
struct aqualinkdata *aqdata = (struct aqualinkdata *) ptr;
struct mg_mgr mgr;
//struct mg_mgr mgr;
if (!_start_net_services(&mgr, aqdata)) {
if (!_start_net_services(&_mgr, aqdata)) {
//LOG(NET_LOG,LOG_ERR, "Failed to start network services\n");
// Not the best way to do this (have thread exit process), but forks for the moment.
_keepNetServicesRunning = false;
@ -1743,12 +1826,12 @@ void *net_services_thread( void *ptr )
while (_keepNetServicesRunning == true)
{
//poll_net_services(&mgr, 10);
mg_mgr_poll(&mgr, 100);
//poll_net_services(&_mgr, 10);
mg_mgr_poll(&_mgr, 100);
if (aqdata->updated == true /*|| _broadcast == true*/) {
//LOG(NET_LOG,LOG_DEBUG, "********** Broadcast ************\n");
_broadcast_aqualinkstate(mgr.active_connections);
_broadcast_aqualinkstate(_mgr.active_connections);
aqdata->updated = false;
//_broadcast = false;
}
@ -1756,15 +1839,100 @@ void *net_services_thread( void *ptr )
f_end:
LOG(NET_LOG,LOG_NOTICE, "Stopping network services thread\n");
mg_mgr_free(&mgr);
mg_mgr_free(&_mgr);
pthread_exit(0);
}
bool start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata)
#ifndef AQ_NO_THREAD_NETSERVICE
void broadcast_aqualinkstate() {
_aqualink_data->updated = true;
}
void broadcast_aqualinkstate_error(char *msg) {
_broadcast_aqualinkstate_error(_mgr.active_connections, msg);
}
void stop_net_services() {
_keepNetServicesRunning = false;
return;
}
bool start_net_services(struct aqualinkdata *aqdata)
{
// Not the best way to see if we are running, but works for now.
if (_net_thread_id != 0 && _keepNetServicesRunning) {
LOG(NET_LOG,LOG_NOTICE, "Network services thread is already running, not starting\n");
return true;
}
_keepNetServicesRunning = true;
LOG(NET_LOG,LOG_NOTICE, "Starting network services thread\n");
if( pthread_create( &_net_thread_id , NULL , net_services_thread, (void*)aqdata) < 0) {
LOG(NET_LOG, LOG_ERR, "could not create network thread\n");
return false;
}
pthread_detach(_net_thread_id);
return true;
}
#else // DON'T THREAD NET SERVICES
void stop_net_services() {
if ( ! _aqconfig_.thread_netservices) {
mg_mgr_free(&_mgr);
return;
}
}
void broadcast_aqualinkstate(/*struct mg_connection *nc*/)
{
if ( ! _aqconfig_.thread_netservices) {
return _start_net_services(mgr, aqdata);
_broadcast_aqualinkstate(_mgr.active_connections);
_aqualink_data->updated = false;
return;
}
}
void broadcast_aqualinkstate_error(/*struct mg_connection *nc,*/ char *msg)
{
if ( ! _aqconfig_.thread_netservices) {
return _broadcast_aqualinkstate_error(_mgr.active_connections, msg);
}
LOG(NET_LOG,LOG_NOTICE, "Broadcast error to network\n");
}
time_t poll_net_services(/*struct mg_mgr *mgr,*/ int timeout_ms)
{
if (timeout_ms < 0)
timeout_ms = 0;
if ( ! _aqconfig_.thread_netservices) {
//return mg_mgr_poll(mgr, timeout_ms);
return mg_mgr_poll(&_mgr, timeout_ms);
}
if (timeout_ms > 5)
delay(5);
else if (timeout_ms > 0)
delay(timeout_ms);
//LOG(NET_LOG,LOG_NOTICE, "Poll network services\n");
return 0;
}
bool start_net_services(/*struct mg_mgr *mgr, */struct aqualinkdata *aqdata)
{
_keepNetServicesRunning = true;
if ( ! _aqconfig_.thread_netservices) {
//return _start_net_services(mgr, aqdata);
return _start_net_services(&_mgr, aqdata);
}
LOG(NET_LOG,LOG_NOTICE, "Starting network services thread\n");
@ -1779,56 +1947,8 @@ bool start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata)
return true;
}
time_t poll_net_services(struct mg_mgr *mgr, int timeout_ms)
{
if (timeout_ms < 0)
timeout_ms = 0;
#endif
if ( ! _aqconfig_.thread_netservices) {
return mg_mgr_poll(mgr, timeout_ms);
}
if (timeout_ms > 5)
delay(5);
else if (timeout_ms > 0)
delay(timeout_ms);
//LOG(NET_LOG,LOG_NOTICE, "Poll network services\n");
return 0;
}
void broadcast_aqualinkstate(struct mg_connection *nc)
{
if ( ! _aqconfig_.thread_netservices) {
_broadcast_aqualinkstate(nc);
_aqualink_data->updated = false;
return;
}
//_broadcast = true;
//LOG(NET_LOG,LOG_NOTICE, "Broadcast status to network\n");
}
void broadcast_aqualinkstate_error(struct mg_connection *nc, char *msg)
{
if ( ! _aqconfig_.thread_netservices) {
return _broadcast_aqualinkstate_error(nc, msg);
}
LOG(NET_LOG,LOG_NOTICE, "Broadcast error to network\n");
}
void stop_net_services(struct mg_mgr *mgr) {
if ( ! _aqconfig_.thread_netservices) {
mg_mgr_free(mgr);
return;
}
_keepNetServicesRunning = false;
return;
}

View File

@ -15,12 +15,22 @@
//void main_server_TEST(struct aqualinkdata *aqdata, char *s_http_port);
//bool start_web_server(struct mg_mgr *mgr, struct aqualinkdata *aqdata, char *port, char* web_root);
//bool start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata, struct aqconfig *aqconfig);
/*
#ifdef AQ_NO_THREAD_NETSERVICE
bool start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata);
void stop_net_services(struct mg_mgr *mgr);
time_t poll_net_services(struct mg_mgr *mgr, int timeout_ms);
void broadcast_aqualinkstate(struct mg_connection *nc);
void broadcast_aqualinkstate_error(struct mg_connection *nc, char *msg);
#else*/
bool start_net_services(struct aqualinkdata *aqdata);
void stop_net_services();
time_t poll_net_services(int timeout_ms);
void broadcast_aqualinkstate();
void broadcast_aqualinkstate_error(char *msg);
void broadcast_logs(char *msg);
//#endif
#endif // WEB_SERVER_H_

Binary file not shown.

View File

@ -133,8 +133,8 @@ report_zero_pool_temp = no
# Not documented. These will change how RS485 / Serial works, Only use if asked to for problem solving purposes.
#serial_readahead_b4_write = yes
#thread_netservices = yes
#rs_poll_speed = -1
#ftdi_low_latency = no
#prioritize_ack = yes
#swg_zero_ignore_count = 20

Binary file not shown.

View File

@ -38,7 +38,7 @@
#define SLOG_MAX 80
#define PACKET_MAX 600
#define VERSION "serial_logger V1.6"
#define VERSION "serial_logger V1.7"
/*
typedef enum used {
@ -83,6 +83,9 @@ bool _playback_file = false;
int timespec_subtract (struct timespec *result, const struct timespec *x, const struct timespec *y);
void broadcast_logs(char *msg){
// Do nothing, just for utils.c to work.
}
void intHandler(int dummy) {
_keepRunning = false;
@ -173,6 +176,63 @@ void advance_cursor() {
pos = (pos+1) % 4;
}
bool canUseAllB(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodID[i])
return true;
}
return false;
}
bool canUsePDA(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodPDAID[i])
return true;
}
return false;
}
bool canUseONET(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodONETID[i])
return true;
}
return false;
}
bool canUseIQAT(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodIAQTID[i])
return true;
}
return false;
}
bool canUseRSSA(unsigned char ID) {
int i;
for (i = 0; i < 2; i++) {
if (ID == _goodRSSAID[i])
return true;
}
return false;
}
bool canUse(unsigned char ID) {
if (canUseAllB(ID))
return true;
else if (canUsePDA(ID))
return true;
else if (canUseONET(ID))
return true;
else if (canUseIQAT(ID))
return true;
else if (canUseRSSA(ID))
return true;
return false;
}
/*
bool canUse(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
@ -197,6 +257,7 @@ bool canUse(unsigned char ID) {
}
return false;
}
*/
char* canUseExtended(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
@ -629,6 +690,43 @@ int main(int argc, char *argv[]) {
LOG(RSSD_LOG, LOG_NOTICE, "\n\n");
char mainID = 0x00;
char rssaID = 0x00;
char extID = 0x00;
for (i = 0; i < sindex; i++) {
if (slog[i].inuse == true)
continue;
if (canUseAllB(slog[i].ID) && (mainID == 0x00 || canUsePDA(mainID)))
mainID = slog[i].ID;
if (canUsePDA(slog[i].ID) && mainID == 0x00)
mainID = slog[i].ID;
else if (canUseRSSA(slog[i].ID) && rssaID == 0x00)
rssaID = slog[i].ID;
else if (canUseONET(slog[i].ID) && extID == 0x00)
extID = slog[i].ID;
else if (canUseIQAT(slog[i].ID) && (extID == 0x00 || canUseONET(extID)))
extID = slog[i].ID;
}
printf("Suggested aqualinkd.conf values\n");
printf("-------------------------\n");
if (strlen (_panelType) > 0)
printf("panel_type = %s\n",_panelType);
if (mainID != 0x00)
printf("device_id = 0x%02hhx\n",mainID);
if (rssaID != 0x00)
printf("rssa_device_id = 0x%02hhx\n",rssaID);
if (extID != 0x00)
printf("extended_device_id = 0x%02hhx\n",extID);
printf("-------------------------\n");
return 0;
}

View File

@ -220,7 +220,9 @@ bool process_rssadapter_packet(unsigned char *packet, int length, struct aqualin
static int cnt=-5;
cnt++;
//char buff[1024];
//LOG(RSSA_LOG,LOG_DEBUG, " Received message\n");
//debuglogPacket(RSSA_LOG, packet, length, true);
if (cnt == 0 || cnt >= 250) {
LOG(RSSA_LOG,LOG_INFO, "Queue device update requests\n");
@ -276,13 +278,13 @@ bool process_rssadapter_packet(unsigned char *packet, int length, struct aqualin
LOG(RSSA_LOG,LOG_ERR,"Units are Unknown\n");
}
} else if (packet[4] == RS_SA_POOLSP) {
LOG(RSSA_LOG,LOG_INFO,"Pool SP %d\n", packet[6]);
LOG(RSSA_LOG,LOG_INFO,"Pool SP is %d\n", packet[6]);
aq_data->pool_htr_set_point = (int) packet[6];
} else if (packet[4] == RS_SA_SPASP) {
LOG(RSSA_LOG,LOG_INFO,"Spa SP %d\n", packet[6]);
LOG(RSSA_LOG,LOG_INFO,"Spa SP is %d\n", packet[6]);
aq_data->spa_htr_set_point = (int) packet[6];
} else if (packet[4] == RS_SA_POOLSP2) {
LOG(RSSA_LOG,LOG_INFO,"Spa SP %d\n", packet[6]);
LOG(RSSA_LOG,LOG_INFO,"Pool SP2 is %d\n", packet[6]);
aq_data->spa_htr_set_point = (int) packet[6];
} else if (packet[4] == 0x03) {
// These are device status messages

12
utils.c
View File

@ -14,6 +14,7 @@
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
@ -39,11 +40,13 @@
#include "utils.h"
#define DEFAULT_LOG_FILE "/tmp/aqualinkd-inline.log"
//#define MAXCFGLINE 265
#define TIMESTAMP_LENGTH 30
// Since this get's compiled without net_services for serial_logger
// pre-define this here rather than include netservices.h
void broadcast_logs(char *msg);
static bool _daemonise = false;
static bool _log2file = false;
@ -431,7 +434,7 @@ void logMessage(int msg_level, const char *format, ...)
_LOG(AQUA_LOG, msg_level, buffer);
}
*/
#define LOGBUFFER 1024
void LOG(int16_t from, int msg_level, const char * format, ...)
{
@ -509,10 +512,13 @@ void _LOG(int16_t from, int msg_level, char *message)
}
*/
// Send logs to any websocket that's interested.
broadcast_logs(message);
// Sent the log to the UI if configured.
if (msg_level <= LOG_ERR && _loq_display_message != NULL) {
snprintf(_loq_display_message, 127, "%s\n",message);
}
}
if (_log2file == TRUE && _log_filename != NULL) {
char time[TIMESTAMP_LENGTH];

View File

@ -17,6 +17,8 @@
#define FALSE 0
#endif
#define LOGBUFFER 1024
#define MAXLEN 256
//#define round(a) (int) (a+0.5) // 0 decimal places (doesn't work for negative numbers)

View File

@ -1,4 +1,4 @@
#define AQUALINKD_NAME "Aqualink Daemon"
#define AQUALINKD_VERSION "2.3.0e"
#define AQUALINKD_VERSION "2.3.0f"