1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//! # rmodbus - Modbus for Rust
//! 
//! A framework to build fast and reliable Modbus-powered applications.
//! 
//! Cargo crate: https://crates.io/crates/rmodbus
//! 
//! ## What is rmodbus
//! 
//! rmodbus is not a yet another Modbus server. rmodbus is a set of tools to
//! quickly build Modbus-powered applications.
//! 
//! ## 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 server frame processing for Modbus TCP/UDP and RTU
//! 
//! * server context can be easily, managed, imported and exported
//! 
//! ## So the server isn't included?
//! 
//! Yes, there's no server included. You build the server by your own. You choose
//! protocol, technology and everything else. rmodbus just process frames and works
//! with Modbus context.
//! 
//! Here's an example of a simple TCP blocking server:
//! 
//! ```rust,ignore
//! use std::io::{Read, Write};
//! use std::net::TcpListener;
//! use std::thread;
//! 
//! use rmodbus::server::{ModbusFrame, ModbusProto, process_frame};
//! 
//! pub fn tcpserver(unit: u8, listen: &str) {
//!     let listener = TcpListener::bind(listen).unwrap();
//!     println!("listening started, ready to accept");
//!     for stream in listener.incoming() {
//!         thread::spawn(move || {
//!             println!("client connected");
//!             let mut stream = stream.unwrap();
//!             loop {
//!                 let mut buf: ModbusFrame = [0; 256];
//!                 let mut response = Vec::new(); // for nostd use FixedVec with alloc [u8;256]
//!                 if stream.read(&mut buf).unwrap_or(0) == 0 {
//!                     return;
//!                 }
//!                 if process_frame(unit, &buf, ModbusProto::TcpUdp, &mut response).is_err() {
//!                         println!("server error");
//!                         return;
//!                     }
//!                 println!("{:x?}", response.as_slice());
//!                 if !response.is_empty() {
//!                     if stream.write(response.as_slice()).is_err() {
//!                         return;
//!                     }
//!                 }
//!             }
//!         });
//!     }
//! }
//! ```
//! 
//! There are also examples for Serial-RTU and UDP in *examples* folder (if you're
//! reading this text somewhere else, visit [rmodbus project
//! repository](https://github.com/alttch/rmodbus).
//! 
//! ## Modbus context
//! 
//! The rule is simple: one standard Modbus context per application. 10k+10k 16-bit
//! registers and 10k+10k coils are usually more than enough. This takes about
//! 43Kbytes of RAM, but if you need to reduce context size, download library
//! source and change *CONTEXT_SIZE* constant in "context.rs".
//! 
//! rmodbus server context is thread-safe, easy to use and has a lot of functions.
//! 
//! The context is created automatically, as soon as the library is imported. No
//! additional action is required.
//! 
//! Every time Modbus context is accessed, a context mutex must be locked. This
//! slows down a performance, but guarantees that the context always has valid data
//! after bulk-sets or after 32-bit data types were written. So make sure your
//! application locks context only when required and only for a short period time.
//! 
//! There are two groups of context functions:
//! 
//! * High-level API: simple functions like *coil_get* automatically lock the
//!   context but do this every time when called. Use this for testing or if the
//!   speed is not important.
//! 
//! * Advanced way is to use low-level API, lock the context manually and then call
//!   proper functions, like *set*, *set_f32* etc.
//! 
//! Take a look at simple PLC example:
//! 
//! ```rust,ignore
//! use rmodbus::server::context;
//! use std::fs::File;
//! use std::io::prelude::*;
//! use std::sync::MutexGuard;
//! 
//! fn looping() {
//!     loop {
//!         // READ WORK MODES ETC
//!         let mut ctx = context::CONTEXT.lock().unwrap();
//!         let _param1 = context::get(1000, &ctx.holdings).unwrap();
//!         let _param2 = context::get_f32(1100, &ctx.holdings).unwrap(); // ieee754 f32
//!         let _param3 = context::get_u32(1200, &ctx.holdings).unwrap(); // u32
//!         let cmd = context::get(1500, &ctx.holdings).unwrap();
//!         context::set(1500, 0, &mut ctx.holdings).unwrap();
//!         if cmd != 0 {
//!             println!("got command code {}", cmd);
//!             match cmd {
//!                 1 => {
//!                     println!("saving memory context");
//!                     let _ = save("/tmp/plc1.dat", &mut ctx).map_err(|_| {
//!                         eprintln!("unable to save context!");
//!                     });
//!                 }
//!                 _ => println!("command not implemented"),
//!             }
//!         }
//!         drop(ctx);
//!         // ==============================================
//!         // DO SOME JOB
//!         // ..........
//!         // WRITE RESULTS
//!         let mut ctx = context::CONTEXT.lock().unwrap();
//!         context::set(0, true, &mut ctx.coils).unwrap();
//!         context::set_bulk(10, &(vec![10, 20]), &mut ctx.holdings).unwrap();
//!         context::set_f32(20, 935.77, &mut ctx.inputs).unwrap();
//!     }
//! }
//! 
//! fn save(fname: &str, ctx: &MutexGuard<context::ModbusContext>) -> Result<(), std::io::Error> {
//!     let mut file = match File::create(fname) {
//!         Ok(v) => v,
//!         Err(e) => return Err(e),
//!     };
//!     for i in context::context_iter(&ctx) {
//!         match file.write(&[i]) {
//!             Ok(_) => {}
//!             Err(e) => return Err(e),
//!         }
//!     }
//!     match file.sync_all() {
//!         Ok(_) => {}
//!         Err(e) => return Err(e),
//!     }
//!     return Ok(());
//! }
//! ```
//! 
//! To let the above program communicate with outer world, Modbus server must be up
//! and running in the separate thread, asynchronously or whatever is preferred.
//! 
//! ## no_std
//! 
//! rmodbus supports no\_std mode. Most of the library code is written the way to
//! support both std and no\_std.
//! 
//! ### Switching library to no_std
//! 
//! Set dependency as:
//! 
//! ```toml
//! rmodbus = { version = "*", features = ["nostd"] }
//! ```
//! 
//! ### Types and crates in no\_std mode
//! 
//! * To perform context bulk gets and obtain responses from Modbus frame
//!   processing, use [FixedVec](https://crates.io/crates/fixedvec) instead of
//!   std::vec::Vec
//! 
//! * In the no\_std mode, rmodbus context is protected with
//!   [spin](https://crates.io/crates/spin) Mutex instead of std::sync::mutex. Note
//!   that spin MutexGuard doesn't require unwrap() after locking.
//! 
//! ## Single-threaded and async apps
//! 
//! Single-threaded applications can gain up to +60-100% speed boost by removing
//! Modbus context mutex. This can be performed by replacing mutex with a fake one.
//! For the compatibility, the context still need to be "unlocked", however the
//! fake mutex does this instantly and without any CPU overhead.
//! 
//! ```toml
//! rmodbus = { version = "*", features = ["single"] }
//! ```
//! 
//! ## Small context
//! 
//! Default Modbus context has 10000 registers of each type, which requires 42500
//! bytes total. For the systems with small RAM amount it's possible to reduce the
//! context size to the 1000 registers of each type (4250 bytes) with the following
//! feature:
//! 
//! ```toml
//! rmodbus = { version = "*", features = ["smallcontext"] }
//! ```
//! 
//! ## Modbus client
//! 
//! Planned.

#![cfg_attr(feature = "nostd", no_std)]

#[cfg(feature = "nostd")]
#[cfg(not(feature = "single"))]
use spin::{Mutex, MutexGuard};

#[cfg(not(feature = "nostd"))]
#[cfg(not(feature = "single"))]
use std::sync::{Mutex, MutexGuard};

#[cfg(feature = "single")]
include!("fake-mutex.rs");

#[cfg(feature = "nostd")]
include!("lib-nostd.rs");

#[cfg(not(feature = "nostd"))]
include!("lib-std.rs");