Search code examples
flutterdartros

Flutter-ROS//Desktop version of app works fine, but while running on android StatusStream is always returning Status.CLOSED


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')
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

Solution

  • 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.