1use crate::proto::s7::header::{Area, TransportSize};
2
3use crate::error::{Error, Result};
4
5#[derive(Debug, Clone)]
6pub struct TagAddress {
7 pub area: Area,
8 pub db_number: u16,
9 pub byte_offset: u32,
10 pub bit_offset: u8,
11 pub transport: TransportSize,
12 pub element_count: u16,
13}
14
15pub fn parse_tag(tag: &str) -> Result<TagAddress> {
16 let parts: Vec<&str> = tag.splitn(2, ',').collect();
17 if parts.len() != 2 {
18 return Err(Error::PlcError {
19 code: 0,
20 message: format!("invalid tag: {}", tag),
21 });
22 }
23 let area_part = parts[0].to_uppercase();
24 let type_part = parts[1].to_uppercase();
25
26 let (area, db_number) = if let Some(rest) = area_part.strip_prefix("DB") {
27 let n: u16 = rest.parse().map_err(|_| Error::PlcError {
28 code: 0,
29 message: format!("invalid DB number in tag: {}", tag),
30 })?;
31 (Area::DataBlock, n)
32 } else {
33 return Err(Error::PlcError {
34 code: 0,
35 message: format!("unsupported area in tag: {}", tag),
36 });
37 };
38
39 if type_part.starts_with(|c: char| c.is_ascii_digit()) {
43 let bits: Vec<&str> = type_part.split('.').collect();
44 if bits.len() == 2 {
45 let byte_offset: u32 = bits[0].parse().map_err(|_| Error::PlcError {
46 code: 0,
47 message: format!("invalid byte offset in tag: {}", tag),
48 })?;
49 let bit_offset: u8 = bits[1].parse().map_err(|_| Error::PlcError {
50 code: 0,
51 message: format!("invalid bit offset in tag: {}", tag),
52 })?;
53 if bit_offset > 7 {
54 return Err(Error::PlcError {
55 code: 0,
56 message: format!("bit offset must be 0-7: {}", tag),
57 });
58 }
59 return Ok(TagAddress {
60 area,
61 db_number,
62 byte_offset,
63 bit_offset,
64 transport: TransportSize::Bit,
65 element_count: 1,
66 });
67 }
68 }
69
70 let (transport, byte_offset) = if let Some(rest) = type_part.strip_prefix("REAL") {
71 (TransportSize::Real, rest.parse().unwrap_or(0))
72 } else if let Some(rest) = type_part.strip_prefix("DWORD") {
73 (TransportSize::DWord, rest.parse().unwrap_or(0))
74 } else if let Some(rest) = type_part.strip_prefix("DINT") {
75 (TransportSize::DInt, rest.parse().unwrap_or(0))
76 } else if let Some(rest) = type_part.strip_prefix("WORD") {
77 (TransportSize::Word, rest.parse().unwrap_or(0))
78 } else if let Some(rest) = type_part.strip_prefix("INT") {
79 (TransportSize::Int, rest.parse().unwrap_or(0))
80 } else if let Some(rest) = type_part.strip_prefix("BYTE") {
81 (TransportSize::Byte, rest.parse().unwrap_or(0))
82 } else {
83 return Err(Error::PlcError {
84 code: 0,
85 message: format!("unsupported type in tag: {}", tag),
86 });
87 };
88
89 Ok(TagAddress {
90 area,
91 db_number,
92 byte_offset,
93 bit_offset: 0,
94 transport,
95 element_count: 1,
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn parse_db_real() {
105 let tag = parse_tag("DB1,REAL4").unwrap();
106 assert_eq!(tag.db_number, 1);
107 assert_eq!(tag.byte_offset, 4);
108 assert_eq!(tag.transport, TransportSize::Real);
109 }
110
111 #[test]
112 fn parse_db_word() {
113 let tag = parse_tag("DB2,WORD10").unwrap();
114 assert_eq!(tag.db_number, 2);
115 assert_eq!(tag.byte_offset, 10);
116 assert_eq!(tag.transport, TransportSize::Word);
117 }
118
119 #[test]
120 fn parse_db_dint() {
121 let tag = parse_tag("DB70,DINT0").unwrap();
122 assert_eq!(tag.db_number, 70);
123 assert_eq!(tag.byte_offset, 0);
124 assert_eq!(tag.transport, TransportSize::DInt);
125 assert_eq!(tag.bit_offset, 0);
126 }
127
128 #[test]
129 fn parse_db_bit_access() {
130 let tag = parse_tag("DB70,332.0").unwrap();
131 assert_eq!(tag.db_number, 70);
132 assert_eq!(tag.byte_offset, 332);
133 assert_eq!(tag.bit_offset, 0);
134 assert_eq!(tag.transport, TransportSize::Bit);
135 }
136
137 #[test]
138 fn parse_db_bit_access_bit7() {
139 let tag = parse_tag("DB70,332.7").unwrap();
140 assert_eq!(tag.db_number, 70);
141 assert_eq!(tag.byte_offset, 332);
142 assert_eq!(tag.bit_offset, 7);
143 assert_eq!(tag.transport, TransportSize::Bit);
144 }
145
146 #[test]
147 fn parse_db_bit_invalid_bit() {
148 let result = parse_tag("DB70,332.8");
149 assert!(result.is_err());
150 }
151
152 #[test]
153 fn parse_invalid_returns_err() {
154 assert!(parse_tag("NOTVALID").is_err());
155 }
156}