From 5e86b2f4cff1130924facef03ebe190dd50ec0b9 Mon Sep 17 00:00:00 2001 From: kevin williams Date: Sat, 2 Nov 2024 09:10:29 -0700 Subject: [PATCH 1/5] add support for mqtt pump configuration --- web/bindings/mqtt.json | 6 ++-- web/interfaces/mqttInterface.ts | 53 +++++++++++++++++++++++++++++---- web/services/config/Config.ts | 3 +- web/services/state/State.ts | 2 +- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/web/bindings/mqtt.json b/web/bindings/mqtt.json index dbf5f103..a9a593ee 100644 --- a/web/bindings/mqtt.json +++ b/web/bindings/mqtt.json @@ -411,11 +411,13 @@ { "topic": "state/pumps/@bind=data.id;/@bind=data.name;", "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.flow > 0 || data.rpm > 0?'\"on\"':'\"off\"';}", - "description": "Bind 'on'/'off' as a message to the state topic." + "description": "Bind 'on'/'off' as a message to the state topic.", + "enabled": true }, { "topic": "state/pumps/@bind=data.id;/@bind=data.name;/rpm", - "message": "{\"rpm\":@bind=data.rpm;}" + "message": "{\"rpm\":@bind=data.rpm;}", + "enabled": true }, { "topic": "state/pumps/@bind=data.id;/@bind=data.name;/flow", diff --git a/web/interfaces/mqttInterface.ts b/web/interfaces/mqttInterface.ts index 3b08417a..4d206714 100644 --- a/web/interfaces/mqttInterface.ts +++ b/web/interfaces/mqttInterface.ts @@ -134,8 +134,8 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { let root = this.rootTopic(); if (typeof this.subscriptions !== 'undefined') { for (let i = 0; i < this.subscriptions.length; i++) { - let sub = this.subscriptions[i]; - if(sub.enabled !== false) this.topics.push(new MqttTopicSubscription(root, sub)); + let sub = this.subscriptions[i]; + if(sub.enabled !== false) this.topics.push(new MqttTopicSubscription(root, sub)); } } else if (typeof root !== 'undefined') { @@ -155,6 +155,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { `state/temps`, `config/tempSensors`, `config/chemController`, + `config/pump`, `state/chemController`, `config/chlorinator`, `state/chlorinator`]; @@ -391,7 +392,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { let sub: MqttTopicSubscription = this.topics.find(elem => topic === elem.topicPath); if (typeof sub !== 'undefined') { - logger.debug(`MQTT: Inbound ${topic} ${message.toString()}`); + logger.silly(`MQTT: Inbound ${topic} ${message.toString()}`); // Alright so now lets process our results. if (typeof sub.fnProcessor === 'function') { sub.executeProcessor(this, msg); @@ -404,7 +405,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { // between them so it doesn't matter but it will become an issue. switch (topics[topics.length - 1].toLowerCase()) { case 'setstate': { - let id = parseInt(msg.id, 10); + let id = parseInt(msg.id, 10); if (typeof id !== 'undefined' && isNaN(id)) { logger.error(`Inbound MQTT ${topics} has an invalid id (${id}) in the message (${msg}).`) }; @@ -456,7 +457,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { switch (topics[topics.length - 2].toLowerCase()) { case 'circuits': case 'circuit': - try { + try { await sys.board.circuits.toggleCircuitStateAsync(id); } catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } @@ -472,6 +473,48 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { logger.warn(`MQTT: Inbound topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${msg} `) } break; + } + case 'pump': + { + let id = parseInt(msg.id, 10); + if (typeof id !== 'undefined' && isNaN(id)) { + logger.error(`Inbound MQTT ${topics} has an invalid id (${id}) in the message (${Utils.stringifyJSON(msg)}).`) + }; + switch (topics[topics.length - 2].toLowerCase()) { + case 'config': + let circuits = msg.circuits; + if(typeof circuits === 'undefined' || typeof circuits[0] === 'undefined') { + logger.error(`Inbound pump config MQTT ${topics} has an invalid circuits node: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { //K.W. it's probably time for a fast json validator like AJV... + let circuits_id = parseInt(circuits[0].id,10); + let circuit = parseInt(circuits[0].circuit,10); + let speed = parseInt(circuits[0].speed,10); + if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { + logger.error(`Inbound pump config MQTT ${topics} contains invalid circuits.id (${Utils.stringifyJSON(circuits_id)}),circuits.circuit (${Utils.stringifyJSON(circuit)}), or circuits.speed (${Utils.stringifyJSON(speed)})in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + let units = circuits[0].units; + if(typeof units !== 'undefined') { + let val = parseInt(circuits[0].units.val,10); + if(typeof val !== 'undefined' && isNaN(val)) { + logger.error(`Inbound pump config MQTT ${topics} invalid circuits.units.val (${circuits.units.val}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + try { await sys.board.pumps.setPumpAsync(msg); } + catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } + } + } else { + logger.error(`Inbound pump config MQTT ${topics} must include circuits.units list in the message (${Utils.stringifyJSON(msg)}).`); + return; + } + } + } + break; + default: + logger.warn(`MQTT: Inbound pump topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${Utils.stringifyJSON(msg)} `) + } + break; } case 'heatsetpoint': try { diff --git a/web/services/config/Config.ts b/web/services/config/Config.ts index 8da0f443..06f457fb 100755 --- a/web/services/config/Config.ts +++ b/web/services/config/Config.ts @@ -569,7 +569,8 @@ export class ConfigRoute { // Change the pump attributes. This will add the pump if it doesn't exist, set // any affiliated circuits and maintain all attribututes of the pump. // RSG: Caveat - you have to send none or all of the pump circuits or any not included be deleted. - try { + console.log(`request: ${JSON.stringify(req.body)}`); + try { let pump = await sys.board.pumps.setPumpAsync(req.body); return res.status(200).send((pump).get(true)); } diff --git a/web/services/state/State.ts b/web/services/state/State.ts index 4e57a5ef..d6c84557 100755 --- a/web/services/state/State.ts +++ b/web/services/state/State.ts @@ -291,7 +291,7 @@ export class StateRoute { return res.status(200).send(pump.getExtended()); }); app.put('/state/circuit/setState', async (req, res, next) => { - try { + try { // Do some work to allow the legacy state calls to work. For some reason the state value is generic while all of the // circuits are actually binary states. While this may need to change in the future it seems like a distant plan // that circuits would have more than 2 states. Not true for other equipment but certainly true for individual circuits/features/groups. From 702ff8c291e981180f5bf242c624abe5c3d2acf7 Mon Sep 17 00:00:00 2001 From: kevin williams Date: Sat, 2 Nov 2024 09:26:00 -0700 Subject: [PATCH 2/5] remove debugging stms and correct formatting --- web/interfaces/mqttInterface.ts | 17 +++++++++-------- web/services/config/Config.ts | 3 +-- web/services/state/State.ts | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web/interfaces/mqttInterface.ts b/web/interfaces/mqttInterface.ts index 4d206714..1c0ccfbc 100644 --- a/web/interfaces/mqttInterface.ts +++ b/web/interfaces/mqttInterface.ts @@ -134,8 +134,8 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { let root = this.rootTopic(); if (typeof this.subscriptions !== 'undefined') { for (let i = 0; i < this.subscriptions.length; i++) { - let sub = this.subscriptions[i]; - if(sub.enabled !== false) this.topics.push(new MqttTopicSubscription(root, sub)); + let sub = this.subscriptions[i]; + if(sub.enabled !== false) this.topics.push(new MqttTopicSubscription(root, sub)); } } else if (typeof root !== 'undefined') { @@ -392,7 +392,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { let sub: MqttTopicSubscription = this.topics.find(elem => topic === elem.topicPath); if (typeof sub !== 'undefined') { - logger.silly(`MQTT: Inbound ${topic} ${message.toString()}`); + logger.debug(`MQTT: Inbound ${topic} ${message.toString()}`); // Alright so now lets process our results. if (typeof sub.fnProcessor === 'function') { sub.executeProcessor(this, msg); @@ -404,8 +404,8 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { // RKS: Not sure why there is no processing of state vs config here. Right now the topics are unique // between them so it doesn't matter but it will become an issue. switch (topics[topics.length - 1].toLowerCase()) { - case 'setstate': { - let id = parseInt(msg.id, 10); + case 'setstate': { + let id = parseInt(msg.id, 10); if (typeof id !== 'undefined' && isNaN(id)) { logger.error(`Inbound MQTT ${topics} has an invalid id (${id}) in the message (${msg}).`) }; @@ -457,7 +457,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { switch (topics[topics.length - 2].toLowerCase()) { case 'circuits': case 'circuit': - try { + try { await sys.board.circuits.toggleCircuitStateAsync(id); } catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } @@ -491,7 +491,8 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { let circuit = parseInt(circuits[0].circuit,10); let speed = parseInt(circuits[0].speed,10); if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { - logger.error(`Inbound pump config MQTT ${topics} contains invalid circuits.id (${Utils.stringifyJSON(circuits_id)}),circuits.circuit (${Utils.stringifyJSON(circuit)}), or circuits.speed (${Utils.stringifyJSON(speed)})in the message (${Utils.stringifyJSON(msg)}).`); + logger.error(`Inbound pump config MQTT ${topics} contains invalid circuits.id (${Utils.stringifyJSON(circuits_id)}),`+ + `circuits.circuit (${Utils.stringifyJSON(circuit)}), or circuits.speed (${Utils.stringifyJSON(speed)})in the message (${Utils.stringifyJSON(msg)}).`); return; } else { let units = circuits[0].units; @@ -513,7 +514,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { break; default: logger.warn(`MQTT: Inbound pump topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${Utils.stringifyJSON(msg)} `) - } + } break; } case 'heatsetpoint': diff --git a/web/services/config/Config.ts b/web/services/config/Config.ts index 06f457fb..e287b777 100755 --- a/web/services/config/Config.ts +++ b/web/services/config/Config.ts @@ -569,8 +569,7 @@ export class ConfigRoute { // Change the pump attributes. This will add the pump if it doesn't exist, set // any affiliated circuits and maintain all attribututes of the pump. // RSG: Caveat - you have to send none or all of the pump circuits or any not included be deleted. - console.log(`request: ${JSON.stringify(req.body)}`); - try { + try { let pump = await sys.board.pumps.setPumpAsync(req.body); return res.status(200).send((pump).get(true)); } diff --git a/web/services/state/State.ts b/web/services/state/State.ts index d6c84557..dc39adbf 100755 --- a/web/services/state/State.ts +++ b/web/services/state/State.ts @@ -291,7 +291,7 @@ export class StateRoute { return res.status(200).send(pump.getExtended()); }); app.put('/state/circuit/setState', async (req, res, next) => { - try { + try { // Do some work to allow the legacy state calls to work. For some reason the state value is generic while all of the // circuits are actually binary states. While this may need to change in the future it seems like a distant plan // that circuits would have more than 2 states. Not true for other equipment but certainly true for individual circuits/features/groups. From 8ddaef0c60720241eef85858d4d1e4e17c1ba7ce Mon Sep 17 00:00:00 2001 From: kevin williams Date: Sat, 2 Nov 2024 21:59:22 -0700 Subject: [PATCH 3/5] revert changes --- web/services/config/Config.ts | 2 +- web/services/state/State.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/services/config/Config.ts b/web/services/config/Config.ts index e287b777..8da0f443 100755 --- a/web/services/config/Config.ts +++ b/web/services/config/Config.ts @@ -569,7 +569,7 @@ export class ConfigRoute { // Change the pump attributes. This will add the pump if it doesn't exist, set // any affiliated circuits and maintain all attribututes of the pump. // RSG: Caveat - you have to send none or all of the pump circuits or any not included be deleted. - try { + try { let pump = await sys.board.pumps.setPumpAsync(req.body); return res.status(200).send((pump).get(true)); } diff --git a/web/services/state/State.ts b/web/services/state/State.ts index dc39adbf..4e57a5ef 100755 --- a/web/services/state/State.ts +++ b/web/services/state/State.ts @@ -291,7 +291,7 @@ export class StateRoute { return res.status(200).send(pump.getExtended()); }); app.put('/state/circuit/setState', async (req, res, next) => { - try { + try { // Do some work to allow the legacy state calls to work. For some reason the state value is generic while all of the // circuits are actually binary states. While this may need to change in the future it seems like a distant plan // that circuits would have more than 2 states. Not true for other equipment but certainly true for individual circuits/features/groups. From 30989f725e4fdb9b4abf5088079ca492c1593e27 Mon Sep 17 00:00:00 2001 From: kevin williams Date: Mon, 18 Nov 2024 22:13:46 -0800 Subject: [PATCH 4/5] support for multiple circuits within a pump --- web/interfaces/mqttInterface.ts | 53 ++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/web/interfaces/mqttInterface.ts b/web/interfaces/mqttInterface.ts index 1c0ccfbc..3cd17027 100644 --- a/web/interfaces/mqttInterface.ts +++ b/web/interfaces/mqttInterface.ts @@ -483,34 +483,39 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { switch (topics[topics.length - 2].toLowerCase()) { case 'config': let circuits = msg.circuits; - if(typeof circuits === 'undefined' || typeof circuits[0] === 'undefined') { - logger.error(`Inbound pump config MQTT ${topics} has an invalid circuits node: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); - return; - } else { //K.W. it's probably time for a fast json validator like AJV... - let circuits_id = parseInt(circuits[0].id,10); - let circuit = parseInt(circuits[0].circuit,10); - let speed = parseInt(circuits[0].speed,10); - if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { - logger.error(`Inbound pump config MQTT ${topics} contains invalid circuits.id (${Utils.stringifyJSON(circuits_id)}),`+ - `circuits.circuit (${Utils.stringifyJSON(circuit)}), or circuits.speed (${Utils.stringifyJSON(speed)})in the message (${Utils.stringifyJSON(msg)}).`); - return; - } else { - let units = circuits[0].units; - if(typeof units !== 'undefined') { - let val = parseInt(circuits[0].units.val,10); - if(typeof val !== 'undefined' && isNaN(val)) { - logger.error(`Inbound pump config MQTT ${topics} invalid circuits.units.val (${circuits.units.val}) in the message (${Utils.stringifyJSON(msg)}).`); - return; + if(typeof circuits !== 'undefined') circuits.forEach(async ckt => { + if(typeof ckt !== 'undefined') { + let circuits_id = parseInt(ckt.id,10); + let circuit = parseInt(ckt.circuit,10); + let speed = parseInt(ckt.speed,10); + if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { + logger.error(`Inbound pump config MQTT ${topics} contains invalid circuit parameters in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + let units = ckt.units; + if(typeof units !== 'undefined') { + let val = parseInt(ckt.units.val,10); + if(typeof val !== 'undefined' && isNaN(val)) { + logger.error(`Inbound pump config MQTT ${topics} invalid circuits.units.val (${circuits.units.val}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + //validated a circuit is well formed + try { await sys.board.pumps.setPumpAsync(msg); } + catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } + } } else { - try { await sys.board.pumps.setPumpAsync(msg); } - catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } + logger.error(`Inbound pump config MQTT ${topics} must include circuits.units list in the message (${Utils.stringifyJSON(msg)}).`); + return; } - } else { - logger.error(`Inbound pump config MQTT ${topics} must include circuits.units list in the message (${Utils.stringifyJSON(msg)}).`); - return; } + } else { + logger.error(`Inbound pump config MQTT ${topics} has an invalid circuit: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } + }); else { + logger.error(`Inbound pump config MQTT ${topics} has a invalid circuits: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); + return; } - } break; default: logger.warn(`MQTT: Inbound pump topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${Utils.stringifyJSON(msg)} `) From 7fd950b8c16cace65cb84d3ea8bbb4fd209ef37a Mon Sep 17 00:00:00 2001 From: kevin williams Date: Mon, 18 Nov 2024 22:18:45 -0800 Subject: [PATCH 5/5] correct tab whitespaces --- web/interfaces/mqttInterface.ts | 76 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/web/interfaces/mqttInterface.ts b/web/interfaces/mqttInterface.ts index 3cd17027..2349b6b2 100644 --- a/web/interfaces/mqttInterface.ts +++ b/web/interfaces/mqttInterface.ts @@ -482,44 +482,44 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { }; switch (topics[topics.length - 2].toLowerCase()) { case 'config': - let circuits = msg.circuits; - if(typeof circuits !== 'undefined') circuits.forEach(async ckt => { - if(typeof ckt !== 'undefined') { - let circuits_id = parseInt(ckt.id,10); - let circuit = parseInt(ckt.circuit,10); - let speed = parseInt(ckt.speed,10); - if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { - logger.error(`Inbound pump config MQTT ${topics} contains invalid circuit parameters in the message (${Utils.stringifyJSON(msg)}).`); - return; - } else { - let units = ckt.units; - if(typeof units !== 'undefined') { - let val = parseInt(ckt.units.val,10); - if(typeof val !== 'undefined' && isNaN(val)) { - logger.error(`Inbound pump config MQTT ${topics} invalid circuits.units.val (${circuits.units.val}) in the message (${Utils.stringifyJSON(msg)}).`); - return; - } else { - //validated a circuit is well formed - try { await sys.board.pumps.setPumpAsync(msg); } - catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } - } - } else { - logger.error(`Inbound pump config MQTT ${topics} must include circuits.units list in the message (${Utils.stringifyJSON(msg)}).`); - return; - } - } - } else { - logger.error(`Inbound pump config MQTT ${topics} has an invalid circuit: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); - return; - } - }); else { - logger.error(`Inbound pump config MQTT ${topics} has a invalid circuits: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); - return; - } - break; - default: - logger.warn(`MQTT: Inbound pump topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${Utils.stringifyJSON(msg)} `) - } + let circuits = msg.circuits; + if(typeof circuits !== 'undefined') circuits.forEach(async ckt => { + if(typeof ckt !== 'undefined') { + let circuits_id = parseInt(ckt.id,10); + let circuit = parseInt(ckt.circuit,10); + let speed = parseInt(ckt.speed,10); + if ((typeof circuits_id !== 'undefined' && isNaN(circuits_id)) || (typeof circuit !== 'undefined' && isNaN(circuit)) || (typeof speed !== 'undefined' && isNaN(speed))) { + logger.error(`Inbound pump config MQTT ${topics} contains invalid circuit parameters in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + let units = ckt.units; + if(typeof units !== 'undefined') { + let val = parseInt(ckt.units.val,10); + if(typeof val !== 'undefined' && isNaN(val)) { + logger.error(`Inbound pump config MQTT ${topics} invalid circuits.units.val (${circuits.units.val}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } else { + //validated a circuit is well formed + try { await sys.board.pumps.setPumpAsync(msg); } + catch (err) { logger.error(`Error processing MQTT topic ${topics[topics.length - 2]}: ${err.message}`); } + } + } else { + logger.error(`Inbound pump config MQTT ${topics} must include circuits.units list in the message (${Utils.stringifyJSON(msg)}).`); + return; + } + } + } else { + logger.error(`Inbound pump config MQTT ${topics} has an invalid circuit: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } + }); else { + logger.error(`Inbound pump config MQTT ${topics} has a invalid circuits: (${Utils.stringifyJSON(circuits)}) in the message (${Utils.stringifyJSON(msg)}).`); + return; + } + break; + default: + logger.warn(`MQTT: Inbound pump topic ${topics[topics.length - 1]} not matched to event ${topics[topics.length - 2].toLowerCase()}. Message ${Utils.stringifyJSON(msg)} `) + } break; } case 'heatsetpoint':