diff --git a/controller/boards/IntelliCenterBoard.ts b/controller/boards/IntelliCenterBoard.ts index 452cb380..3979efc1 100644 --- a/controller/boards/IntelliCenterBoard.ts +++ b/controller/boards/IntelliCenterBoard.ts @@ -263,13 +263,13 @@ export class IntelliCenterBoard extends SystemBoard { } } public async stopAsync() { this._configQueue.close(); return super.stopAsync();} - public initExpansionModules(ocp0A: number, ocp0B: number, ocp1A: number, ocp1B: number, ocp2A: number, ocp2B: number, ocp3A: number, ocp3B: number) { + public initExpansionModules(ocp0A: number, ocp0B: number, xcp1A: number, xcp1B: number, xcp2A: number, xcp2B: number, xcp3A: number, xcp3B: number) { let inv = { bodies: 0, circuits: 0, valves: 0, shared: false, dual: false, covers: 0, chlorinators: 0, chemControllers: 0 }; this.processMasterModules(sys.equipment.modules, ocp0A, ocp0B, inv); // Here we need to set the start id should we have a single body system. if (!inv.shared && !inv.dual) { sys.board.equipmentIds.circuits.start = 2; } // We are a single body system. - this.processExpansionModules(sys.equipment.expansions.getItemById(1, true), ocp1A, ocp1B, inv); - this.processExpansionModules(sys.equipment.expansions.getItemById(2, true), ocp2A, ocp2B, inv); + this.processExpansionModules(sys.equipment.expansions.getItemById(1, true), xcp1A, xcp1B, inv); + this.processExpansionModules(sys.equipment.expansions.getItemById(2, true), xcp2A, xcp2B, inv); // We are still unsure how the 3rd power center is encoded. For now we are simply un-installing it. this.processExpansionModules(sys.equipment.expansions.getItemById(3, true), 0, 0, inv); if (inv.bodies !== sys.equipment.maxBodies || @@ -279,8 +279,8 @@ export class IntelliCenterBoard extends SystemBoard { inv.valves !== sys.equipment.maxValves) { sys.resetData(); this.processMasterModules(sys.equipment.modules, ocp0A, ocp0B); - this.processExpansionModules(sys.equipment.expansions.getItemById(1, true), ocp1A, ocp1B); - this.processExpansionModules(sys.equipment.expansions.getItemById(2, true), ocp2A, ocp2B); + this.processExpansionModules(sys.equipment.expansions.getItemById(1, true), xcp1A, xcp1B); + this.processExpansionModules(sys.equipment.expansions.getItemById(2, true), xcp2A, xcp2B); this.processExpansionModules(sys.equipment.expansions.getItemById(3, true), 0, 0); } sys.equipment.maxBodies = inv.bodies; diff --git a/controller/comms/messages/status/EquipmentStateMessage.ts b/controller/comms/messages/status/EquipmentStateMessage.ts index 0332a3ff..7b38d842 100644 --- a/controller/comms/messages/status/EquipmentStateMessage.ts +++ b/controller/comms/messages/status/EquipmentStateMessage.ts @@ -349,10 +349,11 @@ export class EquipmentStateMessage { // Master = 13-14 // EXP1 = 15-16 // EXP2 = 17-18 + let pc = msg.extractPayloadByte(40); board.initExpansionModules(msg.extractPayloadByte(13), msg.extractPayloadByte(14), - msg.extractPayloadByte(15), msg.extractPayloadByte(16), - msg.extractPayloadByte(17), msg.extractPayloadByte(18), - msg.extractPayloadByte(19), msg.extractPayloadByte(20)); + pc & 0x01 ? msg.extractPayloadByte(15) : 0x00, pc & 0x01 ? msg.extractPayloadByte(16) : 0x00, + pc & 0x02 ? msg.extractPayloadByte(17) : 0x00, pc & 0x02 ? msg.extractPayloadByte(18) : 0x00, + pc & 0x04 ? msg.extractPayloadByte(19) : 0x00, pc & 0x04 ? msg.extractPayloadByte(20) : 0x00); sys.equipment.setEquipmentIds(); } else return; diff --git a/defaultConfig.json b/defaultConfig.json index 2c6f2c41..c94ca021 100755 --- a/defaultConfig.json +++ b/defaultConfig.json @@ -122,9 +122,29 @@ "password": "", "rootTopic": "@bind=(state.equipment.model).replace(' ','-').replace('/','').toLowerCase();", "retain": true, - "qos": 0 + "qos": 0, + "changesOnly": true } }, + "mqttAlt": { + "name": "MQTTAlt", + "type": "mqtt", + "enabled": false, + "fileName": "mqttAlt.json", + "globals": {}, + "options": { + "protocol": "mqtt://", + "host": "192.168.0.1", + "port": 1883, + "username": "", + "password": "", + "rootTopic": "@bind=(state.equipment.model).replace(' ','-').replace('/','').toLowerCase();Alt", + "retain": true, + "qos": 0, + "changesOnly": true + } + }, + "rem": { "name": "Relay Equipment Manager", "type": "rem", diff --git a/web/bindings/mqtt.json b/web/bindings/mqtt.json index 3fb72801..2434769c 100644 --- a/web/bindings/mqtt.json +++ b/web/bindings/mqtt.json @@ -101,7 +101,7 @@ "description": "Remove / and replace with __" } ], - "qos": 2, + "options": { "qos": 2 }, "enabled": false }, { @@ -314,52 +314,103 @@ "description": "Populate the chemControllers topic", "topics": [ { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/acidTankLevel", - "message": "{\"acidTankLevel\":@bind=data.acidTankLevel;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/ph/tankLevel", + "message": "@bind=data.ph.tank.level;", + "enabled": true }, { - "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/alkalinity", - "message": "{\"alkalinity\":@bind=data.alkalinity;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/ph/setpoint", + "message": "@bind=data.ph.setpoint;", + "enabled": true }, { - "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/calciumHardness", - "message": "{\"calciumHardness\":@bind=data.calciumHardness;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/ph/level", + "message": "@bind=data.ph.level;", + "enabled": true }, { - "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/cyanuricAcid", - "message": "{\"cyanuricAcid\":@bind=data.cyanuricAcid;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/ph/doseTime", + "message": "@bind=data.ph.doseTime;", + "enabled": true }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpDosingTime", - "message": "{\"orpDosingTime\":@bind=data.orpDosingTime;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/ph/doseVolume", + "message": "@bind=data.ph.doseVolume;", + "enabled": true }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpLevel", - "message": "{\"orpLevel\":@bind=data.orpLevel;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/tankLevel", + "message": "@bind=data.orp.tank.level;", + "enabled": true }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpSetpoint", - "message": "{\"orpSetpoint\":@bind=data.orpSetpoint;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/setpoint", + "message": "@bind=data.orp.setpoint;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/level", + "message": "@bind=data.orp.level;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/doseTime", + "message": "@bind=data.orp.doseTime;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/doseVolume", + "message": "@bind=data.orp.doseVolume;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orp/saltLevel", + "message": "@bind=data.orp.saltLevel;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/acidTankLevel", + "message": "{\"acidTankLevel\":@bind=data.ph.tank.level;}" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpTankLevel", - "message": "{\"orpTankLevel\":@bind=data.orpTankLevel;}" + "message": "{\"orpTankLevel\":@bind=data.orp.tank.level;}" + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpDosingTime", + "message": "{\"orpDosingTime\":@bind=data.orp.doseTime;}" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/pHDosingTime", - "message": "{\"pHDosingTime\":@bind=data.pHDosingTime;}" + "message": "{\"pHDosingTime\":@bind=data.ph.doseTime;}" }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/pHLevel", - "message": "{\"pHLevel\":@bind=data.pHLevel;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/pHDosingVolume", + "message": "{\"orpDosingVolume\":@bind=data.ph.doseVolume;}" + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpDosingVolume", + "message": "{\"pHDosingVolume\":@bind=data.orp.doseVolume;}" + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpSetpoint", + "message": "{\"orpSetpoint\":@bind=data.orp.setpoint;}" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/pHSetpoint", - "message": "{\"pHSetpoint\":@bind=data.pHSetpoint;}" + "message": "{\"pHSetpoint\":@bind=data.ph.setpoint;}" + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/orpLevel", + "message": "{\"orpLevel\":@bind=data.orp.level;}" + }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/pHLevel", + "message": "{\"pHLevel\":@bind=data.ph.level;}" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/saltLevel", - "message": "{\"saltLevel\":@bind=data.saltLevel;}" + "message": "{\"saltLevel\":@bind=data.orp.probe.saltLevel;}" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/saturationIndex", @@ -367,63 +418,82 @@ }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/status", - "message": "{\"status\":@bind=data.status;}" + "message": "@bind=data.status;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/type", - "message": "{\"type\":@bind=data.type;}" + "message": "@bind=data.type;" + }, + + { + "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/alkalinity", + "message": "{\"alkalinity\":@bind=data.alkalinity;}" + }, + { + "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/calciumHardness", + "message": "@bind=data.calciumHardness;" + }, + { + "topic": "config/chemControllers/@bind=data.id;/@bind=data.name;/cyanuricAcid", + "message": "@bind=data.cyanuricAcid;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/virtualControllerStatus", - "message": "{\"virtualControllerStatus\":@bind=data.virtualControllerStatus;}" + "message": "@bind=data.virtualControllerStatus;" }, + { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/flow", - "message": "{\"flow\":@bind=data.flow;}" + "message": "@bind=data.alarms.flow;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/ph", - "message": "{\"ph\":@bind=data.ph;}" + "message": "@bind=data.alarms.pH;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/orp", - "message": "{\"orp\":@bind=data.orp;}" + "message": "@bind=data.alarms.orp;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/phTank", - "message": "{\"phTank\":@bind=data.phTank;}" + "message": "@bind=data.alarms.pHTank;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/orpTank", - "message": "{\"orpTank\":@bind=data.orpTank;}" + "message": "@bind=data.alarms.orpTank;" }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/probeFault", - "message": "{\"probeFault\":@bind=data.probeFault;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/orpProbeFault", + "message": "@bind=data.alarms.orpProbeFault;" }, + { + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/alarms/pHProbeFault", + "message": "@bind=data.alarms.pHProbeFault;" + }, + { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/waterChemistry", - "message": "{\"waterChemistry\":@bind=data.waterChemistry;}" + "message": "@bind=data.warnings.waterChemistry;" }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/phLockout", - "message": "{\"phLockout\":@bind=data.phLockout;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/pHLockout", + "message": "@bind=data.warnings.pHLockout;" }, { - "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/phDailLimitReached", - "message": "{\"phDailLimitReached\":@bind=data.phDailLimitReached;}" + "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/pHDailyLimitReached", + "message": "@bind=data.warnings.pHDailyLimitReached;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/orpDailyLimitReached", - "message": "{\"orpDailyLimitReached\":@bind=data.orpDailyLimitReached;}" + "message": "@bind=data.warnings.orpDailyLimitReached;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/invalidSetup", - "message": "{\"invalidSetup\":@bind=data.invalidSetup;}" + "message": "@bind=data.warnings.invalidSetup;" }, { "topic": "state/chemControllers/@bind=data.id;/@bind=data.name;/warnings/chlorinatorCommError", - "message": "{\"chlorinatorCommError\":@bind=data.chlorinatorCommError;}" + "message": "@bind=data.warnings.chlorinatorCommError;" } ] }, diff --git a/web/bindings/mqttAlt.json b/web/bindings/mqttAlt.json new file mode 100644 index 00000000..1924b63d --- /dev/null +++ b/web/bindings/mqttAlt.json @@ -0,0 +1,553 @@ +{ + "context": { + "name": "MQTT", + "options": { + "formatter": [ + { + "transform": ".toLowerCase()" + }, + { + "regexkey": "\\s", + "replace": "", + "description": "Remove whitespace" + }, + { + "regexkey": "\\/", + "replace": "", + "description": "Remove /" + }, + { + "regexkey": "\\+", + "replace": "", + "description": "Remove +" + }, + { + "regexkey": "\\$", + "replace": "", + "description": "Remove $" + }, + { + "regexkey": "\\#", + "replace": "", + "description": "Remove #" + } + ], + "rootTopic-DIRECTIONS": "You can override the root topic by renaming _rootTopic to rootTopic", + "_rootTopic": "@bind=(state.equipment.alias).replace(' ','-').replace('/','').toLowerCase();", + "clientId": "@bind='mqttjs_njsPC_'+Math.random().toString(16).substr(2, 8);" + } + }, + "events": [ + { + "name": "config", + "description": "Don't flood the MQTT bus.", + "enabled": false + }, + { + "name": "controller", + "description": "Emit time every minute", + "enabled": true, + "topics": [ + { + "topic": "state/time", + "message": "@bind=data.time;" + }, + { + "topic": "alias", + "message": "@bind=data.alias;" + } + ] + }, + { + "name": "circuit", + "description": "Populate the circuits topics", + "topics": [ + { + "topic": "state/circuits/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind the name." + }, + { + "topic": "state/circuits/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind the on/off status to the topic." + }, + { + "topic": "state/circuits/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "The type of circuit we are dealing with." + }, + { + "topic": "state/circuits/@bind=data.id;/lightingTheme", + "message": "@bind=data.lightingTheme;", + "description": "Bind the lighting theme to the topic.", + "filter": "@bind=data.type.isLight === true;" + }, + { + "topic": "state/circuits/@bind=data.id;/showInFeatures", + "message": "@bind=data.showInFeatures;}", + "description": "Indicates wether the item should show in features." + } + ] + }, + { + "name": "feature", + "description": "Populate the features topics", + "topics": [ + { + "topic": "state/features/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind the name." + }, + { + "topic": "state/features/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind the on/off status to the topic." + }, + { + "topic": "state/features/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "The type of feature we are dealing with." + }, + { + "topic": "state/features/@bind=data.id;/showInFeatures", + "message": "@bind=data.showInFeatures;}", + "description": "Indicates wether the item should show in features." + } + ] + }, + { + "name": "lightGroup", + "description": "Populate the lightGroup topic", + "topics": [ + { + "topic": "state/lightGroups/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind name to the state topic." + }, + { + "topic": "state/lightGroups/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind the on/off status to the topic." + }, + { + "topic": "state/lightGroups/@bind=data.id;/action", + "message": "@bind=data.action;" + }, + { + "topic": "state/lightGroups/@bind=data.id;/lightingTheme", + "message": "@bind=data.lightingTheme;" + }, + { + "topic": "state/lightGroups/@bind=data.id;/type", + "message": "@bind=data.type;" + } + ] + }, + { + "name": "circuitGroup", + "description": "Populate the circuitGroup topic", + "topics": [ + { + "topic": "state/circuitGroups/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind name to the state topic." + }, + { + "topic": "state/circuitGroups/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind the on/off status to the topic." + }, + { + "topic": "state/circuitGroups/@bind=data.id;/action", + "message": "@bind=data.action;" + }, + { + "topic": "state/circuitGroups/@bind=data.id;/type", + "message": "@bind=data.type;" + } + ] + }, + { + "name": "virtualCircuit", + "description": "Populate the virtual circuits topics", + "topics": [ + { + "topic": "state/virtualCircuits/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind the name." + }, + { + "topic": "state/virtualCircuits/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind the on/off status to the topic." + }, + { + "topic": "state/virtualCircuits/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "The type of circuit we are dealing with." + } + ] + }, + { + "name": "valve", + "description": "Populate the valve topics", + "topics": [ + { + "topic": "state/valve/@bind=data.id;", + "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isDiverted?'\"on\"':'\"off\"';,\"isVirtual\":@bind=data.isVirtual? true: false;,\"pinId\": @bind=data.pinId;}", + "description": "Bind 'on'/'off' as a message to the valve state topic.", + "enabled": false + }, + { + "topic": "state/valve/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "The name of the valve" + }, + { + "topic": "state/valve/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "The type of valve we are dealing with." + }, + { + "topic": "state/valve/@bind=data.id;/isDiverted", + "message": "@bind=data.isDiverted;", + "description": "Indicates whether the valve is diverted" + }, + { + "topic": "state/valve/@bind=data.id;/isVirtual", + "message": "@bind=data.isVirtual;", + "description": "Indicates whether the valve is virtual" + }, + { + "topic": "state/valve/@bind=data.id;/pinId", + "message": "@bind=data.pinId;", + "description": "The pin id that is set on the valve", + "filter": "@bind=data.isVirtual === true;" + } + + ] + }, + { + "name": "temps", + "description": "Populate the temps topics", + "topics": [ + { + "topic": "state/temps/air", + "message": "@bind=data.air;", + "description": "Air temp." + }, + { + "topic": "state/temps/solar", + "message": "{\"temp\":@bind=data.solar;}", + "description": "Solar temp", + "filter": "@bind=typeof data.solar !== 'undefined';" + }, + { + "topic": "state/temps/units", + "message": "@bind=data.units;" + } + ] + }, + { + "name": "body", + "description": "Populate the body topic", + "filter": "@bind=data.isActive;", + "topics": [ + { + "topic": "state/temps/bodies/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind 'on'/'off' as a message to the state topic." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "Bind 'on'/'off' as a message to the state topic." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/isOn", + "message": "@bind=data.isOn;", + "description": "Bind 'on'/'off' as a message to the state topic." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/circuit", + "message": "@bind=data.circuit;", + "description": "Bind the associated circuit." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/heatMode", + "message": "@bind=data.heatMode;", + "description": "Heat mode." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/heatStatus", + "message": "@bind=data.heatStatus;", + "description": "Heat status." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/setPoint", + "message": "@bind=data.setPoint;", + "description": "Setpoint." + }, + { + "topic": "state/temps/bodies/@bind=data.id;/temp", + "message": "@bind=data.temp;", + "description": "Body Temp." + } + ] + }, + { + "name": "chlorinator", + "description": "Populate the chlorinator topic", + "topics": [ + { + "topic": "state/chlorinators/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind the name topic." + }, + { + "topic": "state/chlorinators/@bind=data.id;/isOn", + "message": "@bind=data.currentOutput > 0;", + "description": "Bind 'on'/'off' as a message to the state topic." + }, + { + "topic": "state/chlorinators/@bind=data.id;/currentOutput", + "message": "@bind=data.currentOutput;", + "description": "Send current output." + }, + { + "topic": "state/chlorinators/@bind=data.id;/poolSetpoint", + "message": "@bind=data.poolSetpoint;", + "description": "Send pool setpoint." + }, + { + "topic": "state/chlorinators/@bind=data.id;/spaSetpoint", + "message": "@bind=data.spaSetpoint;", + "description": "Send set point." + }, + { + "topic": "state/chlorinators/@bind=data.id;/status", + "message": "@bind=data.status;", + "description": "Send status." + }, + { + "topic": "state/chlorinators/@bind=data.id;/superChlor", + "message": "@bind=data.superChlor;", + "description": "Send superChlor." + }, + { + "topic": "state/chlorinators/@bind=data.id;/superChlorHours", + "message": "@bind=data.superChlorHours;", + "description": "Send superChlorHours." + }, + { + "topic": "state/chlorinators/@bind=data.id;/saltLevel", + "message": "@bind=data.saltLevel;", + "description": "Send salt level." + }, + { + "topic": "state/chlorinators/@bind=data.id;/type", + "message": "@bind=data.type;", + "description": "Send type." + }, + { + "topic": "state/chlorinators/@bind=data.id;/targetOutput", + "message": "@bind=data.targetOutput;", + "description": "Send targetOutput." + }, + { + "topic": "state/chlorinators/@bind=data.id;/virtualControllerStatus", + "message": "@bind=data.virtualControllerStatus;", + "description": "Send virtualControllerStatus." + } + ] + }, + { + "name": "pump", + "description": "Populate the pumps topic", + "topics": [ + { + "topic": "state/pumps/@bind=data.id;/name", + "message": "@bind=data.name;", + "description": "Bind name to the state topic." + }, + { + "topic": "state/pumps/@bind=data.id;/isOn", + "message": "@bind=data.rpm + data.flow > 0;" + }, + { + "topic": "state/pumps/@bind=data.id;/rpm", + "message": "@bind=data.rpm;" + }, + { + "topic": "state/pumps/@bind=data.id;/flow", + "message": "@bind=data.flow;" + }, + { + "topic": "state/pumps/@bind=data.id;/watts", + "message": "@bind=data.watts;" + }, + { + "topic": "state/pumps/@bind=data.id;/status", + "message": "@bind=data.status;" + } + ] + }, + { + "name": "chemController", + "description": "Populate the chemControllers topic", + "topics": [ + { + "topic": "state/chemControllers/@bind=data.id;/name", + "message": "@bind=data.name;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/ph/tankLevel", + "message": "@bind=data.ph.tank.level;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/ph/setpoint", + "message": "@bind=data.ph.setpoint;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/ph/level", + "message": "@bind=data.ph.level;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/ph/doseTime", + "message": "@bind=data.ph.doseTime;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/ph/doseVolume", + "message": "@bind=data.ph.doseVolume;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/tankLevel", + "message": "@bind=data.orp.tank.level;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/setpoint", + "message": "@bind=data.orp.setpoint;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/level", + "message": "@bind=data.orp.level;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/doseTime", + "message": "@bind=data.orp.doseTime;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/doseVolume", + "message": "@bind=data.orp.doseVolume;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/orp/saltLevel", + "message": "@bind=data.orp.saltLevel;", + "enabled": true + }, + { + "topic": "state/chemControllers/@bind=data.id;/saturationIndex", + "message": "@bind=data.saturationIndex;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/status", + "message": "@bind=data.status;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/type", + "message": "@bind=data.type;" + }, + { + "topic": "config/chemControllers/@bind=data.id;/alkalinity", + "message": "@bind=sys.chemControllers.getItemById(data.id).alkalinity;" + }, + { + "topic": "config/chemControllers/@bind=data.id;/calciumHardness", + "message": "@bind=sys.chemControllers.getItemById(data.id).calciumHardness;" + }, + { + "topic": "config/chemControllers/@bind=data.id;/cyanuricAcid", + "message": "@bind=sys.chemControllers.getItemById(data.id).cyanuricAcid;" + }, + { + "topic": "config/chemControllers/@bind=data.id;/borates", + "message": "@bind=sys.chemControllers.getItemById(data.id).borates;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/virtualControllerStatus", + "message": "@bind=data.virtualControllerStatus;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/flow", + "message": "@bind=data.alarms.flow;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/ph", + "message": "@bind=data.alarms.pH;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/orp", + "message": "@bind=data.alarms.orp;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/phTank", + "message": "@bind=data.alarms.pHTank;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/orpTank", + "message": "@bind=data.alarms.orpTank;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/orpProbeFault", + "message": "@bind=data.alarms.orpProbeFault;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/alarms/pHProbeFault", + "message": "@bind=data.alarms.pHProbeFault;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/waterChemistry", + "message": "@bind=data.warnings.waterChemistry;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/pHLockout", + "message": "@bind=data.warnings.pHLockout;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/pHDailyLimitReached", + "message": "@bind=data.warnings.pHDailyLimitReached;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/orpDailyLimitReached", + "message": "@bind=data.warnings.orpDailyLimitReached;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/invalidSetup", + "message": "@bind=data.warnings.invalidSetup;" + }, + { + "topic": "state/chemControllers/@bind=data.id;/warnings/chlorinatorCommError", + "message": "@bind=data.warnings.chlorinatorCommError;" + } + ] + }, + { + "name": "*", + "description": "DEFAULT: Sends the entire emitted response.", + "body": "@bind=data;", + "enabled": false + } + ] +} \ No newline at end of file diff --git a/web/interfaces/mqttInterface.ts b/web/interfaces/mqttInterface.ts index 1bcd6e43..da617ed6 100644 --- a/web/interfaces/mqttInterface.ts +++ b/web/interfaces/mqttInterface.ts @@ -14,7 +14,7 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import { connect, MqttClient, Client } from 'mqtt'; +import { connect, MqttClient, Client, IClientPublishOptions } from 'mqtt'; import * as http2 from "http2"; import * as http from "http"; import * as https from "https"; @@ -28,6 +28,7 @@ import { state as stateAlias } from "../../controller/State"; import { webApp as webAppAlias } from '../Server'; import { utils } from "../../controller/Constants"; import { ServiceParameterError } from '../../controller/Errors'; +import { publicDecrypt } from 'crypto'; export class MqttInterfaceBindings extends BaseInterfaceBindings { constructor(cfg) { @@ -199,16 +200,35 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings { this.buildTokens(t.message, evt, topicToks, e, data[0]); message = this.tokensReplacer(t.message, evt, topicToks, e, data[0]); - let retain = baseOpts.retain; - if (typeof t.retain !== 'undefined') retain = t.retain; - let qos = baseOpts.qos; - if (typeof t.qos !== 'undefined') qos = t.qos; - let publishOptions = { - retain, - qos + let publishOptions: IClientPublishOptions = { retain: typeof baseOpts.retain !== 'undefined' ? baseOpts.retain : true, qos: typeof baseOpts.qos !== 'undefined' ? baseOpts.qos : 2 }; + let changesOnly = typeof baseOpts.changesOnly !== 'undefined' ? baseOpts.changesOnly : true; + if (typeof e.options !== 'undefined') { + if (typeof e.options.retain !== 'undefined') publishOptions.retain = e.options.retain; + if (typeof e.options.qos !== 'undefined') publishOptions.retain = e.options.qos; + if (typeof e.options.changesOnly !== 'undefined') changesOnly = e.options.changesOnly; } - logger.silly(`MQTT send:\ntopic: ${topic}\nmessage: ${message}\nopts:${JSON.stringify(publishOptions)}`) - this.client.publish(topic, message, { retain: true, qos: 2 }); + if (typeof t.options !== 'undefined') { + if (typeof t.options.retain !== 'undefined') publishOptions.retain = t.options.retain; + if (typeof t.options.qos !== 'undefined') publishOptions.qos = t.options.qos; + if (typeof t.options.changeOnly !== 'undefined') changesOnly = t.options.changesOnly; + } + if (changesOnly) { + if (typeof t.lastSent === 'undefined') t.lastSent = []; + let lm = t.lastSent.find(elem => elem.topic === topic); + if (typeof lm === 'undefined' || lm.message !== message) { + this.client.publish(topic, message, publishOptions); + logger.silly(`MQTT send:\ntopic: ${topic}\nmessage: ${message}\nopts:${JSON.stringify(publishOptions)}`); + } + if (typeof lm === 'undefined') t.lastSent.push({ topic: topic, message: message }); + else lm.message = message; + + } + else { + logger.silly(`MQTT send:\ntopic: ${topic}\nmessage: ${message}\nopts:${JSON.stringify(publishOptions)}`); + this.client.publish(topic, message, publishOptions); + if (typeof t.lastSent !== 'undefined') t.lastSent = undefined; + } + }) } } @@ -394,4 +414,10 @@ export interface IMQTT { retain: boolean; enabled?: boolean; filter?: string; + lastSent: MQTTMessage[]; + options: any; +} +class MQTTMessage { + topic: string; + message: string; } \ No newline at end of file