diff --git a/.gitignore b/.gitignore index e9dc58d..661c44d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,126 @@ +# Miscellaneous +*.class +*.lock +!pubspec.lock +*.log +*.pyc +*.swp .DS_Store -.dart_tool/ +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/* + +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* + +# Flutter repo-specific +/bin/cache/ +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +./version + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies .packages +.pub-cache/ .pub/ - build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds +**/.fvm/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +**/android/.idea/ +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/.last_build_id +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Coverage +coverage/ + +# Web related + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to the above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/tasks.json +!.vscode/settings.json +!.idea/codeStyles/ +!.idea/dictionaries/ +!.idea/runConfigurations/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5d9b2b2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "yaml.schemaStore.enable": false +} diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd9c73..a06b1a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,29 @@ -## [1.0.0] - 2022-03-24 - -* Stable release for Espressif Esp32 Soft Ap provisioning with protobuf and cryptography with null-safety. Based on version 1.0.1 from esp_softap_provisioning. +## [2.0.0] - 2023-10-12 +> Author: [@jeroen-meijer](https://github.com/jeroen-meijer) -## [1.0.1] - 2022-03-25 +- **feat!**: Many classes have been altered in small and significant ways. In particular, expect these types of changes: + - Most classes are now marked immutable. + - Functions that previously returned a `Future` now return a `Future`. In cases where these functions returned `false`, they now throw an exception. + - Functions that internally caught exceptions and returned `null` now throw those exceptions. +- **chore!: upgrade Dart SDK constraint to 3.0.0 or higher, upgrade Flutter SDK constraint to 3.10.0 or higher.** +- feat: add `logger.dart` for consumer-customizable logging (import `package:esp_provisioning_softap/logger.dart` and set `logger` as desired). +- chore: add [`very_good_analysis`](https://pub.dev/packages/very_good_analysis) package and linter rules and fix all issues. +- chore: upgrade all dependencies to latest versions. +- chore: format all files. -* Minor bugfixes. sendReceiveCustomData-Endpoint can now be specified. +## [1.0.3] - 2022-09-17 +- Update Kotlin Version from 1.3.50 to 1.5.20 ## [1.0.2] - 2022-05-02 -* Minor bugfixes in provisioning process. +- Minor bugfixes in provisioning process. -## [1.0.3] - 2022-09-17 +## [1.0.1] - 2022-03-25 + +- Minor bugfixes. sendReceiveCustomData-Endpoint can now be specified. + +## [1.0.0] - 2022-03-24 -* Update Kotlin Version from 1.3.50 to 1.5.20 \ No newline at end of file +- Stable release for Espressif Esp32 Soft Ap provisioning with protobuf and cryptography with null-safety. Based on version 1.0.1 from esp_softap_provisioning. diff --git a/README.md b/README.md index 3229a03..84689a0 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ A Flutter plugin for provisioning ESP32 modules with SoftAP [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/nicop2000/esp_provisioning_softap/releases) [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/nicop2000/esp_provisioning_softap/blob/main/LICENSE) + + ## Example App + GIF by original pub-package [esp_softap_provisioning](https://github.com/omert08/esp_softap_provisioning) @@ -14,36 +17,38 @@ GIF by original pub-package [esp_softap_provisioning](https://github.com/omert08 Comparison to esp_provisioning: -| Repo | softap support | ble support | cryptography | protobuf -| ------------- | ------------- | ------------- | ------------- | -------------| -| esp_provisioning_softap | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_check_mark: (2.0.1) | :heavy_check_mark: (2.0.0) -| esp_provisioning | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: (1.4.1) | :heavy_check_mark: (1.0.1) +| Repo | softap support | ble support | cryptography | protobuf | +| ----------------------- | ------------------------ | ------------------------ | -------------------------- | -------------------------- | +| esp_provisioning_softap | :heavy_check_mark: | :heavy_multiplication_x: | :heavy_check_mark: (2.0.1) | :heavy_check_mark: (2.0.0) | +| esp_provisioning | :heavy_multiplication_x: | :heavy_check_mark: | :heavy_check_mark: (1.4.1) | :heavy_check_mark: (1.0.1) | Last update: 24 / 03 / 2022 ## Usage Changes on pubspec.yaml -```flutter pub add esp_provisioning_softap_null_safe``` +`flutter pub add esp_provisioning_softap_null_safe` -then, run ```flutter pub get```, +then, run `flutter pub get`, We need to give permissions for http connections. -* Changes on AndroidManifest.xml (/android/app/src/main/AndroidManifest.xml): +- Changes on AndroidManifest.xml (/android/app/src/main/AndroidManifest.xml): + +Add `` and `android:usesCleartextTraffic="true"` to AndroidManifest.xml. -Add ``` ``` and ```android:usesCleartextTraffic="true"``` to AndroidManifest.xml. ``` + /ios/Runner/Info.plist) : +- Changes on Info.plist (/ios/Runner/Info.plist) : + ``` @@ -76,10 +81,11 @@ For iOS, it's recommended to put platform version >= 9.0 , You can edit this var Library is ready to use, you can check example app directory for implementation. Notice that Proof of posession (POP) should be matching with ESP's. ## Credits -* I updated the code from the pub-package [esp_softap_provisioning](https://github.com/omert08/esp_softap_provisioning) to null safety. + +- I updated the code from the pub-package [esp_softap_provisioning](https://github.com/omert08/esp_softap_provisioning) to null safety. The original package by Omer Taban referred to: -* I have referred to sunshine-tech [esp_provisioning](https://github.com/sunshine-tech/esp_provisioning) repository for native cipher code. -* I have referred to Espressif [esp_prov](https://github.com/espressif/esp-idf/tree/cf457d4/tools/esp_prov) repository for provisioning structure. - +- I have referred to sunshine-tech [esp_provisioning](https://github.com/sunshine-tech/esp_provisioning) repository for native cipher code. + +- I have referred to Espressif [esp_prov](https://github.com/espressif/esp-idf/tree/cf457d4/tools/esp_prov) repository for provisioning structure. diff --git a/analysis_options.yaml b/analysis_options.yaml index d56da71..bbd9703 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,10 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +include: package:very_good_analysis/analysis_options.yaml -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - 'lib/**/*.pb*.dart' linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + # TODO: Remove once docs are implemented. + public_member_api_docs: false diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..84153f9 --- /dev/null +++ b/cspell.json @@ -0,0 +1,12 @@ +{ + "words": [ + "bssid", + "Cryptor", + "proto", + "protocomm", + "Pubkey", + "rssi", + "softap", + "spinkit" + ] +} diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..1d78a98 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,12 @@ +include: package:very_good_analysis/analysis_options.yaml + +analyzer: + exclude: + - 'lib/**/*.pb*.dart' + +linter: + rules: + # TODO: Remove once docs are implemented. + public_member_api_docs: false + library_private_types_in_public_api: false + avoid_print: false diff --git a/example/lib/main.dart b/example/lib/main.dart index 4a4a3c5..0693dae 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,38 +4,43 @@ import 'package:flutter/material.dart'; typedef ItemTapCallback = void Function(Map item); void main() { - runApp(MaterialApp(home: HomeScreen())); + runApp(const MaterialApp(home: HomeScreen())); } class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - backgroundColor: Colors.lightBlue, - title: const Text('ESP SoftAp Provisioning'), - ), - body: Center( - child: MaterialButton( - color: Colors.lightBlueAccent, - elevation: 5, - padding: EdgeInsets.all(15.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(5))), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => SoftApScreen())); - }, - child: Text( - 'Start Provisioning', - style: Theme.of(context) - .textTheme - .headline6 - .copyWith(color: Colors.white), - ), + appBar: AppBar( + backgroundColor: Colors.lightBlue, + title: const Text('ESP SoftAp Provisioning'), + ), + body: Center( + child: MaterialButton( + color: Colors.lightBlueAccent, + elevation: 5, + padding: const EdgeInsets.all(15), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(5)), ), - )); + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const SoftApScreen(), + ), + ); + }, + child: Text( + 'Start Provisioning', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.white), + ), + ), + ), + ); } } diff --git a/example/lib/scan_list.dart b/example/lib/scan_list.dart index 94f9dd5..f5f3879 100644 --- a/example/lib/scan_list.dart +++ b/example/lib/scan_list.dart @@ -2,65 +2,80 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; typedef ItemTapCallback = void Function( - Map item, BuildContext context); + Map item, + BuildContext context, +); class ScanList extends StatelessWidget { + const ScanList( + this.items, + this.icon, { + required this.onTap, + required this.disableLoading, + super.key, + }); + final List> items; final IconData icon; final ItemTapCallback onTap; final bool disableLoading; - ScanList(this.items, this.icon, {this.onTap, this.disableLoading}); - Widget _buildItem( - BuildContext _context, Map item, IconData icon, - {ItemTapCallback onTap}) { + BuildContext context, + Map item, + IconData icon, { + required ItemTapCallback onTap, + }) { return ListTile( - leading: Container( - padding: EdgeInsets.all(4.0), - child: Icon( - icon, - color: Colors.blueAccent, - ), - ), - title: Text( - item['name'] ?? item['ssid'], - style: TextStyle(color: Theme.of(_context).accentColor), + leading: Container( + padding: const EdgeInsets.all(4), + child: Icon( + icon, + color: Colors.blueAccent, ), - trailing: Text(item['rssi'].toString()), - onTap: () { - print('tap'); - if (onTap != null) { - onTap(item, _context); - } - } //showModel(_context, bleDevice), - ); + ), + title: Text( + (item['name'] as String?) ?? (item['ssid']! as String), + style: TextStyle(color: Theme.of(context).colorScheme.secondary), + ), + trailing: Text(item['rssi'].toString()), + onTap: () { + print('tap'); + onTap(item, context); + }, //showModel(context, bleDevice), + ); } - Widget _buildList(BuildContext _context) { + Widget _buildList(BuildContext context) { return Column( children: [ - this.disableLoading != null && this.disableLoading - ? Container() - : SizedBox( - child: Container( - padding: EdgeInsets.all(4.0), - height: 80, - child: Align( - alignment: Alignment.center, - child: SpinKitRipple( - color: Theme.of(_context).textSelectionColor)))), + if (disableLoading) + Container() + else + SizedBox( + child: Container( + padding: const EdgeInsets.all(4), + height: 80, + child: Align( + child: SpinKitRipple( + color: Theme.of(context).primaryColor, + ), + ), + ), + ), Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (BuildContext context, int index) { - return _buildItem(context, items[index], icon, onTap: onTap); - }, - separatorBuilder: (context, index) => Divider( - color: Theme.of(context).dividerColor, - height: 1.0, - ))) + child: ListView.separated( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (BuildContext context, int index) { + return _buildItem(context, items[index], icon, onTap: onTap); + }, + separatorBuilder: (context, index) => Divider( + color: Theme.of(context).dividerColor, + height: 1, + ), + ), + ), ], ); } diff --git a/example/lib/softap_screen/softap_bloc.dart b/example/lib/softap_screen/softap_bloc.dart index 0924ae0..08fa938 100644 --- a/example/lib/softap_screen/softap_bloc.dart +++ b/example/lib/softap_screen/softap_bloc.dart @@ -1,21 +1,9 @@ - -import 'package:esp_provisioning_softap_example/softap_screen/softap_state.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:esp_provisioning_softap_example/softap_screen/softap_event.dart'; import 'package:esp_provisioning_softap_example/softap_screen/softap_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; - -class SoftApBloc extends Bloc { - SoftApBloc() : super(SoftApStateLoaded()); - - @override - Stream mapEventToState(event) async* { - if (event is SoftApEventStart){ - yield* _mapStartToState(); - } - } - Stream _mapStartToState() async* { - - } +class SoftApBloc extends Bloc { + SoftApBloc() : super(SoftApStateLoaded()) { + on((event, emit) {}); + } } - diff --git a/example/lib/softap_screen/softap_event.dart b/example/lib/softap_screen/softap_event.dart index 12efecd..8898679 100644 --- a/example/lib/softap_screen/softap_event.dart +++ b/example/lib/softap_screen/softap_event.dart @@ -1,10 +1,10 @@ import 'package:equatable/equatable.dart'; -abstract class SoftApEvent extends Equatable { +sealed class SoftApEvent extends Equatable { const SoftApEvent(); @override List get props => []; } -class SoftApEventStart extends SoftApEvent {} \ No newline at end of file +class SoftApEventStart extends SoftApEvent {} diff --git a/example/lib/softap_screen/softap_screen.dart b/example/lib/softap_screen/softap_screen.dart index 8006710..45dc65d 100644 --- a/example/lib/softap_screen/softap_screen.dart +++ b/example/lib/softap_screen/softap_screen.dart @@ -7,36 +7,37 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; class SoftApScreen extends StatefulWidget { + const SoftApScreen({super.key}); + @override _SoftApScreenState createState() => _SoftApScreenState(); } class _SoftApScreenState extends State { - - void _showBottomSheet(BuildContext _context) { - - var bottomSheetController = showModalBottomSheet( - context: context, - backgroundColor: Colors.white, - isScrollControlled: true, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: const Radius.circular(20.0), - topRight: const Radius.circular(20.0), - ), + Future _showBottomSheet(BuildContext context) async { + await showModalBottomSheet( + context: context, + backgroundColor: Colors.white, + isScrollControlled: true, + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - builder: (BuildContext context) { - return Container( - padding: EdgeInsets.only(top: 5.0), - height: MediaQuery.of(context).size.height - 50, - child: WiFiScreenSoftAP(), - ); - }); - bottomSheetController.whenComplete(() { - // after prov. - BlocProvider.of(_context).add(SoftApEventStart()); - }); + ), + builder: (BuildContext context) { + return Container( + padding: const EdgeInsets.only(top: 5), + height: MediaQuery.of(context).size.height - 50, + child: const WiFiScreenSoftAP(), + ); + }, + ); + + if (mounted) { + context.read().add(SoftApEventStart()); + } } @override @@ -56,39 +57,43 @@ class _SoftApScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - padding: EdgeInsets.all(4.0), + padding: const EdgeInsets.all(4), width: MediaQuery.of(context).size.width * 0.85, - child:Text('Please connect WiFi to ESP32 AP (PROV_XXX) in "Wi-Fi Settings". Once you complete it please tap on "Ready" button.', - style: TextStyle(fontSize: 18),), + child: const Text( + 'Please connect WiFi to ESP32 AP (PROV_XXX) in ' + '"Wi-Fi Settings". Once you complete it ' + 'please tap on "Ready" button.', + style: TextStyle(fontSize: 18), + ), + ), + SizedBox( + height: MediaQuery.of(context).size.width * 0.1, ), - - SizedBox(height: MediaQuery.of(context).size.width * 0.1,), MaterialButton( color: Colors.lightBlueAccent, elevation: 5, - padding: EdgeInsets.all(15.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(5))), + padding: const EdgeInsets.all(15), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(5)), + ), child: Text( 'Ready', style: Theme.of(context) .textTheme - .headline6 + .titleLarge! .copyWith(color: Colors.white), ), - onPressed: () { _showBottomSheet(this.context); }, ), ], - ) - + ), ); } return Center( - child: SpinKitFoldingCube(color: Theme.of(context).textSelectionColor), + child: SpinKitFoldingCube(color: Theme.of(context).primaryColor), ); }, ), diff --git a/example/lib/softap_screen/softap_state.dart b/example/lib/softap_screen/softap_state.dart index d89e041..738fbd6 100644 --- a/example/lib/softap_screen/softap_state.dart +++ b/example/lib/softap_screen/softap_state.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -abstract class SoftApState extends Equatable { +sealed class SoftApState extends Equatable { const SoftApState(); @override diff --git a/example/lib/softap_service.dart b/example/lib/softap_service.dart index fc44861..d6a7916 100644 --- a/example/lib/softap_service.dart +++ b/example/lib/softap_service.dart @@ -1,17 +1,16 @@ import 'dart:async'; -import 'dart:io'; import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; class SoftAPService { SoftAPService(); Future startProvisioning(String hostname, String pop) async { - Provisioning prov = Provisioning( - transport: TransportHTTP(hostname: hostname), security: Security1(pop: pop)); - var success = await prov.establishSession(); - if (!success) { - throw Exception('Error establishSession'); - } + final prov = Provisioning( + transport: TransportHTTP(hostname: hostname), + security: Security1(pop: pop), + ); + await prov.establishSession(); + return prov; } -} \ No newline at end of file +} diff --git a/example/lib/wifi_screen/password_form_field.dart b/example/lib/wifi_screen/password_form_field.dart index d18cc36..9802ef6 100644 --- a/example/lib/wifi_screen/password_form_field.dart +++ b/example/lib/wifi_screen/password_form_field.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; class PasswordFormField extends StatefulWidget { + const PasswordFormField({ + required this.initialValue, + required this.onSaved, + super.key, + this.onChanged, + }); final String initialValue; - final ValueChanged onChanged; + final ValueChanged? onChanged; final FormFieldSetter onSaved; - PasswordFormField({Key key, this.initialValue, this.onChanged, this.onSaved}) - : super(key: key); - @override _PasswordFormFieldState createState() => _PasswordFormFieldState(); } @@ -23,29 +26,32 @@ class _PasswordFormFieldState extends State { @override Widget build(BuildContext context) { return TextFormField( - obscureText: isObscureText, - initialValue: widget.initialValue, - onChanged: widget.onChanged, - onSaved: widget.onSaved, - validator: (value) { - if (value.isNotEmpty && value.length < 8) { - return 'The minimum password length is 8'; - } - return null; - }, - decoration: InputDecoration( - suffixIcon: FlatButton( - onPressed: () { - setState(() { - isObscureText = !isObscureText; - }); - }, - child: Icon(isObscureText ? Icons.remove_red_eye : Icons.lock_outline, - color: Theme.of(context).accentColor)), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: BorderSide( - width: 1, - )))); + obscureText: isObscureText, + initialValue: widget.initialValue, + onChanged: widget.onChanged, + onSaved: widget.onSaved, + validator: (value) { + if (value != null && value.isNotEmpty && value.length < 8) { + return 'The minimum password length is 8'; + } + return null; + }, + decoration: InputDecoration( + suffixIcon: TextButton( + onPressed: () { + setState(() { + isObscureText = !isObscureText; + }); + }, + child: Icon( + isObscureText ? Icons.remove_red_eye : Icons.lock_outline, + color: Theme.of(context).colorScheme.secondary, + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); } } diff --git a/example/lib/wifi_screen/wifi.dart b/example/lib/wifi_screen/wifi.dart index a1c24ad..2e89470 100644 --- a/example/lib/wifi_screen/wifi.dart +++ b/example/lib/wifi_screen/wifi.dart @@ -1,4 +1,4 @@ export 'wifi_bloc.dart'; export 'wifi_event.dart'; +export 'wifi_screen.dart'; export 'wifi_state.dart'; -export 'wifi_screen.dart'; \ No newline at end of file diff --git a/example/lib/wifi_screen/wifi_bloc.dart b/example/lib/wifi_screen/wifi_bloc.dart index 16162f3..a0fa46f 100644 --- a/example/lib/wifi_screen/wifi_bloc.dart +++ b/example/lib/wifi_screen/wifi_bloc.dart @@ -1,94 +1,99 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; + import 'package:bloc/bloc.dart'; import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; +import 'package:esp_provisioning_softap_example/softap_service.dart'; +import 'package:esp_provisioning_softap_example/wifi_screen/wifi.dart'; import 'package:logger/logger.dart'; -import '../softap_service.dart'; -import './wifi.dart'; -import 'dart:io'; -import 'dart:convert'; class WiFiBlocSoftAP extends Bloc { + WiFiBlocSoftAP() : super(WifiStateLoading()) { + on(_onLoadSoftAP); + on(_onStartProvisioningSoftAP); + } - Provisioning prov; - Logger log = Logger(printer: PrettyPrinter()); - var softApService = SoftAPService(); + Provisioning? prov; + final log = Logger(printer: PrettyPrinter()); + final softApService = SoftAPService(); - WiFiBlocSoftAP() : super(WifiStateLoading()); + Future _onLoadSoftAP( + WifiEventLoadSoftAP event, + Emitter emit, + ) async { + emit(WifiStateConnecting()); + late final Provisioning prov; - @override - Stream mapEventToState(WifiEvent event) async* { - if (event is WifiEventLoadSoftAP) { - yield* _mapLoadToState(); - } - else if (event is WifiEventStartProvisioningSoftAP) { - yield* _mapProvisioningToState(event); - } - } + const pop = 'abcd1234'; - Stream _mapLoadToState() async*{ - yield WifiStateConnecting(); try { - const String pop = "abcd1234"; - if (Platform.isIOS) - { - prov = await softApService.startProvisioning("wifi-prov.local", pop); - } - else{ - prov = await softApService.startProvisioning("192.168.4.1:80", pop); - } - - } catch (e) { + if (Platform.isIOS) { + prov = await softApService.startProvisioning('wifi-prov.local', pop); + } else { + prov = await softApService.startProvisioning('192.168.4.1:80', pop); + } + } catch (e, st) { log.e('Error connecting to device $e'); - yield WifiStateError('Error connecting to device'); + emit(const WifiStateError('Error connecting to device')); + addError(e, st); + return; } - yield WifiStateScanning(); - try { + this.prov = prov; + emit(WifiStateScanning()); - var listWifi = await prov.startScanWiFi(); - yield WifiStateLoaded(wifiList: listWifi ?? []); - log.v('Wifi $listWifi'); - } catch (e) { + try { + final listWifi = await prov.startScanWiFi(); + emit(WifiStateLoaded(wifiList: listWifi ?? [])); + log.t('Wifi $listWifi'); + } catch (e, st) { log.e('Error scan WiFi network $e'); - yield WifiStateError('Error scan WiFi network'); + emit(const WifiStateError('Error scan WiFi network')); + addError(e, st); } } - Stream _mapProvisioningToState( - WifiEventStartProvisioningSoftAP event) async* { - yield WifiStateProvisioning(); - List customData = utf8.encode("Some CUSTOM data\0"); - Uint8List customBytes = Uint8List.fromList(customData); - await prov.sendReceiveCustomData(customBytes); - await prov?.sendWifiConfig(ssid: event.ssid, password: event.password); - await prov?.applyWifiConfig(); - await Future.delayed(Duration(seconds: 10)); - var connectionStatus = await prov.getStatus(); - if (connectionStatus.state == WifiConnectionState.Connected) { - yield WifiStateProvisionedSuccessfully(); + Future _onStartProvisioningSoftAP( + WifiEventStartProvisioningSoftAP event, + Emitter emit, + ) async { + if (prov == null) { + throw StateError( + 'Provisioning is not initialized. ' + 'Add event $WifiEventLoadSoftAP to initialize it.', + ); } - /*else if (connectionStatus.state == 1){ - }*/ - else if (connectionStatus.state == WifiConnectionState.Disconnected){ - yield WifiStateProvisioningDisconnected(); - } - else if (connectionStatus.state == WifiConnectionState.ConnectionFailed){ - if (connectionStatus.failedReason == WifiConnectFailedReason.AuthError){ - yield WifiStateProvisioningAuthError(); - } - else if (connectionStatus.failedReason == WifiConnectFailedReason.NetworkNotFound){ - yield WifiStateProvisioningNetworkNotFound(); + emit(WifiStateProvisioning()); + final customData = utf8.encode('Some CUSTOM data0'); + final customBytes = Uint8List.fromList(customData); + await prov!.sendReceiveCustomData(customBytes); + await prov!.sendWifiConfig(ssid: event.ssid, password: event.password); + await prov!.applyWifiConfig(); + await Future.delayed(const Duration(seconds: 10)); + final connectionStatus = await prov!.getStatus(); + + if (connectionStatus.state == WifiConnectionState.connected) { + emit(WifiStateProvisionedSuccessfully()); + } else if (connectionStatus.state == WifiConnectionState.disconnected) { + emit(WifiStateProvisioningDisconnected()); + } else if (connectionStatus.state == WifiConnectionState.connectionFailed) { + if (connectionStatus.failedReason == WifiConnectFailedReason.authError) { + emit(WifiStateProvisioningAuthError()); + } else if (connectionStatus.failedReason == + WifiConnectFailedReason.networkNotFound) { + emit(WifiStateProvisioningNetworkNotFound()); } } } @override Future close() { - prov?.dispose(); + prov!.dispose(); return super.close(); } } diff --git a/example/lib/wifi_screen/wifi_dialog.dart b/example/lib/wifi_screen/wifi_dialog.dart index b83d7db..b282b11 100644 --- a/example/lib/wifi_screen/wifi_dialog.dart +++ b/example/lib/wifi_screen/wifi_dialog.dart @@ -1,11 +1,15 @@ +import 'package:esp_provisioning_softap_example/wifi_screen/password_form_field.dart'; import 'package:flutter/material.dart'; -import 'password_form_field.dart'; class WifiDialog extends StatefulWidget { - final String wifiName; - final Function(String ssid, String password) onSubmit; + const WifiDialog({ + required this.wifiName, + required this.onSubmit, + super.key, + }); - WifiDialog({Key key, this.wifiName, this.onSubmit}) : super(key: key); + final String wifiName; + final void Function(String ssid, String password) onSubmit; @override _WifiDialogState createState() => _WifiDialogState(); @@ -28,63 +32,66 @@ class _WifiDialogState extends State { elevation: 5, backgroundColor: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0)), //this right here + borderRadius: BorderRadius.circular(10), + ), //this right here child: Container( height: 320, color: Colors.transparent, child: Padding( - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text('Password for WiFi', - style: Theme.of(context).textTheme.bodyText2), - SizedBox( - height: 10.0, + Text( + 'Password for WiFi', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox( + height: 10, ), TextFormField( - onSaved: (text) { - ssid = text; - }, - initialValue: widget.wifiName, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: BorderSide( - color: Theme.of(context).accentColor, - width: 1, - )))), - SizedBox( - height: 10.0, + onSaved: (text) { + ssid = text ?? ''; + }, + initialValue: widget.wifiName, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ), + const SizedBox( + height: 10, ), PasswordFormField( initialValue: password, onSaved: (text) { - password = text; + password = text ?? ''; }, ), - SizedBox( - height: 10.0, + const SizedBox( + height: 10, ), SizedBox( width: double.infinity, - height: 50.0, + height: 50, child: MaterialButton( - child: Text('Provision'), - color: Colors.lightBlueAccent, - onPressed: () { - if (_formKey.currentState.validate()) { - _formKey.currentState.save(); - if (widget.onSubmit != null) { - widget.onSubmit(ssid, password); - } - Navigator.of(context).pop(); - } - }), - ) + color: Colors.lightBlueAccent, + onPressed: () { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + widget.onSubmit(ssid, password); + Navigator.of(context).pop(); + } + }, + child: const Text('Provision'), + ), + ), ], ), ), diff --git a/example/lib/wifi_screen/wifi_event.dart b/example/lib/wifi_screen/wifi_event.dart index 5d6af4d..0e17fb5 100644 --- a/example/lib/wifi_screen/wifi_event.dart +++ b/example/lib/wifi_screen/wifi_event.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -abstract class WifiEvent extends Equatable { +sealed class WifiEvent extends Equatable { const WifiEvent(); @override @@ -9,9 +9,8 @@ abstract class WifiEvent extends Equatable { // events for BLE provisioning class WifiEventLoadBLE extends WifiEvent { - final Map selectedDevice; - const WifiEventLoadBLE(this.selectedDevice); + final Map selectedDevice; @override List get props => [selectedDevice]; @@ -24,20 +23,21 @@ class WifiEventScanningBLE extends WifiEvent {} class WifiEventScannedBLE extends WifiEvent {} class WifiEventLoadedBLE extends WifiEvent { + const WifiEventLoadedBLE({required this.wifiName}); final String wifiName; - WifiEventLoadedBLE({this.wifiName}); - @override List get props => [wifiName]; } class WifiEventStartProvisioningBLE extends WifiEvent { + const WifiEventStartProvisioningBLE({ + required this.ssid, + required this.password, + }); final String ssid; final String password; - WifiEventStartProvisioningBLE({this.ssid, this.password}); - @override List get props => [ssid, password]; } @@ -52,20 +52,22 @@ class WifiEventScanningSoftAP extends WifiEvent {} class WifiEventScannedSoftAP extends WifiEvent {} class WifiEventLoadedSoftAP extends WifiEvent { + const WifiEventLoadedSoftAP({required this.wifiName}); final String wifiName; - WifiEventLoadedSoftAP({this.wifiName}); - @override List get props => [wifiName]; } class WifiEventStartProvisioningSoftAP extends WifiEvent { + const WifiEventStartProvisioningSoftAP({ + required this.ssid, + required this.password, + }); + final String ssid; final String password; - WifiEventStartProvisioningSoftAP({this.ssid, this.password}); - @override List get props => [ssid, password]; -} \ No newline at end of file +} diff --git a/example/lib/wifi_screen/wifi_screen.dart b/example/lib/wifi_screen/wifi_screen.dart index 7e3004a..40a8fca 100644 --- a/example/lib/wifi_screen/wifi_screen.dart +++ b/example/lib/wifi_screen/wifi_screen.dart @@ -1,96 +1,110 @@ +import 'package:esp_provisioning_softap_example/scan_list.dart'; +import 'package:esp_provisioning_softap_example/wifi_screen/wifi.dart'; +import 'package:esp_provisioning_softap_example/wifi_screen/wifi_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../scan_list.dart'; -import 'wifi.dart'; -import 'wifi_dialog.dart'; - +import 'package:flutter_spinkit/flutter_spinkit.dart'; class WiFiScreenSoftAP extends StatefulWidget { - WiFiScreenSoftAP({Key key}) : super(key: key); + const WiFiScreenSoftAP({super.key}); @override _WiFiScreenSoftAPState createState() => _WiFiScreenSoftAPState(); } class _WiFiScreenSoftAPState extends State { - void _showDialog(Map wifi, BuildContext _context) { - showDialog( - context: _context, - builder: (BuildContext context) { - return WifiDialog( - wifiName: wifi['ssid'], - onSubmit: (ssid, password) { - print('ssid =$ssid, password = $password'); - BlocProvider.of(_context).add( - WifiEventStartProvisioningSoftAP(ssid: ssid, password: password)); - }, - ); - }); + Future _showDialog(Map wifi, BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext context) { + return WifiDialog( + wifiName: wifi['ssid']! as String, + onSubmit: (ssid, password) { + print('ssid =$ssid, password = $password'); + BlocProvider.of(context).add( + WifiEventStartProvisioningSoftAP(ssid: ssid, password: password), + ); + }, + ); + }, + ); } Widget _buildStepper(int step, WifiState state) { - List _statusWidget = [ + final statusWidget0 = [ Column( children: [ ElevatedButton.icon( - onPressed: () {}, - icon: SpinKitFoldingCube( - color: Colors.lightBlueAccent, - size: 20, - ), - label: Text('Connecting..')) + onPressed: () {}, + icon: const SpinKitFoldingCube( + color: Colors.lightBlueAccent, + size: 20, + ), + label: const Text('Connecting..'), + ), ], ), Column( children: [ ElevatedButton.icon( - onPressed: () {}, - icon: Icon( - Icons.check, + onPressed: () {}, + icon: const Icon( + Icons.check, + color: Colors.white38, + ), + label: const Text( + 'Connected', + style: TextStyle( color: Colors.white38, ), - label: Text( - 'Connected', - style: TextStyle( - color: Colors.white38, - ), - )), + ), + ), ElevatedButton.icon( - onPressed: () {}, - icon: SpinKitFoldingCube( - color: Colors.lightBlueAccent, - size: 20, - ), - label: Text('Scanning...')) + onPressed: () {}, + icon: const SpinKitFoldingCube( + color: Colors.lightBlueAccent, + size: 20, + ), + label: const Text('Scanning...'), + ), ], - ) + ), ]; - var wifiList; + late Widget wifiList; + if (state is WifiStateLoaded) { wifiList = Expanded( - child: ScanList(state.wifiList, Icons.wifi, disableLoading: true, - onTap: (Map item, BuildContext _context) { - _showDialog(item, _context); - })); + child: ScanList( + state.wifiList, + Icons.wifi, + disableLoading: true, + onTap: _showDialog, + ), + ); } - var body = Expanded(child: Container()); - var statusWidget; + + Widget body = Expanded(child: Container()); + + Widget? statusWidget; if (step < 2) { - statusWidget = Expanded(child: _statusWidget[step]); + statusWidget = Expanded(child: statusWidget0[step]); body = Expanded( - child: - SpinKitDoubleBounce(color: Theme.of(context).accentColor)); + child: SpinKitDoubleBounce( + color: Theme.of(context).colorScheme.secondary, + ), + ); } else { body = wifiList; } + return Column( children: [ Center( - child: Text( - 'Select Wifi network', - style: Theme.of(context).textTheme.headline5, - )), + child: Text( + 'Select Wifi network', + style: Theme.of(context).textTheme.headlineSmall, + ), + ), body, statusWidget ?? Container(), ], @@ -103,17 +117,18 @@ class _WiFiScreenSoftAPState extends State { backgroundColor: Colors.transparent, appBar: AppBar( elevation: 0, - iconTheme: IconThemeData( + iconTheme: const IconThemeData( color: Colors.black, //change your color here ), backgroundColor: Colors.transparent, title: Text( 'Provisioning...', - style: Theme.of(context).textTheme.bodyText1, + style: Theme.of(context).textTheme.bodyLarge, ), ), body: BlocProvider( - create: (BuildContext context) => WiFiBlocSoftAP()..add(WifiEventLoadSoftAP()), + create: (BuildContext context) => + WiFiBlocSoftAP()..add(WifiEventLoadSoftAP()), child: BlocBuilder( builder: (BuildContext context, WifiState state) { if (state is WifiStateConnecting) { @@ -131,57 +146,65 @@ class _WiFiScreenSoftAPState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ SpinKitThreeBounce( - color: Theme.of(context).textSelectionColor, + color: Theme.of(context).primaryColor, size: 20, ), - Text('Provisioning', - style: Theme.of(context).textTheme.bodyText1), + Text( + 'Provisioning', + style: Theme.of(context).textTheme.bodyLarge, + ), ], ), ); } if (state is WifiStateProvisionedSuccessfully) { - return Container( - child: Center( - child: MaterialButton(child: Text('Done'), color: Colors.lightBlueAccent, onPressed: () { + return Center( + child: MaterialButton( + color: Colors.lightBlueAccent, + onPressed: () { Navigator.of(context).pop(); - },), + }, + child: const Text('Done'), ), ); } if (state is WifiStateProvisioningAuthError) { - return Container( - child: Center( - child: MaterialButton(child: Text('Auth Error'), color: Colors.redAccent, onPressed: () { + return Center( + child: MaterialButton( + color: Colors.redAccent, + onPressed: () { Navigator.of(context).pop(); - },), + }, + child: const Text('Auth Error'), ), ); } if (state is WifiStateProvisioningNetworkNotFound) { - return Container( - child: Center( - child: MaterialButton(child: Text('Network Not Found'), color: Colors.redAccent, onPressed: () { + return Center( + child: MaterialButton( + color: Colors.redAccent, + onPressed: () { Navigator.of(context).pop(); - },), + }, + child: const Text('Network Not Found'), ), ); } if (state is WifiStateProvisioningDisconnected) { - return Container( - child: Center( - child: MaterialButton(child: Text('Subol Device Disconnected'), color: Colors.redAccent, onPressed: () { + return Center( + child: MaterialButton( + color: Colors.redAccent, + onPressed: () { Navigator.of(context).pop(); - },), + }, + child: const Text('Device Disconnected'), ), ); } - return Container( - child: Center( - child: SpinKitThreeBounce( - color: Theme.of(context).textSelectionColor, - size: 20, - ), + return Center( + child: SpinKitThreeBounce( + color: Theme.of(context).primaryColor, + size: 20, ), ); }, diff --git a/example/lib/wifi_screen/wifi_state.dart b/example/lib/wifi_screen/wifi_state.dart index 17e6e46..0182880 100644 --- a/example/lib/wifi_screen/wifi_state.dart +++ b/example/lib/wifi_screen/wifi_state.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -abstract class WifiState extends Equatable { +sealed class WifiState extends Equatable { const WifiState(); @override @@ -12,22 +12,23 @@ class WifiStateLoading extends WifiState {} class WifiStateConnecting extends WifiState {} class WifiStateError extends WifiState { + const WifiStateError(this.errorMsg); final String errorMsg; - - WifiStateError(this.errorMsg); } class WifiStateScanning extends WifiState {} - class WifiStateLoaded extends WifiState { + const WifiStateLoaded({required this.wifiList}); final List> wifiList; - - WifiStateLoaded({this.wifiList}); } class WifiStateProvisioning extends WifiState {} + class WifiStateProvisioningDisconnected extends WifiState {} + class WifiStateProvisioningAuthError extends WifiState {} + class WifiStateProvisioningNetworkNotFound extends WifiState {} + class WifiStateProvisionedSuccessfully extends WifiState {} diff --git a/example/pubspec.lock b/example/pubspec.lock index 42c0d12..a1eed0c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,98 +5,119 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.11.0" bloc: - dependency: transitive + dependency: "direct main" description: name: bloc - url: "https://pub.dartlang.org" + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "8.1.2" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.0" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" cryptography: dependency: transitive description: name: cryptography - url: "https://pub.dartlang.org" + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.7.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" equatable: dependency: "direct main" description: name: equatable - url: "https://pub.dartlang.org" + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.5" esp_provisioning_softap: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.0.3" + version: "2.0.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "6a2ef17156f4dc49684f9d99aaf4a93aba8ac49f5eac861755f5730ddf6e2e4e" + url: "https://pub.dev" source: hosted version: "1.0.0" flutter: @@ -108,16 +129,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - url: "https://pub.dartlang.org" + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" source: hosted - version: "7.3.3" + version: "8.1.3" flutter_spinkit: dependency: "direct main" description: name: flutter_spinkit - url: "https://pub.dartlang.org" + sha256: b39c753e909d4796906c5696a14daf33639a76e017136c8d82bf3e620ce5bb8e + url: "https://pub.dev" source: hosted - version: "4.1.2+1" + version: "5.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -127,79 +150,90 @@ packages: dependency: transitive description: name: http - url: "https://pub.dartlang.org" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" source: hosted - version: "0.13.4" + version: "1.1.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185 + url: "https://pub.dev" source: hosted version: "4.0.0" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.7" logger: - dependency: transitive + dependency: "direct main" description: name: logger - url: "https://pub.dartlang.org" + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "2.0.2+1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.5.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.10.0" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.8.3" protobuf: dependency: transitive description: name: protobuf - url: "https://pub.dartlang.org" + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.1.0" provider: dependency: transitive description: name: provider - url: "https://pub.dartlang.org" + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.5" sky_engine: dependency: transitive description: flutter @@ -209,65 +243,90 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" string_validator: dependency: transitive description: name: string_validator - url: "https://pub.dartlang.org" + sha256: "54d4f42cd6878ae72793a58a529d9a18ebfdfbfebd9793bbe55c9b28935e8543" + url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.2" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.6.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + url: "https://pub.dev" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.2.0-194.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4361b06..b2ff121 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,74 +1,28 @@ name: esp_provisioning_softap_example description: Demonstrates how to use the esp_provisioning_softap plugin. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.10.0' dependencies: - flutter: - sdk: flutter - flutter_spinkit: ^4.1.2 - equatable: ^2.0.0 - flutter_bloc: ^7.0.0 + bloc: ^8.1.2 + cupertino_icons: ^1.0.6 + equatable: ^2.0.5 esp_provisioning_softap: path: ../ - # When depending on this package from a real application you should use: - # esp_provisioning_softap: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - # path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 + flutter: + sdk: flutter + flutter_bloc: ^8.1.3 + flutter_spinkit: ^5.2.0 + logger: ^2.0.2+1 dev_dependencies: flutter_test: sdk: flutter + very_good_analysis: ^5.1.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/esp_provisioning_softap.dart b/lib/esp_provisioning_softap.dart index f2a6c3f..c34d7a9 100644 --- a/lib/esp_provisioning_softap.dart +++ b/lib/esp_provisioning_softap.dart @@ -1,6 +1,9 @@ +/// A Flutter plugin for provisioning ESP32 modules with SoftAP. +library esp_provisioning_softap; + +export 'src/connection_models.dart'; export 'src/provisioning.dart'; export 'src/security.dart'; export 'src/security1.dart'; export 'src/transport.dart'; export 'src/transport_http.dart'; -export 'src/connection_models.dart'; \ No newline at end of file diff --git a/lib/logger.dart b/lib/logger.dart new file mode 100644 index 0000000..021cc9a --- /dev/null +++ b/lib/logger.dart @@ -0,0 +1 @@ +export 'src/logger.dart'; diff --git a/lib/src/connection_models.dart b/lib/src/connection_models.dart index a33f7e8..9311aa4 100644 --- a/lib/src/connection_models.dart +++ b/lib/src/connection_models.dart @@ -1,13 +1,13 @@ enum WifiConnectionState { - Connected, - Connecting, - Disconnected, - ConnectionFailed, + connected, + connecting, + disconnected, + connectionFailed, } enum WifiConnectFailedReason { - AuthError, - NetworkNotFound, + authError, + networkNotFound, } class ConnectionStatus { diff --git a/lib/src/cryptor.dart b/lib/src/cryptor.dart index c50bd3b..09c4b7a 100644 --- a/lib/src/cryptor.dart +++ b/lib/src/cryptor.dart @@ -1,24 +1,38 @@ import 'dart:async'; -import 'dart:typed_data'; +import 'dart:convert'; import 'package:flutter/services.dart'; class Cryptor { - static const MethodChannel _channel = - MethodChannel('esp_provisioning_softap'); - - Future init(Uint8List key, Uint8List iv) async { - return await _channel.invokeMethod('init', { - 'key': key, - 'iv': iv, - }); + static const _channel = MethodChannel('esp_provisioning_softap'); + + Future _invokeMethod( + String method, [ + Map? arguments, + ]) async { + final res = await _channel.invokeMethod(method, arguments); + if (res is! T) { + throw Exception(''' +Invalid return type after running method channel call. + +The method channel call for "$method" returned a value of type ${res.runtimeType} which is not assignable to the expected return type of $T. + +-- Call details -- +Method: +"$method" + +Arguments: +${arguments == null ? 'none' : const JsonEncoder.withIndent(' ').convert(arguments)} +'''); + } + + return res; + } + + Future init(Uint8List key, Uint8List iv) { + return _invokeMethod('init', {'key': key, 'iv': iv}); } - Future crypt(Uint8List data) async { - return await _channel.invokeMethod( - 'crypt', - { - 'data': data, - }, - ); + Future crypt(Uint8List data) { + return _invokeMethod('crypt', {'data': data}); } } diff --git a/lib/src/logger.dart b/lib/src/logger.dart new file mode 100644 index 0000000..ebe0ad6 --- /dev/null +++ b/lib/src/logger.dart @@ -0,0 +1,11 @@ +import 'package:logger/logger.dart'; + +/// The global logger for this library. +/// +/// Can be overridden by consumers of this library for custom logging behavior. +Logger logger = Logger( + printer: PrettyPrinter( + printTime: true, + printEmojis: false, + ), +); diff --git a/lib/src/provisioning.dart b/lib/src/provisioning.dart index ddee597..f18f179 100644 --- a/lib/src/provisioning.dart +++ b/lib/src/provisioning.dart @@ -1,85 +1,87 @@ import 'dart:convert'; import 'dart:typed_data'; - +import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; +import 'package:esp_provisioning_softap/logger.dart'; import 'package:esp_provisioning_softap/src/proto/dart/constants.pb.dart'; +import 'package:esp_provisioning_softap/src/proto/dart/session.pb.dart'; import 'package:esp_provisioning_softap/src/proto/dart/wifi_config.pb.dart'; +import 'package:esp_provisioning_softap/src/proto/dart/wifi_constants.pb.dart' + as wifi_constants; import 'package:esp_provisioning_softap/src/proto/dart/wifi_scan.pb.dart'; +import 'package:meta/meta.dart'; -import 'connection_models.dart'; -import 'proto/dart/session.pb.dart'; -import 'security.dart'; - - -import 'transport.dart'; - +@immutable class Provisioning { - Transport transport; - Security security; - - Provisioning({required this.transport, required this.security}); - - Future establishSession() async { - try { - SessionData? responseData; - await transport.connect(); - while (true) { - var request = await security.securitySession(responseData); - if (request == null) { - return true; - } - var response = await transport.sendReceive( - 'prov-session', request.writeToBuffer()); - if (response?.isEmpty ?? true) { - throw Exception('Empty response'); - } - responseData = SessionData.fromBuffer(List.from(response!)); + const Provisioning({ + required this.transport, + required this.security, + }); + + final Transport transport; + final Security security; + + Future establishSession() async { + SessionData? responseData; + await transport.connect(); + while (true) { + final request = await security.securitySession(responseData); + if (request == null) { + return; + } + final response = await transport.sendReceive( + 'prov-session', + request.writeToBuffer(), + ); + if (response?.isEmpty ?? true) { + throw Exception('Empty response'); } - } catch (e) { - print('EstablishSession error $e'); - return false; + responseData = SessionData.fromBuffer(List.from(response!)); } } Future dispose() async { - return await transport.disconnect(); + return transport.disconnect(); } Future>?> startScanWiFi() async { - return await scan(); + return scan(); } Future startScanResponse(Uint8List? data) async { - Uint8List uint8list = await security.decrypt(data!); - var respPayload = WiFiScanPayload.fromBuffer(List.from(uint8list)); + final uint8list = await security.decrypt(data!); + final respPayload = WiFiScanPayload.fromBuffer(List.from(uint8list)); if (respPayload.msg != WiFiScanMsgType.TypeRespScanStart) { throw Exception('Invalid expected message type $respPayload'); } return respPayload; } - Future startScanRequest( - {bool blocking = true, - bool passive = false, - int groupChannels = 5, - int periodMs = 0}) async { - WiFiScanPayload payload = WiFiScanPayload(); - payload.msg = WiFiScanMsgType.TypeCmdScanStart; - - CmdScanStart scanStart = CmdScanStart(); - scanStart.blocking = blocking; - scanStart.passive = passive; - scanStart.groupChannels = groupChannels; - scanStart.periodMs = periodMs; - payload.cmdScanStart = scanStart; - var reqData = await security.encrypt(payload.writeToBuffer()); - var respData = await transport.sendReceive('prov-scan', reqData); - return await startScanResponse(respData); + Future startScanRequest({ + bool blocking = true, + bool passive = false, + int groupChannels = 5, + int periodMs = 0, + }) async { + final scanStart = CmdScanStart() + ..blocking = blocking + ..passive = passive + ..groupChannels = groupChannels + ..periodMs = periodMs; + + final payload = WiFiScanPayload() + ..msg = WiFiScanMsgType.TypeCmdScanStart + ..cmdScanStart = scanStart; + + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-scan', reqData); + + return startScanResponse(respData); } Future scanStatusResponse(Uint8List? data) async { - Uint8List uint8list = await security.decrypt(data!); - var respPayload = WiFiScanPayload.fromBuffer(List.from(uint8list)); + final uint8list = await security.decrypt(data!); + final respPayload = WiFiScanPayload.fromBuffer(List.from(uint8list)); if (respPayload.msg != WiFiScanMsgType.TypeRespScanStatus) { throw Exception('Invalid expected message type $respPayload'); } @@ -87,167 +89,173 @@ class Provisioning { } Future scanStatusRequest() async { - print('scanStatusRequest started'); - WiFiScanPayload payload = WiFiScanPayload(); - payload.msg = WiFiScanMsgType.TypeCmdScanStatus; - print('before encrypt'); - var reqData = await security.encrypt(payload.writeToBuffer()); - print('after encrypt'); - print('before sendreceive'); - - var respData = await transport.sendReceive('prov-scan', reqData); - print('after sendreceive'); - print('before scanStatusResponse'); - - return await scanStatusResponse(respData); + final payload = WiFiScanPayload()..msg = WiFiScanMsgType.TypeCmdScanStatus; + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-scan', reqData); + return scanStatusResponse(respData); } - Future>> scanResultRequest( - {int startIndex = 0, int count = 0}) async { - WiFiScanPayload payload = WiFiScanPayload(); - payload.msg = WiFiScanMsgType.TypeCmdScanResult; - - CmdScanResult cmdScanResult = new CmdScanResult(); - cmdScanResult.startIndex = startIndex; - cmdScanResult.count = count; - - payload.cmdScanResult = cmdScanResult; - print('++++ aaaa ++++'); - var reqData = await security.encrypt(payload.writeToBuffer()); - print('++++ xxxx ++++'); - var respData = await transport.sendReceive('prov-scan', reqData); - print('++++ yyyyy ++++'); - return await scanResultResponse(respData); + Future>> scanResultRequest({ + int startIndex = 0, + int count = 0, + }) async { + final cmdScanResult = CmdScanResult() + ..startIndex = startIndex + ..count = count; + + final payload = WiFiScanPayload() + ..msg = WiFiScanMsgType.TypeCmdScanResult + ..cmdScanResult = cmdScanResult; + + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-scan', reqData); + return scanResultResponse(respData); } Future>> scanResultResponse(Uint8List? data) async { - var respPayload = WiFiScanPayload.fromBuffer(await security.decrypt(data!)); + final respPayload = + WiFiScanPayload.fromBuffer(await security.decrypt(data!)); if (respPayload.msg != WiFiScanMsgType.TypeRespScanResult) { throw Exception('Invalid expected message type $respPayload'); } - List> ret = []; - for (var entry in respPayload.respScanResult.entries) { - ret.add({ - 'ssid': utf8.decode(entry.ssid), - 'channel': entry.channel, - 'rssi': entry.rssi, - 'bssid': entry.bssid, - 'auth': entry.auth.toString(), - }); - } - return ret; + return respPayload.respScanResult.entries.map((e) { + return { + 'ssid': utf8.decode(e.ssid), + 'channel': e.channel, + 'rssi': e.rssi, + 'bssid': e.bssid, + 'auth': e.auth.toString(), + }; + }).toList(); } - Future>?> scan( - - {bool blocking = true, - bool passive = false, - int groupChannels = 5, - int periodMs = 0}) async { - try { - print('Scan Started'); - await startScanRequest( - blocking: blocking, - passive: passive, - groupChannels: groupChannels, - periodMs: periodMs); - var status = await scanStatusRequest(); - var resultCount = status.respScanStatus.resultCount; - List> ret = []; - if (resultCount > 0) { - var index = 0; - var remaining = resultCount; - while (remaining > 0) { - var count = remaining > 4 ? 4 : remaining; - var data = await scanResultRequest(startIndex: index, count: count); - ret.addAll(data); - remaining -= count; - index += count; - } + Future>?> scan({ + bool blocking = true, + bool passive = false, + int groupChannels = 5, + int periodMs = 0, + }) async { + logger.d('Scan Started'); + await startScanRequest( + blocking: blocking, + passive: passive, + groupChannels: groupChannels, + periodMs: periodMs, + ); + final status = await scanStatusRequest(); + final resultCount = status.respScanStatus.resultCount; + final ret = >[]; + if (resultCount > 0) { + var index = 0; + var remaining = resultCount; + while (remaining > 0) { + final count = remaining > 4 ? 4 : remaining; + final data = await scanResultRequest(startIndex: index, count: count); + ret.addAll(data); + remaining -= count; + index += count; } - return ret; - } catch (e) { - print('Error scan wifi $e'); } - return null; + return ret; } - Future sendWifiConfig({required String ssid, required String password}) async { - var payload = WiFiConfigPayload(); - payload.msg = WiFiConfigMsgType.TypeCmdSetConfig; + Future sendWifiConfig({ + required String ssid, + required String password, + }) async { + final cmdSetConfig = CmdSetConfig() + ..ssid = utf8.encode(ssid) + ..passphrase = utf8.encode(password); + + final payload = WiFiConfigPayload() + ..msg = WiFiConfigMsgType.TypeCmdSetConfig + ..cmdSetConfig = cmdSetConfig; + + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-config', reqData); + final respRaw = await security.decrypt(respData!); + final respPayload = WiFiConfigPayload.fromBuffer(respRaw); + if (respPayload.respSetConfig.status == Status.Success) { + return; + } - var cmdSetConfig = CmdSetConfig(); - cmdSetConfig.ssid = utf8.encode(ssid); - cmdSetConfig.passphrase = utf8.encode(password); - payload.cmdSetConfig = cmdSetConfig; - var reqData = await security.encrypt(payload.writeToBuffer()); - var respData = await transport.sendReceive('prov-config', reqData); - var respRaw = await security.decrypt(respData!); - var respPayload = WiFiConfigPayload.fromBuffer(respRaw); - return (respPayload.respSetConfig.status == Status.Success); + throw Exception('Invalid expected message type $respPayload'); } Future applyWifiConfig() async { - var payload = WiFiConfigPayload(); - payload.msg = WiFiConfigMsgType.TypeCmdApplyConfig; - var reqData = await security.encrypt(payload.writeToBuffer()); - var respData = await transport.sendReceive('prov-config', reqData); - var respRaw = await security.decrypt(respData!); - var respPayload = WiFiConfigPayload.fromBuffer(respRaw); + final payload = WiFiConfigPayload() + ..msg = WiFiConfigMsgType.TypeCmdApplyConfig; + + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-config', reqData); + final respRaw = await security.decrypt(respData!); + final respPayload = WiFiConfigPayload.fromBuffer(respRaw); return (respPayload.respApplyConfig.status == Status.Success); } Future getStatus() async { - var payload = WiFiConfigPayload(); - payload.msg = WiFiConfigMsgType.TypeCmdGetStatus; + final cmdGetStatus = CmdGetStatus(); - var cmdGetStatus = CmdGetStatus(); - payload.cmdGetStatus = cmdGetStatus; + final payload = WiFiConfigPayload() + ..msg = WiFiConfigMsgType.TypeCmdGetStatus + ..cmdGetStatus = cmdGetStatus; - var reqData = await security.encrypt(payload.writeToBuffer()); - var respData = await transport.sendReceive('prov-config', reqData); - var respRaw = await security.decrypt(respData!); - var respPayload = WiFiConfigPayload.fromBuffer(respRaw); + final reqData = await security.encrypt(payload.writeToBuffer()); + final respData = await transport.sendReceive('prov-config', reqData); + final respRaw = await security.decrypt(respData!); + final respPayload = WiFiConfigPayload.fromBuffer(respRaw); - if (respPayload.respGetStatus.staState.value == 0) { - return ConnectionStatus( - state: WifiConnectionState.Connected, - ip: respPayload.respGetStatus.connected.ip4Addr); - } else if (respPayload.respGetStatus.staState.value == 1) { - return ConnectionStatus(state: WifiConnectionState.Connecting); - } else if (respPayload.respGetStatus.staState.value == 2) { - return ConnectionStatus(state: WifiConnectionState.Disconnected); - } else if (respPayload.respGetStatus.staState.value == 3) { - if (respPayload.respGetStatus.failReason.value == 0) { + switch (respPayload.respGetStatus.staState) { + case wifi_constants.WifiStationState.Connected: return ConnectionStatus( - state: WifiConnectionState.ConnectionFailed, - failedReason: WifiConnectFailedReason.AuthError, + state: WifiConnectionState.connected, + ip: respPayload.respGetStatus.connected.ip4Addr, ); - } else if (respPayload.respGetStatus.failReason.value == 1) { + case wifi_constants.WifiStationState.Connecting: + return ConnectionStatus(state: WifiConnectionState.connecting); + case wifi_constants.WifiStationState.Disconnected: + return ConnectionStatus(state: WifiConnectionState.disconnected); + case wifi_constants.WifiStationState.ConnectionFailed: + switch (respPayload.respGetStatus.failReason) { + case wifi_constants.WifiConnectFailedReason.AuthError: + return ConnectionStatus( + state: WifiConnectionState.connectionFailed, + failedReason: WifiConnectFailedReason.authError, + ); + case wifi_constants.WifiConnectFailedReason.NetworkNotFound: + return ConnectionStatus( + state: WifiConnectionState.connectionFailed, + failedReason: WifiConnectFailedReason.networkNotFound, + ); + case _: + return ConnectionStatus( + state: WifiConnectionState.connectionFailed, + ); + } + case _: return ConnectionStatus( - state: WifiConnectionState.ConnectionFailed, - failedReason: WifiConnectFailedReason.NetworkNotFound, + state: WifiConnectionState.connectionFailed, + failedReason: WifiConnectFailedReason.authError, ); - } - return ConnectionStatus(state: WifiConnectionState.ConnectionFailed); } - return ConnectionStatus( - state: WifiConnectionState.ConnectionFailed, - failedReason: WifiConnectFailedReason.AuthError, - ); } - Future sendReceiveCustomData(Uint8List data, {int packageSize = 256, String endpoint = 'custom-data'}) async { + Future sendReceiveCustomData( + Uint8List data, { + int packageSize = 256, + String endpoint = 'custom-data', + }) async { var i = data.length; - var offset = 0; - List ret = []; + const offset = 0; + var ret = []; while (i > 0) { - var needToSend = data.sublist(offset, i < packageSize ? i : packageSize); - var encrypted = await security.encrypt(needToSend); - var newData = await transport.sendReceive(endpoint, encrypted); + final needToSend = + data.sublist(offset, i < packageSize ? i : packageSize); + final encrypted = await security.encrypt(needToSend); + final newData = await transport.sendReceive(endpoint, encrypted); - if ((newData?.length ?? 0) > 0 ) { - var decrypted = await security.decrypt(newData!); + if ((newData?.length ?? 0) > 0) { + final decrypted = await security.decrypt(newData!); ret += List.from(decrypted); } i -= packageSize; diff --git a/lib/src/security.dart b/lib/src/security.dart index 317bc4e..9958a26 100644 --- a/lib/src/security.dart +++ b/lib/src/security.dart @@ -1,13 +1,13 @@ import 'dart:typed_data'; -import 'proto/dart/session.pb.dart'; +import 'package:esp_provisioning_softap/src/proto/dart/session.pb.dart'; // Enum for state of protocomm_security1 FSM enum SecurityState { - REQUEST1, - RESPONSE1_REQUEST2, - RESPONSE2, - FINISH, + request1, + response1Request2, + response2, + finished, } abstract class Security { diff --git a/lib/src/security1.dart b/lib/src/security1.dart index e4cbcf3..1630238 100644 --- a/lib/src/security1.dart +++ b/lib/src/security1.dart @@ -1,34 +1,35 @@ import 'dart:convert'; import 'dart:math'; -import 'dart:typed_data'; -import 'package:collection/collection.dart'; import 'package:cryptography/cryptography.dart'; -import 'cryptor.dart'; -import 'proto/dart/sec1.pb.dart'; -import 'proto/dart/session.pb.dart'; -import 'security.dart'; -import 'package:logger/logger.dart'; +import 'package:esp_provisioning_softap/src/cryptor.dart'; +import 'package:esp_provisioning_softap/src/logger.dart'; +import 'package:esp_provisioning_softap/src/proto/dart/sec1.pb.dart'; +import 'package:esp_provisioning_softap/src/proto/dart/session.pb.dart'; +import 'package:esp_provisioning_softap/src/security.dart'; +import 'package:flutter/foundation.dart'; class Security1 implements Security { + Security1({ + required this.pop, + this.sessionState = SecurityState.request1, + this.verbose = false, + }); + final String pop; final bool verbose; - SecurityState sessionState; late SimpleKeyPairData clientKey; late List clientPubKey; late SimplePublicKey devicePublicKey; late Uint8List deviceRandom; - Cryptor crypt = Cryptor(); - var logger = Logger(); - final algorithm = X25519(); + final Cryptor crypt = Cryptor(); - Security1( - {required this.pop, - this.sessionState = SecurityState.REQUEST1, - this.verbose = false}); + final algorithm = Cryptography.instance.x25519(); + + SecurityState sessionState; @override Future encrypt(Uint8List data) async { - logger.i('raw data before encryption: ${data.toString()}'); + logger.i('raw data before encryption: $data'); return crypt.crypt(data); } @@ -45,126 +46,145 @@ class Security1 implements Security { Uint8List _xor(Uint8List a, Uint8List b) { // XOR two inputs of type `bytes` - Uint8List ret = Uint8List(max(a.length, b.length)); + final ret = Uint8List(max(a.length, b.length)); for (var i = 0; i < max(a.length, b.length); i++) { // Convert the characters to corresponding 8-bit ASCII codes - // then XOR them and store in bytearray - final _a = i < a.length ? a[i] : 0; - final _b = i < b.length ? b[i] : 0; - ret[i] = (_a ^ _b); + // then XOR them and store in byte array + final a0 = i < a.length ? a[i] : 0; + final b0 = i < b.length ? b[i] : 0; + ret[i] = a0 ^ b0; } return ret; } @override Future securitySession(SessionData? responseData) async { - if (sessionState == SecurityState.REQUEST1) { - sessionState = SecurityState.RESPONSE1_REQUEST2; - return await setup0Request(); - } - if (sessionState == SecurityState.RESPONSE1_REQUEST2) { - sessionState = SecurityState.RESPONSE2; - if (responseData == null) { - throw Exception('Response Data is null, when needed: if-Statement SecurityState.RESPONSE1_REQUEST2'); - } - await setup0Response(responseData); - return await setup1Request(responseData); - } - if (sessionState == SecurityState.RESPONSE2) { - sessionState = SecurityState.FINISH; - if (responseData == null) { - throw Exception('Response Data is null, when needed: if-Statement SecurityState.RESPONSE2'); - } - await setup1Response(responseData); - return null; + switch (sessionState) { + case SecurityState.request1: + sessionState = SecurityState.response1Request2; + return setup0Request(); + case _ when responseData == null: + throw Exception('Response data is null but was expected.'); + case SecurityState.response1Request2: + sessionState = SecurityState.response2; + await setup0Response(responseData); + return setup1Request(responseData); + case SecurityState.response2: + sessionState = SecurityState.finished; + await setup1Response(responseData); + return null; + case _: + throw Exception( + 'securitySession called with ' + 'invalid sessionState: ${sessionState.name}', + ); } - throw Exception('Unexpected state'); } Future setup0Request() async { - var setupRequest = SessionData(); - - setupRequest.secVer = SecSchemeVersion.SecScheme1; + final setupRequest = SessionData()..secVer = SecSchemeVersion.SecScheme1; await _generateKey(); - SessionCmd0 sc0 = SessionCmd0(); + final sc0 = SessionCmd0(); await clientKey.extractPublicKey().then((publicKey) { sc0.clientPubkey = publicKey.bytes; clientPubKey = publicKey.bytes; }); - Sec1Payload sec1 = Sec1Payload(); - sec1.sc0 = sc0; + final sec1 = Sec1Payload()..sc0 = sc0; setupRequest.sec1 = sec1; - logger.i("setup0Request: clientPubkey = ${clientPubKey.toString()}"); + logger.i('setup0Request: clientPubkey = $clientPubKey'); return setupRequest; } Future setup0Response(SessionData responseData) async { - SessionData setupResp = responseData; + final setupResp = responseData; if (setupResp.secVer != SecSchemeVersion.SecScheme1) { throw Exception('Invalid sec scheme'); } - devicePublicKey = SimplePublicKey(setupResp.sec1.sr0.devicePubkey, - type: KeyPairType.x25519); + devicePublicKey = SimplePublicKey( + setupResp.sec1.sr0.devicePubkey, + type: KeyPairType.x25519, + ); deviceRandom = Uint8List.fromList(setupResp.sec1.sr0.deviceRandom); - logger.i('setup0Response:Device public key ${devicePublicKey.toString()}'); - logger.i('setup0Response:Device random ${deviceRandom.toString()}'); + logger.i( + 'setup0Response:Device public key $devicePublicKey\n' + 'setup0Response:Device random $deviceRandom', + ); final sharedKey = await algorithm.sharedSecretKey( - keyPair: clientKey, remotePublicKey: devicePublicKey); + keyPair: clientKey, + remotePublicKey: devicePublicKey, + ); await sharedKey.extractBytes().then((sharedSecret) async { Uint8List sharedKeyBytes; logger.i( - 'setup0Response: Shared key calculated: ${sharedSecret.toString()}'); - var sink = Sha256().newHashSink(); - sink.add(utf8.encode(pop)); - sink.close(); - final hash = await sink.hash(); - sharedKeyBytes = _xor( - Uint8List.fromList(sharedSecret), Uint8List.fromList(hash.bytes)); - logger.i( - 'setup0Response: pop: $pop, hash: ${hash.bytes.toString()} sharedK: ${sharedKeyBytes.toString()}'); + 'setup0Response: Shared key calculated: $sharedSecret', + ); + final sink = Sha256().newHashSink() + ..add(utf8.encode(pop)) + ..close(); + final hash = await sink.hash(); + sharedKeyBytes = _xor( + Uint8List.fromList(sharedSecret), + Uint8List.fromList(hash.bytes), + ); + logger.i({ + 'setup0Response': { + 'pop': pop, + 'hash': hash.bytes, + 'sharedK': sharedKeyBytes, + }, + }); await crypt.init(sharedKeyBytes, deviceRandom); - logger.i( - 'setup0Response: cipherSecretKey: ${sharedKeyBytes.toString()} cipherNonce: ${deviceRandom.toString()}'); + logger.i({ + 'setup0Response': { + 'cipherSecretKey': sharedKeyBytes, + 'cipherNonce': deviceRandom, + }, + }); return setupResp; }); return null; } Future setup1Request(SessionData responseData) async { - logger.i('setup1Request ${devicePublicKey.toString()}'); - var clientVerify = await encrypt(Uint8List.fromList(devicePublicKey.bytes)); - - logger.i('client verify ${clientVerify.toString()}'); - var setupRequest = SessionData(); - setupRequest.secVer = SecSchemeVersion.SecScheme1; - Sec1Payload sec1 = Sec1Payload(); - sec1.msg = Sec1MsgType.Session_Command1; - SessionCmd1 sc1 = SessionCmd1(); - sc1.clientVerifyData = clientVerify; - sec1.sc1 = sc1; - setupRequest.sec1 = sec1; + logger.i('setup1Request $devicePublicKey'); + final clientVerify = + await encrypt(Uint8List.fromList(devicePublicKey.bytes)); + + logger.i('client verify $clientVerify'); + + final sc1 = SessionCmd1()..clientVerifyData = clientVerify; + + final sec1 = Sec1Payload() + ..msg = Sec1MsgType.Session_Command1 + ..sc1 = sc1; + + final setupRequest = SessionData() + ..secVer = SecSchemeVersion.SecScheme1 + ..sec1 = sec1; + logger.i('setup1Request finished'); return setupRequest; } Future setup1Response(SessionData responseData) async { logger.i('setup1Response'); - var setupResp = responseData; + final setupResp = responseData; if (setupResp.secVer == SecSchemeVersion.SecScheme1) { final deviceVerify = setupResp.sec1.sr1.deviceVerifyData; - logger.i('Device verify: ${deviceVerify.toString()}'); + logger.i('Device verify: $deviceVerify'); final encClientPubkey = await decrypt( - Uint8List.fromList(setupResp.sec1.sr1.deviceVerifyData)); - logger.i('encClientPubkey: ${encClientPubkey.toString()}'); - logger.i('clientKey.publicKey.bytes: ${clientPubKey.toString()}'); + Uint8List.fromList(setupResp.sec1.sr1.deviceVerifyData), + ); + logger + ..i('encClientPubkey: $encClientPubkey') + ..i('clientKey.publicKey.bytes: $clientPubKey'); - Function eq = const ListEquality().equals; - if (!eq(encClientPubkey, clientPubKey)) { + if (!listEquals(encClientPubkey, clientPubKey)) { throw Exception('Mismatch in device verify'); } return null; diff --git a/lib/src/transport_http.dart b/lib/src/transport_http.dart index 5398d81..66f1ed1 100644 --- a/lib/src/transport_http.dart +++ b/lib/src/transport_http.dart @@ -1,29 +1,33 @@ import 'dart:async'; -import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; -import 'package:flutter/material.dart'; + +import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; +import 'package:esp_provisioning_softap/logger.dart'; import 'package:http/http.dart' as http; -import 'transport.dart'; -import 'dart:io'; -import 'dart:convert' as convert; +import 'package:meta/meta.dart'; import 'package:string_validator/string_validator.dart'; +@immutable class TransportHTTP implements Transport { - String hostname; - Duration timeout; - Map headers = new Map(); - var client = http.Client(); + TransportHTTP({ + required this.hostname, + this.timeout = const Duration(seconds: 10), + http.Client? client, + }) : assert( + isURL(hostname), + 'hostname is required to be a valid URL', + ), + client = client ?? http.Client(), + headers = { + HttpHeaders.contentTypeHeader: 'application/x-www-form-urlencoded', + HttpHeaders.acceptHeader: 'text/plain', + }; - TransportHTTP( - {required this.hostname, this.timeout = const Duration(seconds: 10)}) { - if (!isURL(hostname)) { - throw FormatException('hostname should be an URL.'); - } - - headers["Content-type"] = "application/x-www-form-urlencoded"; - //header["Content-type"] = "application/json"; - headers["Accept"] = "text/plain"; - } + final String hostname; + final Duration timeout; + final Map headers; + final http.Client client; @override Future connect() async { @@ -36,46 +40,37 @@ class TransportHTTP implements Transport { } void _updateCookie(http.Response response) { - String? rawCookie = response.headers['set-cookie']; + final rawCookie = response.headers[HttpHeaders.setCookieHeader]; if (rawCookie != null) { - int index = rawCookie.indexOf(';'); - headers['cookie'] = + final index = rawCookie.indexOf(';'); + headers[HttpHeaders.cookieHeader] = (index == -1) ? rawCookie : rawCookie.substring(0, index); } } @override Future sendReceive(String epName, Uint8List data) async { - try { - print("Connecting to " + hostname + "/" + epName); - final response = await client - .post( - Uri.http( - hostname, - "/" + epName, - ), - headers: headers, - body: data) - .timeout(timeout) - .onError((error, stackTrace) { - print("onError"); - print(error.toString()); - print(stackTrace.toString()); - return Future.error(error!); - }); + logger.d('Connecting to $hostname/$epName'); + final response = await client + .post( + Uri.http( + hostname, + '/$epName', + ), + headers: headers, + body: data, + ) + .timeout(timeout); - _updateCookie(response); - if (response.statusCode == 200) { - print('Connection successful'); - // client.close(); - final Uint8List body_bytes = response.bodyBytes; - return body_bytes; - } else { - print('Connection failed – HTTP-Status ${response.statusCode}'); - throw Future.error(Exception("ESP Device doesn't repond. HTTP-Status ${response.statusCode}")); - } - } catch (e) { - throw StateError('StateError in transport_http.dart – Connection error (${e.runtimeType.toString()})' + e.toString()); + _updateCookie(response); + if (response.statusCode == 200) { + logger.d('Connection successful'); + return response.bodyBytes; + } else { + logger.d('Connection failed - HTTP-Status ${response.statusCode}'); + throw Exception( + 'ESP Device is not responding. HTTP-Status ${response.statusCode}', + ); } } } diff --git a/pubspec.lock b/pubspec.lock index da9c2e0..594b560 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,72 +5,82 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.18.0" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" cryptography: dependency: "direct main" description: name: cryptography - url: "https://pub.dartlang.org" + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.7.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -85,65 +95,74 @@ packages: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" source: hosted - version: "0.13.4" + version: "1.1.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.2" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.7" logger: dependency: "direct main" description: name: logger - url: "https://pub.dartlang.org" + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "2.0.2+1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.5.0" meta: - dependency: transitive + dependency: "direct main" description: name: meta - url: "https://pub.dartlang.org" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.10.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.8.3" protobuf: dependency: "direct main" description: name: protobuf - url: "https://pub.dartlang.org" + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.1.0" sky_engine: dependency: transitive description: flutter @@ -153,65 +172,90 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" string_validator: dependency: "direct main" description: name: string_validator - url: "https://pub.dartlang.org" + sha256: "54d4f42cd6878ae72793a58a529d9a18ebfdfbfebd9793bbe55c9b28935e8543" + url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.2" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" source: hosted - version: "0.4.8" + version: "0.6.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.2" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" sdks: - dart: ">=2.16.0-100.0.dev <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.2.0-194.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 11d9f22..d9bea3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,38 +1,33 @@ name: esp_provisioning_softap -description: A Flutter plugin for provisioning ESP32 modules with SoftAP -version: 1.0.3 +description: A Flutter plugin for provisioning ESP32 modules with SoftAP. +version: 2.0.0 homepage: https://github.com/nicop2000/esp_provisioning_softap environment: - sdk: ">=2.17.0 <3.0.0" + sdk: '>=3.0.0 <4.0.0' + flutter: '>=3.10.0' dependencies: + collection: 1.18.0 + cryptography: 2.7.0 flutter: sdk: flutter - protobuf: ^2.0.0 - cryptography: ^2.0.1 - logger: ^1.0.0 - string_validator: ^0.3.0 - http: ^1.1.0 - collection: ^1.15.0 + http: 1.1.0 + logger: 2.0.2+1 + meta: 1.10.0 + protobuf: 3.1.0 + string_validator: 1.0.2 dev_dependencies: flutter_test: sdk: flutter + very_good_analysis: 5.1.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' and Android 'package' identifiers should not ordinarily - # be modified. They are used by the tooling to maintain consistency when - # adding or updating assets for this project. plugin: platforms: android: package: de.petersen.nico.esp_provisioning_softap pluginClass: EspSoftapProvisioningPlugin ios: - pluginClass: EspSoftapProvisioningPlugin \ No newline at end of file + pluginClass: EspSoftapProvisioningPlugin diff --git a/test/esp_softap_provisioning_test.dart b/test/esp_softap_provisioning_test.dart index 5c1707a..a1ba22b 100644 --- a/test/esp_softap_provisioning_test.dart +++ b/test/esp_softap_provisioning_test.dart @@ -1,23 +1,5 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; void main() { - const MethodChannel channel = MethodChannel('esp_provisioning_softap'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - // test('getPlatformVersion', () async { - // expect(await EspSoftapProvisioning.platformVersion, '42'); - // }); + test('example', () {}); }