Tue Feb 22 09:54:27 PM HKT 2022

This commit is contained in:
Terence Tong 2022-02-22 21:54:27 +08:00
parent 57d2e6dfd4
commit 29d8c4789a
83 changed files with 130 additions and 82 deletions

View File

@ -45,6 +45,21 @@ ros2 run tutorial publisher
cd example/subscriber cd example/subscriber
flutter run -d linux 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 ## Testing subscriber
### Run topic subscriber ### Run topic subscriber
``` ```
@ -54,8 +69,16 @@ ros2 run tutorial subscriber
``` ```
cd example/publisher cd example/publisher
flutter run -d linux 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 ## Testing call
### Run tutorial addtwoint service ### Run tutorial addtwoint service
``` ```
@ -65,18 +88,46 @@ ros2 run tutorial service
cd example/client cd example/client
flutter run -d linux 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 ## Testing providing service
### Fire up flutter service ### Fire up flutter service
```
cd example/service
flutter run -d linux
```
### Run tutorial call
```
ros2 run tutorial client
```
### Run the client from shell ### Run the client from shell
``` ```
ros2 run tutorial client 2 3 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 ## Links
- [ROSBridge Protocol v2.0](https://github.com/biobotus/rosbridge_suite/blob/master/ROSBRIDGE_PROTOCOL.md). - [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) - [Original roslib library from Conrad Heidebrecht](https://github.com/Eternali/roslib)
- [RosBridge server implementation](https://github.com/RobotWebTools/rosbridge_suite) - [RosBridge server implementation](https://github.com/RobotWebTools/rosbridge_suite)
- [roslibjs example](https://github.com/RobotWebTools/roslibjs/blob/develop/examples/simple.html)

View File

@ -1,4 +1,4 @@
# client # caller
A new Flutter project. A new Flutter project.

View File

@ -43,7 +43,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // 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 minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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 <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.client"> package="com.example.caller">
<application <application
android:label="client" android:label="caller"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View File

@ -1,4 +1,4 @@
package com.example.client package com.example.caller
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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 <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->

View File

@ -294,7 +294,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.client; PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -422,7 +422,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.client; PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -444,7 +444,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.example.client; PRODUCT_BUNDLE_IDENTIFIER = com.example.caller;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Client</string> <string>Caller</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>client</string> <string>caller</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@ -14,11 +14,11 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Roslibdart Client Example', title: 'Roslibdart Caller Example',
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, 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 { void destroyConnection() async {
await ros.close(); await ros.close();
setState(() {}); setState(() {});

View File

@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
set(BINARY_NAME "client") set(BINARY_NAME "caller")
set(APPLICATION_ID "com.example.client") set(APPLICATION_ID "com.example.caller")
cmake_policy(SET CMP0063 NEW) cmake_policy(SET CMP0063 NEW)

View File

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); 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_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "client"); gtk_window_set_title(window, "caller");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

View File

@ -8,7 +8,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:client/main.dart'; import 'package:caller/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -23,13 +23,13 @@
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <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-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"> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>client</title> <title>caller</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body> <body>

View File

@ -1,6 +1,6 @@
{ {
"name": "client", "name": "caller",
"short_name": "client", "short_name": "caller",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",

View File

@ -49,10 +49,6 @@ class _MyHomePageState extends State<MyHomePage> {
}); });
} }
void initConnection() async {
setState(() {});
}
void destroyConnection() async { void destroyConnection() async {
await ros.close(); await ros.close();
setState(() {}); setState(() {});
@ -81,7 +77,7 @@ class _MyHomePageState extends State<MyHomePage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text('return answer ' + msgToPublished.toString()), Text('Service returns answer ' + msgToPublished.toString()),
], ],
), ),
), ),

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:roslibdart/roslibdart.dart'; import 'package:roslibdart/roslibdart.dart';
import 'dart:async';
import 'dart:convert';
void main() { void main() {
runApp(const ExampleApp()); runApp(const ExampleApp());
@ -37,12 +39,11 @@ class _HomePageState extends State<HomePage> {
queueLength: 10, queueLength: 10,
queueSize: 10); queueSize: 10);
super.initState(); super.initState();
}
void initConnection() async {
ros.connect(); ros.connect();
await chatter.subscribe(); Timer(const Duration(seconds: 3), () async {
setState(() {}); await chatter.subscribe(subscribeHandler);
// await chatter.subscribe();
});
} }
void destroyConnection() async { void destroyConnection() async {
@ -51,51 +52,28 @@ class _HomePageState extends State<HomePage> {
setState(() {}); setState(() {});
} }
String msgReceived = '';
Future<void> subscribeHandler(Map<String, dynamic> msg) async {
msgReceived = json.encode(msg);
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Roslibdart Subscriber Example'), title: const Text('Roslibdart Subscriber Example'),
), ),
body: StreamBuilder<Object>( body: Center(
stream: ros.statusStream, // Center is a layout widget. It takes a single child and positions it
builder: (context, snapshot) { // in the middle of the parent.
return Center( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.center, Text(msgReceived + ' received'),
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();
}
},
),
],
),
);
}),
); );
} }
} }

View File

@ -4,6 +4,9 @@ import 'dart:async';
import 'ros.dart'; import 'ros.dart';
import 'request.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. /// Wrapper to interact with ROS topics.
class Topic { class Topic {
Topic({ Topic({
@ -72,7 +75,7 @@ class Topic {
/// ///
/// Defaults to true. /// Defaults to true.
bool reconnectOnClose; bool reconnectOnClose;
/*
/// Subscribe to the topic if not already subscribed. /// Subscribe to the topic if not already subscribed.
Future<void> subscribe() async { Future<void> subscribe() async {
if (subscribeId == null) { 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. /// Unsubscribe from the topic.
Future<void> unsubscribe() async { Future<void> unsubscribe() async {