-
Notifications
You must be signed in to change notification settings - Fork 395
/
Copy pathService.js
176 lines (163 loc) · 5.63 KB
/
Service.js
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
/**
* @fileOverview
* @author Brandon Alexander - baalexander@gmail.com
*/
import Ros from './Ros.js';
import { EventEmitter } from 'eventemitter3';
/**
* A ROS service client.
* @template TRequest, TResponse
*/
export default class Service extends EventEmitter {
/**
* Stores a reference to the most recent service callback advertised so it can be removed from the EventEmitter during un-advertisement
* @private
* @type {((rosbridgeRequest) => any) | null}
*/
_serviceCallback = null;
isAdvertised = false;
/**
* @param {Object} options
* @param {Ros} options.ros - The ROSLIB.Ros connection handle.
* @param {string} options.name - The service name, like '/add_two_ints'.
* @param {string} options.serviceType - The service type, like 'rospy_tutorials/AddTwoInts'.
*/
constructor(options) {
super();
this.ros = options.ros;
this.name = options.name;
this.serviceType = options.serviceType;
}
/**
* @callback callServiceCallback
* @param {TResponse} response - The response from the service request.
*/
/**
* @callback callServiceFailedCallback
* @param {string} error - The error message reported by ROS.
*/
/**
* Call the service. Returns the service response in the
* callback. Does nothing if this service is currently advertised.
*
* @param {TRequest} request - The service request to send.
* @param {callServiceCallback} [callback] - Function with the following params:
* @param {callServiceFailedCallback} [failedCallback] - The callback function when the service call failed with params:
* @param {number} [timeout] - Optional timeout, in seconds, for the service call. A non-positive value means no timeout.
* If not provided, the rosbridge server will use its default value.
*/
callService(request, callback, failedCallback, timeout) {
if (this.isAdvertised) {
return;
}
var serviceCallId =
'call_service:' + this.name + ':' + (++this.ros.idCounter).toString();
if (callback || failedCallback) {
this.ros.once(serviceCallId, function (message) {
if (message.result !== undefined && message.result === false) {
if (typeof failedCallback === 'function') {
failedCallback(message.values);
}
} else if (typeof callback === 'function') {
callback(message.values);
}
});
}
var call = {
op: 'call_service',
id: serviceCallId,
service: this.name,
type: this.serviceType,
args: request,
timeout: timeout
};
this.ros.callOnConnection(call);
}
/**
* @callback advertiseCallback
* @param {TRequest} request - The service request.
* @param {Partial<TResponse>} response - An empty dictionary. Take care not to overwrite this. Instead, only modify the values within.
* @returns {boolean} true if the service has finished successfully, i.e., without any fatal errors.
*/
/**
* Advertise the service. This turns the Service object from a client
* into a server. The callback will be called with every request
* that's made on this service.
*
* @param {advertiseCallback} callback - This works similarly to the callback for a C++ service and should take the following params
*/
advertise(callback) {
if (this.isAdvertised) {
throw new Error('Cannot advertise the same Service twice!');
}
// Store the new callback for removal during un-advertisement
this._serviceCallback = (rosbridgeRequest) => {
var response = {};
var success = callback(rosbridgeRequest.args, response);
var call = {
op: 'service_response',
service: this.name,
values: response,
result: success
};
if (rosbridgeRequest.id) {
call.id = rosbridgeRequest.id;
}
this.ros.callOnConnection(call);
};
this.ros.on(this.name, this._serviceCallback);
this.ros.callOnConnection({
op: 'advertise_service',
type: this.serviceType,
service: this.name
});
this.isAdvertised = true;
}
unadvertise() {
if (!this.isAdvertised) {
throw new Error(`Tried to un-advertise service ${this.name}, but it was not advertised!`);
}
this.ros.callOnConnection({
op: 'unadvertise_service',
service: this.name
});
// Remove the registered callback
if (this._serviceCallback) {
this.ros.off(this.name, this._serviceCallback);
}
this.isAdvertised = false;
}
/**
* An alternate form of Service advertisement that supports a modern Promise-based interface for use with async/await.
* @param {(request: TRequest) => Promise<TResponse>} callback An asynchronous callback processing the request and returning a response.
*/
advertiseAsync(callback) {
if (this.isAdvertised) {
throw new Error('Cannot advertise the same Service twice!');
}
this._serviceCallback = async (rosbridgeRequest) => {
/** @type {{op: string, service: string, values?: TResponse, result: boolean, id?: string}} */
let rosbridgeResponse = {
op: 'service_response',
service: this.name,
result: false
}
try {
rosbridgeResponse.values = await callback(rosbridgeRequest.args);
rosbridgeResponse.result = true;
} finally {
if (rosbridgeRequest.id) {
rosbridgeResponse.id = rosbridgeRequest.id;
}
this.ros.callOnConnection(rosbridgeResponse);
}
}
this.ros.on(this.name, this._serviceCallback);
this.ros.callOnConnection({
op: 'advertise_service',
type: this.serviceType,
service: this.name
});
this.isAdvertised = true;
}
}