Skip to content

Commit

Permalink
Fix Issue #292 - Added signature for VS pump
Browse files Browse the repository at this point in the history
Added message protocol for UltraTemp and cooling setponts.
  • Loading branch information
rstrouse committed May 14, 2021
1 parent 31a039f commit aae8c1f
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 1 deletion.
10 changes: 10 additions & 0 deletions controller/Equipment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,10 @@ export class Body extends EqItem {
public set setPoint(val: number) { this.setDataVal('setPoint', val); }
public get heatMode(): number { return this.data.heatMode; }
public set heatMode(val: number) { this.setDataVal('heatMode', val); }
public get heatSetpoint(): number { return this.data.setPoint; }
public set heatSetpoint(val: number) { this.setDataVal('setPoint', val); }
public get coolSetpoint(): number { return this.data.coolSetpoint; }
public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
public getHeatModes() { return sys.board.bodies.getHeatModes(this.id); }
//public async setHeatModeAsync(mode: number) { return sys.board.bodies.setHeatModeAsync(this, mode); }
//public setHeatSetpoint(setPoint: number) { sys.board.bodies.setHeatSetpointAsync(this, setPoint); }
Expand Down Expand Up @@ -1423,6 +1427,12 @@ export class Valve extends EqItem {
export class HeaterCollection extends EqItemCollection<Heater> {
constructor(data: any, name?: string) { super(data, name || "heaters"); }
public createItem(data: any): Heater { return new Heater(data); }
public getItemByAddress(address: number, add?: boolean, data?: any): Heater {
let itm = this.find(elem => elem.address === address && typeof elem.address !== 'undefined');
if (typeof itm !== 'undefined') return itm;
if (typeof add !== 'undefined' && add) return this.add(data || { id: this.data.length + 1, address: address });
return this.createItem(data || { id: this.data.length + 1, address: address });
}
}
export class Heater extends EqItem {
public dataName = 'heaterConfig';
Expand Down
4 changes: 4 additions & 0 deletions controller/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,10 @@ export class BodyTempState extends EqState {
}
public get setPoint(): number { return this.data.setPoint; }
public set setPoint(val: number) { this.setDataVal('setPoint', val); }
public get heatSetpoint(): number { return this.data.setPoint; }
public set heatSetpoint(val: number) { this.setDataVal('setPoint', val); }
public get coolSetpoint(): number { return this.data.coolSetpoint; }
public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
public get isOn(): boolean { return this.data.isOn; }
public set isOn(val: boolean) { this.setDataVal('isOn', val); }
public emitData(name: string, data: any) { webApp.emitToClients('body', this.data); }
Expand Down
50 changes: 50 additions & 0 deletions controller/boards/EasyTouchBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,56 @@ class TouchBodyCommands extends BodyCommands {
conn.queueSendMessage(out);
});
}
public async setSetpoints(body: Body, obj: any): Promise<BodyTempState> {
return new Promise<BodyTempState>((resolve, reject) => {
let setPoint = typeof obj.setPoint !== 'undefined' ? parseInt(obj.setPoint, 10) : parseInt(obj.heatSetpoint, 10);
if (isNaN(setPoint)) return Promise.reject(new InvalidEquipmentDataError(`Invalid ${body.name} setpoint ${obj.setPoint || obj.heatSetpoint}`, 'body', obj));
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
// 165,33,16,34,136,4,89,99,7,0,2,71 Request
// 165,33,34,16,1,1,136,1,130 Controller Response
const tempUnits = state.temps.units;
switch (tempUnits) {
case 0: // fahrenheit
if (setPoint < 40 || setPoint > 104) {
logger.warn(`Setpoint of ${setPoint} is outside acceptable range.`);
return;
}
break;
case 1: // celsius
if (setPoint < 4 || setPoint > 40) {
logger.warn(
`Setpoint of ${setPoint} is outside of acceptable range.`
);
return;
}
break;
}
const body1 = sys.bodies.getItemById(1);
const body2 = sys.bodies.getItemById(2);
let temp1 = body1.setPoint || 100;
let temp2 = body2.setPoint || 100;
body.id === 1 ? temp1 = setPoint : temp2 = setPoint;
const mode1 = body1.heatMode;
const mode2 = body2.heatMode;
const out = Outbound.create({
dest: 16,
action: 136,
payload: [temp1, temp2, mode2 << 2 | mode1, 0],
retries: 3,
response: true,
onComplete: (err, msg) => {
if (err) reject(err);
body.setPoint = setPoint;
let bstate = state.temps.bodies.getItemById(body.id);
bstate.setPoint = setPoint;
state.temps.emitEquipmentChange();
resolve(bstate);
}

});
conn.queueSendMessage(out);
});
}
public async setHeatSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
return new Promise<BodyTempState>((resolve, reject) => {
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
Expand Down
54 changes: 54 additions & 0 deletions controller/boards/IntelliCenterBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2985,6 +2985,58 @@ class IntelliCenterBodyCommands extends BodyCommands {
let body3 = sys.bodies.getItemById(3);
let body4 = sys.bodies.getItemById(4);

let temp1 = sys.bodies.getItemById(1).setPoint || 100;
let temp2 = sys.bodies.getItemById(2).setPoint || 100;
let temp3 = sys.bodies.getItemById(3).setPoint || 100;
let temp4 = sys.bodies.getItemById(4).setPoint || 100;
switch (body.id) {
case 1:
byte2 = 18;
temp1 = setPoint;
break;
case 2:
byte2 = 20;
temp2 = setPoint;
break;
case 3:
byte2 = 19;
temp3 = setPoint;
break;
case 4:
byte2 = 21;
temp4 = setPoint;
break;
}
// 6 15 17 18 21 22 24 25
//[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0, 89, 100, 98, 100, 0, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][5, 243]
//[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 235, 27, 167, 1, 0, 0, 89, 81, 98, 103, 5, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][6, 48]
let out = Outbound.create({
action: 168,
response: IntelliCenterBoard.getAckResponse(168),
retries: 5,
payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0,
temp1, temp3, temp2, temp4, body1.heatMode || 0, body2.heatMode || 0, body3.heatMode || 0, body4.heatMode || 0, 15,
sys.general.options.pumpDelay ? 1 : 0, sys.general.options.cooldownDelay ? 1 : 0, 0, 100, 0, 0, 0, 0, sys.general.options.manualPriority ? 1 : 0, sys.general.options.manualHeat ? 1 : 0]
});
return new Promise<BodyTempState>((resolve, reject) => {
out.onComplete = (err, msg) => {
if (err) reject(err);
else {
let bstate = state.temps.bodies.getItemById(body.id);
body.setPoint = bstate.setPoint = setPoint;
resolve(bstate);
}
};
conn.queueSendMessage(out);
});
}
public async setCoolSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
let byte2 = 18;
let body1 = sys.bodies.getItemById(1);
let body2 = sys.bodies.getItemById(2);
let body3 = sys.bodies.getItemById(3);
let body4 = sys.bodies.getItemById(4);

let temp1 = sys.bodies.getItemById(1).setPoint || 100;
let temp2 = sys.bodies.getItemById(2).setPoint || 100;
let temp3 = sys.bodies.getItemById(3).setPoint || 100;
Expand Down Expand Up @@ -3368,6 +3420,8 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
let solarInstalled = htypes.solar > 0;
let heatPumpInstalled = htypes.heatpump > 0;
let gasHeaterInstalled = htypes.gas > 0;
let ultratempInstalled = htypes.ultratemp > 0;

// RKS: 09-26-20 This is a hack to maintain backward compatability with fw versions 1.04 and below.
if (parseFloat(sys.equipment.controllerFirmware) > 1.04) {
sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
Expand Down
13 changes: 13 additions & 0 deletions controller/boards/SystemBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,15 @@ export class BodyCommands extends BoardCommands {
sys.board.heaters.syncHeaterStates();
return Promise.resolve(bstate);
}
public async setCoolSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
let bdy = sys.bodies.getItemById(body.id);
let bstate = state.temps.bodies.getItemById(body.id);
bdy.coolSetpoint = bstate.setPoint = setPoint;
state.emitEquipmentChanges();
sys.board.heaters.syncHeaterStates();
return Promise.resolve(bstate);
}

public getHeatModes(bodyId: number) {
let heatModes = [];
// RKS: 09-26-20 This will need to be overloaded in IntelliCenterBoard when the other heater types are identified. (e.g. ultratemp, hybrid, maxetherm, and mastertemp)
Expand Down Expand Up @@ -2660,6 +2669,7 @@ export class HeaterCommands extends BoardCommands {
if (inst[type.name] === 'undefined') inst[type.name] = 0;
inst[type.name] = inst[type.name] + 1;
inst.total++;
if (type.hasCoolSetpoint) inst['hasCoolSetpoint'] = true;
}
}
return inst;
Expand Down Expand Up @@ -2942,6 +2952,9 @@ export class HeaterCommands extends BoardCommands {
}
//else if (heater.coolingEnabled && state.time.isNight)
}
break;
case 'ultratemp':

break;
case 'gas':
if (mode === 'heater') {
Expand Down
10 changes: 10 additions & 0 deletions controller/comms/messages/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { PumpMessage } from "./config/PumpMessage";
import { VersionMessage } from "./status/VersionMessage";
import { PumpStateMessage } from "./status/PumpStateMessage";
import { EquipmentStateMessage } from "./status/EquipmentStateMessage";
import { HeaterStateMessage } from "./status/HeaterStateMessage";
import { ChlorinatorStateMessage } from "./status/ChlorinatorStateMessage";
import { ChlorinatorMessage } from "./config/ChlorinatorMessage";
import { ExternalMessage } from "./config/ExternalMessage";
Expand Down Expand Up @@ -51,6 +52,7 @@ export enum Protocol {
Chlorinator = 'chlorinator',
IntelliChem = 'intellichem',
IntelliValve = 'intellivalve',
Heater = 'heater',
Unidentified = 'unidentified'
}
export class Message {
Expand Down Expand Up @@ -351,6 +353,7 @@ export class Inbound extends Message {
case Protocol.IntelliChem:
case Protocol.IntelliValve:
case Protocol.Broadcast:
case Protocol.Heater:
case Protocol.Unidentified:
ndx = this.pushBytes(this.preamble, bytes, ndx, 3);
ndx = this.pushBytes(this.header, bytes, ndx, 6);
Expand All @@ -365,6 +368,8 @@ export class Inbound extends Message {

if (this.source >= 96 && this.source <= 111) this.protocol = Protocol.Pump;
else if (this.dest >= 96 && this.dest <= 111) this.protocol = Protocol.Pump;
else if (this.source >= 112 && this.source <= 127) this.protocol = Protocol.Heater;
else if (this.dest >= 112 && this.dest <= 127) this.protocol = Protocol.Heater;
else if (this.dest >= 144 && this.dest <= 158) this.protocol = Protocol.IntelliChem;
else if (this.source >= 144 && this.source <= 158) this.protocol = Protocol.IntelliChem;
else if (this.source == 12 || this.dest == 12) this.protocol = Protocol.IntelliValve;
Expand Down Expand Up @@ -423,6 +428,7 @@ export class Inbound extends Message {
case Protocol.Pump:
case Protocol.IntelliChem:
case Protocol.IntelliValve:
case Protocol.Heater:
case Protocol.Unidentified:
if (this.datalen - this.payload.length <= 0) return ndx; // We don't need any more payload.
ndx = this.pushBytes(this.payload, bytes, ndx, this.datalen - this.payload.length);
Expand Down Expand Up @@ -450,6 +456,7 @@ export class Inbound extends Message {
case Protocol.Pump:
case Protocol.IntelliValve:
case Protocol.IntelliChem:
case Protocol.Heater:
case Protocol.Unidentified:
// If we don't have enough bytes to make the terminator then continue on and
// hope we get them on the next go around.
Expand Down Expand Up @@ -752,6 +759,9 @@ export class Inbound extends Message {
else
this.processBroadcast();
break;
case Protocol.Heater:
HeaterStateMessage.process(this);
break;
case Protocol.Chlorinator:
ChlorinatorStateMessage.process(this);
break;
Expand Down
1 change: 1 addition & 0 deletions controller/comms/messages/config/PumpMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class PumpMessage {
pump.isActive = true;
PumpMessage.processVSF_IT(msg);
break;
case 255: // vs 3050 on old panels.
case 128: // vs
case 134: // vs Ultra Efficiency
pump.type = 128;
Expand Down
43 changes: 43 additions & 0 deletions controller/comms/messages/status/HeaterStateMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* nodejs-poolController. An application to control pool equipment.
Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. [email protected]
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <http://www.gnu.org/licenses/>.
*/
import { Inbound, Protocol } from "../Messages";
import { state, BodyTempState, HeaterState } from "../../../State";
import { sys, ControllerType, Heater } from "../../../Equipment";

export class HeaterStateMessage {
public static process(msg: Inbound) {
if (msg.protocol === Protocol.Heater) {
switch (msg.action) {
case 114: // This is a message from a master controlling the heater
break;
case 115:
HeaterStateMessage.processHeaterStatus(msg);
break;
}
}
}
public static processHeaterStatus(msg: Inbound) {
let heater: Heater;
heater = sys.heaters.getItemByAddress(msg.source);

// We need to decode the message. For a 2 of
//[165, 1, 15, 16, 2, 29][16, 42, 3, 0, 0, 0, 0, 0, 0, 32, 0, 0, 2, 0, 88, 88, 0, 241, 95, 100, 24, 246, 0, 0, 0, 0, 0, 40, 0][4, 221]
//[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
//[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
msg.isProcessed = true;
}
}
9 changes: 9 additions & 0 deletions defaultConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@
"excludeSource": [],
"excludeDest": []
},
"heater": {
"enabled": true,
"includeActions": [],
"excludeActions": [],
"includeSource": [],
"includeDest": [],
"excludeSource": [],
"excludeDest": []
},
"unidentified": {
"enabled": true,
"includeSource": [],
Expand Down
9 changes: 8 additions & 1 deletion web/services/state/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,17 @@ export class StateRoute {
});
app.put('/state/body/setPoint', async (req, res, next) => {
// RKS: 06-24-20 -- Changed this so that users can send in the body id, circuit id, or the name.
// RKS: 05-14-21 -- Added cooling setpoints for the body.
try {
let body = sys.bodies.findByObject(req.body);
if (typeof body === 'undefined') return next(new ServiceParameterError(`Cannot set body setPoint. You must supply a valid id, circuit, name, or type for the body`, 'body', 'id', req.body.id));
let tbody = await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.setPoint, 10));
if (typeof req.body.coolSetpoint !== 'undefined' && !isNaN(parseInt(req.body.coolSetpoint, 10)))
await sys.board.bodies.setCoolSetpointAsync(body, parseInt(req.body.coolSetpoint, 10));
if (typeof req.body.heatSetpoint !== 'undefined' && !isNaN(parseInt(req.body.heatSetpoint, 10)))
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.heatSetpoint, 10));
else if (typeof req.body.setPoint !== 'undefined' && !isNaN(parseInt(req.body.setPoint, 10)))
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.heatSetpoint, 10));
let tbody = state.temps.bodies.getItemById(body.id);
return res.status(200).send(tbody.get(true));
} catch (err) { next(err); }
});
Expand Down

0 comments on commit aae8c1f

Please sign in to comment.