Add button-pressing functionality

Circuits, heat modes, and heat setpoints all function as expected now.

Also refactored a little bit for better code practices and created DOM controls through more appropriate means than just a bunch of strings, which allowed access to member functions rather than loose functions that were problematic for linting, accessing the appropriate unit, etc.
This commit is contained in:
2025-01-06 00:06:36 -06:00
parent ff0a89d7e4
commit 7749156399
2 changed files with 367 additions and 249 deletions

View File

@ -1,8 +1,8 @@
/* global Module Log document */
let poolData;
Module.register("MMM-IntelliCenter", {
poolData: null,
defaults: {
showPoolTemp: true,
showSpaTemp: true,
@ -21,7 +21,7 @@ Module.register("MMM-IntelliCenter", {
showPHTankLevel: true,
pHTankLevelMax: 7,
serverAddress: "",
serverPort: 0,
serverPort: 6680,
multicastInterface: "",
},
@ -44,7 +44,7 @@ Module.register("MMM-IntelliCenter", {
},
getDom() {
if (!poolData) {
if (!this.poolData) {
const wrapper = document.createElement("div");
wrapper.innerHTML = "Loading IntelliCenter...";
wrapper.className += "dimmed light small text-center";
@ -74,174 +74,37 @@ Module.register("MMM-IntelliCenter", {
const contents = [];
if (this.config.showPoolTemp) {
let className = "";
if (poolData.poolTemp <= this.config.coldTemp) {
className += " cold-temp";
} else if (poolData.poolTemp >= this.config.hotTemp) {
className += " hot-temp";
}
contents.push({
header: "Pool temp",
data: `${poolData.poolTemp}&deg;${!poolData.poolStatus ? " (last)" : ""}`,
class: this.config.contentClass + className,
});
let poolTemp = this.getPoolTempDom();
contents.push(poolTemp);
}
if (this.config.showSpaTemp) {
let className = "";
if (poolData.spaTemp <= this.config.coldTemp) {
className = " cold-temp";
} else if (poolData.spaTemp >= this.config.hotTemp) {
className = " hot-temp";
}
contents.push({
header: "Spa temp",
data: `${poolData.spaTemp}&deg;${!poolData.spaStatus ? " (last)" : ""}`,
class: this.config.contentClass + className,
});
let spaTemp = this.getSpaTempDom();
contents.push(spaTemp);
}
if (this.config.showPH) {
let dataStr = poolData.lastPHVal;
if (this.config.showPHTankLevel) {
const percent = Math.round(
((poolData.phTank - 1) / this.config.pHTankLevelMax) * 100,
);
let cls = "";
if (this.config.colored) {
if (percent <= 17) {
cls = "progress-bar-danger";
} else if (percent <= 33) {
cls = "progress-bar-warning";
} else {
cls = "progress-bar-success";
}
}
const progBarDiv = `<div class="progress vertical">
<div class="progress-bar ${cls}" role="progressbar" aria-valuenow="${percent}" aria-valuemin="0" aria-valuemax="100" style="width: ${percent}%;">
</div>
</div>`;
dataStr = `${dataStr} ${progBarDiv}`;
}
contents.push({
header: "pH",
data: dataStr,
class: this.config.contentClass,
});
let ph = this.getPHDom();
contents.push(ph);
}
if (this.config.showOrp) {
contents.push({
header: "ORP",
data: poolData.lastOrpVal.toString(),
class: this.config.contentClass,
});
contents.push(this.getOrpDom());
}
if (this.config.showSaltLevel) {
contents.push({
header: "Salt PPM",
data: poolData.saltPPM.toString(),
class: this.config.contentClass,
});
contents.push(this.getSaltDom());
}
if (this.config.showSaturation) {
contents.push({
header: "Saturation",
data: poolData.saturation.toString(),
class: this.config.contentClass,
});
contents.push(this.getSaturationDom());
}
if (this.config.showControls) {
for (const control in this.config.controls) {
const controlObj = this.config.controls[control];
if (controlObj.type === "circuit") {
let { name } = controlObj;
let on = false;
if (poolData.circuits[controlObj.id]) {
name ??= poolData.circuits[controlObj.id].name;
on = poolData.circuits[controlObj.id].status;
}
let cls = "";
if (this.config.colored) {
cls = on ? "control-on" : "control-off";
}
contents.push({
data: `<button id="sl-control-${controlObj.id}" class="control ${cls}" onclick="setCircuit(this)" data-circuit="${
controlObj.id
}" data-state="${on ? "1" : "0"}"><div class="content">${
name
}</div></button>`,
class: this.config.contentClass,
});
} else if (controlObj.type === "heatpoint") {
// todo: if "body" isn't defined in the user's config correctly, this will error out
const body = controlObj.body.toLowerCase();
if (body !== "pool" && body !== "spa") {
Log.warn(
"Invalid body specified for heatpoint. Valid bodies: pool, spa",
);
continue;
}
const temperature =
body === "pool"
? poolData.poolSetPoint.toString()
: poolData.spaSetPoint.toString();
let dataHtml = '<div class="temperature-container">';
dataHtml += `<button id="sl-temp-up-${controlObj.body}" class="temperature control-off" onclick="setHeatpoint(this, 1)" data-body="${controlObj.body}" data-temperature="${temperature}"><div class="content">+</div></button>`;
dataHtml += `<div class="temperature-label">${controlObj.name}: ${temperature}&deg;</div>`;
dataHtml += `<button id="sl-temp-down-${controlObj.body}" class="temperature control-off" onclick="setHeatpoint(this, -1)" data-body="${controlObj.body}" data-temperature="${temperature}"><div class="content">-</div></button>`;
contents.push({
data: dataHtml,
class: this.config.contentClass,
});
} else if (controlObj.type === "heatmode") {
// todo: if "body" isn't defined in the user's config correctly, this will error out
const body = controlObj.body.toLowerCase();
if (body !== "pool" && body !== "spa") {
Log.warn(
"Invalid body specified for heatmode. Valid bodies: pool, spa",
);
continue;
}
const on =
body === "pool"
? poolData.poolHeaterStatus
: poolData.spaHeaterStatus;
const mode =
typeof controlObj.heatMode === "number" ? controlObj.heatMode : 3;
let cls = "";
if (this.config.colored) {
cls = on ? "control-on" : "control-off";
}
contents.push({
data: `<button id="sl-heat-${controlObj.body}" class="control ${cls}" onclick="setHeatmode(this)" data-body="${
controlObj.body
}" data-state="${on ? "1" : "0"}" data-mode="${mode.toString()}"><div class="content">${
controlObj.name
}</div></button>`,
class: this.config.contentClass,
});
} else {
Log.warn("circuit with unknown type, unable to display:");
Log.warn(controlObj);
}
let controls = this.getControlsDom();
for (const control of controls) {
contents.push(control);
}
}
let headerRow = null;
let contentRow = null;
if (this.config.showFreezeMode && poolData.freezeMode) {
if (this.config.showFreezeMode && this.poolData.freezeMode) {
const row = document.createElement("tr");
table.appendChild(row);
row.className = "cold-temp";
@ -252,7 +115,7 @@ Module.register("MMM-IntelliCenter", {
}
let cols = -1;
for (const item in contents) {
for (const item of contents) {
cols++;
if (cols % this.config.columns === 0) {
headerRow = document.createElement("tr");
@ -261,32 +124,271 @@ Module.register("MMM-IntelliCenter", {
table.appendChild(contentRow);
}
if (contents[item].header) {
if (item.header) {
const headerCell = document.createElement("th");
headerCell.innerHTML = contents[item].header;
headerCell.innerHTML = item.header;
headerRow.appendChild(headerCell);
}
const contentCell = document.createElement("td");
contentCell.innerHTML = contents[item].data;
contentCell.className = contents[item].class;
if (item.dom) {
contentCell.appendChild(item.dom);
} else {
contentCell.innerHTML = item.data;
}
contentCell.className = item.class;
contentRow.appendChild(contentCell);
}
return outermost;
},
getPoolTempDom() {
let className = "";
if (this.poolData.poolTemp <= this.config.coldTemp) {
className += " cold-temp";
} else if (this.poolData.poolTemp >= this.config.hotTemp) {
className += " hot-temp";
}
return {
header: "Pool temp",
data: `${this.poolData.poolTemp}&deg;${!this.poolData.poolStatus ? " (last)" : ""}`,
class: this.config.contentClass + className,
};
},
getSpaTempDom() {
let className = "";
if (this.poolData.spaTemp <= this.config.coldTemp) {
className = " cold-temp";
} else if (this.poolData.spaTemp >= this.config.hotTemp) {
className = " hot-temp";
}
return {
header: "Spa temp",
data: `${this.poolData.spaTemp}&deg;${!this.poolData.spaStatus ? " (last)" : ""}`,
class: this.config.contentClass + className,
};
},
getPHDom() {
let dataStr = this.poolData.lastPHVal;
if (this.config.showPHTankLevel) {
const percent = Math.round(
((this.poolData.phTank - 1) / this.config.pHTankLevelMax) * 100,
);
let cls = "";
if (this.config.colored) {
if (percent <= 17) {
cls = "progress-bar-danger";
} else if (percent <= 33) {
cls = "progress-bar-warning";
} else {
cls = "progress-bar-success";
}
}
const progBarDiv = `<div class="progress vertical">
<div class="progress-bar ${cls}" role="progressbar" aria-valuenow="${percent}" aria-valuemin="0" aria-valuemax="100" style="width: ${percent}%;">
</div>
</div>`;
dataStr = `${dataStr} ${progBarDiv}`;
}
return {
header: "pH",
data: dataStr,
class: this.config.contentClass,
};
},
getOrpDom() {
return {
header: "ORP",
data: this.poolData.lastOrpVal.toString(),
class: this.config.contentClass,
};
},
getSaltDom() {
return {
header: "Salt PPM",
data: this.poolData.saltPPM.toString(),
class: this.config.contentClass,
};
},
getSaturationDom() {
return {
header: "Saturation",
data: this.poolData.saturation.toString(),
class: this.config.contentClass,
};
},
getControlsDom() {
let controls = [];
for (const controlObj of this.config.controls) {
if (controlObj.type === "circuit") {
let circuit = this.getCircuitDom(controlObj);
controls.push(circuit);
} else if (controlObj.type === "heatpoint") {
let circuit = this.getHeatpointDom(controlObj);
if (circuit) {
controls.push(circuit);
}
} else if (controlObj.type === "heatmode") {
let circuit = this.getHeatmodeDom(controlObj);
if (circuit) {
controls.push(circuit);
}
} else {
Log.warn("circuit with unknown type, unable to display:");
Log.warn(controlObj);
}
}
return controls;
},
getCircuitDom(controlObj) {
let { name } = controlObj;
let on = false;
if (this.poolData.circuits[controlObj.id]) {
name ??= this.poolData.circuits[controlObj.id].name;
on = this.poolData.circuits[controlObj.id].status;
}
let cls = "";
if (this.config.colored) {
cls = on ? "control-on" : "control-off";
}
const button = document.createElement("button");
button.id = `sl-control-${controlObj.id}`;
button.classList.add("control", cls);
button.onclick = (e) => {
this.setCircuit(e.currentTarget);
};
button.dataset.circuit = controlObj.id;
button.dataset.state = on ? 1 : 0;
const content = document.createElement("div");
content.classList.add("content");
content.innerText = name;
button.appendChild(content);
return {
dom: button,
class: this.config.contentClass,
};
},
getHeatpointDom(controlObj) {
// todo: if "body" isn't defined in the user's config correctly, this will error out
const body = controlObj.body.toLowerCase();
if (body !== "pool" && body !== "spa") {
Log.warn("Invalid body specified for heatpoint. Valid bodies: pool, spa");
return;
}
const temperature =
body === "pool"
? this.poolData.poolSetPoint.toString()
: this.poolData.spaSetPoint.toString();
const div = document.createElement("div");
div.classList.add("temperature-container");
const buttonUp = document.createElement("button");
buttonUp.id = `sl-temp-up-${controlObj.body}`;
buttonUp.classList.add("control-off", "temperature");
buttonUp.onclick = (e) => {
this.setHeatpoint(e.currentTarget, 1);
};
buttonUp.dataset.body = controlObj.body;
buttonUp.dataset.temperature = temperature;
const contentUp = document.createElement("div");
contentUp.classList.add("content");
contentUp.innerText = "+";
buttonUp.appendChild(contentUp);
div.appendChild(buttonUp);
const label = document.createElement("div");
label.classList.add("temperature-label");
label.innerHTML = `${controlObj.name}: ${temperature}&deg;`;
div.appendChild(label);
const buttonDown = document.createElement("button");
buttonDown.id = `sl-temp-down-${controlObj.body}`;
buttonDown.classList.add("control-off", "temperature");
buttonDown.onclick = (e) => {
this.setHeatpoint(e.currentTarget, -1);
};
buttonDown.dataset.body = controlObj.body;
buttonDown.dataset.temperature = temperature;
const contentDown = document.createElement("div");
contentDown.classList.add("content");
contentDown.innerText = "-";
buttonDown.appendChild(contentDown);
div.appendChild(buttonDown);
return {
dom: div,
class: this.config.contentClass,
};
},
getHeatmodeDom(controlObj) {
// todo: if "body" isn't defined in the user's config correctly, this will error out
const body = controlObj.body.toLowerCase();
if (body !== "pool" && body !== "spa") {
Log.warn("Invalid body specified for heatmode. Valid bodies: pool, spa");
return;
}
const on =
body === "pool"
? this.poolData.poolHeaterStatus
: this.poolData.spaHeaterStatus;
let cls = "";
if (this.config.colored) {
cls = on ? "control-on" : "control-off";
}
const button = document.createElement("button");
button.id = `sl-heat-${controlObj.body}`;
button.classList.add("control", cls);
button.onclick = (e) => {
this.setHeatmode(e.currentTarget);
};
button.dataset.body = controlObj.body;
button.dataset.state = on ? 1 : 0;
const content = document.createElement("div");
content.classList.add("content");
content.innerText = controlObj.name;
button.appendChild(content);
return {
dom: button,
class: this.config.contentClass,
};
},
socketNotificationReceived(notification, payload) {
if (notification === "INTELLICENTER_RESULT") {
poolData = payload;
this.updateDom();
this.showReconnectOverlay(false);
} else if (
notification === "INTELLICENTER_CIRCUIT_DONE" ||
notification === "INTELLICENTER_HEATSTATE_DONE" ||
notification === "INTELLICENTER_HEATPOINT_DONE"
) {
poolData = payload;
this.poolData = payload;
this.updateDom();
this.showReconnectOverlay(false);
} else if (notification === "INTELLICENTER_RECONNECTING") {
@ -306,4 +408,34 @@ Module.register("MMM-IntelliCenter", {
element.classList.add("d-none");
}
},
setCircuit(e) {
const circuitId = e.dataset.circuit;
const on = e.dataset.state !== "0";
this.sendSocketNotification("INTELLICENTER_CIRCUIT", {
id: circuitId,
state: on ? 0 : 1,
});
e.classList.remove("control-on", "control-off");
},
setHeatmode(e) {
const bodyId = e.dataset.body;
const on = e.dataset.state !== "0";
this.sendSocketNotification("INTELLICENTER_HEATSTATE", {
body: bodyId,
state: on ? 0 : 1,
});
e.classList.remove("control-on", "control-off");
},
setHeatpoint(e, tempChange) {
const bodyId = e.dataset.body;
const temp = parseInt(e.dataset.temperature) + tempChange;
this.sendSocketNotification("INTELLICENTER_HEATPOINT", {
body: bodyId,
temperature: temp,
});
e.classList.remove("control-on", "control-off");
},
});