diff --git a/README.md b/README.md index 2b9a439..c938012 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,11 @@ After connecting, the Viam specific extensions for reading and writing can be ca await device.writeRobotPartConfig( partId: 'id', secret: 'secret', + // apiKey is the preferred but optional authentication method + apiKey: APIKey( + id: 'keyID', + key: 'key' + ), ); await device.exitProvisioning(); ``` diff --git a/lib/src/api_key.dart b/lib/src/api_key.dart new file mode 100644 index 0000000..d03a9e4 --- /dev/null +++ b/lib/src/api_key.dart @@ -0,0 +1,12 @@ +part of '../viam_bluetooth_provisioning.dart'; + +class APIKey { + final String id; + final String key; + + APIKey({required this.id, required this.key}); + + String toJson() { + return jsonEncode({'id': id, 'key': key}); + } +} diff --git a/lib/src/bluetooth_device_extensions.dart b/lib/src/bluetooth_device_extensions.dart index dd730a8..094445e 100644 --- a/lib/src/bluetooth_device_extensions.dart +++ b/lib/src/bluetooth_device_extensions.dart @@ -153,6 +153,7 @@ extension ViamWriting on BluetoothDevice { Future writeRobotPartConfig({ required String partId, required String secret, + APIKey? apiKey, String appAddress = 'https://app.viam.com:443', String psk = 'viamsetup', }) async { @@ -186,6 +187,15 @@ extension ViamWriting on BluetoothDevice { orElse: () => throw Exception('appAddressCharacteristic not found'), ); await appAddressCharacteristic.write(encodedAppAddress); + + if (apiKey != null) { + final encodedApiKey = encoder.process(utf8.encode('$psk:${apiKey.toJson()}')); + final apiKeyCharacteristic = bleService.characteristics.firstWhere( + (char) => char.uuid.str == ViamBluetoothUUIDs.apiKeyCredsUUID, + orElse: () => throw Exception('apiKeyCharacteristic not found'), + ); + await apiKeyCharacteristic.write(encodedApiKey); + } } Future exitProvisioning({String psk = 'viamsetup'}) async { diff --git a/lib/src/viam_bluetooth_uuids.dart b/lib/src/viam_bluetooth_uuids.dart index d4856e7..9d25c45 100644 --- a/lib/src/viam_bluetooth_uuids.dart +++ b/lib/src/viam_bluetooth_uuids.dart @@ -7,6 +7,7 @@ class ViamBluetoothUUIDs { static String pskUUID = _uuid.v5(_namespace, _pskKey); static String robotPartUUID = _uuid.v5(_namespace, _robotPartIDKey); static String robotPartSecretUUID = _uuid.v5(_namespace, _robotPartSecretKey); + static String apiKeyCredsUUID = _uuid.v5(_namespace, _apiKeyCredsKey); static String appAddressUUID = _uuid.v5(_namespace, _appAddressKey); static String statusUUID = _uuid.v5(_namespace, _statusKey); static String cryptoUUID = _uuid.v5(_namespace, _cryptoKey); @@ -25,6 +26,7 @@ class ViamBluetoothUUIDs { static const String _pskKey = 'psk'; static const String _robotPartIDKey = 'id'; static const String _robotPartSecretKey = 'secret'; + static const String _apiKeyCredsKey = 'api_key'; static const String _appAddressKey = 'app_address'; static const String _statusKey = 'status'; static const String _cryptoKey = 'pub_key'; diff --git a/lib/viam_bluetooth_provisioning.dart b/lib/viam_bluetooth_provisioning.dart index c2ea62a..b5824d7 100644 --- a/lib/viam_bluetooth_provisioning.dart +++ b/lib/viam_bluetooth_provisioning.dart @@ -14,3 +14,4 @@ part 'src/wifi_network.dart'; part 'src/viam_bluetooth_uuids.dart'; part 'src/viam_bluetooth_provisioning.dart'; part 'src/bluetooth_device_extensions.dart'; +part 'src/api_key.dart'; diff --git a/viam_example_app/lib/provision_peripheral_screen.dart b/viam_example_app/lib/provision_peripheral_screen.dart index 9a6e11c..095c4f1 100644 --- a/viam_example_app/lib/provision_peripheral_screen.dart +++ b/viam_example_app/lib/provision_peripheral_screen.dart @@ -17,6 +17,8 @@ class _ProvisionPeripheralScreen extends State { final TextEditingController _partIdTextController = TextEditingController(); final TextEditingController _secretTextController = TextEditingController(); final TextEditingController _appAddressTextController = TextEditingController(); + final TextEditingController _apiKeyIdTextController = TextEditingController(); + final TextEditingController _apiKeyKeyTextController = TextEditingController(); List _networkList = []; @@ -47,6 +49,8 @@ class _ProvisionPeripheralScreen extends State { _partIdTextController.dispose(); _secretTextController.dispose(); _appAddressTextController.dispose(); + _apiKeyIdTextController.dispose(); + _apiKeyKeyTextController.dispose(); super.dispose(); } @@ -137,10 +141,18 @@ class _ProvisionPeripheralScreen extends State { final partId = _partIdTextController.text; final secret = _secretTextController.text; final appAddress = _appAddressTextController.text; + final apiKeyId = _apiKeyIdTextController.text.trim(); + final apiKeyKey = _apiKeyKeyTextController.text.trim(); + + APIKey? apiKey; + if (apiKeyId.isNotEmpty && apiKeyKey.isNotEmpty) { + apiKey = APIKey(id: apiKeyId, key: apiKeyKey); + } await widget.device.writeRobotPartConfig( partId: partId, secret: secret, appAddress: appAddress, + apiKey: apiKey, ); _showSnackBar('Wrote robot part config'); } catch (e) { @@ -230,6 +242,14 @@ class _ProvisionPeripheralScreen extends State { controller: _secretTextController, decoration: const InputDecoration(labelText: 'Secret'), ), + TextField( + controller: _apiKeyIdTextController, + decoration: const InputDecoration(labelText: 'API Key ID', hintText: '(will be used instead of secret if provided)'), + ), + TextField( + controller: _apiKeyKeyTextController, + decoration: const InputDecoration(labelText: 'API Key', hintText: '(will be used instead of secret if provided)'), + ), TextField( controller: _appAddressTextController, decoration: const InputDecoration(labelText: 'App Address'),