1 Star 0 Fork 0

xiaoY/edge-bacnet

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
bacnet_client.js 61.62 KB
一键复制 编辑 原始数据 按行查看 历史
bitpool-dev 提交于 2023-10-03 06:02 . v1.2.6
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499
/*
MIT License Copyright 2021, 2022 - Bitpool Pty Ltd
*/
const bacnet = require('./resources/node-bacstack-ts/dist/index.js');
const baEnum = bacnet.enum;
const bacnetIdMax = baEnum.ASN1_MAX_PROPERTY_ID;
const { EventEmitter } = require('events');
const { getUnit, roundDecimalPlaces, Store_Config, Read_Config_Sync } = require('./common');
const { ToadScheduler, SimpleIntervalJob, Task } = require('toad-scheduler');
const { BacnetDevice } = require('./bacnet_device');
const {Mutex} = require("async-mutex");
class BacnetClient extends EventEmitter {
//client constructor
constructor(config) {
super();
let that = this;
that.deviceList = [];
that.manualDiscoverQueue = [];
that.networkTree = {};
that.lastWhoIs = null;
that.client = null;
that.lastNetworkPoll = null;
that.scheduler = new ToadScheduler();
that.mutex = new Mutex();
that.manualMutex = new Mutex();
that.pollInProgress = false;
that.scanMatrix = [];
try {
let cachedData = JSON.parse(Read_Config_Sync());
if(cachedData && typeof cachedData == "object") {
if(cachedData.renderList) that.renderList = cachedData.renderList;
if(cachedData.deviceList) {
cachedData.deviceList.forEach(function(device) {
let newBacnetDevice = new BacnetDevice(true, device);
that.deviceList.push(newBacnetDevice);
});
}
if(cachedData.pointList) that.networkTree = cachedData.pointList;
}
that.config = config;
that.roundDecimal = config.roundDecimal;
that.apduSize = config.apduSize;
that.maxSegments = config.maxSegments;
that.discover_polling_schedule = config.discover_polling_schedule;
that.deviceId = config.deviceId;
that.broadCastAddr = config.broadCastAddr;
that.manual_instance_range_enabled = config.manual_instance_range_enabled;
that.manual_instance_range_start = config.manual_instance_range_start;
that.manual_instance_range_end = config.manual_instance_range_end;
that.device_read_schedule = config.device_read_schedule;
that.deviceRetryCount = parseInt(config.retries);
that.readPropertyMultipleOptions = {
maxSegments: that.maxSegments,
maxApdu: that.apduSize
};
try {
that.client = new bacnet.Client({ apduTimeout: config.apduTimeout, interface: config.localIpAdrress, port: config.port, broadcastAddress: config.broadCastAddr});
that.setMaxListeners(1);
const task = new Task('simple task', () => {
that.globalWhoIs();
});
const job = new SimpleIntervalJob({ seconds: parseInt(that.discover_polling_schedule), }, task)
that.scheduler.addSimpleIntervalJob(job);
//query device task
const queryDevices = new Task('simple task', () => {
if(!that.pollInProgress) that.queryDevices();
that.sanitizeDeviceList();
});
const queryJob = new SimpleIntervalJob({ seconds: parseInt(that.device_read_schedule), }, queryDevices)
that.scheduler.addSimpleIntervalJob(queryJob);
//buildNetworkTreeData task
const buildNetworkTree = new Task('simple task', () => {
that.buildNetworkTreeData();
});
const buildNetworkTreeJob = new SimpleIntervalJob({ seconds: 10, }, buildNetworkTree)
that.scheduler.addSimpleIntervalJob(buildNetworkTreeJob);
that.globalWhoIs();
setTimeout(() => {
that.queryDevices();
that.sanitizeDeviceList();
that.buildNetworkTreeData();
}, "5000")
} catch(e) {
that.logOut("Issue initializing client: ", e)
}
//who is callback
that.client.on('iAm', (device) => {
if(device.address !== that.config.localIpAdrress) {
if(that.scanMatrix.length > 0) {
let matrixMap = that.scanMatrix.filter(ele => device.deviceId >= ele.start && device.deviceId <= ele.end);
if(matrixMap.length > 0) {
//only add unique device to array
let foundIndex = that.deviceList.findIndex(ele => ele.getDeviceId() == device.deviceId);
if(foundIndex == -1) {
let newBacnetDevice = new BacnetDevice(false, device);
newBacnetDevice.setLastSeen(Date.now());
that.updateDeviceName(newBacnetDevice);
that.deviceList.push(newBacnetDevice);
} else if(foundIndex !== -1) {
that.deviceList[foundIndex].updateDeviceConfig(device);
that.deviceList[foundIndex].setLastSeen(Date.now());
that.updateDeviceName(that.deviceList[foundIndex]);
}
//emit event for node-red to log
that.emit('deviceFound', device);
}
} else {
//only add unique device to array
let foundIndex = that.deviceList.findIndex(ele => ele.getDeviceId() == device.deviceId);
if(foundIndex == -1) {
let newBacnetDevice = new BacnetDevice(false, device);
newBacnetDevice.setLastSeen(Date.now());
that.updateDeviceName(newBacnetDevice);
that.deviceList.push(newBacnetDevice);
} else if(foundIndex !== -1) {
that.deviceList[foundIndex].updateDeviceConfig(device);
that.deviceList[foundIndex].setLastSeen(Date.now());
that.updateDeviceName(that.deviceList[foundIndex]);
}
//emit event for node-red to log
that.emit('deviceFound', device);
}
}
});
} catch(e) {
that.logOut("Issue with creating bacnet client, see error: ", e);
}
that.client.on('error', (err) => {
that.logOut('Error occurred: ', err);
if(err.errno == -4090){
that.logOut("Invalid Client information or incorrect IP address provided");
} else if(err.errno == -49) {
that.logOut("Invalid IP address provided");
} else {
that.reinitializeClient(that.config);
}
});
}
logOut(param1, param2) {
let that = this;
that.emit('bacnetErrorLog', param1, param2);
}
rebuildDataModel() {
let that = this;
return new Promise((resolve, reject) => {
try {
that.deviceList = [];
that.renderList = [];
that.networkTree = {};
that.pollInProgress = false;
resolve(true);
} catch(e) {
that.logOut("Error clearing BACnet data model: ", e);
reject(e);
}
});
}
queryDevices() {
let that = this;
that.pollInProgress = true;
let index = 0;
query(index);
function query(index) {
that.queryPriorityDevices().then(function() {
let device = that.deviceList[index];
if(index < that.deviceList.length) {
index++;
if(typeof device == "object") {
if(!device.getManualDiscoveryMode()) {
try {
that.getDevicePointList(device).then(function() {
that.removeDeviceFromManualQueue(device);
that.buildJsonObject(device, null).then(function() {
query(index);
}).catch(function(e) {
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
query(index);
});
}).catch(function(e) {
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
that.addDeviceToManualQueue(device);
query(index);
});
} catch(e) {
that.logOut("Error while querying devices: ", e);
query(index);
}
} else {
query(index);
}
} else {
that.logOut("queryDevices: invalid device found: ", device);
query(index);
}
} else if(index == that.deviceList.length) {
if(that.manualDiscoverQueue.length > 0) {
that.queryDevicesManually();
} else {
that.pollInProgress = false;
}
}
});
}
}
queryDevicesManually() {
let that = this;
let index = 0;
query(index);
function query(index) {
that.queryPriorityDevices().then(function() {
let device = that.manualDiscoverQueue[index];
if(index < that.manualDiscoverQueue.length) {
index++;
if(typeof device == "object") {
try {
if(device.shouldBeInManualMode()) {
that.getDevicePointListWithoutObjectList(device).then(function() {
that.buildJsonObject(device, null).then(function() {
query(index);
}).catch(function(e) {
that.logOut(`getDevicePointList error: ${device.getAddress()}`, e);
query(index);
});
}).catch(function(e){
query(index);
});
} else {
that.removeDeviceFromManualQueue(device);
query(index);
}
} catch(e) {
query(index);
}
} else {
query(index);
}
} else if(index == that.manualDiscoverQueue.length) {
that.pollInProgress = false;
}
});
}
}
queryPriorityDevices() {
let that = this;
return new Promise((resolve, reject) => {
let priorityDevices = that.getPriorityDevices();
if(priorityDevices.length > 0) {
let index = 0;
query(index);
function query(index) {
let device = priorityDevices[index];
if(index < priorityDevices.length) {
index++;
if(typeof device == "object" && ((Date.now() - device.getLastPriorityQueueTS()) / 1000) > parseInt(that.device_read_schedule) ) {
try {
let points = device.getPriorityQueue();
that.buildJsonObject(device, points).then(function() {
device.setLastPriorityQueueTS();
query(index);
}).catch(function(e) {
that.logOut(`queryPriorityDevices error: ${device.getAddress()}`, e);
query(index);
});
} catch(e) {
that.logOut("Error while querying priority devices: ", e);
query(index);
}
} else {
query(index);
}
} else if(index == priorityDevices.length) {
resolve()
}
}
} else if(priorityDevices.length == 0) {
resolve()
}
});
}
addDeviceToManualQueue(device) {
let that = this;
if(device.getPointListRetryCount() > that.deviceRetryCount) {
device.setManualDiscoveryMode(true);
let index = that.manualDiscoverQueue.findIndex(ele => ele.getDeviceId() == device.getDeviceId());
if(index == -1) {
that.manualDiscoverQueue.push(device);
}
} else {
device.incrementPointListRetryCount();
}
}
removeDeviceFromManualQueue(device) {
let that = this;
device.setManualDiscoveryMode(false);
device.clearPointListRetryCount()
let index = that.manualDiscoverQueue.findIndex(ele => ele.getDeviceId() == device.getDeviceId());
if(index !== -1) {
that.manualDiscoverQueue.splice(index, 1);
}
}
sanitizeDeviceList() {
let that = this;
//Discover frequencey x 2
let timeoutThreshold = parseInt(that.discover_polling_schedule) * 2;
that.deviceList.forEach(function(device, index) {
if(((Date.now() - device.getLastSeen()) / 1000) > timeoutThreshold && device.getPriorityQueueIsActive() == false) {
//render device hasnt responded to whoIs for disover frequency x 2
let renderListIndex = that.renderList.findIndex(ele => ele.deviceId == device.getDeviceId());
let ipAddr = typeof device.getAddress() == "object" ? device.getAddress().address : device.getAddress();
let deviceKey = ipAddr + "-" + device.getDeviceId();
delete that.networkTree[deviceKey];
that.renderList.splice(renderListIndex, 1);
that.deviceList.splice(index, 1);
}
});
}
updateDeviceName(device) {
let that = this;
that._getDeviceName(device.getAddress(), device.getDeviceId()).then(function(deviceObject) {
if(typeof deviceObject.name == "string") {
device.setDeviceName(deviceObject.name);
device.setPointsList(deviceObject.devicePointEntry);
}
});
}
reinitializeClient(config) {
let that = this;
that.config = config;
that.roundDecimal = config.roundDecimal;
that.apduSize = config.apduSize;
that.maxSegments = config.maxSegments;
that.discover_polling_schedule = config.discover_polling_schedule;
that.deviceId = config.deviceId;
that.broadCastAddr = config.broadCastAddr;
that.manual_instance_range_enabled = config.manual_instance_range_enabled;
that.manual_instance_range_start = config.manual_instance_range_start;
that.manual_instance_range_end = config.manual_instance_range_end;
that.device_read_schedule = config.device_read_schedule;
if(that.scheduler !== null) {
that.scheduler.stop();
}
try {
that.client._settings.apduTimeout = config.apduTimeout;
that.client._settings.interface = config.localIpAdrress;
that.client._settings.port = config.port;
that.client._settings.broadcastAddress = config.broadCastAddr;
that.client._transport.interface = config.localIpAdrress;
that.client._transport.port = config.port;
that.client._transport.broadcastAddress = config.broadCastAddr;
const task = new Task('simple task', () => {
that.globalWhoIs();
});
const job = new SimpleIntervalJob({ seconds: parseInt(config.discover_polling_schedule), }, task)
that.scheduler.addSimpleIntervalJob(job);
// //query device task
const queryDevices = new Task('simple task', () => {
if(!that.pollInProgress) that.queryDevices();
that.sanitizeDeviceList();
});
const queryJob = new SimpleIntervalJob({ seconds: parseInt(config.device_read_schedule), }, queryDevices)
that.scheduler.addSimpleIntervalJob(queryJob);
//buildNetworkTreeData task
const buildNetworkTree = new Task('simple task', () => {
that.buildNetworkTreeData();
});
const buildNetworkTreeJob = new SimpleIntervalJob({ seconds: 10, }, buildNetworkTree)
that.scheduler.addSimpleIntervalJob(buildNetworkTreeJob);
} catch(e){
that.logOut("Error reinitializing bacnet client: ", e)
}
};
getValidPointProperties(point, requestedProps) {
let that = this;
let availableProps = point.propertyList;
let newProps = [];
try{
requestedProps.forEach(function(prop) {
let foundInAvailable = availableProps.find(ele => ele === prop.id);
if(foundInAvailable) newProps.push(prop);
});
//add object name for use in formatting
newProps.push({id: baEnum.PropertyIdentifier.OBJECT_NAME});
} catch(e){
that.logOut("Issue finding valid object properties, see error: ", e);
}
return newProps;
}
doRead(readConfig, outputType, objectPropertyType, msgId) {
let that = this;
that.roundDecimal = readConfig.precision;
let devicesToRead = Object.keys(readConfig.pointsToRead);
try {
let bacnetResults = {};
devicesToRead.forEach(function(key, index) {
let device = that.deviceList.find(ele => `${that.getDeviceAddress(ele)}-${ele.getDeviceId()}` == key);
if(device) {
let deviceName = device.getDeviceName();
let deviceKey = (typeof device.getAddress() == "object") ? device.getAddress().address + "-" + device.getDeviceId() : device.getAddress() + "-" + device.getDeviceId();
let deviceObject = that.networkTree[deviceKey];
if(!bacnetResults[deviceName]) bacnetResults[deviceName] = {};
if(deviceObject) {
for(const pointName in readConfig.pointsToRead[key]) {
let bac_obj = that.getObjectType(readConfig.pointsToRead[key][pointName].meta.objectId.type);
let objectId = pointName + "_" + bac_obj + '_' + readConfig.pointsToRead[key][pointName].meta.objectId.instance;
let point = deviceObject[objectId];
bacnetResults[deviceName][pointName] = point;
}
}
}
if(index == devicesToRead.length - 1 && Object.keys(readConfig.pointsToRead).length > 0) that.emit('values', bacnetResults, outputType, objectPropertyType);
});
} catch(e) {
that.logOut("Issue doing read, see error: ", e);
}
}
getDeviceAddress(device) {
switch(typeof device.getAddress()) {
case "object":
return device.getAddress().address;
case "string":
return device.getAddress();
default:
return device.getAddress();
}
}
_getDeviceName(address, deviceId) {
let that = this;
return new Promise((resolve, reject) => {
that._readDeviceName(address, deviceId, (err, result) => {
if(result) {
try {
if(result.values[0].value) {
const deviceObject = {
name: result.values[0].value,
devicePointEntry: [{ value: { type: 8, instance: deviceId }, type: 12 }]
};
resolve(deviceObject);
} else {
that.logOut("Issue with deviceName payload, see object: ", object);
}
} catch(e){
that.logOut("Unable to get device name: ", e);
}
}
});
});
}
getPropertiesForType(props, type) {
let that = this;
let newProps = [];
props.forEach(function(prop) {
//that.logOut(prop);
switch(type){
case 0: //analog-input
newProps.push(prop);
break;
case 1: //analog-output
newProps.push(prop);
break;
case 2: //analog-value
newProps.push(prop);
break;
case 3: //binary-input
newProps.push(prop);
break;
case 4: //binary-output
newProps.push(prop);
break;
case 5: //binary-value
newProps.push(prop);
break;
case 13:
if(prop.id == baEnum.PropertyIdentifier.PRESENT_VALUE || prop.id == baEnum.PropertyIdentifier.OBJECT_NAME) newProps.push(prop);
break;
case 14:
if(prop.id == baEnum.PropertyIdentifier.PRESENT_VALUE || prop.id == baEnum.PropertyIdentifier.OBJECT_NAME) newProps.push(prop);
break;
case 19:
if(prop.id == baEnum.PropertyIdentifier.PRESENT_VALUE || prop.id == baEnum.PropertyIdentifier.OBJECT_NAME) newProps.push(prop);
break;
}
});
return newProps;
}
getDevicePointList(device) {
let that = this;
return new Promise(async function(resolve, reject) {
try {
device.setManualDiscoveryMode(false);
let result = await that.scanDevice(device);
device.setPointsList(result);
resolve(result);
} catch(e) {
that.logOut(`Error getting point list for ${device.getAddress().toString()} - ${device.getDeviceId()}: `, e);
reject(e);
}
});
}
getDevicePointListWithoutObjectList(device) {
let that = this;
return new Promise(function(resolve, reject) {
try {
that.scanDeviceManually(device).then(function(result) {
device.setPointsList(result);
resolve(result);
}).catch(function(error) {
reject(error);
});
} catch(e) {
that.logOut("Error getting point list: ", e);
reject(e);
}
});
}
scanDeviceManually(device) {
let that = this;
return new Promise(function(resolve, reject) {
let objectNameProperty = [{ id: baEnum.PropertyIdentifier.OBJECT_NAME }];
let address = device.getAddress();
let objectTypeList = [0, 1, 2, 3, 4, 5, 13, 14, 19];
//let objectTypeList = [0, 1, 2, 3, 4, 5, 6, 10, 13, 14, 15, 16, 17, ,19, 20, 56, 178];
//let instanceRange = {start: 0, end: 100};
let instanceRange = device.getmDiscoverInstanceRange();
let requestArray = [];
let maxRequestThreshold = 1000;
let requestRate = 20;
let requestBuffer = [];
let sendBuffer = [];
if(that.manual_instance_range_enabled == true) {
instanceRange.start = that.manual_instance_range_start;
maxRequestThreshold = that.manual_instance_range_end;
if(that.manual_instance_range_end < requestRate) {
requestRate = that.manual_instance_range_end;
}
}
for(let typeListIndex = 0; typeListIndex < objectTypeList.length; typeListIndex++){
let objectType = objectTypeList[typeListIndex];
for(let i = instanceRange.start; i <= instanceRange.end; i++) {
requestArray.push({
objectId: { type: objectType, instance: i },
properties: objectNameProperty
})
if(requestArray.length == requestRate ) {
requestBuffer.push(that._readObjectWithRequestArray(address, requestArray));
instanceRange.end += requestRate;
requestArray = [];
if(i >= maxRequestThreshold) {
if(typeListIndex == objectTypeList.length-1) {
device.setmDiscoverInstanceRange({start: instanceRange.end, end: instanceRange.end + 100})
send();
}
break;
}
}
}
};
function send() {
for(let index = 0; index < requestBuffer.length; index++) {
let promise = requestBuffer[index];
try {
Promise.resolve(promise).then(function(result) {
for (const [key, value] of Object.entries(result)) {
if(key == "value" && typeof value == "object") {
for(let x = 0; x < value.values.length; x++) {
let ele = value.values[x];
let valueRoot = ele.values[0].value[0];
if(!valueRoot.value.errorClass && !valueRoot.value.errorCode) {
sendBuffer.push({"value": ele.objectId, "type": 12});
}
}
}
}
if(index == requestBuffer.length - 1) {
resolve(sendBuffer);
}
}).catch(function(error) {
reject(error);
});
} catch(e) {
reject(e)
}
}
}
});
}
_readObjectWithRequestArray(deviceAddress, requestArray) {
let that = this;
return new Promise((resolve, reject) => {
this.client.readPropertyMultiple(deviceAddress, requestArray, that.readPropertyMultipleOptions, (error, value) => {
resolve({
error: error,
value: value
});
});
});
}
_readDeviceName(deviceAddress, deviceId, callback){
let that = this;
that.client.readProperty(
deviceAddress,
{type: baEnum.ObjectType.DEVICE, instance: deviceId },
baEnum.PropertyIdentifier.OBJECT_NAME,
that.readPropertyMultipleOptions,
callback
);
}
_readObjectList(deviceAddress, deviceId, callback) {
let that = this;
try {
that.client.readProperty(
deviceAddress,
{type: baEnum.ObjectType.DEVICE, instance: deviceId },
baEnum.PropertyIdentifier.OBJECT_LIST,
that.readPropertyMultipleOptions,
callback
);
} catch(e) {
that.logOut("Error reading object list: ", e);
}
}
_readObject(deviceAddress, type, instance, properties) {
let that = this;
return new Promise((resolve, reject) => {
const requestArray = [{
objectId: { type: type, instance: instance },
properties: properties
}];
this.client.readPropertyMultiple(deviceAddress, requestArray, that.readPropertyMultipleOptions, (error, value) => {
resolve({
error: error,
value: value
});
});
});
}
_readObjectFull(deviceAddress, type, instance) {
let that = this;
const allProperties = [
{ id: baEnum.PropertyIdentifier.PRESENT_VALUE },
{ id: baEnum.PropertyIdentifier.DESCRIPTION },
{ id: baEnum.PropertyIdentifier.UNITS },
{ id: baEnum.PropertyIdentifier.OBJECT_NAME },
{ id: baEnum.PropertyIdentifier.OBJECT_TYPE },
{ id: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER },
{ id: baEnum.PropertyIdentifier.SYSTEM_STATUS },
{ id: baEnum.PropertyIdentifier.MODIFICATION_DATE },
{ id: baEnum.PropertyIdentifier.PROGRAM_STATE },
{ id: baEnum.PropertyIdentifier.RECORD_COUNT }
];
return new Promise((resolve, reject) => {
that._readObject(deviceAddress, type, instance, [{ id: baEnum.PropertyIdentifier.ALL }]).then(function(result) {
if(result.value) {
resolve(result);
}
if(result.error) {
const requestArray = [{
objectId: { type: type, instance: instance },
properties: allProperties
}];
that._readObjectWithRequestArray(deviceAddress, requestArray).then(function(manualRequest) {
if(manualRequest.value) {
resolve(manualRequest);
}
if(manualRequest.error) {
reject(manualRequest.error);
}
})
}
}).catch(function(error) {
reject(error);
});
});
};
_readObjectPropList(deviceAddress, type, instance) {
return this._readObject(deviceAddress, type, instance, [
{ id: baEnum.PropertyIdentifier.PROPERTY_LIST }
]);
};
_readObjectId(deviceAddress, type, instance) {
return this._readObject(deviceAddress, type, instance, [
{ id: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER }
]);
}
_readObjectPresentValue(deviceAddress, type, instance) {
return this._readObject(deviceAddress, type, instance, [
{ id: baEnum.PropertyIdentifier.PRESENT_VALUE },
{ id: baEnum.PropertyIdentifier.OBJECT_NAME}
]);
}
doWrite(value, options){
let that = this;
let valuesArray = [];
options.pointsToWrite.forEach(function(point){
let deviceAddress = point.deviceAddress;
if(valuesArray[deviceAddress] == null || valuesArray[deviceAddress] == undefined){
valuesArray[deviceAddress] = [];
}
let writeObject = {
objectId: {
type: point.meta.objectId.type,
instance: point.meta.objectId.instance
},
values: [{
property: {
id: 85,
index: point.meta.arrayIndex
},
value: [{
type: options.appTag,
value: value
}],
priority: options.priority
}]
};
valuesArray[deviceAddress].push(writeObject);
});
return that._writePropertyMultiple(valuesArray);
}
_writePropertyMultiple(values) {
let that = this;
let writePromises = [];
try {
return new Promise((resolve, reject) => {
for(const device in values) {
writePromises.push(that.client.writePropertyMultiple(device, values[device], that.readPropertyMultipleOptions, (err, value) => {
resolve({
error: err,
value: value
})}
));
}
Promise.all(writePromises).then(function(result) {
resolve(result);
}).catch(function(e) {
that.logOut("Error writing: ", e);
});
});
} catch (error) {
that.logOut(error);
}
}
_findValueById(properties, id) {
const property = properties.find(function (element) {
return element.id === id;
});
if (property && property.value && property.value.length > 0) {
return property.value[0].value;
} else {
return null;
}
};
scanDevice(device) {
let that = this;
return new Promise((resolve, reject) => {
this._readObjectList(device.getAddress(), device.getDeviceId(), (err, result) => {
if (!err) {
try {
resolve(result.values);
} catch(e) {
that.logOut("Issue with getting device point list, see error: ", e);
}
} else {
that.logOut(`Error while fetching objects: ${err}`);
reject(err);
}
});
});
}
//closes bacnet client
shutDownClient() {
let that = this;
if(that.client) that.client.close((err, result) => {
that.logOut(err, result);
});
};
globalWhoIs() {
let that = this;
if(that.client) {
that.client.whoIs({'net': 65535});
} else {
that.reinitializeClient(that.config);
}
that.lastWhoIs = Date.now();
}
getNetworkTreeData() {
let that = this;
return new Promise(async function(resolve, reject) {
try {
const reducedDeviceList = JSON.parse(JSON.stringify(that.deviceList));
reducedDeviceList.forEach((device) => {
delete device["pointsList"];
});
resolve({renderList: that.renderList, deviceList: reducedDeviceList, pointList: that.networkTree, pollFrequency: that.discover_polling_schedule});
} catch(e){
reject(e);
}
});
}
getDeviceList() {
let that = this;
return new Promise(async function(resolve, reject) {
try {
resolve({"deviceList": that.deviceList});
} catch(e){
reject(e);
}
});
}
updateDeviceList(json) {
let that = this;
return new Promise(async function(resolve, reject) {
try {
let deviceL = json.body.deviceList;
deviceL.forEach(function(device) {
let foundIndex = that.deviceList.findIndex(ele => ele.getDeviceId() == device.deviceId);
if(foundIndex == -1) {
let newBacnetDevice = new BacnetDevice(false, device);
newBacnetDevice.setLastSeen(Date.now());
that.deviceList.push(newBacnetDevice);
} else if(foundIndex !== -1) {
that.deviceList[foundIndex].updateDeviceConfig(device);
that.deviceList[foundIndex].setLastSeen(Date.now());
}
});
resolve(true);
} catch(e) {
reject(e);
}
});
}
updatePriorityQueue(priorityDevices) {
let that = this;
return new Promise(async function(resolve, reject) {
try {
let keys = Object.keys(priorityDevices);
if(keys.length > 0) {
keys.forEach(function(key) {
let device = that.deviceList.find(ele => `${that.getDeviceAddress(ele)}-${ele.getDeviceId()}` == key);
let points = priorityDevices[key];
if(device) {
device.setPriorityQueue(points);
}
});
} else if(keys.length == 0) {
that.clearPriorityQueues();
}
resolve(true);
} catch(e){
reject(e);
}
});
}
clearPriorityQueues() {
let that = this;
that.deviceList.forEach(function(device) {
device.clearPriorityQueue();
});
}
getPriorityDevices() {
let that = this;
let priorityDevices = that.deviceList.filter(device => device.getPriorityQueueIsActive() == true);
return priorityDevices;
}
sortDevices(a, b) {
if (a.deviceId < b.deviceId) {
return -1;
} else if (a.deviceId > b.deviceId) {
return 1;
}
return 0; // deviceIds are equal
}
sortPoints(a, b) {
if(a.bacnetType > b.bacnetType) {
return 1;
} else if(a.bacnetType < b.bacnetType) {
return -1;
} else if(a.bacnetType == b.bacnetType) {
return 0;
}
return a.label.localeCompare(b.label)
}
buildNetworkTreeData() {
let that = this;
that.buildTreeMutex = new Mutex();
let displayNameCharThreshold = 40;
Store_Config(JSON.stringify({renderList: that.renderList, deviceList: that.deviceList, pointList: that.networkTree}));
return new Promise(async function(resolve, reject) {
if(!that.renderList) that.renderList = [];
if(that.deviceList && that.networkTree) {
that.deviceList.forEach(function(deviceInfo, index) {
that.buildTreeMutex
.acquire()
.then(function(release) {
let ipAddr = typeof deviceInfo.getAddress() == "object" ? deviceInfo.getAddress().address : deviceInfo.getAddress();
let deviceId = deviceInfo.getDeviceId();
let deviceName = deviceInfo.getDeviceName() == null ? ipAddr : deviceInfo.getDeviceName();
let deviceKey = ipAddr + "-" + deviceInfo.getDeviceId();
let deviceObject = that.networkTree[deviceKey];
let isMstpDevice = deviceInfo.getIsMstpDevice();
let manualDiscoveryMode = deviceInfo.getManualDiscoveryMode();
if(deviceObject && typeof deviceName !== "object") {
let children = [];
let pointIndex = 0;
for(const pointName in deviceObject) {
let pointProperties = [];
let values = deviceObject[pointName];
let displayName = pointName;
if(pointName.length > displayNameCharThreshold) {
displayName = "";
let charArray = pointName.split("");
for(let i = 0; i < charArray.length; i++) {
if(i < displayNameCharThreshold){
displayName += charArray[i];
}
}
displayName += "...";
}
if(values.objectName){
pointProperties.push({"key": `${index}-${pointIndex}-0`, "label": `Name: ${values.objectName}`, "data": values.objectName, "icon": "pi pi-bolt", "children": null});
}
if(values.objectType){
pointProperties.push({"key": `${index}-${pointIndex}-1`, "label": `Object Type: ${values.objectType}`, "data": values.objectType, "icon": "pi pi-bolt", "children": null});
}
if(values.objectID && values.objectID.instance) {
pointProperties.push({"key": `${index}-${pointIndex}-2`, "label": `Object Instance: ${values.objectID.instance}`, "data": values.objectID.instance, "icon": "pi pi-bolt", "children": null});
}
if(values.description){
pointProperties.push({"key": `${index}-${pointIndex}-3`, "label": `Description: ${values.description}`, "data": `${values.description}`, "icon": "pi pi-bolt", "children": null});
}
if(values.units){
pointProperties.push({"key": `${index}-${pointIndex}-4`, "label": `Units: ${values.units}`, "data": `${values.units}`, "icon": "pi pi-bolt", "children": null});
}
if(values.presentValue !== "undefined" && values.presentValue !== null && typeof values.presentValue !== "undefined") {
pointProperties.push({"key": `${index}-${pointIndex}-5`, "label": `Present Value: ${values.presentValue}`, "data": `${values.presentValue}`, "icon": "pi pi-bolt", "children": null});
}
if(values.systemStatus !== null && typeof values.systemStatus !== "undefined" && values.systemStatus !== ""){
pointProperties.push({"key": `${index}-${pointIndex}-6`, "label": `System Status: ${values.systemStatus}`, "data": `${values.systemStatus}`, "icon": "pi pi-bolt", "children": null});
}
if(values.modificationDate && !values.modificationDate.errorClass) {
pointProperties.push({"key": `${index}-${pointIndex}-7`, "label": `Modification Date: ${values.modificationDate}`, "data": `${values.modificationDate}`, "icon": "pi pi-bolt", "children": null});
}
if(values.programState){
pointProperties.push({"key": `${index}-${pointIndex}-8`, "label": `Program State: ${values.programState}`, "data": `${values.programState}`, "icon": "pi pi-bolt", "children": null});
}
if(values.recordCount && !values.recordCount.errorClass){
pointProperties.push({"key": `${index}-${pointIndex}-9`, "label": `Record Count: ${values.recordCount}`, "data": `${values.recordCount}`, "icon": "pi pi-bolt", "children": null});
}
children.push({"key": `${index}-${pointIndex}`, "label": displayName, "data": displayName, "pointName": pointName, "icon": that.getPointIcon(values.meta.objectId.type), "children": pointProperties, "type": "point", "parentDevice": deviceName, "showAdded": false, "bacnetType": values.meta.objectId.type})
pointIndex++;
}
let foundIndex = that.renderList.findIndex(ele => ele.deviceId == deviceId && ele.ipAddr == ipAddr);
if(foundIndex !== -1) {
that.renderList[foundIndex] = {"key": index, "label": deviceName, "data": deviceName, "icon": that.getDeviceIcon(isMstpDevice, manualDiscoveryMode), "children": children.sort(that.sortPoints), "type": "device", "lastSeen": deviceInfo.getLastSeen(), "showAdded": false, "ipAddr": ipAddr, "deviceId": deviceId, "isMstpDevice": isMstpDevice};
} else if(foundIndex == -1) {
that.renderList.push({"key": index, "label": deviceName, "data": deviceName, "icon": that.getDeviceIcon(isMstpDevice, manualDiscoveryMode), "children": children.sort(that.sortPoints), "type": "device", "lastSeen": deviceInfo.getLastSeen(), "showAdded": false, "ipAddr": ipAddr, "deviceId": deviceId, "isMstpDevice": isMstpDevice});
}
if(index == that.deviceList.length - 1) {
that.renderList.sort(that.sortDevices);
resolve({renderList: that.renderList, deviceList: that.deviceList, pointList: that.networkTree, pollFrequency: that.discover_polling_schedule});
}
} else {
if(index == that.deviceList.length - 1) {
that.renderList.sort(that.sortDevices);
resolve({renderList: that.renderList, deviceList: that.deviceList, pointList: that.networkTree, pollFrequency: that.discover_polling_schedule});
}
}
release();
});
});
}
});
}
buildJsonObject(device, priorityQueue) {
let that = this;
let address = device.address;
let pointList = priorityQueue !== null ? priorityQueue : device.getPointsList();
let requestMutex = new Mutex();
return new Promise(function(resolve, reject) {
let promiseArray = [];
if(typeof pointList !== "undefined" && pointList.length > 0) {
pointList.forEach(function(point, pointListIndex) {
requestMutex
.acquire()
.then(function(release) {
that._readObjectFull(address, point.value.type, point.value.instance).then(function(result) {
if(!result.error) {
promiseArray.push(result);
}
release();
if(pointListIndex == pointList.length - 1) {
that.buildResponse(promiseArray, device).then(function() {
that.lastNetworkPoll = Date.now();
resolve({deviceList: that.deviceList, pointList: that.networkTree});
}).catch(function(e){
that.logOut("Error while building json object: ", e);
reject(e);
});
}
}).catch(function(e) {
release();
that.logOut("_readObjectFull error: ", e);
if(pointListIndex == pointList.length - 1) {
that.buildResponse(promiseArray, device).then(function() {
that.lastNetworkPoll = Date.now();
resolve({deviceList: that.deviceList, pointList: that.networkTree});
}).catch(function(e){
that.logOut("Error while building json object: ", e);
reject(e);
});
}
});
});
});
} else {
reject("Unable to build network tree, empty point list");
}
});
}
// Builds response object for a fully qualified
buildResponse(fullObjects, device) {
let that = this;
return new Promise(function(resolve, reject) {
let deviceKey = (typeof device.getAddress() == "object") ? device.getAddress().address + "-" + device.getDeviceId() : device.getAddress() + "-" + device.getDeviceId();
let values = that.networkTree[deviceKey] ? that.networkTree[deviceKey] : {};
for(let i = 0; i < fullObjects.length; i++) {
let obj = fullObjects[i];
let successfulResult = !obj.error ? obj.value : null;
if(successfulResult) {
successfulResult.values.forEach(function(pointProperty, pointPropertyIndex) {
let currobjectId = pointProperty.objectId.type
let bac_obj = that.getObjectType(currobjectId);
let objectName = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_NAME);
let objectType = that._findValueById(pointProperty.values, baEnum.PropertyIdentifier.OBJECT_TYPE);
let objectId;
if(objectName !== null && typeof objectName == "string") {
objectId = objectName + "_" + bac_obj + '_' + pointProperty.objectId.instance;
try {
pointProperty.values.forEach(function(object, objectIndex) {
//checks for error code json structure, returned for invalid bacnet requests
if(object && object.value && !object.value.errorClass) {
if(!values[objectId]) values[objectId] = {};
values[objectId].meta = {
objectId: pointProperty.objectId
};
switch(object.id) {
case baEnum.PropertyIdentifier.PRESENT_VALUE:
if(object.value[0] && object.value[0].value !== "undefined" && object.value[0].value !== null) {
//check for binary object type
if(objectType == 3 || objectType == 4 || objectType == 5) {
if(object.value[0].value == 0) {
values[objectId].presentValue = false;
} else if(object.value[0].value == 1) {
values[objectId].presentValue = true;
}
} else if(objectType == 40) {
//character string
values[objectId].presentValue = object.value[0].value;
} else {
values[objectId].presentValue = roundDecimalPlaces(object.value[0].value, 2);
}
}
values[objectId].meta.arrayIndex = object.index;
break;
case baEnum.PropertyIdentifier.DESCRIPTION:
if(object.value[0]) values[objectId].description = object.value[0].value;
break;
case baEnum.PropertyIdentifier.UNITS:
if(object.value[0] && object.value[0].value) values[objectId].units = getUnit(object.value[0].value);
break;
case baEnum.PropertyIdentifier.OBJECT_NAME:
if(object.value[0] && object.value[0].value) values[objectId].objectName = object.value[0].value;
break;
case baEnum.PropertyIdentifier.OBJECT_TYPE:
if(object.value[0] && object.value[0].value) values[objectId].objectType = object.value[0].value;
break;
case baEnum.PropertyIdentifier.OBJECT_IDENTIFIER:
if(object.value[0] && object.value[0].value) values[objectId].objectID = object.value[0].value;
break;
case baEnum.PropertyIdentifier.PROPERTY_LIST:
if(object.value) values[objectId].propertyList = that.mapPropsToArray(object.value);
break;
case baEnum.PropertyIdentifier.SYSTEM_STATUS:
if(object.value[0]){
values[objectId].systemStatus = that.getPROP_SYSTEM_STATUS(object.value[0].value);
}
break;
case baEnum.PropertyIdentifier.MODIFICATION_DATE:
if(object.value[0]) {
values[objectId].modificationDate = object.value[0].value;
}
break;
case baEnum.PropertyIdentifier.PROGRAM_STATE:
if(object.value[0]){
values[objectId].programState = that.getPROP_PROGRAM_STATE(object.value[0].value);
}
break;
case baEnum.PropertyIdentifier.RECORD_COUNT:
if(object.value[0] ) {
values[objectId].recordCount = object.value[0].value;
}
break;
}
}
if(pointPropertyIndex == successfulResult.values.length - 1 && objectIndex == pointProperty.values.length - 1 && i == fullObjects.length - 1) {
that.networkTree[deviceKey] = values;
resolve(that.networkTree);
}
});
} catch(e) {
that.logOut("issue resolving bacnet payload, see error: ", e);
reject(e);
}
}
});
} else {
//error found in point property
if(i == fullObjects.length - 1) {
that.networkTree[deviceKey] = values;
resolve(that.networkTree);
}
}
}
that.networkTree[deviceKey] = values;
resolve(that.networkTree);
});
}
mapPropsToArray(propertyList) {
let uniquePropArray = [];
for(let i = 0; i < propertyList.length; i++) {
if(uniquePropArray.indexOf(propertyList[i].value) === -1) uniquePropArray.push(propertyList[i].value);
}
return uniquePropArray;
}
getPROP_PROGRAM_STATE(value) {
switch(value) {
case 0:
return "0 - Idle";
case 1:
return "1 - Loading";
case 2:
return "2 - Running";
case 3:
return "3 - Waiting";
case 4:
return "4 - Halted";
case 5:
return "5 - Unloading";
default:
return "";
}
}
getPROP_SYSTEM_STATUS(value) {
switch(value) {
case 0:
return "0 - Operational";
case 1:
return "1 - Operational Readonly";
case 2:
return "2 - Download Required";
case 3:
return "3 - Download In Progress";
case 4:
return "4 - Non Operational";
case 5:
return "5 - Backup In Progress";
default:
return "";
}
}
getPointIcon(objectId) {
switch(objectId) {
case 0:
//AI
return "pi pi-tags";
case 1:
//AO
return "pi pi-tags";
case 2:
//AV
return "pi pi-tags";
case 3:
//BI
return "pi pi-tags";
case 4:
//BO
return "pi pi-tags";
case 5:
//BV
return "pi pi-tags";
case 8:
//Device
return "pi pi-box";
case 13:
//MI
return "pi pi-tags";
case 14:
//MO
return "pi pi-tags";
case 19:
//MV
return "pi pi-tags";
case 10:
//File
return "pi pi-file";
case 16:
//Program
return "pi pi-database";
case 20:
//Trendlog
return "pi pi-chart-line";
case 15:
//Notification Class
return "pi pi-bell";
case 56:
return "pi pi-sitemap";
case 178:
return "pi pi-lock";
case 17:
return "pi pi-calendar";
case 6:
return "pi pi-calendar";
default:
//Return circle for all other types
return "pi pi-tags";
}
}
getObjectType(objectId) {
switch(objectId) {
case 0:
return "AI";
case 1:
return "AO";
case 2:
return "AV";
case 3:
return "BI";
case 4:
return "BO";
case 5:
return "BV";
case 8:
return "Device";
case 13:
return "MI";
case 14:
return "MO";
case 19:
return "MV";
case 40:
return "CS";
default:
return "";
}
}
getPROP_RELIABILITY(value) {
switch(value) {
case 0:
return "No Fault Detected";
case 1:
return "No Sensor";
case 2:
return "Over Range";
case 3:
return "Under Range";
case 4:
return "Open Loop";
case 5:
return "Shorted Loop";
case 6:
return "No Output";
case 7:
return "Unreliable Other";
case 8:
return "Process Error";
case 9:
return "Multi State Fault";
case 10:
return "Configuration Error";
case 11:
return "Member Fault";
case 12:
return "Communication Failure";
case 13:
return "Tripped";
default:
return "";
}
}
getStatusFlags(flags) {
return flags.value[0].value;
}
getDeviceIcon(isMstp, manualDiscoveryMode) {
if(manualDiscoveryMode == true) {
//return "pi pi-server"
return "pi pi-exclamation-triangle"
} else if(manualDiscoveryMode == false) {
if(isMstp == true) {
return "pi pi-share-alt"
} else if(isMstp == false) {
return "pi pi-server"
}
}
return "pi pi-server";
};
}
module.exports = { BacnetClient };
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/yqg.tom/edge-bacnet.git
[email protected]:yqg.tom/edge-bacnet.git
yqg.tom
edge-bacnet
edge-bacnet
master

搜索帮助