Sarah's Forge.Dev
← All Projects
flutter_benlink@SarahRoseLives:~
flutter_benlink
A flutter port of the Benshi protocol from benlink
β˜… 0   🍴 0
View on GitHub

flutter_benlink

A Dart port of the excellent Python library benlink by Kyle Husmann, designed for controlling Benshi-protocol radios (like the BTech GMRS-PRO) from a Flutter application.

This library provides the low-level protocol implementation and a high-level RadioController to make it easy to interact with your radio over Bluetooth. The primary goal is to empower developers to create custom, open-source mobile applications for their hardware.

This project is a work in progress, with the ultimate goal of supporting the full feature set of the original benlink library.


⚠️ Disclaimer

This project is an independent effort and is not affiliated with or endorsed by Benshi, BTech, Vero, or any other radio manufacturer. The protocol has been reverse-engineered. Use this library at your own risk. The authors are not responsible for any damage caused to your radio or other equipment.


🎯 Current Focus: Android + Bluetooth Classic

The initial version of this library is focused on providing core functionality for Android apps using Bluetooth Classic (RFCOMM).


βœ… Supported Features

This library already supports the core, non-audio features of the radio protocol:

  • Connection Management: Connects to the radio via flutter_bluetooth_serial.

  • High-Level Controller: An easy-to-use RadioController that manages state using ChangeNotifier, making it simple to integrate with your Flutter UI.

  • Device Info: Read the radio's model, vendor, version, and supported features.

  • Radio Settings: Read and write the main settings block of the radio.

  • Channel Management:

    • Read and write individual channel configurations (frequency, name, tones, etc.).

    • Fetch a list of all channels from the radio.

  • Real-time Status:

    • Get live status updates (Power, TX/RX state, Scan state, GPS lock, etc.).

    • Subscribe to events for changes in status, settings, and the current channel.

  • Power & GPS:

    • Read the current battery voltage and percentage.

    • Fetch the last known GPS position from the device.

  • VFO Control: Read the VFO channel and set its frequency.


πŸ—ΊοΈ Roadmap to Full benlink Compatibility

The goal is to implement the entire feature set of the original Python library. Pull requests are welcome!

  • Audio Streaming: Implement real-time audio capture (TX) and playback (RX) over the audio RFCOMM channel. This is the highest priority feature.

  • TNC / Packet Support: Add support for sending and receiving APRS/BSS data packets and managing BeaconSettings.

  • BLE Support: Add a BleCommandLink to allow connections over Bluetooth Low Energy, in addition to the current RFCOMM link.

  • Expanded Command Set: Implement the remaining commands from the protocol (e.g., programmable function buttons, advanced settings, etc.).

  • Firmware Flashing: As a long-term goal, investigate and implement the firmware update process.


πŸ“¦ Installation

This package is not yet on pub.dev. To use it in your project, add it to your pubspec.yaml as a git dependency:

dependencies:
  flutter_benlink:
    git:
      url: https://github.com/SarahRoseLives/flutter_benlink.git
      ref: main 

Then, run flutter pub get in your terminal.

πŸš€ Quick Start

Ensure you have flutter_bluetooth_serial set up in your Android project.

Use ChangeNotifierProvider (from the provider package) to make the RadioController available to your widget tree.

import 'package:flutter/material.dart';
import 'package:flutter_benlink/flutter_benlink.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:provider/provider.dart';

class RadioInfoScreen extends StatefulWidget {
  final BluetoothDevice server; // The paired radio device

  const RadioInfoScreen({Key? key, required this.server}) : super(key: key);

  @override
  _RadioInfoScreenState createState() => _RadioInfoScreenState();
}

class _RadioInfoScreenState extends State<RadioInfoScreen> {
  RadioController? _radioController;

  @override
  void initState() {
    super.initState();
    _connectToDevice();
  }

  void _connectToDevice() async {
    try {
      final connection = await BluetoothConnection.toAddress(widget.server.address);
      setState(() {
        // Initialize the controller, which will start fetching radio state
        _radioController = RadioController(connection: connection);
      });
    } catch (e) {
      print('Cannot connect, exception occurred: $e');
    }
  }

  @override
  void dispose() {
    _radioController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // If the controller isn't initialized, show a loading indicator
    if (_radioController == null) {
      return Scaffold(
        appBar: AppBar(title: const Text('Connecting...')),
        body: const Center(child: CircularProgressIndicator()),
      );
    }

    // Use a ChangeNotifierProvider to provide the controller to the widget tree
    return ChangeNotifierProvider.value(
      value: _radioController,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Radio Info'),
        ),
        // Use a Consumer to listen for changes and rebuild the UI
        body: Consumer<RadioController>(
          builder: (context, radio, child) {
            // Wait until the radio is ready before showing data
            if (!radio.isReady) {
              return const Center(child: CircularProgressIndicator());
            }

            return ListView(
              padding: const EdgeInsets.all(16.0),
              children: [
                Text('Product: ${radio.deviceInfo?.productName ?? "N/A"}'),
                Text('Firmware Version: ${radio.deviceInfo?.firmwareVersion ?? "N/A"}'),
                Text('Current Channel: ${radio.currentChannelName}'),
                Text('Battery: ${radio.batteryLevelAsPercentage}% (${radio.batteryVoltage?.toStringAsFixed(2)}V)'),
                Text('GPS Locked: ${radio.isGpsLocked ? "Yes" : "No"}'),
              ],
            );
          },
        ),
      ),
    );
  }
}

πŸ™ Acknowledgements

Kyle Husmann (khusmann) for creating and open-sourcing the original benlink library and for doing the heavy lifting of reverse-engineering the protocol.

@spohtl for help with the audio protocol in the original project.

@na7q for early testing and feedback on the original project.

Developer Updates

Loading updates...