@@ -174,104 +174,113 @@ impl EthernetIpClient {
174174 Err ( io:: Error :: other ( "No CIP data item found in CPF" ) )
175175 }
176176
177- pub async fn browse_symbols ( & mut self ) -> io :: Result < Vec < SymbolInfo > > {
177+ pub async fn browse_symbols ( & mut self ) -> Result < Vec < SymbolInfo > , CipError > {
178178 let cip = build_symbol_browse_request ( ) ;
179+
180+ // Transport layer stays io::Error → map into CIP domain
179181 let res = if self . connected {
180- self . send_unit_data ( cip) . await ?
182+ self . send_unit_data ( cip) . await
181183 } else {
182- self . send_rr_data ( cip) . await ?
183- } ;
184+ self . send_rr_data ( cip) . await
185+ }
186+ . map_err ( |_| CipError :: VendorSpecific ( 0xFF ) ) ?;
184187
188+ // CIP response must be at least 4 bytes
185189 if res. len ( ) < 4 {
186- return Err ( io :: Error :: other ( "Malformed CIP response for symbol browse" ) ) ;
190+ return Err ( CipError :: VendorSpecific ( 0xFE ) ) ; // malformed
187191 }
188192
189193 let general_status = res[ 2 ] ;
190194 if general_status != 0 {
191- return Err ( io:: Error :: other ( format ! (
192- "PLC returned error 0x{:02X} for symbol browse" ,
193- general_status
194- ) ) ) ;
195+ return Err ( CipError :: from ( general_status) ) ;
195196 }
196197
197198 let ext_words = res[ 3 ] as usize ;
198199 let data_start = 4 + ext_words * 2 ;
200+
199201 if res. len ( ) < data_start {
200- return Err ( io :: Error :: other ( "Symbol browse response too short" ) ) ;
202+ return Err ( CipError :: VendorSpecific ( 0xFD ) ) ; // too short
201203 }
202204
203205 let symbols = parse_symbol_browse_response ( & res[ data_start..] ) ;
204206 Ok ( symbols)
205207 }
206208
207- pub async fn read_tag ( & mut self , tag : & str ) -> io :: Result < CipValue > {
209+ pub async fn read_tag ( & mut self , tag : & str ) -> Result < CipValue , CipError > {
208210 let cip = build_read_request ( tag, self . slot ) ;
209211
212+ // Transport layer stays io::Error
210213 let res = if self . connected {
211- self . send_unit_data ( cip) . await ?
214+ self . send_unit_data ( cip) . await
212215 } else {
213- self . send_rr_data ( cip) . await ?
214- } ;
216+ self . send_rr_data ( cip) . await
217+ }
218+ . map_err ( |_e| CipError :: VendorSpecific ( 0xFF ) ) ?; // transport failure → CIP error domain
215219
216220 if res. len ( ) < 4 {
217- return Err ( io :: Error :: other ( "Malformed CIP read response" ) ) ;
221+ return Err ( CipError :: VendorSpecific ( 0xFE ) ) ; // malformed response
218222 }
219223
220224 let general_status = res[ 2 ] ;
221225 if general_status != 0 {
222- return Err ( io:: Error :: other ( format ! (
223- "PLC returned error 0x{:02X}" ,
224- general_status
225- ) ) ) ;
226+ return Err ( CipError :: from ( general_status) ) ;
226227 }
227228
228229 let ext_words = res[ 3 ] as usize ;
229230 let data_start = 4 + ext_words * 2 ;
230231 if res. len ( ) < data_start {
231- return Err ( io :: Error :: other ( "CIP read response too short" ) ) ;
232+ return Err ( CipError :: VendorSpecific ( 0xFD ) ) ; // too short
232233 }
233234
234- decode_cip_response ( & res[ data_start..] ) . ok_or ( io:: Error :: other ( "Decode error" ) )
235+ decode_cip_response ( & res[ data_start..] ) . ok_or ( CipError :: VendorSpecific ( 0xFC ) )
236+ // decode error
235237 }
236238
237- pub async fn write_tag ( & mut self , tag : & str , value : CipValue ) -> io :: Result < ( ) > {
239+ pub async fn write_tag ( & mut self , tag : & str , value : CipValue ) -> Result < ( ) , CipError > {
238240 let cip = build_write_request ( tag, & value, self . slot ) ;
241+
239242 let res = if self . connected {
240- self . send_unit_data ( cip) . await ?
243+ self . send_unit_data ( cip) . await
241244 } else {
242- self . send_rr_data ( cip) . await ?
243- } ;
245+ self . send_rr_data ( cip) . await
246+ }
247+ . map_err ( |_| CipError :: VendorSpecific ( 0xFF ) ) ?;
244248
245- decode_write_response ( & res) . map_err ( |status| {
246- io:: Error :: other ( format ! ( "PLC returned write error 0x{:02X}" , status) )
247- } )
249+ match decode_write_response ( & res) {
250+ Ok ( ( ) ) => Ok ( ( ) ) ,
251+ Err ( status) => Err ( CipError :: from ( status) ) ,
252+ }
248253 }
249254
250- pub async fn read_tag_multi ( & mut self , tag : & str , count : usize ) -> io:: Result < Vec < CipValue > > {
255+ pub async fn read_tag_multi (
256+ & mut self ,
257+ tag : & str ,
258+ count : usize ,
259+ ) -> Result < Vec < CipValue > , CipError > {
251260 let cip = build_read_request_count ( tag, count, self . slot ) ;
261+
262+ // Transport layer stays io::Error → map into CIP domain
252263 let res = if self . connected {
253- self . send_unit_data ( cip) . await ?
264+ self . send_unit_data ( cip) . await
254265 } else {
255- self . send_rr_data ( cip) . await ?
256- } ;
266+ self . send_rr_data ( cip) . await
267+ }
268+ . map_err ( |_| CipError :: VendorSpecific ( 0xFF ) ) ?;
257269
258270 if res. len ( ) < 4 {
259- return Err ( io :: Error :: other ( "Malformed CIP read response" ) ) ;
271+ return Err ( CipError :: VendorSpecific ( 0xFE ) ) ; // malformed
260272 }
261273
262274 let general_status = res[ 2 ] ;
263275 if general_status != 0 {
264- return Err ( io:: Error :: other ( format ! (
265- "PLC returned error 0x{:02X}" ,
266- general_status
267- ) ) ) ;
276+ return Err ( CipError :: from ( general_status) ) ;
268277 }
269278
270279 let ext_words = res[ 3 ] as usize ;
271280 let data_start = 4 + ext_words * 2 ;
272281
273282 if res. len ( ) < data_start + 2 {
274- return Err ( io :: Error :: other ( "CIP multi read response too short" ) ) ;
283+ return Err ( CipError :: VendorSpecific ( 0xFD ) ) ; // too short
275284 }
276285
277286 // Extract type ID
@@ -281,27 +290,39 @@ impl EthernetIpClient {
281290 Ok ( crate :: cip:: decode_cip_data_list ( type_id, payload) )
282291 }
283292
284- pub async fn write_tag_multi ( & mut self , tag : & str , values : & [ CipValue ] ) -> io:: Result < ( ) > {
293+ pub async fn write_tag_multi (
294+ & mut self ,
295+ tag : & str ,
296+ values : & [ CipValue ] ,
297+ ) -> Result < ( ) , CipError > {
285298 for ( i, v) in values. iter ( ) . enumerate ( ) {
286299 let indexed = format ! ( "{tag}[{i}]" ) ;
287300 self . write_tag ( & indexed, v. clone ( ) ) . await ?;
288301 }
289302 Ok ( ( ) )
290303 }
291304
292- pub async fn read_tags_msp ( & mut self , tags : & [ & str ] ) -> io:: Result < Vec < MultiResult < CipValue > > > {
305+ pub async fn read_tags_msp (
306+ & mut self ,
307+ tags : & [ & str ] ,
308+ ) -> Result < Vec < MultiResult < CipValue > > , CipError > {
293309 let mut reqs = Vec :: with_capacity ( tags. len ( ) ) ;
294310 for tag in tags {
295311 let cip = build_read_request ( tag, self . slot ) ;
296312 reqs. push ( cip) ;
297313 }
298314
299315 let msp = build_cip_multiple_service_request ( & reqs) ;
316+
317+ // Transport → CIP fallback
300318 let res = if self . connected {
301- self . send_unit_data ( msp) . await ?
319+ self . send_unit_data ( msp) . await
302320 } else {
303- self . send_rr_data ( msp) . await ?
304- } ;
321+ self . send_rr_data ( msp) . await
322+ }
323+ . map_err ( |_| CipError :: VendorSpecific ( 0xFF ) ) ?;
324+
325+ // MSP parser handles CIP status codes internally
305326 Ok ( parse_cip_multiple_service_response ( & res) )
306327 }
307328
@@ -417,36 +438,40 @@ impl EthernetIpClient {
417438 & mut self ,
418439 tag : & str ,
419440 count : u16 ,
420- ) -> io :: Result < ( u16 , Vec < u8 > ) > {
441+ ) -> Result < ( u16 , Vec < u8 > ) , CipError > {
421442 let mut all_data = Vec :: new ( ) ;
422443 let mut offset: u32 = 0 ;
423444 let mut type_id: u16 = 0 ;
424445
425446 loop {
426447 let cip = build_read_fragmented_request ( tag, count, offset, self . slot ) ;
448+
449+ // Transport layer stays io::Error → map into CIP domain
427450 let res = if self . connected {
428- self . send_unit_data ( cip) . await ?
451+ self . send_unit_data ( cip) . await
429452 } else {
430- self . send_rr_data ( cip) . await ?
431- } ;
453+ self . send_rr_data ( cip) . await
454+ }
455+ . map_err ( |_e| CipError :: VendorSpecific ( 0xFF ) ) ?;
432456
433457 if res. len ( ) < 4 {
434- return Err ( io :: Error :: other ( "Fragmented response too short" ) ) ;
458+ return Err ( CipError :: VendorSpecific ( 0xFE ) ) ; // malformed
435459 }
436460
437461 let general_status = res[ 2 ] ;
438462 let ext_words = res[ 3 ] as usize ;
439463 let data_start = 4 + ( ext_words * 2 ) ;
440464
441465 if res. len ( ) < data_start {
442- return Err ( io :: Error :: other ( "No payload in fragment response" ) ) ;
466+ return Err ( CipError :: VendorSpecific ( 0xFD ) ) ; // no payload
443467 }
444468
445469 let mut payload = & res[ data_start..] ;
446470
471+ // First fragment contains Type ID
447472 if offset == 0 {
448473 if payload. len ( ) < 2 {
449- return Err ( io :: Error :: other ( "Missing Type ID in first fragment" ) ) ;
474+ return Err ( CipError :: VendorSpecific ( 0xFC ) ) ; // missing type ID
450475 }
451476 type_id = u16:: from_le_bytes ( [ payload[ 0 ] , payload[ 1 ] ] ) ;
452477 payload = & payload[ 2 ..] ;
@@ -455,21 +480,16 @@ impl EthernetIpClient {
455480 all_data. extend_from_slice ( payload) ;
456481
457482 match general_status {
458- 0x00 => break ,
459- 0x06 => offset = all_data. len ( ) as u32 ,
460- _ => {
461- return Err ( io:: Error :: other ( format ! (
462- "PLC Error: 0x{:02X}" ,
463- general_status
464- ) ) )
465- }
483+ 0x00 => break , // done
484+ 0x06 => offset = all_data. len ( ) as u32 , // partial transfer → continue
485+ other => return Err ( CipError :: from ( other) ) ,
466486 }
467487 }
468488
469489 Ok ( ( type_id, all_data) )
470490 }
471491
472- pub async fn read_array ( & mut self , tag : & str , count : u16 ) -> io :: Result < Vec < CipValue > > {
492+ pub async fn read_array ( & mut self , tag : & str , count : u16 ) -> Result < Vec < CipValue > , CipError > {
473493 let ( type_id, raw) = self . read_tag_fragmented ( tag, count) . await ?;
474494 Ok ( crate :: cip:: decode_cip_data_list ( type_id, & raw ) )
475495 }
0 commit comments