SkillAgentSearch skills...

Melsec

A java library which implements MELSEC Communication protocol (QnA compatible 3E Binary).

Install / Use

/learn @megahoneybadger/Melsec
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

General

A java library which implements MELSEC Communication protocol (QnA compatible 3E Binary).

Releases are available via Maven central. To add a dependency to melsec-client, use:

<dependency>
    <groupId>io.github.megahoneybadger</groupId>
    <artifactId>melsec-client</artifactId>
    <version>0.1</version>
</dependency>

Content

Project consists of the following modules:

  • client - the main library you need to communicate with PLC device
  • eqp - equipment simulator that can be used for tests
  • monitor - console application which allows to connect to a device and test read/write commands
  • tests - contains unit and integration tests

How to build

Firstly get the sources:

git clone https://github.com/megahoneybadger/melsec.git
cd melsec

Secondly compile the project (you will need maven):

mvn compile

Main concepts

Bindings

To communicate with a remote device you will use so-called bindings: typed values that reflect and interpret memory blocks. In other words we are not working with a raw memory (words or bits). Available bindings are:

  • PlcU2 - unsigned short
  • PlcI2 - signed short
  • PlcU4 - unsigned integer
  • PlcI4 - signed integer
  • PlcString - ASCII string
  • PlcStruct - structure
  • PlcBit - boolean

Every binding contains target device code and address. In addition, it may have optional value and id.

var a = new PlcU2( WordDeviceCode.D, 100 );  
var b = new PlcI4( WordDeviceCode.W, 0x200, 200 );  

// You can specify optional id  to differentiate objects
var c = new PlcBit( BitDeviceCode.B, 0, true, "GLASS_EVENT_BIT" );

// Bits and words use different device code families  
var d = new PlcBit( BitDeviceCode.M, 0 );  
  
// String must specify a size in BYTES  
var e = new PlcString( WordDeviceCode.W, 300, 10, "This is a long string", "GLASS_DESCRIPTION" );  
  
var f = PlcStruct  
  .builder( WordDeviceCode.W, 0x100, "Glass" )  
  .u2( 101 )  
  .u2( 27894 )  
  .u2( 31254 ) 
  // we may have offsets in WORDS   
  .offset( 3 )  
  .i2( ( short )-1456 )  
  .offset( 1 ) 
  .i2( ( short )5567 )  
  .string( 4, "helloworld" )  
  .build();

Equipment client

Primary object responsible for communication is an Equipment Client. To make it work you have to create a proper configuration which sets remote device's IP and port.

If you do not have a physical PLC device you can use simulator.

var config = ClientOptions  
    .builder()  
    .address( "127.0.0.1" )  
    .port( 8000 )  
    .loggers(  
        new ConsoleLogger( LogLevel.DEBUG ))  
    .build();  
  
var client = new EquipmentClient( config ); 

// This line initiates connection
client.start();

IO Requests

To read and write bindings you will need to pack them into the requests. Every request may contain a chain of IO operations. Then complete you will receive a response with detailed result information for every binding.

Example #1

var config = ClientOptions  
    .builder()  
    .address( "127.0.0.1" )  
    .port( 8000 )  
    .loggers(  
      new ConsoleLogger( LogLevel.DEBUG ))  
    .build();  
  
 var client = new EquipmentClient( config );  
 client.start();  

 var a = new PlcU2( WordDeviceCode.D, 100 );  
 var b = new PlcU2( WordDeviceCode.D, 200 );  

 var request = IORequest  
    .builder()  
    .read( a, b, new PlcBit( BitDeviceCode.M, 0 ) )  
    .complete( x -> x.items().forEach( y -> System.out.println( y ) ) )  
    .build();  

 // let's establish a connection	   
 Thread.sleep( 500 );  

 client.exec( request );  

 new Scanner( System.in ).nextLine();

Output #1

[melsec][INFO ][10:45:51.051] Client started
[melsec][DEBUG][10:45:51.051] Connection#234 trying to connect to 127.0.0.1:8000
[melsec][INFO ][10:45:51.051] Connection#234 established
[melsec][DEBUG][10:45:52.052] Enqueue mbbr#762 [2w|1b]
[melsec][DEBUG][10:45:52.052] Process mbbr#762 [2w|1b]
[melsec][DEBUG][10:45:52.052] Complete mbbr#762 [2w|1b]
Read [OK] U2 [D100]  125
Read [OK] U2 [D200] 231
Read [OK] bit [M0] 1
    

Example #2

// omit client creation for brevity...

var request = IORequest  
    .builder()  
    .read( new PlcU2( WordDeviceCode.D, -500/*bad address*/ ) )  
    .complete( x -> x.items().forEach( y -> System.out.println( y ) ) )  
    .build();  

// let's establish a connection	   
Thread.sleep( 500 );  

client.exec( request );  

Output #2

[melsec][INFO ][10:53:06.006] Client started
[melsec][DEBUG][10:53:06.006] Connection#816 trying to connect to 127.0.0.1:8000
[melsec][INFO ][10:53:06.006] Connection#816 established
[melsec][DEBUG][10:53:06.006] Enqueue mbbr#50b [1w]
[melsec][DEBUG][10:53:06.006] Process mbbr#50b [1w]
[melsec][ERROR][10:53:06.006] Failed to complete mbbr#50b [1w]. Failed to encode mbbr#50b [1w]. Invalid device address
Read [NG] U2 [D-500] -> Failed to encode mbbr#50b [1w]. Invalid device address

Example #3

// omit client creation for brevity...

var w1 = new PlcString( WordDeviceCode.D, 0, 10, "Hello word" );  
var b1 = new PlcBit( BitDeviceCode.B, 500, true );  
var w2 = new PlcU2( WordDeviceCode.D, 100, "Pressure" );  
var b2 = new PlcBit( BitDeviceCode.B, 501, false, "Reply Bit" );  
  
var request = IORequest  
    .builder()  
    // we want the client to stick to
    // the order: write -> read -> write
    .write( w1, b1 )  
    .read( w2 )  
    .write( b2 )  
    .complete( x -> x.items().forEach( y -> System.out.println( y ) ) )  
    .build();
  
// let's establish a connection	   
Thread.sleep( 500 );  

client.exec( request ); 

Output #3

[melsec][INFO ][10:56:05.005] Client started
[melsec][DEBUG][10:56:05.005] Connection#635 trying to connect to 127.0.0.1:8000
[melsec][INFO ][10:56:05.005] Connection#635 established
[melsec][DEBUG][10:56:06.006] Enqueue mbbw#2d0 [1w], rw#261 [1], mbbr#9d7 [1w], rw#b53 [1]
[melsec][DEBUG][10:56:06.006] Process mbbw#2d0 [1w]
[melsec][DEBUG][10:56:06.006] Complete mbbw#2d0 [1w]
[melsec][DEBUG][10:56:06.006] Process rw#261 [1]
[melsec][DEBUG][10:56:06.006] Complete rw#261 [1]
[melsec][DEBUG][10:56:06.006] Process mbbr#9d7 [1w]
[melsec][DEBUG][10:56:06.006] Complete mbbr#9d7 [1w]
[melsec][DEBUG][10:56:06.006] Process rw#b53 [1]
[melsec][DEBUG][10:56:06.006] Complete rw#b53 [1]
Write [OK] A10 [D0] Hello word
Write [OK] bit [Bx01F4] 1
Read [OK] U2 [D100 Pressure] 2500
Write [OK] bit [Bx01F5 Reply Bit] 0

Example #4

var w1 = new PlcString( WordDeviceCode.D, 0, 2000/*to many points*/, "Hello word" );  
var w2 = new PlcU2( WordDeviceCode.D, 100, "Pressure" );  
  
var request = IORequest  
    .builder()  
    .write( w1 )  
    .read( w2 )  
    .complete( x -> x.items().forEach( y -> System.out.println( y ) ) )  
    .build();  
  
Thread.sleep( 500 );  
  
client.exec( request );

Output #4

[melsec][INFO ][10:58:59.059] Client started
[melsec][DEBUG][10:58:59.059] Connection#816 trying to connect to 127.0.0.1:8000
[melsec][INFO ][10:58:59.059] Connection#816 established
[melsec][DEBUG][10:58:59.059] Enqueue mbbw#5ca [1w], mbbr#9d9 [1w]
[melsec][DEBUG][10:58:59.059] Process mbbw#5ca [1w]
[melsec][ERROR][10:58:59.059] Failed to complete mbbw#5ca [1w]. Failed to encode mbbw#5ca [1w]. Too many points
[melsec][DEBUG][10:58:59.059] Process mbbr#9d9 [1w]
[melsec][DEBUG][10:58:59.059] Complete mbbr#9d9 [1w]
Write [NG] A2000 [D0] Hello word -> Failed to encode mbbw#5ca [1w]. Too many points
Read [OK] U2 [D100 Pressure]

Example #5

var st = PlcStruct
    .builder( WordDeviceCode.W, 0x100, "Employee" )
    .u2( "Age" )
    .u2( "Weight" )
    .u2( "Salary" )
    // pay attention to the offset between fields
    .offset( 3 )
    .string( 20, "Name" )
    .build();

var request = IORequest
    .builder()
    .read( st )
    .complete( x -> x.items().forEach( y -> System.out.println( y ) ) )
    .build();

Thread.sleep( 500 );

client.exec( request );

Output #5

[melsec][INFO ][11:00:37.037] Client started
[melsec][DEBUG][11:00:38.038] Connection#402 trying to connect to 127.0.0.1:8000
[melsec][INFO ][11:00:38.038] Connection#402 established
[melsec][DEBUG][11:00:38.038] Enqueue mbbr#69b [1w]
[melsec][DEBUG][11:00:38.038] Process mbbr#69b [1w]
[melsec][DEBUG][11:00:38.038] Complete mbbr#69b [1w]
Read [OK] struct [Wx0100 Employee]
	U2 [Wx0100 Age] 25
	U2 [Wx0101 Weight] 75
	U2 [Wx0102 Salary] 1000
	A20 [Wx0106] John Smith

Events

You may subscribe to a number of events.

var config = ClientOptions
    .builder()
    .address( "127.0.0.1" )
    .port( 8000 )
    .loggers(
        new ConsoleLogger( LogLevel.DEBUG ))
    .build();

var client = new EquipmentClient( config );

client.events().subscribe( ( IClientStartedEvent ) x -> System.out.println( "client started" ) );
client.events().subscribe( ( IClientStoppedEvent ) x -> System.out.println( "client stopped" ) );

client.events().subscribe( ( IConnectionConnectingEvent ) x -> System.out.println( "connecting" ) );
client.events().subscribe( ( IConnectionEsta
View on GitHub
GitHub Stars12
CategoryDevelopment
Updated1mo ago
Forks2

Languages

Java

Security Score

80/100

Audited on Mar 4, 2026

No findings