Skip to content

Commit e68811c

Browse files
committed
Action Server Support
-Adds interface for ActionServers -Adds experimental ActionServer implementation. I assume there are still bugs here. Protocol generally unspecified - had to hunt through actionlib source code. -Renames ActionClient to ActionClientInterface. -Adds time utilities. -Throw useful errors when invalid message type was specified.
1 parent f1bd92e commit e68811c

13 files changed

Lines changed: 625 additions & 12 deletions

src/actions/ActionServer.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright 2017 Rethink Robotics
3+
*
4+
* Copyright 2017 Chris Smith
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
'use strict';
19+
20+
const timeUtils = require('../lib/Time.js');
21+
const msgUtils = require('../utils/message_utils.js');
22+
const EventEmitter = require('events');
23+
24+
const ActionServerInterface = require('../lib/ActionServerInterface.js');
25+
const GoalHandle = require('./GoalHandle.js');
26+
27+
let GoalIdMsg = null;
28+
let GoalStatusMsg = null;
29+
let GoalStatusArrayMsg = null;
30+
let GoalStatuses = null;
31+
let goalCount = 0;
32+
33+
/**
34+
* @class ActionServer
35+
* EXPERIMENTAL
36+
*
37+
*/
38+
class ActionServer extends EventEmitter {
39+
constructor(options) {
40+
super();
41+
42+
if (GoalStatusMsg === null) {
43+
GoalStatusMsg = msgUtils.requireMsgPackage('actionlib_msgs').msg.GoalStatus;
44+
GoalStatuses = GoalStatusMsg.Constants;
45+
}
46+
47+
if (GoalStatusArrayMsg === null) {
48+
GoalStatusArrayMsg = msgUtils.requireMsgPackage('actionlib_msgs').msg.GoalStatusArray;
49+
}
50+
51+
this._asInterface = new ActionServerInterface(options);
52+
53+
this._asInterface.on('goal', this._handleGoal.bind(this));
54+
this._asInterface.on('cancel', this._handleCancel.bind(this));
55+
56+
const actionType = this._asInterface.getType();
57+
58+
this._messageTypes = {
59+
result: msgUtils.getHandlerForMsgType(actionType + 'Result'),
60+
feedback: msgUtils.getHandlerForMsgType(actionType + 'Feedback'),
61+
actionResult: msgUtils.getHandlerForMsgType(actionType + 'ActionResult'),
62+
actionFeedback: msgUtils.getHandlerForMsgType(actionType + 'ActionFeedback')
63+
};
64+
65+
this._pubSeqs = {
66+
result: 0,
67+
feedback: 0,
68+
status: 0
69+
};
70+
71+
this._goalHandleList = [];
72+
this._goalHandleCache = {};
73+
74+
this._lastCancelStamp = timeUtils.epoch();
75+
76+
this._statusListTimeout = 5;
77+
}
78+
79+
generateGoalId() {
80+
return this._asInterface.generateGoalId();
81+
}
82+
83+
shutdown() {
84+
return this._asInterface.shutdown();
85+
}
86+
87+
_getGoalHandle(id) {
88+
return this._goalHandleCache[id];
89+
}
90+
91+
_handleGoal(msg) {
92+
const newGoalId = msg.goal_id.id;
93+
94+
let handle = this._getGoalHandle(newGoalId);
95+
96+
if (handle) {
97+
if (handle.status === GoalStatuses.RECALLING) {
98+
handle.status = GoalStatuses.RECALLED;
99+
this.publishResult(status.status, this._createMessage('result'));
100+
}
101+
102+
handle._destructionTime = msg.goal_id.stamp;
103+
return false;
104+
}
105+
106+
handle = new GoalHandle(msg.goal_id, this);
107+
this._goalHandleList.push(handle);
108+
this._goalHandleCache[handle.id] = handle;
109+
110+
const goalStamp = msg.goal_id.stamp;
111+
// check if this goal has already been cancelled based on its timestamp
112+
if (!timeUtils.isZeroTime(goalStamp) &&
113+
timeUtils.timeComp(goalStamp, this._lastCancelStamp) < 0) {
114+
handle.setCancelled(this._createMessage('result'));
115+
return false;
116+
}
117+
else {
118+
// track goal, I guess
119+
this.emit('goal', handle);
120+
}
121+
122+
return true;
123+
}
124+
125+
_handleCancel(msg) {
126+
const cancelId = msg.id;
127+
const cancelStamp = msg.stamp;
128+
const cancelStampIsZero = timeUtils.isZeroTime(cancelStamp);
129+
130+
const shouldCancelEverything = (cancelId === '' && cancelStampIsZero);
131+
132+
let goalIdFound = false;
133+
134+
for (let i = 0, len = this._goalHandleList.length; i < len; ++i) {
135+
const handle = this._goalHandleList[i];
136+
const handleId = handle.id;
137+
const handleStamp = handle.status.goal_id.stamp;
138+
139+
if (shouldCancelEverything ||
140+
cancelId === handleId ||
141+
(!timeUtils.isZeroTime(handleStamp) &&
142+
timeUtils.timeComp(handleStamp, cancelStamp) < 0))
143+
{
144+
if (cancelId === handleId) {
145+
goalIdFound = true;
146+
}
147+
148+
if (handle.setCancelRequested()) {
149+
this.emit('cancel', handle);
150+
}
151+
}
152+
}
153+
154+
// if the requested goal_id was not found and it is not empty,
155+
// then we need to store the cancel request
156+
if (cancelId !== '' && !goalIdFound) {
157+
const handle = new GoalHandle(msg, this, GoalStatuses.RECALLING);
158+
this._goalHandleList.push(handle);
159+
this._goalHandleCache[handle.id] = handle;
160+
}
161+
162+
// update the last cancel stamp if new one occurred later
163+
if (timeUtils.timeComp(cancelStamp, this._lastCancelStamp) > 0) {
164+
this._lastCancelStamp = cancelStamp;
165+
}
166+
}
167+
168+
publishResult(status, result) {
169+
const msg = this._createMessage('actionResult', { status, result });
170+
msg.header.stamp = timeUtils.now();
171+
msg.header.seq = this._getAndIncrementSeq('actionResult');
172+
this._asInterface.publishResult(msg);
173+
this.publishStatus();
174+
}
175+
176+
publishFeedback(status, feedback) {
177+
const msg = this._createMessage('actionFeedback', { status, feedback });
178+
msg.header.stamp = timeUtils.now();
179+
msg.header.seq = this._getAndIncrementSeq('actionFeedback');
180+
this._asInterface.publishFeedback(msg);
181+
this.publishStatus();
182+
}
183+
184+
publishStatus() {
185+
const msg = new GoalStatusArrayMsg();
186+
msg.header.stamp = timeUtils.now();
187+
msg.header.seq = this._getAndIncrementSeq('status');
188+
189+
let goalsToRemove = new Set();
190+
191+
const now = timeUtils.toNumber(timeUtils.now());
192+
193+
for (let i = 0, len = this._goalHandleList.length; i < len; ++i) {
194+
const goalHandle = this._goalHandleList[i];
195+
msg.status_list.push(goalHandle.getGoalStatus());
196+
197+
const t = goalHandle._destructionTime;
198+
const tNum = timeUtils.toNumber(t);
199+
if (!timeUtils.isZeroTime(t) &&
200+
timeUtils.toNumber(t) + this._statusListTimeout < now)
201+
{
202+
goalsToRemove.add(goalHandle);
203+
}
204+
}
205+
206+
// clear out any old goal handles
207+
this._goalHandleList = this._goalHandleList.filter((goal) => {
208+
// kind of funky to remove from another object in this filter...
209+
if (goalsToRemove.has(goal)) {
210+
delete this._goalHandleCache[goal.id];
211+
return false;
212+
}
213+
return true;
214+
});
215+
216+
this._asInterface.publishStatus(msg);
217+
}
218+
219+
_getAndIncrementSeq(type) {
220+
return this._pubSeqs[type]++
221+
}
222+
223+
_createMessage(type, args = {}) {
224+
return new this._messageTypes[type](args);
225+
}
226+
}
227+
228+
module.exports = ActionServer;

0 commit comments

Comments
 (0)