mirror of https://github.com/sfeakes/AqualinkD.git
Updates for V3.0.0 (dev)
parent
abf1e30f6c
commit
67302f6661
|
|
@ -139,9 +139,12 @@ NEED TO FIX FOR THIS RELEASE.
|
|||
# Updates in 3.0.0 (dev)
|
||||
* WARNING V3.0 has undergone a significant amount code changes and refactoring, there may be issues.
|
||||
* Serial optimization for AqualinkD HAT.
|
||||
* upgraded network library ( HTTP(S), MQTT(S), WS )
|
||||
* Can now edit webconfig in aqmanager, added many UI customization options.
|
||||
* web/config.js is now web/config.json any custom settings will need to be migrated.
|
||||
* Added example plugin of how to get HomeAssistant devices to show up in AqualinkD UI.
|
||||
* upgraded network library ( HTTP(S), MQTT(S), WS )
|
||||
* Added support for HTTPS and MQTTS
|
||||
* Optimized updates to MQTT and WebUI (only update when absolutely necessary)
|
||||
* Optimized updates to MQTT, web sockets & WebUI (only update when absolutely necessary)
|
||||
* Added options to force upgrades in aqmanager. (add ?upgrade or ?devupgrade to url to enable upgrade button)
|
||||
* MQTT Discovery for all supporting hubs (HomeAssistant Domoticz Hubitat OpenHAB etc)
|
||||
* Moved Domoticz support over to MQTT autodiscovery.
|
||||
|
|
@ -159,7 +162,6 @@ NEED TO FIX FOR THIS RELEASE.
|
|||
* Added preliminary support for Jandy Infinite water color lights
|
||||
- Need to finish off :-
|
||||
* HAT serial optimizations broke some USB serial adapters
|
||||
* WebUI Config in aqmanager.
|
||||
|
||||
|
||||
# Updates in 2.6.11 (Sept 14 2025)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ if [ -d "$CONFDIR" ]; then
|
|||
if [ -f "$CONFDIR/config.js" ]; then
|
||||
ln -sf "$CONFDIR/config.js" /var/www/aqualinkd/config.js
|
||||
fi
|
||||
# If we have a web config, replace the local filesystem with mounted
|
||||
if [ -f "$CONFDIR/config.json" ]; then
|
||||
ln -sf "$CONFDIR/config.json" /var/www/aqualinkd/config.json
|
||||
fi
|
||||
|
||||
# If don't have a cron file, create one
|
||||
if [ ! -f "$CONFDIR/aqualinkd.schedule" ]; then
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -218,21 +218,25 @@ else
|
|||
log "Please install cron, if not the AqualinkD Scheduler will not work"
|
||||
fi
|
||||
|
||||
# V3.0.0 uses config.json not config.js
|
||||
if [ -f "$WEBLocation/config.js" ]; then
|
||||
log "AqualinkD web config is old, please migrate and settings from $WEBLocation/config.js to $WEBLocation/config.json"
|
||||
fi
|
||||
# V2.3.9 & V2.6.0 has kind-a breaking change for config.js, so check existing and rename if needed
|
||||
# we added Aux_V? to the button list
|
||||
if [ -f "$WEBLocation/config.js" ]; then
|
||||
#if [ -f "$WEBLocation/config.js" ]; then
|
||||
# Test is if has AUX_V1 in file AND "Spa" is in file (Spa_mode changed to Spa)
|
||||
# Version 2.6.0 added Chiller as well
|
||||
if ! grep -q '"Aux_V1"' "$WEBLocation/config.js" ||
|
||||
! grep -q '"Spa"' "$WEBLocation/config.js" ||
|
||||
! grep -q '"Chiller"' "$WEBLocation/config.js" ||
|
||||
! grep -q '"Aux_S1"' "$WEBLocation/config.js"; then
|
||||
dateext=`date +%Y%m%d_%H_%M_%S`
|
||||
log "AqualinkD web config is old, making copy to $WEBLocation/config.js.$dateext"
|
||||
log "Please make changes to new version $WEBLocation/config.js"
|
||||
mv $WEBLocation/config.js $WEBLocation/config.js.$dateext
|
||||
fi
|
||||
fi
|
||||
#if ! grep -q '"Aux_V1"' "$WEBLocation/config.js" ||
|
||||
# ! grep -q '"Spa"' "$WEBLocation/config.js" ||
|
||||
# ! grep -q '"Chiller"' "$WEBLocation/config.js" ||
|
||||
# ! grep -q '"Aux_S1"' "$WEBLocation/config.js"; then
|
||||
# dateext=`date +%Y%m%d_%H_%M_%S`
|
||||
# log "AqualinkD web config is old, making copy to $WEBLocation/config.js.$dateext"
|
||||
# log "Please make changes to new version $WEBLocation/config.js"
|
||||
# mv $WEBLocation/config.js $WEBLocation/config.js.$dateext
|
||||
#fi
|
||||
#fi
|
||||
|
||||
|
||||
|
||||
|
|
@ -268,14 +272,14 @@ if [ ! -d "$WEBLocation" ]; then
|
|||
mkdir -p $WEBLocation
|
||||
fi
|
||||
|
||||
if [ -f "$WEBLocation/config.js" ]; then
|
||||
log "AqualinkD web config exists, did not copy new config, you may need to edit existing $WEBLocation/config.js "
|
||||
if [ -f "$WEBLocation/config.json" ]; then
|
||||
log "AqualinkD web config exists, did not copy new config, you may need to edit existing $WEBLocation/config.json "
|
||||
if command -v "rsync" &>/dev/null; then
|
||||
rsync -avq --exclude='config.js' $BUILD/../web/* $WEBLocation
|
||||
rsync -avq --exclude='config.json' $BUILD/../web/* $WEBLocation
|
||||
else
|
||||
# This isn;t the right way to do it, but seems to work.
|
||||
shopt -s extglob
|
||||
`cp -r "$BUILD/../web/"!(*config.js) "$WEBLocation"`
|
||||
`cp -r "$BUILD/../web/"!(*config.json) "$WEBLocation"`
|
||||
shopt -u extglob
|
||||
# Below should work, but doesn't.
|
||||
#shopt -s extglob
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
#include "sensors.h"
|
||||
//#include "aq_panel.h" // Moved to later in file to overcome circular dependancy. (crappy I know)
|
||||
|
||||
#define PRINTF(format, ...) printf("%s:%d: " format, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
//#define PRINTF(format, ...) printf("%s:%d: " format, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#define PRINTF(format, ...)
|
||||
|
||||
#define isMASK_SET(bitmask, mask) ((bitmask & mask) == mask)
|
||||
#define setMASK(bitmask, mask) (bitmask |= mask)
|
||||
|
|
|
|||
|
|
@ -948,7 +948,7 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
|
|||
}
|
||||
|
||||
|
||||
typedef enum {uActioned, uBad, uDevices, uStatus, uHomebridge, uDynamicconf, uDebugStatus, uDebugDownload, uSimulator, uSchedules, uSetSchedules, uAQmanager, uLogDownload, uNotAvailable, uConfig, uSaveConfig, uConfigDownload} uriAtype;
|
||||
typedef enum {uActioned, uBad, uDevices, uStatus, uHomebridge, uDynamicconf, uDebugStatus, uDebugDownload, uSimulator, uSchedules, uSetSchedules, uAQmanager, uLogDownload, uNotAvailable, uConfig, uSaveConfig, uConfigDownload, uSaveWebConfig} uriAtype;
|
||||
//typedef enum {NET_MQTT=0, NET_API, NET_WS, DZ_MQTT} netRequest;
|
||||
const char actionName[][5] = {"MQTT", "API", "WS", "DZ"};
|
||||
|
||||
|
|
@ -1045,6 +1045,8 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
|
|||
return uConfigDownload;
|
||||
} else if (strncmp(ri1, "config/set", 10) == 0) {
|
||||
return uSaveConfig;
|
||||
} else if (strncmp(ri1, "webconfig/set", 13) == 0) {
|
||||
return uSaveWebConfig;
|
||||
} else if (strncmp(ri1, "config", 6) == 0) {
|
||||
return uConfig;
|
||||
} else if (strncmp(ri1, "simulator", 9) == 0 && from == NET_WS) { // Only valid from websocket.
|
||||
|
|
@ -1652,7 +1654,7 @@ void action_web_request(struct mg_connection *nc, struct mg_http_message *http_m
|
|||
{
|
||||
char message[JSON_BUFFER_SIZE];
|
||||
DEBUG_TIMER_START(&tid2);
|
||||
build_webconfig_js(_aqualink_data, message, JSON_BUFFER_SIZE);
|
||||
build_dynamic_webconfig_js(_aqualink_data, message, JSON_BUFFER_SIZE);
|
||||
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_webconfig_js took");
|
||||
mg_http_reply(nc, 200, CONTENT_JS, message);
|
||||
}
|
||||
|
|
@ -1855,6 +1857,14 @@ void action_websocket_request(struct mg_connection *nc, struct mg_ws_message *wm
|
|||
DEBUG_TIMER_STOP(tid, NET_LOG, "action_websocket_request() save_config_js took");
|
||||
ws_send(nc, message);
|
||||
}
|
||||
case uSaveWebConfig:
|
||||
{
|
||||
DEBUG_TIMER_START(&tid);
|
||||
char message[JSON_BUFFER_SIZE];
|
||||
save_web_config_json((char *)wm->data.buf, wm->data.len, message, JSON_BUFFER_SIZE, _aqualink_data);
|
||||
DEBUG_TIMER_STOP(tid, NET_LOG, "action_websocket_request() save_web_config_json took");
|
||||
ws_send(nc, message);
|
||||
}
|
||||
break;
|
||||
case uBad:
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -17,13 +17,19 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#include "config.h"
|
||||
#include "color_lights.h"
|
||||
#include "utils.h"
|
||||
#include "aq_systemutils.h"
|
||||
|
||||
int build_webconfig_js(struct aqualinkdata *aqdata, char* buffer, int size)
|
||||
#define WEBCONFIGFILE "/config.json"
|
||||
void fprintf_json(FILE *fp,const char *json_string);
|
||||
|
||||
// This should be called dynamic web config, not webconfig to avoid confusion.
|
||||
int build_dynamic_webconfig_js(struct aqualinkdata *aqdata, char* buffer, int size)
|
||||
{
|
||||
memset(&buffer[0], 0, size);
|
||||
int length = 0;
|
||||
|
|
@ -33,4 +39,126 @@ int build_webconfig_js(struct aqualinkdata *aqdata, char* buffer, int size)
|
|||
length += sprintf(buffer+length, "var _enable_schedules = %s;\n",(_aqconfig_.enable_scheduler)?"true":"false");
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
char* find_nth_char(const char* str, int character, int n) {
|
||||
char* p = (char*)str;
|
||||
int count = 0;
|
||||
|
||||
while (count < n && (p = strchr(p, character)) != NULL) {
|
||||
count++;
|
||||
if (count == n) {
|
||||
return p;
|
||||
}
|
||||
p++; // Move pointer past the found character for the next search
|
||||
}
|
||||
return NULL; // Return NULL if the Nth instance is not found
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int save_web_config_json(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata)
|
||||
{
|
||||
FILE *fp;
|
||||
char configfile[256];
|
||||
bool ro_root;
|
||||
bool created_file;
|
||||
char *contents;
|
||||
|
||||
snprintf(configfile, 256, "%s%s", _aqconfig_.web_directory,WEBCONFIGFILE);
|
||||
|
||||
|
||||
fp = aq_open_file(configfile, &ro_root, &created_file);
|
||||
|
||||
if (fp == NULL) {
|
||||
LOG(AQUA_LOG,LOG_ERR, "Open config file failed '%s'\n", configfile);
|
||||
//remount_root_ro(true);
|
||||
//fprintf(stdout, "Open file failed 'sprinkler.cron'\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char* secondColonPos = find_nth_char(inBuf, ':', 2);
|
||||
// Check if the pointer is not NULL, and calculate the index inside the printf
|
||||
if (secondColonPos) {
|
||||
contents = malloc(sizeof(char) * inSize);
|
||||
int pos = (secondColonPos - inBuf) + 1;
|
||||
// Need to strip off {"uri":"webconfig/set","values": from beginning and the trailing }
|
||||
snprintf(contents, inSize - pos, "%s", inBuf+pos);
|
||||
|
||||
// Below 2 will print string to file, but on one line
|
||||
// use fprintf_json to make look nicer
|
||||
//fprintf(fp, "%s", contents);
|
||||
//fprintf(fp,"\n");
|
||||
fprintf_json(fp,contents);
|
||||
|
||||
free(contents);
|
||||
} else {
|
||||
// Error bad string of something.
|
||||
LOG(AQUA_LOG,LOG_ERR, "Bad web config '%s'\n", inBuf);
|
||||
}
|
||||
//fclose(fp);
|
||||
aq_close_file(fp, ro_root);
|
||||
|
||||
|
||||
return sprintf(outBuf, "{\"message\":\"Saved Web Config\"}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void fprint_indentation(FILE *fp, int indent_level) {
|
||||
for (int i = 0; i < indent_level; i++) {
|
||||
fprintf(fp," "); // Use 2 spaces for indentation
|
||||
}
|
||||
}
|
||||
|
||||
void fprintf_json(FILE *fp, const char *json_string) {
|
||||
int indent_level = 0;
|
||||
int in_string = 0; // Flag to track if inside a string literal
|
||||
|
||||
for (int i = 0; i < strlen(json_string); i++) {
|
||||
char c = json_string[i];
|
||||
|
||||
if (in_string) {
|
||||
fprintf(fp,"%c", c);
|
||||
if (c == '"' && json_string[i - 1] != '\\') { // End of string, handle escaped quotes
|
||||
in_string = 0;
|
||||
}
|
||||
} else {
|
||||
switch (c) {
|
||||
case '{':
|
||||
case '[':
|
||||
fprintf(fp,"%c\n", c);
|
||||
indent_level++;
|
||||
fprint_indentation(fp,indent_level);
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
fprintf(fp,"\n");
|
||||
indent_level--;
|
||||
fprint_indentation(fp,indent_level);
|
||||
fprintf(fp,"%c", c);
|
||||
break;
|
||||
case ',':
|
||||
fprintf(fp,"%c\n", c);
|
||||
fprint_indentation(fp,indent_level);
|
||||
break;
|
||||
case ':':
|
||||
fprintf(fp,"%c ", c);
|
||||
break;
|
||||
case '"':
|
||||
fprintf(fp,"%c", c);
|
||||
in_string = 1;
|
||||
break;
|
||||
default:
|
||||
fprintf(fp,"%c", c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(fp,"\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
|
||||
|
||||
int build_webconfig_js(struct aqualinkdata *aqdata, char* buffer, int size);
|
||||
int build_dynamic_webconfig_js(struct aqualinkdata *aqdata, char* buffer, int size);
|
||||
int save_web_config_json(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
|
||||
|
||||
/*
|
||||
For manual setup, follow the below.
|
||||
For easy setup, read the online documentation
|
||||
|
||||
1) put below in aqualinkd config.json
|
||||
|
||||
"external_script": "/HA_tilePlugin.js"
|
||||
|
||||
2) Configure any custom icons in aqualinkd config.json, example below 'light.back_floodlights' is HA ID:-
|
||||
|
||||
"light.back_floodlights": {
|
||||
"display": "true",
|
||||
"on_icon": "extra/light-on.png",
|
||||
"off_icon": "extra/light-off-grey.png"
|
||||
},
|
||||
|
||||
3) Add the HA ID's you want to the setTile function below :-
|
||||
homeassistantAction("light.back_floodlights");
|
||||
|
||||
4) IN HOME ASSISTANT you will need to allow Cross-Origin Resource Sharing
|
||||
edit configuration.yaml, and add below (change aqualinkd to the machine name running aqualinkd)
|
||||
|
||||
http:
|
||||
cors_allowed_origins
|
||||
- http://aqualinkd
|
||||
|
||||
5) Add your HA API token & server below.
|
||||
*/
|
||||
|
||||
// Add your HA API token
|
||||
var HA_TOKEN = '<YOUR LONG HA TOKEN HERE>';
|
||||
// Change to your HA server IP
|
||||
var HA_SERVER = 'http://192.168.1.255:8123';
|
||||
|
||||
|
||||
setupTiles();
|
||||
|
||||
|
||||
function setupTiles() {
|
||||
// If we have specific user agents setup, make sure one matches.
|
||||
if (_config?.HA_tilePlugin && _config?.HA_tilePlugin?.userAgents) {
|
||||
var found=false;
|
||||
for (const agent of _config.HA_tilePlugin.userAgents) {
|
||||
if (navigator.userAgent.search(agent) != -1) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
// User agent doesn't match the list, return and tdo nothing.
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If aqualinkd has not added tiles, wait.
|
||||
if ( document.getElementById("Filter_Pump") === null) {
|
||||
setTimeout(setupTiles, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_config?.HA_tilePlugin) {
|
||||
// If you are not using config.json to add them, Add your HA ID's below. replace the below examples.
|
||||
/* ###########################################################
|
||||
#
|
||||
# Add manual entries here
|
||||
#
|
||||
########################################################### */
|
||||
//homeassistantAction("light.back_floodlights");
|
||||
} else {
|
||||
HA_TOKEN = _config.HA_tilePlugin.HA_token;
|
||||
HA_SERVER = _config.HA_tilePlugin.HA_server;
|
||||
for (const id of _config.HA_tilePlugin.HA_entity_ids) {
|
||||
homeassistantAction(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*. Some helper / formating functions */
|
||||
function getStringAfterCharacter(mainString, character) {
|
||||
const index = mainString.indexOf(character);
|
||||
if (index === -1) {
|
||||
// Character not found, return the original string or an empty string as desired
|
||||
return mainString;
|
||||
}
|
||||
return mainString.slice(index + 1);
|
||||
}
|
||||
function getStringBeforeCharacter(mainString, character) {
|
||||
const index = mainString.indexOf(character);
|
||||
if (index === -1) {
|
||||
// Character not found, return the original string or an empty string as desired
|
||||
return mainString;
|
||||
}
|
||||
return mainString.slice(0, index);
|
||||
}
|
||||
|
||||
function formatTwoDecimalsOrInteger(num) {
|
||||
const roundedNum = Math.round(num * 100) / 100;
|
||||
let result = String(roundedNum);
|
||||
|
||||
// Check if the string ends with '.00' and remove it if present
|
||||
if (result.endsWith('.00')) {
|
||||
result = result.slice(0, -3); // Remove the last 3 characters (".00")
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function HA_switchTileState(id) {
|
||||
state = (document.getElementById(id).getAttribute('status') == 'off') ? 'on' : 'off';
|
||||
|
||||
homeassistantAction(id, state);
|
||||
|
||||
setTileOn(id, state);
|
||||
}
|
||||
|
||||
|
||||
function HA_updateDevice(data) {
|
||||
var tile
|
||||
var name
|
||||
|
||||
if (!data || !data.state) {
|
||||
console.log("Error, missing JSON values ID="+data?.entity_id+", Name="+data?.attributes?.friendly_name+", State="+data?.state);
|
||||
return;
|
||||
} else if (!data.attributes.friendly_name) {
|
||||
name = getStringAfterCharacter(data.entity_id, ".");
|
||||
} else {
|
||||
name = data.attributes.friendly_name;
|
||||
}
|
||||
|
||||
const service = getStringBeforeCharacter(data.entity_id, ".");
|
||||
|
||||
// If the tile doesn't exist, create it.
|
||||
if ((tile = document.getElementById(data.entity_id)) == null) {
|
||||
var tile = {};
|
||||
tile["id"] = data.entity_id;
|
||||
tile["name"] = name;
|
||||
tile["display"] = "true";
|
||||
|
||||
if ( service == "sensor" ) {
|
||||
tile["type"] = "value";
|
||||
tile["value"] = data.state;
|
||||
} else {
|
||||
tile["type"] = "switch"; // switch or value
|
||||
}
|
||||
|
||||
if ( service == "cover") {
|
||||
tile["state"] = ((data.state == 'closed') ? 'off' : 'on')
|
||||
} else {
|
||||
tile["state"] = ((data.state == 'off') ? 'off' : 'on');
|
||||
}
|
||||
tile["status"] = tile["state"];
|
||||
|
||||
createTile(tile);
|
||||
|
||||
// Make sure we use out own callback for button press.
|
||||
subdiv = document.getElementById(data.entity_id);
|
||||
subdiv.setAttribute('onclick', "HA_switchTileState('" + data.entity_id + "')");
|
||||
tile = document.getElementById(data.entity_id);
|
||||
}
|
||||
|
||||
switch (service) {
|
||||
case "sensor":
|
||||
setTileValue(data.entity_id, formatTwoDecimalsOrInteger(data.state));
|
||||
break;
|
||||
case "cover":
|
||||
setTileOn(data.entity_id, ((data.state == 'closed') ? 'off' : 'on'), null);
|
||||
break;
|
||||
case "light":
|
||||
case "switch":
|
||||
case "input_boolean":
|
||||
default:
|
||||
setTileOn(data.entity_id, ((data.state == 'off') ? 'off' : 'on'), null);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function getURL(service, action) {
|
||||
switch (service) {
|
||||
case "sensor":
|
||||
return "";
|
||||
break;
|
||||
case "cover":
|
||||
return '/api/services/'+service+'/'+(action=="on"?"open":"close")+"_cover";
|
||||
break;
|
||||
case "light":
|
||||
case "switch":
|
||||
case "input_boolean":
|
||||
default:
|
||||
return '/api/services/'+service+'/turn_'+action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function homeassistantAction(id, action="status") {
|
||||
var http = new XMLHttpRequest();
|
||||
if (http) {
|
||||
http.onreadystatechange = function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status == 200) {
|
||||
var data = JSON.parse(http.responseText);
|
||||
// Sending action on/off returns blank from HA, so only bother acting on status messages
|
||||
if (action == "status") {
|
||||
HA_updateDevice(data);
|
||||
}
|
||||
} else {
|
||||
console.error("Http error "+http.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (action == "status") {
|
||||
http.open('GET', HA_SERVER+'/api/states/' + id, true);
|
||||
http.setRequestHeader("Content-Type", "application/json");
|
||||
http.setRequestHeader("Authorization", "Bearer "+HA_TOKEN);
|
||||
_poller = setTimeout(function() { homeassistantAction(id, action); }, 5000);
|
||||
http.send(null);
|
||||
} else {
|
||||
const service = getStringBeforeCharacter(id, ".");
|
||||
// Below should be api/services/cover/open_cover for cover.
|
||||
http.open('POST', HA_SERVER+getURL(service,action), true);
|
||||
http.setRequestHeader("Content-Type", "application/json");
|
||||
http.setRequestHeader("Authorization", "Bearer "+HA_TOKEN);
|
||||
http.send('{"entity_id": "'+id+'"}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +337,36 @@
|
|||
.config_options_added {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.web_config_options {
|
||||
top: 10px;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
/*height: 100vw;*/
|
||||
}
|
||||
.web_config_options_pane {
|
||||
background-color: var(--options_pane_background);
|
||||
border: 2px solid var(--options_pane_bordercolor);
|
||||
border-radius: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 700px;
|
||||
/*max-height: 870px;*/
|
||||
max-height: var(--max-config-height);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.web_config_textarea {
|
||||
width:90%;
|
||||
/*height:700px;*/
|
||||
height:var(--web-config-textarea-height);
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
filter: alpha(opacity=0);
|
||||
|
|
@ -358,6 +388,35 @@
|
|||
height: 18px !important;
|
||||
}
|
||||
|
||||
.valid_json {
|
||||
background-color: var(--options_pane_background);
|
||||
}
|
||||
.invalid_json {
|
||||
background-color: rgb(255, 100, 0);
|
||||
}
|
||||
|
||||
#alertOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#alertCustomDialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.helptxt {
|
||||
|
|
@ -383,6 +442,8 @@
|
|||
_addedBlankSensor = false;
|
||||
_addedBlankVirtualButton = false;
|
||||
|
||||
const _webConfigFile = "/config.json";
|
||||
|
||||
//var _cfgRestartAlertShown = false;
|
||||
let _AlertsShown = {};
|
||||
|
||||
|
|
@ -1546,6 +1607,182 @@
|
|||
*/
|
||||
}
|
||||
|
||||
|
||||
async function showWebConfig() {
|
||||
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
||||
//console.log("Height="+h);
|
||||
document.documentElement.style.setProperty('--max-config-height', (h-50)+'px');
|
||||
|
||||
document.documentElement.style.setProperty('--web-config-textarea-height', (h-160)+'px');
|
||||
|
||||
document.getElementById("web_config_options").classList.remove("hide");
|
||||
document.getElementById("web_config_options_pane").classList.remove("hide");
|
||||
|
||||
|
||||
|
||||
const jsonInput = document.getElementById('webconfig-json-input');
|
||||
|
||||
try {
|
||||
// Fetch the file from the specified path (make sure config.json exists)
|
||||
const response = await fetch(_webConfigFile, { cache: 'no-store' });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Get the response as plain text
|
||||
const configText = await response.text();
|
||||
|
||||
// Populate the textarea
|
||||
jsonInput.value = configText;
|
||||
jsonInput.placeholder = "Enter or edit your JSON here...";
|
||||
webconfig_validateAndDisplay(); // Optional: Validate immediately upon loading
|
||||
|
||||
} catch (error) {
|
||||
console.error('There was a problem fetching the config file:', error);
|
||||
jsonInput.value = ''; // Clear placeholder
|
||||
jsonInput.placeholder = "Could not load "+_webConfigFile+". Enter JSON manually.";
|
||||
const resultArea = document.getElementById('validation-result');
|
||||
resultArea.classList.add('invalid');
|
||||
resultArea.textContent = 'Error: Could not load '+_webConfigFile+' from server.';
|
||||
}
|
||||
//populateconfigtable(data);
|
||||
}
|
||||
|
||||
function closeWebConfig() {
|
||||
clearconfigtable();
|
||||
|
||||
document.getElementById("web_config_options").classList.add("hide");
|
||||
document.getElementById("web_config_options_pane").classList.add("hide");
|
||||
}
|
||||
|
||||
function saveWebConfig() {
|
||||
if (!webconfig_validateAndDisplay()) {
|
||||
alert("bad web config, please validate");
|
||||
//timedAlert("bad web config, please validate", 5);
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonInput = document.getElementById('webconfig-json-input');
|
||||
const jsonString = jsonInput.value;
|
||||
const jsonObject = JSON.parse(jsonString);
|
||||
|
||||
var json_ordered = {
|
||||
uri: "webconfig/set",
|
||||
values: jsonObject
|
||||
};
|
||||
send_command(json_ordered);
|
||||
closeWebConfig();
|
||||
}
|
||||
|
||||
// Function to validate and display the JSON
|
||||
function webconfig_validateAndDisplay() {
|
||||
const jsonInput = document.getElementById('webconfig-json-input');
|
||||
const resultArea = document.getElementById('validation-result');
|
||||
const jsonString = jsonInput.value;
|
||||
|
||||
|
||||
// Clear previous results
|
||||
resultArea.className = '';
|
||||
resultArea.textContent = '';
|
||||
|
||||
try {
|
||||
// Attempt to parse the string into a JavaScript object
|
||||
const jsonObject = JSON.parse(jsonString);
|
||||
|
||||
// If parsing succeeds, format the JSON nicely (pretty print)
|
||||
const prettyJsonString = JSON.stringify(jsonObject, null, 2);
|
||||
|
||||
// Update the text area with the pretty-printed JSON
|
||||
jsonInput.value = prettyJsonString;
|
||||
|
||||
// Display success message
|
||||
resultArea.classList.add('valid_json');
|
||||
resultArea.textContent = 'JSON is VALID.';
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// If parsing fails, catch the error
|
||||
|
||||
// Display error message
|
||||
resultArea.classList.add('invalid_json');
|
||||
// The error object provides useful information about where the syntax error is
|
||||
resultArea.textContent = 'JSON is INVALID: ' + error.message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function updateWebConfigFromMaster() {
|
||||
let masterJSON;
|
||||
// Get new master
|
||||
try {
|
||||
// Fetch the file from the specified path (make sure config.json exists)
|
||||
// BELOW file should be from github. File with any new config keys in it
|
||||
const response = await fetch("/config.json.tmp", { cache: 'no-store' });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Get the response as plain text
|
||||
const text = await response.text();
|
||||
masterJSON = JSON.parse(text)
|
||||
// Populate the textarea
|
||||
//jsonInput.value = configText;
|
||||
//jsonInput.placeholder = "Enter or edit your JSON here...";
|
||||
//webconfig_validateAndDisplay(); // Optional: Validate immediately upon loading
|
||||
|
||||
} catch (error) {
|
||||
alert("Error loading master cfg "+error);
|
||||
}
|
||||
// Now get local
|
||||
try{
|
||||
const jsonInput = document.getElementById('webconfig-json-input');
|
||||
const jsonString = jsonInput.value;
|
||||
var webcfgJSON = JSON.parse(jsonString);
|
||||
|
||||
webcfgJSON = mergeMissingKeysRecursive(webcfgJSON, masterJSON);
|
||||
|
||||
jsonInput.value = JSON.stringify(webcfgJSON);
|
||||
alert(jsonInput.value);
|
||||
webconfig_validateAndDisplay();
|
||||
} catch (error) {
|
||||
alert("Error "+error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function mergeMissingKeysRecursive(target, source) {
|
||||
// Ensure we are dealing with actual objects at this level
|
||||
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) {
|
||||
return target;
|
||||
}
|
||||
// Iterate over all keys in the source object
|
||||
for (const key in source) {
|
||||
if (Object.hasOwn(source, key)) {
|
||||
const sourceValue = source[key];
|
||||
const targetValue = target[key];
|
||||
|
||||
// If the source value is a nested object and the target value is also
|
||||
// a nested object (and not an array or null), recurse.
|
||||
if (
|
||||
typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue) &&
|
||||
typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)
|
||||
) {
|
||||
// Recursively merge the nested objects
|
||||
target[key] = mergeMissingKeysRecursive(targetValue, sourceValue);
|
||||
} else if (targetValue === undefined) {
|
||||
// Otherwise, if the key is missing entirely in the target, add it
|
||||
target[key] = sourceValue;
|
||||
// console.log("Added missing key:", key);
|
||||
}
|
||||
// If the key already exists and is not an object pair ready for recursion, it is ignored (target takes precedence).
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
function update_status_message(message, error = false) {
|
||||
try {
|
||||
if (error || message.substring(0, 5).toLowerCase() == "error")
|
||||
|
|
@ -2008,6 +2245,21 @@
|
|||
xmlhttp.setRequestHeader("Accept","application/vnd.github.raw");
|
||||
xmlhttp.send();
|
||||
}
|
||||
/*
|
||||
function timedAlert(message, timeout) {
|
||||
document.getElementById('alertDialogMessage').innerText = message;
|
||||
document.getElementById('alertOverlay').style.display = 'block';
|
||||
document.getElementById('alertCustomDialog').style.display = 'block';
|
||||
|
||||
// Set a timeout to automatically close the dialog
|
||||
setTimeout(closeAlert, timeout * 1000);
|
||||
}
|
||||
|
||||
function closeAlert() {
|
||||
document.getElementById('alertOverlay').style.display = 'none';
|
||||
document.getElementById('alertCustomDialog').style.display = 'none';
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
|
||||
<body onload="init();init_collapsible();">
|
||||
|
|
@ -2128,6 +2380,10 @@
|
|||
<input id="editconfig" type="button" onclick="editconfig(this);"
|
||||
value="edit config"></input>
|
||||
</td>
|
||||
<td align="center">
|
||||
<input id="webconfig" type="button" onclick="showWebConfig(this);"
|
||||
value="web config"></input>
|
||||
</td>
|
||||
<td align="center">
|
||||
<input id="downloadconfig" type="button" onclick="requestFileDownload(this);""
|
||||
value="download config"></input>
|
||||
|
|
@ -2179,9 +2435,51 @@
|
|||
</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="web_config_options" class="web_config_options hide" style="display: flex;">
|
||||
<div id="web_config_options_pane" class="web_config_options_pane hide">>
|
||||
<!--<div class="inner">END</div>-->
|
||||
<table id="config_table" border="0" cellpadding="1px" width="100%" style="border-collapse: collapse;">
|
||||
<tbody><tr class="options_title">
|
||||
<th colspan="3" style="padding: 10px;"><span>AqualinkD Web Configuration</span></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="3">
|
||||
<textarea class="web_config_textarea" id="webconfig-json-input" placeholder="Loading configuration..."></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right" style="width:33%;padding-right: 10px;">
|
||||
<input type="button" onclick="webconfig_validateAndDisplay()" value="Validate"></input>
|
||||
</td>
|
||||
<td <td align="center" style="width:33%;">
|
||||
<input type="button" onclick="saveWebConfig()" value="Save"></input>
|
||||
</td>
|
||||
<td align="left" style="width:33%;padding-left: 10px;">
|
||||
<input type="button" onclick="closeWebConfig()" value="Close without Saving"></input>
|
||||
<!--<input onclick="updateWebConfigFromMaster()" value="get new"></input>-->
|
||||
<!-- For the future, to add new keys to json config pulling from github-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<div id="validation-result" style="padding-left: 10px;padding-right: 10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--<iframe src='about:blank' id="logdownload"></iframe>-->
|
||||
</div>
|
||||
<!--
|
||||
<div id="alertOverlay" style="display: none;"></div>
|
||||
<div id="alertCustomDialog" style="display: none;">
|
||||
<p id="alertDialogMessage"></p>
|
||||
<button onclick="closeAlert()">Close</button>
|
||||
</div>
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
180
web/config.js
180
web/config.js
|
|
@ -1,180 +0,0 @@
|
|||
// Display order of devices. Tiles will be displayed in the order below,
|
||||
// any devices you don't want to see you can comment the ID. (with // e.g. `//"Solar_Heater",` )
|
||||
// If the device isn't listed below is will NOT be shown.
|
||||
// For a complete list returned from your particular aqualinkd instance
|
||||
// use the below URL and look at the ID value for each device.
|
||||
// http://aqualink.ip.address/api/devices
|
||||
|
||||
var config_js=true;
|
||||
|
||||
var devices = [
|
||||
"Filter_Pump",
|
||||
"Spa",
|
||||
"Aux_1",
|
||||
"Aux_2",
|
||||
"Aux_3",
|
||||
"Aux_4",
|
||||
"Aux_5",
|
||||
"Aux_6",
|
||||
"Aux_7",
|
||||
"Aux_B1",
|
||||
"Aux_B2",
|
||||
"Aux_B3",
|
||||
"Aux_B4",
|
||||
"Aux_B5",
|
||||
"Aux_B6",
|
||||
"Aux_B7",
|
||||
"Aux_B8",
|
||||
"Pool_Heater",
|
||||
"Spa_Heater",
|
||||
"SWG",
|
||||
//"SWG/Percent",
|
||||
"SWG/PPM",
|
||||
//"SWG/Boost",
|
||||
"Temperature/Air",
|
||||
"Temperature/Pool",
|
||||
"Temperature/Spa",
|
||||
"Pool_Water",
|
||||
"Spa_Water",
|
||||
"Freeze_Protect",
|
||||
"CHEM/pH",
|
||||
"CHEM/ORP",
|
||||
"Solar_Heater",
|
||||
"Extra_Aux",
|
||||
"Chiller",
|
||||
"Aux_V1",
|
||||
"Aux_V2",
|
||||
"Aux_V3",
|
||||
"Aux_V4",
|
||||
"Aux_V5",
|
||||
"Aux_V6",
|
||||
"Aux_V7",
|
||||
"Aux_V8",
|
||||
"Aux_V9",
|
||||
"Aux_V10",
|
||||
"Aux_V11",
|
||||
"Aux_V12",
|
||||
"Aux_V13",
|
||||
"Aux_V14",
|
||||
"Aux_V15",
|
||||
"Aux_S1",
|
||||
"Aux_S2",
|
||||
"Aux_S3",
|
||||
"Aux_S4",
|
||||
"Aux_S5",
|
||||
"Aux_S6",
|
||||
"Aux_S7",
|
||||
"Aux_S8",
|
||||
"Aux_S9",
|
||||
"Aux_S10",
|
||||
];
|
||||
|
||||
// all SWG return a status number, some have different meanings. Change the text below to suit, NOT THE NUMBER.
|
||||
var swgStatus = {
|
||||
0: "On",
|
||||
1: "No flow",
|
||||
2: "Low salt",
|
||||
4: "High salt",
|
||||
8: "Clean cell",
|
||||
9: "Turning off",
|
||||
16: "High current",
|
||||
32: "Low volts",
|
||||
64: "Low temp",
|
||||
128: "Check PCB",
|
||||
253: "General Fault",
|
||||
254: "Unknown",
|
||||
255: "Off"
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* BELOW IS NOT RELIVENT FOR simple.html or simple inteface
|
||||
*
|
||||
*/
|
||||
// Background image, delete or leave blank for solid color
|
||||
//var background_url = "http://192.168.144.224/snap.jpeg";
|
||||
var background_url='hk/background.jpg';
|
||||
//var background_url='';
|
||||
// Reload background image every X seconds.(useful if camera snapshot)
|
||||
// 0 means only load once when page loads.
|
||||
//var background_reload = 10;
|
||||
//var background_reload = 0;
|
||||
|
||||
// By default all Variable Speed Pumps will show RPM or GPM depending on how they are controlled.
|
||||
// this will show RPM for all pumps (ie Jandy VF pumps)
|
||||
//var show_vsp_gpm=false;
|
||||
|
||||
// Keeps sensor tiles in on state showing last reading. (default true= turn off tiles when device is off. ie SWG=off turn off PPM tile)
|
||||
//var turn_off_sensortiles = false;
|
||||
|
||||
// This will turn on/off the Spa Heater when you turn on/off Spa Mode.
|
||||
//var link_spa_and_spa_heater = true;
|
||||
|
||||
/* Example of setting custom range and appropriate messages
|
||||
var tile_thresholds = {
|
||||
"SWG/PPM": {
|
||||
outofrange: {min: 2600, max: 3500, mintext:"Add Salt"},
|
||||
attention: {min: 2700, max: 3400, mintext:"Add Salt"}
|
||||
},
|
||||
"CHEM/pH": {
|
||||
outofrange: {min: 7, max: 8},
|
||||
attention: {min: 7.2, max: 7.8, mintext:"Low", maxtext:"High"}
|
||||
},
|
||||
"CHEM/ORP": {
|
||||
outofrange: {min: 560, max: 900},
|
||||
attention: {min: 650, max: 850, mintext:"Low", maxtext:"High"}
|
||||
},
|
||||
// Example of how to set color to Aux_S1 (if Aux_S1 was a CPU temperature)
|
||||
//"Aux_S1": {
|
||||
// outofrange: {min: 0, max: 170, maxtext:"ALERT High"},
|
||||
// attention: {min: 0, max: 140, maxtext:"High"}
|
||||
//},
|
||||
}
|
||||
*/
|
||||
|
||||
// Change the min max for heater slider
|
||||
var heater_slider_min = 36;
|
||||
var heater_slider_max = 104;
|
||||
// Change the slider for timers
|
||||
var timer_slider_min = 0;
|
||||
//var timer_slider_max = 360;
|
||||
//var timer_slider_step = 10;
|
||||
var timer_slider_max = 120;
|
||||
var timer_slider_step = 1;
|
||||
|
||||
// Colors
|
||||
|
||||
var body_background = "#EBEBEA";
|
||||
var body_text = "#000000";
|
||||
|
||||
var options_pane_background = "#F5F5F5";
|
||||
var options_pane_bordercolor = "#7C7C7C";
|
||||
var options_slider_highlight = "#2196F3";
|
||||
var options_slider_lowlight = "#D3D3D3";
|
||||
|
||||
var head_background = "#2B6A8F";
|
||||
var head_text = "#FFFFFF)";
|
||||
var error_background = "#8F2B2B";
|
||||
|
||||
var tile_background = "#DCDCDC";
|
||||
var tile_text = "#6E6E6E";
|
||||
var tile_on_background = "#FFFFFF";
|
||||
var tile_on_text = "#000000";
|
||||
var tile_status_text = "#575757";
|
||||
|
||||
// Change the default color for vales in and out of range.
|
||||
//var value_tile_normal_color = "#4ec400ff";
|
||||
//var value_tile_attention_color = "#ffbf00ff";
|
||||
//var value_tile_outofrange_color = "#ff0000";
|
||||
|
||||
// Dark colors
|
||||
// var body_background = "#000000";
|
||||
// var tile_background = "#646464";
|
||||
// var tile_text = "#B9B9B9";
|
||||
// var tile_status_text = "#B2B2B2";
|
||||
// var head_background = "#000D53";
|
||||
|
||||
// REMOVE THIS.
|
||||
//document.writeln("<script type='text/javascript' src='extra/extra.js'></script>");
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
{
|
||||
"background_image": {
|
||||
"url": "-hk/background.jpg",
|
||||
"url_reload": 0
|
||||
},
|
||||
"colors": {
|
||||
"body_background": "#EBEBEB",
|
||||
"body_text": "#000000",
|
||||
"options_pane_background": "#F5F5F5",
|
||||
"options_pane_bordercolor": "#7C7C7C",
|
||||
"options_slider_highlight": "#2196F3",
|
||||
"options_slider_lowlight": "#D3D3D3",
|
||||
"head_background": "#2B6A8F",
|
||||
"head_text": "#FFFFFF",
|
||||
"error_background": "#8F2B2B",
|
||||
"tile_background": "#DCDCDC",
|
||||
"tile_text": "#6E6E6E",
|
||||
"tile_on_background": "#FFFFFF",
|
||||
"tile_on_text": "#000000",
|
||||
"tile_status_text": "#575757",
|
||||
"value_tile_normal_color": "#049FF8",
|
||||
"value_tile_normal_color_": "#4ec400ff",
|
||||
"value_tile_attention_color": "#FF8000",
|
||||
"value_tile_outofrange_color": "#FF6400",
|
||||
"options_radio_highlight": "#2196F3",
|
||||
"options_radio_lowlight": "#D3D3D3",
|
||||
"tile_icon_background_color_heat": "rgb(255, 123, 0)",
|
||||
"tile_icon_background_color_cool": "rgb(4, 159, 248)",
|
||||
"tile_icon_background_color_enabled": "rgb(78, 196, 0)",
|
||||
"tile_icon_background_color_disabled": "rgb(110, 110, 110)"
|
||||
},
|
||||
"devices": {
|
||||
"Filter_Pump": {
|
||||
"display": "true"
|
||||
},
|
||||
"Spa": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_1": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_2": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_3": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_4": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_5": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_6": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_7": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B1": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B2": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B3": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B4": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B5": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B6": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B7": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_B8": {
|
||||
"display": "true"
|
||||
},
|
||||
"Pool_Heater": {
|
||||
"display": "true"
|
||||
},
|
||||
"Spa_Heater": {
|
||||
"display": "true"
|
||||
},
|
||||
"SWG": {
|
||||
"display": "true"
|
||||
},
|
||||
"SWG/PPM": {
|
||||
"display": "true"
|
||||
},
|
||||
"SWG/Percent": {
|
||||
"display": "false"
|
||||
},
|
||||
"SWG/Boost": {
|
||||
"display": "false"
|
||||
},
|
||||
"Temperature/Air": {
|
||||
"display": "true"
|
||||
},
|
||||
"Temperature/Pool": {
|
||||
"display": "true"
|
||||
},
|
||||
"Temperature/Spa": {
|
||||
"display": "true"
|
||||
},
|
||||
"Pool_Water": {
|
||||
"display": "true"
|
||||
},
|
||||
"Spa_Water": {
|
||||
"display": "true"
|
||||
},
|
||||
"Freeze_Protect": {
|
||||
"display": "true"
|
||||
},
|
||||
"CHEM/pH": {
|
||||
"display": "true"
|
||||
},
|
||||
"CHEM/ORP": {
|
||||
"display": "true"
|
||||
},
|
||||
"Solar_Heater": {
|
||||
"display": "true"
|
||||
},
|
||||
"Extra_Aux": {
|
||||
"display": "true"
|
||||
},
|
||||
"Chiller": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V1": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V2": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V3": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V4": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V5": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V6": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V7": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V8": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V9": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V10": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V11": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V12": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V13": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V14": {
|
||||
"display": "true"
|
||||
},
|
||||
"Aux_V15": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S1": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S2": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S3": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S4": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S5": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S6": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S7": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S8": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S9": {
|
||||
"display": "true"
|
||||
},
|
||||
"Sensor/Aux_S10": {
|
||||
"display": "true"
|
||||
}
|
||||
},
|
||||
"slider_range": {
|
||||
"heater_slider_min": 36,
|
||||
"heater_slider_max": 104,
|
||||
"timer_slider_min": 0,
|
||||
"timer_slider_max": 120,
|
||||
"timer_slider_step": 1
|
||||
},
|
||||
"tile_thresholds": {
|
||||
"SWG/PPM": {
|
||||
"outofrange": {
|
||||
"min": 2600,
|
||||
"max": 3500,
|
||||
"mintext": "Add Salt"
|
||||
},
|
||||
"attention": {
|
||||
"min": 2700,
|
||||
"max": 3400,
|
||||
"mintext": "Add Salt"
|
||||
}
|
||||
},
|
||||
"CHEM/pH": {
|
||||
"outofrange": {
|
||||
"min": 7,
|
||||
"max": 8
|
||||
},
|
||||
"attention": {
|
||||
"min": 7.2,
|
||||
"max": 7.8,
|
||||
"mintext": "Low",
|
||||
"maxtext": "High"
|
||||
}
|
||||
},
|
||||
"CHEM/ORP": {
|
||||
"outofrange": {
|
||||
"min": 560,
|
||||
"max": 900
|
||||
},
|
||||
"attention": {
|
||||
"min": 650,
|
||||
"max": 850,
|
||||
"mintext": "Low",
|
||||
"maxtext": "High"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swg_status": {
|
||||
"0": "On",
|
||||
"1": "No flow",
|
||||
"2": "Low salt",
|
||||
"4": "High salt",
|
||||
"8": "Clean cell",
|
||||
"9": "Turning off",
|
||||
"16": "High current",
|
||||
"32": "Low volts",
|
||||
"64": "Low temp",
|
||||
"128": "Check PCB",
|
||||
"253": "General Fault",
|
||||
"254": "Unknown",
|
||||
"255": "Off"
|
||||
},
|
||||
"tile_settings": {
|
||||
"turn_off_sensortiles": "true",
|
||||
"show_vsp_gpm": "true",
|
||||
"disable_off_icon_background": "true"
|
||||
},
|
||||
"EXAMPLE_colors_dark": {
|
||||
"body_background": "#121212",
|
||||
"body_text": "#FFFFFF",
|
||||
"options_pane_background": "#1E1E1E",
|
||||
"options_pane_bordercolor": "#555555",
|
||||
"options_slider_highlight": "#64B5F6",
|
||||
"options_slider_lowlight": "#555555",
|
||||
"head_background": "#004d7a",
|
||||
"head_text": "#FFFFFF",
|
||||
"error_background": "#b00020",
|
||||
"tile_background": "#333333",
|
||||
"tile_text": "#AAAAAA",
|
||||
"tile_on_background": "#BBBBBB",
|
||||
"tile_on_text": "#000000",
|
||||
"tile_status_text": "#888888",
|
||||
"value_tile_normal_color": "#049FF8",
|
||||
"value_tile_normal_color_": "#69F0AE",
|
||||
"value_tile_attention_color": "#FFB74D",
|
||||
"value_tile_outofrange_color": "#FF8A65",
|
||||
"options_radio_highlight": "#64B5F6",
|
||||
"options_radio_lowlight": "#555555",
|
||||
"tile_icon_background_color_heat": "rgb(255, 123, 0)",
|
||||
"tile_icon_background_color_cool": "rgb(4, 159, 248)",
|
||||
"tile_icon_background_color_enabled": "rgb(78, 196, 0)",
|
||||
"tile_icon_background_color_disabled": "rgb(110, 110, 110)"
|
||||
}
|
||||
}
|
||||
3347
web/controller.html
3347
web/controller.html
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
|||
aqmanager.html
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-off.png
|
||||
|
|
@ -1 +0,0 @@
|
|||
./switch-on.png
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue