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
17pub use device::{AccessType, Device, DeviceType, DeviceData, DeviceBlock, BlockedDeviceData, TypedDevice};
19pub use manager::{SLMPConnectionManager, SLMPWorker, MonitorDevice, PLCData, PollingInterval};
20
21const 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#[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#[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}