Tue Feb 22 09:54:27 PM HKT 2022
61
README.md
@ -45,6 +45,21 @@ ros2 run tutorial publisher
|
||||
cd example/subscriber
|
||||
flutter run -d linux
|
||||
```
|
||||
### Subscribe using roslibdart
|
||||
```
|
||||
ros = Ros(url: 'ws://127.0.0.1:9090');
|
||||
chatter = Topic(ros: ros, name: '/topic', type: "std_msgs/String", reconnectOnClose: true, queueLength: 10, queueSize: 10);
|
||||
ros.connect();
|
||||
await chatter.subscribe(subscribeHandler);
|
||||
|
||||
Future<void> subscribeHandler(Map<String, dynamic> msg) async {
|
||||
msgReceived = json.encode(msg);
|
||||
setState(() {});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Testing subscriber
|
||||
### Run topic subscriber
|
||||
```
|
||||
@ -54,8 +69,16 @@ ros2 run tutorial subscriber
|
||||
```
|
||||
cd example/publisher
|
||||
flutter run -d linux
|
||||
|
||||
```
|
||||
### Publish using roslibdart
|
||||
```
|
||||
ros = Ros(url: 'ws://127.0.0.1:9090');
|
||||
chatter = Topic(ros: ros, name: '/topic', type: "std_msgs/String", reconnectOnClose: true, queueLength: 10, queueSize: 10);
|
||||
ros.connect();
|
||||
Map<String, dynamic> json = {"data": msgToPublished.toString()};
|
||||
await chatter.publish(json);
|
||||
```
|
||||
|
||||
## Testing call
|
||||
### Run tutorial addtwoint service
|
||||
```
|
||||
@ -65,18 +88,46 @@ ros2 run tutorial service
|
||||
cd example/client
|
||||
flutter run -d linux
|
||||
```
|
||||
|
||||
|
||||
### Call a service using roslibdart
|
||||
```
|
||||
ros = Ros(url: 'ws://127.0.0.1:9090');
|
||||
service = Service(name: 'add_two_ints', ros: ros, type: "tutorial_interfaces/AddTwoInts");
|
||||
ros.connect();
|
||||
Map<String, dynamic> json = {"a": 1, "b": 2};
|
||||
Map<String, dynamic> result = await service.call(json);
|
||||
msgToPublished = result['sum'];
|
||||
```
|
||||
## Testing providing service
|
||||
### Fire up flutter service
|
||||
|
||||
|
||||
```
|
||||
cd example/service
|
||||
flutter run -d linux
|
||||
```
|
||||
### Run tutorial call
|
||||
```
|
||||
ros2 run tutorial client
|
||||
```
|
||||
|
||||
### Run the client from shell
|
||||
```
|
||||
ros2 run tutorial client 2 3
|
||||
```
|
||||
### Provide service using roslibdart
|
||||
```
|
||||
ros = Ros(url: 'ws://127.0.0.1:9090');
|
||||
service = Service(name: 'add_two_ints', ros: ros, type: "tutorial_interfaces/AddTwoInts");
|
||||
ros.connect();
|
||||
await service.advertise(serviceHandler);
|
||||
|
||||
Future<Map<String, dynamic>>? serviceHandler(Map<String, dynamic> args) async {
|
||||
Map<String, dynamic> response = {};
|
||||
response['sum'] = args['a'] + args['b'];
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Links
|
||||
- [ROSBridge Protocol v2.0](https://github.com/biobotus/rosbridge_suite/blob/master/ROSBRIDGE_PROTOCOL.md).
|
||||
- [Original roslib library from Conrad Heidebrecht](https://github.com/Eternali/roslib)
|
||||
- [RosBridge server implementation](https://github.com/RobotWebTools/rosbridge_suite)
|
||||
- [roslibjs example](https://github.com/RobotWebTools/roslibjs/blob/develop/examples/simple.html)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# client
|
||||
# caller
|
||||
|
||||
A new Flutter project.
|
||||
|
@ -43,7 +43,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.client"
|
||||
applicationId "com.example.caller"
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.client">
|
||||
package="com.example.caller">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.client">
|
||||
package="com.example.caller">
|
||||
<application
|
||||
android:label="client"
|
||||
android:label="caller"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
@ -1,4 +1,4 @@
|
||||
package com.example.client
|
||||
package com.example.caller
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.client">
|
||||
package="com.example.caller">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
@ -294,7 +294,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.client;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -422,7 +422,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.client;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@ -444,7 +444,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.client;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 564 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Client</string>
|
||||
<string>Caller</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>client</string>
|
||||
<string>caller</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
@ -14,11 +14,11 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Roslibdart Client Example',
|
||||
title: 'Roslibdart Caller Example',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'Roslibdart Client Example'),
|
||||
home: const MyHomePage(title: 'Roslibdart Caller Example'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -54,10 +54,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
});
|
||||
}
|
||||
|
||||
void initConnection() async {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void destroyConnection() async {
|
||||
await ros.close();
|
||||
setState(() {});
|
@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
set(BINARY_NAME "client")
|
||||
set(APPLICATION_ID "com.example.client")
|
||||
set(BINARY_NAME "caller")
|
||||
set(APPLICATION_ID "com.example.caller")
|
||||
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "client");
|
||||
gtk_header_bar_set_title(header_bar, "caller");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "client");
|
||||
gtk_window_set_title(window, "caller");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
@ -8,7 +8,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:client/main.dart';
|
||||
import 'package:caller/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -23,13 +23,13 @@
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="client">
|
||||
<meta name="apple-mobile-web-app-title" content="caller">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>client</title>
|
||||
<title>caller</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "client",
|
||||
"short_name": "client",
|
||||
"name": "caller",
|
||||
"short_name": "caller",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
@ -49,10 +49,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
});
|
||||
}
|
||||
|
||||
void initConnection() async {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void destroyConnection() async {
|
||||
await ros.close();
|
||||
setState(() {});
|
||||
@ -81,7 +77,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text('return answer ' + msgToPublished.toString()),
|
||||
Text('Service returns answer ' + msgToPublished.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:roslibdart/roslibdart.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
void main() {
|
||||
runApp(const ExampleApp());
|
||||
@ -37,12 +39,11 @@ class _HomePageState extends State<HomePage> {
|
||||
queueLength: 10,
|
||||
queueSize: 10);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void initConnection() async {
|
||||
ros.connect();
|
||||
await chatter.subscribe();
|
||||
setState(() {});
|
||||
Timer(const Duration(seconds: 3), () async {
|
||||
await chatter.subscribe(subscribeHandler);
|
||||
// await chatter.subscribe();
|
||||
});
|
||||
}
|
||||
|
||||
void destroyConnection() async {
|
||||
@ -51,51 +52,28 @@ class _HomePageState extends State<HomePage> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
String msgReceived = '';
|
||||
Future<void> subscribeHandler(Map<String, dynamic> msg) async {
|
||||
msgReceived = json.encode(msg);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Roslibdart Subscriber Example'),
|
||||
),
|
||||
body: StreamBuilder<Object>(
|
||||
stream: ros.statusStream,
|
||||
builder: (context, snapshot) {
|
||||
return Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
StreamBuilder<Map<String, dynamic>>(
|
||||
stream: chatter.subscription,
|
||||
builder: (BuildContext context2,
|
||||
AsyncSnapshot<Map<String, dynamic>> snapshot2) {
|
||||
if (snapshot2.hasData && snapshot2.data != null) {
|
||||
return Text('${snapshot2.data!['msg']}');
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
ActionChip(
|
||||
label: Text(snapshot.data == Status.connected
|
||||
? 'DISCONNECT'
|
||||
: 'CONNECT'),
|
||||
backgroundColor: snapshot.data == Status.connected
|
||||
? Colors.green[300]
|
||||
: Colors.grey[300],
|
||||
onPressed: () {
|
||||
// print(snapshot.data);
|
||||
if (snapshot.data != Status.connected) {
|
||||
initConnection();
|
||||
} else {
|
||||
destroyConnection();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(msgReceived + ' received'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import 'dart:async';
|
||||
import 'ros.dart';
|
||||
import 'request.dart';
|
||||
|
||||
// Receiver function to handle requests when the service is advertising.
|
||||
typedef SubscribeHandler = Future<void> Function(Map<String, dynamic> args);
|
||||
|
||||
/// Wrapper to interact with ROS topics.
|
||||
class Topic {
|
||||
Topic({
|
||||
@ -72,7 +75,7 @@ class Topic {
|
||||
///
|
||||
/// Defaults to true.
|
||||
bool reconnectOnClose;
|
||||
|
||||
/*
|
||||
/// Subscribe to the topic if not already subscribed.
|
||||
Future<void> subscribe() async {
|
||||
if (subscribeId == null) {
|
||||
@ -91,6 +94,30 @@ class Topic {
|
||||
));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Future<void> subscribe(SubscribeHandler subscribeHandler) async {
|
||||
if (subscribeId == null) {
|
||||
// Create the listenable broadcast subscription stream.
|
||||
subscription = ros.stream;
|
||||
subscribeId = ros.requestSubscriber(name);
|
||||
await safeSend(Request(
|
||||
op: 'subscribe',
|
||||
id: subscribeId,
|
||||
type: type,
|
||||
topic: name,
|
||||
compression: compression,
|
||||
throttleRate: throttleRate,
|
||||
queueLength: queueLength,
|
||||
));
|
||||
subscription!.listen((Map<String, dynamic> message) async {
|
||||
if (message['topic'] != name) {
|
||||
return;
|
||||
}
|
||||
await subscribeHandler(message['msg']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Unsubscribe from the topic.
|
||||
Future<void> unsubscribe() async {
|
||||
|