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

TCP / RTU Client Manager #255

Open
wants to merge 19 commits into
base: v4.0-dev
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
33 changes: 33 additions & 0 deletions examples/javascript/tcp/ModbusTCPClientManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const modbus = require('../../..')

const manager = new modbus.ModbusTCPClientManager()

const socket = manager.findOrCreateSocket({
host: 'localhost',
port: 5052,
})

socket.on('connect', () => {
console.log('SOCKET CONNECTED')

setInterval(() => {
for(const [clientId, client] of manager.clients){
client
.readCoils(0, 5)
.then(({response}) => console.log(response.body.valuesAsArray))
.catch(console.error)
}
}, 1000)
})

for(let i = 0; i < 50; i++) {
manager.findOrCreateClient({
host: 'localhost',
port: 5052,
slaveId: i
})
}


console.log(manager.clientCount) // should be 50
console.log(manager.socketCount) // should be 1
32 changes: 32 additions & 0 deletions examples/typescript/tcp/ModbusTCPClientManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Modbus from '../../../dist/modbus'

const manager = new Modbus.ModbusTCPClientManager()

const socket = manager.findOrCreateSocket({
host: 'localhost',
port: 5052
})

socket.on('connect', () => {
console.log('SOCKET CONNECTED')

setInterval(() => {
for (const [clientId, client] of manager.clients) {
client
.readCoils(0, 5)
.then(({ response }) => console.log(response.body.valuesAsArray))
.catch(console.error)
}
}, 1000)
})

for (let i = 0; i < 50; i++) {
manager.findOrCreateClient({
host: 'localhost',
port: 5052,
slaveId: i
})
}

console.log(manager.clientCount) // should be 50
console.log(manager.socketCount) // should be 1
2 changes: 2 additions & 0 deletions src/client-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as ModbusRTUClientManager } from './modbus-rtu-client-manager'
export { default as ModbusTCPClientManager } from './modbus-tcp-client-manager'
197 changes: 197 additions & 0 deletions src/client-manager/modbus-rtu-client-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import SerialPort, { OpenOptions } from 'serialport'
import { MapUtils } from '../map-utils'
import ModbusRTUClient from '../modbus-rtu-client'

// typealiases
type SlaveId = number
type SocketId = string
type ClientId = string

interface IRTUInfo extends OpenOptions {
path: string
}

interface IRTUSlaveInfo extends IRTUInfo {
slaveId: SlaveId
}

export default class ModbusRTUClientManager {

public static async ListSerialPorts () {
const ports = await SerialPort.list()

return ports
}

public readonly clients = new Map<ClientId, ModbusRTUClient>()
public readonly sockets = new Map<SocketId, SerialPort>()

public get socketCount () {
return this.sockets.size
}

public get clientCount () {
return this.clients.size
}

public findClient (rtuSlaveInfo: IRTUSlaveInfo) {
const clientId = this.marshalClientId(rtuSlaveInfo)
return this.clients.get(clientId)
}

/**
* Returns a map of all clients that are bound to a specific socket
*/
public filterClientsBySocket (rtuInfo: IRTUInfo) {
return MapUtils.Filter(this.clients,
(
([clientId]) => {
const { path } = this.unmarshalClientId(clientId)
return rtuInfo.path === path
}
)
)
}

/**
* Finds or creates a modbus rtu client
*/
public findOrCreateClient (rtuClientInfo: IRTUSlaveInfo) {
return this.findClient(rtuClientInfo) || this.createClient(rtuClientInfo)
}

/**
* Creates a modbus rtu client
*/
public createClient (rtuInfo: IRTUSlaveInfo, timeout = 2000) {
const socket = this.findOrCreateSocket(rtuInfo)
const client = new ModbusRTUClient(socket, rtuInfo.slaveId, timeout)
const clientId = this.marshalClientId(rtuInfo)
this.clients.set(clientId, client)
return client
}

/**
* Finds a modbus rtu client
*/
public findSocket (rtuInfo: IRTUInfo) {
return this.sockets.get(this.marshalSocketId(rtuInfo))
}

/**
* Finds or creates a rtu socket connection
* @param {string} host
* @param {number} port
*/
public findOrCreateSocket (rtuInfo: IRTUInfo) {
return this.findSocket(rtuInfo) || this.createSocket(rtuInfo)
}

/**
* Creates a rtu socket connection
*/
public createSocket (rtuInfo: IRTUInfo) {
const {
path,
...options
} = rtuInfo
const socket = new SerialPort(path, options)

// set maximum listeners to the maximum number of clients for
// a single rtu master
socket.setMaxListeners(255)

const socketId = this.marshalSocketId(rtuInfo)
this.sockets.set(socketId, socket)

return socket
}

/**
* Removes a rtu socket connection and all bound clients
*/
public removeSocket (rtuInfo: IRTUInfo) {
const socket = this.findSocket(rtuInfo)
if (socket) {
this.removeClientsBySocket(rtuInfo)
socket.end()
const socketId = this.marshalSocketId(rtuInfo)
this.sockets.delete(socketId)
}
}

/**
* Removes a modbus rtu client
*/
public removeClient (rtuSlaveInfo: IRTUSlaveInfo) {
const client = this.findClient(rtuSlaveInfo)
if (client) {
client.manuallyRejectAllRequests()
this.clients.delete(this.marshalClientId(rtuSlaveInfo))
}
}

/**
* Removes all clients assosciated to a single socket
*/
public removeClientsBySocket (rtuInfo: IRTUInfo) {
const clients = this.filterClientsBySocket(rtuInfo)
for (const [clientId] of clients) {
const { slaveId } = this.unmarshalClientId(clientId)
this.removeClient({
path: rtuInfo.path,
slaveId
})
}
}

/**
* Removes all unused sockets. An unused socket is a socket
* that has no clients that use it
*/
public removeAllUnusedSockets () {
const socketsWithoutClients = this.findSocketsWithoutClients()
for (const [socketId] of socketsWithoutClients) {
const rtuInfo = this.unmarshalSocketId(socketId)
this.removeSocket(rtuInfo)
}
}

/**
* Finds sockets that do not have any clients using it
*/
private findSocketsWithoutClients () {
return MapUtils.Filter(this.sockets,
(
([socketId]) => {
const rtuInfo = this.unmarshalSocketId(socketId)
const clients = this.filterClientsBySocket(rtuInfo)
return clients.size === 0
}
)
)
}

private marshalSocketId ({ path }: IRTUInfo): SocketId {
return `${path}`
}

private unmarshalSocketId (socketId: SocketId): IRTUInfo {
return {
path: socketId
}
}

private marshalClientId ({ path, slaveId }: IRTUSlaveInfo): ClientId {
return `${path}.${slaveId}`
}

private unmarshalClientId (clientId: ClientId): IRTUSlaveInfo {
const [path, slaveId] = clientId.split('.')

return {
path,
slaveId: parseInt(slaveId, 10)
}
}
}
Loading