ros2_flutter 0.1.0 copy "ros2_flutter: ^0.1.0" to clipboard
ros2_flutter: ^0.1.0 copied to clipboard

Flutter widgets for ROS 2 — connection lifecycle, camera views, laser scan rendering and a teleop joystick, built on ros2_client.

example/lib/main.dart

// A minimal robot control panel: live telemetry, camera, lidar and teleop.
//
// On the robot:
//   ros2 launch rosbridge_server rosbridge_websocket_launch.xml
import 'package:flutter/material.dart';
import 'package:ros2_flutter/ros2_flutter.dart';

void main() {
  registerStandardMessages();
  runApp(const RobotApp());
}

class RobotApp extends StatelessWidget {
  const RobotApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ROS 2 Control Panel',
      theme: ThemeData(
        colorSchemeSeed: Colors.teal,
        brightness: Brightness.dark,
      ),
      home: RosConnection(
        // Point this at your robot.
        uri: Uri.parse('ws://127.0.0.1:9090'),
        defaultCompression: Compression.cbor,
        child: const ControlPanel(),
      ),
    );
  }
}

class ControlPanel extends StatelessWidget {
  const ControlPanel({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Robot'),
        actions: const [
          Padding(
            padding: EdgeInsets.only(right: 12),
            child: Center(child: _ConnectionChip()),
          ),
        ],
      ),
      body: RosConnectionBuilder(
        builder: (context, state) {
          if (state != RosConnectionState.connected) {
            return const _Offline();
          }
          return LayoutBuilder(
            builder: (context, constraints) {
              final wide = constraints.maxWidth > 720;
              final panels = [
                const Expanded(child: _Card(title: 'Camera', child: RosCameraView())),
                const Expanded(child: _Card(title: 'Lidar', child: RosLaserScanView())),
              ];
              return Column(
                children: [
                  const _Telemetry(),
                  Expanded(
                    child: wide
                        ? Row(children: panels)
                        : Column(children: panels),
                  ),
                  const Padding(
                    padding: EdgeInsets.all(16),
                    child: TeleopJoystick(),
                  ),
                ],
              );
            },
          );
        },
      ),
    );
  }
}

class _ConnectionChip extends StatelessWidget {
  const _ConnectionChip();

  @override
  Widget build(BuildContext context) {
    return RosConnectionBuilder(
      builder: (context, state) {
        final (color, label) = switch (state) {
          RosConnectionState.connected => (Colors.green, 'Connected'),
          RosConnectionState.connecting => (Colors.amber, 'Connecting'),
          RosConnectionState.reconnecting => (Colors.orange, 'Reconnecting'),
          RosConnectionState.disconnected => (Colors.red, 'Offline'),
          RosConnectionState.closed => (Colors.grey, 'Closed'),
        };
        return Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.circle, size: 10, color: color),
            const SizedBox(width: 6),
            Text(label),
          ],
        );
      },
    );
  }
}

class _Offline extends StatelessWidget {
  const _Offline();

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          CircularProgressIndicator(),
          SizedBox(height: 16),
          Text('Waiting for the robot…'),
          SizedBox(height: 8),
          Text('ros2 launch rosbridge_server rosbridge_websocket_launch.xml',
              style: TextStyle(fontFamily: 'monospace', fontSize: 12)),
        ],
      ),
    );
  }
}

/// Battery and odometry readouts.
class _Telemetry extends StatelessWidget {
  const _Telemetry();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(12),
      child: Row(
        children: [
          Expanded(
            child: RosTopicBuilder<BatteryState>(
              topic: '/battery_state',
              builder: (context, battery) => _Stat(
                icon: Icons.battery_full,
                label: 'Battery',
                value: battery == null
                    ? '—'
                    : '${(battery.percentage * 100).round()}%',
              ),
            ),
          ),
          Expanded(
            child: RosTopicBuilder<Odometry>(
              topic: '/odom',
              throttleRate: 200,
              builder: (context, odom) => _Stat(
                icon: Icons.my_location,
                label: 'Position',
                value: odom == null
                    ? '—'
                    : '${odom.pose.position.x.toStringAsFixed(2)}, '
                        '${odom.pose.position.y.toStringAsFixed(2)}',
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _Stat extends StatelessWidget {
  const _Stat({required this.icon, required this.label, required this.value});

  final IconData icon;
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Icon(icon, size: 20),
        const SizedBox(width: 8),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(label, style: Theme.of(context).textTheme.labelSmall),
            Text(value, style: Theme.of(context).textTheme.titleMedium),
          ],
        ),
      ],
    );
  }
}

class _Card extends StatelessWidget {
  const _Card({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(8),
      clipBehavior: Clip.antiAlias,
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8),
            child: Text(title, style: Theme.of(context).textTheme.titleSmall),
          ),
          Expanded(child: child),
        ],
      ),
    );
  }
}
0
likes
160
points
52
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter widgets for ROS 2 — connection lifecycle, camera views, laser scan rendering and a teleop joystick, built on ros2_client.

Repository (GitHub)
View/report issues

Topics

#ros #ros2 #robotics #teleop #widget

License

BSD-3-Clause (license)

Dependencies

flutter, ros2_client

More

Packages that depend on ros2_flutter