diff --git a/MMM-IntelliCenter.js b/MMM-IntelliCenter.js index 32339e3..eb602c0 100644 --- a/MMM-IntelliCenter.js +++ b/MMM-IntelliCenter.js @@ -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}°${!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}°${!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 = `
-
-
-
`; - - 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: ``, - 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 = '
'; - dataHtml += ``; - dataHtml += `
${controlObj.name}: ${temperature}°
`; - dataHtml += ``; - - 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: ``, - 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}°${!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}°${!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 = `
+
+
+
`; + + 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}°`; + 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"); + }, }); diff --git a/node_helper.js b/node_helper.js index c0561de..1f8161f 100644 --- a/node_helper.js +++ b/node_helper.js @@ -40,39 +40,75 @@ let initialConnectDone = false; module.exports = NodeHelper.create({ setCircuit(circuitState) { - this.setCircuitState(circuitState, (poolStatus) => { - this.sendSocketNotification("INTELLICENTER_CIRCUIT_DONE", { - circuitState, - status: poolStatus, - }); - }); + if (!foundUnit) { + return; + } + + Log.info( + `[MMM-IntelliCenter] setting circuit ${circuitState.id} to ${!!circuitState.state}`, + ); + foundUnit.send( + messages.SetObjectStatus(circuitState.id, !!circuitState.state), + ); }, setHeatpoint(heatpoint) { - this.setHeatpointState(heatpoint, (poolStatus) => { - this.sendSocketNotification("INTELLICENTER_HEATPOINT_DONE", { - heatpoint, - status: poolStatus, - }); - }); + if (!foundUnit) { + return; + } + + let heatObjnam = ""; + if (heatpoint.body === "spa") { + heatObjnam = spaObjnam; + } else if (heatpoint.body === "pool") { + heatObjnam = poolObjnam; + } + + if (!heatObjnam) { + Log.error( + `[MMM-IntelliCenter] unable to determine objnam from given heatpoint body ${heatpoint.body} - expected "spa" or "pool"`, + ); + return; + } + + Log.info( + `[MMM-IntelliCenter] setting heatpoint for body ${heatObjnam} to ${heatpoint.temperature} deg`, + ); + foundUnit.send(messages.SetSetpoint(heatObjnam, heatpoint.temperature)); }, setHeatstate(heatstate) { - this.setHeatstateState(heatstate, (poolStatus) => { - this.sendSocketNotification("INTELLICENTER_HEATSTATE_DONE", { - heatstate, - status: poolStatus, - }); - }); + if (!foundUnit) { + return; + } + let heatObjnam = ""; + if (heatstate.body === "spa") { + heatObjnam = spaObjnam; + } else if (heatstate.body === "pool") { + heatObjnam = poolObjnam; + } + + if (!heatObjnam) { + Log.error( + `[MMM-IntelliCenter] unable to determine objnam from given heatstate body ${heatstate.body} - expected "spa" or "pool"`, + ); + return; + } + + Log.info( + `[MMM-IntelliCenter] setting heat state for body ${heatObjnam} to ${!!heatstate.state}`, + ); + foundUnit.send(messages.SetHeatMode(heatObjnam, !!heatstate.state)); }, setLightcmd(lightCmd) { - this.setLights(lightCmd, (poolStatus) => { - this.sendSocketNotification("INTELLICENTER_LIGHTCMD_DONE", { - lightCmd, - status: poolStatus, - }); - }); + if (!foundUnit) { + return; + } + + Log.info(`[MMM-IntelliCenter] sending light command ${lightCmd}`); + // NYI in node-intellicenter + // foundUnit.send(messages.SendLightCommand(lightCmd)); }, notifyReconnecting() { @@ -185,9 +221,9 @@ module.exports = NodeHelper.create({ if (obj.params.LOTMP) { poolData.poolSetPoint = parseInt(obj.params.LOTMP); } - // todo: HTSRC probably not the right check for this - if (obj.params.HTSRC) { - poolData.poolHeaterStatus = obj.params.HTSRC !== "00000"; + // todo: is MODE the right check for this? + if (obj.params.MODE) { + poolData.poolHeaterStatus = obj.params.MODE === "11"; } if (obj.params.STATUS) { poolData.poolStatus = obj.params.STATUS === "ON"; @@ -201,9 +237,9 @@ module.exports = NodeHelper.create({ if (obj.params.LOTMP) { poolData.spaSetPoint = parseInt(obj.params.LOTMP); } - // todo: HTSRC probably not the right check for this - if (obj.params.HTSRC) { - poolData.spaHeaterStatus = obj.params.HTSRC !== "00000"; + // todo: is MODE the right check for this? + if (obj.params.MODE) { + poolData.spaHeaterStatus = obj.params.MODE === "11"; } if (obj.params.STATUS) { poolData.spaStatus = obj.params.STATUS === "ON"; @@ -317,7 +353,7 @@ module.exports = NodeHelper.create({ await foundUnit.send( messages.SubscribeToUpdates(obj, [ "LOTMP", - "HTSRC", + "MODE", "STATUS", "LSTTMP", ]), @@ -413,54 +449,4 @@ module.exports = NodeHelper.create({ this.findServer(cb, reconnectCb); } }, - - setCircuitState(circuitState, cb) { - if (!foundUnit) { - cb(); - return; - } - - Log.info( - `[MMM-IntelliCenter] setting circuit ${circuitState.id} to ${circuitState.state}`, - ); - foundUnit.setCircuitState(0, circuitState.id, circuitState.state); - foundUnit.getPoolStatus(); - }, - - setHeatpointState(heatpoint, cb) { - if (!foundUnit) { - cb(); - return; - } - - Log.info( - `[MMM-IntelliCenter] setting heatpoint for body ${heatpoint.body} to ${heatpoint.temperature} deg`, - ); - foundUnit.setSetPoint(0, heatpoint.body, heatpoint.temperature); - foundUnit.getPoolStatus(); - }, - - setHeatstateState(heatstate, cb) { - if (!foundUnit) { - cb(); - return; - } - - Log.info( - `[MMM-IntelliCenter] setting heat state for body ${heatstate.body} to ${heatstate.state}`, - ); - foundUnit.setHeatMode(0, heatstate.body, heatstate.state); - foundUnit.getPoolStatus(); - }, - - setLights(lightCmd, cb) { - if (!foundUnit) { - cb(); - return; - } - - Log.info(`[MMM-IntelliCenter] sending light command ${lightCmd}`); - foundUnit.sendLightCommand(0, lightCmd); - foundUnit.getPoolStatus(); - }, });