├── example ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── manifest.json │ └── index.html ├── README.md ├── test │ └── widget_test.dart ├── .gitignore ├── lib │ ├── main.dart │ └── home_page.dart ├── .metadata └── pubspec.yaml ├── lib ├── src │ ├── stop_bits.dart │ ├── data_bits.dart │ ├── parity.dart │ ├── flow_control.dart │ ├── serial_port_info.dart │ ├── signal_state.dart │ ├── signal_options.dart │ ├── serial_options.dart │ └── web.dart └── serial.dart ├── .metadata ├── test └── serial_test.dart ├── pubspec.yaml ├── .github └── workflows │ ├── publish.yaml │ └── github-pages.yaml ├── CHANGELOG.md ├── .gitignore ├── README.md ├── LICENSE └── analysis_options.yaml /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xclud/dart_serial/HEAD/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xclud/dart_serial/HEAD/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xclud/dart_serial/HEAD/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xclud/dart_serial/HEAD/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xclud/dart_serial/HEAD/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /lib/src/stop_bits.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Stop bits. 4 | enum StopBits { 5 | /// One. 6 | one, 7 | 8 | /// Two. 9 | two, 10 | } 11 | 12 | int _getStopBits(StopBits sb) { 13 | switch (sb) { 14 | case StopBits.one: 15 | return 1; 16 | case StopBits.two: 17 | return 2; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8017883a5dfd13d709c4bd04d617f54a6cb2271f 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /lib/src/data_bits.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Data bits. 4 | enum DataBits { 5 | /// Seven. 6 | seven, 7 | 8 | ///Eight. 9 | eight, 10 | } 11 | 12 | int _getDataBits(DataBits dataBits) { 13 | switch (dataBits) { 14 | case DataBits.seven: 15 | return 7; 16 | case DataBits.eight: 17 | return 8; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/serial_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | //import 'package:serial/serial.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () { 7 | // final calculator = Calculator(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /lib/serial.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | import 'dart:js_interop'; 4 | import 'package:web/web.dart'; 5 | 6 | part 'src/data_bits.dart'; 7 | part 'src/flow_control.dart'; 8 | part 'src/parity.dart'; 9 | part 'src/serial_options.dart'; 10 | part 'src/serial_port_info.dart'; 11 | part 'src/signal_options.dart'; 12 | part 'src/signal_state.dart'; 13 | part 'src/stop_bits.dart'; 14 | part 'src/web.dart'; 15 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: serial 2 | description: Serial port for flutter. Wrapper around the `window.navigator.serial` for web platform. 3 | version: 0.0.7+1 4 | homepage: https://pwa.ir 5 | repository: https://github.com/xclud/dart_serial/ 6 | 7 | environment: 8 | sdk: ">=3.3.0 <4.0.0" 9 | 10 | dependencies: 11 | web: ^1.1.1 12 | 13 | dev_dependencies: 14 | test: ^1.25.15 15 | lints: ^5.1.1 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | publishing: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "Checkout" 12 | uses: actions/checkout@v2 13 | 14 | - name: "serial" 15 | uses: k-paxian/dart-package-publisher@master 16 | with: 17 | credentialJson: ${{ secrets.CREDENTIAL_JSON }} 18 | -------------------------------------------------------------------------------- /lib/src/parity.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// The parity mode. 4 | enum Parity { 5 | /// None. 6 | none, 7 | 8 | /// Even. 9 | even, 10 | 11 | /// Odd. 12 | odd, 13 | } 14 | 15 | String _getParityString(Parity parity) { 16 | switch (parity) { 17 | case Parity.none: 18 | return 'none'; 19 | case Parity.even: 20 | return 'even'; 21 | case Parity.odd: 22 | return 'odd'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/flow_control.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// The flow control type, either [none] or [hardware]. The default value is [none]. 4 | enum FlowControl { 5 | /// None. 6 | none, 7 | 8 | /// Hardware. 9 | hardware, 10 | } 11 | 12 | String _getFlowControlString(FlowControl fc) { 13 | switch (fc) { 14 | case FlowControl.none: 15 | return 'none'; 16 | case FlowControl.hardware: 17 | return 'hardware'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.7+1 2 | 3 | Use Streaming APIs from `web` package. 4 | 5 | ## 0.0.6 6 | 7 | Migrate to `dart:js_interop`. 8 | 9 | ## 0.0.5 10 | 11 | Maintenance. 12 | 13 | ## 0.0.4 14 | 15 | Dart SDK 4.0 support. 16 | 17 | ## 0.0.3 18 | 19 | * Fixed a bug when build in release mode. Thanks to [cw-20021366](https://github.com/cw-20021366). 20 | 21 | ## 0.0.2 22 | 23 | * Readable and read functionality added. 24 | 25 | ## 0.0.1 26 | 27 | * First release. 28 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.lock 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | //import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | //import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/serial_port_info.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Provides information about the [SerialPort]. 4 | @JS() 5 | extension type SerialPortInfo._(JSObject _) { 6 | /// If the port is part of a USB device, an unsigned short [int] that identifies a USB device vendor, otherwise `null`. 7 | external int? get usbVendorId; 8 | 9 | /// If the port is part of a USB device, an unsigned short [int] that identiffies a USB device, otherwise `null`. 10 | external int? get usbProductId; 11 | 12 | /// If the port is a Bluetooth RFCOMM service, this property is an unsigned long [int] or [String] representing the device's Bluetooth service class ID. If not, it is `null`. 13 | external JSObject? get bluetoothServiceClassId; 14 | } 15 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_web_libraries_in_flutter 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:example/home_page.dart'; 5 | 6 | void main() { 7 | runApp(const MyApp()); 8 | } 9 | 10 | /// 11 | class MyApp extends StatefulWidget { 12 | /// 13 | const MyApp({super.key}); 14 | 15 | @override 16 | State createState() => _MyAppState(); 17 | } 18 | 19 | class _MyAppState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | final theme = ThemeData( 23 | useMaterial3: true, 24 | colorSchemeSeed: Colors.purple, 25 | brightness: Brightness.dark, 26 | inputDecorationTheme: InputDecorationTheme( 27 | border: OutlineInputBorder(), 28 | alignLabelWithHint: true, 29 | isDense: true, 30 | ), 31 | ); 32 | 33 | return MaterialApp( 34 | theme: theme, 35 | debugShowCheckedModeBanner: false, 36 | home: HomePage(), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/signal_state.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Signal State. 4 | @JS() 5 | extension type SignalState._(JSObject _) implements JSAny { 6 | /// Factory. 7 | external factory SignalState({ 8 | bool? clearToSend, 9 | bool? dataCarrierDetect, 10 | bool? dataSetReady, 11 | bool? ringIndicator, 12 | }); 13 | 14 | /// A boolean indicating to the other end of a serial connection that is clear to send data. 15 | external bool? get clearToSend; 16 | external set clearToSend(bool? v); 17 | 18 | /// A boolean that toggles the control signal needed to communicate over a serial connection. 19 | external bool? get dataCarrierDetect; 20 | external set dataCarrierDetect(bool? v); 21 | 22 | /// A boolean indicating whether the device is ready to send and receive data. 23 | external bool? get dataSetReady; 24 | external set dataSetReady(bool? v); 25 | 26 | /// A boolean indicating whether a ring signal should be sent down the serial connection. 27 | external bool? get ringIndicator; 28 | external set ringIndicator(bool? v); 29 | } 30 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Serial Port", 3 | "short_name": "Serial Port", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `serial` is a wrapper around the `window.navigator.serial`. This package does not provide any additional API, but merely helps to make the `dart:html` package work "out of the box" without the need of manually writing any javascript code. 2 | 3 | ## Web Demo 4 | 5 | [Web Demo](https://serial.pwa.ir) 6 | 7 | ## Requirements 8 | 9 | In order to access serial ports on web, you need your web page to open from an HTTPS url. 10 | 11 | > Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers. 12 | 13 | More information: [https://developer.mozilla.org/en-US/docs/Web/API/SerialPort](https://developer.mozilla.org/en-US/docs/Web/API/SerialPort) 14 | 15 | ## Usage 16 | 17 | ``` dart 18 | import 'dart:html'; 19 | import 'package:serial/serial.dart'; 20 | 21 | final port = await window.navigator.serial.requestPort(); 22 | await port.open(baudRate: 9600); 23 | 24 | final writer = port.writable.writer; 25 | 26 | await writer.ready; 27 | await writer.write(Uint8List.fromList('Hello World.'.codeUnits)); 28 | 29 | await writer.ready; 30 | await writer.close(); 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Mahdi K. Fard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/src/signal_options.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Options for [SignalPortExtensions.setSignals]. 4 | @JS() 5 | extension type _SignalOptions._(JSObject _) { 6 | /// The external constructor. 7 | external factory _SignalOptions({ 8 | bool dataTerminalReady = false, 9 | bool requestToSend = false, 10 | // bool break = false 11 | }); 12 | 13 | /// A boolean indicating whether to invoke the operating system to either assert (if true) or de-assert (if false) the "data terminal ready" or "DTR" signal on the serial port. 14 | external bool get dataTerminalReady; 15 | external set dataTerminalReady(bool v); 16 | 17 | /// A boolean indicating whether to invoke the operating system to either assert (if true) or de-assert (if false) the "request to send" or "RTS" signal on the serial port. 18 | external bool get requestToSend; 19 | external set requestToSend(bool v); 20 | 21 | /// A boolean indicating whether to invoke the operating system to either assert (if true) or de-assert (if false) the "break" signal on the serial port. 22 | // TODO: break is reserved keyword 23 | // external bool get break; 24 | // external set break(bool v); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/serial_options.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | @JS() 4 | extension type _SerialOptions._(JSObject _) { 5 | /// The external constructor. 6 | external factory _SerialOptions({ 7 | required int baudRate, 8 | int dataBits = 8, 9 | int stopBits = 1, 10 | String parity = 'none', 11 | int bufferSize = 255, 12 | String flowControl = 'none', 13 | }); 14 | 15 | /// A positive, non-zero value indicating the baud rate at which serial communication should be established. 16 | external int get baudRate; 17 | external set baudRate(int v); 18 | 19 | /// The number of data bits per frame. Either 7 or 8. 20 | external int get dataBits; 21 | external set dataBits(int v); 22 | 23 | /// The number of stop bits at the end of a frame. Either 1 or 2. 24 | external int get stopBits; 25 | external set stopBits(int v); 26 | 27 | /// The parity mode. 28 | external String get parity; 29 | external set parity(String v); 30 | 31 | /// A positive, non-zero value indicating the size of the read and write buffers that should be created. 32 | external int get bufferSize; 33 | external set bufferSize(int v); 34 | 35 | /// Flow control. 36 | external String get flowControl; 37 | external set flowControl(String v); 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yaml: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | 3 | concurrency: 4 | group: production 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: [main] 10 | 11 | jobs: 12 | build: 13 | name: Build Web App 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Fetch all history for all tags and branches 18 | run: | 19 | git config remote.origin.url https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }} 20 | git fetch --unshallow 21 | - uses: subosito/flutter-action@v2 22 | with: 23 | channel: "master" 24 | - run: cd example && flutter pub get 25 | - run: cd example && flutter test 26 | - name: Build Web App 27 | run: cd example && flutter build web 28 | - name: Upload Artifacts 29 | uses: actions/upload-pages-artifact@v3 30 | with: 31 | path: example/build/web 32 | deploy: 33 | name: Deploy GitHub Pages 34 | needs: build 35 | runs-on: ubuntu-latest 36 | permissions: 37 | pages: write 38 | id-token: write 39 | environment: 40 | name: github-pages 41 | url: ${{ steps.deployment.outputs.page_url }} 42 | steps: 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v4 46 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | analyzer: 3 | exclude: 4 | - "lib/**/generated/**" 5 | language: 6 | strict-raw-types: false 7 | errors: 8 | always_declare_return_types: error 9 | always_specify_types: error 10 | always_use_package_imports: error 11 | annotate_overrides: error 12 | argument_type_not_assignable: error 13 | avoid_function_literals_in_foreach_calls: error 14 | avoid_renaming_method_parameters: error 15 | avoid_types_on_closure_parameters: error 16 | avoid_unnecessary_containers: error 17 | await_only_futures: error 18 | curly_braces_in_flow_control_structures: error 19 | dead_code: error 20 | duplicate_import: error 21 | file_names: error 22 | invalid_assignment: error 23 | missing_return: error 24 | prefer_const_constructors: error 25 | prefer_const_constructors_in_immutables: error 26 | prefer_const_declarations: error 27 | prefer_collection_literals: error 28 | prefer_contains: error 29 | prefer_const_literals_to_create_immutables: error 30 | prefer_final_fields: error 31 | prefer_single_quotes: error 32 | public_member_api_docs: error 33 | sized_box_for_whitespace: error 34 | sort_constructors_first: error 35 | sort_unnamed_constructors_first: error 36 | use_function_type_syntax_for_parameters: error 37 | use_key_in_widget_constructors: error 38 | unnecessary_import: error 39 | unnecessary_string_interpolations: error 40 | unnecessary_this: error 41 | unused_import: error 42 | linter: 43 | rules: 44 | always_use_package_imports: true 45 | avoid_print: true 46 | prefer_single_quotes: true 47 | public_member_api_docs: true 48 | sort_constructors_first: true 49 | sort_unnamed_constructors_first: true 50 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 496049263e4efd68ced664ee2c90db9c93f08571 8 | channel: master 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 17 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 18 | - platform: android 19 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 20 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 21 | - platform: ios 22 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 23 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 24 | - platform: linux 25 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 26 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 27 | - platform: macos 28 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 29 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 30 | - platform: web 31 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 32 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 33 | - platform: windows 34 | create_revision: 496049263e4efd68ced664ee2c90db9c93f08571 35 | base_revision: 496049263e4efd68ced664ee2c90db9c93f08571 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Serial Port 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=3.4.0 <4.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | serial: 33 | path: ../ 34 | web: ^1.1.1 35 | gap: any 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | 41 | # The "flutter_lints" package below contains a set of recommended lints to 42 | # encourage good coding practices. The lint set provided by the package is 43 | # activated in the `analysis_options.yaml` file located at the root of your 44 | # package. See that file for information about deactivating specific lint 45 | # rules and activating additional ones. 46 | flutter_lints: ^2.0.0 47 | 48 | # For information on the generic Dart part of this file, see the 49 | # following page: https://dart.dev/tools/pub/pubspec 50 | 51 | # The following section is specific to Flutter. 52 | flutter: 53 | # The following line ensures that the Material Icons font is 54 | # included with your application, so that you can use the icons in 55 | # the material Icons class. 56 | uses-material-design: true 57 | 58 | # To add assets to your application, add an assets section, like this: 59 | # assets: 60 | # - images/a_dot_burr.jpeg 61 | # - images/a_dot_ham.jpeg 62 | 63 | # An image asset can refer to one or more resolution-specific "variants", see 64 | # https://flutter.dev/assets-and-images/#resolution-aware. 65 | 66 | # For details regarding adding assets from package dependencies, see 67 | # https://flutter.dev/assets-and-images/#from-packages 68 | 69 | # To add custom fonts to your application, add a fonts section here, 70 | # in this "flutter" section. Each entry in this list should have a 71 | # "family" key with the font family name, and a "fonts" key with a 72 | # list giving the asset and other descriptors for the font. For 73 | # example: 74 | # fonts: 75 | # - family: Schyler 76 | # fonts: 77 | # - asset: fonts/Schyler-Regular.ttf 78 | # - asset: fonts/Schyler-Italic.ttf 79 | # style: italic 80 | # - family: Trajan Pro 81 | # fonts: 82 | # - asset: fonts/TrajanPro.ttf 83 | # - asset: fonts/TrajanPro_Bold.ttf 84 | # weight: 700 85 | # 86 | # For details regarding fonts from package dependencies, 87 | # see https://flutter.dev/custom-fonts/#from-packages 88 | -------------------------------------------------------------------------------- /lib/src/web.dart: -------------------------------------------------------------------------------- 1 | part of '../serial.dart'; 2 | 3 | /// Adds [serial] to [Navigator]. 4 | extension NavigatorExtension on Navigator { 5 | /// Serial accessor. 6 | external Serial get serial; 7 | } 8 | 9 | /// Adds [serial] to [WorkerNavigator]. 10 | extension WorkerNavigatorExtension on WorkerNavigator { 11 | /// Serial accessor. 12 | external Serial get serial; 13 | } 14 | 15 | /// Serial. 16 | @JS() 17 | extension type Serial._(JSObject _) { 18 | /// Presents the user with a dialog asking them to select a serial device to connect to. 19 | /// It returns a [JSPromise] that resolves with an instance of [SerialPort] representing the device chosen by the user. 20 | /// 21 | /// When the user first visits a site it will not have permission to access any serial devices. 22 | /// A site must first call [requestPort] to prompt the user to select which device the site should be allowed to control. 23 | /// 24 | /// This method must be called via transient activation. 25 | /// The user has to interact with the page or a UI element in order for this feature to work. 26 | @JS('requestPort') 27 | external JSPromise requestPort(); 28 | 29 | /// Returns a [JSPromise] that resolves with an array of [SerialPort] objects representing 30 | /// serial ports connected to the host which the origin has permission to access. 31 | @JS('getPorts') 32 | external JSPromise> getPorts(); 33 | } 34 | 35 | /// The SerialPort interface of the Web Serial API provides access to a serial port on the host device. 36 | @JS() 37 | extension type SerialPort._(JSObject _) implements JSObject { 38 | @JS('open') 39 | external JSPromise _open(_SerialOptions options); 40 | 41 | /// Opens the serial port. 42 | JSPromise open({ 43 | required int baudRate, 44 | DataBits dataBits = DataBits.eight, 45 | StopBits stopBits = StopBits.one, 46 | Parity parity = Parity.none, 47 | int bufferSize = 255, 48 | FlowControl flowControl = FlowControl.none, 49 | }) { 50 | final options = _SerialOptions( 51 | baudRate: baudRate, 52 | dataBits: _getDataBits(dataBits), 53 | stopBits: _getStopBits(stopBits), 54 | parity: _getParityString(parity), 55 | bufferSize: bufferSize, 56 | flowControl: _getFlowControlString(flowControl), 57 | ); 58 | 59 | return _open(options); 60 | } 61 | 62 | // /// Fired when the port connects to the device. 63 | // external JSAny? get connect; 64 | // external set connect(JSAny? value); 65 | 66 | // /// Fired when the port disconnects from the device. 67 | // external JSAny? get disconnect; 68 | // external set disconnect(JSAny? value); 69 | 70 | /// Returns a [JSPromise] that resolves when access to the serial port is revoked. 71 | external JSPromise forget(); 72 | 73 | /// Closes the serial port. 74 | external JSPromise close(); 75 | 76 | /// Returns an object containing identifying information for the device available via the port. 77 | external SerialPortInfo getInfo(); 78 | 79 | /// Returns a [WritableStream] for sending data to the device connected to the port. 80 | /// Chunks written to this stream must be instances of [JSArrayBuffer], [JSTypedArray], or [JSDataView]. 81 | /// 82 | /// This property is non-null as long as the port is open and has not encountered a fatal error. 83 | external WritableStream? get writable; 84 | 85 | /// Returns a [ReadableStream] for receiving data from the device connected to the port. 86 | /// Chunks read from this stream are instances of [JSUint8Array]. 87 | /// 88 | /// This property is non-null as long as the port is open and has not encountered a fatal error. 89 | external ReadableStream? get readable; 90 | 91 | /// Indicates whether the port is logically connected to the device. 92 | /// 93 | /// When a wireless device goes out of range of the host, any wireless serial port opened by a web app automatically closes, even though it stays logically connected. In such cases, the web app could attempt to reopen the port using SerialPort.open(). 94 | /// 95 | /// However, if the wireless device was intentionally disconnected (for example, if the user chose to disconnect it using the operating system control panel), the web app should refrain from reopening the port to prevent reconnecting to the wireless device. 96 | external bool get connected; 97 | 98 | /// Returns a [JSPromise] that resolves with an object containing the current state of the port's control signals. 99 | @JS('getSignals') 100 | external JSPromise getSignals(); 101 | @JS('setSignals') 102 | external JSPromise _setSignals(_SignalOptions options); 103 | 104 | /// Sets control signals on the port and returns a [JSPromise] that resolves when they are set. 105 | JSPromise setSignals({ 106 | bool dataTerminalReady = false, 107 | bool requestToSend = false, 108 | // bool break = false, 109 | }) { 110 | final promise = _setSignals(_SignalOptions( 111 | dataTerminalReady: dataTerminalReady, 112 | requestToSend: requestToSend, 113 | // break: break 114 | )); 115 | 116 | return promise; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /example/lib/home_page.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_web_libraries_in_flutter 2 | 3 | import 'dart:async'; 4 | import 'dart:js_interop'; 5 | import 'dart:typed_data'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:gap/gap.dart'; 8 | import 'package:web/web.dart' as web; 9 | 10 | import 'package:serial/serial.dart'; 11 | 12 | /// 13 | class HomePage extends StatefulWidget { 14 | /// 15 | const HomePage({super.key}); 16 | 17 | @override 18 | State createState() => _HomePageState(); 19 | } 20 | 21 | class _HomePageState extends State { 22 | SerialPort? _port; 23 | web.ReadableStreamDefaultReader? _reader; 24 | bool _keepReading = true; 25 | 26 | final _received = []; 27 | 28 | final _controller1 = TextEditingController(); 29 | 30 | Future _openPort() async { 31 | await _port?.close().toDart; 32 | final port = await web.window.navigator.serial.requestPort().toDart; 33 | 34 | await port.open(baudRate: 9600).toDart; 35 | 36 | _port = port; 37 | _keepReading = true; 38 | 39 | _startReceiving(port); 40 | 41 | setState(() {}); 42 | } 43 | 44 | Future _writeToPort(Uint8List data) async { 45 | if (data.isEmpty) { 46 | return; 47 | } 48 | 49 | final port = _port; 50 | 51 | if (port == null) { 52 | return; 53 | } 54 | 55 | final writer = port.writable?.getWriter(); 56 | 57 | if (writer != null) { 58 | await writer.write(data.toJS).toDart; 59 | writer.releaseLock(); 60 | } 61 | } 62 | 63 | Future _startReceiving(SerialPort port) async { 64 | while (port.readable != null && _keepReading) { 65 | final reader = 66 | port.readable!.getReader() as web.ReadableStreamDefaultReader; 67 | 68 | _reader = reader; 69 | 70 | while (_keepReading) { 71 | try { 72 | final result = await reader.read().toDart; 73 | 74 | if (result.done) { 75 | ///Reader has been canceled. 76 | break; 77 | } 78 | 79 | final value = result.value; 80 | if (value != null && value.isA()) { 81 | final data = value as JSUint8Array; 82 | _received.add(data.toDart); 83 | setState(() {}); 84 | } 85 | } catch (e) { 86 | print(e); 87 | } finally { 88 | reader.releaseLock(); 89 | } 90 | } 91 | 92 | reader.releaseLock(); 93 | } 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | final theme = Theme.of(context); 99 | 100 | final port = _port; 101 | 102 | return Scaffold( 103 | appBar: AppBar( 104 | backgroundColor: theme.colorScheme.inversePrimary, 105 | title: const Text('Serial Port'), 106 | actions: [ 107 | IconButton( 108 | onPressed: _openPort, 109 | icon: Icon(Icons.device_hub), 110 | tooltip: 'Open Serial Port', 111 | ), 112 | IconButton( 113 | onPressed: port == null 114 | ? null 115 | : () async { 116 | _keepReading = false; 117 | final reader = _reader; 118 | if (reader != null) { 119 | await reader.cancel().toDart; 120 | _reader = null; 121 | } 122 | await port.close().toDart; 123 | _port = null; 124 | 125 | setState(() {}); 126 | }, 127 | icon: Icon(Icons.close), 128 | tooltip: 'Close Serial Port', 129 | ), 130 | ], 131 | ), 132 | body: Column( 133 | crossAxisAlignment: CrossAxisAlignment.stretch, 134 | children: [ 135 | Expanded( 136 | child: Padding( 137 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 138 | child: Container( 139 | decoration: BoxDecoration( 140 | border: Border.all(color: Colors.white54), 141 | borderRadius: BorderRadius.circular(4), 142 | ), 143 | child: _received.isNotEmpty 144 | ? ListView( 145 | padding: const EdgeInsets.all(4), 146 | children: _received.map((e) { 147 | final text = String.fromCharCodes(e); 148 | return Padding( 149 | padding: const EdgeInsets.all(4.0), 150 | child: Text(text), 151 | ); 152 | }).toList(), 153 | ) 154 | : Center( 155 | child: Text( 156 | 'No data received yet.', 157 | textAlign: TextAlign.center, 158 | )), 159 | ), 160 | ), 161 | ), 162 | Padding( 163 | padding: const EdgeInsets.all(4.0), 164 | child: Column( 165 | crossAxisAlignment: CrossAxisAlignment.stretch, 166 | children: [ 167 | Padding( 168 | padding: const EdgeInsets.all(4.0), 169 | child: TextFormField( 170 | controller: _controller1, 171 | ), 172 | ), 173 | Gap(8), 174 | Padding( 175 | padding: const EdgeInsets.all(4.0), 176 | child: ElevatedButton( 177 | child: const Text('Send'), 178 | onPressed: () { 179 | _writeToPort( 180 | Uint8List.fromList(_controller1.text.codeUnits)); 181 | }, 182 | ), 183 | ), 184 | ], 185 | ), 186 | ), 187 | ], 188 | ), 189 | ); 190 | } 191 | } 192 | --------------------------------------------------------------------------------