slmp/
lib.rs

1mod commands;
2mod device;
3mod manager;
4
5use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
6use std::sync::Arc;
7use tokio::io::{AsyncReadExt, AsyncWriteExt};
8use tokio::net::{TcpStream};
9use tokio::sync::Mutex;
10use tokio::time::{timeout, Duration};
11
12use commands::read::*;
13use commands::write::*;
14
15use device::DeviceSize;
16
17// Public
18pub use device::{AccessType, Device, DeviceType, DeviceData, DeviceBlock, BlockedDeviceData, TypedDevice};
19pub use manager::{SLMPConnectionManager, SLMPWorker, MonitorDevice, PLCData, PollingInterval};
20
21// Constants
22const BUFSIZE: usize = 1024;
23const CONNECT_TIMEOUT: Duration = Duration::from_secs(1);
24const DEFAULT_SEND_TIMEOUT_SEC: Duration = Duration::from_secs(1);
25const DEFAULT_RECV_TIMEOUT_SEC: Duration = Duration::from_secs(1);
26
27macro_rules! invalidDataError {
28    ($msg:expr) => {
29        std::io::Error::new(std::io::ErrorKind::InvalidData, $msg)
30    };
31}
32macro_rules! check {
33    ($data:expr, $idx:expr, $expected:expr, $msg:expr) => {
34        if $data[$idx] != $expected {
35            return Err(invalidDataError!($msg));
36        }
37    };
38}
39
40#[derive(Copy, Clone, PartialEq, Eq)]
41pub enum CPU {A, Q, R, F, L}
42
43/// Available data type for SLMP communication.
44#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
45pub enum DataType {
46    Bool = 1,
47    U16 = 2,
48    I16 = 3,
49    U32 = 4,
50    I32 = 5,
51    F32 = 6,
52    F64 = 7,
53}
54
55impl DataType {
56    #[inline(always)]
57    const fn byte_size(&self) -> usize {
58        match self {
59            DataType::Bool => 1,
60            DataType::U16 | DataType::I16 => 2,
61            DataType::U32 | DataType::I32 | DataType::F32=> 4,
62            DataType::F64 => 8,
63        }
64    }
65
66    #[inline(always)]
67    const fn device_size(&self) -> DeviceSize {
68        match self {
69            DataType::Bool => DeviceSize::Bit,
70            DataType::U16 | DataType::I16 => DeviceSize::SingleWord,
71            DataType::U32 | DataType::I32 | DataType::F32 => DeviceSize::DoubleWord,
72            DataType::F64 => DeviceSize::QuadrupleWord,
73        }
74    }
75}
76
77/// Available typed-data for SLMP communication.
78/// It is used for all of write requests.
79#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
80pub enum TypedData {
81    Bool(bool),
82    U16(u16),
83    I16(i16),
84    U32(u32),
85    I32(i32),
86    F32(f32),
87    F64(f64),
88}
89
90impl TypedData {
91    #[inline(always)]
92    const fn from(value: &[u8], data_type: DataType) -> Self {
93        match data_type {
94            DataType::Bool => TypedData::Bool(value[0] == 1),
95            DataType::U16 => TypedData::U16(u16::from_le_bytes([value[0], value[1]])),
96            DataType::I16 => TypedData::I16(i16::from_le_bytes([value[0], value[1]])),
97            DataType::U32 => TypedData::U32(u32::from_le_bytes([value[0], value[1], value[2], value[3]])),
98            DataType::I32 => TypedData::I32(i32::from_le_bytes([value[0], value[1], value[2], value[3]])),
99            DataType::F32 => TypedData::F32(f32::from_le_bytes([value[0], value[1], value[2], value[3]])),
100            DataType::F64 => TypedData::F64(f64::from_le_bytes([value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7]])),
101        }
102    }
103
104    #[inline(always)]
105    const fn to_bytes(&self) -> &[u8] {
106        unsafe {
107            match self {
108                TypedData::Bool(true)  => &[1, 0],
109                TypedData::Bool(false) => &[0, 0],
110                TypedData::U16(v) => std::slice::from_raw_parts(v as *const u16 as *const u8, 2),
111                TypedData::I16(v) => std::slice::from_raw_parts(v as *const i16 as *const u8, 2),
112                TypedData::U32(v) => std::slice::from_raw_parts(v as *const u32 as *const u8, 4),
113                TypedData::I32(v) => std::slice::from_raw_parts(v as *const i32 as *const u8, 4),
114                TypedData::F32(v) => std::slice::from_raw_parts(v as *const f32 as *const u8, 4),
115                TypedData::F64(v) => std::slice::from_raw_parts(v as *const f64 as *const u8, 8),
116            }
117        }
118    }
119    
120    #[inline(always)]
121    const fn get_type(&self) -> DataType {
122        match self {
123            TypedData::Bool(_) => DataType::Bool,
124            TypedData::U16(_) => DataType::U16,
125            TypedData::I16(_) => DataType::I16,
126            TypedData::U32(_) => DataType::U32,
127            TypedData::I32(_) => DataType::I32,
128            TypedData::F32(_) => DataType::F32,
129            TypedData::F64(_) => DataType::F64,
130        }
131    }
132}
133
134
135#[derive(Copy, Clone)]
136pub struct SLMP4EConnectionProps {
137    pub ip: &'static str,
138    pub port : u16,
139    pub cpu: CPU,
140    pub serial_id: u16,
141    pub network_id: u8,
142    pub pc_id: u8,
143    pub io_id: u16,
144    pub area_id: u8,
145    pub cpu_timer: u16,
146}
147
148impl TryFrom<SLMP4EConnectionProps> for SocketAddr {
149    type Error = std::io::Error;
150    fn try_from(value: SLMP4EConnectionProps) -> Result<Self, Self::Error> {
151        let ip: IpAddr = value.ip.parse::<IpAddr>()
152            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
153        let port: u16 = value.port;
154        Ok(SocketAddr::new(ip, port))
155    }
156}
157
158#[derive(Clone)]
159pub struct SLMPClient {
160    connection_props: SLMP4EConnectionProps,
161    stream: Arc<Mutex<Option<TcpStream>>>,
162    send_timeout: Duration,
163    recv_timeout: Duration,
164    buffer: [u8; BUFSIZE],
165}
166
167impl SLMP4EConnectionProps {
168    #[inline(always)]
169    const fn generate_header(&self, command_len: u16) -> [u8; 15] {
170        const BLANK_CODE: u8 = 0x00;
171        const REQUEST_CODE: [u8; 2] = [0x54, 0x00];
172
173        let serial_id: [u8; 2] = self.serial_id.to_le_bytes();
174        let io_id: [u8; 2] = self.io_id.to_le_bytes();
175        let cpu_timer: [u8; 2] = self.cpu_timer.to_le_bytes();
176        let command_len: [u8; 2] = command_len.to_le_bytes();
177
178        [
179            REQUEST_CODE[0],
180            REQUEST_CODE[1],
181            serial_id[0],
182            serial_id[1],
183            BLANK_CODE,
184            BLANK_CODE,
185            self.network_id,
186            self.pc_id,
187            io_id[0],
188            io_id[1],
189            self.area_id,
190            command_len[0],
191            command_len[1],
192            cpu_timer[0],
193            cpu_timer[1],
194        ]
195
196    }
197}
198
199impl SLMPClient {
200    pub fn new(connection_props: SLMP4EConnectionProps) -> Self {
201        Self {
202            connection_props,
203            stream: Arc::new(Mutex::new(None)),
204            send_timeout: DEFAULT_SEND_TIMEOUT_SEC,
205            recv_timeout: DEFAULT_RECV_TIMEOUT_SEC,
206            buffer: [0; BUFSIZE],
207        }
208    }
209
210    pub async fn close(&self) {
211        let mut lock = self.stream.lock().await;
212        if let Some(mut stream) = lock.take() {
213            let _ = stream.shutdown().await;
214        }
215    }
216
217    #[allow(dead_code)]
218    pub fn set_send_timeout(&mut self, dur: Duration) {
219        self.send_timeout = dur;
220    }
221
222    #[allow(dead_code)]
223    pub fn set_recv_timeout(&mut self, dur: Duration) {
224        self.recv_timeout = dur;
225    }
226
227    pub async fn connect(&self) -> std::io::Result<()> {
228        self.close().await;
229        
230        let addr: (&str, u16) = (self.connection_props.ip, self.connection_props.port);
231        let socket_addr: SocketAddr = addr
232            .to_socket_addrs()?
233            .next()
234            .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "resolve failed"))?;
235
236        let stream: TcpStream = tokio::time::timeout(CONNECT_TIMEOUT, TcpStream::connect(socket_addr))
237            .await.map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut,"Connect Failed (Timeout)"))
238            .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut,"Connect Failed (Timeout)"))??;
239
240        let mut lock = self.stream.lock().await;
241        *lock = Some(stream);
242        
243        Ok(())
244    }
245
246    async fn request_response(&mut self, msg: &[u8]) -> std::io::Result<&[u8]> {
247        const RECVFRAME_PREFIX_FIXED_LEN: usize = 15;
248
249        let mut stream = self.stream.lock().await;
250        let stream = stream.as_mut().ok_or(std::io::Error::new(std::io::ErrorKind::NotConnected, "Not Connected"))?;
251
252        timeout(self.send_timeout, stream.write_all(&msg)).await
253            .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut,"Send Failed (Timeout)"))??;
254        
255        let bytes_read = timeout(self.recv_timeout, stream.read(&mut self.buffer)).await
256            .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut,"Read Failed (Timeout)"))??;
257
258        self.validate_response(&self.buffer[..bytes_read])?;
259
260        Ok(&self.buffer[RECVFRAME_PREFIX_FIXED_LEN..bytes_read])
261    }
262
263    fn validate_response(&self, data: &[u8]) -> std::io::Result<()> {
264        const FIXED_FRAME_LEN: usize = 13;
265        const RESPONSE_CODE: [u8; 2] = [0xD4, 0x00];
266        const BLANK_CODE: u8 = 0x00;
267        
268        let data_len: usize = data.len();
269        if data_len < FIXED_FRAME_LEN {
270            return Err(invalidDataError!("Received Invalid Length Data"));
271        }
272
273        let data_block_len: usize = u16::from_le_bytes([data[11], data[12]]) as usize;
274        if data_block_len != data_len - FIXED_FRAME_LEN {
275            return Err(invalidDataError!("Received Invalid Data Frame"));
276        }
277
278        let error = u16::from_le_bytes([data[13], data[14]]);
279        if error != 0 {
280            let error_msg = match error {
281                0xC059 => "WrongCommand",
282                0xC05C => "WrongFormat",
283                0xC061 => "WrongLength",
284                0xCEE0 => "Busy",
285                0xCEE1 => "ExceedReqLength",
286                0xCEE2 => "ExceedRespLength",
287                0xCF10 => "ServerNotFound",
288                0xCF20 => "WrongConfigItem",
289                0xCF30 => "PrmIDNotFound",
290                0xCF31 => "NotStartExclusiveWrite",
291                0xCF70 => "RelayFailure",
292                0xCF71 => "TimeoutError",
293                _ => "Unknown Error",
294            };
295            return Err(invalidDataError!(format!("SLMP Returns Error: {error_msg} (0x{error:X})")));
296        }
297        
298        check!(data, 0..2, RESPONSE_CODE, "Received Invalid Response Data");
299        check!(data, 2..4, self.connection_props.serial_id.to_le_bytes(), "Received Invalid Serial ID");
300        check!(data, 4..6, [BLANK_CODE; 2], "Received Invalid Blank Code");
301        check!(data, 6, self.connection_props.network_id, "Received Invalid Network ID");
302        check!(data, 7, self.connection_props.pc_id, "Received Invalid PC ID");
303        check!(data, 8..10, self.connection_props.io_id.to_le_bytes(), "Received Invalid IO ID");
304        check!(data,10, self.connection_props.area_id, "Received Invalid Area ID");
305
306        Ok(())
307    }
308
309    pub async fn bulk_write<'a>(&mut self, start_device: Device, data: &'a [TypedData]) -> std::io::Result<()>
310    {
311        let query = SLMPBulkWriteQuery {
312            connection_props: self.connection_props,
313            start_device,
314            data,
315        };
316        let cmd: SLMPBulkWriteCommand = query.try_into()?;
317
318        self.request_response(&cmd).await.map(|_| ())
319    }
320
321
322    pub async fn random_write<'a>(&mut self, data: &'a [DeviceData]) -> std::io::Result<()>
323    {
324        let mut sorted_data: Vec<DeviceData> = data.iter()
325            .filter(|x| !matches!(x.data, TypedData::F64(_)))
326            .copied()
327            .collect();
328        sorted_data.sort_by_key(|p| p.device.address);
329        sorted_data.sort_by_key(|p| p.data.get_type());
330
331        let bit_access_points: u8 = sorted_data.iter().filter(|x| x.data.get_type().device_size() == DeviceSize::Bit).count() as u8;
332        let single_word_access_points: u8 = sorted_data.iter().filter(|x| x.data.get_type().device_size() == DeviceSize::SingleWord).count() as u8;
333        let double_word_access_points: u8 = sorted_data.iter().filter(|x| x.data.get_type().device_size() == DeviceSize::DoubleWord).count() as u8;
334        
335        let query = SLMPRandomWriteQuery {
336            connection_props: self.connection_props,
337            sorted_data: &sorted_data,
338            bit_access_points,
339            single_word_access_points,
340            double_word_access_points
341        };
342        let cmd: SLMPRandomWriteCommand = query.try_into()?;
343
344        self.request_response(&cmd).await.map(|_| ())
345    }
346
347    pub async fn block_write<'a>(&mut self, data: &'a [BlockedDeviceData<'a>]) -> std::io::Result<()>
348    {
349        let mut sorted_data = data.to_vec();
350        sorted_data.sort_by_key(|p| p.access_type);
351
352        let word_access_points: u8 = sorted_data.iter().filter(|x| x.access_type == AccessType::Word).count() as u8;
353        let bit_access_points: u8 = sorted_data.iter().filter(|x| x.access_type == AccessType::Bit).count() as u8;
354        
355        let query = SLMPBlockWriteQuery {
356            connection_props: self.connection_props,
357            sorted_data: &sorted_data,
358            word_access_points,
359            bit_access_points
360        };
361        let cmd: SLMPBlockWriteCommand = query.try_into()?;
362
363        self.request_response(&cmd).await.map(|_| ())
364    }
365
366    pub async fn bulk_read(&mut self, start_device: Device, device_num: usize, data_type: DataType) ->  std::io::Result<Vec<DeviceData>> 
367    {
368        let query = SLMPBulkReadQuery {
369            connection_props: self.connection_props,
370            start_device,
371            device_num,
372            data_type,
373        };
374        let cmd: SLMPBulkReadCommand = query.try_into()?;
375
376        let recv: &[u8] = &(self.request_response(&cmd).await?);
377
378        match data_type {
379            DataType::Bool => {
380                let device_type = start_device.device_type;
381                let start_address = start_device.address;
382
383                let mut ret: Vec<DeviceData> = Vec::with_capacity(device_num);
384                for (i, data) in recv.iter().flat_map(|&x| [(x >> 4) & 0x01, x & 0x01]).enumerate() {
385                    if i < device_num {
386                        ret.push(DeviceData {
387                            device: Device {device_type, address: start_address + i},
388                            data: TypedData::Bool(if data == 1 { true } else { false })
389                        })
390                    }
391                }
392                Ok(ret)
393            }
394            _ => {
395                let chunk_size = data_type.byte_size();
396                let skip_address = chunk_size / 2;
397                let device_type = start_device.device_type;
398                let start_address = start_device.address;
399
400                let mut ret: Vec<DeviceData> = Vec::with_capacity(device_num);
401                for (i, data) in recv.chunks_exact(chunk_size).enumerate() {
402                    ret.push(DeviceData {
403                        device: Device {device_type, address: start_address + skip_address * i},
404                        data: TypedData::from(data, data_type)
405                    });
406                }
407
408                Ok(ret)
409            }
410        }
411    }
412
413    pub async fn random_read(&mut self, devices: &[TypedDevice]) ->  std::io::Result<Vec<DeviceData>> 
414    {
415        const SINGLE_WORD_BYTELEN: usize = 2;
416        const DOUBLE_WORD_BYTELEN: usize = 4;
417
418        let mut sorted_devices: Vec<TypedDevice> = devices.iter()
419            .filter(|x| !matches!(x.data_type, DataType::F64 | DataType::Bool))
420            .copied()
421            .collect();
422        sorted_devices.sort_by_key(|p| p.device.address);
423        sorted_devices.sort_by_key(|p| p.data_type);
424
425        let single_word_access_points: u8 = sorted_devices.iter().filter(|x| x.data_type.device_size() == DeviceSize::SingleWord).count() as u8;
426        let double_word_access_points: u8 = sorted_devices.iter().filter(|x| x.data_type.device_size() == DeviceSize::DoubleWord).count() as u8;
427        let total_access_points: usize = (single_word_access_points + double_word_access_points) as usize;
428
429        let single_word_data_byte_len: usize = single_word_access_points as usize * SINGLE_WORD_BYTELEN;
430
431        let query = SLMPRandomReadQuery {
432            connection_props: self.connection_props,
433            sorted_devices: &sorted_devices,
434            single_word_access_points,
435            double_word_access_points,
436        };
437        let cmd: SLMPRandomReadCommand = query.try_into()?;
438
439        let recv: &[u8] = &(self.request_response(&cmd).await?);
440
441        let single_word_data: &[u8] = &recv[..single_word_data_byte_len];
442        let double_word_data: &[u8] = &recv[single_word_data_byte_len..];
443
444        let mut ret: Vec<DeviceData> = Vec::with_capacity(total_access_points);
445
446        let mut i = 0;
447
448        for x in single_word_data.chunks_exact(SINGLE_WORD_BYTELEN) {
449            ret.push(DeviceData {
450                device: sorted_devices[i].device,
451                data: TypedData::from(x, sorted_devices[i].data_type),
452            });
453            i += 1;
454        }
455        for x in double_word_data.chunks_exact(DOUBLE_WORD_BYTELEN) {
456            ret.push(DeviceData {
457                device: sorted_devices[i].device,
458                data: TypedData::from(x, sorted_devices[i].data_type),
459            });
460            i += 1;
461        }
462
463        Ok(ret)
464    }
465
466
467    pub async fn block_read(&mut self, device_blocks: &[DeviceBlock]) ->  std::io::Result<Vec<DeviceData>> 
468    {
469        const WORD_RESPONSE_BYTEELEN: usize = 2;
470        const BIT_RESPONSE_BYTEELEN: usize = 1;
471
472        let mut sorted_block = device_blocks.to_vec();
473        sorted_block.sort_by_key(|p| p.start_device.address);
474        sorted_block.sort_by_key(|p| p.access_type);
475
476        let word_access_points: u8 = sorted_block.iter().filter(|x| x.access_type == AccessType::Word).count() as u8;
477        let bit_access_points: u8 = sorted_block.iter().filter(|x| x.access_type == AccessType::Bit).count() as u8;
478
479        let query = SLMPBlockReadQuery {
480            connection_props: self.connection_props,
481            sorted_block: &sorted_block,
482            word_access_points,
483            bit_access_points,
484        };
485        let cmd: SLMPBlockReadCommand = query.try_into()?;
486
487        let recv: &[u8] = &(self.request_response(&cmd).await?);
488
489        let data_num = sorted_block.iter().fold(0, |a, b| a + b.size);
490        let mut ret: Vec<DeviceData> = Vec::with_capacity(data_num);
491
492        let mut read_addr = 0;
493
494        for block in &sorted_block {
495            let start_address = block.start_device.address;
496            let device_type = block.start_device.device_type;
497            let block_bytelen = match block.access_type {
498                AccessType::Word => WORD_RESPONSE_BYTEELEN * block.size,
499                AccessType::Bit => BIT_RESPONSE_BYTEELEN * div_ceil(block.size, 8)
500            };
501            let blocked_data = &recv[read_addr..(read_addr + block_bytelen)];
502            read_addr += block_bytelen;
503
504            match block.access_type {
505                AccessType::Word => {
506                    for (i, x) in blocked_data.chunks_exact(WORD_RESPONSE_BYTEELEN).enumerate() {
507                        ret.push(DeviceData{
508                            device: Device {device_type, address: start_address + i},
509                            data: TypedData::from(x, DataType::U16),
510                        });
511                    }
512                },
513                AccessType::Bit => {
514                    for (i, x) in blocked_data.chunks_exact(BIT_RESPONSE_BYTEELEN).enumerate() {
515                        for (j, y) in u8_to_bits(x[0]).into_iter().enumerate() {
516                            let bit_index = 8 * i + j;
517                            if bit_index < block.size {
518                                ret.push(DeviceData{
519                                    device: Device {device_type, address: start_address + bit_index},
520                                    data: TypedData::Bool(y),
521                                });
522                            }
523                        }
524                    }
525                }
526            }
527        }
528
529        Ok(ret)
530    }
531
532}
533
534
535#[inline(always)]
536pub(crate) const fn div_ceil(a: usize, b: usize) -> usize {
537    (a + b - 1) / b
538}
539
540#[inline(always)]
541pub(crate) const fn u8_to_bits(n: u8) -> [bool; 8] {
542    [ n & 1 != 0, n & 2 != 0, n & 4 != 0, n & 8 != 0, n & 16 != 0, n & 32 != 0, n & 64 != 0, n & 128 != 0 ]
543}
544
545#[inline(always)]
546pub(crate) const fn bits_to_u8(bits: [bool; 8]) -> u8 {
547    ((bits[0] as u8) << 0) |
548    ((bits[1] as u8) << 1) |
549    ((bits[2] as u8) << 2) |
550    ((bits[3] as u8) << 3) |
551    ((bits[4] as u8) << 4) |
552    ((bits[5] as u8) << 5) |
553    ((bits[6] as u8) << 6) |
554    ((bits[7] as u8) << 7)
555}