a b/URBasic/dashboard.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
'''
25
__author__ = "Martin Huus Bjerge"
26
__copyright__ = "Copyright 2017, Rope Robotics ApS, Denmark"
27
__license__ = "MIT License"
28
29
import URBasic
30
import threading
31
import socket
32
import struct
33
import select
34
import time
35
36
DEFAULT_TIMEOUT = 2.0
37
38
class ConnectionState:
39
    ERROR = 0
40
    DISCONNECTED = 1
41
    CONNECTED = 2
42
    PAUSED = 3
43
    STARTED = 4
44
45
46
class DashBoard(threading.Thread): 
47
    '''
48
    A Universal Robot can be controlled from remote by sending simple commands to the 
49
    GUI over a TCP/IP socket. This interface is called the "DashBoard server". 
50
    The server is running on port 29999 on the robots IP address.
51
    See more at: http://www.universal-robots.com/how-tos-and-faqs/how-to/ur-how-tos/dashboard-server-port-29999-15690/
52
    
53
    The constructor takes a UR robot hostname as input, and optional a logger object.
54
55
    Input parameters:
56
    host (string):  hostname or IP of UR Robot (RT CLient server)
57
    logger (URBasis_DataLogging obj): A instance if a logger object if common logging is needed.
58
59
    
60
    Example:
61
    rob = URBasic.realTimeClient.RT_CLient('192.168.56.101')
62
    self.close_rtc()
63
64
    
65
    '''
66
67
    def __init__(self, robotModel):
68
        '''
69
        Constructor see class description for more info.
70
        '''
71
        if(False):
72
            assert isinstance(robotModel, URBasic.robotModel.RobotModel)  ### This line is to get code completion for RobotModel
73
        self.__robotModel = robotModel
74
75
        logger = URBasic.dataLogging.DataLogging()
76
        name = logger.AddEventLogging(__name__)        
77
        self._logger = logger.__dict__[name]
78
        self.__reconnectTimeout = 60 #Seconds (while in run)
79
        self.__conn_state = ConnectionState.DISCONNECTED
80
        self.last_respond = None
81
        self.__stop_event = True
82
        threading.Thread.__init__(self)
83
        self.__dataEvent = threading.Condition()
84
        self.__dataAccess = threading.Lock()
85
        self.__sock = None
86
        self.start()
87
        self.wait_dbs()
88
        self._logger.info('Dashboard server constructor done')
89
90
91
    def ur_load(self, file):
92
        '''
93
        Load the specified program. Return when loading has completed.
94
        
95
        Return value to Log file:
96
        "Loading program: <program.urp>" OR "File not found: <program.urp>"
97
        '''
98
        self.__send('load ' + file + '\n')
99
100
    def ur_play(self):
101
        '''
102
        Starts program, if any program is loaded and robot is ready. Return when the program execution has been started.
103
104
        Return value to Log file:
105
        "Starting program"
106
        '''
107
        self.__send('play\n')
108
        
109
    def ur_stop(self):
110
        '''
111
        Stops running program and returns when stopping is completed.
112
        
113
        Return value to Log file:
114
        "Stopped"
115
        '''
116
        self.__send('stop\n')
117
118
119
    def ur_pause(self):
120
        '''
121
        Pauses the running program and returns when pausing is completed.
122
        
123
        Return value to Log file:
124
        "Pausing program"
125
        '''
126
        self.__send('pause\n')
127
128
129
    def ur_shutdown(self):
130
        '''
131
        Shuts down and turns off robot and controller.
132
        
133
        Return value to Log file:
134
        "Shutting down"
135
        '''
136
        self.__send('shutdown\n')
137
        
138
    def ur_running(self):
139
        '''
140
        Execution state enquiry.
141
        
142
        Return value to Log file:
143
        "Robot running: True" OR "Robot running: False"
144
        '''
145
        self.__send('running\n')
146
        
147
    def ur_robotmode(self):
148
        '''
149
        Robot mode enquiry
150
        
151
        Return value to Log file:
152
        "Robotmode: <mode>", where <mode> is:        
153
        NO_CONTROLLER
154
        DISCONNECTED
155
        CONFIRM_SAFETY
156
        BOOTING
157
        POWER_OFF
158
        POWER_ON
159
        IDLE
160
        BACKDRIVE
161
        RUNNING
162
        '''
163
        self.__send('robotmode\n')
164
165
    def ur_get_loaded_program(self):
166
        '''
167
        Which program is loaded.
168
        
169
        Return value to Log file:
170
        "Program loaded: <path to loaded program file>" OR "No program loaded"
171
        '''
172
        self.__send('get loaded program\n')
173
174
    def ur_popup(self,  popupText=''):
175
        '''
176
        The popup-text will be translated to the selected language, if the text exists in the language file.
177
        
178
        Return value to Log file:
179
        "showing popup"
180
        '''
181
        self.__send('popup ' + popupText + '\n')
182
183
    def ur_close_popup(self):
184
        '''
185
        Closes the popup.
186
        
187
        Return value to Log file:
188
        "closing popup"
189
        '''
190
        self.__send('close popup\n')
191
192
    def ur_addToLog(self, logMessage):
193
        '''
194
        Adds log-message to the Log history.
195
196
        Return value to Log file:
197
        "Added log message" Or "No log message to add"
198
        '''
199
        self.__send('addToLog ' + logMessage + '\n')
200
201
    def ur_setUserRole(self, role):
202
        '''
203
        Simple control of user privileges: controls the available options on the Welcome screen.
204
        
205
        Return value to Log file:
206
        "Setting user role: <role>" OR "Failed setting user role: <role>"
207
        '''
208
        self.__send('setUserRole ' + role + '\n')
209
210
    def ur_isProgramSaved(self):
211
        '''
212
        Returns the save state of the active program.
213
        
214
        Return value to Log file:
215
        "True" OR "False"
216
        '''
217
        self.__send('isProgramSaved\n')
218
219
    def ur_programState(self):
220
        '''
221
        Returns the state of the active program, or STOPPED if no program is loaded.
222
        
223
        Return value to Log file:
224
        "STOPPED" if no program is running OR "PLAYING" if program is running
225
        '''
226
        self.__send('programState\n')
227
228
    def ur_polyscopeVersion(self):
229
        '''
230
        Returns the version of the Polyscope software.
231
        
232
        Return value to Log file:
233
        version number, like "3.0.15547"
234
        '''
235
        self.__send('polyscopeVersion\n')
236
237
    def ur_setUserRole_where(self, role, level):
238
        '''
239
        "setUserRole <role>, where <role> is"
240
        programmer = "SETUP Robot" button is disabled, "Expert Mode" is available (if correct password is supplied)
241
        operator = Only "RUN Program" and "SHUTDOWN Robot" buttons are enabled, "Expert Mode" cannot be activated
242
        none ( or send setUserRole) = All buttons enabled, "Expert Mode" is available (if correct password is supplied)
243
        locked = All buttons disabled and "Expert Mode" cannot be activated
244
        Control of user privileges: controls the available options on the Welcome screen.
245
        
246
        Note: If the Welcome screen is not active when the command is sent, 
247
        the user privileges defined by the new user role will not be effective 
248
        until the user switches to the Welcome screen.
249
250
        Return value to Log file:
251
        "Setting user role: <role>" OR "Failed setting user role: <role>"
252
        '''
253
        self.__send('setUserRole '+ role + ', where ' + role + ' is' + level +'\n')
254
255
    def ur_power_on(self):
256
        '''
257
        Powers on the robot arm.
258
        
259
        Return value to Log file:
260
        "Powering on"
261
        '''
262
        self.__send('power on\n')
263
264
    def ur_power_off(self):
265
        '''
266
        Powers off the robot arm.
267
        
268
        Return value to Log file:
269
        "Powering off"
270
        '''
271
        self.__send('power off\n')
272
273
    def ur_brake_release(self):
274
        '''
275
        Releases the brakes.
276
        
277
        Return value to Log file:
278
        "Brake releasing"        
279
        '''
280
        self.__send('brake release\n')
281
282
    def ur_safetymode(self):
283
        '''
284
        Safety mode enquiry.
285
        
286
        Return value to Log file:
287
        "safety mode: <mode>", where <mode> is
288
        
289
        NORMAL
290
        REDUCED
291
        PROTECTIVE_STOP
292
        RECOVERY
293
        SAFEGUARD_STOP
294
        SYSTEM_EMERGENCY_STOP
295
        ROBOT_EMERGENCY_STOP
296
        VIOLATION
297
        FAULT        
298
        '''
299
        return self.__send('safetymode\n')
300
301
    def ur_unlock_protective_stop(self):
302
        '''
303
        Closes the current popup and unlocks protective stop.
304
        
305
        Return value to Log file:
306
        "Protective stop releasing"
307
        '''
308
        self.__send('unlock protective stop\n')
309
310
    def ur_close_safety_popup(self):
311
        '''
312
        Closes a safety popup.
313
        
314
        Return value to Log file:
315
        "closing safety popup"        
316
        '''
317
        self.__send('close safety popup\n')
318
319
    def ur_load_installation(self, instal='default.installation'):
320
        '''
321
        Loads the specified installation file.
322
        
323
        Return value to Log file:
324
        "Loading installation: <default.installation>" OR "File not found: <default.installation>"
325
        '''
326
        self.__send('load installation '+ instal +'\n')
327
328
        
329
    
330
        
331
        
332
333
334
335
    def __connect(self):
336
        '''
337
        Initialize DashBoard connection to host.
338
        
339
        Return value:
340
        success (boolean)
341
        '''       
342
        if self.__sock:
343
            return True
344
345
        t0 = time.time()
346
        while (time.time()-t0<self.__reconnectTimeout) and self.__conn_state < ConnectionState.CONNECTED:
347
            try:
348
                self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)            
349
                self.__sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)         
350
                self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
351
                self.__sock.settimeout(DEFAULT_TIMEOUT)
352
                self.__sock.connect((self.__robotModel.ipAddress, 29999))
353
                self.__conn_state = ConnectionState.CONNECTED
354
                time.sleep(0.5)
355
                self._logger.info('Connected')
356
                return True
357
            except (socket.timeout, socket.error):
358
                self.__sock = None
359
                self._logger.error('Dashboard connecting')
360
361
        return False
362
363
    def close(self):
364
        '''
365
        Close the DashBoard connection.
366
        Example:
367
        rob = URBasic.dashboard.DashBoard('192.168.56.101', rtde_conf_filename='rtde_configuration.xml', logger=logger)
368
        rob.close_dbs()
369
        '''
370
#        if self.IsRtcConnected():
371
#            self.close_rtc()
372
373
        if self.__stop_event is False:
374
            self.__stop_event = True
375
            self.join()
376
        if self.__sock:
377
            self.__sock.close()
378
            self.__sock = None
379
        self.__conn_state = ConnectionState.DISCONNECTED
380
        return True
381
382
    def dbs_is_running(self):
383
        '''
384
        Return True if Dash Board server is running
385
        '''
386
        return self.__conn_state >= ConnectionState.STARTED
387
388
    
389
    def run(self):
390
        self.__stop_event = False
391
        t0 = time.time()
392
        while (time.time()-t0<self.__reconnectTimeout) and self.__conn_state < ConnectionState.CONNECTED:
393
            if not self.__connect():
394
                self._logger.warning("UR Dashboard connection failed!")
395
396
        if self.__conn_state < ConnectionState.CONNECTED:
397
            self._logger.error("UR Dashboard interface not able to connect and timed out!")
398
            return
399
        
400
        while (not self.__stop_event) and (time.time()-t0<self.__reconnectTimeout):
401
            try:
402
                msg = self.__receive()
403
                if msg is not None:
404
                    self._logger.info('UR Dashboard respond ' + msg)
405
                    self.last_respond = msg
406
407
                with self.__dataEvent:
408
                    self.__dataEvent.notifyAll()
409
                t0 = time.time()
410
                self.__conn_state = ConnectionState.STARTED
411
412
            except Exception:
413
                if self.__conn_state >= ConnectionState.CONNECTED:
414
                    self.__conn_state = ConnectionState.ERROR
415
                    self._logger.error("Dashboard server interface stopped running")
416
417
                    try:
418
                        self.__sock.close()
419
                    except:
420
                        pass
421
                    self.__sock = None
422
                    self.__connect()
423
424
                if self.__conn_state >= ConnectionState.CONNECTED:
425
                    self._logger.info("Dashboard server interface reconnected")
426
                else:
427
                    self._logger.warning("Dashboard server reconnection failed!")
428
429
        self.__conn_state = ConnectionState.PAUSED
430
        with self.__dataEvent:
431
            self.__dataEvent.notifyAll()
432
        self._logger.info("Dashboard server interface is stopped")
433
434
    def wait_dbs(self):
435
        '''Wait while the data receiving thread is receiving a new message.'''
436
        with self.__dataEvent:
437
            self.__dataEvent.wait()
438
        
439
    def __send(self, cmd):
440
        '''
441
        Send command to Robot Controller. 
442
443
        Input parameters:
444
        cmd (str)
445
446
        Return value:
447
        success (boolean)
448
        '''
449
        t0 = time.time()
450
        while (time.time()-t0<self.__reconnectTimeout):
451
            try:
452
                buf = bytes(cmd, 'utf-8')
453
                (_, writable, _) = select.select([], [self.__sock], [], DEFAULT_TIMEOUT)
454
                if len(writable):
455
                    self.__sock.sendall(buf)
456
                    self.wait_dbs()
457
                    return True
458
            except:
459
                self._logger.error('Could not send program!')
460
461
        self._logger.error('Program re-sending timed out - Could not send program!')
462
        return False
463
464
465
466
      
467
468
    def __receive(self):
469
        '''
470
        Receive the respond a send command from the Robot Controller. 
471
472
        Return value:
473
        Output from Robot controller (type is depended on the input parameters)
474
        '''
475
        (readable, _, _) = select.select([self.__sock], [], [], DEFAULT_TIMEOUT)
476
        if len(readable):
477
            data = self.__sock.recv(1024)
478
            if len(data) == 0:
479
                return None
480
            
481
            fmt = ">" + str(len(data)) + "B"
482
            out =  struct.unpack_from(fmt, data)        
483
            return ''.join(map(chr,out[:-1]))
484
            
485