17 Commits
v1.1.0 ... main

Author SHA1 Message Date
223ebff543 Prepare for tagging a new version 2021-10-13 13:59:34 -05:00
15f8c57cea Drop support for nodejs 10.x, add 16.x
10.x is EOL and 16.x is current. 10.x doesn't like eslint 8.0.
2021-10-13 13:55:41 -05:00
3136d217d8 Update eslint version 2021-10-13 13:53:10 -05:00
229c69e203 Satisfy linter 2021-10-13 13:51:31 -05:00
641bd83207 Show different colors based on filled value
This shows green until the bar is at 50%, yellow until 33%, and red
below that.

This also fixes the bar to show white when "colored" is disabled in the
config.
2021-10-13 13:49:34 -05:00
e9fbd20f46 Subtract one from the returned pHTank value
When I checked this yesterday, my IntelliChem was set to 5/6, but the
module was showing 100%. So maybe the API returns it on a 1-7 scale
instead of a 0-6 scale or something? I don't know. Let's try this.
2021-10-13 13:47:59 -05:00
a85d5767b0 Bump ansi-regex from 5.0.0 to 5.0.1 (#1)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-10 20:47:26 -05:00
162a2cc733 Update control font scale to match latest MMM 2021-10-10 12:33:57 -05:00
fdae6d29e9 Add support for showing pH tank level
This is on by default.
2021-10-10 12:29:45 -05:00
90a8526394 Update dependencies 2021-05-11 11:05:15 -05:00
34f6beeec7 Update package data 2020-08-23 19:56:57 -05:00
a24b03d299 Linter fixes 2020-08-23 19:51:39 -05:00
c5f71f6976 Add linter to github workflow 2020-08-21 17:38:19 -05:00
2a84b7f4fd Add fixes for linters, add github workflows
This runs eslint --fix as well as manually fixing things that the automatic fixer couldn't handle.
2020-08-21 14:58:11 -05:00
cca1e1bee9 My how time flies...corrected version dates 2020-04-08 21:49:42 -05:00
b69e557e33 Fixed heat mode display to read the correct pool equipment property 2020-04-08 21:42:49 -05:00
48426cbc3b Added ability to specify mode used to enable the heater 2020-04-07 13:03:22 -05:00
11 changed files with 1403 additions and 452 deletions

21
.eslintrc Normal file
View File

@ -0,0 +1,21 @@
{
"rules": {
"comma-dangle": 0,
"indent": [2, 4],
"max-len": "off",
"radix": [2, "as-needed"],
"no-console": 0,
"linebreak-style": "off",
"prettier/prettier": 0,
"quotes": ["error", "single"],
"jsdoc/require-jsdoc": 0
},
"settings": {
"import/core-modules": [ "node_helper" ]
},
"env": {
"browser": true,
"node": true,
"es6": true
}
}

29
.github/workflows/nodejs.yml vendored Normal file
View File

@ -0,0 +1,29 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm run lint

8
.markdownlint.json Normal file
View File

@ -0,0 +1,8 @@
{
"default": true,
"no-duplicate-header": {
"siblings_only": true
},
"line-length": false,
"no-inline-html": false
}

View File

@ -1,24 +1,56 @@
# 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.1.0] - 2020-03-01
## [1.2.0] - 2021-10-13
### Added
- Can optionally display pH tank level alongside raw pH value now (on by default).
### Fixed
- Better font size scaling with newer MagicMirror versions.
## [1.1.2] - 2020-04-08
### Fixed
- Fixed which property was being read to determine whether heat mode was enabled or not. Previously the state of the heater itself was being used to display on/off status, now the requested mode is used instead (the heater toggles on/off as part of normal operation even while heating is enabled).
## [1.1.1] - 2020-04-07
### Added
- Ability to specify which heat mode to use when enabling heating for a specific body. Previously mode 1 was always sent which means "solar" while most people probably want mode 3 which is "heat pump".
## [1.1.0] - 2020-04-01
### Added
- Ability to show buttons for controlling pool equipment (with a touch screen, for example). NOTE: running `npm install` again is necessary after upgrading to this version if the heat controls are used.
## [1.0.3] - 2018-04-27
### Changed
- Packaged a newer `node-screenlogic` dependency to fix server broadcast in certain environments.
## [1.0.2] - 2018-04-26
### Added
- New option `updateInterval` to control how often the pool data is updated (default 30 minutes).
## [1.0.1] - 2018-04-25
### Added
- New option `showFreezeMode` shows a banner acrosss the top if the pool is currently in freeze-protection mode.
## [1.0.0] - 2018-04-24
- Initial release.

View File

@ -1,286 +1,311 @@
poolData = {};
var moduleObj;
let poolData = {};
let moduleObj;
Module.register("MMM-ScreenLogic",{
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",
updateInterval: 30 * 60 * 1000
},
Module.register('MMM-ScreenLogic',{
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',
updateInterval: 30 * 60 * 1000,
showPHTankLevel: true,
pHTankLevelMax: 6
},
start: function() {
// this isn't a great solution...is there a better one? needed to do stuff with buttons
moduleObj = this;
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;
}
start: function() {
// this isn't a great solution...is there a better one? needed to do stuff with buttons
moduleObj = this;
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('SCREENLOGIC_CONFIG', this.config);
this.sendSocketNotification('SCREENLOGIC_UPDATE');
},
this.sendSocketNotification('SCREENLOGIC_CONFIG', this.config);
this.sendSocketNotification('SCREENLOGIC_UPDATE');
},
getStyles: function() {
return ["screenlogic.css"];
},
getStyles: function() {
return ['screenlogic.css'];
},
getDom: function() {
if (!poolData.status) {
var wrapper = document.createElement("div");
wrapper.innerHTML = 'Loading...';
wrapper.className += "dimmed light small";
getDom: function() {
if (!poolData.status) {
let wrapper = document.createElement('div');
wrapper.innerHTML = 'Loading...';
wrapper.className += 'dimmed light small';
return wrapper;
} else {
var table = document.createElement('table');
table.className = "small";
if (this.config.colored) {
table.className += " colored";
}
return wrapper;
} else {
let table = document.createElement('table');
table.className = 'small';
if (this.config.colored) {
table.className += ' colored';
}
var contents = [];
let contents = [];
if (this.config.showPoolTemp) {
var className = "";
if (poolData.status.currentTemp[0] <= this.config.coldTemp) {
className += " cold-temp";
} else if (poolData.status.currentTemp[0] >= this.config.hotTemp) {
className += " hot-temp";
}
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] + "&deg;" + (!isPoolActive(poolData.status) ? " (last)" : ""),
class: this.config.contentClass + className
});
}
if (this.config.showSpaTemp) {
var 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: 'Pool temp',
data: poolData.status.currentTemp[0] + '&deg;' + (!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] + "&deg;" + (!isSpaActive(poolData.status) ? " (last)" : ""),
class: this.config.contentClass + className
});
}
if (this.config.showPH) {
contents.push({
header: "pH",
data: poolData.status.pH,
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 (var control in this.config.controls) {
var controlObj = this.config.controls[control];
contents.push({
header: 'Spa temp',
data: poolData.status.currentTemp[1] + '&deg;' + (!isSpaActive(poolData.status) ? ' (last)' : ''),
class: this.config.contentClass + className
});
}
if (this.config.showPH) {
let dataStr = poolData.status.pH
if (this.config.showPHTankLevel) {
let percent = Math.round(((poolData.status.pHTank - 1) / this.config.pHTankLevelMax) * 100)
let cls = ''
if (this.config.colored) {
if (percent <= 50 && percent > 33) {
cls = 'progress-bar-warning'
} else if (percent <= 33) {
cls = 'progress-bar-danger'
} else {
cls = 'progress-bar-success'
}
}
let 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>`
if (controlObj.type === 'circuit') {
var name = controlObj.name;
for (var circuit in poolData.controllerConfig.bodyArray) {
if (poolData.controllerConfig.bodyArray[circuit].circuitId == controlObj.id) {
if (!name) {
name = poolData.controllerConfig.bodyArray[circuit].name;
}
}
}
dataStr = `${dataStr} ${progBarDiv}`
}
var on = false;
for (var circuit in poolData.status.circuitArray) {
if (poolData.status.circuitArray[circuit].id == controlObj.id) {
on = poolData.status.circuitArray[circuit].state !== 0;
}
}
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 (let control in this.config.controls) {
let controlObj = this.config.controls[control];
var cls = '';
if (this.config.colored) {
cls = on ? 'control-on' : 'control-off';
}
if (controlObj.type === 'circuit') {
let name = controlObj.name;
for (let circuit in poolData.controllerConfig.bodyArray) {
if (poolData.controllerConfig.bodyArray[circuit].circuitId === controlObj.id) {
if (!name) {
name = poolData.controllerConfig.bodyArray[circuit].name;
}
}
}
contents.push({
data: '<button id="sl-control-' + controlObj.id + '" class="control ' + cls + '" onclick="setCircuit(this)" data-circuit="' +
let on = false;
for (let 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;
}
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;
}
var temperature = poolData.status.setPoint[controlObj.body];
let temperature = poolData.status.setPoint[controlObj.body];
var 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>';
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') {
if (controlObj.body < 0 || controlObj.body > poolData.status.heatStatus.length) {
Log.warn('Invalid body specified for heatmode');
continue;
}
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;
}
var on = poolData.status.heatStatus[controlObj.body] !== 0;
let on = poolData.status.heatMode[controlObj.body] !== 0;
let mode = typeof controlObj.heatMode === 'number' ? controlObj.heatMode : 3;
var cls = '';
if (this.config.colored) {
cls = on ? 'control-on' : 'control-off';
}
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') + '"><div class="content">' +
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);
}
}
}
class: this.config.contentClass
});
} else {
Log.warn('circuit with unknown type, unable to display:');
Log.warn(controlObj);
}
}
}
var headerRow = null;
var contentRow = null;
let headerRow = null;
let contentRow = null;
if (this.config.showFreezeMode && poolData.status.freezeMode !== 0) {
var row = document.createElement('tr');
table.appendChild(row);
row.className = 'cold-temp';
var cell = document.createElement('th');
row.appendChild(cell);
cell.colSpan = this.config.columns;
cell.innerHTML = '<center>FREEZE MODE</center>';
}
if (this.config.showFreezeMode && poolData.status.freezeMode !== 0) {
let row = document.createElement('tr');
table.appendChild(row);
row.className = 'cold-temp';
let cell = document.createElement('th');
row.appendChild(cell);
cell.colSpan = this.config.columns;
cell.innerHTML = '<center>FREEZE MODE</center>';
}
var cols = -1;
for (var item in contents) {
cols++;
if (cols % this.config.columns === 0) {
headerRow = document.createElement('tr');
contentRow = document.createElement('tr');
table.appendChild(headerRow);
table.appendChild(contentRow);
}
let cols = -1;
for (let 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) {
var headerCell = document.createElement('th');
headerCell.innerHTML = contents[item].header;
headerRow.appendChild(headerCell);
}
if (contents[item].header) {
let headerCell = document.createElement('th');
headerCell.innerHTML = contents[item].header;
headerRow.appendChild(headerCell);
}
var contentCell = document.createElement('td');
contentCell.innerHTML = contents[item].data;
contentCell.className = contents[item].class;
contentRow.appendChild(contentCell);
}
let contentCell = document.createElement('td');
contentCell.innerHTML = contents[item].data;
contentCell.className = contents[item].class;
contentRow.appendChild(contentCell);
}
return table;
}
},
return table;
}
},
socketNotificationReceived: function(notification, payload) {
if (notification === 'SCREENLOGIC_RESULT') {
poolData = payload;
this.updateDom();
} else if (notification === 'SCREENLOGIC_CIRCUIT_DONE'
socketNotificationReceived: function(notification, payload) {
if (notification === 'SCREENLOGIC_RESULT') {
poolData = payload;
this.updateDom();
} else if (notification === 'SCREENLOGIC_CIRCUIT_DONE'
|| notification === 'SCREENLOGIC_HEATSTATE_DONE'
|| notification === 'SCREENLOGIC_HEATPOINT_DONE') {
poolData.status = payload.status;
this.updateDom();
}
},
poolData.status = payload.status;
this.updateDom();
}
},
});
const SPA_CIRCUIT_ID = 500;
const POOL_CIRCUIT_ID = 505;
function isPoolActive(status) {
for (var i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === POOL_CIRCUIT_ID) {
return status.circuitArray[i].state === 1;
}
}
for (let i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === POOL_CIRCUIT_ID) {
return status.circuitArray[i].state === 1;
}
}
}
function hasSpa(status) {
for (var i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === SPA_CIRCUIT_ID) {
return true;
}
}
for (let i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === SPA_CIRCUIT_ID) {
return true;
}
}
return false;
return false;
}
function isSpaActive(status) {
for (var i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === SPA_CIRCUIT_ID) {
return status.circuitArray[i].state === 1;
}
}
for (let i = 0; i < status.circuitArray.length; i++) {
if (status.circuitArray[i].id === SPA_CIRCUIT_ID) {
return status.circuitArray[i].state === 1;
}
}
}
function setCircuit(e) {
var circuitId = parseInt(e.dataset.circuit);
var on = e.dataset.state !== '0';
moduleObj.sendSocketNotification('SCREENLOGIC_CIRCUIT', {id: circuitId, state: on ? 0 : 1});
e.classList.remove('control-on', 'control-off');
let circuitId = parseInt(e.dataset.circuit);
let on = e.dataset.state !== '0';
moduleObj.sendSocketNotification('SCREENLOGIC_CIRCUIT', {id: circuitId, state: on ? 0 : 1});
e.classList.remove('control-on', 'control-off');
}
function setHeatmode(e) {
var bodyId = parseInt(e.dataset.body);
var on = e.dataset.state !== '0';
moduleObj.sendSocketNotification('SCREENLOGIC_HEATSTATE', {body: bodyId, state: on ? 0 : 1});
e.classList.remove('control-on', 'control-off');
let bodyId = parseInt(e.dataset.body);
let on = e.dataset.state !== '0';
let mode = e.dataset.mode;
moduleObj.sendSocketNotification('SCREENLOGIC_HEATSTATE', {body: bodyId, state: on ? 0 : parseInt(mode)});
e.classList.remove('control-on', 'control-off');
}
function setHeatpoint(e, tempChange) {
var bodyId = parseInt(e.dataset.body);
var temp = parseInt(e.dataset.temperature) + tempChange;
moduleObj.sendSocketNotification('SCREENLOGIC_HEATPOINT', {body: bodyId, temperature: temp});
e.classList.remove('control-on', 'control-off');
let bodyId = parseInt(e.dataset.body);
let temp = parseInt(e.dataset.temperature) + tempChange;
moduleObj.sendSocketNotification('SCREENLOGIC_HEATPOINT', {body: bodyId, temperature: temp});
e.classList.remove('control-on', 'control-off');
}

View File

@ -1,20 +1,23 @@
# MMM-ScreenLogic
A <a href="https://github.com/MichMich/MagicMirror">MagicMirror²</a> module used to connect to a local Pentair ScreenLogic pool controller system.
[MagicMirror²](https://github.com/MichMich/MagicMirror) module used to connect to a local Pentair ScreenLogic pool controller system.
## Installation
1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/parnic/MMM-ScreenLogic.git`.
2. `cd MMM-ScreenLogic`
3. Execute `npm install` to install the node dependencies.
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` property and a `name` to display.<br><br>Valid `type`s:<br>`circuit` - toggle a circuit on or off. Must also have an `id` property defining the circuit ID to set (see [node-screenlogic](https://github.com/parnic/node-screenlogic) documentation for circuit IDs). `name` is optional; if not specified, the name of the equipment in the ScreenLogic system will be used.<br>`heatmode` - enable or disable the heater for the pool or spa. Must also have a `body` property that defines which body to toggle the heater for (`0` is the pool, `1` is the spa)<br>`heatpoint` - set the heat temperature for the pool or spa. Must also have a `body` property that defines which body to set the heat point for (`0` is the pool, `1` is the spa)|`[]`|
|`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-screenlogic](https://github.com/parnic/node-screenlogic) documentation for circuit IDs). `name` is an optional string; if not specified, the name of the equipment in the ScreenLogic 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 ScreenLogic 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 ScreenLogic unit to connect to (usually 80). If not set, the system will search for a unit to connect to. If set, `serverAddress` must also be set.| |
@ -22,6 +25,8 @@ A <a href="https://github.com/MichMich/MagicMirror">MagicMirror²</a> module use
|`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`|
@ -29,31 +34,36 @@ A <a href="https://github.com/MichMich/MagicMirror">MagicMirror²</a> module use
|`updateInterval`|Integer|How frequently, in milliseconds, to update pool data.|`30 * 60 * 1000` (every 30 minutes)|
Here is an example of an entry in config.js
```
```js
{
module: 'MMM-ScreenLogic',
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'}
]
}
module: 'MMM-ScreenLogic',
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
### With color
![Screenshot with color](/screenshot.png?raw=true "colored: true")
## 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 ScreenLogic 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.
@ -63,4 +73,5 @@ The data is updated every 30 minutes by default (configurable with `updateInterv
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 ScreenLogic 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 ScreenLogic controllers over the network: [node-screenlogic](https://github.com/parnic/node-screenlogic), so feel free to check that out for more information.

View File

@ -1,210 +1,210 @@
var NodeHelper = require('node_helper');
module.exports = NodeHelper.create({
start: function() {
this.setTimer(30 * 60 * 1000);
},
start: function() {
this.setTimer(30 * 60 * 1000);
},
doUpdate: function() {
var self = this;
getPoolData(this.config, function(poolData) {
self.sendSocketNotification('SCREENLOGIC_RESULT', poolData);
});
},
doUpdate: function() {
var self = this;
getPoolData(this.config, function(poolData) {
self.sendSocketNotification('SCREENLOGIC_RESULT', poolData);
});
},
setCircuit: function(circuitState) {
var self = this;
setCircuitState(circuitState, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_CIRCUIT_DONE', {circuitState: circuitState, status: poolStatus});
});
},
setCircuit: function(circuitState) {
var self = this;
setCircuitState(circuitState, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_CIRCUIT_DONE', {circuitState: circuitState, status: poolStatus});
});
},
setHeatpoint: function(heatpoint) {
var self = this;
setHeatpointState(heatpoint, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_HEATPOINT_DONE', {heatpoint: heatpoint, status: poolStatus});
});
},
setHeatpoint: function(heatpoint) {
var self = this;
setHeatpointState(heatpoint, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_HEATPOINT_DONE', {heatpoint: heatpoint, status: poolStatus});
});
},
setHeatstate: function(heatstate) {
var self = this;
setHeatstateState(heatstate, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_HEATSTATE_DONE', {heatstate: heatstate, status: poolStatus});
});
},
setHeatstate: function(heatstate) {
var self = this;
setHeatstateState(heatstate, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_HEATSTATE_DONE', {heatstate: heatstate, status: poolStatus});
});
},
setLightcmd: function(lightCmd) {
var self = this;
setLights(lightCmd, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_LIGHTCMD_DONE', {lightCmd: lightCmd, status: poolStatus});
});
},
setLightcmd: function(lightCmd) {
var self = this;
setLights(lightCmd, function(poolStatus) {
self.sendSocketNotification('SCREENLOGIC_LIGHTCMD_DONE', {lightCmd: lightCmd, status: poolStatus});
});
},
restartTimer: function() {
var interval = this.updateInterval;
this.updateInterval = undefined;
this.setTimer(interval);
},
restartTimer: function() {
var interval = this.updateInterval;
this.updateInterval = undefined;
this.setTimer(interval);
},
setTimer: function(updateInterval) {
var update = true;
update = typeof this.updateInterval === 'undefined' || this.updateInterval != updateInterval;
this.updateInterval = updateInterval;
setTimer: function(updateInterval) {
var update = true;
update = typeof this.updateInterval === 'undefined' || this.updateInterval !== updateInterval;
this.updateInterval = updateInterval;
if (update) {
if (typeof this.timer !== 'undefined') {
clearInterval(this.timer);
}
if (update) {
if (typeof this.timer !== 'undefined') {
clearInterval(this.timer);
}
var self = this;
self.timer = setInterval(function() {
self.doUpdate()
}, self.updateInterval);
}
},
var self = this;
self.timer = setInterval(function() {
self.doUpdate();
}, self.updateInterval);
}
},
socketNotificationReceived: function(notification, payload) {
if (notification === 'SCREENLOGIC_CONFIG') {
this.config = payload;
this.setTimer(this.config.updateInterval);
}
if (notification === 'SCREENLOGIC_UPDATE') {
this.doUpdate();
}
if (notification === 'SCREENLOGIC_CIRCUIT') {
this.setCircuit(payload);
}
if (notification === 'SCREENLOGIC_HEATPOINT') {
this.setHeatpoint(payload);
}
if (notification === 'SCREENLOGIC_HEATSTATE') {
this.setHeatstate(payload);
}
if (notification === 'SCREENLOGIC_LIGHTCMD') {
this.setLightcmd(payload);
}
}
socketNotificationReceived: function(notification, payload) {
if (notification === 'SCREENLOGIC_CONFIG') {
this.config = payload;
this.setTimer(this.config.updateInterval);
}
if (notification === 'SCREENLOGIC_UPDATE') {
this.doUpdate();
}
if (notification === 'SCREENLOGIC_CIRCUIT') {
this.setCircuit(payload);
}
if (notification === 'SCREENLOGIC_HEATPOINT') {
this.setHeatpoint(payload);
}
if (notification === 'SCREENLOGIC_HEATSTATE') {
this.setHeatstate(payload);
}
if (notification === 'SCREENLOGIC_LIGHTCMD') {
this.setLightcmd(payload);
}
}
});
const ScreenLogic = require('node-screenlogic');
var foundUnit;
function getPoolData(config, cb) {
if (!foundUnit && typeof config !== 'undefined' && config.serverAddress && config.serverPort) {
foundUnit = new ScreenLogic.UnitConnection(config.serverPort, config.serverAddress);
}
if (!foundUnit && typeof config !== 'undefined' && config.serverAddress && config.serverPort) {
foundUnit = new ScreenLogic.UnitConnection(config.serverPort, config.serverAddress);
}
if (foundUnit) {
populateSystemData(cb);
} else {
findServer(cb);
}
if (foundUnit) {
populateSystemData(cb);
} else {
findServer(cb);
}
}
function findServer(cb) {
var finder = new ScreenLogic.FindUnits();
finder.on('serverFound', function(server) {
finder.close();
var finder = new ScreenLogic.FindUnits();
finder.on('serverFound', function(server) {
finder.close();
foundUnit = new ScreenLogic.UnitConnection(server);
populateSystemData(cb);
});
foundUnit = new ScreenLogic.UnitConnection(server);
populateSystemData(cb);
});
finder.search();
finder.search();
}
function populateSystemData(cb) {
var poolData = {};
var poolData = {};
if (!foundUnit) {
cb(poolData);
return;
}
if (!foundUnit) {
cb(poolData);
return;
}
foundUnit.once('loggedIn', function() {
foundUnit.getControllerConfig();
}).once('controllerConfig', function(config) {
poolData.controllerConfig = config;
poolData.degStr = config.degC ? 'C' : 'F';
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
poolData.status = status;
foundUnit.once('loggedIn', function() {
foundUnit.getControllerConfig();
}).once('controllerConfig', function(config) {
poolData.controllerConfig = config;
poolData.degStr = config.degC ? 'C' : 'F';
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
poolData.status = status;
foundUnit.close();
cb(poolData);
});
foundUnit.close();
cb(poolData);
});
foundUnit.connect();
foundUnit.connect();
}
function setCircuitState(circuitState, cb) {
if (!foundUnit) {
cb();
return;
}
if (!foundUnit) {
cb();
return;
}
foundUnit.once('loggedIn', function() {
foundUnit.setCircuitState(0, circuitState.id, circuitState.state);
}).once('circuitStateChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.once('loggedIn', function() {
foundUnit.setCircuitState(0, circuitState.id, circuitState.state);
}).once('circuitStateChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.connect();
foundUnit.connect();
}
function setHeatpointState(heatpoint, cb) {
if (!foundUnit) {
cb();
return;
}
if (!foundUnit) {
cb();
return;
}
foundUnit.once('loggedIn', function() {
foundUnit.setSetPoint(0, heatpoint.body, heatpoint.temperature);
}).once('setPointChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.once('loggedIn', function() {
foundUnit.setSetPoint(0, heatpoint.body, heatpoint.temperature);
}).once('setPointChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.connect();
foundUnit.connect();
}
function setHeatstateState(heatstate, cb) {
if (!foundUnit) {
cb();
return;
}
if (!foundUnit) {
cb();
return;
}
foundUnit.once('loggedIn', function() {
foundUnit.setHeatMode(0, heatstate.body, heatstate.state);
}).once('heatModeChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.once('loggedIn', function() {
foundUnit.setHeatMode(0, heatstate.body, heatstate.state);
}).once('heatModeChanged', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.connect();
foundUnit.connect();
}
function setLights(lightCmd, cb) {
if (!foundUnit) {
cb();
return;
}
if (!foundUnit) {
cb();
return;
}
foundUnit.once('loggedIn', function() {
foundUnit.sendLightCommand(0, lightCmd);
}).once('sentLightCommand', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.once('loggedIn', function() {
foundUnit.sendLightCommand(0, lightCmd);
}).once('sentLightCommand', function() {
foundUnit.getPoolStatus();
}).once('poolStatus', function(status) {
foundUnit.close();
cb(status);
});
foundUnit.connect();
foundUnit.connect();
}

795
package-lock.json generated
View File

@ -1,21 +1,796 @@
{
"name": "magic-mirror-module-screenlogic",
"version": "1.0.3",
"name": "mmm-screenlogic",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"node-screenlogic": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-screenlogic/-/node-screenlogic-1.3.1.tgz",
"integrity": "sha512-KbqK2lH2PcyGcmQCDqEvL55OWtSG4TvupglBpNrBuuxEbd5id8C3iKT0+9af69BJHDiTT6oAbog9NsJcUI2Zrw==",
"@eslint/eslintrc": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.2.tgz",
"integrity": "sha512-x1ZXdEFsvTcnbTZgqcWUL9w2ybgZCw/qbKTPQnab+XnYA2bMQpJCh+/bBzCRfDJaJdlrrQlOk49jNtru9gL/6Q==",
"dev": true,
"requires": {
"smart-buffer": "~4.0.1"
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.0.0",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
"js-yaml": "^3.13.1",
"minimatch": "^3.0.4",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
}
},
"@humanwhocodes/config-array": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz",
"integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^1.2.0",
"debug": "^4.1.1",
"minimatch": "^3.0.4"
}
},
"@humanwhocodes/object-schema": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz",
"integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
"dev": true
},
"acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
"dev": true
},
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
"dev": true,
"requires": {
"ansi-colors": "^4.1.1"
}
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"eslint": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.0.0.tgz",
"integrity": "sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.0.2",
"@humanwhocodes/config-array": "^0.6.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^6.0.0",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1",
"globals": "^13.6.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"progress": "^2.0.0",
"regexpp": "^3.2.0",
"semver": "^7.2.1",
"strip-ansi": "^6.0.0",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"eslint-scope": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
"integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
}
},
"eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
"integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^2.0.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
}
}
},
"eslint-visitor-keys": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz",
"integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==",
"dev": true
},
"espree": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz",
"integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==",
"dev": true,
"requires": {
"acorn": "^8.5.0",
"acorn-jsx": "^5.3.1",
"eslint-visitor-keys": "^3.0.0"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
"dev": true,
"requires": {
"estraverse": "^5.1.0"
}
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
}
},
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dev": true,
"requires": {
"flat-cache": "^3.0.4"
}
},
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"dev": true,
"requires": {
"flatted": "^3.1.0",
"rimraf": "^3.0.2"
}
},
"flatted": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"requires": {
"is-glob": "^4.0.3"
}
},
"globals": {
"version": "13.11.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz",
"integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
}
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
}
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"node-screenlogic": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/node-screenlogic/-/node-screenlogic-1.6.1.tgz",
"integrity": "sha512-HcjhFvv2OORlXgv9kfnMZA+mCZdCGeTe/3vx5kcKckH130zenVaM2jKcGuzNBdDQpGPz/sogcaZpfCp0xwA+Ag==",
"requires": {
"debug": "^4.3.1",
"smart-buffer": "~4.1.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"dev": true,
"requires": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.3"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"smart-buffer": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz",
"integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw=="
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
"integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1"
}
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
}

View File

@ -1,12 +1,18 @@
{
"name": "magic-mirror-module-screenlogic",
"version": "1.1.0",
"description": "Show data from Pentair ScreenLogic systems",
"main": "MMM-ScreenLogic.js",
"author": "Chris Pickett",
"repository": "https://github.com/parnic/MMM-ScreenLogic.git",
"license": "MIT",
"dependencies": {
"node-screenlogic": "1.x"
}
}
{
"name": "mmm-screenlogic",
"version": "1.1.0",
"description": "Show data from Pentair ScreenLogic systems",
"main": "MMM-ScreenLogic.js",
"author": "parnic",
"repository": "https://github.com/parnic/MMM-ScreenLogic.git",
"license": "MIT",
"dependencies": {
"node-screenlogic": "^1.6.1"
},
"devDependencies": {
"eslint": "^8.0.0"
},
"scripts": {
"lint": "./node_modules/.bin/eslint ."
}
}

View File

@ -1,29 +1,29 @@
.MMM-ScreenLogic table.colored .cold-temp {
color: #BCDDFF;
color: #BCDDFF;
}
.MMM-ScreenLogic table.colored .hot-temp {
color: #FF8E99;
color: #FF8E99;
}
.MMM-ScreenLogic td {
width: 33%;
width: 33%;
}
.MMM-ScreenLogic .control {
background: #000;
border: 1px solid #6c757d;
font-size: .9rem;
font-size: .8rem;
color: #fff;
display: block;
text-align: center;
line-height: 1.5;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
box-sizing: border-box;
position: relative;
width: 80%;
height: 5rem;
margin: auto;
box-sizing: border-box;
position: relative;
width: 80%;
height: 5rem;
margin: auto;
font-weight: bold;
-webkit-user-select: none;
-moz-user-select: none;
@ -34,13 +34,13 @@
}
.MMM-ScreenLogic .control:after {
padding-bottom: 100%;
content: "";
display: block;
padding-bottom: 100%;
content: "";
display: block;
}
.MMM-ScreenLogic .content {
position: absolute;
position: absolute;
width: 90%;
height: 90%;
left: 0px;
@ -52,13 +52,13 @@
}
.MMM-ScreenLogic .control-on {
background: #28a745 !important;
border-color: #28a745 !important;
background: #28a745 !important;
border-color: #28a745 !important;
}
.MMM-ScreenLogic .control-off {
background: transparent !important;
border-color: #6c757d !important;
background: transparent !important;
border-color: #6c757d !important;
}
.MMM-ScreenLogic .temperature-container {
@ -81,10 +81,10 @@
background: #000;
border: 0px solid #6c757d;
color: #fff;
position: relative;
width: 80%;
height: 2.45rem;
margin: auto;
position: relative;
width: 80%;
height: 2.45rem;
margin: auto;
font-weight: bold;
-webkit-user-select: none;
-moz-user-select: none;
@ -93,3 +93,47 @@
cursor: none;
outline: none;
}
.MMM-ScreenLogic .vertical {
display: inline-block;
width: 20%;
height: 0.7em;
-webkit-transform: rotate(-90deg); /* Chrome, Safari, Opera */
transform: rotate(-90deg);
}
.MMM-ScreenLogic .vertical {
box-shadow: inset 0px 0px 3px #ccc;
}
.MMM-ScreenLogic .progress-bar {
box-shadow: inset 0px 0px 3px rgba(100, 100, 100, 0.6);
}
.MMM-ScreenLogic .progress-bar {
float: left;
width: 0;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #fff;
-webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
.MMM-ScreenLogic .progress-bar-success {
background-color: #5cb85c;
}
.MMM-ScreenLogic .progress-bar-warning {
background-color: #ffc107;
}
.MMM-ScreenLogic .progress-bar-danger {
background-color: #dc3545;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 38 KiB