Skip to content

Commit

Permalink
added simple addressing option
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeL99 committed Dec 28, 2018
1 parent cd31a53 commit cbf4ad7
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 19 deletions.
38 changes: 36 additions & 2 deletions src/modbus-command-factory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
import { ModbusCommand } from './modbus-commands'

export interface ModbusCommandFactory {
export class ModbusCommandFactoryOptions {

fromPacket(packet: Buffer): ModbusCommand<any>
/**
* If this option is set, the server will use 0 based coil, input status, and register addresses.
* For instance, register 40001 will be 0, 40002 will be 1, etc.
*
* ```
* | | simple | modbus |
* |------------------|--------|--------|
* | Coil | 0 | 1 |
* | Coil | 1 | 2 |
* | Input | 0 | 10001 |
* | Input | 1 | 10002 |
* | Holding Register | 0 | 30001 |
* | Holding Register | 1 | 30002 |
* | Input Register | 0 | 40001 |
* | Input Register | 1 | 40002 |
* ```
* @default: true
*/
simpleAddressing?: boolean

}

export abstract class ModbusCommandFactory {

protected readonly simpleAddressing: boolean = true

protected constructor(options?: ModbusCommandFactoryOptions) {
if(options !== undefined){
if(options.simpleAddressing === false) {
this.simpleAddressing = false
}
}
}

abstract fromPacket(packet: Buffer): ModbusCommand<any>

}
37 changes: 30 additions & 7 deletions src/tcp/modbus-tcp-command-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ import {
UnitIdGetter
} from '../modbus-commands'
import { ModbusCommandError } from '../error/modbus-errors'
import { ModbusCommandFactory } from '../modbus-command-factory'
import { ModbusCommandFactory, ModbusCommandFactoryOptions } from '../modbus-command-factory'

export class ModbusTcpCommandFactory implements ModbusCommandFactory {
export class ModbusTcpCommandFactory extends ModbusCommandFactory {

private _options?: ModbusCommandFactoryOptions

constructor(options?: ModbusCommandFactoryOptions) {
super(options)
this._options = options
}

private _unitIdGetter: UnitIdGetter = (requestPacket => {
return requestPacket.readUInt8(6)
Expand Down Expand Up @@ -55,7 +62,7 @@ export class ModbusTcpCommandFactory implements ModbusCommandFactory {
const paddedData = [...data, ...(new Array<boolean>(8 - (coilsRequested % 8)).fill(false))]
for (let i = 0; i < byteLength; i++) {
// Take a slice of the array of length 8, reverse it, then fill the accumulator with it (starting from right)
response[9+i] = paddedData.slice(i * 8, 8 + (i * 8)).reduce(
response[9 + i] = paddedData.slice(i * 8, 8 + (i * 8)).reduce(
(accumulator, currentValue, currentIndex) => accumulator | ((currentValue ? 1 : 0) << currentIndex),
0x00)
}
Expand Down Expand Up @@ -88,18 +95,34 @@ export class ModbusTcpCommandFactory implements ModbusCommandFactory {
return Buffer.from(new Uint8Array(response))
})

private _registerAddressGetter: RegisterAddressGetter = (requestPacket => {
return requestPacket.readUInt16BE(8) + 40001
private _holdingRegisterAddressGetter: RegisterAddressGetter = (requestPacket => {
return this.simpleAddressing ? requestPacket.readUInt16BE(8) : requestPacket.readUInt16BE(8) + 40001
})

// private _simepleHoldingRegisterAddressGetter: RegisterAddressGetter = (requestPacket => {
// return requestPacket.readUInt16BE(8)
// })
//
// private _modbusHoldingRegisterAddressGetter: RegisterAddressGetter = (requestPacket => {
// return requestPacket.readUInt16BE(8) + 40001
// })

private _registerValueGetter: RegisterValueGetter = (requestPacket => {
return requestPacket.readUInt16BE(10)
})

private _coilAddressGetter: CoilAddressGetter = (requestPacket => {
return requestPacket.readUInt16BE(8) + 1
return this.simpleAddressing ? requestPacket.readUInt16BE(8) : requestPacket.readUInt16BE(8) + 1
})

// private _simpleCoilAddressGetter: CoilAddressGetter = (requestPacket => {
// return requestPacket.readUInt16BE(8)
// })
//
// private _modbusCoilAddressGetter: CoilAddressGetter = (requestPacket => {
// return requestPacket.readUInt16BE(8) + 1
// })

private _coilLengthGetter: CoilLengthGetter = (requestPacket => {
return requestPacket.readUInt16BE(10)
})
Expand All @@ -117,7 +140,7 @@ export class ModbusTcpCommandFactory implements ModbusCommandFactory {
case ModbusFunctionCode.PRESET_SINGLE_REGISTER:
return new PresetSingleRegisterCommand(packet, this._unitIdGetter,
this._functionCodeGetter, this._packetCopySuccessGetter,
this._failureGetter, this._registerAddressGetter,
this._failureGetter, this._holdingRegisterAddressGetter,
this._registerValueGetter)
case ModbusFunctionCode.READ_COIL_STATUS:
return new ReadCoilStatusCommand(packet, this._unitIdGetter,
Expand Down
18 changes: 13 additions & 5 deletions src/tcp/modbus-tcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,33 @@ import {
PresetSingleRegisterCommand,
ReadCoilStatusCommand
} from '../modbus-commands'
import { ModbusCommandFactoryOptions } from '../modbus-command-factory'

// TODO: Properly handle connection open, close, and packet boundaries

interface ModbusTcpServerOptions {
/**
* Options that only affect the server (timeouts, etc.) should go here,
* options that affect the commands being emitted should be added to the ModbusCommandFactoryOptions
*/
export interface ModbusTcpServerOptions extends ModbusCommandFactoryOptions {
}

// TODO: Properly handle connection open, close, and packet boundaries
export class ModbusTcpServer extends ModbusServer {
private _tcpServer: net.Server
private _eventFactory = new ModbusTcpCommandFactory()
private _commandFactory = new ModbusTcpCommandFactory()
private _options?: ModbusTcpServerOptions

constructor(options?: ModbusTcpServerOptions) {
super()

this._options = options
this._commandFactory = new ModbusTcpCommandFactory(options)

this._tcpServer = net.createServer(socket => {
const _this: ModbusTcpServer = this

socket.on('data', data => {
// Build object from packet
let command = this._eventFactory.fromPacket(data)
let command = this._commandFactory.fromPacket(data)

// Listen for success or failure events being emitted from command object
command.onComplete.once((command: ModbusCommand<any>) => {
Expand Down
52 changes: 48 additions & 4 deletions src/tcp/tcp-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {
import { ModbusCommandError } from '../error/modbus-errors'
import { ModbusTcp } from '../simple-modbus'

const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()

describe("PresetSingleRegisterCommand test", () => {

// 0-1 = Transaction ID
Expand All @@ -23,31 +21,49 @@ describe("PresetSingleRegisterCommand test", () => {
const validResponseBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x11, 0x06, 0x01, 0x10, 0x01, 0x10]

it("should return an instance of PresetSingleRegisterCommand", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(command).toBeInstanceOf(PresetSingleRegisterCommand)
})

it("should return the right function code", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(command.functionCode).toEqual(ModbusFunctionCode.PRESET_SINGLE_REGISTER)
})

it("should return the right register address", () => {
it("should return the right register address (Blank)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
expect(command.registerAddress).toEqual(272)
})

it("should return the right register address (Simple)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory({simpleAddressing: true})
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
expect(command.registerAddress).toEqual(272)
})

it("should return the right register address (Modbus)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory({simpleAddressing: false})
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
expect(command.registerAddress).toEqual(40273)
})

it("should return the right register value", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
expect(command.registerValue).toEqual(272)
})

it("should return the right Unit ID", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
expect(command.unitId).toEqual(0x11)
})

it("should emit a complete response on success", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
command.onComplete.on((command: ModbusCommand<any>) => {
expect(command.responsePacket).toEqual(Buffer.from(validResponseBytes))
Expand All @@ -57,6 +73,7 @@ describe("PresetSingleRegisterCommand test", () => {
})

it("should emit a success response on success", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as PresetSingleRegisterCommand)
command.onSuccess.on((command: ModbusCommand<any>) => {
expect(command.responsePacket).toEqual(Buffer.from(validResponseBytes))
Expand All @@ -66,6 +83,7 @@ describe("PresetSingleRegisterCommand test", () => {
})

it("should emit a complete response on failure", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const failureBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x11, 0x86, 0x04]
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
command.onComplete.on((command: ModbusCommand<any>) => {
Expand All @@ -76,6 +94,7 @@ describe("PresetSingleRegisterCommand test", () => {
})

it("should emit a failure response on failure", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const failureBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x11, 0x86, 0x04]
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
command.onFailure.on((command: ModbusCommand<any>) => {
Expand All @@ -86,6 +105,7 @@ describe("PresetSingleRegisterCommand test", () => {
})

it("should throw an error when accessing response packet before success or fail has been called", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(() => {
let response = command.responsePacket
Expand Down Expand Up @@ -115,31 +135,49 @@ describe("ReadCoilStatusCommand test", () => {
const validResponseBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x05, 0x01, 0x05, 0xCD, 0x6B, 0xB2, 0x0E, 0x1B]

it("should return an instance of ReadCoilStatusCommand", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(command).toBeInstanceOf(ReadCoilStatusCommand)
})

it("should return the right function code", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(command.functionCode).toEqual(ModbusFunctionCode.READ_COIL_STATUS)
})

it("should return the right coil address", () => {
it("should return the right coil address (Blank)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
expect(command.coilStartAddress).toEqual(272)
})

it("should return the right coil address (Simple)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory({simpleAddressing: true})
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
expect(command.coilStartAddress).toEqual(272)
})

it("should return the right coil address (Modbus)", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory({simpleAddressing: false})
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
expect(command.coilStartAddress).toEqual(273)
})

it("should return the right coil length", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
expect(command.numberOfCoils).toEqual(37)
})

it("should return the right Unit ID", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
expect(command.unitId).toEqual(5)
})

it("should emit a complete response on success", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
command.onComplete.on((command: ModbusCommand<any>) => {
expect(command.responsePacket).toEqual(Buffer.from(validResponseBytes))
Expand All @@ -149,6 +187,7 @@ describe("ReadCoilStatusCommand test", () => {
})

it("should emit a success response on success", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = (commandFactory.fromPacket(Buffer.from(validCommandBytes)) as ReadCoilStatusCommand)
command.onSuccess.on((command: ModbusCommand<any>) => {
expect(command.responsePacket).toEqual(Buffer.from(validResponseBytes))
Expand All @@ -158,6 +197,7 @@ describe("ReadCoilStatusCommand test", () => {
})

it("should emit a complete response on failure", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const failureBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x05, 0x81, 0x04]
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
command.onComplete.on((command: ModbusCommand<any>) => {
Expand All @@ -168,6 +208,7 @@ describe("ReadCoilStatusCommand test", () => {
})

it("should emit a failure response on failure", done => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const failureBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x05, 0x81, 0x04]
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
command.onFailure.on((command: ModbusCommand<any>) => {
Expand All @@ -178,6 +219,7 @@ describe("ReadCoilStatusCommand test", () => {
})

it("should throw an error when accessing response packet before success or fail has been called", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const command = commandFactory.fromPacket(Buffer.from(validCommandBytes))
expect(() => {
let response = command.responsePacket
Expand All @@ -190,13 +232,15 @@ describe("ReadCoilStatusCommand test", () => {
describe("Malformed packet tests", () => {

it("should throw a command exception on invalid fc", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const invalidCommandBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x11, 0x14, 0x00, 0x00, 0x00, 0x03]
expect(() => {
const command = commandFactory.fromPacket(Buffer.from(invalidCommandBytes))
}).toThrowError(new ModbusCommandError('Function code not implemented'))
})

it("should throw a command exception on short packet", () => {
const commandFactory: ModbusTcp.CommandFactory = new ModbusTcp.CommandFactory()
const invalidCommandBytes = [0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x11, 0x14, 0x00, 0x00]
expect(() => {
const command = commandFactory.fromPacket(Buffer.from(invalidCommandBytes))
Expand Down

0 comments on commit cbf4ad7

Please sign in to comment.