B4J Library Asynchronous modbus TCP master

Hello everyone,

Here a modbus TCP master library, fully programmed in b4j.
This library is asynchronous, by use of the asyncstreams and Callback method.
The response is an event, a separate sub, different then your sub where you did the request.
This has the advantage that an unresponsive server will not slow down your application.

The zip file here is a command line example, with the library.
You can compile this library as internal library as desired, but like this, you can look inside the code of the lib.

Supported functions:
-Read holding registers
-Read input registers
-Read coils
-Read discrete inputs
-Write single register
-Write multiple registers
-Write single coil
-Write multiple coils

Please note that I am not a professional programmer, so comments or feedback is always appreciated!

Kind regards,
Coldrestart.


** UPDATE on 25/07/2020**
Changes made in the library in the GUI version;
-Fixed typo in function name.
-TCP port and IP address are now in the Connect function instead of the initialization of the object.

**UPDATE on 18/08/2020**
Added: type selection, only for the representation, in signed, unsigned, hex and binary.

**UPDATE on 22/09/2020**
Changes made in the CLI version:
-The port and IP adress are passed when using the function "ConnectServer", instead of the initialization function.
Changes made for the CLI and GUI version: Conversion sub "ToBinaryString" was returning 9 bits instead of 8.
This issue was reported by the user "Madru" of the forum, thanks to him for reporting this issue.


GUI_MB_TCP_MASTER.png
 

Attachments

  • ModbusMaster_2020_09_22.zip
    9.4 KB · Views: 679
  • ModbusTCPMasterGUI_2020_09_22.zip
    13.3 KB · Views: 682
Last edited:

yaniv hanya

Active Member
Licensed User
how can we use it in b4a? we need exactly the same project for resperyPI device (android) to connect to monbus. how can we wrap/ translate it to b4a?
 

Coldrestart

Member
Licensed User
Longtime User
Yes, you can use this also in b4a, but you need to modify it.
I do not have much experiance in b4a, but you need to take in account that you are using asyncstreams.
So I think you need to initialize and access the lib in a service, and with the callsubdelayed functions, communicate with you UI. (Main thread)
Also, a timeout timer keeps track for an unresponsive slave, I also think that when the app is closed by the OS, this detection system is not working properly anymore.
 

Coldrestart

Member
Licensed User
Longtime User
Update 25/07/2020 - GUI example added, TCP port and IP address are now in the Connect function instead of the initialization of the object -> change made only in the GUI version.
 

Coldrestart

Member
Licensed User
Longtime User
Feedback from a user on the forum, that is good to know:
The starting address of a register is the PDU register, is zero based, while the official adressing is not zero based.
So it is possible if in a manual of a slave device, you need to read holding register 5001, in the lib, you need to send start register 5000.
If you don't like this, you can add a "+1" in the lib for the startaddress.

For more information about the modbus protocol, I refer to the official modbus website: https://modbus.org
There you can find the .pdf documents that describe the modbus protocol.


1596736833940.png
 

Coldrestart

Member
Licensed User
Longtime User
**UPDATE on 22/09/2020**
Changes made in the CLI version:
-The port and IP adress are passed when using the function "ConnectServer", instead of the initialization function.

Changes made for the CLI and GUI version: Conversion sub "ToBinaryString" was returning 9 bits instead of 8.
This issue was reported by the user "Madru" of the forum, thanks to him for reporting this issue.
 

TomDuncan

Active Member
Licensed User
Longtime User
Hi,
I have an epever charge controller with an rs485 connection. It used modbus (as a virtual com port)
Does your library have the ability to connect via usb?
I would like to build a solar logging program using b4j.
Tom
 

Coldrestart

Member
Licensed User
Longtime User
Hello,

No, the library only supports modbus TCP.
Modbus RTU requires handling an additional CRC and timing rules (for example, 3,5char delay between each poll).

Kind regards,
Coldrestart
 
Hello,

No, the library only supports modbus TCP.
Modbus RTU requires handling an additional CRC and timing rules (for example, 3,5char delay between each poll).

Kind regards,
Coldrestart

Hi,
I have read different postings about Modbus which all appear to define RTU, a difficult task! It makes sense on the B4A platform but on B4J platform why it is difficult task?

For example, in Android OS there is timing issues to route the data on USB data port but necessarily they should... If still there are problems and we must have to use Modbus TCP on B4A then can any expert write a decent efficient class/code for B4R ESP8266 to make master/slave Wi-Fi bridge to route data to ESP hardware serial port which will be connected to Modbus devices.

On the other hand, at B4J platform there are physical serial port or USB to serial ports are available and we are using them efficiently via RS485 so why not experts just make it easy task for people like newbies! (I am also new B4X Lover)

Modbus RTU requires handling an additional CRC and timing rules (for example, 3,5char delay between each poll).
If the problem is CRC then it is not big deal as I have done it on PIC microcontroller with Proton PicBasic. Just a small routine which calculate CRC16 which I can post here.
And 3.5 character delay between each delay!!! I think a small delay between each poll like 50ms or less or more can work.

Please experts humbly think about it. Thanks.
 

FabianGS

Member
Hey guys, hope you are doing well! :D, i just tried to use this, all works perfectly but i have a few questions. How can i get, specifictly, the type of variable that PLC is Holding (BOOL, DoubleFloat, etc). it always returns only the value stored in there, Also to get the number extacly of that register, as you may know, it has Atomic Types (Register) we can access to read and change its value. Would be great some help.
Thanks in Advance
 

FabianGS

Member
Hello,

It's your job to track down the request that you did. The transaction ID is the link between your request and the reply of the slave device.
Maybe this library suits your needs more: https://www.b4x.com/android/forum/threads/modbus-tcp-library.144028/#content

Kind regards,
Coldrestart
Thank you very much for your help!
This is what i did. but what i get return is a 124 bit when it should return a Hexadecimal 0X0000 or 0x00FF
inside Timer1_tick:
Select chbDataType.SelectedIndex
        Case 0
            MBobject.ReadHoldingRegisters(1,txtSlaveAddress.Text,txtStartReg.Text,txtNumbReg.Text)
        Case 1
            MBobject.ReadCoils(1,txtSlaveAddress.Text,txtStartReg.Text,txtNumbReg.Text)
        Case 2
            MBobject.ReadDiscreteInputs(1,txtSlaveAddress.Text,txtStartReg.Text,txtNumbReg.Text)
        Case 3
            MBobject.ReadInputRegisters(1,txtSlaveAddress.Text,txtStartReg.Text,txtNumbReg.Text)
    End Select
Coidls.PNG
 

Coldrestart

Member
Licensed User
Longtime User
The example was made to only read holding registers.
The datatype is a conversion of returned holding register.

For each type, you should change the transationID like 1,2,3,4 (now you used always 1), when the MBresponseEvent is triggered, in the "case" section you know which request has the number and you can do the corresponding conversion.

In the original example, in the MBresponseEvent, the conversion of the values is done in the conversion functions:

1699553685906.png
 

FabianGS

Member
I saw in the
The example was made to only read holding registers.
The datatype is a conversion of returned holding register.

For each type, you should change the transationID like 1,2,3,4 (now you used always 1), when the MBresponseEvent is triggered, in the "case" section you know which request has the number and you can do the corresponding conversion.

In the original example, in the MBresponseEvent, the conversion of the values is done in the conversion functions:

View attachment 147642
I saw in the example the functionality to read Coils also, depending on the bytes assign it. and a function to read, so as you said, this only works for reading Holding Registers, i just did a chenge in the SelectionBos to do exactly as a holdingRegisters does, read a COIL and so on.
Read COILs:
Public Sub ReadCoils (transactionID As Short,unit As Short, startAddress As Short, numInputs As Short) As Boolean
    ''error when request is send
    If numInputs > 2000 Then
        CallException(transactionID,unit,fctReadCoil,excIllegalDataVal)
        Return False
    End If
    
    Try
        If IsConnected = True Then
            'Send message to the network
            Astream.Write(CreateReadHeader(transactionID,unit,startAddress,numInputs,fctReadCoil))
                
            'To track the send messages en their timeout
            TrackTimeout(transactionID)
            Return True
        Else
            CallException(transactionID,unit,fctReadCoil,excExceptionNotConnected)
            Return False
        End If
        
    Catch
        Log(LastException)
    End Try
    
End Sub
ReadCoil:
    ''Constants for access
    Private const  fctReadCoil As Byte  = 1
    Private const  fctReadDiscreteInputs As Byte= 2
    Private const  fctReadHoldingRegister As Byte = 3
    Private const  fctReadInputRegister As Byte = 4
    Private const  fctWriteSingleCoil As Byte = 5
    Private const  fctWriteSingleRegister As Byte = 6
    Private const  fctWriteMultipleCoils As Byte = 15
    Private const  fctWriteMultipleRegister As Byte = 16
    Private const  fctReadWriteMultipleRegister As Byte = 23 'not supported in this class
    
    
    '''''----------------------------------------------------
 
Top