Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Modbus framework for Rust

License

NotificationsYou must be signed in to change notification settings

alttch/rmodbus

Repository files navigation

A framework to build fast and reliable Modbus-powered applications.

What is rmodbus

rmodbus is not a yet another Modbus client/server. rmodbus is a set of tools toquickly build Modbus-powered applications. Consider rmodbus is arequest/response codec, plus context manager.

rmodbus is a part ofEVA ICS v4 industrialautomation stack andRoboPLC I/O.

Why yet another Modbus lib?

  • rmodbus is transport- and protocol-independent
  • rmodbus is platform independent (no_std is fully supported!)
  • can be easily used in blocking and async (non-blocking) applications
  • tuned for speed and reliability
  • provides a set of tools to easily work with Modbus context
  • supports client/server frame processing for Modbus TCP/UDP, RTU and ASCII
  • server context can be easily managed, imported and exported

So no server is included?

Yes, there is no server included. You build the server by your own. You choosethe transport protocol, technology and everything else. rmodbus just processframes and works with Modbus context.

For synchronous servers and clients (std) we recommend usingRoboPLC ModbusI/O modules.

Here is an example of a simple TCP blocking server:

use std::io::{Read,Write};use std::net::TcpListener;use std::thread;use std::sync::RwLock;use once_cell::sync::Lazy;use rmodbus::{    server::{storage::ModbusStorageFull, context::ModbusContext,ModbusFrame},ModbusFrameBuf,ModbusProto,};staticCONTEXT:Lazy<RwLock<ModbusStorageFull>> =Lazy::new(<_>::default);pubfntcpserver(unit:u8,listen:&str){let listener =TcpListener::bind(listen).unwrap();println!("listening started, ready to accept");for streamin listener.incoming(){        thread::spawn(move ||{println!("client connected");letmut stream = stream.unwrap();loop{letmut buf:ModbusFrameBuf =[0;256];letmut response =Vec::new();// for nostd use FixedVec with alloc [u8;256]if stream.read(&mut buf).unwrap_or(0) ==0{return;}letmut frame =ModbusFrame::new(unit,&buf,ModbusProto::TcpUdp,&mut response);if frame.parse().is_err(){println!("server error");return;}if frame.processing_required{let result =if frame.readonly{                        frame.process_read(&*CONTEXT.read().unwrap())}else{                        frame.process_write(&mut*CONTEXT.write().unwrap())};if result.is_err(){println!("frame processing error");return;}}if frame.response_required{                    frame.finalize_response().unwrap();println!("{:x?}", response.as_slice());if stream.write(response.as_slice()).is_err(){return;}}}});}}

There are also examples for Serial-RTU, Serial-ASCII and UDP inexamplesfolder (if you're reading this text somewhere else, visitrmodbus projectrepository.

Launch the examples as:

cargo run --example appcargo run --example tcpserver

Modbus context

The rule is simple: one standard Modbus context per application. 10k+10k 16-bitregisters and 10k+10k coils are usually more than enough. This takes about59Kbytes of RAM.

rmodbus server context is thread-safe, easy to use and has a lot of functions.

The context must be protected with a mutex/rwlock and every time Modbus contextis accessed, a context mutex must be locked. This slows down performance, butguarantees that the context always has valid data after bulk-sets and afterwrites of long data types. So make sure your application locks context onlywhen required and only for a short period time.

A simple PLC example:

use std::error::Error;use std::fs::File;use std::io::{Read,Write};use rmodbus::server::{storage::ModbusStorageFull, context::ModbusContext};#[path ="../examples/servers/tcp.rs"]mod srv;// put 1 to holding register 1500 to save current context to /tmp/plc1.dat// if the file exists, context will be loaded at the next startfnlooping(){println!("Loop started");loop{// READ WORK MODES ETClet ctx = srv::CONTEXT.read().unwrap();let _param1 = ctx.get_holding(1000).unwrap();let _param2 = ctx.get_holdings_as_f32(1100).unwrap();// ieee754 f32let _param3 = ctx.get_holdings_as_u32(1200).unwrap();// u32let cmd = ctx.get_holding(1500).unwrap();drop(ctx);if cmd !=0{println!("got command code {}", cmd);letmut ctx = srv::CONTEXT.write().unwrap();            ctx.set_holding(1500,0).unwrap();match cmd{1 =>{println!("saving memory context");let _ =save("/tmp/plc1.dat",&ctx).map_err(|_|{eprintln!("unable to save context!");});}                _ =>println!("command not implemented"),}}// ==============================================// DO SOME JOB// ..........// WRITE RESULTSletmut ctx = srv::CONTEXT.write().unwrap();        ctx.set_coil(0,true).unwrap();        ctx.set_holdings_bulk(10,&(vec![10,20])).unwrap();        ctx.set_inputs_from_f32(20,935.77).unwrap();}}fnsave(fname:&str,ctx:&ModbusStorageFull) ->Result<(),Box<dynError>>{let config = bincode::config::standard();letmut file =File::create(fname)?;    file.write(&bincode::encode_to_vec(ctx, config)?)?;    file.sync_all()?;Ok(())}fnload(fname:&str,ctx:&mutModbusStorageFull) ->Result<(),Box<dynError>>{let config = bincode::config::standard();letmut file =File::open(fname)?;letmut data:Vec<u8> =Vec::new();    file.read_to_end(&mut data)?;(*ctx, _) = bincode::decode_from_slice(&data, config)?;Ok(())}fnmain(){// read contextlet unit_id =1;{letmut ctx = srv::CONTEXT.write().unwrap();let _ =load(&"/tmp/plc1.dat",&mut ctx).map_err(|_|{eprintln!("warning: no saved context");});}use std::thread;    thread::spawn(move ||{        srv::tcpserver(unit_id,"localhost:5502");});looping();}

To let the above program communicate with outer world, Modbus server must be upand running in the separate thread, asynchronously or whatever is preferred.

no_std

rmodbus supportsno_std mode. Most of the library code is written the way tosupport bothstd andno_std.

Forno_std, set the dependency as:

rmodbus = {version ="*",default-features =false }

Small storage

The full Modbus storage has 10000 registers of each type, which requires 60000bytes total. For systems with small RAM amount there is a pre-defined smallstorage with 1000 registers:

use rmodbus::server::{storage::ModbusStorageSmall, context::ModbusContext};

Custom-sized storage

Starting from the version 0.7 it is allowed to define storage of any size usinggeneric constants. The generic constants order is: coils, discretes, inputs,holdings.

E.g. let us define a context for 128 coils, 16 discretes, 0 inputs and 100holdings:

use rmodbus::server::{storage::ModbusStorage, context::ModbusContext};let context =ModbusStorage::<128,16,0,100>::new();

Custom server implementation

Starting from the version 0.9 it is allowed to provide custom server implementationby implementinguse rmodbus::server::context::ModbusContext on custom struct.For sample implementation have a look atsrc/server/storage.rs

Custom type representations inu16 sized registers

Starting from version <todo: insert version number here>, you can implementserver::RegisterRepresentable<N> on your own types and useModbusContext::set_*_as_representable andModbusContext::get_*_as_representablemethods to directly store and read your own types in the registers.

Vectors

Some of rmodbus functions use vectors to store result. Different vector types can be used:

  • When thestd feature is enabled (default),std::vec::Vec can be used.

  • With thefixedvec feature,fixedvec::FixedVec can be used.

  • With theheapless feature,heapless::Vec can be used.

  • When thealloc feature is enabled, Rust core allocationalloc::vec::Veccan be used in no-std mode. E.gcargo build --no-default-features --features alloc builds in no-std mode, and supports using core allocationalloc::vec::Vec. Whenstd feature is enabled, thealloc feature isignored.

Modbus client

Modbus client is designed with the same principles as the server: the crategives frame generator / processor, while the frames can be read / written withany source and with any required way.

TCP client Example:

use std::io::{Read,Write};use std::net::TcpStream;use std::time::Duration;use rmodbus::{client::ModbusRequest, guess_response_frame_len,ModbusProto};fnmain(){let timeout =Duration::from_secs(1);// open TCP connectionletmut stream =TcpStream::connect("localhost:5502").unwrap();    stream.set_read_timeout(Some(timeout)).unwrap();    stream.set_write_timeout(Some(timeout)).unwrap();// create request objectletmut mreq =ModbusRequest::new(1,ModbusProto::TcpUdp);    mreq.tr_id =2;// just for test, default tr_id is 1// set 2 coilsletmut request =Vec::new();    mreq.generate_set_coils_bulk(0,&[true,true],&mut request).unwrap();// write request to stream    stream.write(&request).unwrap();// read first 6 bytes of response frameletmut buf =[0u8;6];    stream.read_exact(&mut buf).unwrap();letmut response =Vec::new();    response.extend_from_slice(&buf);let len =guess_response_frame_len(&buf,ModbusProto::TcpUdp).unwrap();// read rest of response frameif len >6{letmut rest =vec![0u8;(len -6)asusize];        stream.read_exact(&mut rest).unwrap();        response.extend(rest);}// check if frame has no Modbus error inside    mreq.parse_ok(&response).unwrap();// get coil values back    mreq.generate_get_coils(0,2,&mut request).unwrap();    stream.write(&request).unwrap();letmut buf =[0u8;6];    stream.read_exact(&mut buf).unwrap();letmut response =Vec::new();    response.extend_from_slice(&buf);let len =guess_response_frame_len(&buf,ModbusProto::TcpUdp).unwrap();if len >6{letmut rest =vec![0u8;(len -6)asusize];        stream.read_exact(&mut rest).unwrap();        response.extend(rest);}letmut data =Vec::new();// check if frame has no Modbus error inside and parse response bools into data vec    mreq.parse_bool(&response,&mut data).unwrap();for iin0..data.len(){println!("{} {}", i, data[i]);}}

About the authors

Bohemia Automation /Altertech is a group of companies with 15+ yearsof experience in the enterprise automation and industrial IoT. Our setupsinclude power plants, factories and urban infrastructure. Largest of them have1M+ sensors and controlled devices and the bar raises higher and higher everyday.


[8]ページ先頭

©2009-2025 Movatter.jp