webserial 1.2.0 copy "webserial: ^1.2.0" to clipboard
webserial: ^1.2.0 copied to clipboard

Platformweb

WebSerial support for Dart built on the web package.

example/lib/main.dart

import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;

import 'package:webserial/webserial.dart';

// Extension type to safely access the `port` property on connect/disconnect events.
extension type SerialPortEvent._(JSObject _) implements web.Event, JSObject {
  // port is actually pretty useless as according to this its always undefined in "modern browers"
  // https://github.com/WICG/serial/issues/156#issuecomment-1007087173
  // external JSSerialPort? get port;
  external JSSerialPort? get target;
}

void main() {
  runApp(const MainApp());
}

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

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  JSSerialPort? _port;
  web.ReadableStreamDefaultReader? _reader;
  bool _isReading = false;
  String _status = 'Click to connect to a serial device.';

  void setStatus(String s) {
    _status = s;
  }

  @override
  void initState() {
    super.initState();
    // Global listener is essential for automatic reconnection.
    _setupGlobalEventListeners();
  }

  /// Sets up a global listener to detect when any permitted device is reconnected.
  void _setupGlobalEventListeners() {
    serial.onconnect = ((web.Event event) {
      final portEvent = event as SerialPortEvent;
      debugPrint('Global event: A serial port was connected.');
      final connectedPort = portEvent.target;
      if (_port == null && mounted) {
        setState(() {
          setStatus('Known device reconnected. Attempting to auto-connect...');
        });
        _connectToPort(connectedPort);
      }
    } as void Function(web.Event))
        .toJS;
  }

  /// This function is for the *initial* user-driven connection.
  void chooseSerialDevice() async {
    if (_port != null) {
      debugPrint("A port is already open.");
      return;
    }
    try {
      final filters = [
        JSFilterObject(usbVendorId: 0x1D50, usbProductId: 0x6192)
      ];
      final selectedPort = await requestWebSerialPort(filters.toJS);
      if (selectedPort != null) {
        await _connectToPort(selectedPort);
      } else {
        debugPrint("No port selected.");
      }
    } catch (e) {
      debugPrint("Error requesting serial port: $e");
      setState(() {
        _status = "Error: ${e.toString()}";
      });
    }
  }

  /// Common logic to connect to a given port.
  Future<void> _connectToPort(JSSerialPort? port) async {
    if (_port != null || port == null) return;

    try {
      _port = port;
      await _port!
          .open(
            JSSerialOptions(
              baudRate: 115200,
              dataBits: 8,
              stopBits: 1,
              parity: "none",
              bufferSize: 64,
              flowControl: "none",
            ),
          )
          .toDart;

      debugPrint("Port opened successfully.");
      final info = _port!.getInfo();
      setState(() {
        _status =
            "Connected to VendorID: ${info.usbVendorId}, ProductID: ${info.usbProductId}";
      });

      // Attach an instance-specific listener for disconnection.
      _port!.ondisconnect = ((web.Event event) {
        debugPrint('Instance event: The active port disconnected.');
        _closePort();
      } as void Function(web.Event))
          .toJS;

      _isReading = true;
      _readLoop();
      _sendData();
    } catch (e) {
      debugPrint("Error during connection process: $e");
      await _closePort();
    }
  }

  void _sendData() {
    if (_port?.writable == null) return;
    final writer = _port!.writable!.getWriter();
    final request = Uint8List.fromList([0x02]);
    final JSUint8Array jsReq = request.toJS;
    writer.write(jsReq);
    writer.releaseLock();
    debugPrint("Sent data: $request");
  }

  Future<void> _readLoop() async {
    if (_port?.readable == null) return;

    _reader = _port!.readable!.getReader() as web.ReadableStreamDefaultReader?;
    debugPrint("Starting read loop...");

    while (_isReading) {
      try {
        final result = await _reader!.read().toDart;
        if (result.done) {
          debugPrint("Reader reported stream is done.");
          break;
        }
        debugPrint("DATA: ${result.value}");
      } catch (e) {
        debugPrint("Read loop failed, initiating cleanup. Error: $e");
        break;
      }
    }
    debugPrint("Exited read loop.");
    await _closePort();
  }

  Future<void> _closePort() async {
    if (_port == null) return;

    _isReading = false;

    final portToClose = _port;
    final readerToCancel = _reader;

    // Update state and UI immediately to prevent race conditions.
    _port = null;
    _reader = null;

    if (mounted) {
      setState(() {
        _status = 'Port disconnected. Click to reconnect or plug in device.';
      });
    }

    // Perform async cleanup in the background.
    try {
      await readerToCancel?.cancel().toDart;
    } catch (e) {
      debugPrint("Error cancelling reader (expected): $e");
    }
    try {
      await portToClose?.close().toDart;
    } catch (e) {
      debugPrint("Error closing port (expected): $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Web Serial Demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_status),
              const SizedBox(height: 20),
              MaterialButton(
                color: Theme.of(context).primaryColor,
                textColor: Colors.white,
                onPressed: _port == null ? chooseSerialDevice : null,
                disabledColor: Colors.grey,
                child: const Text('Connect Serial Device'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
150
points
81
downloads

Publisher

verified publishermanichord.com

Weekly Downloads

WebSerial support for Dart built on the web package.

Repository (GitHub)
View/report issues

Documentation

API reference

License

unknown (license)

Dependencies

web

More

Packages that depend on webserial