-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathinterruption-handler.l
428 lines (384 loc) · 18.5 KB
/
interruption-handler.l
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
;; ;; don't explicitly load to avoid introducing cyclic dependencies
;; ;; instead, let if fail when robot-interface has not been loaded
;; (require :robot-interface "package://pr2eus/robot-interface.l")
(unless (find-package "ROSEUS_RESUME") (make-package "ROSEUS_RESUME"))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONTROLLER INTERFACE
;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ros::simple-action-client
(:current-time ()
(cond
((null (send ros::comm-state :action-goal))
(ros::time 0))
(t
(ros::time- (ros::time-now)
(send (send ros::comm-state :action-goal) :header :stamp))))))
(defmethod controller-action-client
(:current-time ()
(cond
((send ri :simulation-modep)
(ros::time (/ (or current-time 0.0) 1000.0)))
((null (send ros::comm-state :action-goal))
(ros::time 0))
(t
;; use time measured from last feedback instead
(ros::time- last-feedback-msg-stamp
(send (send ros::comm-state :action-goal) :header :stamp))))))
(defclass roseus_resume::action-snapshot
:super propertied-object
:slots (ac goal time))
(defclass roseus_resume::controller-action-snapshot
:super roseus_resume::action-snapshot)
(defclass roseus_resume::interruption-snapshot
:super propertied-object
:slots (action-status robot-state))
(defmethod roseus_resume::action-snapshot
(:init (action-client)
(setq ac action-client)
(setq goal (send (send ac :comm-state) :action-goal))
(setq time (send ac :current-time)))
(:name () (send ac :name))
(:action-client () ac)
(:goal () (send goal :goal))
(:angle-vector (robot-state)
(let ((cacts (find (get ac :name) (send (ac . ri) :default-controller)
:key #'(lambda (x) (cdr (assoc :controller-action x)))
:test #'string=)))
(when cacts
(let ((controller-joint-names (cdr (assoc :joint-names cacts)))
(joint-names (cdr (assoc :name robot-state)))
(joint-positions (cdr (assoc :position robot-state))))
(labels ((ros-joint-angle (nm val)
(let ((joint (send ((ac . ri) . robot)
(intern (string-upcase nm) *keyword-package*))))
(if (derivedp joint rotational-joint)
(rad2deg val)
(* val 1000))))
(joint-angle (nm)
(ros-joint-angle nm
(elt joint-positions (position nm joint-names :test #'string=)))))
(map float-vector #'joint-angle controller-joint-names))))))
(:time () time))
(defmethod roseus_resume::interruption-snapshot
(:init (status robot-st)
(setq action-status status)
(setq robot-state robot-st)
self)
(:action-status () action-status)
(:robot-state () robot-state))
(defmethod robot-interface
(:robot-state () robot-state)
(:get-action-status ()
(let (acc)
(labels ((check-active (ac)
(send ac :spin-once)
(= (send ac :get-state) actionlib_msgs::GoalStatus::*active*))
(make-action-snapshot (ac)
(if (and (derivedp ac controller-action-client)
(derivedp (ac . ros::action-spec)
control_msgs::followjointtrajectoryaction))
(instance roseus_resume::controller-action-snapshot :init ac)
(instance roseus_resume::action-snapshot :init ac)))
(get-actions-from-slots ()
(let (action-acc)
(labels ((maybe-push-action (value)
(cond
((derivedp value ros::simple-action-client)
(push value action-acc))
((consp value)
(mapc #'maybe-push-action value)))))
(dolist (slot (send self :slots))
(maybe-push-action (cdr slot)))
(nreverse action-acc)))))
(dolist (ac (get-actions-from-slots))
;; only interrupt active controllers
(when (check-active ac)
(push (make-action-snapshot ac) acc)))
(dolist (ac (get self :additional-controllers))
;; only interrupt active controllers
(when (check-active ac)
(push (make-action-snapshot ac) acc)))
(nreverse acc))))
(:interrupt-angle-vector (&optional status)
;; controller-actions are stopped with `:cancel-angle-vector', to ensure a quick stop
;; for more smooth stopping, use `:stop-motion' instead
(send self :cancel-angle-vector))
(:interrupt-additional-controllers (status)
;; stop other actions by directly sending a cancel request
(if (derivedp status roseus_resume::interruption-snapshot)
(setq status (send status :action-status)))
(dolist (snapshot
(remove-if #'(lambda (s) (find (send s :action-client) controller-actions)) status))
(send (send snapshot :action-client) :cancel-goal)))
(:resume-angle-vector (status &key (scale 1.0) (min-time 1.0))
(if (derivedp status roseus_resume::interruption-snapshot)
(setq status (send status :action-status)))
(unless (some #'identity status)
(return-from :resume-angle-vector nil))
(send self :spin-once) ;; update joint positions
(let ((offset 0.0)
(controller-action-status
(remove-if-not #'(lambda (val) (derivedp val roseus_resume::controller-action-snapshot))
status)))
(labels
((offset-point (point tm)
(send point :time_from_start (ros::time+ (send point :time_from_start) (ros::time tm)))
point)
(actual-positions (name)
(let* ((controller
(find name (send self controller-type)
:key #'(lambda (x) (cdr (assoc :controller-action x)))
:test #'string-equal))
(joint-names (cdr (assoc :joint-names controller)))
(joint-positions
(mapcar #'(lambda (j)
(send robot (intern (string-upcase j) *keyword-package*)
:ros-joint-angle))
joint-names)))
(assert joint-names "Cannot find :joint-names of controller ~A" controller)
(mapcar #'cons joint-names joint-positions)))
(update-offset (start end)
(dolist (p start)
(let* ((diff (abs (- (cdr p) (cdr (assoc (car p) end :test #'string=)))))
(joint (send robot (intern (string-upcase (car p)) *keyword-package*)))
(off (* scale (/ diff (send joint :max-joint-velocity)))))
(if (> off offset) (setq offset off))))))
(dolist (s controller-action-status)
(let* ((goal (send s :goal))
(tm (send s :time))
(last-point (car (last (send goal :trajectory :points)))))
;; Remove completed steps
(send goal :trajectory :points
(remove-if #'(lambda (p) (ros::time< (send p :time_from_start) tm))
(send goal :trajectory :points)))
;; Ensure last waypoint
(unless (send goal :trajectory :points)
(send last-point :time_from_start tm)
(send goal :trajectory :points (list last-point)))
;; Shift based on time_from_start
(send goal :trajectory :points
(mapcar #'(lambda (p) (offset-point p (- (send tm :to-sec))))
(send goal :trajectory :points)))
;; Update offset
(update-offset (actual-positions (send s :name))
(map cons #'cons
(send goal :trajectory :joint_names)
(send (car (send goal :trajectory :points)) :positions)))))
;; Check min-time
(setq offset (max offset min-time))
;; Shift based on time offset
(dolist (s controller-action-status)
(let ((goal (send s :goal)))
(send goal :trajectory :points
(mapcar #'(lambda (p) (offset-point p offset))
(send goal :trajectory :points)))))
;; Send commands
(dolist (s controller-action-status)
(let* ((ac (send s :action-client))
(action-goal (send ac :make-goal-instance))
(goal (send s :goal))
(tm (ros::time-now)))
;; Prepare message
(send action-goal :header :stamp tm)
(send action-goal :header :seq 1)
(send action-goal :goal :trajectory (send goal :trajectory))
(send action-goal :goal :trajectory :header :stamp tm)
(send action-goal :goal :trajectory :header :seq 1)
;; Send goal to controller
(send ac :spin-once)
(send ac :send-goal action-goal)))
;; Wait offset before resuming
(when controller-action-status
(unix:usleep (truncate (* offset 1000000))))
t)))
(:resume-additional-controllers (status)
(if (derivedp status roseus_resume::interruption-snapshot)
(setq status (send status :action-status)))
;; Send commands
(dolist (s status)
;; skip controller actions
(when (and (derivedp s roseus_resume::action-snapshot)
(not (derivedp s roseus_resume::controller-action-snapshot)))
(let* ((ac (send s :action-client))
(action-goal (send ac :make-goal-instance))
(goal (send s :goal))
(tm (ros::time-now)))
;; Prepare message
(send action-goal :header :stamp tm)
(send action-goal :header :seq 1)
(send action-goal :goal goal)
;; Send goal to controller
(send ac :spin-once)
(send ac :send-goal action-goal))))))
;;;;;;;;;;;;;;;;;;;;;;;
;; PACKAGE DEFINITION
;;;;;;;;;;;;;;;;;;;;;;;
(import 'robot-interface (find-package "ROSEUS_RESUME"))
(in-package "ROSEUS_RESUME")
(export '(on-interruption on-standby on-resume install-interruption-handler
*current-status*
intervention defintervention define-urgent-intervention
install-default-intervention install-urgent-intervention-framework))
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INTERRUPTION HANDLERS
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; status object
(defvar *current-status* nil
"Snapshot of interrupted state")
;; conditions
(defcondition on-interruption :slots (status))
(defcondition on-standby :slots (status interruption-instance))
(defcondition on-resume :slots (status))
;; define callback
(defun generate-interruption-handler-with-controller-interrupt (ri)
;; compile handler to keep the top-level env during the interruption repl
(compile
(eval
`(defun interruption-handler-with-controller-interrupt (c)
(let ((*current-status*
(handler-case (instance interruption-snapshot :init
(send ,ri :get-action-status)
(send ,ri :robot-state))
;; ensure interruption even upon error
(error (e) (signals on-interruption :status nil)
(euserror e)))))
(ros::ros-info ";; action-status: ~A"
(and *current-status* (send *current-status* :action-status)))
(ros::ros-info ";; on-interruption...")
(signals on-interruption :status *current-status*)
(ros::ros-info ";; on-standby...")
(signals on-standby :status *current-status*
:interruption-instance c)
(ros::ros-info ";; on-resume...")
(signals on-resume :status *current-status*))))))
;; install callback
(defun install-interruption-handler (ri &rest additional-controllers)
;; store additional controllers
(let (acc)
(dolist (controller additional-controllers)
(if (derivedp controller ros::simple-action-client)
(progn
(ros::ros-info "Registering additional controller: ~S" controller)
(push controller acc))
(progn
(ros::ros-warn "Additional controllers must be derived from ros::simple-action-client class: ~S" controller)
(ros::ros-warn "Skipping ~S..." controller))))
(setf (get ri :additional-controllers)
(remove-duplicates (append (get ri :additional-controllers) (nreverse acc)))))
;; generate & install interruption handlers
(unless (fboundp 'interruption-handler-with-controller-interrupt)
(ros::ros-info "Installing interruption handler...")
(generate-interruption-handler-with-controller-interrupt ri)
(install-handler interruption 'interruption-handler-with-controller-interrupt)
(install-handler on-interruption
#'(lambda (c)
(send ri :interrupt-angle-vector (send c :status))
(send ri :interrupt-additional-controllers (send c :status))))
(install-handler on-standby
#'(lambda (c) (invoke-next-handler (send c :interruption-instance))))
(install-handler on-resume
#'(lambda (c)
(send ri :resume-additional-controllers (send c :status))
(send ri :resume-angle-vector (send c :status))))
(ros::ros-info "Interruption handler installed.")
t))
;;;;;;;;;;;;;;;;;
;; INTERVENTION
;;;;;;;;;;;;;;;;;
(defcondition intervention :slots (ros-msg))
(defvar *intervention-groupname* "roseus_resume.intervention")
(defvar *default-intervention-groupname* "roseus_resume.default_intervention")
(defvar *urgent-intervention-signal* unix::sigurg)
(defvar *monitor-interventions* t)
(defvar *synch-port* (instance sys:synch-memory-port :init))
;; utility
(defun signal-intervention (topic-name condition-name msg)
(ros::ros-info ";; intervention message received at ~A" topic-name)
(if (derivedp (instance condition-name) intervention)
(signals condition-name :ros-msg msg)
(signals condition-name)))
(defun urgent-intervention-cb (topic-name condition-name msg)
(ros::ros-info ";; urgent intervention message received at ~A" topic-name)
(send *synch-port* :write (list condition-name :ros-msg msg))
(unix::kill (unix:getpid) *urgent-intervention-signal*))
(defun signal-urgent-intervention (sig code)
(ros::ros-info ";; signal-urgent-intervention")
(apply #'signals (send *synch-port* :read)))
(defun spin-intervention-groupname ()
(ros::ros-info ";; checking for intervention messages...")
(while *monitor-interventions*
(ros::spin-once *intervention-groupname*)
(ros::sleep))
(ros::ros-warn ";; stop checking for intervention messages..."))
;; default intervention installer
(defun install-default-intervention (robot-interface &key
(interrupt-topic "/roseus_resume/interrupt")
(resume-topic "/roseus_resume/resume")
(top-selector-interval))
;; Spin topics as a timer-job, meaning that they are only invoked during idle time.
;; For interrupting actions during their execution, try registering interventions
;; directly to the robot-interface's groupname `(*ri* . groupname)`, or using
;; the `install-urgent-intervention-framework' instead
(when (ros::create-nodehandle *default-intervention-groupname*)
;; only execute for the first time
(if (or (not (eq (class *standard-input*) file-stream))
(unix:isatty *standard-input*))
;; check for resumptions as a timer-job if process is atty
(progn
(if top-selector-interval
(setq *top-selector-interval* top-selector-interval))
(setq *timer-job* (cons #'(lambda () (ros::spin-once *default-intervention-groupname*))
*timer-job*)))
;; check for resumptions in a custom handler if process is not atty
(install-handler interruption
#'(lambda (c)
(lisp::print-error-message c)
(ros::ros-info "Process interrupted by user. Send a message to ~A to continue"
resume-topic)
(handler-case
(while (ros::ok)
(ros::spin-once *default-intervention-groupname*)
(ros::sleep))
(unix::sigcont-received () ))))))
;; spin interruptions on the robot and resumptions as a timer-job or handler
(defintervention interruption interrupt-topic std_msgs::Empty
:groupname (send robot-interface :get-val "GROUPNAME"))
(defintervention unix::sigcont-received resume-topic std_msgs::Empty
:groupname *default-intervention-groupname*)
t)
(defun install-urgent-intervention-framework ()
;; Dispatch unix signals when a topic message is received,
;; ensuring immediate interruption at any execution point
(when (ros::create-nodehandle *intervention-groupname*)
;; create and spin nodehandle in a new thread
;; only execute for the first time
(warn ";; Urgent interventions are experimental! Handle with care~%")
(sys:make-thread 1)
(sys:thread-no-wait 'spin-intervention-groupname)
(unix:signal *urgent-intervention-signal* 'signal-urgent-intervention)
;; generate & install default interventions
(define-urgent-intervention interruption-topic "roseus_resume/interrupt" std_msgs::Empty)
(define-urgent-intervention resume-topic "roseus_resume/resume" std_msgs::Empty)
(install-handler interruption-topic
#'(lambda (c) (signals unix::sigint-received :message "topic interrupt")))
(install-handler resume-topic #'(lambda (c) (signals unix::sigcont-received)))
t))
;; macros
(defmacro defintervention (condition-name topic-name message-type &key groupname)
`(progn
(unless (boundp ',condition-name)
(defcondition ,condition-name :super intervention))
(unless (and (classp ,condition-name) (derivedp (instance ,condition-name) condition))
(error value-error "condition class expected in ~A(~S)" ',condition-name ,condition-name))
,(if groupname
`(ros::subscribe ,topic-name ,message-type #'signal-intervention
,topic-name ,condition-name
:groupname ,groupname)
`(ros::subscribe ,topic-name ,message-type #'signal-intervention
,topic-name ,condition-name))))
(defmacro define-urgent-intervention (condition-name topic-name message-type)
`(progn
(defcondition ,condition-name :super intervention)
(ros::subscribe ,topic-name ,message-type #'urgent-intervention-cb
,topic-name ,condition-name
:groupname *intervention-groupname*)))