115 lines
3.2 KiB
Dart
115 lines
3.2 KiB
Dart
// Copyright (c) 2019 Conrad Heidebrecht.
|
|
|
|
import 'dart:async';
|
|
import 'ros.dart';
|
|
import 'request.dart';
|
|
|
|
// Receiver function to handle requests when the service is advertising.
|
|
typedef ServiceHandler = Future<Map<String, dynamic>>? Function(
|
|
Map<String, dynamic> args);
|
|
|
|
/// Wrapper to interact with ROS services.
|
|
class Service {
|
|
Service({
|
|
required this.ros,
|
|
required this.name,
|
|
required this.type,
|
|
});
|
|
|
|
/// The ROS connection.
|
|
Ros ros;
|
|
|
|
/// Name of the service.
|
|
String name;
|
|
|
|
/// Type of the service.
|
|
String type;
|
|
|
|
/// Advertiser that is listened to for service requests when advertising.
|
|
Stream<Map<String, dynamic>>? _advertiser;
|
|
|
|
/// Checks whether or not the service is currently advertising.
|
|
bool get isAdvertised => _advertiser != null;
|
|
|
|
StreamSubscription? listener;
|
|
|
|
/// Call the service with a request ([req]).
|
|
Future call(dynamic req) {
|
|
// The service can't be called if it's currently advertising.
|
|
if (isAdvertised) return Future.value(false);
|
|
// Set up the response receiver by filtering data from the ROS node by the ID generated.
|
|
final callId = ros.requestServiceCaller(name);
|
|
final receiver = ros.stream.where((message) => message['id'] == callId).map(
|
|
(Map<String, dynamic> message) => message['result'] == null
|
|
? Future.error(message['values']!)
|
|
: Future.value(message['values']));
|
|
// Wait for the receiver to receive a single response and then return.
|
|
final completer = Completer();
|
|
|
|
listener = receiver.listen((d) {
|
|
completer.complete(d);
|
|
listener!.cancel();
|
|
});
|
|
// Actually send the request.
|
|
ros.send(Request(
|
|
op: 'call_service',
|
|
id: callId,
|
|
service: name,
|
|
type: type,
|
|
args: req,
|
|
));
|
|
return completer.future;
|
|
}
|
|
|
|
// Advertise the service and provide a [handler] to deal with requests.
|
|
Future<void> advertise(ServiceHandler handler) async {
|
|
if (isAdvertised) return;
|
|
// Send the advertise request.
|
|
ros.send(Request(
|
|
op: 'advertise_service',
|
|
type: type,
|
|
service: name,
|
|
));
|
|
// Listen for requests, forward them to the handler and then
|
|
// send the response back to the ROS node.
|
|
_advertiser = ros.stream;
|
|
_advertiser!.listen((Map<String, dynamic> message) async {
|
|
if (message['service'] != name) {
|
|
return;
|
|
}
|
|
Map<String, dynamic>? resp = await handler(message['args']);
|
|
ros.send(Request(
|
|
op: 'service_response',
|
|
id: message['id'],
|
|
service: name,
|
|
values: resp ?? {},
|
|
result: resp != null,
|
|
));
|
|
});
|
|
|
|
/*
|
|
_advertiser = ros.stream
|
|
.where((message) => message['service'] == name)
|
|
.asyncMap((req) => handler(req['args'])!.then((resp) {
|
|
ros.send(Request(
|
|
op: 'service_response',
|
|
id: req['id'],
|
|
service: name,
|
|
values: resp ?? {},
|
|
result: resp != null,
|
|
));
|
|
}));
|
|
*/
|
|
}
|
|
|
|
// Stop advertising the service.
|
|
void unadvertise() {
|
|
if (!isAdvertised) return;
|
|
ros.send(Request(
|
|
op: 'unadvertise_service',
|
|
service: name,
|
|
));
|
|
_advertiser = null;
|
|
}
|
|
}
|