Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc fixes #134

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/asn1.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,10 @@ const bacappEncodeApplicationData = module.exports.bacappEncodeApplicationData =
case baEnum.ApplicationTags.READ_ACCESS_SPECIFICATION:
encodeReadAccessSpecification(buffer, value.value);
break;
case undefined:
throw new Error('Cannot encode a value if the type has not been specified');
default:
throw 'Unknown type';
throw 'Unknown ApplicationTags type: ' + baEnum.getEnumName(baEnum.ApplicationTags, value.type);
}
};

Expand Down
39 changes: 38 additions & 1 deletion lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const baNpdu = require('./npdu');
const baBvlc = require('./bvlc');
const baEnum = require('./enum');

const DEFAULT_HOP_COUNT = 0xFF;
const BVLC_HEADER_LENGTH = 4;

/**
Expand Down Expand Up @@ -1190,5 +1189,43 @@ class Client extends EventEmitter {
close() {
this._transport.close();
}

/**
* Helper function to take an array of enums and produce a bitstring suitable
* for inclusion as a property.
*
* @example
* [bacnet.enum.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED]: [
* {value: bacnet.createBitstring([
* bacnet.enum.ObjectTypesSupported.ANALOG_INPUT,
* bacnet.enum.ObjectTypesSupported.ANALOG_OUTPUT,
* ]),
* type: bacnet.enum.ApplicationTags.BIT_STRING},
* ],
*/
static createBitstring(items) {
let offset = 0;
let bytes = [];
let bitsUsed = 0;
while (items.length) {
// Find any values between offset and offset+8, for the next byte
let value = 0;
items = items.filter(i => {
if (i >= offset + 8) return true; // leave for future iteration
value |= 1 << (i - offset);
bitsUsed = Math.max(bitsUsed, i);
return false; // remove from list
});
bytes.push(value);
offset += 8;
}
bitsUsed++;

return {
value: bytes,
bitsUsed: bitsUsed,
};
}

}
module.exports = Client;
24 changes: 24 additions & 0 deletions lib/enum.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
'use strict';

/**
* Turn an enum into a string suitable for debugging.
*
* @param object group
* Enum group, e.g. bacnet.enum.ConfirmedServiceChoice.
*
* @param Number value
* Enum value, e.g. 1. Note that this *must* be an integer value, so you may
* need to use parseInt(). Non-integer values will result in an exception.
*
* @example
* const s = bacnet.enum.getEnumName(
* bacnet.enum.PropertyIdentifier,
* bacnet.enum.PropertyIdentifier.PRESENT_VALUE
* );
* console.log(s); // "PRESENT_VALUE(85)"
*/
module.exports.getEnumName = function(group, value) {
if (!Number.isInteger(value)) {
throw new Error('getEnumName() can only be passed an integer value, was given "' + value + '"');
}
return Object.keys(group).find(key => group[key] === value) + '(' + value + ')';
}

module.exports.PDU_TYPE_MASK = 0xF0;
module.exports.ASN1_MAX_OBJECT = 0x3FF;
module.exports.ASN1_INSTANCE_BITS = 22;
Expand Down
3 changes: 2 additions & 1 deletion lib/npdu.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const baEnum = require('./enum');

const DEFAULT_HOP_COUNT = 0xFF;
const BACNET_PROTOCOL_VERSION = 1;
const BacnetAddressTypes = {
NONE: 0,
Expand Down Expand Up @@ -103,7 +104,7 @@ module.exports.encode = (buffer, funct, destination, source, hopCount, networkMs
}

if (hasDestination) {
buffer.buffer[buffer.offset++] = hopCount;
buffer.buffer[buffer.offset++] = hopCount || DEFAULT_HOP_COUNT;
}

if ((funct & baEnum.NpduControlBits.NETWORK_LAYER_MESSAGE) > 0) {
Expand Down
7 changes: 5 additions & 2 deletions lib/transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
const createSocket = require('dgram').createSocket;
const EventEmitter = require('events').EventEmitter;

const DefaultBACnetPort = 47808;

class Transport extends EventEmitter {
constructor(settings) {
super();
this._settings = settings;
this._server = createSocket({type: 'udp4', reuseAddr: true});
this._server.on('message', (msg, rinfo) => this.emit('message', msg, rinfo.address));
this._server.on('message', (msg, rinfo) => this.emit('message', msg, rinfo.address + (rinfo.port === DefaultBACnetPort ? '' : ':' + rinfo.port)));
this._server.on('error', (err) => this.emit('message', err));
}

Expand All @@ -21,7 +23,8 @@ class Transport extends EventEmitter {
}

send(buffer, offset, receiver) {
this._server.send(buffer, 0, offset, this._settings.port, receiver);
const [address, port] = receiver.split(':');
this._server.send(buffer, 0, offset, port || DefaultBACnetPort, address);
}

open() {
Expand Down
38 changes: 38 additions & 0 deletions test/unit/client.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');
const baEnum = require('../../lib/enum');
const client = require('../../lib/client');

describe('bacstack - client', () => {
it('should successfuly encode a bitstring > 32 bits', () => {
const result = client.createBitstring([
baEnum.ServicesSupported.CONFIRMED_COV_NOTIFICATION,
baEnum.ServicesSupported.READ_PROPERTY,
baEnum.ServicesSupported.WHO_IS,
]);
expect(result).to.deep.equal({
value: [2, 16, 0, 0, 4],
bitsUsed: 35,
});
});
it('should successfuly encode a bitstring < 8 bits', () => {
const result = client.createBitstring([
baEnum.ServicesSupported.GET_ALARM_SUMMARY,
]);
expect(result).to.deep.equal({
value: [8],
bitsUsed: 4,
});
});
it('should successfuly encode a bitstring of only one bit', () => {
const result = client.createBitstring([
baEnum.ServicesSupported.ACKNOWLEDGE_ALARM,
]);
expect(result).to.deep.equal({
value: [1],
bitsUsed: 1,
});
});
});