Flutter communication with ROS video streaming
October 15, 2021
8 mins read
Introduction
The main goal of the project is to stablish a communication between a Flutter desktop application and the ROS node. Also, to send and receive messages and visualize them in the app. The result of the implementation will be shown in Gazebo and the app itself.
Roslib
We are using Roslib ^0.0.3
, a flutter library for communicating to a ROS node over websockets with rosbridge, influenced by roslibjs, according to its author.
Websockets in ROS
In order to work with websockets, it is neccesary to be able to execute it in a ROS node. That’s why we are using rosbridge_suite
package. To install it, you can execute the following command:
1
sudo apt-get install ros-<rosdistro>-rosbridge-server
Where <rosditro>
is the workspace ROS version. In our case, it is melodic
.
ROS
Before moving to the code, it is important to review some ROS concepts such as node
and topics
.
According to the ROS wiki, a node is a process that performs computation. Nodes are combined together into a graph and communicate with one another using streaming topics, RPC services, and the Parameter Server. It is an executable that ROS uses to communicate with other nodes.
In the other side, topics are named buses over which nodes exchange messages. Topics have anonymous publish/subscribe semantics, which decouples the production of information from its consumption. In that way, nodes can publish messages to a topic as well as subscribe to receive messages.
We are using these concepts to start building our widgets in Flutter.
Widget
ROS connection
For the creation of the app we are going to create a Widget called JoyStickPage
that returns a Scaffold
widget. Inside of it we define our variables to initiate our Ros
and Topic
instances.
We’ll work with the camera
, cmd_vel
, and imu
topics of the turtlebot robot.
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
class JoyStickPage extends StatefulWidget {
@override
_JoyStickPageState createState() => _JoyStickPageState();
}
class _JoyStickPageState extends State<JoyStickPage> {
late Ros ros;
late Topic cmd_vel;
late Topic imu;
late Topic camera;
@override
void initState() {
ros = Ros(url: 'ws://0.0.0.0:9090');
cmd_vel = Topic(
ros: ros,
name: '/cmd_vel',
type: "geometry_msgs/Twist",
reconnectOnClose: true,
queueLength: 10,
queueSize: 10);
imu = Topic(
ros: ros,
name: 'imu',
type: 'sensor_msgs/Imu',
queueSize: 10,
queueLength: 10,
);
camera = Topic(
ros: ros,
name: 'camera/image/compressed',
type: 'sensor_msgs/CompressedImage',
queueSize: 10,
queueLength: 10,
);
super.initState();
}
void initConnection() async {
ros.connect();
await cmd_vel.subscribe();
await imu.subscribe();
await camera.subscribe();
setState(() {});
}
void destroyConnection() async {
await cmd_vel.unsubscribe();
await imu.unsubscribe();
await camera.unsubscribe();
await ros.close();
setState(() {});
}
We also declare two void functions that help us publishing the messsages from de joystick to the cmd_vel
topic of the turtlebot.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void publishCmd(double _linear_speed, double _angular_speed) async {
var linear = {'x': _linear_speed, 'y': 0.0, 'z': 0.0};
var angular = {'x': 0.0, 'y': 0.0, 'z': _angular_speed};
var twist = {'linear': linear, 'angular': angular};
await cmd_vel.publish(twist);
print('cmd published');
publishCounter();
}
void _move(double _degrees, double _distance) {
print(
'Degree:' + _degrees.toString() + ' Distance:' + _distance.toString());
double radians = _degrees * ((22 / 7) / 180);
double linear_speed = cos(radians) * _distance;
double angular_speed = -sin(radians) * _distance;
publishCmd(linear_speed, angular_speed);
}
Joystick
For the plotting of the Joystick, we are using the flutter library control_pad
which can be found here.
Image
We also need to build a Widget able to show the image received from the camera topic of our robot. in order to do that, we create a function to get the image from the string message that returns from the topic. The encoding given to the images is using base64, so we decode in the same way:
1
2
3
4
5
6
7
8
9
10
11
12
13
Widget getImagenBase64(String imagen) {
var _imageBase64 = imagen;
const Base64Codec base64 = Base64Codec();
if (_imageBase64 == null) return new Container(child: Text('Image'),);
var bytes = base64.decode(_imageBase64);
return Image.memory(
bytes,
gaplessPlayback: true,
width: 400,
fit: BoxFit.fitWidth,
);
}
Streaming
Our app will be constantly reading the new messages of the websocket node thanks to the StreamBuilder
Widget declare as below. The connection will be triggered with a button of class ActionChip
. Finally, we call the getImageBase64()
to render the image in our app with an embedded HTML tag.
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
...
body: StreamBuilder<Object>(
stream: ros.statusStream,
builder: (context, snapshot) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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) {
this.initConnection();
} else {
this.destroyConnection();
}
},
),
Padding(padding: EdgeInsets.all(10)),
Container(
child: JoystickView(
onDirectionChanged: ( double degrees, double distance) {
_move(degrees, distance);
},
),),
Padding(padding: EdgeInsets.all(20)),
StreamBuilder(
stream: camera.subscription,
builder: (context2,AsyncSnapshot<dynamic> snapshot2){
if (snapshot2.hasData){
return Html(
data: """<img src="data:https//image/jpeg;base64,${snapshot2.data['msg']['data']}" >"""
);
}
else{
return CircularProgressIndicator();
}
}
),
]
),
);
},
...
Final result
For details of the implementation see the project repo.