node-red-contrib-zwave-js
Version:
The most powerful, high performing and highly polished Z-Wave node for Node-RED based on Z-Wave JS. If you want a fully featured Z-Wave framework in your Node-RED instance, you have found it.
2,098 lines (1,928 loc) • 101 kB
JavaScript
/* eslint-env jquery */
/* eslint-env browser */
/*eslint no-undef: "warn"*/
/*eslint no-unused-vars: "warn"*/
/* UI Inclusion Functions */
let StartInclusionExclusion;
let StartReplace;
let GrantSelected;
let ValidateDSK;
/* Firmware */
let CheckForUpdate;
/* UI RF Functions */
let SetPowerLevel;
let SetRegion;
let backupNVMRaw;
let restoreNVM;
let GroupedNodes = true;
/* Just stuff */
let DriverReady = false;
const WindowSize = { w: 700, h: 600 };
let NetworkIdentifier = undefined;
/* Commands used throughout */
const DCs = {
backupNVMRaw: {
API: 'ControllerAPI',
name: 'backupNVMRaw',
noWait: true
},
getRFRegion: {
API: 'ControllerAPI',
name: 'getRFRegion',
noWait: false
},
setRFRegion: {
API: 'ControllerAPI',
name: 'setRFRegion',
noWait: false
},
getPowerlevel: {
API: 'ControllerAPI',
name: 'getPowerlevel',
noWait: false
},
setPowerlevel: {
API: 'ControllerAPI',
name: 'setPowerlevel',
noWait: false
},
checkLifelineHealth: {
API: 'DriverAPI',
name: 'checkLifelineHealth',
noWait: true
},
abortFirmwareUpdate: {
API: 'ControllerAPI',
name: 'abortFirmwareUpdate',
noWait: false
},
addAssociations: {
API: 'AssociationsAPI',
name: 'addAssociations',
noWait: false
},
removeAssociations: {
API: 'AssociationsAPI',
name: 'removeAssociations',
noWait: false
},
getAssociations: {
API: 'AssociationsAPI',
name: 'getAssociations',
noWait: false
},
getAllAssociationGroups: {
API: 'AssociationsAPI',
name: 'getAllAssociationGroups',
noWait: false
},
getNodes: {
API: 'ControllerAPI',
name: 'getNodes',
noWait: false
},
verifyDSK: {
API: 'IEAPI',
name: 'verifyDSK',
noWait: false
},
grantClasses: {
API: 'IEAPI',
name: 'grantClasses',
noWait: false
},
getNodeStatistics: {
API: 'DriverAPI',
name: 'getNodeStatistics',
noWait: false
},
getControllerStatistics: {
API: 'DriverAPI',
name: 'getControllerStatistics',
noWait: false
},
checkKeyReq: {
API: 'IEAPI',
name: 'checkKeyReq',
noWait: false
},
replaceFailedNode: {
API: 'IEAPI',
name: 'replaceFailedNode',
noWait: false
},
beginExclusion: {
API: 'IEAPI',
name: 'beginExclusion',
noWait: false
},
beginInclusion: {
API: 'IEAPI',
name: 'beginInclusion',
noWait: false
},
stopIE: {
API: 'IEAPI',
name: 'stopIE',
noWait: false
},
commitScans: {
API: 'IEAPI',
name: 'commitScans',
noWait: false
},
unprovisionSmartStartNode: {
API: 'IEAPI',
name: 'unprovisionSmartStartNode',
noWait: false
},
unprovisionAllSmartStart: {
API: 'IEAPI',
name: 'unprovisionAllSmartStart',
noWait: false
},
rebuildNodeRoutes: {
API: 'ControllerAPI',
name: 'rebuildNodeRoutes',
noWait: true
},
beginRebuildingRoutes: {
API: 'ControllerAPI',
name: 'beginRebuildingRoutes',
noWait: false
},
stopRebuildingRoutes: {
API: 'ControllerAPI',
name: 'stopRebuildingRoutes',
noWait: false
},
hardReset: {
API: 'ControllerAPI',
name: 'hardReset',
noWait: false
},
refreshInfo: {
API: 'ControllerAPI',
name: 'refreshInfo',
noWait: true
},
removeFailedNode: {
API: 'ControllerAPI',
name: 'removeFailedNode',
noWait: false
},
setNodeName: {
API: 'ControllerAPI',
name: 'setNodeName',
noWait: false
},
setNodeLocation: {
API: 'ControllerAPI',
name: 'setNodeLocation',
noWait: false
},
getDefinedValueIDs: {
API: 'ValueAPI',
name: 'getDefinedValueIDs',
noWait: false
},
getValue: {
API: 'ValueAPI',
name: 'getValue',
noWait: false
},
setValue: {
API: 'ValueAPI',
name: 'setValue',
noWait: true
},
getValueMetadata: {
API: 'ValueAPI',
name: 'getValueMetadata',
noWait: false
},
getValueDB: {
API: 'DriverAPI',
name: 'getValueDB',
noWait: false
},
getAvailableFirmwareUpdates: {
API: 'ControllerAPI',
name: 'getAvailableFirmwareUpdates',
noWait: false
},
firmwareUpdateOTA: {
API: 'ControllerAPI',
name: 'firmwareUpdateOTA',
noWait: false
}
};
let StepsAPI;
const StepList = {
SecurityMode: 0,
NIF: 1,
Remove: 2,
Classes: 3,
DSK: 4,
AddDone: 5,
AddDoneInsecure: 6,
RemoveDone: 7,
ReplaceSecurityMode: 8,
Aborted: 9,
SmartStart: 10,
SmartStartList: 11,
SmartStartListEdit: 12,
SmartStartDone: 13,
RemoveDoneUnconfirmed: 14
};
const JSONFormatter = {};
JSONFormatter.json = {
replacer: function (match, pIndent, pKey, pVal, pEnd) {
var key = '<span class=json-key>';
var val = '<span class=json-value>';
var str = '<span class=json-string>';
var r = pIndent || '';
if (pKey) r = r + key + pKey + '</span>';
if (pVal) r = r + (pVal[0] === '"' ? str : val) + pVal + '</span>';
return r + (pEnd || '');
},
prettyPrint: function (obj) {
var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/gm;
return JSON.stringify(obj, null, 3)
.replace(/&/g, '&')
.replace(/\\"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(jsonLine, JSONFormatter.json.replacer);
}
};
const ZwaveJsUI = (function () {
let HCForm; // Health Check Form
let HCRounds; // Health Check Rounds
let FirmwareForm; // FrimwareForm
let RFForm; // FrimwareForm
let FWRunning = false; // Firmware Updare Running
const Groups = {}; // Association Groups
let Removing = false; // Removing Failed Node
let Timer; // Timers
let IETime; // IE Time
let SecurityTime; // Security Time
const BatteryUIElements = {}; // Battery Icon Elements
let BA = undefined; // Node Button Array
let HoveredNode = undefined; // Hovered Node
let selectedNode; // Selected Node
let LastTargetForBA; // BA TArget
let WakeResolver; // Resolve for wake wait
let WakeResolverTarget; // Target Wake Node
let NodesListed = false; // nodes listed
function modalAlert(message, title) {
const Buts = {
Ok: function () {}
};
modalPrompt(message, title, Buts);
}
function modalPrompt(message, title, buttons, addCancel, IsHTML) {
const Options = {
draggable: false,
modal: true,
resizable: false,
width: 'auto',
title: title,
minHeight: 75,
buttons: {}
};
Object.keys(buttons).forEach((BT) => {
Options.buttons[BT] = function () {
$(this).dialog('destroy');
buttons[BT]();
};
});
if (addCancel) {
Options.buttons['Cancel'] = function () {
$(this).dialog('destroy');
};
}
const D = $('<div>').css({
padding: 10,
maxWidth: 500,
wordWrap: 'break-word'
});
if (IsHTML) {
D.html(message);
} else {
D.text(message);
}
D.dialog(Options);
return D;
}
function ShowCommandViewer() {
$('<div>')
.css({ maxHeight: '80%' })
.html('')
.attr('id', 'CommandLog')
.dialog({
draggable: true,
modal: false,
resizable: true,
width: WindowSize.w,
height: WindowSize.h,
title: 'UI Monitor',
buttons: {
Close: function () {
$(this).dialog('destroy');
},
'Clear Log': function () {
$('#CommandLog').empty();
}
}
});
}
function processHealthCheckProgress(topic, data) {
const P = Math.round((100 * data.payload) / HCRounds);
HCForm.html(
`<div style="width:430px; margin:auto;margin-top:40px;font-size:18px">Running Health Check. This may take a few minutes, please wait...</div><div class="progressbar"><div style="width:${P}%"></div></div>`
);
}
function processHealthResults(topic, data) {
const RatingsArray = data.payload.HealthCheck.results.map((R) => R.rating);
const Min = Math.min(...RatingsArray);
const Max = Math.max(...RatingsArray);
const Average = Math.round(
RatingsArray.reduce((a, b) => a + b, 0) / RatingsArray.length
);
const MC = Min < 4 ? 'red' : Min < 6 ? 'orange' : 'green';
const AC = Average < 4 ? 'red' : Average < 6 ? 'orange' : 'green';
const MXC = Max < 4 ? 'red' : Max < 6 ? 'orange' : 'green';
const Data = {
rounds: data.payload.HealthCheck.results,
Worst: Min,
Best: Max,
Average: Average,
MC: MC,
AC: AC,
MXC: MXC
};
Data.TX = data.payload.Statistics[HoveredNode.nodeId.toString()].commandsTX;
Data.RX = data.payload.Statistics[HoveredNode.nodeId.toString()].commandsRX;
Data.TXD =
data.payload.Statistics[HoveredNode.nodeId.toString()].commandsDroppedTX;
Data.RXD =
data.payload.Statistics[HoveredNode.nodeId.toString()].commandsDroppedRX;
Data.TO =
data.payload.Statistics[HoveredNode.nodeId.toString()].timeoutResponse;
HCForm.html('');
const Template = $('#TPL_HealthCheck').html();
const templateScript = Handlebars.compile(Template);
const HTML = templateScript(Data);
HCForm.append(HTML);
RED.comms.unsubscribe(
`/zwave-js/${NetworkIdentifier}/healthcheck`,
processHealthResults
);
RED.comms.unsubscribe(
`/zwave-js/${NetworkIdentifier}/healthcheckprogress`,
processHealthCheckProgress
);
}
function RenderHealthCheck(Rounds) {
HCRounds = Rounds;
ControllerCMD(
DCs.checkLifelineHealth.API,
DCs.checkLifelineHealth.name,
undefined,
[HoveredNode.nodeId, Rounds],
DCs.checkLifelineHealth.noWait
)
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not start Health Check.'
);
throw new Error(err.responseText || err.message);
})
.then(() => {
RED.comms.subscribe(
`/zwave-js/${NetworkIdentifier}/healthcheck`,
processHealthResults
);
RED.comms.subscribe(
`/zwave-js/${NetworkIdentifier}/healthcheckprogress`,
processHealthCheckProgress
);
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: 'Node Health Check : Node ' + HoveredNode.nodeId,
minHeight: 75,
buttons: {
Cancel: function () {
RED.comms.unsubscribe(
`/zwave-js/${NetworkIdentifier}/healthcheck`,
processHealthResults
);
RED.comms.unsubscribe(
`/zwave-js/${NetworkIdentifier}/healthcheckprogress`,
processHealthCheckProgress
);
$(this).dialog('destroy');
}
}
};
HCForm = $('<div>')
.css({ padding: 10 })
.html(
`<div style="width:430px; margin:auto;margin-top:40px;font-size:18px">Running Health Check. This may take a few minutes, please wait...</div><div class="progressbar" style="width:70%;margin: auto; margin-top:50px"><div></div></div>`
);
HCForm.dialog(Options);
});
}
function HealthCheck() {
IsDriverReady();
const Buttons = {
'Yes (1 Round)': () => {
RenderHealthCheck(1);
},
'Yes (3 Rounds)': () => {
RenderHealthCheck(3);
},
'Yes (5 Rounds)': () => {
RenderHealthCheck(5);
}
};
modalPrompt(
"A Node Health Check involves running diagnostics on a node and it's routing table, Care should be taken not to run this whilst large amounts of traffic is flowing through the network. Continue?",
'Run Diagnostics?',
Buttons,
true
);
}
function AbortUpdate() {
if (FWRunning) {
ControllerCMD(
DCs.abortFirmwareUpdate.API,
DCs.abortFirmwareUpdate.name,
undefined,
[selectedNode],
DCs.abortFirmwareUpdate.noWait
)
.then(() => {
FirmwareForm.dialog('destroy');
})
.catch((err) => {
FirmwareForm.dialog('destroy');
console.error(err);
});
} else {
FirmwareForm.dialog('destroy');
}
FWRunning = false;
}
async function PerformUpdateFromService(Node, File) {
const nodeRow = $('#zwave-js-node-list').find(`[data-nodeid='${Node}']`);
if (nodeRow.data().info.status.toUpperCase() === 'ASLEEP') {
const A = await WaitForNodeWake(Node);
if (!A) {
return;
}
}
ControllerCMD(
DCs.firmwareUpdateOTA.API,
DCs.firmwareUpdateOTA.name,
undefined,
[Node, File],
DCs.firmwareUpdateOTA.noWait
)
.then(() => {
FWRunning = true;
selectNode(Node);
$(":button:contains('Begin Update')")
.prop('disabled', true)
.addClass('ui-state-disabled');
$('#FWProgress').css({ display: 'block' });
})
.catch((err) => {
modalAlert(err.responseText || err.message, 'Firmware rejected');
throw new Error(err.responseText || err.message);
});
}
async function PerformUpdate() {
const CurrentFWMode = $('#tabs').tabs('option', 'active');
if (CurrentFWMode === 0) {
const SelectedFW = $('#NODE_FWCV').find(':selected').data('FWTarget');
PerformUpdateFromService(SelectedFW.node, SelectedFW.file);
return;
}
const FE = $('#FILE_FW')[0].files[0];
const NID = parseInt($('#NODE_FW option:selected').val());
const Target = $('#TARGET_FW').val();
const nodeRow = $('#zwave-js-node-list').find(`[data-nodeid='${NID}']`);
if (nodeRow.data().info.status.toUpperCase() === 'ASLEEP') {
const A = await WaitForNodeWake(NID);
if (!A) {
return;
}
}
const FD = new FormData();
FD.append('Binary', FE);
FD.append('NodeID', NID);
FD.append('Target', Target);
const Options = {
url: `zwave-js/${NetworkIdentifier}/firmwareupdate`,
method: 'POST',
contentType: false,
processData: false,
data: FD
};
$.ajax(Options)
.then(() => {
FWRunning = true;
selectNode(NID);
$(":button:contains('Begin Update')")
.prop('disabled', true)
.addClass('ui-state-disabled');
$('#FWProgress').css({ display: 'block' });
})
.catch((err) => {
modalAlert(err.responseText || err.message, 'Firmware rejected');
throw new Error(err.responseText || err.message);
});
}
function FirmwareUpdate() {
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: `ZWave Device Firmware Updater (Network ${NetworkIdentifier})`,
minHeight: 75,
buttons: {
'Begin Update': PerformUpdate,
Cancel: function () {
AbortUpdate();
}
}
};
FirmwareForm = $('<div>').css({ padding: 10 }).html('Please wait...');
FirmwareForm.dialog(Options);
$.getJSON(`zwave-js/${NetworkIdentifier}/cfg-nodelist`, (data) => {
FirmwareForm.html('');
const Template = $('#TPL_Firmware').html();
const templateScript = Handlebars.compile(Template);
const HTML = templateScript({ nodes: data });
FirmwareForm.append(HTML);
$('#FWProgress').css({ display: 'none' });
});
}
function AddAssociation() {
const NI = $('<input>')
.attr('type', 'number')
.attr('value', 1)
.attr('min', 1);
const EI = $('<input>')
.attr('type', 'number')
.attr('value', 0)
.attr('min', 0);
const Buttons = {
Add: function () {
const EP = parseInt(EI.val());
const ND = parseInt(NI.val());
const AD = { nodeId: ND };
if (EP > 0) {
AD.endpoint = EP;
}
const TR = $('<tr>');
$('<td>')
.html(
`<div class="zwave-js-ac" style="display:inline-block"><i class="fa fa-plus fa-lg"></i></div> ${ND}`
)
.appendTo(TR);
$('<td>')
.text(EP < 1 ? '0 (Root Device)' : EP)
.appendTo(TR);
const TD3 = $('<td>').css({ textAlign: 'right' }).appendTo(TR);
$('<input>')
.attr('type', 'button')
.addClass('ui-button ui-corner-all ui-widget')
.attr('value', 'Delete')
.attr('data-address', JSON.stringify(AD))
.attr('data-committed', false)
.attr('data-action', 'add')
.click(DeleteAssociation)
.appendTo(TD3);
$('#zwave-js-associations-table').append(TR);
}
};
const HTML = $('<div>').append('Node ID: ');
NI.appendTo(HTML);
HTML.append(' Endpoint: ');
EI.appendTo(HTML);
modalPrompt(HTML, 'New Association', Buttons, true, true);
}
function DeleteAssociation() {
const Button = $(this);
const Buttons = {
Yes: function () {
const Committed =
Button.attr('data-committed') === 'true' ? true : false;
if (Committed) {
Button.attr('data-committed', false);
Button.attr('data-action', 'remove');
Button.closest('tr')
.children('td:first')
.find('.zwave-js-ac')
.css({ display: 'inline-block' })
.html('<i class="fa fa-trash fa-lg"></i>');
Button.off('click');
} else {
Button.closest('tr').remove();
}
}
};
modalPrompt(
'Are you sure you wish to remove this Association',
'Remove Association',
Buttons,
true
);
}
function GMEndPointSelected() {
const Endpoint = $(event.target).val();
const GroupIDs = Object.keys(Groups[Endpoint]);
const GroupSelector = $('#NODE_G');
GroupSelector.empty();
$('<option>Select Group...</option>').appendTo(GroupSelector);
GroupIDs.forEach((GID) => {
const Group = Groups[Endpoint][GID];
$(`<option value="${GID}">${GID} - ${Group.label}</option>`).appendTo(
GroupSelector
);
});
}
function GMGroupSelected() {
const Endpoint = parseInt($('#NODE_EP').val());
const Group = parseInt($('#NODE_G').val());
const AA = {
nodeId: parseInt(HoveredNode.nodeId),
endpoint: Endpoint
};
ControllerCMD(
DCs.getAssociations.API,
DCs.getAssociations.name,
undefined,
[AA],
DCs.getAssociations.noWait
)
.then(({ object }) => {
const Targets = object.Associations.filter((A) => A.GroupID === Group);
$('#zwave-js-associations-table').find('tr:gt(0)').remove();
// shoukd only be 1
Targets.forEach((AG) => {
AG.AssociationAddress.forEach((AD) => {
const TR = $('<tr>');
$('<td>')
.html(
`<div class="zwave-js-ac"><i class="fa fa-plus fa-lg"></i></div> ${AD.nodeId}`
)
.appendTo(TR);
$('<td>')
.html(AD.endpoint ?? '0 (Root Device)')
.appendTo(TR);
const TD3 = $('<td>').css({ textAlign: 'right' }).appendTo(TR);
$('<input>')
.attr('type', 'button')
.addClass('ui-button ui-corner-all ui-widget')
.attr('value', 'Delete')
.attr('data-address', JSON.stringify(AD))
.attr('data-committed', true)
.click(DeleteAssociation)
.appendTo(TD3);
$('#zwave-js-associations-table').append(TR);
});
});
})
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not get associations.'
);
throw new Error(err.responseText || err.message);
});
}
async function WaitForNodeWake(NodeID) {
const Buttons = {
Cancel: function () {
WakeResolver(false);
}
};
const WD = modalPrompt(
'This device is asleep, please wake it up...',
'Waiting for device to wake up',
Buttons,
false
);
WakeResolverTarget = NodeID;
const Result = await new Promise((res) => {
WakeResolver = res;
});
WakeResolver = undefined;
WakeResolverTarget = undefined;
try {
WD.dialog('destroy');
} catch (Err) {
// WD could already be destroyed
}
return Result;
}
function AssociationMGMT() {
ControllerCMD(
DCs.getAllAssociationGroups.API,
DCs.getAllAssociationGroups.name,
undefined,
[HoveredNode.nodeId],
DCs.getAllAssociationGroups.noWait
)
.then(({ object }) => {
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: `ZWave Association Management: Node ${HoveredNode.nodeId}`,
minHeight: 75,
buttons: {
'Commit Changes': async function () {
const nodeRow = $('#zwave-js-node-list').find(
`[data-nodeid='${HoveredNode.nodeId}']`
);
if (nodeRow.data().info.status.toUpperCase() === 'ASLEEP') {
const A = await WaitForNodeWake(HoveredNode.nodeId);
if (!A) {
return;
}
}
const Removals = $('#zwave-js-associations-table').find(
"input[data-committed='false'][data-action='remove']"
);
const Additions = $('#zwave-js-associations-table').find(
"input[data-committed='false'][data-action='add']"
);
const D = modalPrompt(
'Processing association changes...',
'Please wait.',
{},
false
);
const DoRemovals = () => {
return new Promise((resolve, reject) => {
const PL = [
{
nodeId: HoveredNode.nodeId,
endpoint: parseInt($('#NODE_EP').val())
},
parseInt($('#NODE_G').val()),
[]
];
Removals.each(function (index) {
PL[2].push(JSON.parse($(this).attr('data-address')));
});
if (Removals.length < 1) {
resolve();
return;
}
ControllerCMD(
DCs.removeAssociations.API,
DCs.removeAssociations.name,
undefined,
PL,
DCs.removeAssociations.noWait
)
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
};
const DoAdditions = () => {
return new Promise((resolve, reject) => {
const PL = [
{
nodeId: HoveredNode.nodeId,
endpoint: parseInt($('#NODE_EP').val())
},
parseInt($('#NODE_G').val()),
[]
];
Additions.each(function (index) {
PL[2].push(JSON.parse($(this).attr('data-address')));
});
if (Additions.length < 1) {
resolve();
return;
}
ControllerCMD(
DCs.addAssociations.API,
DCs.addAssociations.name,
undefined,
PL,
DCs.addAssociations.noWait
)
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
};
DoRemovals()
.then(() => {
DoAdditions()
.then(() => {
D.dialog('destroy');
GMGroupSelected();
})
.catch((err) => {
D.dialog('destroy');
modalAlert(
err.responseText || err.message,
'Could not process association changes.'
);
throw new Error(err.responseText || err.message);
});
})
.catch((err) => {
D.dialog('destroy');
modalAlert(
err.responseText || err.message,
'Could not process association changes.'
);
throw new Error(err.responseText || err.message);
});
},
Close: function () {
$(this).dialog('destroy');
}
}
};
const Form = $('<div>')
.css({ padding: 60, paddingTop: 30 })
.html('Please wait...');
Form.dialog(Options);
const Template = $('#TPL_Associations').html();
const templateScript = Handlebars.compile(Template);
const HTML = templateScript({ endpoints: object });
Form.html('');
Form.append(HTML);
$('#AMAddBTN').click(AddAssociation);
$('#NODE_EP').change(GMEndPointSelected);
$('#NODE_G').change(GMGroupSelected);
object.forEach((EP) => {
Groups[EP.Endpoint] = {};
EP.Groups.forEach((AG) => {
Groups[EP.Endpoint][AG.GroupID] = {
label: AG.AssociationGroupInfo.label,
maxNodes: AG.AssociationGroupInfo.maxNodes
};
});
});
})
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not get associtions.'
);
throw new Error(err.responseText || err.message);
});
}
async function GenerateMapJSON(Nodes) {
return new Promise(function (res, rej) {
ControllerCMD(
DCs.getNodeStatistics.API,
DCs.getNodeStatistics.name,
undefined,
undefined,
DCs.getNodeStatistics.noWait
)
.then(({ object }) => {
const _Nodes = [];
Nodes.forEach((N) => {
const _Node = {
controller: N.isControllerNode,
nodeId: N.nodeId,
lastSeen: N.lastSeen,
name: N.name,
location: N.location,
powerSource: N.powerSource,
statistics: object[N.nodeId.toString()]
};
_Nodes.push(_Node);
});
ControllerCMD(
DCs.getControllerStatistics.API,
DCs.getControllerStatistics.name,
undefined,
undefined,
DCs.getControllerStatistics.noWait
)
.then(({ object }) => {
_Nodes.filter((_PC) => _PC.controller)[0].statistics = object;
res(_Nodes);
})
.catch((err) => {
rej(err.responseText || err.message);
});
})
.catch((err) => {
rej(err.responseText || err.message);
});
});
}
function NetworkMap() {
ControllerCMD(
DCs.getNodes.API,
DCs.getNodes.name,
undefined,
undefined,
DCs.getNodes.noWait
)
.then(({ object }) => {
GenerateMapJSON(object)
.then((Elements) => {
localStorage.setItem('ZWJSMapData', JSON.stringify(Elements));
window.open('zwave-js/mesh', '_blank');
})
.catch((err) => {
modalAlert(err, 'Could not generate map.');
throw new Error(err);
});
})
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not generate map.');
throw new Error(err.responseText || err.message);
});
}
let nodeOpts;
function CheckDriverReady() {
const Options = {
url: `zwave-js/${NetworkIdentifier}/driverready`,
method: 'GET'
};
return $.ajax(Options);
}
function IsNodeReady(Node) {
if (!Node.ready) {
modalAlert(
'This node is not ready. Shift + click to view options',
'Node Not Ready'
);
throw new Error('Node Not Ready');
}
}
function IsDriverReady() {
if (!DriverReady) {
modalAlert(
'The Controller has not yet been initialised.',
'Controller Not Ready'
);
throw new Error('Driver Not Ready');
}
}
function ControllerCMD(mode, method, node, params, dontwait) {
IsDriverReady();
const NoTimeoutFor = ['installConfigUpdate'];
const Options = {
url: `zwave-js/${NetworkIdentifier}/cmd`,
method: 'POST',
contentType: 'application/json'
};
const Payload = {
mode: mode,
method: method
};
if (node !== undefined) {
Payload.node = node;
}
if (params !== undefined) {
Payload.params = params;
}
if (dontwait !== undefined) {
Payload.noWait = dontwait;
}
if (NoTimeoutFor.includes(method)) {
Options.timeout = 0;
Payload.noTimeout = true;
} else {
// Hopefully we will never have to depend on this, if so - there is something seriously wrong with the browser, that the user should resolve.
// Our internal timeouts of 15s will see to anything driver/server related
Options.timeout = 30000;
}
const RestrictedModes = ['IEAPI'];
const RestrictedMethods = [
'setPowerlevel',
'updateFirmware',
'abortFirmwareUpdate',
'setRFRegion',
'hardReset',
'backupNVMRaw'
];
if (
!RestrictedModes.includes(mode) &&
!RestrictedMethods.includes(method)
) {
const Copy = JSON.parse(JSON.stringify({ payload: Payload }));
delete Copy.payload.noTimeout;
delete Copy.payload.noWait;
const HTML = `${new Date().toString()}<hr /><pre class="MonitorEntry">${JSONFormatter.json.prettyPrint(
Copy
)}</pre><br />`;
try {
$('#CommandLog').append(HTML);
$('#CommandLog').scrollTop($('#CommandLog')[0].scrollHeight);
// eslint-disable-next-line no-empty
} catch (err) {}
}
Options.data = JSON.stringify(Payload);
return $.ajax(Options);
}
function AddNodeGroup(G) {
const GP = $('<div>').css({
height: '30px',
lineHeight: '30px',
paddingLeft: '15px',
backgroundColor: 'lightgray',
fontWeight: 'bold'
});
GP.attr('id', 'zwave-js-node-group-' + G.replace(/ /g, '-'));
GP.html(G);
$('#zwave-js-node-list').append(GP);
return GP;
}
/*
GetNodes is called for every node READY event,
so we better limit this as we will get a flood of these during start up
*/
let GNTimer = undefined;
function GetNodesThrottled() {
if (GNTimer !== undefined) {
clearTimeout(GNTimer);
GNTimer = undefined;
}
GNTimer = setTimeout(() => {
GetNodes();
}, 250);
}
function GetNodes() {
BA = undefined;
deselectCurrentNode();
ControllerCMD(
DCs.getNodes.API,
DCs.getNodes.name,
undefined,
undefined,
DCs.getNodes.noWait
)
.then(({ object }) => {
const controllerNode = object.filter((N) => N.isControllerNode);
if (controllerNode.length > 0) {
makeInfo(
'#zwave-js-controller-info',
controllerNode[0].deviceConfig,
controllerNode[0].firmwareVersion
);
}
$('#zwave-js-node-list').empty();
const Nodes = object.filter((node) => node && !node.isControllerNode);
if (GroupedNodes) {
let Groups = {};
Nodes.forEach((N) => {
if (N.location === undefined || N.location.length < 1) {
if (!Groups.hasOwnProperty('No Location')) {
Groups['No Location'] = [];
}
Groups['No Location'].push(renderNode(N));
} else {
if (!Groups.hasOwnProperty(N.location)) {
Groups[N.location] = [];
}
Groups[N.location].push(renderNode(N));
}
});
Groups = sortByKey(Groups);
Object.keys(Groups).forEach((G) => {
AddNodeGroup(G);
Groups[G].forEach((NE) => {
$('#zwave-js-node-list').append(NE);
});
});
} else {
Nodes.forEach((N) => $('#zwave-js-node-list').append(renderNode(N)));
}
NodesListed = true;
$('#zwave-js-node-properties').treeList('empty');
})
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not fetch nodes.');
throw new Error(err.responseText || err.message);
});
}
function EnableCritical(Value) {
if (Value) {
$('.CriticalDisable').prop('disabled', false);
$('.CriticalDisable').css({ opacity: '1.0' });
} else {
$('.CriticalDisable').prop('disabled', true);
$('.CriticalDisable').css({ opacity: '0.4' });
}
}
restoreNVM = () => {
$('#FILE_BU').on('change', () => {
const FE = $('#FILE_BU')[0].files[0];
const FD = new FormData();
FD.append('Binary', FE);
const Options = {
url: `zwave-js/${NetworkIdentifier}/restorenvm`,
method: 'POST',
contentType: false,
processData: false,
data: FD
};
$.ajax(Options)
.then(() => {
EnableCritical(false);
$('#NVMProgressLabel').html('Starting Restore...');
$('#NVMProgress').css({ display: 'block' });
})
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not restore NVM.');
EnableCritical(true);
throw new Error(err.responseText || err.message);
});
$('#FILE_BU').off('change');
});
$('#FILE_BU').click();
};
backupNVMRaw = () => {
EnableCritical(false);
ControllerCMD(
DCs.backupNVMRaw.API,
DCs.backupNVMRaw.name,
undefined,
undefined,
DCs.backupNVMRaw.noWait
)
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not back NVM.');
EnableCritical(true);
throw new Error(err.responseText || err.message);
})
.then(() => {
$('#NVMProgressLabel').html('Backing up NVM...');
$('#NVMProgress').css({ display: 'block' });
});
};
SetRegion = () => {
EnableCritical(false);
ControllerCMD(
DCs.setRFRegion.API,
DCs.setRFRegion.name,
undefined,
[parseInt($('#RF_REGION').val())],
DCs.setRFRegion.noWait
)
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not set RF Region.');
EnableCritical(true);
throw new Error(err.responseText || err.message);
})
.then(({ object }) => {
EnableCritical(true);
if (!object.success) {
modalAlert(
'The controller did not accept the values provided.',
'Could not set RF Region.'
);
} else {
modalAlert('Settings were applied successfully.', 'RF Region set.');
}
});
};
SetPowerLevel = () => {
EnableCritical(false);
ControllerCMD(
DCs.setPowerlevel.API,
DCs.setPowerlevel.name,
undefined,
[
parseFloat($('#RF_POWER').slider('value')),
parseFloat($('#RF_0DBM').slider('value'))
],
DCs.setPowerlevel.noWait
)
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not set power level.'
);
EnableCritical(true);
throw new Error(err.responseText || err.message);
})
.then(({ object }) => {
EnableCritical(true);
if (!object.success) {
modalAlert(
'The controller did not accept the values provided.',
'Could not set power level.'
);
} else {
modalAlert('Settings were applied successfully.', 'Power level set.');
}
});
};
function sortByKey(obj) {
const keys = Object.keys(obj);
keys.sort();
const sorted = {};
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
sorted[key] = obj[key];
}
return sorted;
}
function ListRequestedClass(Classes) {
Classes.forEach((SC) => {
$('tr#TR_' + SC).css({ opacity: 1.0 });
$('input#SC_' + SC).prop('disabled', false);
});
StepsAPI.setStepIndex(StepList.Classes);
}
function DisplayDSK(DSK) {
$('#DSK_Previw').html(DSK);
StepsAPI.setStepIndex(StepList.DSK);
}
ValidateDSK = () => {
const B = event.target;
ControllerCMD(
DCs.verifyDSK.API,
DCs.verifyDSK.name,
undefined,
[$('#SC_DSK').val()],
DCs.verifyDSK.noWait
)
.catch((err) => {
modalAlert(err.responseText || err.message, 'Could not verify DSK.');
throw new Error(err.responseText || err.message);
})
.then(() => {
$(B).html('Please wait...');
ClearIETimer();
ClearSecurityCountDown();
$(B).prop('disabled', true);
});
};
GrantSelected = () => {
const B = event.target;
const Granted = [];
$('.SecurityClassCB').each(function () {
if ($(this).is(':checked')) {
Granted.push(parseInt($(this).attr('id').replace('SC_', '')));
}
});
ControllerCMD(
DCs.grantClasses.API,
DCs.grantClasses.name,
undefined,
[Granted],
DCs.grantClasses.noWait
)
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not grant Security Classes.'
);
throw new Error(err.responseText || err.message);
})
.then(() => {
$(B).html('Please wait...');
ClearIETimer();
ClearSecurityCountDown();
$(B).prop('disabled', true);
});
};
StartReplace = (Mode) => {
const B = event.target;
const OT = $(B).html();
$(B).html('Please wait...');
$(B).prop('disabled', true);
const Request = {};
switch (Mode) {
case 'S2':
Request.strategy = 4;
break;
case 'S0':
Request.strategy = 3;
break;
case 'None':
Request.strategy = 2;
break;
}
ControllerCMD(
DCs.checkKeyReq.API,
DCs.checkKeyReq.name,
undefined,
[Request.strategy],
DCs.checkKeyReq.noWait
)
.then(({ object }) => {
if (object.ok) {
ControllerCMD(
DCs.replaceFailedNode.API,
DCs.replaceFailedNode.name,
undefined,
[parseInt(HoveredNode.nodeId), Request],
DCs.replaceFailedNode.noWait
).catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not replace Node.'
);
$(B).html(OT);
$(B).prop('disabled', false);
throw new Error(err.responseText || err.message);
});
} else {
modalAlert(object.message, 'Could not replace Node.');
$(B).html(OT);
$(B).prop('disabled', false);
}
})
.catch((err) => {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(err.responseText || err.message, 'Could not replace Node.');
throw new Error(err.responseText || err.message);
});
};
StartInclusionExclusion = (Mode) => {
const B = event.target;
const OT = $(B).html();
$(B).html('Please wait...');
$(B).prop('disabled', true);
const Request = {};
const PreferS0 = $('#PS0').is(':checked');
switch (Mode) {
case 'Default':
Request.strategy = 0;
Request.forceSecurity = PreferS0;
break;
case 'EditSmartStart':
StepsAPI.setStepIndex(StepList.SmartStartListEdit);
$('#SSPurgeButton').css({ display: 'inline-block' });
$.ajax({
url: `zwave-js/${NetworkIdentifier}/smart-start-list`,
method: 'GET',
dataType: 'json',
error: function (err) {
modalAlert(
err.responseText || err.message,
'Could not fetch Smart Start list.'
);
throw new Error(err.responseText || err.message);
},
success: function (List) {
List.forEach((Entry) => {
const Item = $('<tr class="SmartStartEntry">');
Item.append(`<td>${Entry.dsk.substring(0, 5)}</td>`);
if (Entry.manufacturer === undefined) {
Item.append(`<td>Unknown Manufacturer</td>`);
Item.append(`<td>Unknown Product</td>`);
} else {
Item.append(`<td>${Entry.manufacturer}</td>`);
Item.append(
`<td>${Entry.label}<br /><span style="font-size:12px">${Entry.description}</span></td>`
);
}
const BTNTD = $('<td>');
BTNTD.css('text-align', 'right');
const BTN = $('<button>');
BTN.addClass('ui-button ui-corner-all ui-widget');
BTN.html('Remove');
BTN.click(() => {
const Buttons = {
Yes: function () {
ControllerCMD(
DCs.unprovisionSmartStartNode.API,
DCs.unprovisionSmartStartNode.name,
undefined,
[Entry.dsk],
DCs.unprovisionSmartStartNode.noWait
)
.then(() => {
Item.remove();
})
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not remove Smart Start entry.'
);
throw new Error(err.responseText || err.message);
});
}
};
modalPrompt(
'Are you sure you wish to remove this entry?',
'Remove Smart Start Entry',
Buttons,
true
);
});
BTN.appendTo(BTNTD);
Item.append(BTNTD);
$('#SmartStartEditList').append(Item);
});
}
});
return;
case 'SmartStart':
ControllerCMD(
DCs.checkKeyReq.API,
DCs.checkKeyReq.name,
undefined,
[1],
DCs.checkKeyReq.noWait
)
.catch((err) => {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(
err.responseText || err.message,
'Could not start Inclusion'
);
throw new Error(err.responseText || err.message);
})
.then(({ object }) => {
if (object.ok) {
$('#SmartStartCommit').css({ display: 'inline' });
$.ajax({
url: `zwave-js/${NetworkIdentifier}/smartstart/startserver`,
method: 'GET',
error: function (err) {
modalAlert(
err.responseText || err.message,
'Could not start Inclusion'
);
throw new Error(err.responseText || err.message);
},
success: function (QRData) {
StepsAPI.setStepIndex(StepList.SmartStart);
new QRCode($('#SmartStartQR')[0], {
text: QRData,
width: 150,
height: 150,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.L
});
$('#SmartStartURL').html(QRData);
}
});
} else {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(object.message, 'Could not start Inclusion');
}
});
return;
case 'None':
Request.strategy = 2;
break;
case 'S0':
Request.strategy = 3;
break;
case 'S2':
Request.strategy = 4;
break;
case 'Remove':
ControllerCMD(
DCs.beginExclusion.API,
DCs.beginExclusion.name,
undefined,
[$('#ERP').is(':checked')],
DCs.beginExclusion.noWait
).catch((err) => {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(
err.responseText || err.message,
'Could not start Exclusion'
);
throw new Error(err.responseText || err.message);
});
return;
}
ControllerCMD(
DCs.checkKeyReq.API,
DCs.checkKeyReq.name,
undefined,
[Request.strategy],
DCs.checkKeyReq.noWait
)
.then(({ object }) => {
if (object.ok) {
ControllerCMD(
DCs.beginInclusion.API,
DCs.beginInclusion.name,
undefined,
[Request],
DCs.beginInclusion.noWait
).catch((err) => {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(
err.responseText || err.message,
'Could not start Inclusion'
);
throw new Error(err.responseText || err.message);
});
} else {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(object.message, 'Could not start Inclusion');
}
})
.catch((err) => {
$(B).html(OT);
$(B).prop('disabled', false);
modalAlert(
err.responseText || err.message,
'Could not start Inclusion'
);
throw new Error(err.responseText || err.message);
});
};
function ShowIncludeExcludePrompt() {
const ParentDialog = $('<div>').css({ padding: 10 }).html('Please wait...');
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: `Node Inclusion/Exclusion (Network ${NetworkIdentifier})`,
minHeight: 75,
buttons: [
{
id: 'SSPurgeButton',
text: 'Remove All',
click: function () {
const Buttons = {
'Yes - Remove': function () {
ControllerCMD(
DCs.unprovisionAllSmartStart.API,
DCs.unprovisionAllSmartStart.name,
undefined,
undefined,
DCs.unprovisionAllSmartStart.noWait
)
.catch((err) => {
ParentDialog.dialog('destroy');
modalAlert(
err.responseText || err.message,
'Could not purge Smart Start entries'
);
throw new Error(err.responseText || err.message);
})
.then(() => {
ParentDialog.dialog('destroy');
});
}
};
modalPrompt(
'Are you sure you wish to remove all pre-provisioned device entries (the devices them self wont be removed)',
'Purge Provisioning List',
Buttons,
true
);
}
},
{
id: 'IEButton',
text: 'Cancel',
click: function () {
$.ajax({
url: `zwave-js/${NetworkIdentifier}/smartstart/stopserver`,
method: 'GET'
}).catch((err) => {
console.error(err);
});
ClearIETimer();
ClearSecurityCountDown();
ControllerCMD(
DCs.stopIE.API,
DCs.stopIE.name,
undefined,
undefined,
DCs.stopIE.noWait
).catch((err) => {
console.error(err);
});
ParentDialog.dialog('destroy');
}
},
{
id: 'SmartStartCommit',
text: 'Commit Scans',
click: function () {
const SSEntries = $('.SmartStartEntry');
const Entries = [];
SSEntries.each(function (i, e) {
Entries.push($(e).data('inclusionPackage'));
});
ControllerCMD(
DCs.commitScans.API,
DCs.commitScans.name,
undefined,
Entries,
DCs.commitScans.noWait
)
.then(() => {
StepsAPI.setStepIndex(StepList.SmartStartDone);
$('#SmartStartCommit').css({ display: 'none' });
$('#IEButton').css({ display: 'none' });
$('#IEClose').css({ display: 'inline-block' });
$.ajax({
url: `zwave-js/${NetworkIdentifier}/smartstart/stopserver`,
method: 'GET'
}).catch((err) => {
console.error(err);
});
})
.catch((err) => {
$.ajax({
url: `zwave-js/${NetworkIdentifier}/smartstart/stopserver`,
method: 'GET'
}).catch((err) => {
console.error(err);
});
modalAlert(
err.responseText || err.message,
'Could not commit Smart Start entries.'
);
throw new Error(err.responseText || err.message);
});
}
},
{
id: 'IEClose',
text: 'Ok',
click: function () {
ParentDialog.dialog('destroy');
}
}
]
};
ParentDialog.dialog(Options);
ParentDialog.html('');
ParentDialog.append($('#TPL_Include').html());
const Steps = $('#IncludeWizard').steps({ showFooterButtons: false });
StepsAPI = Steps.data('plugin_Steps');
$('#SmartStartCommit').css({ display: 'none' });
$('#IEClose').css({ display: 'none' });
$('#SSPurgeButton').css({ display: 'none' });
}
function ShowReplacePrompt() {
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: 'Replace Node',
minHeight: 75,
buttons: [
{
id: 'IEButton',
text: 'Cancel',
click: function () {
ControllerCMD(
DCs.stopIE.API,
DCs.stopIE.name,
undefined,
undefined,
DCs.stopIE.noWait
).catch((err) => {
console.error(err);
});
$(this).dialog('destroy');
}
}
]
};
const IncludeForm = $('<div>').css({ padding: 10 }).html('Please wait...');
IncludeForm.dialog(Options);
IncludeForm.html('');
IncludeForm.append($('#TPL_Include').html());
const Steps = $('#IncludeWizard').steps({ showFooterButtons: false });
StepsAPI = Steps.data('plugin_Steps');
StepsAPI.setStepIndex(StepList.ReplaceSecurityMode);
}
function StartNodeHeal() {
ControllerCMD(
DCs.rebuildNodeRoutes.API,
DCs.rebuildNodeRoutes.name,
undefined,
[HoveredNode.nodeId],
DCs.rebuildNodeRoutes.noWait
).catch((err) => {
modalAlert(err.responseText || err.message, 'Could not start Node heal.');
throw new Error(err.responseText || err.message);
});
}
function StartHeal() {
ControllerCMD(
DCs.beginRebuildingRoutes.API,
DCs.beginRebuildingRoutes.name,
undefined,
undefined,
DCs.beginRebuildingRoutes.noWait
).catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not start Network heal.'
);
throw new Error(err.responseText || err.message);
});
}
function StopHeal() {
ControllerCMD(
DCs.stopRebuildingRoutes.API,
DCs.stopRebuildingRoutes.name,
undefined,
undefined,
DCs.stopRebuildingRoutes.noWait
).catch((err) => {
console.error(err);
});
}
function Reset() {
const Buttons = {
'Yes - Reset': function () {
ControllerCMD(
DCs.hardReset.API,
DCs.hardReset.name,
undefined,
undefined,
DCs.hardReset.noWait
)
.then(() => {
modalAlert('Your Controller has been reset.', 'Reset Complete');
GetNodes();
})
.catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not reset the Controller.'
);
throw new Error(err.responseText || err.message);
});
}
};
modalPrompt(
'Are you sure you wish to reset your Controller? This action is irreversible, and will clear the Controllers data and configuration.',
'Reset Controller',
Buttons,
true
);
}
function InterviewNode() {
ControllerCMD(
DCs.refreshInfo.API,
DCs.refreshInfo.name,
undefined,
[HoveredNode.nodeId],
DCs.refreshInfo.noWait
).catch((err) => {
modalAlert(
err.responseText || err.message,
'Could not interview the Node.'
);
throw new Error(err.responseText || err.message);
});
}
function OpenDB() {
const info = HoveredNode.deviceConfig;
const id = [
'0x' + info.manufacturerId.toString(16).padStart(4, '0'),
'0x' + info.devices[0].productType.toString(16).padStart(4, '0'),
'0x' + info.devices[0].productId.toString(16).padStart(4, '0'),
info.firmwareVersion.min
].join(':');
window.open(`https://devices.zwave-js.io/?jumpTo=${id}`, '_blank');
}
function RFSettings() {
const Options = {
draggable: false,
modal: true,
resizable: false,
width: WindowSize.w,
height: WindowSize.h,
title: `Advanced Transceiver Settings (Network ${NetworkIdentifier})`,
minHeight: 75,
buttons: {
Close: function () {
$(this).dialog('destroy');
}
}
};
RFForm = $('<div>').css({ padding: 10 }).html('Please wait...');
RFForm.dialog(Options);
RFForm.html('');
const Template = $('#TPL_RF').html();
const templateScript = Handlebars.compile(Template);
const HTML = templateScript({});
RFForm.append(HTML);
$('#NVMProgress').css({ display: 'none' });
const PowerSlider = $('#RF_POWER_SLIDER');
$('#RF_POWER').slider({
min: -12.8,
max: 12.7,
step: 0.1,
range: 'min',
slide: function (event, ui) {
PowerSlider.text(ui.value);
}
});
const MeasuredSlider = $('#RF_0DBM_SLIDER');
$('#RF_0DBM').slider({
min: -12.8,
max: 12.7,
step: 0.1,
range: 'min',
slide: function (event, ui) {
MeasuredSlider.text(ui.value);
}
});
const GetPower = () => {
ControllerCMD(
DCs.getPowerlevel.API,
DCs.getPowerlevel.name,
undefined,
undefined,
DCs.getPowerlevel.noWait
)
.then(({ object }) => {
PowerSlider.text(object.powerlevel);
$('#RF_POWER').slider('value', object.powerlevel);
MeasuredSlider.text(object.measured0dBm);
$('#RF_0DBM').slider('value', object.measured0dBm);
})
.catch((err) => {
$('#RF_TR_POWER').css({ opacity: '0.3', pointerEvents: 'none' });
console.error(err);
});
};
ControllerCMD(
DCs.getRFRegion.API,
DCs.getRFRegion.name,
undefined,
undefined,
DCs.getRFRegion.noWait
)
.then(({ object }) => {
$('#RF_REGION').val(object);
GetPower();
})
.catch((err) => {
$('#RF_TR_REGION').css({ opacity: '0.3', pointerEvents: 'none' });
console.error(err);
GetPower();
});
}
function RemoveFailedNode() {
if (Removing) {
modalAlert(
'A node is already being removed, please allow a minute or 2.',
'Could Not Remove Node'
);
return;
}
const Buttons = {
'Yes - Remove': function () {
const D = modalPrompt(
'Checking if Node has failed...',
'Please wait.',
{},
false
);
Removing = true;
ControllerCMD(
DCs.removeFailedNode.API,
DCs.removeFailedNode.name,
undefined,
[H