Closed bukebuchi closed 6 years ago
h5.modbus.Slave
always expects the listener
option to be of type h5.modbus.Listener
and the transport
option to be h5.modbus.Transport
.
h5.modbus.Master
always expects the connection
option to be of type h5.modbus.Connection
and the transport
option to be h5.modbus.Transport
.
If you pass the h5.modbus.Listener
as the connection
to the Master
it will most likely crash due to NULL-reference exception.
So is this the real deal?
sensor (slave) <->4G modem <-> server(master)
I mean, is the sensor directly connected to the 4G modem through RS485?
Or is it something like this :
Network 1 Network 2
------------------------ ------------------------------------------
Master PC <=ETH=> router <=4G=> 4G modem <=ETH=> PC <=RS485=> Slave sensor
(is a or is
connected to
a router)
Does the 4G modem
have an external, static IP (can the Master PC
connect to the 4G modem
)?
Or only the router
has an external, static IP and the 4G modem
has to initiate the connection?
In any case, check issue #15 and this gist (you'd have to change the plcConn
from net.Socket
to SerialPort
and master.transport
from the default IpTransport
to RtuTransport
).
firstly,Thank you for your reply. the sensor directly connected to the 4G modem through RS485,the 4G modem is a transparent ransmission module,we call it dtu or M2M. you can think 4G modem is a wire which send the packet transparently from sensor to the cloud server. it can send the packet transparently from 485 wire to static IP server . 485(modbus RTU) TCP (persistent connection) sensor ------ -----------------4G-----------------------------------the cloud server(with static IP) i want to put the code to the cloud server(centos linux), after tcp of the server connected,the server send modbus rtu packet to 4G and transparently to the sensor.and the sensor will reply the RTU packet to the server.finally,the server handle the RTU packet. there is no serialport and the protocol is modbus RTU not modbus TCP. it is just transport via TCP.
const transport = new modbus.RtuTransport({eofTimeout: 0}); const listener = new modbus.TcpListener(); const slave = new modbus.Slave({ listener: listener, transport: transport, requestHandler: handleRequest }); const master = new modbus.Master({ transport: transport, connection: listener, //the param i don't know how to pass maxConcurrentTransactions: CONCURRENT_TRANSACTIONS, defaultUnit: 1, defaultMaxRetries: 0 });
your code master.js is const connection = new modbus.TcpConnection({}); const master = new modbus.Master({ transport: transport, connection: connection , //the param i don't know how to pass maxConcurrentTransactions: CONCURRENT_TRANSACTIONS, defaultUnit: 1, defaultMaxRetries: 0 }); i think the code is for modbus TCP and the code is for PC slave,it can send packet to master,right? how to use Master.js to write data to server socket.
master.js have to write the master IP and port as client,right? TcpConnection.js this.socketOptions = Object.assign({port: 80, host: 'localhost'}, options.socketOptions); slave.js have to write port as server listener,right? TcpListener.js this.serverOptions = Object.assign({port: 502, host: '0.0.0.0'}, options.serverOptions);
and i just want to write and receive packet through the server which set up the socket,how to do?
setUpClient(socket)
{
const client = new RemoteClient({
address: socket.remoteAddress,
onClientWriteport: socket.remotePort,
family: socket.remoteFamily
});
client.on('close', this.onClientClose.bind(this, socket));
client.on('write', this.onClientWrite.bind(this, socket));
const socketListeners = {
close: client.destroy.bind(client),
error: client.emit.bind(client, 'error'),
readable: function()
{
const data = socket.read();
if (data !== null)
{
client.emit('data', data);
client.emit('write', data); //i test this code added can write the packet to 4G and to sensor
}
}
};
client.emit('write', data); //i test this code added can write the packet to 4G and to sensor
but the data is not modbus RTU protocol and i want to use Mater.js funtion like startTransaction to send the data.
function startTransaction(id, functionCode, quantity, interval)
{
var t = master.execute({
id: id,
request: {
functionCode: functionCode,
address: 32,
quantity: quantity
},
interval: interval
});
t.on('error', onError); t.on('request', onRequest); t.on('response', onResponse); }
Then you must use TcpConnection
and RtuTransport
:
const master = modbus.createMaster({
connection: {
type: 'tcp',
socketOptions: {
// IP of the 4G modem
host: '123.123.123.123',
port: 502
},
// `Connection.open()` will be called automatically
autoOpen: true,
// connection will be re-established after disconnect
autoReconnect: true,
// connection will be marked as stable after 2.5s
// (resets connection attempts counter and sets up
// the no activity timer)
minConnectTime: 2500,
// each consecutive reconnect attempt is delayed by
// 250 ms (first immediate, second 250 ms, 3rd 500 ms)
// up to 5s
maxReconnectTime: 5000,
// close the connection if we don't receive anything
// from the slave for 10s
noActivityTime: 10000
},
transport: {
type: 'rtu',
// End of frame timeout
eofTimeout: 100
}
});
master.once('open', function()
{
console.log('Connected!');
const t = master.readHoldingRegisters(0x0000, 10);
t.on('error', err => console.error(err.message));
t.on('response', res => console.log(res.toString()));
});
See this post for an explanation of how the transport.eofTimeout
works.
You should not change any code in the library.
thank you for your reply! this way has a problem. the 4G module don't have a static IP and port. the 4G module just can be a client to send packet to server.if i can modify the code on 4G module and let it change to server maybe the problem can be solved,right? i can get the 4G module client.remoteInfo.address and changed client.remoteInfo.port by modbus.TcpListener(); but the socket is not the same ,right?
If you can make the modem initiate a connection to the cloud server, then on you'll have to run two simple Node.js TCP servers and the h5.modbus.Master
on the cloud server.
Run two Node.js TCP servers on the cloud server, one internal and one external and connect them together (and add error handling):
const net = require('net');
const externalServer = net.createServer();
const internalServer = net.createServer();
let externalClient = null;
let internalClient = null;
externalServer.on('connection', function(conn)
{
externalClient = conn;
externalClient.pipe(internalClient);
});
externalServer.on('connection', function(conn)
{
internalClient = conn;
internalClient.pipe(externalClient);
});
externalServer.listen(3000);
internalServer.listen(502, '127.0.0.1');
Also on the cloud server, run the Master that connects to the internal TCP server:
const master = modbus.createMaster({
connection: {
type: 'tcp'
},
transport: {
type: 'rtu',
eofTimeout: 100
}
});
master.once('open', function()
{
console.log('Connected!');
const t = master.readHoldingRegisters(0x0000, 10);
t.on('error', err => console.error(err.message));
t.on('response', res => console.log(res.toString()));
});
Finally, configure the 4G modem so it connects to <CLOUD_SERVER_HOST>:3000
.
Running on the cloud server
--------------------------------------------------------
Master <->> Internal server 502 <-> External server 3000 <<-> 4G modem <-> Sensor
i change this code as you said. the 4G modem has successfulLy conneted to server there is a problem the log like this
Connected!
No response was received from the slave in the specified time.
No response was received from the slave in the specified time.
_stream_readable.js:501
dest.on('unpipe', onunpipe);
^
TypeError: Cannot read property 'on' of null
at Socket.Readable.pipe (_stream_readable.js:501:7)
at Server.
If you can make the modem initiate a connection to the cloud server you mean i will setup a socket between modem and the cloud server firstly?
That code is missing error handling.
With simulator:
node modem.js
node bridge.js
node master.js
modem.js
is a fake 4G modem (connects to the bridge and the slave).
With the real modem:
master.js
to rtu
.node bridge.js
on the cloud server.node master.js
on the cloud server.<CLOUD_SERVER_HOST>:3000
.sorry to bother you i can not open the link https://gist.github.com/morkai/f46702f77bd5e78536165a3b1b9a40f8 is this site right?
Yes, it works.
bridge.js:
'use strict';
const {createServer} = require('net');
const externalServer = createServer();
const internalServer = createServer();
let externalClient = null;
let internalClient = null;
externalServer.on('connection', function(conn)
{
if (externalClient)
{
console.log('[external] Ignoring connection!');
return;
}
console.log('[external] Accepted connection!');
externalClient = conn;
externalClient.on('error', (err) => console.error(`[external#error] ${err.message}`));
externalClient.on('close', function()
{
console.log('[external#close]');
externalClient = null;
});
externalClient.on('readable', function()
{
const data = externalClient.read();
if (data && internalClient)
{
internalClient.write(data);
}
});
});
internalServer.on('connection', function(conn)
{
if (internalClient)
{
console.log('[internal] Ignoring connection!');
return;
}
console.log('[internal] Accepted connection!');
internalClient = conn;
internalClient.on('error', (err) => console.error(`[internal#error] ${err.message}`));
internalClient.on('close', function()
{
console.log('[internal#close]');
internalClient = null;
});
internalClient.on('readable', function()
{
const data = internalClient.read();
if (data && externalClient)
{
externalClient.write(data);
}
});
});
externalServer.listen(3000);
internalServer.listen(3001, '127.0.0.1');
master.js
'use strict';
const modbus = require('h5.modbus');
const connection = modbus.createConnection({
type: 'tcp',
socketOptions: {
host: '127.0.0.1',
port: 3001
}
});
connection.on('open', () => console.log('[connection#open]'));
connection.on('close', () => console.log('[connection#close]'));
connection.on('data', (data) => console.log('[rx]', data));
connection.on('write', (data) => console.log('[tx]', data));
const transport = modbus.createTransport({
type: 'ip'
});
const master = modbus.createMaster({
connection: connection,
transport: transport
});
master.on('error', (err) => console.log(`[master#error] ${err.message}`));
master.once('open', function()
{
const t = master.readHoldingRegisters(0x0000, 1, {
interval: 1000
});
t.on('error', (err) => console.log(`[transaction#error] ${err.message}`));
t.on('request', (rid) => console.log(`[transaction#request#${rid}] ${t.request}`));
t.on('response', (res) => console.log(`[transaction#response] ${res}`));
});
'use strict';
const {connect} = require('net');
const connections = {
server: null,
plc: null
};
setUpConnection('server', 'plc', '127.0.0.1', 3000);
setUpConnection('plc', 'server', '127.0.0.1', 502);
function setUpConnection(source, target, host, port)
{
let connection = connections[source];
if (connection)
{
connection.removeAllListeners();
connection.on('error', () => {});
connection.destroy();
}
console.log(`[${source}#connecting]`);
connections[source] = connection = connect(port, host);
connection.on('connect', () => console.log(`[${source}#connect]`));
connection.on('error', (err) => console.error(`[${source}#error] ${err.message}`));
connection.on('close', () =>
{
console.log(`[${source}#close]`);
setTimeout(setUpConnection, 1000, source, target, host, port);
});
connection.on('readable', () =>
{
const data = connection.read();
if (data && connections[target])
{
connections[target].write(data);
}
});
}
Hi,i test the code like you said i find it can not work the externalServer and internalServer has their own sockets which is created by only port and the sockets contains their own functions like write or read but the master.js const connection = modbus.createConnection({ type: 'tcp', socketOptions: { host: '127.0.0.1', port: 3001 } }); the connection is created by TcpConnection.js setUpSocket funtion setUpSocket(socket) { if (!socket) { socket = new Socket(); } console.log('setUpSocket!'); socket.on('connect', this.onSocketConnect); socket.on('close', this.onSocketClose); socket.on('error', this.onSocketError); socket.on('readable', this.onSocketReadable); socket.setNoDelay(this.socketOptions.noDelay !== false);
return socket;
}
the socket is different from the sockets created by the externalServer and internalServer ,which has its own funtion like write and read. even the sockets created by the externalServer and internalServer use the same port and IP with the socket created by master.js am i right? i have tested this code ,which can not send the packet to 4G
i want to modify the Master.js ` start(requestId) { console.log('start'); if (!this.frame || !this.transport.update(this.frame, requestId)) { this.frame = this.transport.encode(requestId, this.transaction.unit, this.transaction.request.toBuffer()); }
this.requestId = requestId;
this.timeoutTimer = setTimeout(this.onTimeout.bind(this), this.transaction.timeout);
this.connection.write(this.frame); //this place
this.transaction.emit('request', requestId);
} `
how to replace this code this.connection.write(this.frame); the write function is contained by TcpConnection.js i want to use onClientWrite(socket, data) which is contained by TcpListener.js how to implement this code?
h5.modbus.Master
is a client. h5.modbus.Slave
is a server. TcpConnection
uses net.Socket
. TcpListener
uses net.Server
. Modbus is a client-server protocol. Master sends requests to the Slave and Slave responds. Slave doesn't send anything to the Master on its own. They do not mix. You can't copy-paste some code from TcpListener
to Master
. It doesn't make sense.
bridge.js
to your.cloud.server.comnode bridge.js
on your.cloud.server.comyour.cloud.server.com:3000 port is open
.your.cloud.server.com:3000
.node bridge.js
print [external] Accepted connection!
in the console.If you don't see [external] Accepted connection!
and the modem doesn't have an externally accessible IP, then what you're trying to accomplish is not possible.
What I'm trying to say is that your problem doesn't have anything to do with this library ;)
now, i run node bridge.js the log [external] Accepted connection! [internal] Accepted connection! [externalClient.write] <Buffer 00 04 00 01 00 02 21 da>
i run master.js the log [transaction#request#20] 0x04 (REQ) Read 2 input registers starting from address 1 [transaction#error] No response was received from the slave in the specified time. the data can not send to 4G modem is there way to use the library like what i said above ? modbus RTU over TCP.
Change
const transport = modbus.createTransport({
type: 'ip'
});
in master.js
to:
const transport = modbus.createTransport({
type: 'rtu',
eofTimeout: 1000
});
the 4G log
[06-04 20:35:30] 0:(115.159.201.153:3000) Connected . means the server has connected
master.js log
[root@VM_214_70_centos h5.modbus]# node master.js
[connection#open]
[tx] <Buffer 00 04 00 01 00 02 21 da>
[transaction#error] No response was received from the slave in the specified time.
[tx] <Buffer 00 04 00 01 00 02 21 da>
bridge.js log
[root@VM_214_70_centos h5.modbus]# node bridge.js
[internal] Accepted connection!
[external] Accepted connection!
[externalClient.write] <Buffer 00 04 00 01 00 02 21 da>
[externalClient.write] <Buffer 00 04 00 01 00 02 21 da>
the code
'use strict';
const modbus = require('h5.modbus');
const connection = modbus.createConnection({
type: 'tcp',
socketOptions: {
host: '127.0.0.1',
port: 3001
}
});
connection.on('open', () => console.log('[connection#open]'));
connection.on('close', () => console.log('[connection#close]'));
connection.on('data', (data) => console.log('[rx]', data));
connection.on('write', (data) => console.log('[tx]', data));
const transport = modbus.createTransport({
type: 'rtu',
eofTimeout: 1000
});
const master = modbus.createMaster({
connection: connection,
transport: transport
});
master.on('error', (err) => console.log(`[master#error] ${err.message}`));
master.once('open', function()
{
const t = master.readInputRegisters(0x0001, 2, {
interval: 10000
});
t.on('error', (err) => console.log(`[transaction#error] ${err.message}`));
t.on('request', (rid) => console.log(`[transaction#request#${rid}] ${t.request}`));
t.on('response', (res) => console.log(`[transaction#response] ${res}`));
});
'use strict';
const {createServer} = require('net');
const externalServer = createServer();
const internalServer = createServer();
let externalClient = null;
let internalClient = null;
externalServer.on('connection', function(conn)
{
if (externalClient)
{
console.log('[external] Ignoring connection!');
return;
}
console.log('[external] Accepted connection!');
externalClient = conn;
externalClient.on('error', (err) => console.error(`[external#error] ${err.message}`));
externalClient.on('close', function()
{
console.log('[external#close]');
externalClient = null;
});
externalClient.on('readable', function()
{
const data = externalClient.read();
if (data && internalClient)
{
console.log('[ internalClient.write]',data);
internalClient.write(data);
}
});
});
internalServer.on('connection', function(conn)
{
if (internalClient)
{
console.log('[internal] Ignoring connection!');
return;
}
console.log('[internal] Accepted connection!');
internalClient = conn;
internalClient.on('error', (err) => console.error(`[internal#error] ${err.message}`));
internalClient.on('close', function()
{
console.log('[internal#close]');
internalClient = null;
});
internalClient.on('readable', function()
{
const data = internalClient.read();
if (data && externalClient)
{
console.log('[externalClient.write]',data);
externalClient.write(data);
}
});
});
externalServer.listen(3000);
internalServer.listen(3001, '127.0.0.1');`
i have tested this code just modify TcpListener.js
setUpClient(socket) { const client = new RemoteClient({ address: socket.remoteAddress, port: socket.remotePort, family: socket.remoteFamily });
client.on('close', this.onClientClose.bind(this, socket));
client.on('write', this.onClientWrite.bind(this, socket));
const socketListeners = {
close: client.destroy.bind(client),
error: client.emit.bind(client, 'error'),
readable: function()
{
const data = socket.read();
if (data !== null)
{
client.emit('data', data);
## client.emit('write', data);//i can sent the data from 4G back to 4G
}
}
};
socket.on('close', socketListeners.close);
socket.on('error', socketListeners.error);
socket.on('readable', socketListeners.readable);
socket.setNoDelay(this.serverOptions.noDelay !== false);
this.socketListeners.set(socket, socketListeners);
this.clients.set(socket, client);
this.emit('client', client);
}
client.emit('data', data); client.emit('write', data); just use the server i can send and receive data. but the data can not decode and encode on modbus RTU protocol
So the modem connects to the server:
[external] Accepted connection!
Master sends data to the modem:
[externalClient.write] <Buffer 00 04 00 01 00 02 21 da>
but the modem doesn't respond with anything:
[transaction#error] No response was received from the slave in the specified time.
The request is lost or ignored by the sensor.
Try setting the Modbus unit ID to the correct one (if not specified, defaults to 0):
const t = master.readInputRegisters(0x0001, 2, {
unit: 0,
interval: 10000
});
yeah,set unit ID, the problem is the same. the problem is not response from 4G, i find the data can not send to 4G. the only way i tested successfully is use one server ,no use other TcpConnection socket. the server can write and read data between 4G and the cloud server the code added client.emit('write', data); //added to setUpClient function for test
setUpClient(socket) { const client = new RemoteClient({ address: socket.remoteAddress, port: socket.remotePort, family: socket.remoteFamily });
client.on('close', this.onClientClose.bind(this, socket));
client.on('write', this.onClientWrite.bind(this, socket));
const socketListeners = {
close: client.destroy.bind(client),
error: client.emit.bind(client, 'error'),
readable: function()
{
const data = socket.read();
if (data !== null)
{
client.emit('data', data);
client.emit('write', data); //added to test
}
}
};
You should forget about h5.modbus
for a moment and create a simple, working script in plain Node.js.
sensor (485) modbus RTU Is there way to poll such system: sensor (slave) <->4G modem <-> server(master)?
i want to use example/slave.js and master.js to setup server to listen so that i can write and receive on modbus RTU but the master.js = client has to know the 4G modem IP
the code i changed like this const slave = new modbus.Slave({ listener: listener, transport: transport, requestHandler: handleRequest }); const master = new modbus.Master({ transport: transport, connection: listener, //connection change to listener maxConcurrentTransactions: CONCURRENT_TRANSACTIONS, defaultUnit: 1, defaultMaxRetries: 0 }); this will cause a problem at Master.js start(requestId) { if (!this.frame || !this.transport.update(this.frame, requestId)) { this.frame = this.transport.encode(requestId, this.transaction.unit, this.transaction.request.toBuffer()); }
//console.log('this.connection.socket.remoteAddress ',this.connection.socket);
this.connection.write(this.frame); //this place is the problem //this.connection.emit('write',this.frame); // this.connection.onClientWrite(this.connection.socket,this.frame); //i modify this code like this this.transaction.emit('request', requestId); }
this.connection.write(this.frame) is not undefined i follow this error to Tcplistener.js the Tcplistener.js do not have a funtion write,so i modify this code like this this.connection.onClientWrite(this.connection.socket,this.frame); but i find the this.connection.socket is null . is there way to send and receive the modbus RTU packet on server? my english is not good , i hope above is clear.