1use std::fmt;
2use std::time::Duration;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5pub enum SlmpTransportMode {
6 Tcp,
7 Udp,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11pub enum SlmpFrameType {
12 Frame3E,
13 Frame4E,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub enum SlmpCompatibilityMode {
18 Legacy,
19 Iqr,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
23pub enum SlmpPlcFamily {
24 IqF,
25 IqR,
26 IqL,
27 MxF,
28 MxR,
29 QCpu,
30 LCpu,
31 QnU,
32 QnUDV,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct SlmpPlcFamilyDefaults {
37 pub frame_type: SlmpFrameType,
38 pub compatibility_mode: SlmpCompatibilityMode,
39}
40
41impl SlmpPlcFamily {
42 pub fn canonical_name(self) -> &'static str {
43 match self {
44 Self::IqF => "iq-f",
45 Self::IqR => "iq-r",
46 Self::IqL => "iq-l",
47 Self::MxF => "mx-f",
48 Self::MxR => "mx-r",
49 Self::QCpu => "qcpu",
50 Self::LCpu => "lcpu",
51 Self::QnU => "qnu",
52 Self::QnUDV => "qnudv",
53 }
54 }
55
56 pub fn parse_label(value: &str) -> Option<Self> {
57 match value
58 .trim()
59 .to_ascii_lowercase()
60 .replace(['-', '_'], "")
61 .as_str()
62 {
63 "iqf" => Some(Self::IqF),
64 "iqr" => Some(Self::IqR),
65 "iql" => Some(Self::IqL),
66 "mxf" => Some(Self::MxF),
67 "mxr" => Some(Self::MxR),
68 "qcpu" => Some(Self::QCpu),
69 "lcpu" => Some(Self::LCpu),
70 "qnu" => Some(Self::QnU),
71 "qnudv" => Some(Self::QnUDV),
72 _ => None,
73 }
74 }
75
76 pub fn defaults(self) -> SlmpPlcFamilyDefaults {
77 match self {
78 Self::IqF => SlmpPlcFamilyDefaults {
79 frame_type: SlmpFrameType::Frame3E,
80 compatibility_mode: SlmpCompatibilityMode::Legacy,
81 },
82 Self::IqR | Self::IqL | Self::MxF | Self::MxR => SlmpPlcFamilyDefaults {
83 frame_type: SlmpFrameType::Frame4E,
84 compatibility_mode: SlmpCompatibilityMode::Iqr,
85 },
86 Self::QCpu | Self::LCpu | Self::QnU | Self::QnUDV => SlmpPlcFamilyDefaults {
87 frame_type: SlmpFrameType::Frame3E,
88 compatibility_mode: SlmpCompatibilityMode::Legacy,
89 },
90 }
91 }
92
93 pub fn address_family(self) -> Self {
94 match self {
95 Self::IqL => Self::IqR,
99 other => other,
100 }
101 }
102
103 pub fn uses_iqf_xy_octal(self) -> bool {
104 matches!(self.address_family(), Self::IqF)
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
109pub enum SlmpTraceDirection {
110 Send,
111 Receive,
112}
113
114#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
115pub struct SlmpTrafficStats {
116 pub request_count: u64,
117 pub tx_bytes: u64,
118 pub rx_bytes: u64,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122#[repr(u16)]
123pub enum SlmpCommand {
124 DeviceRead = 0x0401,
125 DeviceWrite = 0x1401,
126 DeviceReadRandom = 0x0403,
127 DeviceWriteRandom = 0x1402,
128 DeviceReadBlock = 0x0406,
129 DeviceWriteBlock = 0x1406,
130 MonitorRegister = 0x0801,
131 Monitor = 0x0802,
132 ReadTypeName = 0x0101,
133 LabelArrayRead = 0x041A,
134 LabelArrayWrite = 0x141A,
135 LabelReadRandom = 0x041C,
136 LabelWriteRandom = 0x141B,
137 MemoryRead = 0x0613,
138 MemoryWrite = 0x1613,
139 ExtendUnitRead = 0x0601,
140 ExtendUnitWrite = 0x1601,
141 RemoteRun = 0x1001,
142 RemoteStop = 0x1002,
143 RemotePause = 0x1003,
144 RemoteLatchClear = 0x1005,
145 RemoteReset = 0x1006,
146 RemotePasswordUnlock = 0x1630,
147 RemotePasswordLock = 0x1631,
148 SelfTest = 0x0619,
149 ClearError = 0x1617,
150}
151
152impl SlmpCommand {
153 pub fn as_u16(self) -> u16 {
154 self as u16
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
159#[repr(u16)]
160pub enum SlmpDeviceCode {
161 SM = 0x0091,
162 SD = 0x00A9,
163 X = 0x009C,
164 Y = 0x009D,
165 M = 0x0090,
166 L = 0x0092,
167 F = 0x0093,
168 V = 0x0094,
169 B = 0x00A0,
170 D = 0x00A8,
171 W = 0x00B4,
172 TS = 0x00C1,
173 TC = 0x00C0,
174 TN = 0x00C2,
175 LTS = 0x0051,
176 LTC = 0x0050,
177 LTN = 0x0052,
178 STS = 0x00C7,
179 STC = 0x00C6,
180 STN = 0x00C8,
181 LSTS = 0x0059,
182 LSTC = 0x0058,
183 LSTN = 0x005A,
184 LCC = 0x0054,
185 LCS = 0x0055,
186 LCN = 0x0056,
187 CS = 0x00C4,
188 CC = 0x00C3,
189 CN = 0x00C5,
190 SB = 0x00A1,
191 SW = 0x00B5,
192 DX = 0x00A2,
193 DY = 0x00A3,
194 Z = 0x00CC,
195 LZ = 0x0062,
196 R = 0x00AF,
197 ZR = 0x00B0,
198 RD = 0x002C,
199 G = 0x00AB,
200 HG = 0x002E,
201}
202
203impl SlmpDeviceCode {
204 pub fn as_u16(self) -> u16 {
205 self as u16
206 }
207
208 pub fn as_u8(self) -> u8 {
209 (self.as_u16() & 0x00FF) as u8
210 }
211
212 pub fn prefix(self) -> &'static str {
213 match self {
214 Self::SM => "SM",
215 Self::SD => "SD",
216 Self::X => "X",
217 Self::Y => "Y",
218 Self::M => "M",
219 Self::L => "L",
220 Self::F => "F",
221 Self::V => "V",
222 Self::B => "B",
223 Self::D => "D",
224 Self::W => "W",
225 Self::TS => "TS",
226 Self::TC => "TC",
227 Self::TN => "TN",
228 Self::LTS => "LTS",
229 Self::LTC => "LTC",
230 Self::LTN => "LTN",
231 Self::STS => "STS",
232 Self::STC => "STC",
233 Self::STN => "STN",
234 Self::LSTS => "LSTS",
235 Self::LSTC => "LSTC",
236 Self::LSTN => "LSTN",
237 Self::LCC => "LCC",
238 Self::LCS => "LCS",
239 Self::LCN => "LCN",
240 Self::CS => "CS",
241 Self::CC => "CC",
242 Self::CN => "CN",
243 Self::SB => "SB",
244 Self::SW => "SW",
245 Self::DX => "DX",
246 Self::DY => "DY",
247 Self::Z => "Z",
248 Self::LZ => "LZ",
249 Self::R => "R",
250 Self::ZR => "ZR",
251 Self::RD => "RD",
252 Self::G => "G",
253 Self::HG => "HG",
254 }
255 }
256
257 pub fn is_hex_addressed(self) -> bool {
258 matches!(
259 self,
260 Self::X | Self::Y | Self::B | Self::W | Self::SB | Self::SW | Self::DX | Self::DY
261 )
262 }
263
264 pub fn is_bit_device(self) -> bool {
265 matches!(
266 self,
267 Self::SM
268 | Self::X
269 | Self::Y
270 | Self::M
271 | Self::L
272 | Self::F
273 | Self::V
274 | Self::B
275 | Self::TS
276 | Self::TC
277 | Self::LTS
278 | Self::LTC
279 | Self::STS
280 | Self::STC
281 | Self::LSTS
282 | Self::LSTC
283 | Self::CS
284 | Self::CC
285 | Self::LCS
286 | Self::LCC
287 | Self::SB
288 | Self::DX
289 | Self::DY
290 )
291 }
292
293 pub fn is_word_device(self) -> bool {
294 matches!(
295 self,
296 Self::SD
297 | Self::D
298 | Self::W
299 | Self::TN
300 | Self::LTN
301 | Self::STN
302 | Self::LSTN
303 | Self::CN
304 | Self::LCN
305 | Self::SW
306 | Self::Z
307 | Self::LZ
308 | Self::R
309 | Self::ZR
310 | Self::RD
311 | Self::G
312 | Self::HG
313 )
314 }
315
316 pub fn is_word_batchable(self) -> bool {
317 matches!(
318 self,
319 Self::SD
320 | Self::D
321 | Self::W
322 | Self::TN
323 | Self::LTN
324 | Self::STN
325 | Self::LSTN
326 | Self::CN
327 | Self::LCN
328 | Self::SW
329 | Self::Z
330 | Self::LZ
331 | Self::R
332 | Self::ZR
333 | Self::RD
334 )
335 }
336
337 pub fn parse_prefix(prefix: &str) -> Option<Self> {
338 match prefix {
339 "LSTS" => Some(Self::LSTS),
340 "LSTC" => Some(Self::LSTC),
341 "LSTN" => Some(Self::LSTN),
342 "LTS" => Some(Self::LTS),
343 "LTC" => Some(Self::LTC),
344 "LTN" => Some(Self::LTN),
345 "STS" => Some(Self::STS),
346 "STC" => Some(Self::STC),
347 "STN" => Some(Self::STN),
348 "SM" => Some(Self::SM),
349 "SD" => Some(Self::SD),
350 "TS" => Some(Self::TS),
351 "TC" => Some(Self::TC),
352 "TN" => Some(Self::TN),
353 "CS" => Some(Self::CS),
354 "CC" => Some(Self::CC),
355 "CN" => Some(Self::CN),
356 "SB" => Some(Self::SB),
357 "SW" => Some(Self::SW),
358 "DX" => Some(Self::DX),
359 "DY" => Some(Self::DY),
360 "LCS" => Some(Self::LCS),
361 "LCC" => Some(Self::LCC),
362 "LCN" => Some(Self::LCN),
363 "LZ" => Some(Self::LZ),
364 "ZR" => Some(Self::ZR),
365 "RD" => Some(Self::RD),
366 "HG" => Some(Self::HG),
367 "X" => Some(Self::X),
368 "Y" => Some(Self::Y),
369 "M" => Some(Self::M),
370 "L" => Some(Self::L),
371 "F" => Some(Self::F),
372 "V" => Some(Self::V),
373 "B" => Some(Self::B),
374 "D" => Some(Self::D),
375 "W" => Some(Self::W),
376 "Z" => Some(Self::Z),
377 "R" => Some(Self::R),
378 "G" => Some(Self::G),
379 _ => None,
380 }
381 }
382}
383
384impl fmt::Display for SlmpDeviceCode {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.write_str(self.prefix())
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
391pub struct SlmpTargetAddress {
392 pub network: u8,
393 pub station: u8,
394 pub module_io: u16,
395 pub multidrop: u8,
396}
397
398impl Default for SlmpTargetAddress {
399 fn default() -> Self {
400 Self {
401 network: 0x00,
402 station: 0xFF,
403 module_io: 0x03FF,
404 multidrop: 0x00,
405 }
406 }
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
410pub struct SlmpDeviceAddress {
411 pub code: SlmpDeviceCode,
412 pub number: u32,
413}
414
415impl SlmpDeviceAddress {
416 pub const fn new(code: SlmpDeviceCode, number: u32) -> Self {
417 Self { code, number }
418 }
419}
420
421impl fmt::Display for SlmpDeviceAddress {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 write!(f, "{}{}", self.code, self.number)
424 }
425}
426
427#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
428pub struct SlmpTypeNameInfo {
429 pub model: String,
430 pub model_code: u16,
431 pub has_model_code: bool,
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
435pub enum SlmpCpuOperationStatus {
436 Unknown,
437 Run,
438 Stop,
439 Pause,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
443pub struct SlmpCpuOperationState {
444 pub status: SlmpCpuOperationStatus,
445 pub raw_status_word: u16,
446 pub raw_code: u8,
447}
448
449#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
450pub struct SlmpBlockRead {
451 pub device: SlmpDeviceAddress,
452 pub points: u16,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
456pub struct SlmpBlockWrite {
457 pub device: SlmpDeviceAddress,
458 pub values: Vec<u16>,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
462pub struct SlmpBlockWriteOptions {
463 pub split_mixed_blocks: bool,
464 pub retry_mixed_on_error: bool,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
468pub struct SlmpRandomReadResult {
469 pub word_values: Vec<u16>,
470 pub dword_values: Vec<u32>,
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
474pub struct SlmpBlockReadResult {
475 pub word_values: Vec<u16>,
476 pub bit_values: Vec<u16>,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
480pub struct SlmpLabelArrayReadPoint {
481 pub label: String,
482 pub unit_specification: u8,
483 pub array_data_length: u16,
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
487pub struct SlmpLabelArrayWritePoint {
488 pub label: String,
489 pub unit_specification: u8,
490 pub array_data_length: u16,
491 pub data: Vec<u8>,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
495pub struct SlmpLabelRandomWritePoint {
496 pub label: String,
497 pub data: Vec<u8>,
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
501pub struct SlmpLabelArrayReadResult {
502 pub data_type_id: u8,
503 pub unit_specification: u8,
504 pub array_data_length: u16,
505 pub data: Vec<u8>,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
509pub struct SlmpLabelRandomReadResult {
510 pub data_type_id: u8,
511 pub spare: u8,
512 pub read_data_length: u16,
513 pub data: Vec<u8>,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
517pub struct SlmpLongTimerResult {
518 pub index: u32,
519 pub device: String,
520 pub current_value: u32,
521 pub contact: bool,
522 pub coil: bool,
523 pub status_word: u16,
524 pub raw_words: Vec<u16>,
525}
526
527#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
528pub struct SlmpExtensionSpec {
529 pub extension_specification: u16,
530 pub extension_specification_modification: u8,
531 pub device_modification_index: u8,
532 pub device_modification_flags: u8,
533 pub direct_memory_specification: u8,
534}
535
536impl Default for SlmpExtensionSpec {
537 fn default() -> Self {
538 Self {
539 extension_specification: 0x0000,
540 extension_specification_modification: 0x00,
541 device_modification_index: 0x00,
542 device_modification_flags: 0x00,
543 direct_memory_specification: 0x00,
544 }
545 }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
549pub struct SlmpQualifiedDeviceAddress {
550 pub device: SlmpDeviceAddress,
551 pub extension_specification: Option<u16>,
552 pub direct_memory_specification: Option<u8>,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
556pub struct SlmpNamedTarget {
557 pub name: String,
558 pub target: SlmpTargetAddress,
559}
560
561#[derive(Debug, Clone)]
562pub struct SlmpConnectionOptions {
563 pub host: String,
564 pub port: u16,
565 pub timeout: Duration,
566 pub plc_family: SlmpPlcFamily,
567 pub frame_type: SlmpFrameType,
568 pub compatibility_mode: SlmpCompatibilityMode,
569 pub target: SlmpTargetAddress,
570 pub transport_mode: SlmpTransportMode,
571 pub monitoring_timer: u16,
572}
573
574impl SlmpConnectionOptions {
575 pub fn new(host: impl Into<String>, family: SlmpPlcFamily) -> Self {
576 let defaults = family.defaults();
577 Self {
578 host: host.into(),
579 port: 1025,
580 timeout: Duration::from_secs(3),
581 plc_family: family,
582 frame_type: defaults.frame_type,
583 compatibility_mode: defaults.compatibility_mode,
584 target: SlmpTargetAddress::default(),
585 transport_mode: SlmpTransportMode::Tcp,
586 monitoring_timer: 0x0010,
587 }
588 }
589
590 pub fn set_plc_family(&mut self, family: SlmpPlcFamily) {
591 let defaults = family.defaults();
592 self.plc_family = family;
593 self.frame_type = defaults.frame_type;
594 self.compatibility_mode = defaults.compatibility_mode;
595 }
596}