mirror of
https://github.com/parnic/MMM-IntelliCenter.git
synced 2025-06-16 05:10:14 -05:00
Initial commit, WIP
This gets the module far enough along that it can use the library, find and connect to a unit, and issue requests to it. This is a copy/paste of the ScreenLogic version, so I expect a lot of changes/renames will need to happen still.
This commit is contained in:
25
.github/workflows/nodejs.yml
vendored
Normal file
25
.github/workflows/nodejs.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x, 20.x, 22.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run build --if-present
|
||||||
|
- run: npm run lint
|
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
8
.markdownlint.json
Normal file
8
.markdownlint.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"no-duplicate-header": {
|
||||||
|
"siblings_only": true
|
||||||
|
},
|
||||||
|
"line-length": false,
|
||||||
|
"no-inline-html": false
|
||||||
|
}
|
10
.stylelintrc
Normal file
10
.stylelintrc
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "stylelint-config-standard",
|
||||||
|
"plugins": ["stylelint-prettier"],
|
||||||
|
"rules": {
|
||||||
|
"selector-class-pattern": [
|
||||||
|
"^([a-z][a-z0-9]*|MMM-IntelliCenter)(-[a-z0-9]+)*$",
|
||||||
|
{ "message": "Expected class selector to be kebab-case" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"cwd": "${workspaceFolder}/../../",
|
||||||
|
"args": ["serveronly"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-01-04
|
||||||
|
|
||||||
|
- Initial release.
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 parnic
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
332
MMM-IntelliCenter.js
Normal file
332
MMM-IntelliCenter.js
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
/* global Module Log document */
|
||||||
|
|
||||||
|
let poolData = {};
|
||||||
|
|
||||||
|
Module.register("MMM-IntelliCenter", {
|
||||||
|
defaults: {
|
||||||
|
showPoolTemp: true,
|
||||||
|
showSpaTemp: true,
|
||||||
|
showPH: true,
|
||||||
|
showOrp: true,
|
||||||
|
showSaltLevel: true,
|
||||||
|
showSaturation: true,
|
||||||
|
showFreezeMode: true,
|
||||||
|
showControls: false,
|
||||||
|
controls: [],
|
||||||
|
colored: true,
|
||||||
|
coldTemp: 84,
|
||||||
|
hotTemp: 90,
|
||||||
|
columns: 3,
|
||||||
|
contentClass: "light",
|
||||||
|
showPHTankLevel: true,
|
||||||
|
pHTankLevelMax: 6,
|
||||||
|
serverAddress: "",
|
||||||
|
serverPort: 0,
|
||||||
|
multicastInterface: ""
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (
|
||||||
|
this.config.showControls &&
|
||||||
|
(!this.config.controls || this.config.controls.length === 0)
|
||||||
|
) {
|
||||||
|
Log.warn(
|
||||||
|
"Controls are enabled, but no controls are configured. See README for info on setting up controls."
|
||||||
|
);
|
||||||
|
this.config.showControls = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendSocketNotification("INTELLICENTER_CONFIG", this.config);
|
||||||
|
},
|
||||||
|
|
||||||
|
getStyles() {
|
||||||
|
return ["intellicenter.css"];
|
||||||
|
},
|
||||||
|
|
||||||
|
getDom() {
|
||||||
|
if (!poolData.status) {
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.innerHTML = "Loading IntelliCenter...";
|
||||||
|
wrapper.className += "dimmed light small text-center";
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
const outermost = document.createElement("div");
|
||||||
|
outermost.classList.add("container");
|
||||||
|
|
||||||
|
const reconnectDiv = document.createElement("div");
|
||||||
|
reconnectDiv.classList.add("overlay", "reconnecting", "d-none");
|
||||||
|
|
||||||
|
const reconnectLabel = document.createElement("div");
|
||||||
|
reconnectLabel.classList.add("margin-auto", "bg-blur");
|
||||||
|
reconnectLabel.innerHTML = "Reconnecting...";
|
||||||
|
reconnectDiv.appendChild(reconnectLabel);
|
||||||
|
|
||||||
|
const table = document.createElement("table");
|
||||||
|
table.classList.add("base-content", "small");
|
||||||
|
if (this.config.colored) {
|
||||||
|
table.classList.add("colored");
|
||||||
|
}
|
||||||
|
|
||||||
|
outermost.appendChild(reconnectDiv);
|
||||||
|
outermost.appendChild(table);
|
||||||
|
|
||||||
|
const contents = [];
|
||||||
|
|
||||||
|
if (this.config.showPoolTemp) {
|
||||||
|
let className = "";
|
||||||
|
if (poolData.status.currentTemp[0] <= this.config.coldTemp) {
|
||||||
|
className += " cold-temp";
|
||||||
|
} else if (poolData.status.currentTemp[0] >= this.config.hotTemp) {
|
||||||
|
className += " hot-temp";
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.push({
|
||||||
|
header: "Pool temp",
|
||||||
|
data: `${poolData.status.currentTemp[0]}°${!isPoolActive(poolData.status) ? " (last)" : ""}`,
|
||||||
|
class: this.config.contentClass + className
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showSpaTemp) {
|
||||||
|
let className = "";
|
||||||
|
if (poolData.status.currentTemp[1] <= this.config.coldTemp) {
|
||||||
|
className = " cold-temp";
|
||||||
|
} else if (poolData.status.currentTemp[1] >= this.config.hotTemp) {
|
||||||
|
className = " hot-temp";
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.push({
|
||||||
|
header: "Spa temp",
|
||||||
|
data: `${poolData.status.currentTemp[1]}°${!isSpaActive(poolData.status) ? " (last)" : ""}`,
|
||||||
|
class: this.config.contentClass + className
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showPH) {
|
||||||
|
let dataStr = poolData.status.pH;
|
||||||
|
if (this.config.showPHTankLevel) {
|
||||||
|
const percent = Math.round(
|
||||||
|
((poolData.status.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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showOrp) {
|
||||||
|
contents.push({
|
||||||
|
header: "ORP",
|
||||||
|
data: poolData.status.orp,
|
||||||
|
class: this.config.contentClass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showSaltLevel) {
|
||||||
|
contents.push({
|
||||||
|
header: "Salt PPM",
|
||||||
|
data: poolData.status.saltPPM,
|
||||||
|
class: this.config.contentClass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showSaturation) {
|
||||||
|
contents.push({
|
||||||
|
header: "Saturation",
|
||||||
|
data: poolData.status.saturation,
|
||||||
|
class: this.config.contentClass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.config.showControls) {
|
||||||
|
for (const control in this.config.controls) {
|
||||||
|
const controlObj = this.config.controls[control];
|
||||||
|
|
||||||
|
if (controlObj.type === "circuit") {
|
||||||
|
let { name } = controlObj;
|
||||||
|
for (const circuit in poolData.controllerConfig.bodyArray) {
|
||||||
|
if (
|
||||||
|
poolData.controllerConfig.bodyArray[circuit].circuitId ===
|
||||||
|
controlObj.id
|
||||||
|
) {
|
||||||
|
if (!name) {
|
||||||
|
name = poolData.controllerConfig.bodyArray[circuit].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let on = false;
|
||||||
|
for (const circuit in poolData.status.circuitArray) {
|
||||||
|
if (poolData.status.circuitArray[circuit].id === controlObj.id) {
|
||||||
|
on = poolData.status.circuitArray[circuit].state !== 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
if (
|
||||||
|
controlObj.body < 0 ||
|
||||||
|
controlObj.body > poolData.status.setPoint.length
|
||||||
|
) {
|
||||||
|
Log.warn("Invalid body specified for heatpoint");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const temperature = poolData.status.setPoint[controlObj.body];
|
||||||
|
|
||||||
|
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}°</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") {
|
||||||
|
if (
|
||||||
|
controlObj.body < 0 ||
|
||||||
|
controlObj.body > poolData.status.heatMode.length
|
||||||
|
) {
|
||||||
|
Log.warn("Invalid body specified for heatmode");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const on = poolData.status.heatMode[controlObj.body] !== 0;
|
||||||
|
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 headerRow = null;
|
||||||
|
let contentRow = null;
|
||||||
|
|
||||||
|
if (this.config.showFreezeMode && poolData.status.freezeMode !== 0) {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
table.appendChild(row);
|
||||||
|
row.className = "cold-temp";
|
||||||
|
const cell = document.createElement("th");
|
||||||
|
row.appendChild(cell);
|
||||||
|
cell.colSpan = this.config.columns;
|
||||||
|
cell.innerHTML = "<center>FREEZE MODE</center>";
|
||||||
|
}
|
||||||
|
|
||||||
|
let cols = -1;
|
||||||
|
for (const item in contents) {
|
||||||
|
cols++;
|
||||||
|
if (cols % this.config.columns === 0) {
|
||||||
|
headerRow = document.createElement("tr");
|
||||||
|
contentRow = document.createElement("tr");
|
||||||
|
table.appendChild(headerRow);
|
||||||
|
table.appendChild(contentRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents[item].header) {
|
||||||
|
const headerCell = document.createElement("th");
|
||||||
|
headerCell.innerHTML = contents[item].header;
|
||||||
|
headerRow.appendChild(headerCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentCell = document.createElement("td");
|
||||||
|
contentCell.innerHTML = contents[item].data;
|
||||||
|
contentCell.className = contents[item].class;
|
||||||
|
contentRow.appendChild(contentCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outermost;
|
||||||
|
},
|
||||||
|
|
||||||
|
socketNotificationReceived(notification, payload) {
|
||||||
|
if (notification === "INTELLICENTER_RESULT") {
|
||||||
|
poolData = payload;
|
||||||
|
this.updateDom();
|
||||||
|
showReconnectOverlay(false);
|
||||||
|
} else if (
|
||||||
|
notification === "INTELLICENTER_CIRCUIT_DONE" ||
|
||||||
|
notification === "INTELLICENTER_HEATSTATE_DONE" ||
|
||||||
|
notification === "INTELLICENTER_HEATPOINT_DONE"
|
||||||
|
) {
|
||||||
|
poolData.status = payload.status;
|
||||||
|
this.updateDom();
|
||||||
|
showReconnectOverlay(false);
|
||||||
|
} else if (notification === "INTELLICENTER_RECONNECTING") {
|
||||||
|
showReconnectOverlay(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showReconnectOverlay(show) {
|
||||||
|
const element = document.querySelector(".MMM-IntelliCenter .reconnecting");
|
||||||
|
if (!element || !element.classList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
element.classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
element.classList.add("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPA_CIRCUIT_ID = 500;
|
||||||
|
const POOL_CIRCUIT_ID = 505;
|
||||||
|
|
||||||
|
function isPoolActive(status) {
|
||||||
|
for (let i = 0; i < status.circuitArray.length; i += 1) {
|
||||||
|
if (status.circuitArray[i].id === POOL_CIRCUIT_ID) {
|
||||||
|
return status.circuitArray[i].state === 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSpaActive(status) {
|
||||||
|
for (let i = 0; i < status.circuitArray.length; i += 1) {
|
||||||
|
if (status.circuitArray[i].id === SPA_CIRCUIT_ID) {
|
||||||
|
return status.circuitArray[i].state === 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
README.md
Normal file
76
README.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# MMM-IntelliCenter
|
||||||
|
|
||||||
|
[MagicMirror²](https://github.com/MichMich/MagicMirror) module used to connect to a local Pentair IntelliCenter pool controller system.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/parnic/MMM-IntelliCenter.git`.
|
||||||
|
2. `cd MMM-IntelliCenter`
|
||||||
|
3. Execute `npm install --production` to install the node dependencies.
|
||||||
|
4. Add the module inside `config.js` placing it where you prefer.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
| Option | Type | Description | Default |
|
||||||
|
| ----------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
||||||
|
| `coldTemp` | Integer | Show the temperature colored blue if it's at or below this level for pool/spa (requires option `colored`). This is in whatever scale your system is set to (Fahrenheit/Celsius). | `84` |
|
||||||
|
| `colored` | Boolean | Whether you'd like colored output or not. | `true` |
|
||||||
|
| `columns` | Integer | How many columns to use to display the data before starting a new row. | `3` |
|
||||||
|
| `contentClass` | String | The CSS class used to display content values (beneath the header). | `"light"` |
|
||||||
|
| `controls` | Array | List of controls to show buttons for. Must also set `showControls` to `true`.<br><br>Each entry in this list is an object with a `type` string property and a `name` string to display.<br><br>Valid `type`s:<br>`"circuit"` - toggle a circuit on or off. Must also have an `id` number property defining the circuit ID to set (see [node-intellicenter](https://github.com/parnic/node-intellicenter) documentation for circuit IDs). `name` is an optional string; if not specified, the name of the equipment in the IntelliCenter system will be used.<br>`"heatmode"` - enable or disable the heater for the pool or spa. Must also have a `body` number property that defines which body to toggle the heater for (`0` is the pool, `1` is the spa). Can optionally have a `heatMode` number property that defines which heat mode to set; if not present, defaults to 3 ("heat pump").<br>`"heatpoint"` - set the heat temperature for the pool or spa. Must also have a `body` number property that defines which body to set the heat point for (`0` is the pool, `1` is the spa) | `[]` |
|
||||||
|
| `hotTemp` | Integer | Show the temperature colored red if it's at or above this level for pool/spa (requires option `colored`). This is in whatever scale your system is set to (Fahrenheit/Celsius). | `90` |
|
||||||
|
| `serverAddress` | String | The IPv4 address of a IntelliCenter unit to connect to. If not set, the system will search for a unit to connect to. If set, `serverPort` must also be set. | |
|
||||||
|
| `serverPort` | Integer | The port of a IntelliCenter unit to connect to (usually 6680). If not set, the system will search for a unit to connect to. If set, `serverAddress` must also be set. | |
|
||||||
|
| `showControls` | Boolean | Whether you'd like to show buttons for controlling pool equipment. Must also setup the `controls` array. | `false` |
|
||||||
|
| `showFreezeMode` | Boolean | Whether you'd like to show a banner when the pool is in freeze mode or not. [added in v1.0.1] | `true` |
|
||||||
|
| `showOrp` | Boolean | Whether you'd like to show ORP level or not. | `true` |
|
||||||
|
| `showPH` | Boolean | Whether you'd like to show pH level or not. | `true` |
|
||||||
|
| `showPHTankLevel` | Boolean | Whether you'd like to show how much pH balancer is in the tank or not. Only functions if `showPH` is also on. | `true` |
|
||||||
|
| `pHTankLevelMax` | Boolean | If `showPHTankLevel` is enabled, this is the maximum value that the system returns for a full tank. My systems has this always set to 6, but maybe it differs based on what type of pH balancer you're using. | `6` |
|
||||||
|
| `showPoolTemp` | Boolean | Whether you'd like to show pool temperature or not. | `true` |
|
||||||
|
| `showSaltLevel` | Boolean | Whether you'd like to show salt level (in PPM) or not. | `true` |
|
||||||
|
| `showSaturation` | Boolean | Whether you'd like to show saturation/balance or not. | `true` |
|
||||||
|
| `showSpaTemp` | Boolean | Whether you'd like to show spa temperature or not. | `true` |
|
||||||
|
|
||||||
|
Here is an example of an entry in config.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
module: 'MMM-IntelliCenter',
|
||||||
|
header: 'Pool info',
|
||||||
|
position: 'top_left',
|
||||||
|
config: {
|
||||||
|
showSpaTemp: false,
|
||||||
|
columns: 2,
|
||||||
|
contentClass: 'thin',
|
||||||
|
showControls: true,
|
||||||
|
controls: [
|
||||||
|
{type: 'circuit', id: 500},
|
||||||
|
{type: 'circuit', id: 505, name: 'Pool'},
|
||||||
|
{type: 'heatmode', body: 0, name: 'Pool heater'},
|
||||||
|
{type: 'heatpoint', body: 0, name: 'Pool'},
|
||||||
|
{type: 'heatmode', body: 1, heatMode: 2, name: 'Spa heater'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|
### With color
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Pull requests are very welcome! If you'd like to see any additional functionality, don't hesitate to let me know.
|
||||||
|
|
||||||
|
This module only works with IntelliCenter controllers on the local network via either a UDP broadcast on 255.255.255.255 or a direct connection if you've specified an address and port in the configuration.
|
||||||
|
|
||||||
|
The data is updated when the pool equipment sends an update (which typically happens 0-10 seconds after anything changes), and direct updates are requested after any control is toggled/changed.
|
||||||
|
|
||||||
|
When toggling a circuit or changing heat mode, sometimes other circuits are affected. For example, some pools share the same pump for the pool and spa, so when the pool is toggled on the spa must be toggled off. Unfortunately the IntelliCenter system doesn't update its internal status at any predictable rate, so the data on the screen can be wrong immediately after toggling a circuit until the next periodic update runs. If you know of a reliable way around this, please open a pull request!
|
||||||
|
|
||||||
|
## Libraries
|
||||||
|
|
||||||
|
This uses a Node.JS library I created for interfacing with IntelliCenter controllers over the network: [node-intellicenter](https://github.com/parnic/node-intellicenter), so feel free to check that out for more information.
|
8
eslint.config.mjs
Normal file
8
eslint.config.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import eslint from "@eslint/js";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: ["eslint.config.mjs"]
|
||||||
|
}
|
||||||
|
];
|
165
intellicenter.css
Normal file
165
intellicenter.css
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
.MMM-IntelliCenter table.colored .cold-temp {
|
||||||
|
color: #bcddff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter table.colored .hot-temp {
|
||||||
|
color: #ff8e99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter td {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .control {
|
||||||
|
background: #000;
|
||||||
|
border: 1px solid #6c757d;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition:
|
||||||
|
color 0.15s ease-in-out,
|
||||||
|
background-color 0.15s ease-in-out,
|
||||||
|
border-color 0.15s ease-in-out,
|
||||||
|
box-shadow 0.15s ease-in-out;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
width: 80%;
|
||||||
|
height: 5rem;
|
||||||
|
margin: auto;
|
||||||
|
font-weight: bold;
|
||||||
|
user-select: none;
|
||||||
|
cursor: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .control::after {
|
||||||
|
padding-bottom: 100%;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .content {
|
||||||
|
position: absolute;
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 5%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .control-on {
|
||||||
|
background: #28a745 !important;
|
||||||
|
border-color: #28a745 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .control-off {
|
||||||
|
background: transparent !important;
|
||||||
|
border-color: #6c757d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .temperature-container {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
border: 1px solid #6c757d;
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .temperature-label {
|
||||||
|
height: 0;
|
||||||
|
transform: translateY(-0.5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .temperature {
|
||||||
|
background: #000;
|
||||||
|
border: 0 solid #6c757d;
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
width: 80%;
|
||||||
|
height: 2.45rem;
|
||||||
|
margin: auto;
|
||||||
|
font-weight: bold;
|
||||||
|
user-select: none;
|
||||||
|
cursor: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .vertical {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20%;
|
||||||
|
height: 0.7em;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
box-shadow: inset 0 0 3px #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .progress-bar {
|
||||||
|
box-shadow: inset 0 0 3px rgb(100 100 100 / 60%);
|
||||||
|
float: left;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: width 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .progress-bar-success {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .progress-bar-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .progress-bar-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .container {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .base-content,
|
||||||
|
.overlay {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .overlay {
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .reconnecting {
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .margin-auto {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .bg-blur {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 18%;
|
||||||
|
background: #fff6;
|
||||||
|
color: white;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.MMM-IntelliCenter .d-none {
|
||||||
|
display: none !important;
|
||||||
|
}
|
283
node_helper.js
Normal file
283
node_helper.js
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
/* global module require clearInterval clearTimeout setTimeout setInterval config */
|
||||||
|
|
||||||
|
var NodeHelper = require("node_helper");
|
||||||
|
|
||||||
|
let FindUnits;
|
||||||
|
let Unit;
|
||||||
|
import("node-intellicenter").then((x) => {
|
||||||
|
FindUnits = x.FindUnits;
|
||||||
|
Unit = x.Unit;
|
||||||
|
});
|
||||||
|
let messages;
|
||||||
|
import("node-intellicenter/messages").then((x) => {
|
||||||
|
messages = x.messages;
|
||||||
|
});
|
||||||
|
const Log = require("logger");
|
||||||
|
|
||||||
|
const reconnectDelayMs = 10 * 1000;
|
||||||
|
const unitFinderTimeoutMs = 5 * 1000;
|
||||||
|
let foundUnit = false;
|
||||||
|
const poolData = {};
|
||||||
|
let refreshTimer;
|
||||||
|
let unitFinderRetry;
|
||||||
|
let unitReconnectTimer;
|
||||||
|
|
||||||
|
module.exports = NodeHelper.create({
|
||||||
|
setCircuit(circuitState) {
|
||||||
|
this.setCircuitState(circuitState, (poolStatus) => {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_CIRCUIT_DONE", {
|
||||||
|
circuitState,
|
||||||
|
status: poolStatus
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setHeatpoint(heatpoint) {
|
||||||
|
this.setHeatpointState(heatpoint, (poolStatus) => {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_HEATPOINT_DONE", {
|
||||||
|
heatpoint,
|
||||||
|
status: poolStatus
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setHeatstate(heatstate) {
|
||||||
|
this.setHeatstateState(heatstate, (poolStatus) => {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_HEATSTATE_DONE", {
|
||||||
|
heatstate,
|
||||||
|
status: poolStatus
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setLightcmd(lightCmd) {
|
||||||
|
this.setLights(lightCmd, (poolStatus) => {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_LIGHTCMD_DONE", {
|
||||||
|
lightCmd,
|
||||||
|
status: poolStatus
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
notifyReconnecting() {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_RECONNECTING");
|
||||||
|
},
|
||||||
|
|
||||||
|
socketNotificationReceived(notification, payload) {
|
||||||
|
if (notification === "INTELLICENTER_CONFIG") {
|
||||||
|
if (!this.config) {
|
||||||
|
this.config = payload;
|
||||||
|
this.connect(
|
||||||
|
(status) => {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_RESULT", status);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.notifyReconnecting();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (poolData.status) {
|
||||||
|
this.sendSocketNotification("INTELLICENTER_RESULT", poolData);
|
||||||
|
}
|
||||||
|
// If we don't have a status yet, assume the initial connection is still in progress and this socket notification will be delivered when setup is done
|
||||||
|
}
|
||||||
|
if (notification === "INTELLICENTER_CIRCUIT") {
|
||||||
|
this.setCircuit(payload);
|
||||||
|
}
|
||||||
|
if (notification === "INTELLICENTER_HEATPOINT") {
|
||||||
|
this.setHeatpoint(payload);
|
||||||
|
}
|
||||||
|
if (notification === "INTELLICENTER_HEATSTATE") {
|
||||||
|
this.setHeatstate(payload);
|
||||||
|
}
|
||||||
|
if (notification === "INTELLICENTER_LIGHTCMD") {
|
||||||
|
this.setLightcmd(payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFoundUnit() {
|
||||||
|
foundUnit = null;
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer);
|
||||||
|
refreshTimer = null;
|
||||||
|
}
|
||||||
|
if (unitFinderRetry) {
|
||||||
|
clearInterval(unitFinderRetry);
|
||||||
|
unitFinderRetry = null;
|
||||||
|
}
|
||||||
|
if (unitReconnectTimer) {
|
||||||
|
clearTimeout(unitReconnectTimer);
|
||||||
|
unitReconnectTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setupUnit(cb, reconnectCb) {
|
||||||
|
Log.info("[MMM-IntelliCenter] initial connection to unit...");
|
||||||
|
|
||||||
|
foundUnit
|
||||||
|
.on("error", (e) => {
|
||||||
|
Log.error(
|
||||||
|
`[MMM-IntelliCenter] error in unit connection. restarting the connection process in ${reconnectDelayMs / 1000} seconds`
|
||||||
|
);
|
||||||
|
Log.error(e);
|
||||||
|
|
||||||
|
reconnectCb();
|
||||||
|
this.resetFoundUnit();
|
||||||
|
unitReconnectTimer = setTimeout(() => {
|
||||||
|
this.connect(cb, reconnectCb);
|
||||||
|
}, reconnectDelayMs);
|
||||||
|
})
|
||||||
|
.on("close", () => {
|
||||||
|
Log.error(
|
||||||
|
`[MMM-IntelliCenter] unit connection closed unexpectedly. restarting the connection process in ${reconnectDelayMs / 1000} seconds`
|
||||||
|
);
|
||||||
|
|
||||||
|
reconnectCb();
|
||||||
|
this.resetFoundUnit();
|
||||||
|
unitReconnectTimer = setTimeout(() => {
|
||||||
|
this.connect(cb, reconnectCb);
|
||||||
|
}, reconnectDelayMs);
|
||||||
|
})
|
||||||
|
.once("connected", () => {
|
||||||
|
Log.info(
|
||||||
|
"[MMM-IntelliCenter] logged into unit. getting basic configuration..."
|
||||||
|
);
|
||||||
|
foundUnit.send(new messages.GetSystemInformation()).then(() => {
|
||||||
|
Log.info("[MMM-IntelliCenter] got it!");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.once("controllerConfig", (config) => {
|
||||||
|
Log.info(
|
||||||
|
"[MMM-IntelliCenter] configuration received. adding client..."
|
||||||
|
);
|
||||||
|
poolData.controllerConfig = config;
|
||||||
|
poolData.degStr = this.config.degC ? "C" : "F";
|
||||||
|
foundUnit.addClient(1234);
|
||||||
|
})
|
||||||
|
.once("addClient", () => {
|
||||||
|
Log.info(
|
||||||
|
"[MMM-IntelliCenter] client added successfully and listening for changes"
|
||||||
|
);
|
||||||
|
foundUnit.getPoolStatus();
|
||||||
|
// Connection seems to time out every 10 minutes without some sort of request made
|
||||||
|
refreshTimer = setInterval(
|
||||||
|
() => {
|
||||||
|
foundUnit.pingServer();
|
||||||
|
},
|
||||||
|
1 * 60 * 1000
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.on("poolStatus", (status) => {
|
||||||
|
Log.info("[MMM-IntelliCenter] received pool status update");
|
||||||
|
poolData.status = status;
|
||||||
|
cb(poolData);
|
||||||
|
});
|
||||||
|
|
||||||
|
foundUnit.connect();
|
||||||
|
},
|
||||||
|
|
||||||
|
findServer(cb, reconnectCb) {
|
||||||
|
Log.info("[MMM-IntelliCenter] starting search for local units");
|
||||||
|
const finder = new FindUnits(this.config.multicastInterface);
|
||||||
|
finder
|
||||||
|
.on("serverFound", (server) => {
|
||||||
|
finder.close();
|
||||||
|
Log.info(
|
||||||
|
`[MMM-IntelliCenter] local unit found at ${server.addressStr}:${server.port}`
|
||||||
|
);
|
||||||
|
|
||||||
|
foundUnit = new Unit(server.addressStr, server.port);
|
||||||
|
this.setupUnit(cb, reconnectCb);
|
||||||
|
|
||||||
|
clearInterval(unitFinderRetry);
|
||||||
|
unitFinderRetry = null;
|
||||||
|
})
|
||||||
|
.on("error", (e) => {
|
||||||
|
Log.error(
|
||||||
|
`[MMM-IntelliCenter] error trying to find a server. scheduling a retry in ${reconnectDelayMs / 1000} seconds`
|
||||||
|
);
|
||||||
|
Log.error(e);
|
||||||
|
this.resetFoundUnit();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.findServer(cb);
|
||||||
|
}, reconnectDelayMs);
|
||||||
|
});
|
||||||
|
|
||||||
|
finder.search();
|
||||||
|
unitFinderRetry = setInterval(() => {
|
||||||
|
Log.info(
|
||||||
|
`[MMM-IntelliCenter] didn't find any units within ${unitFinderTimeoutMs / 1000} seconds, trying again...`
|
||||||
|
);
|
||||||
|
finder.search();
|
||||||
|
}, unitFinderTimeoutMs);
|
||||||
|
},
|
||||||
|
|
||||||
|
connect(cb, reconnectCb) {
|
||||||
|
if (
|
||||||
|
!foundUnit &&
|
||||||
|
typeof config !== "undefined" &&
|
||||||
|
this.config.serverAddress &&
|
||||||
|
this.config.serverPort
|
||||||
|
) {
|
||||||
|
Log.info(
|
||||||
|
`[MMM-IntelliCenter] connecting directly to configured unit at ${this.config.serverAddress}:${this.config.serverPort}`
|
||||||
|
);
|
||||||
|
foundUnit = new Unit(this.config.serverAddress, this.config.serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundUnit) {
|
||||||
|
this.setupUnit(cb, reconnectCb);
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
2413
package-lock.json
generated
Normal file
2413
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "mmm-intellicenter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Show data from Pentair IntelliCenter systems",
|
||||||
|
"main": "MMM-IntelliCenter.js",
|
||||||
|
"author": "parnic",
|
||||||
|
"repository": "https://github.com/parnic/MMM-IntelliCenter.git",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-intellicenter": "^0.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"stylelint": "^16.12.0",
|
||||||
|
"stylelint-config-standard": "^36.0.1",
|
||||||
|
"stylelint-prettier": "^5.0.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint . && stylelint intellicenter.css && prettier . --check",
|
||||||
|
"lint:fix": "eslint . --fix && stylelint intellicenter.css --fix && prettier . --write"
|
||||||
|
}
|
||||||
|
}
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Reference in New Issue
Block a user