Skip to content

Commit

Permalink
Added the ability to set the temperature units for aqualinkD. To set …
Browse files Browse the repository at this point in the history
…the units change the config.json file to reflec the units expected on the MQTT server. #413
  • Loading branch information
rstrouse committed Jun 6, 2022
1 parent a4190ba commit 778fe92
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 58 deletions.
4 changes: 4 additions & 0 deletions defaultConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
"retain": true,
"qos": 0,
"changesOnly": true
},
"vars": {
"tempPrecision": 2,
"tempUnits": "@bind=sys.board.valueMaps.tempUnits.getName(state.temps.units);"
}

},
Expand Down
50 changes: 16 additions & 34 deletions web/bindings/aqualinkD.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,16 @@
"transform": ".toLowerCase()"
},
{
"regexkey": "\\s",
"replace": "",
"description": "Remove whitespace"
},
{
"regexkey": "\\/",
"replace": "",
"description": "Remove /"
},
{
"regexkey": "\\+",
"replace": "",
"description": "Remove +"
},
{
"regexkey": "\\$",
"replace": "",
"description": "Remove $"
},
{
"regexkey": "\\#",
"replace": "",
"description": "Remove #"
"regexkey": "[\\s+\\/\\+\\$\\#]",
"replace": "-",
"description": "Remove [whitespace,/,+,$,#]"
}
],
"rootTopic-DIRECTIONS": "You can override the root topic by renaming _rootTopic to rootTopic",
"_rootTopic": "@bind=(state.equipment.alias).replace(' ','-').replace('/','').toLowerCase();",
"clientId": "@bind='aqualinkd_njsPC_'+ Math.random().toString(16).substr(2, 8);"
"clientId": "@bind=`aqualinkd_njsPC_${webApp.mac().replace(/:/g, '_'}-${webApp.httpPort()}`;"
},
"vars": {
}
},
"subscriptions": [
Expand Down Expand Up @@ -136,7 +118,7 @@
"topic": "Temperature/Pool",
"description": "The current temperature emitted by the controller for the pool. However, we only want to set this when the pool circuit is on.",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"state.temps.bodies.getItemById(1).temp = temp;",
"if(state.circuits.getItemById(6).isOn) sys.board.system.setTempsAsync({ waterSensor1: temp });"
]
Expand All @@ -145,7 +127,7 @@
"topic": "Temperature/Spa",
"description": "The current temperature emitted by the controller for the spa. However, we only want to set this when the spa circuit is on.",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"state.temps.bodies.getItemById(2).temp = temp;",
"if(state.circuits.getItemById(1).isOn) sys.board.system.setTempsAsync({ waterSensor1: temp });"
]
Expand All @@ -154,15 +136,15 @@
"topic": "Temperature/Air",
"description": "The current temperature emitted by the controller for air temperature.",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"sys.board.system.setTempsAsync({ air: temp });"
]
},
{
"topic": "Temperature/Solar",
"description": "The current temperature emitted by the controller for solar.",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"sys.board.system.setTempsAsync({ solarSensor1: temp });"
]
},
Expand Down Expand Up @@ -320,15 +302,15 @@
"topic": "Pool_Heater/setpoint",
"description": "The setpoint for the pool body",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let body = sys.bodies.getItemById(1); sys.board.bodies.setHeatSetpointAsync(body, parseInt(temp, 10));"
]
},
{
"topic": "Spa_Heater/setpoint",
"description": "The setpoint for the spa body",
"processor": [
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let temp = ctx.util.convert.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"let body = sys.bodies.getItemById(2); sys.board.bodies.setHeatSetpointAsync(body, parseInt(temp, 10));"
]
},
Expand All @@ -352,8 +334,8 @@
"topic": "Freeze_Protect/setpoint",
"description": "The current freeze protection setpoint",
"processor": [
"let temp = ctx.util.temperature.convertUnits(parseFloat(value), 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"sys.general.options.freezeThreshold = parseInt(temp, 10);"
"let temp = ctx.util.temperature.convertUnits(parseFloat(value), ctx.vars.tempUnits, sys.board.valueMaps.tempUnits.getName(state.temps.units));",
"sys.general.options.freezeThreshold = parseInt(Math.round(temp), 10);"
]
},
{
Expand Down Expand Up @@ -457,7 +439,7 @@
"filter": "@bind=data.id; === 1;",
"processor": [
"let units = sys.board.valueMaps.tempUnits.getName(state.temps.units);",
"return ctx.util.convert.temperature.convertUnits(data.setPoint, units, 'F');"
"return ctx.util.roundNumber(ctx.util.convert.temperature.convertUnits(data.setPoint, units, ctx.vars.tempUnits), ctx.vars.tempPrecision);"
]
},
{
Expand All @@ -467,7 +449,7 @@
"filter": "@bind=data.id; === 2;",
"processor": [
"let units = sys.board.valueMaps.tempUnits.getName(state.temps.units);",
"return ctx.util.convert.temperature.convertUnits(data.setPoint, units, 'F');"
"return ctx.util.roundNumber(ctx.util.convert.temperature.convertUnits(data.setPoint, units, ctx.vars.tempUnits), ctx.vars.tempPrecision);"
]
},
{
Expand Down
17 changes: 12 additions & 5 deletions web/interfaces/baseInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class BaseInterfaceBindings {
public cfg;
public events: InterfaceEvent[];
public bindEvent(evt: string, ...data: any) { };
protected bindVarTokens(e: InterfaceEvent, evt: string, ...data: any) {
public bindVarTokens(e: IInterfaceEvent, evt: string, ...data: any) {
let v = {};
let toks = {};
let vars = extend(true, {}, this.cfg.vars, this.context.vars, typeof e !== 'undefined' && e.vars ? e.vars : {});
Expand All @@ -76,7 +76,7 @@ export class BaseInterfaceBindings {
//console.log(v);
return v;
}
protected matchTokens(input: string, eventName: string, toks: any, e: InterfaceEvent, data, vars): any {
protected matchTokens(input: string, eventName: string, toks: any, e: IInterfaceEvent, data, vars): any {
toks = toks || [];
let s = input;
let regx = /(?<=@bind\=\s*).*?(?=\;)/g;
Expand All @@ -102,7 +102,7 @@ export class BaseInterfaceBindings {
return toks;

}
protected buildTokens(input: string, eventName: string, toks: any, e: InterfaceEvent, data): any {
protected buildTokens(input: string, eventName: string, toks: any, e: IInterfaceEvent, data): any {
toks = toks || [];
let s = input;
let regx = /(?<=@bind\=\s*).*?(?=\;)/g;
Expand Down Expand Up @@ -163,8 +163,15 @@ export class BaseInterfaceBindings {
}
public async stopAsync() { }
}

export class InterfaceEvent {
export interface IInterfaceEvent {
enabled: boolean;
filter?: string;
options?: any;
body?: any;
vars?: any;
processor?: string[]
}
export class InterfaceEvent implements IInterfaceEvent {
public name: string;
public enabled: boolean = true;
public filter: string;
Expand Down
46 changes: 27 additions & 19 deletions web/interfaces/mqttInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import extend = require("extend");
import { logger } from "../../logger/Logger";
import { PoolSystem, sys } from "../../controller/Equipment";
import { State, state } from "../../controller/State";
import { InterfaceEvent, BaseInterfaceBindings, InterfaceContext } from "./baseInterface";
import { InterfaceEvent, BaseInterfaceBindings, InterfaceContext, IInterfaceEvent } from "./baseInterface";
import { sys as sysAlias } from "../../controller/Equipment";
import { state as stateAlias } from "../../controller/State";
import { webApp as webAppAlias } from '../Server';
Expand Down Expand Up @@ -264,6 +264,19 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
if (t.useRootTopic !== false) topic = `${rootTopic}/${topic}`;
// Filter out any topics where there may be undefined in it. We don't want any of this if that is the case.
if (topic.endsWith('/undefined') || topic.indexOf('/undefined/') !== -1 || topic.startsWith('null/') || topic.indexOf('/null') !== -1) return;
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;
}
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 (typeof t.processor !== 'undefined') {
if (t.ignoreProcessor) message = "err";
else {
Expand All @@ -275,7 +288,8 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
} catch (err) { logger.error(`Error compiling subscription processor: ${err} -- ${fnBody}`); t.ignoreProcessor = true; }
}
if (typeof t._fnProcessor === 'function') {
let ctx = { util: utils, rootTopic: rootTopic, topic: topic }
let vars = this.bindVarTokens(e, evt, data);
let ctx = { util: utils, rootTopic: rootTopic, topic: topic, opts: opts, vars: vars }
try {
message = t._fnProcessor(ctx, t, sys, state, data[0]).toString();
topic = ctx.topic;
Expand All @@ -288,18 +302,6 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
message = this.tokensReplacer(t.message, evt, topicToks, e, data[0]);
}

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;
}
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);
Expand Down Expand Up @@ -338,7 +340,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
if (typeof sub !== 'undefined') {
logger.debug(`Topic not found ${topic}`)
// Alright so now lets process our results.
if (typeof sub.processor === 'function') {
if (typeof sub.fnProcessor === 'function') {
sub.executeProcessor(this, msg);
return;
}
Expand Down Expand Up @@ -536,26 +538,32 @@ class MQTTMessage {
class MqttSubscriptions {
public subscriptions: IMQTTSubscription[]
}
class MqttTopicSubscription {
class MqttTopicSubscription implements IInterfaceEvent {
root: string;
topic: string;
enabled: boolean;
processor: (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
fnProcessor: (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
options: any = {};
constructor(root: string, sub: any) {
this.root = sub.root || root;
this.topic = sub.topic;
if (typeof sub.processor !== 'undefined') {
let fnBody = Array.isArray(sub.processor) ? sub.processor.join('\n') : sub.processor;
try {
this.processor = new Function('ctx', 'sub', 'sys', 'state', 'value', fnBody) as (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
this.fnProcessor = new Function('ctx', 'sub', 'sys', 'state', 'value', fnBody) as (ctx: any, sub: MqttTopicSubscription, sys: PoolSystem, state: State, value: any) => void;
} catch (err) { logger.error(`Error compiling subscription processor: ${err} -- ${fnBody}`); }
}
}
public get topicPath(): string { return `${this.root}/${this.topic}` };
public executeProcessor(bindings: MqttInterfaceBindings, value: any) {
let baseOpts = extend(true, { headers: {} }, bindings.cfg.options, bindings.context.options);
let opts = extend(true, baseOpts, this.options);
let vars = bindings.bindVarTokens(this, this.topic, value);

let ctx = {
util: utils,
client: bindings.client,
vars: vars || {},
publish: (topic: string, message: any, options?: any) => {
try {
let msg: string;
Expand Down Expand Up @@ -588,7 +596,7 @@ class MqttTopicSubscription {
}
};

this.processor(ctx, this, sys, state, value);
this.fnProcessor(ctx, this, sys, state, value);
state.emitEquipmentChanges();
}
}
Expand Down

0 comments on commit 778fe92

Please sign in to comment.