44from compas_fab .sensors .exceptions import ProtocolError
55from compas_fab .sensors .exceptions import SensorTimeoutError
66
7- ERROR_CODES = {
8- '000' : 'No error' ,
9- '001' : 'False checksum' ,
10- '002' : 'False command' ,
11- '003' : 'False frame' ,
12- '004' : 'False value or parameter' ,
13- '005' : 'Missed command 000 to begin RS-485 control' ,
14- '006' : 'Out of range' ,
15- '007' : 'Buffer overflow' ,
16- '010' : 'All outputs Off' ,
17- '020' : 'Display Off' ,
18- '99' : 'Argument out of Range' ,
19- '100' : 'Distance out of Range (see FSP)' ,
20- '101' : 'Angle out of Range (see FSP)' ,
21- '102' : 'Flatness out of Range (see FSP)' ,
22- '103' : 'Length out of Range (see FSP)' ,
23- '200' : 'Fatal Error (Reset sensor, Power Off / On)'
24- }
25-
267__all__ = ['PosCon3D' , 'PosConCM' ]
278
289class PosCon3D (SerialSensor ):
@@ -71,20 +52,39 @@ class is a context manager type, so it's best used in combination
7152 1 : 'Low signal' ,
7253 2 : 'No edge' ,
7354 3 : 'Low signal, no edge' ,
74- 4 : 'No signal'
75- }
55+ 4 : 'No signal' }
56+ ERROR_CODES = {
57+ '000' : 'No error' ,
58+ '001' : 'False checksum' ,
59+ '002' : 'False command' ,
60+ '003' : 'False frame' ,
61+ '004' : 'False value or parameter' ,
62+ '005' : 'Missed command 000 to begin RS-485 control' ,
63+ '006' : 'Out of range' ,
64+ '007' : 'Buffer overflow' ,
65+ '010' : 'All outputs Off' ,
66+ '020' : 'Display Off' ,
67+ '99' : 'Argument out of Range' ,
68+ '100' : 'Distance out of Range (see FSP)' ,
69+ '101' : 'Angle out of Range (see FSP)' ,
70+ '102' : 'Flatness out of Range (see FSP)' ,
71+ '103' : 'Length out of Range (see FSP)' ,
72+ '200' : 'Fatal Error (Reset sensor, Power Off / On)' }
7673
7774 def __init__ (self , serial , address ):
7875 super (PosCon3D , self ).__init__ (serial )
7976 self .address = address
8077
78+
8179 def __enter__ (self ):
8280 self .begin ()
8381 return self
8482
83+
8584 def __exit__ (self , * args ):
8685 self .end ()
8786
87+
8888 def begin (self ):
8989 """Locks the sensor to start RS-485 communication.
9090
@@ -95,6 +95,7 @@ def begin(self):
9595 """
9696 return self .send_command (self .address , '000' , '1' )
9797
98+
9899 def end (self ):
99100 """Unlocks the sensor from RS-485 communication.
100101
@@ -105,6 +106,7 @@ def end(self):
105106 """
106107 return self .send_command (self .address , '000' , '0' )
107108
109+
108110 def send_command (self , address , command , data = None ):
109111 """Sends a command to the sensor's address specified. The command
110112 can optionally contain a data string.
@@ -156,12 +158,14 @@ def send_command(self, address, command, data=None):
156158
157159 return None
158160
161+
159162 def format_command (self , address , command , data = None ):
160163 """Formats the command."""
161164 data = data + ',' if data else ''
162165 frame = self .FRAME_HEAD % (address , command , data )
163166 return self .FRAME_TAIL % (frame , self .calculate_checksum (frame ))
164167
168+
165169 def calculate_checksum (self , command ):
166170 """Checks that message is complete."""
167171 code_points = [ord (c ) for c in list (command )]
@@ -171,6 +175,7 @@ def calculate_checksum(self, command):
171175
172176 return str (checksum ).zfill (3 )
173177
178+
174179 def get_payload (self , result ):
175180 """Gets payload."""
176181 data = result .split (',' )[2 :- 1 ]
@@ -180,10 +185,11 @@ def get_payload(self, result):
180185 return data [0 ]
181186 else :
182187 if data [0 ] == 'E' :
183- raise ProtocolError (ERROR_CODES [str (data [1 ])])
188+ raise ProtocolError (self . ERROR_CODES [str (data [1 ])])
184189
185190 return data
186191
192+
187193 def get_address (self ):
188194 """Gets the address of the RS-485 sensors currently connected to the bus. This command
189195 is only really useful when this class is initialized with the broadcast address,
@@ -200,6 +206,7 @@ def get_address(self):
200206 """
201207 return int (self .send_command (self .address , '013' ))
202208
209+
203210 def set_measurement_type (self , measurement_type ):
204211 """Defines the measurement type to use.
205212
@@ -227,6 +234,7 @@ def set_measurement_type(self, measurement_type):
227234
228235 return self .send_command (self .address , '020' , str (self .MEASUREMENT_TYPES .index (measurement_type )))
229236
237+
230238 def set_precision (self , precision ):
231239 """Defines the precision the sensor will use to determine edges:
232240
@@ -251,6 +259,7 @@ def set_precision(self, precision):
251259 raise ProtocolError ('Precision must be 0 (standard), 1 (high) or 2 (very high)' )
252260 return self .send_command (self .address , '040' , str (precision ))
253261
262+
254263 def set_edge_height (self , height ):
255264 """Defines the minimum height of an edge to be detected.
256265
@@ -261,6 +270,7 @@ def set_edge_height(self, height):
261270 """
262271 return self .send_command (self .address , '042' , str (height ))
263272
273+
264274 def get_measurement (self ):
265275 """Retrieves the current measurement of the sensor according to the current settings.
266276
@@ -283,6 +293,7 @@ def get_measurement(self):
283293
284294 return (value , self .QUALITY [quality ])
285295
296+
286297 def get_live_monitor_data (self ):
287298 """Retrieves the distance to the surface in the center of the laser beam and the
288299 angle at which it's found.
@@ -302,26 +313,31 @@ def get_live_monitor_data(self):
302313
303314 return map (float , result )
304315
316+
305317 def reset (self ):
306318 """Resets the sensor to factory settings."""
307319 self .send_command (self .address , '003' )
308320
321+
309322 def activate_flex_mount (self , reference_thickness ):
310323 """Activates the FLEX Mount feature of the sensor to allow positioning it on an
311324 angled installation. The reference thickness is only required if the surface is
312325 uneven and an additional leveling auxiliary plate as been added."""
313326 result = self .send_command (self .address , '062' , str (reference_thickness ))
314327 return map (float , result )
315328
329+
316330 def deactivate_flex_mount (self ):
317331 """Deactivates the FLEX Mount feature."""
318332 self .send_command (self .address , '063' )
319333
334+
320335 def set_flex_mount (self , angle , distance ):
321336 """Sets the FLEX Mount feature to a specific angle and distance."""
322337 result = self .send_command (self .address , '060' , '%.2f,%.2f' % (angle , distance ))
323338 return map (float , result )
324339
340+
325341 def adjust_to_dark_object (self , is_dark_object ):
326342 """Adjusts the sensor to detect darker or lighter surfaces."""
327343 data = '1' if is_dark_object else '0'
@@ -374,27 +390,41 @@ class provides access to the serial interface (RS-485).
374390 'Z_center' : 30 ,
375391 'X_left' : 31 ,
376392 'X_right' : 32 ,
377- 'Z_top' : 33
378- }
393+ 'Z_top' : 33 }
379394 QUALITY = {
380395 0 : 'Valid' ,
381396 1 : 'Low signal' ,
382397 2 : 'No edge' ,
383398 3 : 'Low signal, no edge' ,
384- 4 : 'No signal'
385- }
399+ 4 : 'No signal' }
400+ ERROR_CODES = {
401+ '1' : 'Wrong message type' ,
402+ '2' : 'Wrong payload format' ,
403+ '3' : 'Wrong argument' ,
404+ '4' : 'Wrong argument count' ,
405+ '5' : 'Not enough data' ,
406+ '6' : 'Index do not exist' ,
407+ '7' : 'Index locked' ,
408+ '8' : 'Access not allowed' ,
409+ '9' : 'Not enough memory for encoding' ,
410+ '10' : 'Not possible to encode argument' ,
411+ '11' : 'Application specific error' ,
412+ '12' : 'Wrong state' }
386413
387414 def __init__ (self , serial , address ):
388415 super (PosConCM , self ).__init__ (serial )
389416 self .address = address
390417
418+
391419 def __enter__ (self ):
392420 self .begin ()
393421 return self
394422
423+
395424 def __exit__ (self , * args ):
396425 self .end ()
397426
427+
398428 def begin (self ):
399429 """Locks the sensor to start RS-485 communication.
400430
@@ -405,6 +435,7 @@ def begin(self):
405435 """
406436 return self .send_command (self .address , 'W010' , '0' )
407437
438+
408439 def end (self ):
409440 """Unlocks the sensor from RS-485 communication.
410441
@@ -415,12 +446,14 @@ def end(self):
415446 """
416447 return self .send_command (self .address , 'W010' , '1' )
417448
449+
418450 def format_command (self , address , command , data = None ):
419451 """Formats the command."""
420452 data = data or ''
421453 frame = self .FRAME_HEAD % (str (address ).zfill (2 ), command , data )
422454 return self .FRAME_TAIL % (frame , self .calculate_checksum (frame ))
423455
456+
424457 def calculate_checksum (self , command ):
425458 """Checks that message is complete.
426459
@@ -430,22 +463,27 @@ def calculate_checksum(self, command):
430463 """
431464 return '****'
432465
466+
433467 def get_payload (self , result ):
434468 """Gets payload."""
435469 frame_head = result [:- 6 ]
436470 result_type = frame_head [3 ]
437471
472+ print (frame_head )
473+
438474 if result_type == 'a' :
439475 raise SensorTimeoutError ('Sensor has not completed reading' )
440476
441477 if result_type == 'E' :
442- raise ProtocolError ('Application error, Result=%s' % frame_head )
478+ error_index = frame_head .split (';' )
479+ raise ProtocolError ('Application error, Result=%s' % frame_head + 'Error type: ' + str (self .ERROR_CODES [str (error_index [1 ])]))
443480
444481 if result_type == 'B' :
445482 raise ProtocolError ('Sensor is busy, Result=%s' % frame_head )
446483
447484 return result [5 :- 6 ].split (';' )
448485
486+
449487 def send_command (self , address , command , data = None ):
450488 """Sends a command to the sensor's address specified. The command
451489 can optionally contain a data string.
@@ -491,6 +529,7 @@ def send_command(self, address, command, data=None):
491529
492530 return None
493531
532+
494533 def get_address (self ):
495534 """Gets the address of the RS-485 sensors currently connected to the bus. This command
496535 is only really useful when this class is initialized with the broadcast address,
@@ -508,6 +547,7 @@ def get_address(self):
508547 result = self .send_command (self .address , 'R005' )
509548 return int (result [0 ])
510549
550+
511551 def set_measurement_type (self , measurement_type ):
512552 """Defines the measurement type to use.
513553
@@ -532,6 +572,7 @@ def set_measurement_type(self, measurement_type):
532572
533573 return self .send_command (self .address , 'W020' , str (self .MEASUREMENT_TYPES [measurement_type ]))
534574
575+
535576 def set_precision (self , precision ):
536577 """Defines the precision the sensor will use to determine edges:
537578
@@ -556,6 +597,7 @@ def set_precision(self, precision):
556597 raise ProtocolError ('Precision must be 0 (standard), 1 (high) or 2 (very high)' )
557598 return self .send_command (self .address , 'W033' , str (precision ))
558599
600+
559601 def get_measurement (self ):
560602 """Retrieves the current measurement of the sensor according to the current settings.
561603
@@ -578,33 +620,39 @@ def get_measurement(self):
578620
579621 return (value , self .QUALITY [quality ])
580622
623+
581624 def activate_flex_mount (self ):
582625 """Activates the FLEX Mount feature of the sensor to allow positioning it on an
583626 angled installation. The reference thickness is only required if the surface is
584627 uneven and an additional leveling auxiliary plate as been added."""
585628 result = self .send_command (self .address , 'W035' , '1' )
586629 return map (float , result )
587630
631+
588632 def deactivate_flex_mount (self ):
589633 """Deactivates the FLEX Mount feature."""
590634 result = self .send_command (self .address , 'W035' , '0' )
591635 return map (float , result )
592636
637+
593638 def set_flex_mount (self , angle , distance ):
594639 """Sets the FLEX Mount feature to a specific angle and distance."""
595640 data = '{:.2f};{:.2f}' .format (angle , distance )
596641 return self .send_command (self .address , 'W036' , data )
597642
643+
598644 def teach_flex_mount (self , reference_thickness ):
599645 """Sets the FLEX Mount feature to a specific angle and distance."""
600646 return self .send_command (self .address , 'W037' , str (reference_thickness ))
601647
648+
602649 def adjust_to_dark_object (self , is_dark_object ):
603650 """Adjusts the sensor to detect darker or lighter surfaces."""
604651 data = '1' if is_dark_object else '0'
605652 result = self .send_command (self .address , 'W032' , data )
606653 return map (float , result )
607654
655+
608656 def reset (self ):
609657 """Resets the sensor to factory settings."""
610658 self .send_command (self .address , 'W202' , '0' )
0 commit comments