Version 3.0.1

pull/492/head v3.0.1
sfeakes 2025-12-29 13:24:54 -06:00
parent ab5c8f591c
commit 1333597ce1
19 changed files with 316 additions and 217 deletions

View File

@ -153,6 +153,13 @@ AqualinkD will be moving over to github hosted runners for compiling, currently
<br>
<br>
# Updates in 3.0.1
* UI Update for web config.
* UI Now support for themes. (auto, dark, light -or- custom)
* Fixed UI not updating for sensors.
* Updates to UOM's for HA. gal/min, W, rpm
* Fix issue with multiple bad sensors in config.
# Updates in 3.0.0
* <B>NOTE:- When upgradeing to v3.0.0</b> if you see bank AqualinkD screen (ie no buttons), please clear browser cache. This also goes for the mobile/webapp (you may need to delete and re-add to mobile home screen)
* Serial optimization for AqualinkD HAT.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -10,7 +10,7 @@
#include "sensors.h"
//#include "aq_panel.h" // Moved to later in file to overcome circular dependancy. (crappy I know)
//#define DPRINTF(format, ...) printf("%s:%d: " format, __FILE__, __LINE__, ##__VA_ARGS__)
#define DPRINTF(format, ...) printf("%s:%d: " format, __FILE__, __LINE__, ##__VA_ARGS__)
//#define DPRINTF(format, ...)
#define isMASK_SET(bitmask, mask) ((bitmask & mask) == mask)

View File

@ -123,7 +123,7 @@ char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] =
"Mardi Gras", // 0x50 (home panel) // 0x4b (simulator)
"Cool Cabaret" // 0x51 (home panel) // 0x4c
},
{// 7 = Jandy Infinate Water Colors (RS485)
{// 7 = Jandy Infinite Water Colors (RS485)
"Off",
"Alpine White",
"Sky Blue",

View File

@ -243,6 +243,15 @@ void init_parameters (struct aqconfig * parms)
_cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.web_config;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_web_cfg_file;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].config_mask |= CFG_READONLY;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.paneltype_mask;
_cfgParams[_numCfgParams].value_type = CFG_SPECIAL;
@ -1628,17 +1637,21 @@ void check_print_config (struct aqualinkdata *aqdata)
}
// Make sure all sensors are fully populated
int sec = 0; // sensor error count
for (i=0; i < aqdata->num_sensors; i++ ) {
//if (aqdata->sensors[i].uom == NULL) {
// aqdata->sensors[i].uom = cleanalloc("°C");
//}
if ( aqdata->sensors[i].label == NULL || aqdata->sensors[i].path == NULL ) {
LOG(AQUA_LOG,LOG_ERR, "Invalid sensor %d, removing!\n",i+1);
LOG(AQUA_LOG,LOG_ERR, "Invalid sensor %d, removing!\n",i+1+sec);
sec++;
setMASK(errors, ERR_INVALID_SENSORS);
if (i == (aqdata->num_sensors-1) ) { // last sensor
// don't need to do anything, just reduce total number sensors
} else if (aqdata->num_sensors > 1) { // there are more sensors adter this bad one
//printf("Remove last sensor\n");
} else if (aqdata->num_sensors > 1) { // there are more sensors after this bad one
for (int j=i; j < aqdata->num_sensors; j++) {
//printf("Moved sensor %d to %d\n",(j+1),j);
aqdata->sensors[j].label = aqdata->sensors[j+1].label;
aqdata->sensors[j].path = aqdata->sensors[j+1].path;
aqdata->sensors[j].factor = aqdata->sensors[j+1].factor;
@ -1648,6 +1661,7 @@ void check_print_config (struct aqualinkdata *aqdata)
sprintf(aqdata->sensors[j].ID, "Aux_S%d", j+1);
//printf("Sensor %d = %s, %s\n",j,aqdata->sensors[j].ID,aqdata->sensors[j].label);
}
i--; // Need re-test i incase we have multiple blank sensors
}
if (aqdata->num_sensors > 1) {
aqdata->num_sensors --;

View File

@ -68,6 +68,7 @@ struct aqconfig
unsigned int log_level;
unsigned int mg_log_level;
char *web_directory;
char *web_config;
unsigned char device_id;
unsigned char rssa_device_id;
uint16_t paneltype_mask;
@ -257,6 +258,7 @@ int _numCfgParams;
#define CFG_N_cert_dir "https_cert_dir"
#define CFG_N_web_directory "web_directory"
#define CFG_N_web_cfg_file "web_config_json"
#define CFG_N_device_id "device_id"
#define CFG_V_device_id "[\"0x0a\", \"0x0b\", \"0x09\", \"0x08\", \"0x60\", \"0x33\", \"0xFF\"]"

View File

@ -636,7 +636,7 @@ void publish_mqtt_discovery(struct aqualinkdata *aqdata, struct mg_connection *n
"Pump",pn,"GPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","GPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_GPM_TOPIC,
"GPM");
"gal/min");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_discovery_topic, "Pump",pn,"GPM");
send_mqtt(nc, topic, msg);
@ -688,7 +688,7 @@ void publish_mqtt_discovery(struct aqualinkdata *aqdata, struct mg_connection *n
"Pump",pn,"RPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","RPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_RPM_TOPIC,
"RPM");
"rpm");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_discovery_topic, "Pump",pn,"RPM");
send_mqtt(nc, topic, msg);
@ -698,7 +698,7 @@ void publish_mqtt_discovery(struct aqualinkdata *aqdata, struct mg_connection *n
"Pump",pn,"Watts",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Watts",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_WATTS_TOPIC,
"Watts");
"W");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_discovery_topic, "Pump",pn,"Watts");
send_mqtt(nc, topic, msg);
}

View File

@ -1588,12 +1588,40 @@ void log_http_request(int level, char *message, struct mg_http_message *http_msg
strncpy(uri, http_msg->uri.buf, http_msg->uri.len + http_msg->query.len + 1);
uri[http_msg->uri.len + http_msg->query.len + 1] = '\0';
LOG(NET_LOG,level, "%s: '%s'\n", message, uri);
LOG(NET_LOG,level, "%s: '%s', length %d\n", message, uri, http_msg->uri.len);
free(uri);
}
void action_web_request(struct mg_connection *nc, struct mg_http_message *http_msg) {
void serve_file(struct mg_connection *nc, struct mg_http_message *http_msg)
{
// Anything with .json should not be cached.
if (FAST_SUFFIX_4_CI(http_msg->uri.buf, http_msg->uri.len, "json"))
{
if (_aqconfig_.web_config != NULL && strncmp(http_msg->uri.buf, "/config.json", 12) == 0)
{
mg_http_serve_file(nc, http_msg, _aqconfig_.web_config, &_http_server_opts_nocache);
LOG(NET_LOG, LOG_NOTICE, "Using %s for web config\n", _aqconfig_.web_config);
}
else
{
mg_http_serve_dir(nc, http_msg, &_http_server_opts_nocache);
}
}
else // can cache anything here.
{
if (http_msg->uri.len <= 12 && strncmp(http_msg->uri.buf, "/aqmanager", 10) == 0) {
char buf[256];
snprintf(buf, 256, "%s/aqmanager.html", _aqconfig_.web_directory);
mg_http_serve_file(nc, http_msg, buf, &_http_server_opts);
} else {
mg_http_serve_dir(nc, http_msg, &_http_server_opts);
}
}
}
void action_web_request(struct mg_connection *nc, struct mg_http_message *http_msg)
{
char *msg = NULL;
// struct http_message *http_msg = (struct http_message *)ev_data;
#ifdef AQ_TM_DEBUG
@ -1601,165 +1629,168 @@ void action_web_request(struct mg_connection *nc, struct mg_http_message *http_m
int tid2;
#endif
//DEBUG_TIMER_START(&tid);
if (getLogLevel(NET_LOG) >= LOG_INFO) { // Simply for log message, check we are at
// this log level before running all this junk
/*
char *uri = (char *)malloc(http_msg->uri.len + http_msg->query_string.len + 2);
strncpy(uri, http_msg->uri.p, http_msg->uri.len + http_msg->query_string.len + 1);
uri[http_msg->uri.len + http_msg->query_string.len + 1] = '\0';
LOG(NET_LOG,LOG_INFO, "URI request: '%s'\n", uri);
free(uri);*/
log_http_request(LOG_INFO, "URI request: ", http_msg);
// DEBUG_TIMER_START(&tid);
if (getLogLevel(NET_LOG) >= LOG_INFO)
{ // Simply for log message, check we are at
log_http_request(LOG_INFO, "URI request", http_msg);
}
//DEBUG_TIMER_STOP(tid, NET_LOG, "action_web_request debug print crap took");
// DEBUG_TIMER_STOP(tid, NET_LOG, "action_web_request debug print crap took");
//LOG(NET_LOG,LOG_INFO, "Message request:\n'%.*s'\n", http_msg->message.len, http_msg->message.p);
// LOG(NET_LOG,LOG_INFO, "Message request:\n'%.*s'\n", http_msg->message.len, http_msg->message.p);
// If we have a get request, pass it
if (strncmp(http_msg->uri.buf, "/api", 4 ) != 0) {
DEBUG_TIMER_START(&tid);
//if ( FAST_SUFFIX_3_CI(http_msg->uri.buf, http_msg->uri.len, ".js") ) {
if ( FAST_SUFFIX_4_CI(http_msg->uri.buf, http_msg->uri.len, "json") ) {
mg_http_serve_dir(nc, http_msg, &_http_server_opts_nocache);
} else {
mg_http_serve_dir(nc, http_msg, &_http_server_opts);
}
DEBUG_TIMER_STOP(tid, NET_LOG, "action_web_request() serve file took");
} else {
char buf[JSON_BUFFER_SIZE];
float value = 0;
// If we have a get request (ie file), pass it
if (strncmp(http_msg->uri.buf, "/api", 4) != 0)
{
DEBUG_TIMER_START(&tid);
// If query string.
if (http_msg->query.len > 1) {
//mg_get_http_var(&http_msg->query, "value", buf, sizeof(buf)); // Old mosquitto
mg_http_get_var(&http_msg->query, "value", buf, sizeof(buf));
value = atof(buf);
} else if (http_msg->body.len > 1) {
value = pass_mg_body(&http_msg->body);
}
int len = mg_url_decode(http_msg->uri.buf, http_msg->uri.len, buf, 50, 0);
if (strncmp(http_msg->uri.buf, "/api/",4) == 0) {
switch (action_URI(NET_API, &buf[5], len-5, value, false, &msg)) {
case uActioned:
mg_http_reply(nc, 200, CONTENT_TEXT, GET_RTN_OK);
break;
case uDevices:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_device_JSON(_aqualink_data, message, JSON_BUFFER_SIZE, false);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_device_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uHomebridge:
{
char message[JSON_BUFFER_SIZE];
build_device_JSON(_aqualink_data, message, JSON_BUFFER_SIZE, true);
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uStatus:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_aqualink_status_JSON(_aqualink_data, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_aqualink_status_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uDynamicconf:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
/*
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);
*/
build_dynamic_webconfig_json(_aqualink_data, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_webconfig_json took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uSchedules:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_schedules_js(message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_schedules_js took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uSetSchedules:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
save_schedules_js(http_msg->body.buf, http_msg->body.len, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() save_schedules_js took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uConfig:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_aqualink_config_JSON(message, JSON_BUFFER_SIZE, _aqualink_data);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_aqualink_config_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
#ifndef AQ_MANAGER
case uDebugStatus:
{
char message[JSON_BUFFER_SIZE];
snprintf(message,80,"{\"sLevel\":\"%s\", \"iLevel\":%d, \"logReady\":\"%s\"}\n",elevel2text(getLogLevel(NET_LOG)),getLogLevel(NET_LOG),islogFileReady()?"true":"false" );
mg_http_reply(nc, 200, CONTENT_JS, message);
}
break;
#else
case uLogDownload:
//int lines = 1000;
#define DEFAULT_LOG_DOWNLOAD_LINES 100
// If lines was passed in post use it, if not see if it's next path in URI is a number
if (value == 0.0) {
// /api/<downloadmsg>/<lines>
char *pt = rsm_lastindexof(buf, "/", strlen(buf));
value = atoi(pt+1);
}
LOG(NET_LOG, LOG_DEBUG, "Downloading log of max %d lines\n",value>0?(int)value:DEFAULT_LOG_DOWNLOAD_LINES);
if (write_systemd_logmessages_2file("/dev/shm/aqualinkd.log", value>0?(int)value:DEFAULT_LOG_DOWNLOAD_LINES) ) {
mg_http_serve_file(nc, http_msg, "/dev/shm/aqualinkd.log", &_http_server_opts_nocache);
remove("/dev/shm/aqualinkd.log");
}
break;
case uConfigDownload:
LOG(NET_LOG, LOG_DEBUG, "Downloading config\n");
mg_http_serve_file(nc, http_msg, _aqconfig_.config_file, &_http_server_opts_nocache);
break;
#endif
case uBad:
default:
if (msg == NULL) {
mg_http_reply(nc, 400, CONTENT_TEXT, GET_RTN_UNKNOWN);
} else {
mg_http_reply(nc, 400, CONTENT_TEXT, msg);
}
break;
}
} else {
mg_http_reply(nc, 400, CONTENT_TEXT, GET_RTN_UNKNOWN);
}
sprintf(buf, "action_web_request() request '%.*s' took",(int)http_msg->uri.len, http_msg->uri.buf);
DEBUG_TIMER_STOP(tid, NET_LOG, buf);
serve_file(nc, http_msg);
DEBUG_TIMER_STOP(tid, NET_LOG, "action_web_request() serve file took");
return;
}
char buf[JSON_BUFFER_SIZE];
float value = 0;
DEBUG_TIMER_START(&tid);
// If query string.
if (http_msg->query.len > 1)
{
// mg_get_http_var(&http_msg->query, "value", buf, sizeof(buf)); // Old mosquitto
mg_http_get_var(&http_msg->query, "value", buf, sizeof(buf));
value = atof(buf);
}
else if (http_msg->body.len > 1)
{
value = pass_mg_body(&http_msg->body);
}
int len = mg_url_decode(http_msg->uri.buf, http_msg->uri.len, buf, 50, 0);
if (strncmp(http_msg->uri.buf, "/api/", 4) == 0)
{
switch (action_URI(NET_API, &buf[5], len - 5, value, false, &msg))
{
case uActioned:
mg_http_reply(nc, 200, CONTENT_TEXT, GET_RTN_OK);
break;
case uDevices:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_device_JSON(_aqualink_data, message, JSON_BUFFER_SIZE, false);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_device_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uHomebridge:
{
char message[JSON_BUFFER_SIZE];
build_device_JSON(_aqualink_data, message, JSON_BUFFER_SIZE, true);
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uStatus:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_aqualink_status_JSON(_aqualink_data, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_aqualink_status_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uDynamicconf:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
/*
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);
*/
build_dynamic_webconfig_json(_aqualink_data, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_webconfig_json took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uSchedules:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_schedules_js(message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_schedules_js took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uSetSchedules:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
save_schedules_js(http_msg->body.buf, http_msg->body.len, message, JSON_BUFFER_SIZE);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() save_schedules_js took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
case uConfig:
{
char message[JSON_BUFFER_SIZE];
DEBUG_TIMER_START(&tid2);
build_aqualink_config_JSON(message, JSON_BUFFER_SIZE, _aqualink_data);
DEBUG_TIMER_STOP(tid2, NET_LOG, "action_web_request() build_aqualink_config_JSON took");
mg_http_reply(nc, 200, CONTENT_JSON, message);
}
break;
#ifndef AQ_MANAGER
case uDebugStatus:
{
char message[JSON_BUFFER_SIZE];
snprintf(message, 80, "{\"sLevel\":\"%s\", \"iLevel\":%d, \"logReady\":\"%s\"}\n", elevel2text(getLogLevel(NET_LOG)), getLogLevel(NET_LOG), islogFileReady() ? "true" : "false");
mg_http_reply(nc, 200, CONTENT_JS, message);
}
break;
#else
case uLogDownload:
// int lines = 1000;
#define DEFAULT_LOG_DOWNLOAD_LINES 100
// If lines was passed in post use it, if not see if it's next path in URI is a number
if (value == 0.0)
{
// /api/<downloadmsg>/<lines>
char *pt = rsm_lastindexof(buf, "/", strlen(buf));
value = atoi(pt + 1);
}
LOG(NET_LOG, LOG_DEBUG, "Downloading log of max %d lines\n", value > 0 ? (int)value : DEFAULT_LOG_DOWNLOAD_LINES);
if (write_systemd_logmessages_2file("/dev/shm/aqualinkd.log", value > 0 ? (int)value : DEFAULT_LOG_DOWNLOAD_LINES))
{
mg_http_serve_file(nc, http_msg, "/dev/shm/aqualinkd.log", &_http_server_opts_nocache);
remove("/dev/shm/aqualinkd.log");
}
break;
case uConfigDownload:
LOG(NET_LOG, LOG_DEBUG, "Downloading config\n");
mg_http_serve_file(nc, http_msg, _aqconfig_.config_file, &_http_server_opts_nocache);
break;
#endif
case uBad:
default:
if (msg == NULL)
{
mg_http_reply(nc, 400, CONTENT_TEXT, GET_RTN_UNKNOWN);
}
else
{
mg_http_reply(nc, 400, CONTENT_TEXT, msg);
}
break;
}
}
else
{
mg_http_reply(nc, 400, CONTENT_TEXT, GET_RTN_UNKNOWN);
}
sprintf(buf, "action_web_request() request '%.*s' took", (int)http_msg->uri.len, http_msg->uri.buf);
DEBUG_TIMER_STOP(tid, NET_LOG, buf);
}
void action_websocket_request(struct mg_connection *nc, struct mg_ws_message *wm) {

View File

@ -145,7 +145,7 @@ bool read_sensor(external_sensor *sensor) {
status = regcomp(&preg, sensor->regex, REG_EXTENDED);
if (status != 0) {
LOG(AQUA_LOG,LOG_ERR, "Compiling sensor regex %s\n",sensor->regex);
return 1;
return FALSE;
}
// Run regex

View File

@ -4,5 +4,5 @@
#define AQUALINKD_SHORT_NAME "AqualinkD"
// Use Magor . Minor . Patch
#define AQUALINKD_VERSION "3.0.0"
#define AQUALINKD_VERSION "3.0.1"

View File

@ -84,8 +84,11 @@ int save_web_config_json(const char* inBuf, int inSize, char* outBuf, int outSiz
bool created_file;
char *contents;
snprintf(configfile, 256, "%s%s", _aqconfig_.web_directory,WEBCONFIGFILE);
if ( _aqconfig_.web_config !=NULL) {
snprintf(configfile, 256, "%s", _aqconfig_.web_config);
} else {
snprintf(configfile, 256, "%s%s", _aqconfig_.web_directory,WEBCONFIGFILE);
}
fp = aq_open_file(configfile, &ro_root, &created_file);

View File

@ -450,6 +450,7 @@
.invalid_json {
background-color: rgb(255, 100, 0);
border-radius: 9px !important;
}
.textarea-container {
@ -1585,7 +1586,7 @@
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 - 180) + 'px');
document.documentElement.style.setProperty('--web-config-textarea-height', (h - 190) + 'px');
document.getElementById("web_config_options").classList.remove("hide");
document.getElementById("web_config_options_pane").classList.remove("hide");
// Link the scroll for the two text areas.
@ -2492,6 +2493,11 @@
<!-- <input type="button" onclick="updateWebConfigFromMaster()" value="get new"></input> -->
</td>
</tr>
<tr>
<td colspan="3" align="right" style="width:100%;padding:0px;">
<input type="button" onclick="updateWebConfigFromMaster()" value="Get latest config from Github" style="scale:0.75;"></input>
</td>
</tr>
<tr>
<td colspan="3">
<div id="validation-result" style="padding-left: 10px;padding-right: 10px;"></div>

View File

@ -1,9 +1,12 @@
{
"theme": "",
"background_image": {
"url": "hk/background.jpg",
"url-light": "hk/background-light.jpg",
"url-dark": "hk/background-dark.jpg",
"url_reload": 0
},
"colors": {
"colors-light": {
"body_background": "#EBEBEB",
"body_text": "#000000",
"options_pane_background": "#F5F5F5",
@ -26,7 +29,34 @@
"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)"
"tile_icon_background_color_disabled": "rgb(110, 110, 110)",
"tile_off_opacity": "0.90"
},
"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": "#7b7b7b",
"value_tile_normal_color": "#049FF8",
"value_tile_attention_color": "#b04300",
"value_tile_outofrange_color": "#b02300",
"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)",
"tile_off_opacity": "0.90"
},
"devices": {
"Filter_Pump": {
@ -272,31 +302,5 @@
"show_vsp_gpm": "true",
"disable_off_icon_background": "true",
"light_program_use_dropdown": "false"
},
"colors-dark-example": {
"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)"
}
}

BIN
web/hk/background-dark.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

BIN
web/hk/background-light.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

View File

@ -9,6 +9,7 @@
<meta name='mobile-web-app-capable' content='yes'>
<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-title" content="AqualinkD">
<link href='aqualinkd.png' rel='apple-touch-icon'>
@ -71,6 +72,8 @@
--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);
--tile_off_opacity: 0.95;
}
div {
@ -256,8 +259,8 @@
padding: 5px;
height: var(--tile-width);
width: var(--tile-width);
filter: opacity(95%);
opacity: 0.95;
/*filter: opacity(90%);*/
opacity: var(--tile_off_opacity);
}
.on {
@ -763,6 +766,7 @@
var _tile_line_char_limit = 0;
var _devices = [];
var _enable_schedules = false;
var _theme = "";
var _switchOnUri;
var _switchOffUri;
@ -770,8 +774,8 @@
var _DisableOffIconCircle = true;
//_switchOnUri = createSwitchIcon("rgb(255, 255, 255)", false);
//_switchOffUri = createSwitchIcon(_DisableOffIconCircle?"rgb(110, 110, 110)":"rgb(220, 220, 220)", _DisableOffIconCircle);
const _isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
//console.log(_isDarkMode ? "Dark mode is on" : "Light mode is on");
async function loadConfig() {
try {
@ -790,6 +794,31 @@
//_targetClearErrorMsg = new Date(Date.now() + 5000); // Set 5 sec from now.
}
// Set current theme
if ( _config?.theme === "auto" ) {
_theme = window.matchMedia("(prefers-color-scheme: dark)").matches?"-dark":"-light";
} else if ( _config?.theme ) {
_theme = "-"+_config?.theme;
} else {
_theme = "";
}
// Check we have correct objects for theme.
if (_config?.background_image?.["url"+_theme]) {
// console.log("Found background image - "+_config?.background_image?.["url"+_theme]);
} else {
console.log("ERROR: No image background found for theme, json path \"background_image\": {\"url"+_theme+"\":.....}`");
}
if (_config?.["colors"+_theme]) {
//console.log("Found colors - colors"+_theme+"{..}");
} else {
if ( _theme !== "" ) {
// Don't error on blank theme, as we use default colors.
console.log("ERROR: No colors found for theme '"+_theme+"', json path \"colors"+_theme+"\":{...}`");
}
}
// Set defaults.
if (typeof _config.background_image?.url === 'undefined') {
@ -798,8 +827,9 @@
_config.background_image.url_reload = 0;
}
if (_config?.colors?.tile_on_background) {
_switchOnUri = createSwitchIcon(_config.colors.tile_on_background, false);
//if (_config?.colors?.tile_on_background) {
if (_config?.["colors"+_theme]?.tile_on_background) {
_switchOnUri = createSwitchIcon(_config["colors"+_theme].tile_on_background, false);
} else {
_switchOnUri = createSwitchIcon("rgb(255, 255, 255)", false);
}
@ -811,14 +841,16 @@
}
if (_DisableOffIconCircle) {
if (_config?.colors?.tile_icon_background_color_disabled) {
_switchOffUri = createSwitchIcon(_config.colors.tile_icon_background_color_disabled, _DisableOffIconCircle);
//if (_config?.colors?.tile_icon_background_color_disabled) {
if (_config?.["colors"+_theme]?.tile_icon_background_color_disabled) {
_switchOffUri = createSwitchIcon(_config["colors"+_theme].tile_icon_background_color_disabled, _DisableOffIconCircle);
} else {
_switchOffUri = createSwitchIcon("rgb(110, 110, 110)", _DisableOffIconCircle);
}
} else {
if (_config?.colors?.tile_background) {
_switchOffUri = createSwitchIcon(_config.colors.tile_background, _DisableOffIconCircle);
//if (_config?.colors?.tile_background) {
if (_config?.["colors"+_theme]?.tile_background) {
_switchOffUri = createSwitchIcon(_config["colors"+_theme].tile_background, _DisableOffIconCircle);
} else {
_switchOffUri = createSwitchIcon("rgb(220, 220, 220)", _DisableOffIconCircle);
}
@ -1103,13 +1135,13 @@
// Simply loop over each key/value in color jason array and set that property adding -- to the beginning of key
function setColors() {
if ( _config?.colors ) {
if ( _config?.["colors"+_theme] ) {
const rootElement = document.documentElement;
Object.entries(_config.colors).forEach(([key, value]) => {
Object.entries(_config["colors"+_theme]).forEach(([key, value]) => {
//console.log(`Key (Color Variable): ${key}, Value (Color Code): ${value}`);
//console.log(key+ " " +document.documentElement.style.getPropertyValue('--' + key)+" | "+window.getComputedStyle(document.documentElement).getPropertyValue('--' + key));
if (window.getComputedStyle(rootElement).getPropertyValue('--'+key) && _config?.colors[key]) {
if (window.getComputedStyle(rootElement).getPropertyValue('--'+key) && _config?.["colors"+_theme][key]) {
document.documentElement.style.setProperty('--'+key, value);
} else {
console.log(`Invalid config (Color Variable): ${key}, Value : ${value}`);
@ -1215,9 +1247,9 @@
}
function load_background() {
// check for undevined AND defined but empty
// check for undefined AND defined but empty
//if (typeof background_url === 'undefined' || background_url === undefined)
if (!_config.background_image?.url) {
if (!_config.background_image?.["url"+_theme]) {
return;
}
//alert("load");
@ -1233,13 +1265,13 @@
//if (typeof background_reload !== 'undefined' && background_reload > 0) {
if ( _config.background_image?.url_reload > 0) {
if (_config.background_image.url.indexOf('?') > 0 )
image.src = _config.background_image.url + '&aqrnd=' + new Date().getTime();
image.src = _config.background_image["url"+_theme] + '&aqrnd=' + new Date().getTime();
else
image.src = _config.background_image.url + '?' + new Date().getTime();
image.src = _config.background_image["url"+_theme] + '?' + new Date().getTime();
setTimeout(load_background, (_config.background_image.url_reload * 1000));
} else {
image.src = _config.background_image.url;
image.src = _config.background_image["url"+_theme];
}
}
// If the grid continer is larger than the page container, increase page so background looks nice
@ -1566,7 +1598,7 @@
}
} catch (e) {
// Devices that are hidden are not found
//console.log('ERROR id=' + id + ' Line '+new Error().lineNumber+' | element = '+document.getElementById(id)+"\n"+e);
console.log('ERROR id=' + id + ' Line '+new Error().lineNumber+' | element = '+document.getElementById(id)+"\n"+e);
}
//document.getElementById(id + '_tile_icon_value').textContent = value;
var tile;
@ -3154,8 +3186,8 @@
for (var obj in data.sensors) {
//console.log("Set Value `Sensor/"+obj.toString()+"' to "+data.sensors[obj]);
//setTileValue("Sensor/"+obj.toString(), data.sensors[obj]);
setTileValue(obj.toString(), data.sensors[obj]);
setTileValue("Sensor/"+obj.toString(), data.sensors[obj]);
//setTileValue(obj.toString(), data.sensors[obj]);
}
for (var obj in data.alternate_modes) {