"use strict";
const _ = require("underscore");
const assert = require("node-opcua-assert").assert;
const opcua = require("node-opcua");
const StatusCodes = opcua.StatusCodes;
const DataType = opcua.DataType;
const standardUnits = opcua.standardUnits;
const doDebug = false;
/**
* @method createHVACSystem
*
* @startuml
*
* class HVACModuleType {
* }
* HVACModuleType -up-> ObjectType
* HVACModuleType o-down-> ExteriorTemperatureSensor << (P,#F0F0FF)TemperatureSensorType >>
* HVACModuleType o-down-> InteriorTemperatureSensor << (P,#F0F0FF)TemperatureSensorType >>
* HVACModuleType o-down-> TargetTemperature << (P,#F0F0FF)TemperatureSensorType >>
* HVACModuleType o-down-> HVACEnabledEventType << (E,#00F0FF)BaseEventType >>
* HVACModuleType o-down-> HVACDisabledEventType << (E,#00F0FF)BaseEventType >>
* HVACModuleType o-left-> SetTargetTemperature<< (M,#ABFFF0) >>
* HVACModuleType o-left---> Enable << (M,#ABFFF0) >>
* HVACModuleType o-left---> Disable << (M,#ABFFF0) >>
* AnalogItemType -up-> DataItemType
* DataItemType -up-> BaseDataVariableType
* @enduml
*
* compact version
* @startuml
*
* class HVACModuleType << (C,F0F0F0)ObjectType >> {
* ExteriorTemperatureSensor: AnalogItemType
* InteriorTemperatureSensor: AnalogItemType
* TargetTemperature : Variable
* --------
* HVACEnabledEventType
* HVACDisabledEventType
* --------
* SetTargetTemperature
* Enable
* Disable
* }
* @enduml
*
* @param addressSpace
* @return {*}
*/
exports.createHVACSystem = function (addressSpace) {
const namespace = addressSpace.getOwnNamespace();
const HVACEnabledEventType = namespace.addEventType({
browseName: "HVACEnabledEventType"
});
const HVACDisabledEventType = namespace.addEventType({
browseName: "HVACDisabledEventType"
});
const HVACModuleType = namespace.addObjectType({
browseName: "HVACModuleType"
});
namespace.addAnalogDataItem({
componentOf: HVACModuleType,
browseName: "ExteriorTemperature",
accessLevel: "CurrentRead",
valuePrecision: 0.01,
instrumentRange: {low: -70, high: 120},
engineeringUnitsRange: {low: -100, high: 200},
engineeringUnits: standardUnits.degree_celsius, // � Celsius
description: "External temperature Sensor",
minimumSamplingInterval: 500,
dataType: "Double",
modellingRule: "Mandatory"
});
namespace.addAnalogDataItem({
componentOf: HVACModuleType,
browseName: "InteriorTemperature",
accessLevel: "CurrentRead",
valuePrecision: 0.01,
instrumentRange: {low: -70, high: 120},
engineeringUnitsRange: {low: -100, high: 200},
engineeringUnits: standardUnits.degree_celsius,
description: "External temperature Sensor",
minimumSamplingInterval: 500,
dataType: "Double",
modellingRule: "Mandatory"
});
// EURange (10,+27)
namespace.addAnalogDataItem({
modellingRule: "Mandatory",
componentOf: HVACModuleType,
browseName: "TargetTemperature",
minimumSamplingInterval: 0, // could be event Based
dataType: "Double",
instrumentRange: {low: -70, high: 120},
engineeringUnitsRange: {low: -100, high: 200}
});
namespace.addMethod(HVACModuleType, {
modellingRule: "Mandatory",
browseName: "Enable",
description: "Enable the hvac system",
alwaysGeneratesEvent: HVACEnabledEventType,
inputArguments: [],
outputArguments: []
});
namespace.addMethod(HVACModuleType, {
modellingRule: "Mandatory",
browseName: "Disable",
description: "Disable the hvac system",
alwaysGeneratesEvent: HVACDisabledEventType,
inputArguments: [],
outputArguments: []
});
namespace.addMethod(HVACModuleType, {
modellingRule: "Mandatory",
browseName: "SetTargetTemperature",
inputArguments: [
{
name: "targetTemperature",
description: {text: "specifies the target temperature"},
dataType: DataType.Double
}
],
outputArguments: []
});
namespace.addTwoStateDiscrete({
modellingRule: "Mandatory",
componentOf: HVACModuleType,
browseName: "MainSwitch",
trueState: "Up/ON",
falseState: "Down/OFF",
value: false
});
const myHVAC = HVACModuleType.instantiate({
browseName: "MyHVAC1"
});
// initialize interiorTemperature :
myHVAC.interiorTemperature.setValueFromSource({dataType: DataType.Double, value: 16});
myHVAC.targetTemperature.setValueFromSource({dataType: DataType.Double, value: 16});
// bind the method
myHVAC.enable.bindMethod(function (inputArguments, context, callback) {
const myResult = {
statusCode: StatusCodes.Good
};
callback(null, myResult);
});
function updateInteriorTemperature() {
const currentTemp = myHVAC.interiorTemperature.readValue().value.value;
const targetTemp = myHVAC.targetTemperature.readValue().value.value;
const newInteriorTemp = currentTemp + (targetTemp - currentTemp) / 100.0;
myHVAC.interiorTemperature.setValueFromSource({dataType: DataType.Float, value: newInteriorTemp});
//xx console.log("Tick = target temp = ",targetTemp," current =",currentTemp," new= ",newInteriorTemp);
}
const timerId = setInterval(updateInteriorTemperature, 60);
myHVAC.on("dispose", function () {
clearInterval(timerId);
});
//xx console.log(" => ",myHVAC.setTargetTemperature.inputArguments.readValue().toString());
// bind the method
myHVAC.setTargetTemperature.bindMethod(function (inputArguments, context, callback) {
if (doDebug) {
console.log(" In SetTargetTemperature".cyan.bold);
console.log("inputArguments", inputArguments[0].toString());
}
const targetTemperature = inputArguments[0];
assert(targetTemperature instanceof opcua.Variant);
const variable = myHVAC.targetTemperature;
if (doDebug) {
console.log("instrumentRange=", myHVAC.targetTemperature.instrumentRange.readValue().value.toString());
console.log("instrumentRange=", HVACModuleType.targetTemperature.instrumentRange.readValue().value.toString());
}
const s = variable.isValueInRange(targetTemperature);
if (s.isNot(StatusCodes.Good)) {
console.log(" Invalid Value specified for targetTemperature".red.bold);
return callback(null, {statusCode: s});
}
variable.setValueFromSource(targetTemperature, StatusCodes.Good);
callback();
});
return myHVAC.nodeId.toString();
};