Skip to main content

omron_fins/
lib.rs

1//! # Omron FINS Protocol Library
2//!
3//! A Rust library for communicating with Omron PLCs using the FINS (Factory Interface Network Service) protocol.
4//!
5//! This is a **protocol-only** library—no business logic, polling, schedulers,
6//! or application-level features. Each call produces exactly 1 request and 1 response.
7//! No automatic retries, caching, or reconnection.
8//!
9//! ## Features
10//!
11//! - **Protocol-only** — focuses solely on FINS protocol implementation
12//! - **Deterministic** — each call produces exactly 1 request and 1 response
13//! - **Type-safe** — memory areas as enums, compile-time validation
14//! - **No panics** — all errors returned as `Result<T, FinsError>`
15//! - **Complete API** — read, write, fill, transfer, run/stop, forced set/reset
16//! - **Utility functions** — bit manipulation, formatting, and conversion helpers
17//!
18//! ## Quick Start
19//!
20//! ```no_run
21//! use omron_fins::{Client, ClientConfig, MemoryArea};
22//! use std::net::Ipv4Addr;
23//!
24//! fn main() -> omron_fins::Result<()> {
25//!     // Connect to PLC at factory default IP (192.168.1.250)
26//!     // Using source_node=1, dest_node=0 (same defaults as Python fins-driver)
27//!     let config = ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0);
28//!     let client = Client::new(config)?;
29//!
30//!     // Read D1 (1 word from DM area)
31//!     let data = client.read(MemoryArea::DM, 1, 1)?;
32//!     println!("D1 = {:?}", data);
33//!
34//!     // Read 10 words from DM100
35//!     let data = client.read(MemoryArea::DM, 100, 10)?;
36//!     println!("DM100-109: {:?}", data);
37//!
38//!     // Write values to DM200
39//!     client.write(MemoryArea::DM, 200, &[0x1234, 0x5678])?;
40//!
41//!     // Read a single bit from CIO 0.05
42//!     let bit = client.read_bit(MemoryArea::CIO, 0, 5)?;
43//!     println!("CIO 0.05 = {}", bit);
44//!
45//!     // Write a single bit
46//!     client.write_bit(MemoryArea::CIO, 0, 5, true)?;
47//!
48//!     Ok(())
49//! }
50//! ```
51//!
52//! ### Equivalent Python (fins-driver)
53//!
54//! This library is compatible with the Python [fins-driver](https://pypi.org/project/fins-driver/) library:
55//!
56//! ```python
57//! from fins import FinsClient
58//!
59//! client = FinsClient(host='192.168.1.250', port=9600)
60//! client.connect()
61//! response = client.memory_area_read('D1')
62//! print(response.data)
63//! client.close()
64//! ```
65//!
66//! ## Memory Areas
67//!
68//! The library supports the following Omron PLC memory areas:
69//!
70//! | Area | Description | Word Access | Bit Access |
71//! |------|-------------|:-----------:|:----------:|
72//! | [`MemoryArea::CIO`] | Core I/O - inputs, outputs, internal relays | ✓ | ✓ |
73//! | [`MemoryArea::WR`] | Work area - temporary work bits/words | ✓ | ✓ |
74//! | [`MemoryArea::HR`] | Holding area - retentive bits/words | ✓ | ✓ |
75//! | [`MemoryArea::DM`] | Data Memory - numeric data storage | ✓ | ✗ |
76//! | [`MemoryArea::AR`] | Auxiliary Relay - system status/control | ✓ | ✓ |
77//!
78//! ## Core Operations
79//!
80//! ### Word Operations
81//!
82//! ```no_run
83//! # use omron_fins::{Client, ClientConfig, MemoryArea};
84//! # use std::net::Ipv4Addr;
85//! # let client = Client::new(ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0)).unwrap();
86//! // Read words
87//! let data = client.read(MemoryArea::DM, 100, 10)?;
88//!
89//! // Write words
90//! client.write(MemoryArea::DM, 200, &[0x1234, 0x5678])?;
91//!
92//! // Fill memory with a value
93//! client.fill(MemoryArea::DM, 100, 50, 0x0000)?;
94//!
95//! // Transfer between areas
96//! client.transfer(MemoryArea::DM, 100, MemoryArea::DM, 200, 10)?;
97//! # Ok::<(), omron_fins::FinsError>(())
98//! ```
99//!
100//! ### Bit Operations
101//!
102//! ```no_run
103//! # use omron_fins::{Client, ClientConfig, MemoryArea};
104//! # use std::net::Ipv4Addr;
105//! # let client = Client::new(ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0)).unwrap();
106//! // Read a bit (CIO 0.05)
107//! let bit = client.read_bit(MemoryArea::CIO, 0, 5)?;
108//!
109//! // Write a bit
110//! client.write_bit(MemoryArea::CIO, 0, 5, true)?;
111//! # Ok::<(), omron_fins::FinsError>(())
112//! ```
113//!
114//! ### Type Helpers
115//!
116//! Read and write multi-word types directly:
117//!
118//! ```no_run
119//! # use omron_fins::{Client, ClientConfig, MemoryArea};
120//! # use std::net::Ipv4Addr;
121//! # let client = Client::new(ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0)).unwrap();
122//! // f32 (REAL) - 2 words
123//! let temp: f32 = client.read_f32(MemoryArea::DM, 100)?;
124//! client.write_f32(MemoryArea::DM, 100, 3.14159)?;
125//!
126//! // f64 (LREAL) - 4 words
127//! let value: f64 = client.read_f64(MemoryArea::DM, 100)?;
128//! client.write_f64(MemoryArea::DM, 100, 3.141592653589793)?;
129//!
130//! // i32 (DINT) - 2 words
131//! let counter: i32 = client.read_i32(MemoryArea::DM, 100)?;
132//! client.write_i32(MemoryArea::DM, 100, -123456)?;
133//!
134//! // String (ASCII) - variable words (2 chars per word)
135//! client.write_string(MemoryArea::DM, 200, "PRODUCT-001")?;
136//! let code: String = client.read_string(MemoryArea::DM, 200, 6)?;
137//! # Ok::<(), omron_fins::FinsError>(())
138//! ```
139//!
140//! ### PLC Control
141//!
142//! ```no_run
143//! # use omron_fins::{Client, ClientConfig, PlcMode};
144//! # use std::net::Ipv4Addr;
145//! # let client = Client::new(ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0)).unwrap();
146//! // Put PLC in run mode
147//! client.run(PlcMode::Monitor)?;
148//!
149//! // Stop PLC
150//! client.stop()?;
151//! # Ok::<(), omron_fins::FinsError>(())
152//! ```
153//!
154//! ## Utility Functions
155//!
156//! The [`utils`] module provides helper functions for bit manipulation and formatting:
157//!
158//! ```
159//! use omron_fins::utils::{get_bit, set_bit, word_to_bits, format_binary, format_hex};
160//!
161//! let value: u16 = 0b1010_0101;
162//!
163//! // Get individual bits
164//! assert!(get_bit(value, 0));   // bit 0 is ON
165//! assert!(!get_bit(value, 1));  // bit 1 is OFF
166//!
167//! // Modify bits
168//! let modified = set_bit(value, 1, true);
169//!
170//! // Convert to bit array
171//! let bits = word_to_bits(value);
172//!
173//! // Format for display
174//! println!("{}", format_binary(value));  // "0b0000_0000_1010_0101"
175//! println!("{}", format_hex(value));     // "0x00A5"
176//! ```
177//!
178//! ## Error Handling
179//!
180//! All operations return [`Result<T, FinsError>`]. The library never panics in public code.
181//!
182//! ```no_run
183//! use omron_fins::{Client, ClientConfig, MemoryArea, FinsError};
184//! use std::net::Ipv4Addr;
185//!
186//! let config = ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0);
187//! let client = Client::new(config)?;
188//!
189//! match client.read(MemoryArea::DM, 100, 10) {
190//!     Ok(data) => println!("Data: {:?}", data),
191//!     Err(FinsError::Timeout) => println!("Communication timeout"),
192//!     Err(FinsError::PlcError { main_code, sub_code }) => {
193//!         println!("PLC error: main=0x{:02X}, sub=0x{:02X}", main_code, sub_code);
194//!     }
195//!     Err(FinsError::InvalidAddressing { reason }) => {
196//!         println!("Invalid addressing: {}", reason);
197//!     }
198//!     Err(e) => println!("Error: {}", e),
199//! }
200//! # Ok::<(), FinsError>(())
201//! ```
202//!
203//! ## Configuration
204//!
205//! ```no_run
206//! use omron_fins::ClientConfig;
207//! use std::net::Ipv4Addr;
208//! use std::time::Duration;
209//!
210//! let config = ClientConfig::new(Ipv4Addr::new(192, 168, 1, 250), 1, 0)
211//!     .with_port(9601)                        // Custom port (default: 9600)
212//!     .with_timeout(Duration::from_secs(5))   // Custom timeout (default: 2s)
213//!     .with_source_network(1)                 // Source network address
214//!     .with_dest_network(2);                  // Destination network address
215//! ```
216//!
217//! ## Design Philosophy
218//!
219//! This library follows the principle of **determinism over abstraction**:
220//!
221//! 1. Each operation does exactly what it says
222//! 2. No magic or implicit behavior
223//! 3. The application has full control over retry, caching, and reconnection
224//! 4. Errors are always explicit and descriptive
225//!
226//! For more details, see the [ARCHITECTURE.md](https://github.com/deviagomendes/omron-fins-rs/blob/main/ARCHITECTURE.md) file.
227
228#![warn(clippy::all)]
229#![warn(missing_docs)]
230#![warn(rust_2018_idioms)]
231
232mod client;
233mod command;
234mod error;
235mod header;
236mod memory;
237mod response;
238mod transport;
239pub mod types;
240pub mod utils;
241
242#[cfg(feature = "napi")]
243mod js_bindings;
244
245// Public re-exports
246pub use client::{Client, ClientConfig};
247pub use command::{
248    Address, FillCommand, ForceSpec, ForcedBit, ForcedSetResetCancelCommand, ForcedSetResetCommand,
249    MultiReadSpec, MultipleReadCommand, PlcMode, ReadBitCommand, ReadWordCommand, RunCommand,
250    StopCommand, TransferCommand, WriteBitCommand, WriteWordCommand, MAX_WORDS_PER_COMMAND,
251};
252pub use error::{fins_error_description, FinsError, Result};
253pub use header::{FinsHeader, NodeAddress, FINS_HEADER_SIZE};
254pub use memory::MemoryArea;
255pub use response::FinsResponse;
256pub use transport::{UdpTransport, DEFAULT_FINS_PORT, DEFAULT_TIMEOUT, MAX_PACKET_SIZE};
257pub use types::{DataType, PlcValue};