Diff of /URBasic/rtde.py [000000] .. [c1b1c5]

Switch to unified view

a b/URBasic/rtde.py
1
'''
2
Python 3.x library to control an UR robot through its TCP/IP interfaces
3
Copyright (C) 2017  Martin Huus Bjerge, Rope Robotics ApS, Denmark
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
and associated documentation files (the "Software"), to deal in the Software without restriction,
7
including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
9
is furnished to do so, subject to the following conditions:
10
11
The above copyright notice and this permission notice shall be included in all copies
12
or substantial portions of the Software.
13
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL "Rope Robotics ApS" BE LIABLE FOR ANY CLAIM,
17
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
20
Except as contained in this notice, the name of "Rope Robotics ApS" shall not be used
21
in advertising or otherwise to promote the sale, use or other dealings in this Software
22
without prior written authorization from "Rope Robotics ApS".
23
'''
24
__author__ = "Martin Huus Bjerge"
25
__copyright__ = "Copyright 2017, Rope Robotics ApS, Denmark"
26
__license__ = "MIT License"
27
28
from pkg_resources import resource_filename
29
import URBasic
30
import threading
31
import socket
32
import struct
33
import select
34
import numpy as np
35
import xml.etree.ElementTree as ET
36
import time
37
import os.path
38
39
DEFAULT_TIMEOUT = 1.0
40
41
class Command:
42
    RTDE_REQUEST_PROTOCOL_VERSION = 86        # ascii V
43
    RTDE_GET_URCONTROL_VERSION = 118          # ascii v
44
    RTDE_TEXT_MESSAGE = 77                    # ascii M
45
    RTDE_DATA_PACKAGE = 85                    # ascii U
46
    RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS = 79   # ascii O
47
    RTDE_CONTROL_PACKAGE_SETUP_INPUTS = 73    # ascii I
48
    RTDE_CONTROL_PACKAGE_START = 83           # ascii S
49
    RTDE_CONTROL_PACKAGE_PAUSE = 80           # ascii P
50
51
52
class ConnectionState:
53
    ERROR = 0
54
    DISCONNECTED = 1
55
    CONNECTED = 2
56
    PAUSED = 3
57
    STARTED = 4
58
59
class RTDE(threading.Thread): #, metaclass=Singleton
60
    '''
61
    Interface to UR robot Real Time Data Exchange interface.
62
    See this site for more detail:
63
    http://www.universal-robots.com/how-tos-and-faqs/how-to/ur-how-tos/real-time-data-exchange-rtde-guide-22229/
64
65
    The constructor takes a UR robot hostname as input and a path to a RTDE configuration file.
66
67
    Input parameters:
68
    host (string):  Hostname or IP of UR Robot (RT CLient server)
69
    conf_filename (string):  Path to xml file describing what channels to activate
70
    logger (URBasis_DataLogging obj): A instance if a logger object if common logging is needed.
71
72
    Example:
73
    rob = URBasic.rtde.RTDE('192.168.56.101', 'rtde_configuration.xml')
74
    rob.close_rtde()
75
    '''
76
77
78
    def __init__(self, robotModel, conf_filename=None):
79
        '''
80
        Constructor see class description for more info.
81
        '''
82
        if(False):
83
            assert isinstance(robotModel, URBasic.robotModel.RobotModel)  ### This line is to get code completion for RobotModel
84
        self.__robotModel = robotModel
85
86
        logger = URBasic.dataLogging.DataLogging()
87
        name = logger.AddEventLogging(__name__,log2Consol=False)
88
        self._logger = logger.__dict__[name]
89
        self.__reconnectTimeout = 600 #Seconds (while in run)
90
        self.__dataSend = RTDEDataObject()
91
        # always use the rtdeCOnfiguration in the packages folder
92
        conf_filename = resource_filename(__name__, 'rtdeConfigurationDefault.xml')
93
        self.__conf_filename = conf_filename
94
        self.__stop_event = True
95
        threading.Thread.__init__(self)
96
        self.__dataEvent = threading.Condition()
97
98
        self.__conn_state = ConnectionState.DISCONNECTED
99
        self.__sock = None
100
        self.__rtde_output_names = None
101
        self.__rtde_output_config = None
102
        self.__rtde_input_names = None
103
        self.__rtde_input_initValues = None
104
        self.__rtde_input_config = None
105
        self.__controllerVersion = None
106
        self.__protocol_version = None
107
        self.__packageCounter = 0
108
        self.start()
109
        self._logger.info('RTDE constructor done')
110
111
112
113
114
    def __connect(self):
115
        '''
116
        Initialize RTDE connection to host and set up data interfaces based on configuration XML.
117
118
        Return value:
119
        success (boolean)
120
        '''
121
        if self.__sock:
122
            return True
123
124
        try:
125
            self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
126
            self.__sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
127
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
128
            self.__sock.settimeout(DEFAULT_TIMEOUT)
129
            self.__sock.connect((self.__robotModel.ipAddress, 30004))
130
            self.__conn_state = ConnectionState.CONNECTED
131
        except (socket.timeout, socket.error):
132
            if self.__sock:
133
                self.sock.close()
134
            self.__sock = None
135
            return False
136
        return True
137
138
    def __disconnect(self):
139
        '''
140
        Close the RTDE connection.
141
        '''
142
        if self.__sock:
143
            self.__sock.close()
144
            self.__sock = None
145
        self.__conn_state = ConnectionState.DISCONNECTED
146
        return True
147
148
    def __isConnected(self):
149
        '''
150
        Returns True if the connection is open.
151
152
        Return value:
153
        open (boolean)
154
        '''
155
        return self.__conn_state > ConnectionState.DISCONNECTED
156
157
    def isRunning(self):
158
        '''
159
        Return True if RTDE interface is running
160
        '''
161
162
        return self.__conn_state >= ConnectionState.STARTED
163
164
    def __getControllerVersion(self):
165
        '''
166
        Returns the software version of the robot controller running the RTDE server.
167
168
        Return values:
169
        major (int)
170
        minor (int)
171
        bugfix (int)
172
        '''
173
        cmd = Command.RTDE_GET_URCONTROL_VERSION
174
        self.__send(cmd)
175
176
    def __negotiateProtocolVersion(self, protocol):
177
        '''
178
        Negotiate the protocol version with the server.
179
        Returns True if the controller supports the specified protocol version.
180
        We recommend that you use this to ensure full compatibility between your
181
        application and future versions of the robot controller.
182
183
        Input parameters:
184
        protocol (int): protocol version number
185
186
        Return value:
187
        success (boolean)
188
        '''
189
        cmd = Command.RTDE_REQUEST_PROTOCOL_VERSION
190
        payload = struct.pack('>H',protocol)
191
        self.__send(cmd, payload)
192
193
    def __setupInput(self, input_variables=None, types=[], initValues=None):
194
        '''
195
        Configure an input package that the external(this) application will send to the robot controller.
196
        An input package is a collection of input input_variables that the external application will provide
197
        to the robot controller in a single update. Variables is a list of variable names and should be
198
        a subset of the names supported as input by the RTDE interface.The list of types is optional,
199
        but if any types are provided it should have the same length as the input_variables list.
200
        The provided types will be matched with the types that the RTDE interface expects and the
201
        function returns None if they are not equal. Multiple input packages can be configured.
202
        The returned InputObject has a reference to the recipe id which is used to identify the
203
        specific input format when sending an update.
204
        If input_variables is empty, xml configuration file is used.
205
206
        Input parameters:
207
        input_variables (list<string> or Str): [Optional] Variable names from the list of possible RTDE inputs
208
        types (list<string> or str): [Optional] Types matching the input_variables
209
210
        Return value:
211
        success (boolean)
212
        '''
213
214
        if input_variables is None:
215
            tree = ET.parse(self.__conf_filename)
216
            root = tree.getroot()
217
218
            #setup data that can be send
219
            recive = root.find('send')
220
            input_variables = []
221
            initValues = []
222
            for child in recive:
223
                input_variables.append(child.attrib['name'])
224
                initValues.append(float(child.attrib['initValue']))
225
226
        cmd = Command.RTDE_CONTROL_PACKAGE_SETUP_INPUTS
227
        if type(input_variables) is list:
228
            payload = ','.join(input_variables)
229
        elif type(input_variables) is str:
230
            payload = input_variables
231
        else:
232
            self._logger.error('Variables must be list of stings or a single string, input_variables is: ' + str(type(input_variables)))
233
            return None
234
235
        self.__rtde_input_names = input_variables
236
        self.__rtde_input_initValues = initValues
237
238
        payload = payload.encode('utf-8')
239
        self.__send(cmd, payload)
240
241
        return True
242
243
    def __setupOutput(self, output_variables=None, types=[]):
244
        '''
245
        Configure an output package that the robot controller will send to the
246
        external(this) application at the control frequency. Variables is a list of
247
        variable names and should be a subset of the names supported as output by the
248
        RTDE interface. The list of types is optional, but if any types are provided
249
        it should have the same length as the output_variables list. The provided types will
250
        be matched with the types that the RTDE interface expects and the function
251
        returns False if they are not equal. Only one output package format can be
252
        specified and hence no recipe id is used for output.
253
        If output_variables is empty, xml configuration file is used.
254
255
        Input parameters:
256
        output_variables (list<string> or str): [Optional] Variable names from the list of possible RTDE outputs
257
        types (list<string> or str): [Optional] Types matching the output_variables
258
259
        Return value:
260
        success (boolean)
261
        '''
262
263
        if output_variables is None:
264
            if not os.path.isfile(self.__conf_filename):
265
                self._logger.error("Configuration file don't exist : " + self.__conf_filename)
266
                return False
267
            tree = ET.parse(self.__conf_filename)
268
            root = tree.getroot()
269
270
            #Setup data to be recived
271
            recive = root.find('receive')
272
            output_variables = ['timestamp']
273
            for child in recive:
274
                output_variables.append(child.attrib['name'])
275
276
277
        cmd = Command.RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS
278
        if type(output_variables) is list:
279
            payload = ','.join(output_variables)
280
        elif type(output_variables) is str:
281
            payload = output_variables
282
        else:
283
            self._logger.error('Variables must be list of stings or a single string, output_variables is: ' + str(type(output_variables)))
284
            return None
285
286
        self.__rtde_output_names = output_variables
287
        payload = payload.encode('utf-8')
288
        self.__send(cmd, payload)
289
        return True
290
291
    def __sendStart(self):
292
        '''
293
        Sends a start command to the RTDE server.
294
        Setup of all inputs and outputs must be done before starting the RTDE interface
295
296
        Return value:
297
        success (boolean)
298
        '''
299
        cmd = Command.RTDE_CONTROL_PACKAGE_START
300
        self.__send(cmd)
301
        return True
302
303
    def __sendPause(self):
304
        '''
305
        Sends a pause command to the RTDE server
306
        When paused it is possible to change the input and output configurations
307
308
        Return value:
309
        success (boolean)
310
        '''
311
        cmd = Command.RTDE_CONTROL_PACKAGE_PAUSE
312
        self.__send(cmd)
313
        return True
314
315
    def sendData(self):
316
        '''
317
        Send the contents of a RTDEDataObject as input to the RTDE server.
318
        Returns True if successful.
319
320
        Return value:
321
        success (boolean)
322
        '''
323
        if self.__conn_state != ConnectionState.STARTED:
324
            self._logger.error('Cannot send when RTDE is inactive')
325
            return
326
        #if not (self.__rtde_input_config.names.has_key(self.__dataSend.recipe_id)):
327
        #    self._logger.error('Input configuration id not found: ' + str(self.__dataSend.recipe_id))
328
        #    return
329
        if self.__robotModel.StopRunningFlag():
330
            self._logger.info('"sendData" send ignored due to "stopRunningFlag" True')
331
            return
332
        #config = self.__rtde_input_config[self.__dataSend.recipe_id]
333
        config = self.__rtde_input_config
334
        return self.__send(Command.RTDE_DATA_PACKAGE, config.pack(self.__dataSend))
335
336
    def setData(self, variable_name, value):
337
        '''
338
        Set data to be send to the robot
339
        Object is locked while updating to avoid sending half updated values,
340
        hence send all values as two lists of equal lengths
341
342
        Input parameters:
343
        variable_name (List/str):  Variable name from the list of possible RTDE inputs
344
        value (list/int/double)
345
346
        Return value:
347
        Status (Bool): True=Data sucesfull updated, False=Data not updated
348
        '''
349
350
        #check if input is list of equal length
351
        if type(variable_name) is list:
352
            if type(variable_name) != type(value):
353
                raise ValueError("RTDE " + str(variable_name) + " is not type of " + str(value))
354
                #return False
355
            if len(variable_name) != len(value):
356
                raise ValueError("List of RTDE Output values does not have same length as list of variable names")
357
                #return False
358
            for ii in range(len(value)):
359
                if self.hasattr(self.__rtde_input_config.names, variable_name[ii]):
360
                    self.__dataSend.__dict__[variable_name[ii]] = value[ii]
361
                else:
362
                    raise ValueError(str(variable_name[ii]) + " not found in RTDE OUTPUT config")
363
                    #return False
364
365
        else:
366
            if variable_name in self.__rtde_input_config.names:
367
                self.__dataSend.__dict__[variable_name] = value
368
            else:
369
                raise ValueError(str(variable_name) + " not found in RTDE OUTPUT config")
370
371
    def __send(self, command, payload=bytes()):
372
        '''
373
        Send command and data (payload) to Robot Controller
374
        and receive the respond from the Robot Controller.
375
376
        Input parameters:
377
        cmd (int)
378
        payload (bytes)
379
380
        Return value:
381
        success (boolean)
382
        '''
383
        fmt = '>HB'
384
        size = struct.calcsize(fmt) + len(payload)
385
        buf = struct.pack(fmt, size, command) + payload
386
        if self.__sock is None:
387
            self._logger.debug('Unable to send: not connected to Robot')
388
            return False
389
390
        (_, writable, _) = select.select([], [self.__sock], [], DEFAULT_TIMEOUT)
391
        if len(writable):
392
            self.__sock.sendall(buf)
393
            return True
394
        else:
395
            self._logger.info("RTDE disconnected")
396
            self.__disconnect()
397
            return False
398
399
    def __receive(self):
400
        byte_buffer = bytes()
401
402
        (readable, _, _) = select.select([self.__sock], [], [], DEFAULT_TIMEOUT)
403
        if (len(readable)):
404
            more = self.__sock.recv(4096)  # TODO: MAKE THIS 4096 instead of 16384
405
            if len(more) == 0:
406
                self._logger.info("RTDE disconnected")
407
                self.__disconnect()
408
                return None
409
            byte_buffer +=  more
410
411
        while len(byte_buffer) >= 3:
412
            (packet_size, packet_command) = struct.unpack_from('>HB', byte_buffer)
413
            buffer_length = len(byte_buffer)
414
415
            if ((buffer_length) >= packet_size):
416
                packet, byte_buffer = byte_buffer[3:packet_size], byte_buffer[packet_size:]
417
                data = self.__decodePayload(packet_command, packet)
418
419
                if(packet_command == Command.RTDE_GET_URCONTROL_VERSION):
420
                    self.__verifyControllerVersion(data)
421
                elif(packet_command == Command.RTDE_REQUEST_PROTOCOL_VERSION):
422
                    self.__verifyProtocolVersion(data)
423
                elif(packet_command == Command.RTDE_CONTROL_PACKAGE_SETUP_INPUTS):
424
                    self.__rtde_input_config = data
425
                    self.__rtde_input_config.names = self.__rtde_input_names
426
                    #self.__rtde_input_config[self.__rtde_input_config.id] = self.__rtde_input_config
427
                    self.__dataSend = RTDEDataObject.create_empty(self.__rtde_input_names, self.__rtde_input_config.id)
428
                    if self.__rtde_input_initValues is not None:
429
                        for ii in range(len(self.__rtde_input_config.names)):
430
                            if 'UINT8' == self.__rtde_input_config.types[ii]:
431
                                self.setData(self.__rtde_input_config.names[ii], int(self.__rtde_input_initValues[ii]))
432
                            elif 'UINT32' == self.__rtde_input_config.types[ii]:
433
                                self.setData(self.__rtde_input_config.names[ii], int(self.__rtde_input_initValues[ii]))
434
                            elif 'INT32' == self.__rtde_input_config.types[ii]:
435
                                self.setData(self.__rtde_input_config.names[ii], int(self.__rtde_input_initValues[ii]))
436
                            elif 'DOUBLE' == self.__rtde_input_config.types[ii]:
437
                                self.setData(self.__rtde_input_config.names[ii], (self.__rtde_input_initValues[ii]))
438
                            else:
439
                                self._logger.error('Unknown data type')
440
441
                elif(packet_command == Command.RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS):
442
                    self.__rtde_output_config = data
443
                    self.__rtde_output_config.names = self.__rtde_output_names
444
                elif(packet_command == Command.RTDE_CONTROL_PACKAGE_START):
445
                    self._logger.info('RTDE started')
446
                    self.__conn_state = ConnectionState.STARTED
447
                elif(packet_command == Command.RTDE_CONTROL_PACKAGE_PAUSE):
448
                    self._logger.info('RTDE paused')
449
                    self.__conn_state = ConnectionState.PAUSED
450
                elif(packet_command == Command.RTDE_DATA_PACKAGE):
451
                    self.__updateModel(data)
452
                elif(packet_command == 0):
453
                    byte_buffer = bytes()
454
            else:  # TODO: make this so that it still checks the packet content
455
                print("skipping package - unexpected packet_size - length: " , str(len(byte_buffer)), str(packet_size), str(packet_command))
456
                byte_buffer = bytes()
457
458
        if len(byte_buffer) != 0:
459
            self._logger.warning('skipping package - not a package but buffer was not empty')
460
            byte_buffer = bytes()
461
462
    def __updateModel(self, rtde_data_package):
463
        self.__packageCounter = self.__packageCounter + 1
464
        #print("got a rtde package nr " + str(self.__packageCounter))
465
        #print("RTDe Package keys:", rtde_data_package.keys())
466
        if(self.__packageCounter % 1000 == 0):
467
            self._logger.info("Total packages: " + str(self.__packageCounter))
468
        if(self.__robotModel.dataDir['timestamp'] != None):
469
            delta = rtde_data_package['timestamp'] - self.__robotModel.dataDir['timestamp']
470
            if(delta > 0.00800001):
471
                self._logger.error("Lost some RTDE at " + str(rtde_data_package['timestamp']) + " - " + str(delta*1000) + " milliseconds since last package")
472
        for tagname in rtde_data_package.keys():
473
            self.__robotModel.dataDir[tagname] = rtde_data_package[tagname]
474
475
    def __verifyControllerVersion(self, data):
476
        self.__controllerVersion = data
477
        (major, minor, bugfix, build) = self.__controllerVersion
478
        if major and minor and bugfix:
479
            self._logger.info('Controller version: ' + str(major) + '.' + str(minor) + '.' + str(bugfix) + '-' + str(build))
480
            if major <= 3 and minor <= 2 and bugfix < 19171:
481
                self._logger.error("Please upgrade your controller to minimum version 3.2.19171")
482
                raise ValueError("Please upgrade your controller to minimum version 3.2.19171")
483
484
    def __verifyProtocolVersion(self, data):
485
        self.__protocol_version = data
486
        if(self.__protocol_version != 1):
487
            raise ValueError("We only support protocol version 1 at the moment")
488
489
    def __decodePayload(self, cmd, payload):
490
        #print(cmd)
491
        '''
492
        Decode the package received from the Robot
493
        payload (bytes)
494
495
        Return value(s):
496
        Output from Robot controller (type is depended on the cmd value)
497
        '''
498
        if cmd == Command.RTDE_REQUEST_PROTOCOL_VERSION:
499
            if len(payload) != 1:
500
                self._logger.error('RTDE_REQUEST_PROTOCOL_VERSION: Wrong payload size')
501
                return None
502
            return struct.unpack_from('>B', payload)[0]
503
504
        elif cmd == Command.RTDE_GET_URCONTROL_VERSION:
505
            if 12 == len(payload):
506
                return np.append(np.array(struct.unpack_from('>III', payload)), 0)
507
            elif 16 == len(payload):
508
                return np.array(struct.unpack_from('>IIII', payload))
509
            else:
510
                self._logger.error('RTDE_GET_URCONTROL_VERSION: Wrong payload size')
511
                return None
512
513
514
        elif cmd == Command.RTDE_TEXT_MESSAGE:
515
            if len(payload) < 1:
516
                self._logger.error('RTDE_TEXT_MESSAGE: No payload')
517
                return None
518
            EXCEPTION_MESSAGE = 0
519
            ERROR_MESSAGE = 1
520
            WARNING_MESSAGE = 2
521
            INFO_MESSAGE = 3
522
            fmt = ">" + str(len(payload)) + "B"
523
            out = struct.unpack_from(fmt, payload)
524
            level = out[0]
525
            message = ''.join(map(chr,out[1:]))
526
            if(level == EXCEPTION_MESSAGE or
527
               level == ERROR_MESSAGE):
528
                self._logger.error('Server message: ' + message)
529
            elif level == WARNING_MESSAGE:
530
                self._logger.warning('Server message: ' + message)
531
            elif level == INFO_MESSAGE:
532
                self._logger.info('Server message: ' + message)
533
534
        elif cmd == Command.RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS:
535
            if len(payload) < 1:
536
                self._logger.error('RTDE_CONTROL_PACKAGE_SETUP_OUTPUTS: No payload')
537
                return None
538
            has_recipe_id = False
539
            output_config = RTDE_IO_Config.unpack_recipe(payload, has_recipe_id)
540
            return output_config
541
542
        elif cmd == Command.RTDE_CONTROL_PACKAGE_SETUP_INPUTS:
543
            if len(payload) < 1:
544
                self._logger.error('RTDE_CONTROL_PACKAGE_SETUP_INPUTS: No payload')
545
                return None
546
            has_recipe_id = True
547
            input_config = RTDE_IO_Config.unpack_recipe(payload, has_recipe_id)
548
            return input_config
549
550
        elif cmd == Command.RTDE_CONTROL_PACKAGE_START:
551
            if len(payload) != 1:
552
                self._logger.error('RTDE_CONTROL_PACKAGE_START: Wrong payload size')
553
                return None
554
            return bool(struct.unpack_from('>B', payload)[0])
555
556
        elif cmd == Command.RTDE_CONTROL_PACKAGE_PAUSE:
557
            if len(payload) != 1:
558
                self._logger.error('RTDE_CONTROL_PACKAGE_PAUSE: Wrong payload size')
559
                return None
560
            return bool(struct.unpack_from('>B', payload)[0])
561
562
        elif cmd == Command.RTDE_DATA_PACKAGE:
563
            if self.__rtde_output_config is None:
564
                self._logger.error('RTDE_DATA_PACKAGE: Missing output configuration')
565
                return None
566
            output = self.__rtde_output_config.unpack(payload)
567
            return output
568
569
        else:
570
            self._logger.error('Unknown RTDE command type: ' + chr(cmd) + str(cmd))
571
572
573
    def __listEquals(self, l1, l2):
574
        if len(l1) != len(l2):
575
            return False
576
        for i in range(len((l1))):
577
            if l1[i] != l2[i]:
578
                return False
579
        return True
580
581
    def __wait(self):
582
        '''Wait while the data receiving thread is receiving a new data set.'''
583
        cnt = 0
584
        while self.__conn_state < ConnectionState.STARTED:
585
            time.sleep(1)
586
            cnt +=1
587
            if cnt>5:
588
                self._logger.warning('wait_rtde timed out while RTDE interface not running')
589
                return False
590
591
        with self.__dataEvent:
592
            self.__dataEvent.wait()
593
        return True
594
595
596
597
    '''Threading Data receive'''
598
    def close(self):
599
        if self.__stop_event is False:
600
            self.__stop_event = True
601
            self.__wait()
602
            self.join()
603
            self.__disconnect()
604
605
    def run(self):
606
        self.__stop_event = False
607
        t0 = time.time()
608
        while (time.time()-t0<self.__reconnectTimeout) and self.__conn_state != ConnectionState.STARTED:
609
            self.__connect()
610
            self.__disconnect()
611
            self.__connect()
612
            self.__getControllerVersion()
613
            self.__receive()
614
            self.__negotiateProtocolVersion(1)
615
            self.__receive()
616
            self.__setupOutput()
617
            self.__receive()
618
            self.__setupInput()
619
            self.__receive()
620
            self.__sendStart()
621
            self.__receive()
622
            #time.sleep(0.5)
623
        if self.__conn_state != ConnectionState.STARTED:
624
            self._logger.error("RTDE interface not able to connect and timed out!")
625
            return
626
627
        while (not self.__stop_event) and (time.time()-t0<self.__reconnectTimeout):
628
            try:
629
                #self.__receive(Command.RTDE_DATA_PACKAGE)
630
                #startTime = time.time()
631
                self.__receive()
632
                t0 = time.time()
633
                #delta = t0-startTime
634
                #print("Time to recieve: " + str(delta))
635
            except Exception:
636
                if self.__conn_state >= ConnectionState.STARTED:
637
                    self.__conn_state = ConnectionState.ERROR
638
                    self._logger.error("RTDE interface stopped running")
639
640
                self.__sendPause()
641
                if not self.__sendStart():
642
                    self.__disconnect()
643
                    time.sleep(1)
644
                    self.__connect()
645
                    self.__setupOutput()
646
                    self.__setupInput()
647
                    self.__sendStart()
648
649
                if self.__conn_state == ConnectionState.STARTED:
650
                    self._logger.info("RTDE interface restarted")
651
                else:
652
                    self._logger.warning("RTDE reconnection failed!")
653
654
        self.__sendPause()
655
        with self.__dataEvent:
656
            self.__dataEvent.notifyAll()
657
        self._logger.info("RTDE interface is stopped")
658
659
660
class RTDE_IO_Config(object):
661
    __slots__ = ['id', 'names', 'types', 'fmt']
662
    @staticmethod
663
    def unpack_recipe(buf, has_recipe_id):
664
        rmd = RTDE_IO_Config();
665
        if has_recipe_id:
666
            rmd.id = struct.unpack_from('>B', buf)[0]
667
            fmt = ">" + str(len(buf)) + "B"
668
            buf = struct.unpack_from(fmt, buf)
669
            buf = ''.join(map(chr,buf[1:]))
670
            rmd.types = buf.split(',')
671
            rmd.fmt = '>B'
672
        else:
673
            fmt = ">" + str(len(buf)) + "B"
674
            buf = struct.unpack_from(fmt, buf)
675
            buf = ''.join(map(chr,buf[:]))
676
            rmd.types = buf.split(',')
677
            rmd.fmt = '>'
678
        for i in rmd.types:
679
            if i=='INT32':
680
                rmd.fmt += 'i'
681
            elif i=='UINT32':
682
                rmd.fmt += 'I'
683
            elif i=='VECTOR6D':
684
                rmd.fmt += 'd'*6
685
            elif i=='VECTOR3D':
686
                rmd.fmt += 'd'*3
687
            elif i=='VECTOR6INT32':
688
                rmd.fmt += 'i'*6
689
            elif i=='VECTOR6UINT32':
690
                rmd.fmt += 'I'*6
691
            elif i=='DOUBLE':
692
                rmd.fmt += 'd'
693
            elif i=='UINT64':
694
                rmd.fmt += 'Q'
695
            elif i=='UINT8':
696
                rmd.fmt += 'B'
697
            elif i=='IN_USE':
698
                raise ValueError('An input parameter is already in use.')
699
            else:
700
                raise ValueError('Unknown data type: ' + i)
701
        return rmd
702
703
    def pack(self, state):
704
        l = state.pack(self.names, self.types)
705
        return struct.pack(self.fmt, *l)
706
707
    def unpack(self, data):
708
        li =  struct.unpack_from(self.fmt, data)
709
        return RTDEDataObject.unpack(li, self.names, self.types)
710
711
class RTDEDataObject(object):
712
    '''
713
    Data container for data send to or received from the Robot Controller.
714
    The Object will have attributes for each of that data tags received or send.
715
    e.g.  obj.actual_digital_output_bits
716
    '''
717
    recipe_id = None
718
    def pack(self, names, types):
719
        if len(names) != len(types):
720
            raise ValueError('List sizes are not identical.')
721
        l = []
722
        if(self.recipe_id is not None):
723
            l.append(self.recipe_id)
724
        for i in range(len(names)):
725
            if self.__dict__[names[i]] is None:
726
                raise ValueError('Uninitialized parameter: ' + names[i])
727
            if types[i].startswith('VECTOR'):
728
                l.extend(self.__dict__[names[i]])
729
            else:
730
                l.append(self.__dict__[names[i]])
731
        return l
732
733
    @staticmethod
734
    def unpack(data, names, types):
735
        if len(names) != len(types):
736
            raise ValueError('List sizes are not identical.')
737
        obj = dict()
738
        offset = 0
739
        for i in range(len(names)):
740
            obj[names[i]] = RTDEDataObject.unpack_field(data, offset, types[i])
741
            offset += RTDEDataObject.get_item_size(types[i])
742
        return obj
743
744
    @staticmethod
745
    def create_empty(names, recipe_id):
746
        obj = RTDEDataObject()
747
        for i in range(len(names)):
748
            obj.__dict__[names[i]] = None
749
        obj.recipe_id = recipe_id
750
        return obj
751
752
    @staticmethod
753
    def get_item_size(data_type):
754
        if data_type.startswith('VECTOR6'):
755
            return 6
756
        elif data_type.startswith('VECTOR3'):
757
            return 3
758
        return 1
759
760
    @staticmethod
761
    def unpack_field(data, offset, data_type):
762
        size = RTDEDataObject.get_item_size(data_type)
763
        if(data_type == 'VECTOR6D' or
764
           data_type == 'VECTOR3D'):
765
            return np.array([float(data[offset+i]) for i in range(size)])
766
        elif(data_type == 'VECTOR6UINT32'):
767
            return np.array([int(data[offset+i]) for i in range(size)])
768
        elif(data_type == 'DOUBLE'):
769
            return float(data[offset])
770
        elif(data_type == 'UINT32' or
771
             data_type == 'UINT64'):
772
            return int(data[offset])
773
        elif(data_type == 'VECTOR6INT32'):
774
            return np.array([int(data[offset+i]) for i in range(size)])
775
        elif(data_type == 'INT32' or
776
             data_type == 'UINT8'):
777
            return int(data[offset])
778
        raise ValueError('unpack_field: unknown data type: ' + data_type)