flutter_industrial_scale 0.0.1 copy "flutter_industrial_scale: ^0.0.1" to clipboard
flutter_industrial_scale: ^0.0.1 copied to clipboard

Professional-grade weighing scale plugin. Connect via BLE, Bluetooth, USB, and Serial with built-in support for MT-SICS and CAS industrial protocols.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_industrial_scale/flutter_industrial_scale.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ScaleExampleApp(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

class ScaleExampleApp extends StatefulWidget {
  const ScaleExampleApp({super.key});

  @override
  State<ScaleExampleApp> createState() => _ScaleExampleAppState();
}

class _ScaleExampleAppState extends State<ScaleExampleApp> {
  final IndustrialScale _industrialScale = IndustrialScale.instance;
  final List<ScaleDevice> _devices = [];
  ScaleConnection? _connection;
  WeightReading? _lastReading;
  bool _isScanning = false;

  ScaleParser _selectedParser = const AsciiParser();
  final List<ScaleParser> _parsers = const [
    AsciiParser(),
    MtSicsParser(),
    CasParser(),
  ];

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

  void _startScan() {
    setState(() {
      _devices.clear();
      _isScanning = true;
    });

    _industrialScale.scan().listen(
      (device) {
        if (!_devices.any((d) => d.id == device.id)) {
          setState(() {
            _devices.add(device);
          });
        }
      },
      onDone: () => setState(() => _isScanning = false),
      onError: (e) {
        setState(() => _isScanning = false);
        if (e is ScaleNativeException) {
          if (!mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(
                'Native Hardware Bridge Unavailable: $e\nRunning in Simulator (Mock) mode.',
              ),
              duration: const Duration(seconds: 5),
            ),
          );
        }
      },
    );
  }

  Future<void> _connect(ScaleDevice device) async {
    try {
      if (_connection != null) {
        await _connection!.disconnect();
      }

      final connection = await _industrialScale.connect(
        device,
        parser: _selectedParser,
      );
      setState(() {
        _connection = connection;
        _lastReading = null;
      });

      connection.weightStream.listen(
        (reading) {
          setState(() => _lastReading = reading);
        },
        onError: (e) {
          if (!mounted) return;
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(SnackBar(content: Text('Stream Error: $e')));
        },
      );
    } catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('Connection Error: $e')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Industrial Scale Example'),
        backgroundColor: Colors.blueGrey[900],
        foregroundColor: Colors.white,
        actions: [
          if (_isScanning)
            const Center(
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(
                    color: Colors.white,
                    strokeWidth: 2,
                  ),
                ),
              ),
            )
          else
            IconButton(onPressed: _startScan, icon: const Icon(Icons.refresh)),
        ],
      ),
      body: Column(
        children: [
          _buildDisplayCard(),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: DropdownMenu<ScaleParser>(
              initialSelection: _selectedParser,
              expandedInsets: EdgeInsets.zero,
              label: const Text('Communication Protocol / Parser'),
              dropdownMenuEntries: _parsers.map((p) {
                return DropdownMenuEntry(value: p, label: p.name);
              }).toList(),
              onSelected: (p) {
                if (p != null) setState(() => _selectedParser = p);
              },
            ),
          ),
          const Divider(height: 1),
          Expanded(child: _buildDeviceList()),
        ],
      ),
    );
  }

  Widget _buildDisplayCard() {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.all(16),
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: BorderRadius.circular(12),
        boxShadow: const [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 10,
            offset: Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                _connection != null ? 'CONNECTED' : 'DISCONNECTED',
                style: TextStyle(
                  color: _connection != null ? Colors.green : Colors.red,
                  fontWeight: FontWeight.bold,
                  fontSize: 12,
                ),
              ),
              if (_lastReading != null)
                Text(
                  _lastReading!.isStable ? 'STABLE' : 'UNSTABLE',
                  style: TextStyle(
                    color: _lastReading!.isStable ? Colors.blue : Colors.amber,
                    fontWeight: FontWeight.bold,
                    fontSize: 12,
                  ),
                ),
            ],
          ),
          const SizedBox(height: 16),
          FittedBox(
            child: Text(
              _lastReading?.value.toStringAsFixed(3) ?? '0.000',
              style: const TextStyle(
                color: Colors.greenAccent,
                fontSize: 80,
                fontFamily: 'monospace',
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          Text(
            _lastReading?.unit ?? '--',
            style: const TextStyle(color: Colors.greenAccent, fontSize: 24),
          ),
          if (_connection != null) ...[
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: () => _connection?.tare(),
                  icon: const Icon(Icons.exposure_zero),
                  label: const Text('TARE'),
                ),
                const SizedBox(width: 16),
                ElevatedButton.icon(
                  onPressed: () async {
                    try {
                      await _connection?.disconnect();
                    } catch (e) {
                      debugPrint('Disconnect error: $e');
                    } finally {
                      setState(() => _connection = null);
                    }
                  },
                  icon: const Icon(Icons.close),
                  label: const Text('CLOSE'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red[900],
                    foregroundColor: Colors.white,
                  ),
                ),
              ],
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildDeviceList() {
    if (_devices.isEmpty && !_isScanning) {
      return const Center(
        child: Text(
          'No devices found.\nTry bonded Bluetooth or USB devices.',
          textAlign: TextAlign.center,
        ),
      );
    }

    return ListView.builder(
      itemCount: _devices.length,
      itemBuilder: (context, index) {
        final device = _devices[index];
        return ListTile(
          leading: CircleAvatar(
            backgroundColor: _getColorForType(device.type),
            child: Icon(_getIconForType(device.type), color: Colors.white),
          ),
          title: Text(
            device.name,
            style: const TextStyle(fontWeight: FontWeight.bold),
          ),
          subtitle: Text('${device.type.name.toUpperCase()} • ${device.id}'),
          trailing: const Icon(Icons.connect_without_contact),
          onTap: () => _connect(device),
        );
      },
    );
  }

  IconData _getIconForType(TransportType type) {
    switch (type) {
      case TransportType.ble:
        return Icons.bluetooth_audio;
      case TransportType.bluetooth:
        return Icons.bluetooth;
      case TransportType.usb:
        return Icons.usb;
      case TransportType.serial:
        return Icons.settings_input_component;
      case TransportType.mock:
        return Icons.bug_report;
    }
  }

  Color _getColorForType(TransportType type) {
    switch (type) {
      case TransportType.ble:
        return Colors.blue;
      case TransportType.bluetooth:
        return Colors.indigo;
      case TransportType.usb:
        return Colors.orange;
      case TransportType.serial:
        return Colors.purple;
      case TransportType.mock:
        return Colors.grey;
    }
  }
}
1
likes
160
points
86
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Professional-grade weighing scale plugin. Connect via BLE, Bluetooth, USB, and Serial with built-in support for MT-SICS and CAS industrial protocols.

Repository (GitHub)
View/report issues

Topics

#hardware #bluetooth #serial

License

MIT (license)

Dependencies

ffi, flutter, hooks, logging, meta, native_toolchain_c, plugin_platform_interface

More

Packages that depend on flutter_industrial_scale

Packages that implement flutter_industrial_scale