Merge pull request #276 from sfeakes/Simulator-dev

Simulator dev
pull/100/merge V2.3.4
sfeakes 2024-04-21 20:06:34 -05:00 committed by GitHub
commit a033fc3fbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 2725 additions and 741 deletions

View File

@ -14,7 +14,8 @@ AQ_PDA = true
AQ_ONETOUCH = true
AQ_IAQTOUCH = true
AQ_MANAGER =true
AQ_RS_EXTRA_OPTS = false
#AQ_RS_EXTRA_OPTS = false
#AQ_CONTAINER = false // this is for compiling for containers
#AQ_MEMCMP = true // Not implimented correctly yet.
# Turn off threadded net services
@ -75,7 +76,7 @@ endif
# Main source files
SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c aq_programmer.c net_services.c json_messages.c rs_msg_utils.c\
devices_jandy.c packetLogger.c devices_pentair.c color_lights.c serialadapter.c aq_timer.c aq_scheduler.c web_config.c\
serial_logger.c mongoose.c timespec_subtract.c
serial_logger.c mongoose.c simulator.c timespec_subtract.c
AQ_FLAGS =
@ -130,6 +131,7 @@ DBG_CFLAGS = $(DBGFLAGS) $(AQ_FLAGS) $(MGFLAGS)
# Other sources.
DBG_SRC = $(SRCS) debug_timer.c
SL_SRC = serial_logger.c aq_serial.c utils.c packetLogger.c rs_msg_utils.c timespec_subtract.c
#MG_SRC = mongoose.c
# Build durectories
OBJ_DIR := ./build
@ -140,7 +142,7 @@ SL_OBJ_DIR := $(OBJ_DIR)/slog
OBJ_FILES := $(patsubst %.c,$(OBJ_DIR)/%.o,$(SRCS))
DBG_OBJ_FILES := $(patsubst %.c,$(DBG_OBJ_DIR)/%.o,$(DBG_SRC))
SL_OBJ_FILES := $(patsubst %.c,$(SL_OBJ_DIR)/%.o,$(SL_SRC))
#MG_OBJ_FILES := $(patsubst %.c,$(OBJ_DIR)/%.o,$(MG_SRC))
# define the executable file
MAIN = ./release/aqualinkd
@ -161,6 +163,12 @@ slog: $(SLOG)
aqdebug: $(DEBG)
$(info $(DEBG) has been compiled)
# Container, add container flag and compile
container: CFLAGS := $(CFLAGS) -D AQ_CONTAINER
container: $(MAIN) $(SLOG)
$(info $(MAIN) has been compiled (** For Container use **))
$(info $(SLOG) has been compiled (** For Container use **))
#debug, Just change compile flags and call MAIN
debug: CFLAGS = $(DFLAGS)
debug: $(MAIN) $(SLOG)

View File

@ -56,9 +56,10 @@ https://github.com/sfeakes/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
</td></tr>
</table>
### Simulator
Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to fully configure the master control panel<br>
<img src="extras/simulator.png?raw=true" width="550">
### Simulators
Designed to mimic AqualinkRS devices, used to fully configure the master control panel<br>
<img src="extras/onetouch_sim.png?raw=true">
<img src="extras/allbutton_sim.png?raw=true">
### In Apple Home app.
<img src="extras/HomeKit2.png?raw=true" width="800"></img>
@ -71,13 +72,26 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
## All Web interfaces.
* http://aqualink.ip/ <- (Standard WEB UI
* http://aqualink.ip/simple.html <- (Simple opion if you don't like the above)
* http://aqualink.ip/simulator.html <- (RS8 All Button Control Panel simulator)
* http://aqualink.ip/debug.html <- (Turn on/off debug/serial debug & download logs)
* http://aqualink.ip/simulator.html <- (Displays all simulators in one page with tabs)
* http://aqualink.ip/aqmanager.html <- (Manage AqualinkD configuration & runtime)
* http://aqualink.ip/allbutton_sim.html <- (All Button Simulator)
* http://aqualink.ip/onetouch_sim.html <- (One Touch Simulator)
* http://aqualink.ip/aquapda_sim.html <- (PDA simulator)
#<a name="release"></a>
# ToDo (future release)
* Allow selecting of pre-defined VSP programs (Aqualink Touch & OneTouch protocols.)
* Add set time to OneTouch protocol.
* Publish AqualinkD Management console. (Configure, Restart, run serial_logger) within AqualinkD.
* Update AqualinkD Management console to manage configuration
* Create iAqualink Touch Simulator
# Update in Release 2.3.4
* Changes for Docker
* Updated simulator code base and added new simulators for AllButton, OneTouch & PDA.
* <aqualinkd.ip>/allbutton_sim.html
* <aqualinkd.ip>/onetouch_sim.html
* <aqualinkd.ip>/aquapda_sim.html
* On PDA only panel AqualinkD has to share the same ID with the PDA simulator. There for AqualinkD will not respond to commands while simulator is active.
* Now you can completley program the control panel with the simulators removing the need to have Jandy device.
# Update in Release 2.3.3
* Introduced Aqualink Manager UI http://aqualink.ip/aqmanager.html

View File

@ -29,12 +29,14 @@
#define LIGHT_MODE_BUFER PTHREAD_ARG
typedef enum emulation_type{
SIM_NONE = -1,
ALLBUTTON,
RSSADAPTER,
ONETOUCH,
IAQTOUCH,
AQUAPDA, // AQUAPALM and PDA are taken as specific type.
JANDY_DEVICE // Very rarley used.
JANDY_DEVICE, // Very rarley used.
SIMULATOR
} emulation_type;
typedef enum {

View File

@ -38,7 +38,11 @@ Example /etc/cron.d/aqualinkd
*/
bool remount_root_ro(bool readonly) {
// NSF Check if config is RO_ROOT set
#ifdef AQ_CONTAINER
// In container this is pointless
return false;
#endif
if (readonly) {
LOG(SCHD_LOG,LOG_INFO, "reMounting root RO\n");

View File

@ -336,30 +336,15 @@ 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 if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
return init_serial_port(_aqconfig_.serial_port);
#elif AQ_RS_EXTRA_OPTS
if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
if (_aqconfig_.rs_poll_speed < 0) {
return init_blocking_serial_port(_aqconfig_.serial_port);
}
#else
return init_blocking_serial_port(_aqconfig_.serial_port);
#endif
}
#ifdef AQ_RS_EXTRA_OPTS
int init_readahead_serial_port(const char* tty)
{
return _init_serial_port(tty, false, true);
}
#endif
int init_blocking_serial_port(const char* tty)
{
_blocking_fds = _init_serial_port(tty, true, false);
@ -767,14 +752,6 @@ void send_packet(int fd, unsigned char *packet, int length)
if (nwrite != length)
LOG(RSSD_LOG, LOG_ERR, "write to serial port failed\n");
} else {
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.readahead_b4_write) {
if (cleanOutSerial(fd, false) != 0x00) {
LOG(RSSD_LOG, LOG_ERR, "ERROR on RS485, AqualinkD was too slow in replying to message! (please check for performance issues)\n");
cleanOutSerial(fd, true);
}
}
#endif
int nwrite, i;
for (i = 0; i < length; i += nwrite) {
nwrite = write(fd, packet + i, length - i);
@ -1026,25 +1003,8 @@ int get_packet(int fd, unsigned char* packet)
// 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
if (_aqconfig_.readahead_b4_write) {
if (endOfPacket == true) {
do {
bytesRead = read(fd, &byte, 1);
//if (bytesRead==1) { LOG(RSSD_LOG,LOG_ERR, "Cleanout buffer read 0x%02hhx\n",byte); }
if (bytesRead==1 && byte != 0x00) {
LOG(RSSD_LOG,LOG_ERR, "SERIOUS ERROR on RS485, AqualinkD caught packet collision on bus, ignoring!\n");
LOG(RSSD_LOG,LOG_ERR, "Error Cleanout read 0x%02hhx\n",byte);
// Run the buffer out
do {
bytesRead = read(fd, &byte, 1);
if (bytesRead==1) { LOG(RSSD_LOG,LOG_ERR, "Error Cleanout read 0x%02hhx\n",byte); }
} while (bytesRead==1);
return 0;
}
} while (bytesRead==1);
}
}
*/
*/
//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){

View File

@ -458,7 +458,7 @@ typedef enum {
int init_serial_port(const char* tty);
int init_blocking_serial_port(const char* tty);
int init_readahead_serial_port(const char* tty);
//int init_readahead_serial_port(const char* tty);
void close_serial_port(int file_descriptor);
void close_blocking_serial_port();

View File

@ -138,6 +138,16 @@ typedef enum pump_type {
} pump_type;
*/
/*
typedef enum simulator_type {
SIM_NONE,
SIM_ALLB,
SIM_ONET,
SIM_PDA,
SIM_IAQT
} simulator_type;
*/
#define PUMP_PRIMING -1
#define PUMP_OFFLINE -2
#define PUMP_ERROR -3
@ -227,7 +237,16 @@ struct aqualinkdata
//unsigned short total_ordered_buttons;
unsigned char last_packet_type;
int swg_delayed_percent;
bool simulate_panel;
//bool simulate_panel; // NSF remove in future
unsigned char simulator_packet[AQ_MAXPKTLEN+1];
bool simulator_packet_updated;
int simulator_packet_length;
//bool simulator_active; // should be redundant with other two
unsigned char simulator_id;
//simulator_type simulator_active;
emulation_type simulator_active;
bool aqManagerActive;
int open_websockets;
struct programmingthread active_thread;

View File

@ -50,6 +50,7 @@
#include "version.h"
#include "rs_msg_utils.h"
#include "serialadapter.h"
#include "simulator.h"
#include "debug_timer.h"
#ifdef AQ_MANAGER
@ -743,7 +744,8 @@ void _processMessage(char *message, bool reset)
{
LOG(AQRS_LOG,LOG_DEBUG_SERIAL, "Ignoring '%s'\n", msg);
//_aqualink_data.display_message = msg;
if (in_programming_mode(&_aqualink_data) == false && _aqualink_data.simulate_panel == false &&
//if (in_programming_mode(&_aqualink_data) == false && _aqualink_data.simulate_panel == false &&
if (in_programming_mode(&_aqualink_data) == false &&
stristr(msg, "JANDY AquaLinkRS") == NULL &&
//stristr(msg, "PUMP O") == NULL &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
strncasecmp(msg, "PUMP O", 6) != 0 &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
@ -1059,7 +1061,6 @@ int main(int argc, char *argv[])
char defaultCfg[] = "./aqualinkd.conf";
char *cfgFile;
//printf ("TIMER = %d\n",TIMR_LOG);
#ifdef AQ_MEMCMP
@ -1137,7 +1138,7 @@ int main(int argc, char *argv[])
else if (strcmp(argv[i], "-rsrd") == 0)
{
_cmdln_lograwRS485 = true;
}
}
}
// Set this here, so it doesn;t get reset if the manager restarts the AqualinkD process.
@ -1316,14 +1317,6 @@ int startup(char *self, char *cfgFile)
if (READ_RSDEV_SWG && _aqconfig_.swg_zero_ignore != DEFAULT_SWG_ZERO_IGNORE_COUNT)
LOG(AQUA_LOG,LOG_NOTICE, "Ignore SWG 0 msg count = %d\n", _aqconfig_.swg_zero_ignore);
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.readahead_b4_write == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial Read Ahead Write = %s\n", bool2text(_aqconfig_.readahead_b4_write));
if (_aqconfig_.prioritize_ack == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial Prioritize Ack = %s\n", bool2text(_aqconfig_.prioritize_ack));
#endif
if (_aqconfig_.ftdi_low_latency == true)
LOG(AQUA_LOG,LOG_NOTICE, "Serial FTDI low latency = %s\n", bool2text(_aqconfig_.ftdi_low_latency));
@ -1342,13 +1335,6 @@ int startup(char *self, char *cfgFile)
LOG(AQUA_LOG,LOG_NOTICE, "RS Poll Speed = %d\n", _aqconfig_.rs_poll_speed);
#endif
#if defined AQ_RS_EXTRA_OPTS && defined AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0 && _aqconfig_.readahead_b4_write) {
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++)
{
@ -1451,6 +1437,20 @@ void caculate_ack_packet(int rs_fd, unsigned char *packet_buffer, emulation_type
//DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"PDA Emulation type Processed packet in");
break;
#endif
case SIMULATOR:
if (_aqualink_data.simulator_active == ALLBUTTON) {
send_extended_ack(rs_fd, (packet_buffer[PKT_CMD]==CMD_MSG_LONG?ACK_SCREEN_BUSY_SCROLL:ACK_NORMAL), pop_simulator_cmd(packet_buffer[PKT_CMD]));
} else if (_aqualink_data.simulator_active == ONETOUCH) {
send_extended_ack(rs_fd, ACK_ONETOUCH, pop_simulator_cmd(packet_buffer[PKT_CMD]));
} else if (_aqualink_data.simulator_active == IAQTOUCH) {
LOG(SIM_LOG,LOG_WARNING, "IAQTOUCH not implimented yet!\n");
} else if (_aqualink_data.simulator_active == AQUAPDA) {
send_extended_ack(rs_fd, ACK_PDA, pop_simulator_cmd(packet_buffer[PKT_CMD]));
} else {
LOG(SIM_LOG,LOG_ERR, "No idea on this protocol (%d), not implimented!!!\n",_aqualink_data.simulator_active);
}
break;
default:
LOG(AQUA_LOG,LOG_WARNING, "Can't caculate ACK, No idea what packet this source packet was for!\n");
//DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"Unknown Emulation type Processed packet in");
@ -1507,7 +1507,7 @@ void main_loop()
int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING; // Will get reset if non blocking
sprintf(_aqualink_data.last_display_message, "%s", "Connecting to Control Panel");
_aqualink_data.simulate_panel = false;
//_aqualink_data.simulate_panel = false;
_aqualink_data.active_thread.thread_id = 0;
_aqualink_data.air_temp = TEMP_UNKNOWN;
_aqualink_data.pool_temp = TEMP_UNKNOWN;
@ -1528,6 +1528,8 @@ void main_loop()
_aqualink_data.open_websockets = 0;
_aqualink_data.ph = TEMP_UNKNOWN;
_aqualink_data.orp = TEMP_UNKNOWN;
_aqualink_data.simulator_id = NUL;
_aqualink_data.simulator_active = SIM_NONE;
pthread_mutex_init(&_aqualink_data.active_thread.thread_mutex, NULL);
pthread_cond_init(&_aqualink_data.active_thread.thread_cond, NULL);
@ -1563,8 +1565,10 @@ void main_loop()
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);
exit(EXIT_FAILURE);
LOG(AQUA_LOG,LOG_ERR, "Error Aqualink setting serial port: %s\n", _aqconfig_.serial_port);
#ifndef AQ_CONTAINER
exit(EXIT_FAILURE);
#endif
}
LOG(AQUA_LOG,LOG_NOTICE, "Listening to Aqualink RS8 on serial port: %s\n", _aqconfig_.serial_port);
@ -1634,16 +1638,15 @@ void main_loop()
LOG(AQUA_LOG,LOG_NOTICE, "Waiting for Control Panel probe\n");
i=0;
// Turn off read ahead while dealing with probes
#ifdef AQ_RS_EXTRA_OPTS
bool read_ahead = _aqconfig_.readahead_b4_write;
_aqconfig_.readahead_b4_write = false;
#endif
// 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 == blank_read_reconnect) {
LOG(AQUA_LOG,LOG_ERR, "Nothing read on '%s', are you sure that's right?\n",_aqconfig_.serial_port);
#ifdef AQ_CONTAINER
// Reset blank reads here, we want to ignore TTY errors in container to keep it running
blank_read = 1;
#endif
} 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();
@ -1749,11 +1752,6 @@ void main_loop()
*
*/
//int max_blank_read = 0;
#ifdef AQ_RS_EXTRA_OPTS
_aqconfig_.readahead_b4_write = read_ahead;
#endif
LOG(AQUA_LOG,LOG_NOTICE, "Starting communication with Control Panel\n");
// Not the best way to do this, but ok for moment
@ -1819,7 +1817,6 @@ void main_loop()
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0) {
#else
//if (!_aqconfig_.readahead_b4_write) {
if (serial_blockingmode() && (packet_length == AQSERR_READ || packet_length == AQSERR_TIMEOUT) ) {
#endif
LOG(AQUA_LOG,LOG_ERR, "Nothing read on blocking serial port\n");
@ -1843,80 +1840,73 @@ void main_loop()
blank_read = 0;
//changed = false;
if (_aqualink_data.simulator_active != SIM_NONE) {
// Check if we have a valid connection
if ( _aqualink_data.simulator_id != NUL && packet_buffer[PKT_DEST] == _aqualink_data.simulator_id) {
// Action comand and Send to web
processSimulatorPacket(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, SIMULATOR);
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"Simulator Emulation Processed packet in");
}
else if ( _aqualink_data.simulator_id == NUL
&& packet_buffer[PKT_CMD] == CMD_PROBE
&& packet_buffer[PKT_DEST] != _aqconfig_.device_id // Check no conflicting id's
#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH
&& packet_buffer[PKT_DEST] != _aqconfig_.extended_device_id // Check no conflicting id's
#endif
)
{
if (is_simulator_packet(&_aqualink_data, packet_buffer, packet_length)) {
_aqualink_data.simulator_id = packet_buffer[PKT_DEST];
// reply to probe
LOG(SIM_LOG,LOG_NOTICE, "Got probe on '0x%02hhx', using for simulator ID\n",packet_buffer[PKT_DEST]);
processSimulatorPacket(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, SIMULATOR);
} else {
LOG(SIM_LOG,LOG_INFO, "Got probe on '0x%02hhx' Still waiting for valid simulator probe\n",packet_buffer[PKT_DEST]);
}
DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"Simulator Emulation Processed packet in");
}
}
if (packet_length > 0 && packet_buffer[PKT_DEST] == _aqconfig_.device_id && getProtocolType(packet_buffer) == JANDY)
{
if (getLogLevel(AQUA_LOG) >= LOG_DEBUG) {
LOG(AQUA_LOG,LOG_DEBUG, "RS received packet of type %s length %d\n", get_packet_type(packet_buffer, packet_length), packet_length);
logPacketRead(packet_buffer, packet_length);
}
#ifdef AQ_RS_EXTRA_OPTS
// If we did not process the packet, above we need to record it for the caculate_ack_packet call.
// Should find a better place to put this, but since prioritize_ack is expermental it's ok for now.
// NSF We shouldn;t need to do the same for rssa / onetouch / iaqtouch and probably rssaadapter since they don;t queue commands & programming commands.
if (_aqconfig_.prioritize_ack) {
_aqualink_data.last_packet_type = packet_buffer[PKT_CMD];
else
#endif
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
#ifdef AQ_PDA
if (isPDA_PANEL)
caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA);
if (isPDA_PANEL) {
// If we are in simulator mode, the sim has already send the ack
if (_aqualink_data.simulator_active == SIM_NONE) {
caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA);
}
}
else
#endif
caculate_ack_packet(rs_fd, packet_buffer, ALLBUTTON);
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.prioritize_ack)
_aqualink_data.updated = process_packet(packet_buffer, packet_length);
#endif
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) {
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.prioritize_ack) {
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
}
else
#endif
{
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
}
_aqualink_data.updated = process_rssadapter_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, RSSADAPTER);
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) {
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.prioritize_ack) {
set_onetouch_lastmsg(packet_buffer[PKT_CMD]);
caculate_ack_packet(rs_fd, packet_buffer, ONETOUCH);
_aqualink_data.updated = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data);
}
else
#endif
{
_aqualink_data.updated = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, ONETOUCH);
}
_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) {
#ifdef AQ_RS_EXTRA_OPTS
if (_aqconfig_.prioritize_ack) {
set_iaqtouch_lastmsg(packet_buffer[PKT_CMD]);
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
}
else
#endif
{
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
}
_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

View File

@ -138,13 +138,6 @@ void init_parameters (struct aqconfig * parms)
parms->sync_panel_time = true;
#ifdef AQ_RS_EXTRA_OPTS
// Default parameters for threading and USB blocking
parms->readahead_b4_write = false;
parms->prioritize_ack = false;
#endif
#ifdef AQ_NO_THREAD_NETSERVICE
parms->rs_poll_speed = DEFAULT_POLL_SPEED;
parms->thread_netservices = true;
@ -153,7 +146,7 @@ void init_parameters (struct aqconfig * parms)
parms->enable_scheduler = true;
parms->ftdi_low_latency = true;
parms->frame_delay = 0;
generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN);
}
@ -589,14 +582,6 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "display_warnings_in_web", 23) == 0) {
_aqconfig_.display_warnings_web = text2bool(value);
rtn=true;
#ifdef AQ_RS_EXTRA_OPTS
} else if (strncasecmp (param, "serial_readahead_b4_write", 25) == 0) {
_aqconfig_.readahead_b4_write = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "prioritize_ack", 14) == 0) {
_aqconfig_.prioritize_ack = text2bool(value);
rtn=true;
#endif
} else if (strncasecmp (param, "mqtt_timed_update", 17) == 0) {
_aqconfig_.mqtt_timed_update = text2bool(value);
rtn=true;

View File

@ -54,7 +54,7 @@ struct aqconfig
char *mqtt_server;
char *mqtt_user;
char *mqtt_passwd;
char mqtt_ID[MQTT_ID_LEN];
char mqtt_ID[MQTT_ID_LEN+1];
int dzidx_air_temp;
int dzidx_pool_water_temp;
int dzidx_spa_water_temp;
@ -85,10 +85,12 @@ struct aqconfig
bool log_raw_bytes; // Read as bytes
unsigned char RSSD_LOG_filter;
//bool log_raw_RS_bytes;
/*
#ifdef AQ_RS_EXTRA_OPTS
bool readahead_b4_write;
bool prioritize_ack;
#endif
*/
bool mqtt_timed_update;
bool sync_panel_time;
bool enable_scheduler;

BIN
extras/allbutton_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
extras/onetouch_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -32,6 +32,7 @@
#include "version.h"
#include "aq_timer.h"
#include "aq_programmer.h"
#include "rs_msg_utils.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\"}"
@ -91,10 +92,12 @@ int build_logmsg_JSON(char *dest, int loglevel, const char *src, int dest_len, i
const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
{
/*
if (aqdata->active_thread.thread_id != 0 && !aqdata->simulate_panel) {
//return JSON_PROGRAMMING;
return programtypeDisplayName(aqdata->active_thread.ptype);
}
*/
//if (aqdata->last_message != NULL && stristr(aqdata->last_message, "SERVICE") != NULL ) {
if (aqdata->service_mode_state == ON) {
@ -516,6 +519,7 @@ int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int
length += logmaskjsonobject(PROG_LOG, buffer+length);
length += logmaskjsonobject(DBGT_LOG, buffer+length);
length += logmaskjsonobject(TIMR_LOG, buffer+length);
length += logmaskjsonobject(SIM_LOG, buffer+length);
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
@ -709,6 +713,83 @@ int build_aux_labels_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
//return strlen(buffer);
}
/*
const char* emulationtype2name(emulation_type type) {
switch (type) {
case ALLBUTTON:
return "allbutton";
break;
case RSSADAPTER:
return "allbutton";
break;
case ONETOUCH:
return "onetouch";
break;
case IAQTOUCH:
return "iaqualinktouch";
break;
case AQUAPDA:
return "aquapda";
break;
case JANDY_DEVICE:
return "jandydevice";
break;
case SIMULATOR:
return "allbutton";
break;
default:
return "none";
break;
}
}
*/
int build_aqualink_simulator_packet_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
memset(&buffer[0], 0, size);
int length = 0;
int i;
length += sprintf(buffer+length, "{\"type\": \"simpacket\"");
if (aqdata->simulator_packet[PKT_DEST] >= 0x40 && aqdata->simulator_packet[PKT_DEST] <= 0x43) {
length += sprintf(buffer+length, ",\"simtype\": \"onetouch\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x08 && aqdata->simulator_packet[PKT_DEST] <= 0x0a) {
length += sprintf(buffer+length, ",\"simtype\": \"allbutton\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x30 && aqdata->simulator_packet[PKT_DEST] <= 0x33) {
length += sprintf(buffer+length, ",\"simtype\": \"iaqtouch\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x60 && aqdata->simulator_packet[PKT_DEST] <= 0x63) {
length += sprintf(buffer+length, ",\"simtype\": \"aquapda\"");
} else {
length += sprintf(buffer+length, ",\"simtype\": \"unknown\"");
}
//if (aqdata->simulator_packet[i][])
//length += sprintf(buffer+length, ",\"simtype\": \"onetouch\"");
length += sprintf(buffer+length, ",\"raw\": [");
for (i=0; i < aqdata->simulator_packet_length; i++)
{
length += sprintf(buffer+length, "\"0x%02hhx\",", aqdata->simulator_packet[i]);
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
length += sprintf(buffer+length, ",\"dec\": [");
for (i=0; i < aqdata->simulator_packet_length; i++)
{
length += sprintf(buffer+length, "%d,", aqdata->simulator_packet[i]);
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
length += sprintf(buffer+length, "}");
//printf("Buffer=%d, used=%d, OUT='%s'\n",size,length,buffer);
return length;
}
// WS Received '{"parameter":"SPA_HTR","value":99}'
// WS Received '{"command":"KEY_HTR_POOL"}'

View File

@ -9,6 +9,7 @@
#define JSON_LABEL_SIZE 600
#define JSON_BUFFER_SIZE 5120
#define JSON_STATUS_SIZE 2048
#define JSON_SIMULATOR_SIZE 2048
#define JSON_MQTT_MSG_SIZE 100
@ -57,6 +58,7 @@ int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int
//int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch, char* buffer, int size, bool homekit);
//int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch1, int programable_switch2, char* buffer, int size, bool homekit);
int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool homekit);
int build_aqualink_simulator_packet_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
#endif /* JSON_MESSAGES_H_ */

View File

@ -43,6 +43,7 @@
#include "aq_timer.h"
#include "aq_scheduler.h"
#include "rs_msg_utils.h"
#include "simulator.h"
#include "version.h"
#ifdef AQ_PDA
@ -141,6 +142,24 @@ void _broadcast_aqualinkstate_error(struct mg_connection *nc, char *msg)
// Maybe enhacment in future to sent error messages to MQTT
}
void _broadcast_simulator_message(struct mg_connection *nc) {
struct mg_connection *c;
char data[JSON_SIMULATOR_SIZE];
build_aqualink_simulator_packet_JSON(_aqualink_data, data, JSON_SIMULATOR_SIZE);
for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
if (is_websocket(c) && is_websocket_simulator(c)) {
ws_send(c, data);
}
}
//LOG(NET_LOG,LOG_DEBUG, "Sent to simulator '%s'\n",data);
_aqualink_data->simulator_packet_updated = false;
}
#ifdef AQ_MANAGER
#define WS_LOG_LENGTH 200
@ -159,7 +178,13 @@ sd_journal *open_journal() {
sd_journal *journal;
char filter[51];
#ifndef AQ_CONTAINER
// Below works for local
if (sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY) < 0)
#else
// Container doesn't have local systemd_journal so use hosts through mapped filesystem
if (sd_journal_open_directory(&journal, "/var/log/journal", SD_JOURNAL_SYSTEM) < 0)
#endif
{
LOGSystemError(errno, NET_LOG, "Failed to open journal");
return journal;
@ -171,6 +196,7 @@ sd_journal *open_journal() {
sd_journal_close(journal);
return journal;
}
/* Docker wll also have problem with this
// Daemon will change PID after printing startup message, so don't filter on current PID
if (_aqconfig_.deamonize != true) {
snprintf(filter, 50, "_PID=%d",getpid());
@ -180,7 +206,7 @@ sd_journal *open_journal() {
sd_journal_close(journal);
return journal;
}
}
}*/
if (sd_journal_set_data_threshold(journal, LOGBUFFER) < 0)
{
@ -414,7 +440,8 @@ void _broadcast_aqualinkstate(struct mg_connection *nc)
#endif
for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
if (is_websocket(c))
//if (is_websocket(c) && !is_websocket_simulator(c)) // No need to broadcast status messages to simulator.
if (is_websocket(c)) // All button simulator needs status messages
ws_send(c, data);
#ifndef MG_DISABLE_MQTT
else if (is_mqtt(c))
@ -959,10 +986,25 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
} else if (strncmp(ri1, "schedules", 9) == 0) {
return uSchedules;
} else if (strncmp(ri1, "simulator", 9) == 0 && from == NET_WS) { // Only valid from websocket.
if (ri2 != NULL && strncmp(ri2, "onetouch", 8) == 0) {
start_simulator(_aqualink_data, ONETOUCH);
} else if (ri2 != NULL && strncmp(ri2, "allbutton", 9) == 0) {
start_simulator(_aqualink_data, ALLBUTTON);
} else if (ri2 != NULL && strncmp(ri2, "aquapda", 7) == 0) {
start_simulator(_aqualink_data, AQUAPDA);
} else if (ri2 != NULL && strncmp(ri2, "iaqtouch", 8) == 0) {
start_simulator(_aqualink_data, IAQTOUCH);
} else {
return uBad;
}
return uSimulator;
} else if (strncmp(ri1, "simcmd", 10) == 0 && from == NET_WS) { // Only valid from websocket.
simulator_send_cmd((unsigned char)value);
return uActioned;
/*
} else if (strncmp(ri1, "rawcommand", 10) == 0 && from == NET_WS) { // Only valid from websocket.
aq_send_cmd((unsigned char)value);
return uActioned;
return uActioned;*/
#ifdef AQ_MANAGER
} else if (strncmp(ri1, "aqmanager", 9) == 0 && from == NET_WS) { // Only valid from websocket.
return uAQmanager;
@ -1562,9 +1604,11 @@ void action_websocket_request(struct mg_connection *nc, struct websocket_message
break;
case uSimulator:
{
LOG(NET_LOG,LOG_DEBUG, "Started Simulator Mode\n");
LOG(NET_LOG,LOG_DEBUG, "Request to start Simulator\n");
set_websocket_simulator(nc);
_aqualink_data->simulate_panel = true;
//_aqualink_data->simulate_panel = true;
// Clear simulator ID incase sim type changes
//_aqualink_data->simulator_id = NUL;
DEBUG_TIMER_START(&tid);
char message[JSON_BUFFER_SIZE];
build_aqualink_status_JSON(_aqualink_data, message, JSON_BUFFER_SIZE);
@ -1706,9 +1750,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
if (is_websocket(nc)) {
_aqualink_data->open_websockets--;
LOG(NET_LOG,LOG_DEBUG, "-- Websocket left\n");
// Need something below to detect is_websocket_simulator() and turn off aq_data.simulate_panel
if (is_websocket_simulator(nc)) {
_aqualink_data->simulate_panel = false;
stop_simulator(_aqualink_data);
LOG(NET_LOG,LOG_DEBUG, "Stoped Simulator Mode\n");
} else if (is_websocket_aqmanager(nc)) {
_aqualink_data->aqManagerActive = false;
@ -1923,9 +1966,9 @@ void *net_services_thread( void *ptr )
while (_keepNetServicesRunning == true)
{
//poll_net_services(&_mgr, 10);
// Shorten poll cycle when logging messages to WS
//mg_mgr_poll(&_mgr, (_aqualink_data->aqManagerActive)?50:100);
mg_mgr_poll(&_mgr, 100);
// Shorten poll cycle when in simulator mode
mg_mgr_poll(&_mgr, (_aqualink_data->simulator_active != SIM_NONE)?10:100);
//mg_mgr_poll(&_mgr, 100);
if (aqdata->updated == true /*|| _broadcast == true*/) {
//LOG(NET_LOG,LOG_DEBUG, "********** Broadcast ************\n");
@ -1933,10 +1976,13 @@ void *net_services_thread( void *ptr )
aqdata->updated = false;
}
#ifdef AQ_MANAGER
if ( ! broadcast_systemd_logmessages(_aqualink_data->aqManagerActive)) {
if ( ! broadcast_systemd_logmessages(aqdata->aqManagerActive)) {
LOG(AQUA_LOG,LOG_ERR, "Couldn't open systemd journal log\n");
}
#endif
if (aqdata->simulator_active != SIM_NONE && aqdata->simulator_packet_updated == true ) {
_broadcast_simulator_message(_mgr.active_connections);
}
}
f_end:
@ -1958,6 +2004,10 @@ void broadcast_aqualinkstate() {
void broadcast_aqualinkstate_error(char *msg) {
_broadcast_aqualinkstate_error(_mgr.active_connections, msg);
}
void broadcast_simulator_message() {
_aqualink_data->simulator_packet_updated = true;
}
void stop_net_services() {
_keepNetServicesRunning = false;
@ -2009,6 +2059,11 @@ void broadcast_aqualinkstate_error(/*struct mg_connection *nc,*/ char *msg)
}
LOG(NET_LOG,LOG_NOTICE, "Broadcast error to network\n");
}
void broadcast_simulator_message() {
if ( ! _aqconfig_.thread_netservices) {
return _broadcast_simulator_message();
}
}
time_t poll_net_services(/*struct mg_mgr *mgr,*/ int timeout_ms)
{
if (timeout_ms < 0)

View File

@ -29,6 +29,7 @@ void stop_net_services();
time_t poll_net_services(int timeout_ms);
void broadcast_aqualinkstate();
void broadcast_aqualinkstate_error(char *msg);
void broadcast_simulator_message();
// superseded with systemd/sd-journal
//void broadcast_log(char *msg);

View File

@ -865,7 +865,7 @@ bool set_PDA_numeric_field_value(struct aqualinkdata *aq_data, int val, int cur_
char *hghlight_chars;
int hlight_length=0;
int i=0;
//hghlight_chars = pda_m_hlightchars(&hlight_length);
hghlight_chars = pda_m_hlightchars(&hlight_length); // NSF May need to take this out and there for the LOG entry after while
while (hlight_length >= 15 || hlight_length <= 0) {
delay(500);
waitForPDANextMessageType(aq_data,CMD_PDA_HIGHLIGHTCHARS,5);

Binary file not shown.

View File

@ -132,8 +132,6 @@ report_zero_pool_temp = no
#serial_debug_filter = 0x00
# Not documented. These are experimental. Will change how RS485 / Serial works, Only use if asked to for problem solving purposes.
#serial_readahead_b4_write = yes
#prioritize_ack = yes
rs485_frame_delay = 4
# Get rid of the startup warning message about no low latency. BETTER option is to buy a better adapter.

View File

@ -98,11 +98,12 @@ device_id=0x60
#extended_device_id_programming = no
# Not documented
serial_readahead_b4_write = yes
mqtt_timed_update = no
thread_netservices = yes
rs_poll_speed = -1
#serial_readahead_b4_write = yes
#mqtt_timed_update = no
#thread_netservices = yes
#rs_poll_speed = -1
#rs_poll_speed = 1
rs485_frame_delay = 4
# Your RS panel size. ie 4, 6, 8, 12 or 16 relates to RS4, RS6, RS8, RS12 or RS16.
# VERY important that you select 12 or 16, if you have either of those size panels.

Binary file not shown.

View File

@ -41,7 +41,7 @@
#define SLOG_MAX 80
#define PACKET_MAX 600
#define VERSION "serial_logger V2.0"
#define VERSION "serial_logger V2.1"
/*
typedef enum used {
@ -86,7 +86,7 @@ bool _playback_file = false;
int sl_timespec_subtract (struct timespec *result, const struct timespec *x, const struct timespec *y);
int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, bool panleProbe, bool rsSerialSpeedTest, bool errorMonitor);
int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, bool panleProbe, bool rsSerialSpeedTest, bool errorMonitor, bool printAllIDs);
#ifdef SERIAL_LOGGER
@ -101,7 +101,7 @@ void intHandler(int dummy) {
}
#else
int serial_logger (int rs_fd, char *port_name, int logLevel) {
return _serial_logger(rs_fd, port_name, PACKET_MAX, (logLevel>=LOG_NOTICE?logLevel:LOG_NOTICE), true, false, false);
return _serial_logger(rs_fd, port_name, PACKET_MAX, (logLevel>=LOG_NOTICE?logLevel:LOG_NOTICE), true, false, false, false);
}
#endif
@ -362,11 +362,9 @@ int main(int argc, char *argv[]) {
bool rsSerialSpeedTest = false;
bool serialBlocking = true;
bool errorMonitor = false;
bool printAllIDs = false;
// aq_serial.c uses the following
#ifdef AQ_RS_EXTRA_OPTS
_aqconfig_.readahead_b4_write = false;
#endif
_aqconfig_.log_protocol_packets = false;
_aqconfig_.log_raw_bytes = false;
_aqconfig_.ftdi_low_latency = true;
@ -391,6 +389,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "\t-lpack (log RS packets to %s)\n",RS485LOGFILE);
fprintf(stderr, "\t-lrawb (log raw RS bytes to %s)\n",RS485BYTELOGFILE);
fprintf(stderr, "\t-e (monitor errors)\n");
fprintf(stderr, "\t-a (Print all ID's the panel queried)\n");
fprintf(stderr, "\nie:\t%s /dev/ttyUSB0 -d -p 1000 -i 0x08 -i 0x0a\n\n", argv[0]);
return 1;
}
@ -424,6 +423,8 @@ int main(int argc, char *argv[]) {
serialBlocking = false;
} else if (strcmp(argv[i], "-e") == 0) {
errorMonitor = true;
} else if (strcmp(argv[i], "-a") == 0) {
printAllIDs = true;
}
}
@ -471,7 +472,7 @@ int main(int argc, char *argv[]) {
startPacketLogger();
_serial_logger(rs_fd, argv[1], logPackets, logLevel, panleProbe, rsSerialSpeedTest, errorMonitor);
_serial_logger(rs_fd, argv[1], logPackets, logLevel, panleProbe, rsSerialSpeedTest, errorMonitor, printAllIDs);
stopPacketLogger();
@ -483,7 +484,7 @@ int main(int argc, char *argv[]) {
int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, bool panleProbe, bool rsSerialSpeedTest, bool errorMonitor) {
int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, bool panleProbe, bool rsSerialSpeedTest, bool errorMonitor, bool printAllIDs) {
int packet_length;
int last_packet_length = 0;
unsigned char packet_buffer[AQ_MAXPKTLEN];
@ -569,6 +570,7 @@ int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, boo
}
}
if (found != true && sindex < SLOG_MAX) {
//printf("Added id 0x%02hhx\n",packet_buffer[PKT_DEST]);
slog[sindex].ID = packet_buffer[PKT_DEST];
slog[sindex].inuse = false;
sindex++;
@ -654,7 +656,8 @@ int _serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, boo
for (i = 0; i < sindex; i++) {
//LOG(RSSD_LOG, LOG_NOTICE, "ID 0x%02hhx is %s %s\n", slog[i].ID, (slog[i].inuse == true) ? "in use" : "not used",
// (slog[i].inuse == false && canUse(slog[i].ID) == true)? " <-- can use for Aqualinkd" : "");
if (logLevel >= LOG_DEBUG || slog[i].inuse == true || canUse(slog[i].ID) == true) {
//if (logLevel >= LOG_DEBUG || slog[i].inuse == true || canUse(slog[i].ID) == true) {
if (logLevel >= LOG_DEBUG || slog[i].inuse == true || canUse(slog[i].ID) == true || printAllIDs == true) {
LOG(RSSD_LOG, LOG_NOTICE, "ID 0x%02hhx is %s %s\n", slog[i].ID, (slog[i].inuse == true) ? "in use " : "not used",
(slog[i].inuse == false)?canUseExtended(slog[i].ID):getDevice(slog[i].ID));
}

139
simulator.c Normal file
View File

@ -0,0 +1,139 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "aqualink.h"
#include "net_services.h"
#include "packetLogger.h"
#define MAX_STACK 20
int _sim_stack_place = 0;
unsigned char _commands[MAX_STACK];
bool push_simulator_cmd(unsigned char cmd);
int simulator_cmd_length()
{
return _sim_stack_place;
}
// External command
void simulator_send_cmd(unsigned char cmd)
{
push_simulator_cmd(cmd);
}
bool push_simulator_cmd(unsigned char cmd)
{
if (_sim_stack_place < MAX_STACK) {
_commands[_sim_stack_place] = cmd;
_sim_stack_place++;
} else {
LOG(SIM_LOG, LOG_ERR, "Command queue overflow, too many unsent commands to RS control panel\n");
return false;
}
return true;
}
unsigned char pop_simulator_cmd(unsigned char receive_type)
{
unsigned char cmd = NUL;
if (_sim_stack_place > 0 && receive_type == CMD_STATUS ) {
cmd = _commands[0];
_sim_stack_place--;
memmove(&_commands[0], &_commands[1], sizeof(unsigned char) * _sim_stack_place ) ;
}
LOG(SIM_LOG,LOG_DEBUG, "Sending '0x%02hhx' to controller\n", cmd);
return cmd;
}
bool processSimulatorPacket(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata)
{
// copy packed into buffer to be sent to web
//memset(aqdata->simulator_packet, 0, sizeof aqdata->simulator_packet);
memcpy(aqdata->simulator_packet, packet, packet_length);
aqdata->simulator_packet_length = packet_length;
if ( getLogLevel(SIM_LOG) >= LOG_DEBUG ) {
char buff[1000];
//sprintf("Sending control command:")
beautifyPacket(buff, packet, packet_length, false);
LOG(SIM_LOG,LOG_DEBUG, "Received message : %s", buff);
}
broadcast_simulator_message();
return true;
}
bool start_simulator(struct aqualinkdata *aqdata, emulation_type type) {
// If we are a PDA panel and PDA sim, we are connected, so just set that
// since PDA only panel can only handle one remote ID.
if (isPDA_PANEL && type == AQUAPDA) {
aqdata->simulator_active = type;
aqdata->simulator_id = _aqconfig_.device_id;
}
// if type is same AND id is valid, sim is already started, their is nothing to do.
if (aqdata->simulator_active == type) {
if (aqdata->simulator_id >= 0x40 && aqdata->simulator_id <= 0x43) {
LOG(SIM_LOG,LOG_NOTICE, "OneTouch Simulator already active!\n");
return true;
} else if (aqdata->simulator_id >= 0x08 && aqdata->simulator_id <= 0x0a) {
LOG(SIM_LOG,LOG_NOTICE, "AllButton Simulator already active!\n");
return true;
} else if (aqdata->simulator_id >= 0x30 && aqdata->simulator_id <= 0x33) {
LOG(SIM_LOG,LOG_NOTICE, "iAqualinkTouch Simulator already active!\n");
return true;
} else if (aqdata->simulator_id >= 0x60 && aqdata->simulator_id <= 0x63) {
LOG(SIM_LOG,LOG_NOTICE, "PDA Simulator already active!\n");
return true;
}
}
// Check it's a valid request
if (type == ALLBUTTON) {
LOG(SIM_LOG,LOG_NOTICE, "Starting AllButton Simulator!\n");
} else if (type == ONETOUCH) {
LOG(SIM_LOG,LOG_NOTICE, "Starting OneTouch Simulator!\n");
} else if (type == AQUAPDA ) {
LOG(SIM_LOG,LOG_NOTICE, "Starting PDA Simulator!\n");
} else if (type == IAQTOUCH) {
LOG(SIM_LOG,LOG_NOTICE, "Starting iAqualinkTouch Simulator!\n");
} else {
LOG(SIM_LOG,LOG_ERR, "Request to start simulator of unknown type : %d", type);
return false;
}
// start the simulator
aqdata->simulator_active = type;
aqdata->simulator_id = NUL;
return true;
}
bool stop_simulator(struct aqualinkdata *aqdata) {
aqdata->simulator_active = SIM_NONE;
aqdata->simulator_id = NUL;
LOG(SIM_LOG,LOG_DEBUG, "Stoped Simulator Mode\n");
return true;
}
bool is_simulator_packet(struct aqualinkdata *aqdata, unsigned char *packet, int packet_length) {
if ( (aqdata->simulator_active == ONETOUCH && packet[PKT_DEST] >= 0x40 && packet[PKT_DEST] <= 0x43) ||
(aqdata->simulator_active == ALLBUTTON && packet[PKT_DEST] >= 0x08 && packet[PKT_DEST] <= 0x0a) ||
(aqdata->simulator_active == IAQTOUCH && packet[PKT_DEST] >= 0x30 && packet[PKT_DEST] <= 0x33) ||
(aqdata->simulator_active == AQUAPDA && packet[PKT_DEST] >= 0x60 && packet[PKT_DEST] <= 0x63) ) {
return true;
} else {
return false;
}
}

18
simulator.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef SIMULATOR_H_
#define SIMULATOR_H_
#include <stdbool.h>
bool processSimulatorPacket(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
//unsigned char pop_simulator_cmd(struct aqualinkdata *aq_data);
unsigned char pop_simulator_cmd(unsigned char receive_type);
int simulator_cmd_length();
//bool push_simulator_cmd(unsigned char cmd);
void simulator_send_cmd(unsigned char cmd);
bool start_simulator(struct aqualinkdata *aqdata, emulation_type type);
bool stop_simulator(struct aqualinkdata *aqdata);
bool is_simulator_packet(struct aqualinkdata *aqdata, unsigned char *packet, int packet_length);
#endif // SIMULATOR_H_

0
util.c Normal file
View File

View File

@ -322,6 +322,9 @@ const char* logmask2name(int16_t from)
case TIMR_LOG:
return "Schd/Timer:";
break;
case SIM_LOG:
return "Simulator: ";
break;
case AQUA_LOG:
default:
return "AqualinkD: ";

View File

@ -42,6 +42,7 @@
#define PROG_LOG (1 << 10) // Programmer
#define DBGT_LOG (1 << 11) // Debug Timer
#define TIMR_LOG (1 << 12) // Timers
#define SIM_LOG (1 << 13) // Simulator
// Set scheduler log to timer log
#define SCHD_LOG TIMR_LOG

View File

@ -1,4 +1,4 @@
#define AQUALINKD_NAME "Aqualink Daemon"
#define AQUALINKD_VERSION "2.3.3"
#define AQUALINKD_VERSION "2.3.4"

772
web/allbutton_sim.html Normal file
View File

@ -0,0 +1,772 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=windows-1252'>
<title>AqualinkD Simulator</title>
<meta name='viewport' content='width=device-width'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<link href="aqualinkd.png" rel="apple-touch-icon">
<link href="aqualinkd.png" rel="icon">
<style>
html {}
body {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-weight: 300;
background-color: white;
color: #000000;
margin: 0 !important;
padding: 0 !important;
}
.wrapper {
display: flex;
justify-content: center;
width: 100%;
/*position: absolute;
justify-content: center;
width: 100%;
height: 100%;*/
}
table {
background-color: rgb(221, 221, 221);
padding: 10px;
/* border-collapse: collapse;*/
}
th {
font-weight: normal;
background-color: white;
padding-top: 0px;
padding-bottom: 0px;
padding-left: 4px;
padding-right: 4px;
}
.td_led {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 4px;
padding-right: 0px;
}
.td_button {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 0px;
padding-right: 8px;
}
#title {
background-color: rgb(200, 200, 200);
font-weight: 600;
}
input[type=button],
input[type=submit],
input[type=reset] {
/*font-family: monospace;*/
background-color: rgb(165, 165, 165);
border: none;
color: rgb(0, 0, 0);
padding: 2px 2px;
text-decoration: none;
margin: 0px 0px;
min-width: 70px;
border-radius: 70px;
}
.led {
border-radius: calc(var(--tile_icon-height) / 2);
border-radius: 20px;
height: 20px;
width: 20px;
text-align: center;
vertical-align: middle;
filter: alpha(opacity=100);
opacity: 1.0;
}
.lcd_display {
/* font-family: monospace;*/
background-color: #2b2b2b;
color: white;
white-space: pre;
}
.lcd_history {
/* font-family: monospace;*/
background-color: #aeaeae;
color: white;
white-space: pre;
}
.on {
background-color: rgb(255, 0, 0);
}
.off {
background-color: rgb(116, 116, 116);
}
.error {
background-color: rgb(255, 0, 0) !important;
font-weight: 600;
}
.hidden {
display: none;
}
.version {
/*font-family: monospace;*/
font-size: x-small;
}
</style>
<script type='text/javascript'>
var _panel_size = 6;
var _panel_set = 0;
var _keep_socket_alive=true;
// See if we are in an iframe and capture focus
function capture_iframe_focus() {
var targetNode = parent.document.getElementById('allbutton_iframe');
if (targetNode != null) {
var observer = new MutationObserver(function(){
if(targetNode.style.display != 'none'){
console.log("AllButton sim Displaying");
startWebsockets();
} else {
console.log("AllButton sim Hiding");
stopWebsockets();
}
});
observer.observe(targetNode, { attributes: true, childList: true });
return true;
}
return false;
}
function send(source) {
console.log("from" + source.id);
var cmd = {};
cmd.uri = "simcmd"
switch (source.id) {
case "Filter_Pump_button":
cmd.value = "0x02";
break;
case "Spa_Mode_button":
cmd.value = "0x01";
break;
case "Aux_1_button":
cmd.value = "0x05";
break;
case "Aux_2_button":
cmd.value = "0x0a";
break;
case "Aux_3_button":
cmd.value = "0x0f";
break;
case "Aux_4_button":
if (_panel_size < 12)
cmd.value = "0x06";
else
cmd.value = "0x14"; //Different on RS12+ (0x14)
break;
case "Aux_5_button":
if (_panel_size < 12)
cmd.value = "0x0b";
else
cmd.value = "0x03"; //Different on RS12+ (0x03)
break;
case "Aux_6_button":
if (_panel_size < 12)
cmd.value = "0x10";
else
cmd.value = "0x07"; //Different on RS12+ (0x07)
break;
case "Aux_7_button":
if (_panel_size < 12)
cmd.value = "0x15";
else
cmd.value = "0x06"; //Different on RS12+ (0x06)
break;
case "Aux_B1_button":
cmd.value = "0x0b";
break;
case "Aux_B2_button":
cmd.value = "0x10";
break;
case "Aux_B3_button":
cmd.value = "0x15";
break;
case "Aux_B4_button":
cmd.value = "0x1a";
break;
case "Aux_B5_button":
cmd.value = "0x04";
break;
case "Aux_B6_button":
cmd.value = "0x08";
break;
case "Aux_B7_button":
cmd.value = "0x0d";
break;
case "Aux_B8_button":
cmd.value = "0x0c";
break;
case "Pool_Heater_button":
cmd.value = "0x12";
break;
case "Spa_Heater_button":
cmd.value = "0x17";
break;
case "Solar_Heater_button":
cmd.value = "0x1c";
break;
case "B_menu":
cmd.value = "0x09";
break;
case "B_cancel":
cmd.value = "0x0e";
break;
case "B_back":
cmd.value = "0x13";
break;
case "B_forward":
cmd.value = "0x18";
break;
case "B_enter":
cmd.value = "0x1d";
break;
case "B_hold":
cmd.value = "0x19";
break;
case "B_overide":
cmd.value = "0x1c";
break;
default:
alert("Unknown button");
return;
break;
}
cmd.value = todec(cmd.value);
send_command(cmd);
// I know we are converting hex to dec and back to hex, but here for checking.
//document.getElementById("messages").innerHTML = "Sent "+tohex(cmd.value)+" please wait for response!";
}
function tohex(value) {
var rtn = parseInt(value).toString(16);
if (rtn.length <= 1)
return "0x0"+rtn;
else
return "0x"+rtn;
}
function todec(value) {
let number = parseInt(value, 16);
return number;
}
function set_panel_size(size)
{
if (_panel_set == size){
return;
}
_panel_set = size;
switch(size){
case 6:
document.getElementById("Aux_6_led").classList.add("hidden");
document.getElementById("Aux_6_button").classList.add("hidden");
document.getElementById("Aux_7_led").classList.add("hidden");
document.getElementById("Aux_7_button").classList.add("hidden");
case 8:
document.getElementById("Aux_B1_led").classList.add("hidden");
document.getElementById("Aux_B1_button").classList.add("hidden");
document.getElementById("Aux_B2_led").classList.add("hidden");
document.getElementById("Aux_B2_button").classList.add("hidden");
document.getElementById("Aux_B3_led").classList.add("hidden");
document.getElementById("Aux_B3_button").classList.add("hidden");
document.getElementById("Aux_B4_led").classList.add("hidden");
document.getElementById("Aux_B4_button").classList.add("hidden");
case 12:
document.getElementById("Aux_B5_button").classList.add("hidden");
document.getElementById("Aux_B6_button").classList.add("hidden");
document.getElementById("Aux_B7_button").classList.add("hidden");
document.getElementById("Aux_B8_button").classList.add("hidden");
}
}
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
document.getElementById("title").innerHTML = ' !!! PDA only panels are not Supported !!! '
document.getElementById("title").classList.add("error");
document.getElementById("messages").innerHTML = ' !!! PDA Not Supported !!! '
document.getElementById("messages").classList.add("error");
}
for (var obj in data.leds) {
if ((led = document.getElementById(obj.toString() + "_led")) == null) {
//console.log("Error " + obj.toString() + " LED not found");
//return;
} else {
if (data.leds[obj] == "on" || data.leds[obj] == "enabled" || data.leds[obj] == "flash") {
//console.log("Set " + obj.toString() + " LED on");
led.classList.add("on");
led.classList.remove("off");
} else {
//console.log("Set " + obj.toString() + " LED off");
led.classList.add("off");
led.classList.remove("on");
}
}
if (obj.toString() == "Aux_4" && _panel_size < 6)
_panel_size=6;
if (obj.toString() == "Aux_6" && _panel_size < 8)
_panel_size=8;
if (obj.toString() == "Aux_B1" && _panel_size < 12)
_panel_size=12;
if (obj.toString() == "Aux_B4")
_panel_size=16;
}
set_panel_size(_panel_size);
const versionlabel = document.getElementById("version");
if (versionlabel.innerHTML = "&nbsp;") {
versionlabel.innerHTML = "AqualinkD "+data.aqualinkd_version+"<br>"+data.panel_type+" "+data.version;
}
}
function update_device(data) {
for (var obj in data['devices']) {
}
}
function set_labels(data) {
for (var obj in data) {
if ((button = document.getElementById(obj + "_button")) != null && data[obj] != "NONE") {
button.value = data[obj];
}
}
}
function update_status_message(message=null, error=false) {
const status = document.getElementById("status");
if (message != null) {
status.innerHTML = message;
} else {
status.innerHTML = "AqualinkD All Button Simulator";
}
if (error) {
status.classList.add("error");
} else {
status.classList.remove("error");
}
}
function packet_to_ascii(packet) {
var msg="";
for (i=5; i <= 20; i++) {
if (packet[i] >= 31 && packet[i] <= 127) {
msg=msg+String.fromCharCode(packet[i]);
//console.log("char ("+packet[i]+") = "+msg);
} else if (packet[i] == 223) {
msg=msg+"&deg";
} else if (packet[i] == 0) {
break; // End on a nul
} else {
console.log("Bad char in string '"+msg+"' next ("+packet[i]+") from ["+packet+"]" );
}
}
return msg;
}
function lcd_display(line, message) {
const message_lcd = document.getElementById("lcd");
const message_lcd1 = document.getElementById("lcd_2");
const message_lcd2 = document.getElementById("lcd_3");
if (message == "" || message == " "){message = "&nbsp;";}
message_lcd2.innerHTML = message_lcd1.innerHTML;
message_lcd1.innerHTML = message_lcd.innerHTML;
message_lcd.innerHTML = message;
}
function lcd_display_append(line, message) {
const message_lcd = document.getElementById("lcd");
message_lcd.innerHTML = message_lcd.innerHTML+message;
}
const PKT_CMD = 3;
const PKT_DATA1 = 4;
const CMD_PROBE = 0;
const CMD_STATUS = 2
const CMD_MSG = 3 // message
const CMD_MSG_LONG = 4
const CMD_MSG_LOOP_ST = 8 // pda highlight
function process_packet(data) {
if (data.simtype != "allbutton") {
update_status_message("Wrong message - is Another simulator running?", true);
return;
}
switch(data.dec[PKT_CMD]) {
case CMD_PROBE:
// We are about to start
break;
case CMD_MSG: // Message
//console.log("Received Line "+data.dec[PKT_DATA1]+" = "+packet_to_ascii(data.dec));
lcd_display(data.dec[PKT_DATA1], packet_to_ascii(data.dec));
break;
case CMD_MSG_LONG: // Message
//console.log("Received Line long "+data.dec[PKT_DATA1]+" = "+packet_to_ascii(data.dec));
lcd_display_append(data.dec[PKT_DATA1], packet_to_ascii(data.dec));
break;
case CMD_MSG_LOOP_ST:
case CMD_STATUS:
// Nothing to do for these messages
break;
default:
console.log("Received unknown command "+data.dec[PKT_CMD]+" "+tohex(data.dec[PKT_CMD]));
break;
}
}
function get_appropriate_ws_url() {
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) == "https") {
pcol = "wss://";
u = u.substr(8);
} else {
pcol = "ws://";
if (u.substring(0, 4) == "http")
u = u.substr(7);
}
u = u.split('/');
//alert (pcol + u[0] + ":6500");
return pcol + u[0];
}
/* dumb increment protocol */
var socket_di;
function startWebsockets() {
socket_di = new WebSocket(get_appropriate_ws_url());
_keep_socket_alive = true;
try {
socket_di.onopen = function () {
// success!
start_simulator();
get_devices();
}
socket_di.onmessage = function got_packet(msg) {
//document.getElementById("status").classList.remove("error");
update_status_message();
var data = JSON.parse(msg.data);
if (data.type == 'simpacket') {
process_packet(data);
} else if (data.type == 'status') {
update_status(data);
} else if (data.type == 'devices') {
//update_status(data);
update_device(data);
} else if (data.type == 'aux_labels') {
set_labels(data);
}
}
socket_di.onclose = function () {
// something went wrong
//document.getElementById("status").innerHTML = ' !!! Connection error !!! '
//document.getElementById("status").classList.add("error");
update_status_message(' !!! Connection error !!! ', true);
// Try to reconnect every 5 seconds.
setTimeout(function () {
if (_keep_socket_alive == true) {
startWebsockets();
}
}, 5000);
}
} catch (exception) {
alert('<p>Error' + exception);
}
}
function stopWebsockets() {
if (socket_di) {
socket_di.close();
}
_keep_socket_alive = false;
}
function start_simulator() {
var msg = {
//command: "simulator"
uri: "simulator/allbutton"
};
document.getElementById("lcd").innerHTML = "Connecting to panel, please wait!"
socket_di.send(JSON.stringify(msg));
}
function get_devices() {
var msg = {
uri: "devices"
};
socket_di.send(JSON.stringify(msg));
}
function send_command(cmd) {
socket_di.send(JSON.stringify(cmd));
}
/*
function reset() {
socket_di.send("reset\n");
}
*/
function init() {
if ( capture_iframe_focus() ) {
// In iframe wait for focus event to start
} else {
//update_status_message();
startWebsockets();
}
}
</script>
<body onload="init();">
<div class="wrapper">
<table border='0' id="deviceList">
<tr style="title">
<td style="title" colspan="12" align="center"><label id="status">&nbsp;&nbsp;AqualinkD All Button Simulator&nbsp;&nbsp;</label></th>
</tr>
<tr>
<th colspan="12" align="center" class="lcd_history"><label id="lcd_3">&nbsp;</label></th>
</tr>
<tr>
<th colspan="12" align="center" class="lcd_history"><label id="lcd_2">&nbsp;</label></th>
</tr>
<tr>
<th colspan="12" class="lcd_display"><label id="lcd">&nbsp;</label></th>
</tr>
<tr>
<td class="td_led">
<div id="Filter_Pump_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Filter_Pump_button" type="button" onclick="send(this);" value="Filter Pump">
</td>
<td class="td_led">
<div id="Spa_Mode_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Spa_Mode_button" type="button" onclick="send(this);" value="Spa Mode">
</td>
<td class="td_led">
<div id="Aux_1_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_1_button" type="button" onclick="send(this);" value="AUX1">
</td>
<td class="td_led">
<div id="Aux_2_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_2_button" type="button" onclick="send(this);" value="AUX2">
</td>
<td class="td_led">
<div id="Aux_3_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_3_button" type="button" onclick="send(this);" value="AUX3">
</td>
<td class="td_led">
<div id="Aux_4_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_4_button" type="button" onclick="send(this);" value="AUX4">
</td>
</tr>
<tr>
<td class="td_led">
<div id="Aux_5_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_5_button" type="button" onclick="send(this);" value="AUX5">
</td>
<td class="td_led">
<div id="Aux_6_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_6_button" type="button" onclick="send(this);" value="AUX6">
</td>
<td class="td_led">
<div id="Aux_7_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_7_button" type="button" onclick="send(this);" value="AUX7">
</td>
<td class="td_led">
<div id="Aux_B1_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_B1_button" type="button" onclick="send(this);" value="AUXB1">
</td>
<td class="td_led">
<div id="Aux_B2_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_B2_button" type="button" onclick="send(this);" value="AUXB2">
</td>
<td class="td_led">
<div id="Aux_B3_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_B3_button" type="button" onclick="send(this);" value="AUXB3">
</td>
</tr>
<tr>
<td class="td_led">
<div id="Aux_B4_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Aux_B4_button" type="button" onclick="send(this);" value="AUXB4">
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
<input id="Aux_B5_button" type="button" onclick="send(this);" value="AUXB5">
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
<input id="Aux_B6_button" type="button" onclick="send(this);" value="AUXB6">
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
<input id="Aux_B7_button" type="button" onclick="send(this);" value="AUXB7">
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
<input id="Aux_B8_button" type="button" onclick="send(this);" value="AUXB8">
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
&nbsp;
</td>
</tr>
<tr>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
&nbsp;
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
&nbsp;
</td>
<td class="td_led">
&nbsp;
</td>
<td class="td_button">
&nbsp;
</td>
<td class="td_led">
<div id="Pool_Heater_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Pool_Heater_button" type="button" onclick="send(this);" value="Pool Heater">
</td>
<td class="td_led">
<div id="Spa_Heater_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Spa_Heater_button" type="button" onclick="send(this);" value="Spa Heater">
</td>
<td class="td_led">
<div id="Solar_Heater_led" class="led off"></div>
</td>
<td class="td_button">
<input id="Solar_Heater_button" type="button" onclick="send(this);" value="Solar Heater">
</td>
</tr>
<tr>
<TD>&nbsp;</TD>
</tr>
<tr>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_menu" value="Menu">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_cancel" value="Cancel">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_back" value="Back <">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_forward" value="Forward >">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_enter" value="Enter *">
</td>
</tr>
<tr>
<td colspan="12" align="center"><label id="messages">&nbsp;</label></td>
</tr>
<tr>
<td colspan="12" id="version" class="version">&nbsp;</td>
</tr>
</table>
</div>
</body>
</html>

View File

@ -485,6 +485,7 @@
*/
if (data['deamonized'] == 'off') {
console.log("deamonized=" + data['deamonized'] + " Need to rename Restart to Reload");
//disablebutton("restart");
}
/*

686
web/aquapda_sim.html Normal file
View File

@ -0,0 +1,686 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=windows-1252'>
<title>AqualinkD Simulator</title>
<meta name='viewport' content='width=device-width'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<link href="aqualinkd.png" rel="apple-touch-icon">
<link href="aqualinkd.png" rel="icon">
<style>
html {}
body {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-weight: 300;
background-color: white;
color: #000000;
margin: 0 !important;
padding: 0 !important;
display: flex;
flex-direction: column;
}
.inner {
display: inline;
}
.wrapper {
display: flex;
justify-content: center;
width: 100%;
align-items: center;
/*position: absolute;
justify-content: center;
width: 100%;
height: 100%;*/
}
table {
background-color: rgb(221, 221, 221);
border-collapse: collapse;
}
th {
/*
background-color: white;
border-spacing: 10px;*/
padding-top: 0px;
padding-bottom: 0px;
padding-left: 0px;
padding-right: 0px;
}
td {
/*
text-align: center;
vertical-align: middle;
*/
/*padding: 10px;*/
padding-top: 3px;
padding-bottom: 3px;
padding-left: 0px;
padding-right: 0px;
}
#title {
background-color: rgb(200, 200, 200);
font-weight: 600;
}
input[type=button],
input[type=submit],
input[type=reset] {
background-color: rgb(165, 165, 165);
border: none;
color: rgb(0, 0, 0);
padding: 2px 2px;
text-decoration: none;
margin: 0px 0px;
min-width: 70px;
border-radius: 70px;
}
.button_s {
min-width: 40px !important;
border-radius: 40px !important;
}
.led {
border-radius: calc(var(--tile_icon-height) / 2);
border-radius: 20px;
height: 20px;
width: 20px;
text-align: center;
vertical-align: middle;
filter: alpha(opacity=100);
opacity: 1.0;
}
.on {
background-color: rgb(255, 0, 0);
}
.off {
background-color: rgb(116, 116, 116);
}
.error {
background-color: rgb(255, 0, 0) !important;
font-weight: 600;
}
.hidden {
display: none;
}
.lcd_display {
font-family: monospace;
background-color: #2b2b2b;
color: white;
white-space: pre;
}
.lcd_highlight {
font-family: monospace;
background-color: #8e1e1e !important;
color: white !important;
white-space: pre;
}
.version {
/*font-family: monospace;*/
font-size: x-small;
}
.hide {
display: none;
/*
filter: alpha(opacity=0);
opacity: 0.0;
*/
}
</style>
<script type='text/javascript'>
var _socket_di;
var _keep_socket_alive=true;
const PKT_CMD = 3;
const PKT_DATA1 = 4;
const PKT_DATA2 = 5;
const PKT_DATA3 = 6;
const CMD_PROBE = 0;
const CMD_MSG_LONG = 4;
const CMD_PDA_CLEAR = 9;
const CMD_PDA_HIGHLIGHT = 8;
const CMD_PDA_HIGHLIGHTCHARS = 16;
const CMD_PDA_SHIFTLINES = 15;
const PDA_LINES = 9; // 0 to 9
var _hlightcharlineno = -1;
var _hlightcharline = "";
var _hlightcharstart = -1;
var _hlightcharend = -1;
var _initial_click=false;
// See if we are in an iframe and capture focus
function capture_iframe_focus() {
var targetNode = parent.document.getElementById('aquapda_iframe');
if (targetNode != null) {
var observer = new MutationObserver(function(){
if(targetNode.style.display != 'none'){
console.log("AquaPDA sim Displaying");
startWebsockets();
} else {
console.log("AquaPDA sim Hiding");
stopWebsockets();
}
});
observer.observe(targetNode, { attributes: true, childList: true });
return true;
}
return false;
}
function send(source) {
console.log("from" + source.id);
var cmd = {};
cmd.uri = "simcmd"
switch (source.id) {
case "Up":
cmd.value = "0x06";
break;
case "Down":
cmd.value = "0x05";
break;
case "Select":
cmd.value = "0x04";
break;
case "Page_up":
cmd.value = "0x03";
break;
case "Back":
cmd.value = "0x02";
break;
case "Page_down":
cmd.value = "0x01";
break;
default:
alert("Unknown button");
return;
break;
}
cmd.value = todec(cmd.value);
send_command(cmd);
}
function tohex(value) {
var rtn = parseInt(value).toString(16);
if (rtn.length <= 1)
return "0x0" + rtn;
else
return "0x" + rtn;
}
function todec(value) {
let number = parseInt(value, 16);
return number;
}
function lcd_clear() {
//var container = document.getElementById("lcd_display");
for (i = 0; i <= PDA_LINES; i++) {
//container.lastElementChild.remove();
//child = container.children[i];
//console.log("Line "+i+" replacing "+child.innerHTML)
//child.innerHTML = "&nbsp;";
var line = document.getElementById("lcd-l" + i);
//console.log("Line "+i+" replacing "+line.innerHTML)
line.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
line.classList.remove("lcd_highlight");
line.classList.add("lcd_display");
}
lcd_clear_highlight_chrs(false);
}
function lcd_display(lineno, message) {
// We get lines 0,2,3,4,5,6.... no line 1. (so using 0 as 1)
if (lineno == 0) { lineno = 1; }
// Line 64 is time,
if (lineno == 64) { lineno = 0; }
// line 130 is pool air temp
if (lineno == 130) { lineno = 2; }
var line = document.getElementById("lcd-l" + lineno);
//console.log("Line "+lineno+" replacing '"+line.innerHTML+"' with '"+message+"'");
line.innerHTML = message;
// See if we need to re-highlight any chrs
check_rehighlight_chrs(lineno);
}
function lcd_display_shiftlines(first, last, direction) {
if (direction == 255) {
for (i = first; i <= last; i++) {
var line1 = document.getElementById("lcd-l" + i);
var line2 = document.getElementById("lcd-l" + (i + 1));
line1.innerHTML = line2.innerHTML;
}
} else if (direction == 1) {
for (i = last; i >= first; i--) {
var line1 = document.getElementById("lcd-l" + i);
var line2 = document.getElementById("lcd-l" + (i - 1) );
line1.innerHTML = line2.innerHTML;
}
}
}
function lcd_display_highlight(lineno) {
// We get lines 0,2,3,4,5,6.... no line 1. (so using 0 as 1)
if (lineno == 0) { lineno = 1; }
for (i = 0; i <= PDA_LINES; i++) {
var line = document.getElementById("lcd-l" + i);
if (i != lineno) {
line.classList.remove("lcd_highlight");
line.classList.add("lcd_display");
//console.log("remove highlight to line #"+i+" "+line.innerHTML)
} else {
line.classList.add("lcd_highlight");
line.classList.remove("lcd_display");
//console.log("add highlight to line #"+i+" "+line.innerHTML)
}
}
//line.classList.add("lcd_highlight");
//line.classList.remove("lcd_display");
}
function check_rehighlight_chrs(lineno) {
if (_hlightcharlineno == lineno && _hlightcharstart >= 0 && _hlightcharend > 0) {
// We would have received a new line text, so don't preserve old text
lcd_display_highlight_chrs(_hlightcharlineno, _hlightcharstart, _hlightcharend, false);
}
}
function lcd_clear_highlight_chrs(preserve = true) {
// if preserve, then we put back the old text.
if (preserve && _hlightcharlineno >= 0 && _hlightcharline != "") {
var line = document.getElementById("lcd-l" + _hlightcharlineno);
line.innerHTML = _hlightcharline;
}
_hlightcharlineno = -1;
_hlightcharline = "";
_hlightcharstart = -1;
_hlightcharend = -1;
}
function lcd_display_highlight_chrs(lineno, start, end, preserve = true) {
lcd_clear_highlight_chrs(preserve);
var line = document.getElementById("lcd-l" + lineno);
text = line.innerHTML;
var newtxt = text.slice(0, end + 1) + "</span>" + text.slice(end + 1);
var newtxt = newtxt.slice(0, start) + "<span class=\"lcd_highlight\">" + newtxt.slice(start);
line.innerHTML = newtxt;
_hlightcharlineno = lineno;
_hlightcharline = text;
_hlightcharstart = start;
_hlightcharend = end;
console.log("Line '" + text + "' now '" + newtxt + "'");
}
function packet_to_ascii(packet) {
var msg = "";
for (i = 5; i <= 20; i++) {
if (packet[i] >= 31 && packet[i] <= 127) {
msg = msg + String.fromCharCode(packet[i]);
} else if (packet[i] == 223) {
msg = msg + "&deg";
} else if (packet[i] == 0) {
break; // End on a nul
} else {
console.log("Bad char in string '" + msg + "' next (" + packet[i] + ")");
}
}
return msg;
}
function process_packet(data) {
if (data.simtype != "aquapda") {
update_status_message("Another simulator running", true);
return;
}
switch (data.dec[PKT_CMD]) {
case CMD_PROBE:
// We are about to start
break;
case CMD_PDA_CLEAR: // Clear
//console.log("Received Clear");
lcd_clear();
_shift_lines = false;
break;
case CMD_MSG_LONG: // Message
//console.log("Received Line " + data.dec[PKT_DATA1] + " = " + packet_to_ascii(data.dec));
lcd_display(data.dec[PKT_DATA1], packet_to_ascii(data.dec));
break;
case CMD_PDA_HIGHLIGHT:
//console.log("Received Highlight " + data.dec[PKT_DATA1]);
// We sometimes get 255 for line number, need to find out why
lcd_display_highlight(data.dec[PKT_DATA1]);
break;
case CMD_PDA_HIGHLIGHTCHARS:
//console.log("Received Highlight chars, Line=" + data.dec[PKT_DATA1] + ", Start=" + data.dec[PKT_DATA2] + ", Stop=" + data.dec[PKT_DATA3]);
lcd_display_highlight_chrs(data.dec[PKT_DATA1], data.dec[PKT_DATA2], data.dec[PKT_DATA3]);
break;
case CMD_PDA_SHIFTLINES:
//console.log("Received shift lines, Start=" + data.dec[PKT_DATA1] + ", End=" + data.dec[PKT_DATA2] + ", Direction=" + data.dec[PKT_DATA3]);
lcd_display_shiftlines(data.dec[PKT_DATA1], data.dec[PKT_DATA2], data.dec[PKT_DATA3]);
break;
default:
// 2 is READY for commands
console.log("Received unknown command " + data.dec[PKT_CMD] + " " + tohex(data.dec[PKT_CMD]));
break;
}
}
function update_status_message(message = null, error = false) {
const status = document.getElementById("status");
if (message != null) {
status.innerHTML = message;
} else {
status.innerHTML = "AqualinkD PDA Simulator";
}
if (error) {
status.classList.add("error");
} else {
status.classList.remove("error");
}
}
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
// If we are a PDA only panel, we are already connected, send the back command to get some PDA clar
// message to reset the screen.
if (_initial_click == false) {
document.getElementById("pdaWarning").style.display = 'block';
document.getElementById("Back").click();
_initial_click = true;
}
}
const versionlabel = document.getElementById("version");
if (versionlabel.innerHTML = "&nbsp;") {
versionlabel.innerHTML = "AqualinkD " + data.aqualinkd_version + "<br>" + data.panel_type + " " + data.version;
}
}
function get_appropriate_ws_url() {
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) == "https") {
pcol = "wss://";
u = u.substr(8);
} else {
pcol = "ws://";
if (u.substring(0, 4) == "http")
u = u.substr(7);
}
u = u.split('/');
//alert (pcol + u[0] + ":6500");
return pcol + u[0];
}
/* dumb increment protocol */
function startWebsockets() {
_socket_di = new WebSocket(get_appropriate_ws_url());
_keep_socket_alive = true;
try {
_socket_di.onopen = function () {
// success!
start_simulator();
//get_devices();
}
_socket_di.onmessage = function got_packet(msg) {
update_status_message();
var data = JSON.parse(msg.data);
if (data.type == 'simpacket') {
process_packet(data);
} else if (data.type == 'status') {
update_status(data);
}
}
_socket_di.onclose = function () {
// something went wrong
//document.getElementById("status").innerHTML = ' !!! Connection error !!! '
//document.getElementById("status").classList.add("error");
update_status_message("!!! Connection error !!!", true);
// Try to reconnect every 5 seconds.
setTimeout(function () {
if (_keep_socket_alive == true) {
startWebsockets();
}
}, 5000);
}
} catch (exception) {
alert('<p>Error' + exception);
}
}
function stopWebsockets() {
if (_socket_di) {
_socket_di.close();
}
_keep_socket_alive = false;
}
function start_simulator() {
var msg = {
//command: "simulator"
uri: "simulator/aquapda"
};
lcd_clear();
document.getElementById("lcd-l2").innerHTML = " Connecting ";
document.getElementById("lcd-l3").innerHTML = " Please wait! ";
_socket_di.send(JSON.stringify(msg));
}
function get_devices() {
var msg = {
uri: "devices"
};
_socket_di.send(JSON.stringify(msg));
}
function send_command(cmd) {
_socket_di.send(JSON.stringify(cmd));
}
/*
function reset() {
_socket_di.send("reset\n");
}
*/
function init() {
if ( capture_iframe_focus() ) {
// In iframe wait for focus event to start
} else {
update_status_message();
startWebsockets();
}
}
</script>
<body onload="init();">
<div class="wrapper">
<div class="inner">
<div>
<table border='0' id="deviceList">
<tr>
<th id="status" align="center" colspan="5">&nbsp;</th>
</tr>
<tr>
<td></td>
<td colspan="3" align="center">
<table border='0'>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l0">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l1">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l2"></td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l3">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l4">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l5">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l6">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l7">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l8">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l9">&nbsp;</td>
<td></td>
</tr>
<!--
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l10">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l11">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td colspan="3" class="lcd_display" id="lcd-l12">&nbsp;</td>
<td></td>
</tr>
-->
</table>
</td>
<td></td>
</tr>
<tr>
<td></td>
<td align="center"><input class="button_s" id="Back" type="button" onclick="send(this);"value="&#8617;"></td>
<td align="center"><input class="button_s" id="Up" type="button" onclick="send(this);"value="&uarr;"></td>
<!--<td align="center"><input class="button_s" id="Power" type="button" onclick="send(this);"value="&#8635;"></td>-->
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td align="center" colspan="3"><input id="Select" type="button" onclick="send(this);"
value="Select"></td>
<td></td>
</tr>
<tr>
<td></td>
<td align="center"><input class="button_s" id="Page_up" type="button" onclick="send(this);" value="1"></td>
<td align="center"><input class="button_s" id="Down" type="button" onclick="send(this);"value="&darr;"></td>
<td align="center"><input class="button_s" id="Page_down" type="button" onclick="send(this);" value="2">
</td>
<td></td>
</tr>
<tr>
<td colspan="5" id="version" class="version">&nbsp;</td>
</tr>
</table>
</div>
<!--<div class="inner">END</div>-->
<div id="pdaWarning" class='hide'>
Warning<br>AqualinkD will not respond to commands while this window is open,<br>Please close when finished.
</div>
</div>
</div>
</body>
</html>

View File

@ -141,6 +141,27 @@
/*flex-direction: column;*/
}
.simulator_pane {
background-color: var(--options_pane_background);
border: 2px solid var(--options_pane_bordercolor);
border-radius: 20px;
justify-content: center;
align-items: center;
/* Actual size of sim 305 by 385 */
width: 325px;
height: 405px;
padding: 10px;
/*overflow-y: scroll; didn't work*/
/*height: 100%;*/
/*flex-basis: content;*/
/*flex-direction: column;*/
}
.simiframe {
width: 305px;
height: 385px;
}
.options {
top: 10px;
/*left: 10px;*/
@ -609,6 +630,8 @@
document.getElementById('vspswitch_options').classList.remove("hide");
document.getElementById('timer_options').classList.remove("hide");
document.getElementById('scheduler_options').classList.remove("hide");
//document.getElementById('simulator_options').classList.remove("hide");
document.getElementById('simulator_iframe').classList.remove("hide");
setColors();
load_background();
showTileOptions(false);
@ -1233,6 +1256,7 @@
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'switch_program') {
active_option = document.getElementById('pswitch_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1240,6 +1264,7 @@
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'setpoint_swg') {
active_option = document.getElementById('swg_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1247,6 +1272,7 @@
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'setpoint_freeze') {
active_option = document.getElementById('swg_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1254,6 +1280,7 @@
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'switch_vsp') {
active_option = document.getElementById('vspswitch_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1261,6 +1288,7 @@
document.getElementById('swg_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'switch_timer') {
active_option = document.getElementById('timer_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1268,6 +1296,7 @@
document.getElementById('swg_options').style.display = 'none';
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'scheduler') {
active_option = document.getElementById('scheduler_options');
document.getElementById('thermostat_options').style.display = 'none';
@ -1275,6 +1304,15 @@
document.getElementById('swg_options').style.display = 'none';
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
} else if (id != null && document.getElementById(id).getAttribute('type') == 'simulator') {
active_option = document.getElementById('simulator_iframe');
document.getElementById('thermostat_options').style.display = 'none';
document.getElementById('pswitch_options').style.display = 'none';
document.getElementById('swg_options').style.display = 'none';
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
}
active_option.style.display = 'flex';
@ -1310,6 +1348,7 @@
document.getElementById('vspswitch_options').style.display = 'none';
document.getElementById('timer_options').style.display = 'none';
document.getElementById('scheduler_options').style.display = 'none';
document.getElementById('simulator_iframe').style.display = 'none';
document.getElementById('wrapper').classList.remove("opaque");
return;
}
@ -1363,6 +1402,8 @@
} else if (type == 'scheduler') {
title = document.getElementById("scheduler_options_title");
close_button = document.getElementById("scheduler_options_close");
} else if (type == 'simulator') {
close_button = document.getElementById("simulator_iframe_close");
} else {
slider = document.getElementById("option_slider_range");
slider_output = document.getElementById("option_slider_text_value");
@ -1415,6 +1456,8 @@
if (type == 'scheduler') {
title.innerHTML = "Scheduler";
} else if (type == 'simulator') {
// title.innerHTML = "Simulator";
} else {
title.innerHTML = document.getElementById(id + '_name').innerHTML;
}
@ -1480,6 +1523,8 @@
cs_clearSchedules();
//cs_loadJSON("/api/schedules", cs_schedules,'jsonp');
get_schedules();
} else if (type == 'simulator') {
startSimulator();
} else {
slider.value = sp_value;
oswitch = document.getElementById("option_switch");
@ -1597,6 +1642,7 @@
if ( ! cs_createJSON() ) {
return // This stops pane from closing.
}
} else if (type == 'simulator') {
} else {
var value = slider.value;
if (state == (tile.getAttribute('status') == 'off'))
@ -1673,6 +1719,16 @@
}
}
function showSimulator(caller){
caller.setAttribute('type', 'simulator');
showTileOptions(true, caller.id);
}
function startSimulator() {
console.log("Starting ifram sim init");
document.getElementById('onetouch_iframe').display = 'block';
}
// NSF CHANGE TO USING WS FOR THIS REQUEST
/*
function cs_loadJSON(path, success, error) {
@ -2418,6 +2474,7 @@
<!-- <div id='body_wrap' class='body_wrap' onmousedown='showTileOptions(false);'> -->
<div id='wrapper' class="wrapper">
<div id="header" class="head" onclick="showBackground();">
<!--<div id="header" class="head" onclick="event.stopPropagation();showSimulator(this);">-->
<table border='0' width='100%' cellpadding='0' cellspacing='0'>
<tr>
<td width="70px" align='left' id='td_name'><span class='title' id='name'
@ -2426,7 +2483,7 @@
<td align='center'><span class='title' id='message'></span>
</td>
<td width="70px" align='right' id='td_timedate'><span class='timedate' id='datetime'
onclick="event.stopPropagation();showScheduler(this);"></class>
onclick="event.stopPropagation();showScheduler(this);">X</class>
</td>
</tr>
</table>
@ -2650,6 +2707,14 @@
</div>
<div id="simulator_iframe" class='options hide'>
<div id="onetouch_iframe" class="simulator_pane" onclick='event.stopPropagation();''>
<iframe id="simulatoriframe" src="/onetouch_sim.html" class="simiframe"></iframe>
<input type="button" class="options_button" id="simulator_iframe_close" value="Close">
</div>
</div>
<div id='scheduler_options' class='options hide'>
<div id='scheduler_options_pane' class='scheduler_pane' onclick='event.stopPropagation();'>
<table id="cronschedules" border="0">
@ -2684,6 +2749,8 @@
</div>
</div>
</div>
</body>

651
web/onetouch_sim.html Normal file
View File

@ -0,0 +1,651 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=windows-1252'>
<title>AqualinkD Simulator</title>
<meta name='viewport' content='width=device-width'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<link href="aqualinkd.png" rel="apple-touch-icon">
<link href="aqualinkd.png" rel="icon">
<style>
html {}
body {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-weight: 300;
background-color: white;
color: #000000;
margin: 0 !important;
padding: 0 !important;
display:flex;
flex-direction:column;
}
.wrapper {
display: flex;
justify-content: center;
width: 100%;
/*position: absolute;
justify-content: center;
width: 100%;
height: 100%;*/
}
table {
background-color: rgb(221, 221, 221);
border-collapse: collapse;
}
th {
/*
background-color: white;
border-spacing: 10px;*/
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
}
td {
/*
text-align: center;
vertical-align: middle;
*/
/*padding: 10px;*/
padding-top: 2px;
padding-bottom: 2px;
padding-left: 10px;
padding-right: 10px;
}
#title {
background-color: rgb(200, 200, 200);
font-weight: 600;
}
input[type=button],
input[type=submit],
input[type=reset] {
background-color: rgb(165, 165, 165);
border: none;
color: rgb(0, 0, 0);
padding: 2px 2px;
text-decoration: none;
margin: 0px 0px;
min-width: 60px;
border-radius: 60px;
}
.led {
border-radius: calc(var(--tile_icon-height) / 2);
border-radius: 20px;
height: 20px;
width: 20px;
text-align: center;
vertical-align: middle;
filter: alpha(opacity=100);
opacity: 1.0;
}
.on {
background-color: rgb(255, 0, 0);
}
.off {
background-color: rgb(116, 116, 116);
}
.error {
background-color: rgb(255, 0, 0) !important;
font-weight: 600;
}
.hidden {
display: none;
}
.lcd_display {
font-family: monospace;
background-color: #2b2b2b;
color: white;
white-space: pre;
}
.lcd_highlight {
font-family: monospace;
background-color: #8e1e1e !important;
/*background-color: #8b7d7d !important;*/
/*background-color: #868383 !important;*/
color: white !important;
white-space: pre;
}
.version {
/*font-family: monospace;*/
font-size: x-small;
}
</style>
<script type='text/javascript'>
console.log("SIM LOAD");
var _socket_di;
var _keep_socket_alive=true;
const PKT_CMD = 3;
const PKT_DATA1 = 4;
const PKT_DATA2 = 5;
const PKT_DATA3 = 6;
const CMD_PROBE = 0;
const CMD_MSG_LONG = 4;
const CMD_PDA_CLEAR = 9;
const CMD_PDA_HIGHLIGHT = 8;
const CMD_PDA_HIGHLIGHTCHARS = 16;
const CMD_PDA_SHIFTLINES = 15;
const ONET_LINES = 11; // 0 to 11
var _hlightcharlineno = -1;
var _hlightcharline = "";
var _hlightcharstart = -1;
var _hlightcharend = -1;
// See if we are in an iframe and capture focus
function capture_iframe_focus() {
var targetNode = parent.document.getElementById('onetouch_iframe');
/*
if (targetNode == null)
targetNode = parent.document.getElementById('simulator_iframe');*/
console.log("OneTouch checking if iframe");
if (targetNode != null) {
var observer = new MutationObserver(function(){
if(targetNode.style.display != 'none'){
console.log("OneTouch sim Displaying");
startWebsockets();
} else {
console.log("OneTouch sim Hiding");
stopWebsockets();
}
});
observer.observe(targetNode, { attributes: true, childList: true });
console.log("OneTouch in iframe");
return true;
}
console.log("OneTouch no iframe");
return false;
}
function send(source) {
console.log("from" + source.id);
var cmd = {};
cmd.uri = "simcmd"
switch (source.id) {
case "Up":
cmd.value = "0x06";
break;
case "Down":
cmd.value = "0x05";
break;
case "Select":
cmd.value = "0x04";
break;
case "Page_up":
cmd.value = "0x03";
break;
case "Back":
cmd.value = "0x02";
break;
case "Page_down":
cmd.value = "0x01";
break;
default:
alert("Unknown button");
return;
break;
}
cmd.value = todec(cmd.value);
send_command(cmd);
}
function tohex(value) {
var rtn = parseInt(value).toString(16);
if (rtn.length <= 1)
return "0x0"+rtn;
else
return "0x"+rtn;
}
function todec(value) {
let number = parseInt(value, 16);
return number;
}
function lcd_clear() {
//var container = document.getElementById("lcd_display");
for (i=0; i <= ONET_LINES; i++) {
//container.lastElementChild.remove();
//child = container.children[i];
//console.log("Line "+i+" replacing "+child.innerHTML)
//child.innerHTML = "&nbsp;";
var line = document.getElementById("lcd-l"+i);
//console.log("Line "+i+" replacing "+line.innerHTML)
line.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
line.classList.remove("lcd_highlight");
line.classList.add("lcd_display");
}
lcd_clear_highlight_chrs(false);
}
function lcd_display(lineno, message) {
// We get lines 0,2,3,4,5,6.... no line 1. (so using 0 as 1)
//if (lineno == 0){lineno=1;}
var line = document.getElementById("lcd-l"+lineno);
//console.log("Line "+lineno+" replacing '"+line.innerHTML+"' with '"+message+"'");
line.innerHTML = message;
// See if we need to re-highlight any chrs
check_rehighlight_chrs(lineno);
}
function lcd_display_shiftlines(first, last, direction) {
// Add 1 to first & last we get 0 to 10 not 1 to 11
if (direction == 255) {
for (i = first; i <= last; i++) {
var line1 = document.getElementById("lcd-l" + i);
var line2 = document.getElementById("lcd-l" + (i + 1));
line1.innerHTML = line2.innerHTML;
}
} else if (direction == 1) {
for (i = last; i >= first; i--) {
var line1 = document.getElementById("lcd-l" + i);
var line2 = document.getElementById("lcd-l" + (i - 1) );
line1.innerHTML = line2.innerHTML;
}
}
}
function lcd_display_highlight(lineno) {
// We get lines 0,2,3,4,5,6.... no line 1. (so using 0 as 1)
//if (lineno == 0){lineno=1;}
for (i=0; i <= ONET_LINES; i++) {
var line = document.getElementById("lcd-l"+i);
if (i != lineno) {
line.classList.remove("lcd_highlight");
line.classList.add("lcd_display");
//console.log("remove highlight to line #"+i+" "+line.innerHTML)
} else {
line.classList.add("lcd_highlight");
line.classList.remove("lcd_display");
//console.log("add highlight to line #"+i+" "+line.innerHTML)
}
}
//line.classList.add("lcd_highlight");
//line.classList.remove("lcd_display");
}
function check_rehighlight_chrs(lineno) {
if (_hlightcharlineno == lineno && _hlightcharstart >= 0 && _hlightcharend > 0 ) {
// We would have received a new line text, so don't preserve old text
lcd_display_highlight_chrs(_hlightcharlineno, _hlightcharstart, _hlightcharend, false);
}
}
function lcd_clear_highlight_chrs(preserve=true){
// if preserve, then we put back the old text.
if (preserve && _hlightcharlineno >= 0 && _hlightcharline != "") {
var line = document.getElementById("lcd-l"+_hlightcharlineno);
line.innerHTML = _hlightcharline;
}
_hlightcharlineno = -1;
_hlightcharline = "";
_hlightcharstart = -1;
_hlightcharend = -1;
}
function lcd_display_highlight_chrs(lineno, start, end, preserve=true) {
lcd_clear_highlight_chrs(preserve);
var line = document.getElementById("lcd-l"+lineno);
text = line.innerHTML;
var newtxt = text.slice(0, end+1) + "</span>" + text.slice(end+1);
var newtxt = newtxt.slice(0, start) + "<span class=\"lcd_highlight\">" + newtxt.slice(start);
line.innerHTML = newtxt;
_hlightcharlineno = lineno;
_hlightcharline = text;
_hlightcharstart = start;
_hlightcharend = end;
console.log("Line '"+text+"' now '"+newtxt+"'");
}
function packet_to_ascii(packet) {
var msg="";
for (i=5; i <= 20; i++) {
if (packet[i] >= 31 && packet[i] <= 127) {
msg=msg+String.fromCharCode(packet[i]);
} else if (packet[i] == 223) {
msg=msg+"&deg";
} else if (packet[i] == 0) {
break; // End on a nul
} else {
console.log("Bad char in string '"+msg+"' next ("+packet[i]+")" );
}
}
return msg;
}
function process_packet(data) {
if (data.simtype != "onetouch") {
update_status_message("! Another simulator running !", true);
return;
}
switch(data.dec[PKT_CMD]) {
case CMD_PROBE:
// We are about to start
document.getElementById("lcd-l2").innerHTML = " Connected ";
document.getElementById("lcd-l3").innerHTML = "";
break;
case CMD_PDA_CLEAR: // Clear
//console.log("Received Clear");
lcd_clear();
break;
case CMD_MSG_LONG: // Message
//console.log("Received Line "+data.dec[PKT_DATA1]+" = "+packet_to_ascii(data.dec));
lcd_display(data.dec[PKT_DATA1], packet_to_ascii(data.dec));
break;
case CMD_PDA_HIGHLIGHT:
//console.log("Received Highlight "+data.dec[PKT_DATA1]);
// We sometimes get 255 for line number, need to find out why
lcd_display_highlight(data.dec[PKT_DATA1]);
break;
case CMD_PDA_HIGHLIGHTCHARS:
//console.log("Received Highlight chars, Line="+data.dec[PKT_DATA1]+", Start="+data.dec[PKT_DATA2]+", Stop="+data.dec[PKT_DATA3]);
lcd_display_highlight_chrs(data.dec[PKT_DATA1], data.dec[PKT_DATA2], data.dec[PKT_DATA3]);
break;
case CMD_PDA_SHIFTLINES:
//console.log("Received shift lines, Start=" + data.dec[PKT_DATA1] + ", End=" + data.dec[PKT_DATA2] + ", Direction=" + data.dec[PKT_DATA3]);
lcd_display_shiftlines(data.dec[PKT_DATA1], data.dec[PKT_DATA2], data.dec[PKT_DATA3]);
break;
default:
// 2 is READY for commands
console.log("Received unknown command "+data.dec[PKT_CMD]+" "+tohex(data.dec[PKT_CMD]));
break;
}
}
function update_status_message(message=null, error=false) {
const status = document.getElementById("status");
if (message != null) {
status.innerHTML = message;
} else {
status.innerHTML = "AqualinkD Onetouch Simulator";
}
if (error) {
status.classList.add("error");
} else {
status.classList.remove("error");
}
}
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
//document.getElementById("status").innerHTML = ' !!! PDA only panels are not Supported !!! '
//document.getElementById("status").classList.add("error");
update_status_message("PDA only panels are not Supported", true);
}
const versionlabel = document.getElementById("version");
if (versionlabel.innerHTML = "&nbsp;") {
versionlabel.innerHTML = "AqualinkD "+data.aqualinkd_version+"<br>"+data.panel_type+" "+data.version;
}
}
function get_appropriate_ws_url() {
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) == "https") {
pcol = "wss://";
u = u.substr(8);
} else {
pcol = "ws://";
if (u.substring(0, 4) == "http")
u = u.substr(7);
}
u = u.split('/');
//alert (pcol + u[0] + ":6500");
return pcol + u[0];
}
/* dumb increment protocol */
function startWebsockets() {
_socket_di = new WebSocket(get_appropriate_ws_url());
_keep_socket_alive = true;
try {
_socket_di.onopen = function () {
// success!
start_simulator();
//get_devices();
}
_socket_di.onmessage = function got_packet(msg) {
update_status_message();
var data = JSON.parse(msg.data);
if (data.type == 'simpacket') {
process_packet(data);
} else if (data.type == 'status') {
update_status(data);
}
}
_socket_di.onclose = function () {
// something went wrong
//document.getElementById("status").innerHTML = ' !!! Connection error !!! '
//document.getElementById("status").classList.add("error");
update_status_message("!!! Connection error !!!", true);
// Try to reconnect every 5 seconds.
setTimeout(function () {
if (_keep_socket_alive == true) {
startWebsockets();
}
}, 5000);
}
} catch (exception) {
alert('<p>Error' + exception);
}
}
function stopWebsockets() {
if (_socket_di) {
_socket_di.close();
}
_keep_socket_alive = false;
}
function start_simulator() {
var msg = {
//command: "simulator"
uri: "simulator/onetouch"
};
lcd_clear();
document.getElementById("lcd-l2").innerHTML = " Connecting ";
document.getElementById("lcd-l3").innerHTML = " Please wait! ";
_socket_di.send(JSON.stringify(msg));
}
function get_devices() {
var msg = {
uri: "devices"
};
_socket_di.send(JSON.stringify(msg));
}
function send_command(cmd) {
_socket_di.send(JSON.stringify(cmd));
}
/*
function reset() {
_socket_di.send("reset\n");
}
*/
function init() {
if ( capture_iframe_focus() ) {
// In iframe wait for focus event to start
} else {
update_status_message();
startWebsockets();
}
}
// Print all events
/*
Object.keys(window).forEach(key => {
if (/^on/.test(key)) {
window.addEventListener(key.slice(2), event => {
console.log(event);
});
}
});
*/
</script>
<body onload="init();">
<div class="wrapper">
<div class="inner">
<table border='0' id="deviceList">
<tr>
<th id="status" align="center" colspan="3">&nbsp;</th>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l0">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l1"></td>
<td></td>
</tr>
<tr>
<td><input id="Page_up" type="button" onclick="send(this);" value="&uarr;&uarr;"></td>
<td class="lcd_display" id="lcd-l2"></td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l3">&nbsp;</td>
<td><input id="Up" type="button" onclick="send(this);" value="&uarr;"></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l4">&nbsp;</td>
<td></td>
</tr>
<tr>
<td><input id="Back" type="button" onclick="send(this);" value="&#8617;"></td>
<td class="lcd_display" id="lcd-l5">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l6">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l7">&nbsp;</td>
<td><input id="Down" type="button" onclick="send(this);" value="&darr;"></td>
</tr>
<tr>
<td><input id="Page_down" type="button" onclick="send(this);" value="&darr;&darr;"></td>
<td class="lcd_display" id="lcd-l8">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l9">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display" id="lcd-l10">&nbsp;</td>
<td></td>
</tr>
<tr>
<td></td>
<td class="lcd_display"id="lcd-l11">&nbsp;</td>
<td></td>
</tr>
<!--
<tr>
<td></td>
<td class="lcd_display" id="lcd-l12">&nbsp;</td>
<td></td>
</tr>
-->
<tr>
<td></td>
<th align="center"><input id="Select" type="button" onclick="send(this);" value="Select"></th>
<td></td>
</tr>
<tr>
<td colspan="3" id="version" class="version">&nbsp;</td>
</tr>
</table>
<!--<div class="inner">END</div>-->
</div>
</div>
</body>
</html>

View File

@ -10,590 +10,111 @@
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<link href="aqualinkd.png" rel="apple-touch-icon">
<link href="aqualinkd.png" rel="icon">
<style>
html {}
body {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-weight: 300;
background-color: white;
color: #000000;
margin: 0 !important;
padding: 0 !important;
/* Style the tab */
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.wrapper {
display: flex;
justify-content: center;
width: 100%;
/*position: absolute;
justify-content: center;
width: 100%;
height: 100%;*/
}
table {
background-color: rgb(221, 221, 221);
padding: 10px;
}
th {
background-color: white;
}
#title {
background-color: rgb(200, 200, 200);
font-weight: 600;
}
input[type=button],
input[type=submit],
input[type=reset] {
background-color: rgb(165, 165, 165);
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
color: rgb(0, 0, 0);
padding: 2px 2px;
text-decoration: none;
margin: 0px 0px;
min-width: 70px;
border-radius: 70px;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.led {
border-radius: calc(var(--tile_icon-height) / 2);
border-radius: 20px;
height: 20px;
width: 20px;
text-align: center;
vertical-align: middle;
filter: alpha(opacity=100);
opacity: 1.0;
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
.on {
background-color: rgb(255, 0, 0);
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
.off {
background-color: rgb(116, 116, 116);
/* Style the tab content */
.tabcontent {
display: none;
/*padding: 6px 12px;*/
border: 1px solid #ccc;
border-top: none;
}
.error {
background-color: rgb(255, 0, 0) !important;
font-weight: 600;
}
.hidden {
display: none;
.tabiframe {
width: 100%;
height: 500px;
}
</style>
<script type='text/javascript'>
function openPage(evt, pageName) {
// Declare all variables
var i, tabcontent, tablinks;
var _panel_size = 6;
var _panel_set = 0;
function send(source) {
console.log("from" + source.id);
var cmd = {};
cmd.uri = "rawcommand"
switch (source.id) {
case "Filter_Pump_button":
cmd.value = "0x02";
break;
case "Spa_Mode_button":
cmd.value = "0x01";
break;
case "Aux_1_button":
cmd.value = "0x05";
break;
case "Aux_2_button":
cmd.value = "0x0a";
break;
case "Aux_3_button":
cmd.value = "0x0f";
break;
case "Aux_4_button":
if (_panel_size < 12)
cmd.value = "0x06";
else
cmd.value = "0x14"; //Different on RS12+ (0x14)
break;
case "Aux_5_button":
if (_panel_size < 12)
cmd.value = "0x0b";
else
cmd.value = "0x03"; //Different on RS12+ (0x03)
break;
case "Aux_6_button":
if (_panel_size < 12)
cmd.value = "0x10";
else
cmd.value = "0x07"; //Different on RS12+ (0x07)
break;
case "Aux_7_button":
if (_panel_size < 12)
cmd.value = "0x15";
else
cmd.value = "0x06"; //Different on RS12+ (0x06)
break;
case "Aux_B1_button":
cmd.value = "0x0b";
break;
case "Aux_B2_button":
cmd.value = "0x10";
break;
case "Aux_B3_button":
cmd.value = "0x15";
break;
case "Aux_B4_button":
cmd.value = "0x1a";
break;
case "Aux_B5_button":
cmd.value = "0x04";
break;
case "Aux_B6_button":
cmd.value = "0x08";
break;
case "Aux_B7_button":
cmd.value = "0x0d";
break;
case "Aux_B8_button":
cmd.value = "0x0c";
break;
case "Pool_Heater_button":
cmd.value = "0x12";
break;
case "Spa_Heater_button":
cmd.value = "0x17";
break;
case "Solar_Heater_button":
cmd.value = "0x1c";
break;
case "B_menu":
cmd.value = "0x09";
break;
case "B_cancel":
cmd.value = "0x0e";
break;
case "B_back":
cmd.value = "0x13";
break;
case "B_forward":
cmd.value = "0x18";
break;
case "B_enter":
cmd.value = "0x1d";
break;
case "B_hold":
cmd.value = "0x19";
break;
case "B_overide":
cmd.value = "0x1c";
break;
default:
alert("Unknown button");
return;
break;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
cmd.value = todec(cmd.value);
send_command(cmd);
// I know we are converting hex to dec and back to hex, but here for checking.
document.getElementById("messages").innerHTML = "Sent "+tohex(cmd.value)+" please wait for response!";
}
function tohex(value) {
var rtn = parseInt(value).toString(16);
if (rtn.length <= 1)
return "0x0"+rtn;
else
return "0x"+rtn;
}
function todec(value) {
let number = parseInt(value, 16);
return number;
}
function set_panel_size(size)
{
if (_panel_set == size){
return;
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
_panel_set = size;
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(pageName).style.display = "block";
switch(size){
case 6:
document.getElementById("Aux_6_led").classList.add("hidden");
document.getElementById("Aux_6_button").classList.add("hidden");
document.getElementById("Aux_7_led").classList.add("hidden");
document.getElementById("Aux_7_button").classList.add("hidden");
case 8:
document.getElementById("Aux_B1_led").classList.add("hidden");
document.getElementById("Aux_B1_button").classList.add("hidden");
document.getElementById("Aux_B2_led").classList.add("hidden");
document.getElementById("Aux_B2_button").classList.add("hidden");
document.getElementById("Aux_B3_led").classList.add("hidden");
document.getElementById("Aux_B3_button").classList.add("hidden");
document.getElementById("Aux_B4_led").classList.add("hidden");
document.getElementById("Aux_B4_button").classList.add("hidden");
case 12:
document.getElementById("Aux_B5_button").classList.add("hidden");
document.getElementById("Aux_B6_button").classList.add("hidden");
document.getElementById("Aux_B7_button").classList.add("hidden");
document.getElementById("Aux_B8_button").classList.add("hidden");
}
}
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
document.getElementById("title").innerHTML = ' !!! PDA only panels are not Supported !!! '
document.getElementById("title").classList.add("error");
document.getElementById("messages").innerHTML = ' !!! PDA Not Supported !!! '
document.getElementById("messages").classList.add("error");
}
if (document.getElementById("status").getAttribute("raw") != data.panel_message) {
document.getElementById("status_three").innerHTML = document.getElementById("status_two").innerHTML;
document.getElementById("status_two").innerHTML = document.getElementById("status").innerHTML;
if (data.panel_message == "") {
document.getElementById("status").innerHTML = "&nbsp;";
} else {
document.getElementById("status").innerHTML = data.panel_message;
}
document.getElementById("status").setAttribute("raw", data.panel_message);
document.getElementById("messages").innerHTML = "&nbsp;";
}
for (var obj in data.leds) {
if ((led = document.getElementById(obj.toString() + "_led")) == null) {
//console.log("Error " + obj.toString() + " LED not found");
//return;
} else {
if (data.leds[obj] == "on" || data.leds[obj] == "enabled" || data.leds[obj] == "flash") {
//console.log("Set " + obj.toString() + " LED on");
led.classList.add("on");
led.classList.remove("off");
} else {
//console.log("Set " + obj.toString() + " LED off");
led.classList.add("off");
led.classList.remove("on");
}
}
if (obj.toString() == "Aux_4" && _panel_size < 6)
_panel_size=6;
if (obj.toString() == "Aux_6" && _panel_size < 8)
_panel_size=8;
if (obj.toString() == "Aux_B1" && _panel_size < 12)
_panel_size=12;
if (obj.toString() == "Aux_B4")
_panel_size=16;
}
set_panel_size(_panel_size);
}
function update_device(data) {
for (var obj in data['devices']) {
}
}
function set_labels(data) {
for (var obj in data) {
if ((button = document.getElementById(obj + "_button")) != null && data[obj] != "NONE") {
button.value = data[obj];
}
}
}
function get_appropriate_ws_url() {
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) == "https") {
pcol = "wss://";
u = u.substr(8);
if (evt != null) {
evt.currentTarget.className += " active";
} else {
pcol = "ws://";
if (u.substring(0, 4) == "http")
u = u.substr(7);
document.getElementById('opentab').className += " active";
}
u = u.split('/');
//alert (pcol + u[0] + ":6500");
return pcol + u[0];
}
/* dumb increment protocol */
var socket_di;
function startWebsockets() {
socket_di = new WebSocket(get_appropriate_ws_url());
try {
socket_di.onopen = function () {
// success!
start_simulator();
get_devices();
}
socket_di.onmessage = function got_packet(msg) {
document.getElementById("status").classList.remove("error");
var data = JSON.parse(msg.data);
if (data.type == 'status') {
update_status(data);
} else if (data.type == 'devices') {
//update_status(data);
update_device(data);
} else if (data.type == 'aux_labels') {
set_labels(data);
}
}
socket_di.onclose = function () {
// something went wrong
document.getElementById("status").innerHTML = ' !!! Connection error !!! '
document.getElementById("status").classList.add("error");
// Try to reconnect every 5 seconds.
setTimeout(function () {
startWebsockets();
}, 5000);
}
} catch (exception) {
alert('<p>Error' + exception);
}
}
function start_simulator() {
var msg = {
//command: "simulator"
uri: "simulator"
};
socket_di.send(JSON.stringify(msg));
}
function get_devices() {
var msg = {
uri: "devices"
};
socket_di.send(JSON.stringify(msg));
}
function send_command(cmd) {
socket_di.send(JSON.stringify(cmd));
}
/*
function reset() {
socket_di.send("reset\n");
}
*/
function init() {
startWebsockets();
}
</script>
<body onload="init();">
<div class="wrapper">
<table border='0' id="deviceList">
<tr style="title">
<td style="title" colspan="12" align="center"><label id="title">&nbsp;&nbsp;AqualinkD All Button Simulator&nbsp;&nbsp;</label></th>
</tr>
<tr>
<td colspan="12" align="center"><label id="status_three">&nbsp;</label></td>
</tr>
<tr>
<td colspan="12" align="center"><label id="status_two">&nbsp;</label></td>
</tr>
<tr>
<th colspan="12"><label id="status">AqualinkD</label></th>
</tr>
<tr>
<td align="right">
<div id="Filter_Pump_led" class="led off"></div>
</td>
<td align="left">
<input id="Filter_Pump_button" type="button" onclick="send(this);" value="Filter Pump">
</td>
<td align="right">
<div id="Spa_Mode_led" class="led off"></div>
</td>
<td align="left">
<input id="Spa_Mode_button" type="button" onclick="send(this);" value="Spa Mode">
</td>
<td align="right">
<div id="Aux_1_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_1_button" type="button" onclick="send(this);" value="AUX1">
</td>
<td align="right">
<div id="Aux_2_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_2_button" type="button" onclick="send(this);" value="AUX2">
</td>
<td align="right">
<div id="Aux_3_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_3_button" type="button" onclick="send(this);" value="AUX3">
</td>
<td align="right">
<div id="Aux_4_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_4_button" type="button" onclick="send(this);" value="AUX4">
</td>
</tr>
<tr>
<td align="right">
<div id="Aux_5_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_5_button" type="button" onclick="send(this);" value="AUX5">
</td>
<td align="right">
<div id="Aux_6_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_6_button" type="button" onclick="send(this);" value="AUX6">
</td>
<td align="right">
<div id="Aux_7_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_7_button" type="button" onclick="send(this);" value="AUX7">
</td>
<td align="right">
<div id="Aux_B1_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_B1_button" type="button" onclick="send(this);" value="AUXB1">
</td>
<td align="right">
<div id="Aux_B2_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_B2_button" type="button" onclick="send(this);" value="AUXB2">
</td>
<td align="right">
<div id="Aux_B3_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_B3_button" type="button" onclick="send(this);" value="AUXB3">
</td>
</tr>
<tr>
<td align="right">
<div id="Aux_B4_led" class="led off"></div>
</td>
<td align="left">
<input id="Aux_B4_button" type="button" onclick="send(this);" value="AUXB4">
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
<input id="Aux_B5_button" type="button" onclick="send(this);" value="AUXB5">
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
<input id="Aux_B6_button" type="button" onclick="send(this);" value="AUXB6">
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
<input id="Aux_B7_button" type="button" onclick="send(this);" value="AUXB7">
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
<input id="Aux_B8_button" type="button" onclick="send(this);" value="AUXB8">
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
&nbsp;
</td>
</tr>
<tr>
<td align="right">
&nbsp;
</td>
<td align="left">
&nbsp;
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
&nbsp;
</td>
<td align="right">
&nbsp;
</td>
<td align="left">
&nbsp;
</td>
<td align="right">
<div id="Pool_Heater_led" class="led off"></div>
</td>
<td align="left">
<input id="Pool_Heater_button" type="button" onclick="send(this);" value="Pool Heater">
</td>
<td align="right">
<div id="Spa_Heater_led" class="led off"></div>
</td>
<td align="left">
<input id="Spa_Heater_button" type="button" onclick="send(this);" value="Spa Heater">
</td>
<td align="right">
<div id="Solar_Heater_led" class="led off"></div>
</td>
<td align="left">
<input id="Solar_Heater_button" type="button" onclick="send(this);" value="Solar Heater">
</td>
</tr>
<tr>
<TD>&nbsp;</TD>
</tr>
<tr>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_menu" value="Menu">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_cancel" value="Cancel">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_back" value="Back <">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_forward" value="Forward >">
</td>
<td align="center" colspan="2">
<input type="button" onclick="send(this);" id="B_enter" value="Enter *">
</td>
</tr>
<tr>
<td colspan="12" align="center"><label id="messages">&nbsp;</label></td>
</tr>
</table>
<body onload="openPage(null, 'aqualinka_iframe');">
<div class="wrapper">
<div class="tab">
<button id="opentab" class="tablinks" onclick="openPage(event, 'aqualinka_iframe')">AqualinkD</button>
<button class="tablinks" onclick="openPage(event, 'allbutton_iframe')">Allbutton Simulator</button>
<button class="tablinks" onclick="openPage(event, 'onetouch_iframe')">One Touch Simulator</button>
<!--<button class="tablinks" onclick="openPage(event, 'iaqtouch')">iAqualink Touch Simulator</button>-->
<button class="tablinks" onclick="openPage(event, 'aquapda_iframe')">PDA Simulator</button>
</div>
</body>
<!-- Tab content -->
<div id="aqualinka_iframe" class="tabcontent">
<iframe id="aqd" src="/" class="tabiframe"></iframe>
</div>
<div id="allbutton_iframe" class="tabcontent">
<iframe id="aqd" src="/allbutton_sim.html" class="tabiframe"></iframe>
</div>
<div id="onetouch_iframe" class="tabcontent">
<iframe id="aqd" src="/onetouch_sim.html" class="tabiframe"></iframe>
</div>
<div id="aquapda_iframe" class="tabcontent">
<iframe id="aqd" src="/aquapda_sim.html" class="tabiframe"></iframe>
</div>
<div id="iaqtouch_iframe" class="tabcontent">
<iframe id="aqd" src="/iaqtouch_sim.html" class="tabiframe"></iframe>
</div>
</div>
</body>
</html>