I'm working on a teleoperation app. I use roslib to implement ros features into Flutter, but there are problems with connection on Android. Snapshot.connectionState is always waiting and Stream status changes from null in the beggining to Status.CLOSED. Moreover Android Studio gives the following error:
E/BpSurfaceComposerClient(15711): Failed to transact (-1)
UPD: When I try to connect using ros = Ros(url: 'ws://10.0.2.15:9090');
, "CONNECT" button changes to "DISCONNECT", that is supposed to mean proper connection, but cmd_vel
topic doesn't receive any information from app, so robot just stands still and, moreover, connection can't be terminated by clicking "DISCONNECT" button.
And here's my code
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:roslib/roslib.dart';
import 'dart:convert';
class mainPage extends StatefulWidget {
const mainPage({Key? key}) : super(key: key);
@override
State<mainPage> createState() => _mainPageState();
}
class _mainPageState extends State<mainPage> {
int linearCounter = 0;
int angularCounter = 0;
late Ros ros;
late Topic cmd_vel;
late Topic image_raw;
@override
void initState() {
ros = Ros(url: 'ws://10.0.2.15:9090');
image_raw = Topic(
ros: ros,
name: '/camera/rgb/image_raw/compressed',
type: "sensor_msgs/CompressedImage",
reconnectOnClose: true,
queueLength: 10,
queueSize: 10,
);
cmd_vel = Topic(
ros: ros,
name: '/cmd_vel',
type: "geometry_msgs/Twist",
reconnectOnClose: true,
queueLength: 10,
queueSize: 10
);
super.initState();
}
void move(double coordinate, double angle) {
double linearSpeed = 0.0;
double angularSpeed = 0.0;
linearSpeed = linearCounter * coordinate;
angularSpeed = angularCounter * angle;
publishCmd(linearSpeed, angularSpeed);
}
void initConnection() async {
ros.connect();
await image_raw.subscribe();
await cmd_vel.advertise();
setState(() {});
}
void destroyConnection() async {
await image_raw.unsubscribe();
await ros.close();
setState(() {});
}
void publishCmd(double coord, double ang) async {
var linear = {'x': coord, 'y': 0.0, 'z': 0.0};
var angular = {'x': 0.0, 'y': 0.0, 'z': ang};
var geomTwist = {'linear': linear, 'angular': angular};
await cmd_vel.publish(geomTwist);
if (kDebugMode) {
print('cmd published');
}
}
Widget getImage(String vid_snapshot) {
const Base64Codec base64 = Base64Codec();
return Flexible(
flex: 1,
child: Image.memory(
base64.decode(vid_snapshot),
gaplessPlayback: true,
height: 400,
fit: BoxFit.fill,
),
);
}
@override
Widget build(BuildContext context) {
return StreamBuilder<Object>(
stream: ros.statusStream,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Scaffold(
backgroundColor: Colors.grey[700],
appBar: AppBar(
title: const Text('Teleoperation app'),
centerTitle: true
),
body: SafeArea(
minimum: const EdgeInsets.only(bottom: 30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder(
stream: image_raw.subscription,
builder: (vidContext, AsyncSnapshot<dynamic> vidSnapshot) {
if (vidSnapshot.data == null) {
return const Text('No signal');
}
Map<String, dynamic> value = Map<String, dynamic>.from(vidSnapshot.data);
return getImage(value['msg']['data']);
},
),
],
),
Padding(padding: EdgeInsets.only(bottom: 30.0)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
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();
}
},
),
],
),
Padding(padding: EdgeInsets.only(bottom: 15.0)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
minimumSize: Size(130.0, 40.0),
),
onPressed: () {
linearCounter++;
move(0.1, 0.0);
},
icon: Icon(Icons.arrow_upward),
label: Text('Forward')
),
],
),
Padding(padding: EdgeInsets.only(bottom: 30.0)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
minimumSize: Size(130.0, 40.0),
),
onPressed: () {
angularCounter++;
move(0.0, 0.1);
},
icon: Icon(Icons.arrow_back),
label: Text('Left')
),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
minimumSize: Size(130.0, 40.0),
),
onPressed: () {
angularCounter = 0;
linearCounter = 0;
move(0.0, 0.0);
},
icon: Icon(Icons.stop_circle_outlined),
label: Text('Stop')
),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
minimumSize: Size(130.0, 40.0),
),
onPressed: () {
angularCounter--;
move(0.0, 0.1);
},
icon: Icon(Icons.arrow_forward),
label: Text('Right')
),
],
),
Padding(padding: EdgeInsets.only(bottom: 30.0)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
minimumSize: Size(130.0, 40.0),
),
onPressed: () {
linearCounter--;
move(0.1, 0.0);
},
icon: Icon(Icons.arrow_downward),
label: Text('Backward')
),
],
),
],
),
),
);
},
);
}
}
I've managed to solve the problem. Your phone and computer should be connected to the same Wi-Fi network. In your ~/.bashrc
file you should add two lines:
export ROS_MASTER_URI=http://192.168.1.110:11311
export ROS_IP=192.168.1.110
192.168.1.110
is IP address of your computer in the network.
In your app change line ros = Ros(url: 'ws://10.0.2.15:9090');
to ros = Ros(url: 'ws://192.168.1.110:9090');
And that's the solution. The main reason of my problem was that I did everything on virtual machine and couldn't figure out where the mistake was, even though the virtual machine itself caused an error.