diff options
author | Scott Murray <scott.murray@konsulko.com> | 2023-09-14 14:01:24 -0400 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2023-09-14 14:37:02 -0400 |
commit | cbbb9f40e283d12f6c52ad28609516f390316f7a (patch) | |
tree | d8d2cd8f4641299dd35a3138c0e28f11443928d6 /lib/vehicle-signals | |
parent | 5ce59ba69f1451ec18c565b7b18301856553f574 (diff) |
Rework to use KUKSA.val databroker gRPC API
Rework to move from the WebSocket API with the older KUKSA.val
server to the gRPC "VAL" API of the databroker.
Changes include:
- All VISS WebSocket API code has been removed, and the signal
providers replumbed to be driven by a new VssClient class with
a homescreen-specific child class to hold all the gRPC API
handling.
- The generated code for the VAL API and its dependencies has
been checked in under lib/generated, as there still does not
seem to be a good way to generate it during the Flutter build.
- The configuration file is now expected to be "homescreen.yaml"
instead of "homescreen_config.yaml". The authorization token
field name has been renamed to "authorization", and there are
new "use-tls" and "ca-certificate" configuration fields. TLS
is disabled by default for now, and the default CA certificate
is /etc/kuksa.val/CA.pem.
- Updated .gitignore to cover a couple of generated files that
weren't included.
Bug-AGL: SPEC-4762, SPEC-4903
Signed-off-by: Scott Murray <scott.murray@konsulko.com>
Change-Id: I1b95ed27a72435364d54ec846f2be88e3d8bb092
Diffstat (limited to 'lib/vehicle-signals')
-rw-r--r-- | lib/vehicle-signals/viss_config.dart | 29 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_connected_widget.dart | 65 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_connection_widget.dart | 41 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_methods.dart | 116 | ||||
-rw-r--r-- | lib/vehicle-signals/vss_client.dart | 113 | ||||
-rw-r--r-- | lib/vehicle-signals/vss_provider.dart | 75 | ||||
-rw-r--r-- | lib/vehicle-signals/vss_signal_providers.dart (renamed from lib/vehicle-signals/vss_providers.dart) | 32 |
7 files changed, 188 insertions, 283 deletions
diff --git a/lib/vehicle-signals/viss_config.dart b/lib/vehicle-signals/viss_config.dart deleted file mode 100644 index c2be5ee..0000000 --- a/lib/vehicle-signals/viss_config.dart +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -//import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_homescreen/config.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -//import 'package:http/http.dart' as http; - -final sockConnectprovider = FutureProvider.family<WebSocket, HttpClient>( - (ref, client) => connect(client, ref)); - -Future<HttpClient> initializeClient() async { - SecurityContext ctx = SecurityContext.defaultContext; - - HttpClient client = HttpClient(context: ctx) - ..findProxy = null - ..badCertificateCallback = (cert, host, port) { - return true; - }; - return client; -} - -Future<WebSocket> connect(HttpClient client, ref) async { - final config = ref.read(ConfigStateprovider); - WebSocket socket = await WebSocket.connect( - "wss://${config.hostname}:${config.port}", - customClient: client); - return socket; -} diff --git a/lib/vehicle-signals/viss_connected_widget.dart b/lib/vehicle-signals/viss_connected_widget.dart deleted file mode 100644 index dd3e4aa..0000000 --- a/lib/vehicle-signals/viss_connected_widget.dart +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter_homescreen/vehicle-signals/viss_config.dart'; -import 'package:flutter_homescreen/vehicle-signals/viss_methods.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_homescreen/vehicle-signals/vss_providers.dart'; - -class VISServerConnectedWidget extends ConsumerStatefulWidget { - const VISServerConnectedWidget( - {Key? key, required this.client, required this.socket}) - : super(key: key); - final WebSocket socket; - final HttpClient client; - - @override - ConsumerState<VISServerConnectedWidget> createState() => - _VISServerConnectedWidgetState(); -} - -class _VISServerConnectedWidgetState - extends ConsumerState<VISServerConnectedWidget> { - late Timer _timer; - - void _updateSocket() { - ref.read(VISServerSocketProvider.notifier).update(widget.socket); - } - - @override - void initState() { - super.initState(); - VISS.init(widget.socket, ref); - Future.delayed(Duration.zero, () => _updateSocket()); - _timer = Timer.periodic(const Duration(seconds: 2), (timer) { - if (widget.socket.readyState == 3) { - ref.refresh(sockConnectprovider(widget.client)); - } - }); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - widget.socket.listen( - (data) { - VISS.parseData(ref, data); - }, - onError: (e, stk) { - print(e.toString()); - ref.refresh(sockConnectprovider(widget.client)); - }, - ); - }); - } - - @override - void dispose() { - super.dispose(); - _timer.cancel(); - widget.socket.close(786887, "Connection lost with server!"); - } - - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/lib/vehicle-signals/viss_connection_widget.dart b/lib/vehicle-signals/viss_connection_widget.dart deleted file mode 100644 index a2abee7..0000000 --- a/lib/vehicle-signals/viss_connection_widget.dart +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -import 'dart:io'; -import 'package:flutter_homescreen/vehicle-signals/viss_config.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'viss_connected_widget.dart'; - -class VISServerConnectionWidget extends ConsumerWidget { - VISServerConnectionWidget({Key? key, required this.client}) : super(key: key); - final HttpClient client; - late WebSocket socket; - - @override - Widget build(BuildContext context, ref) { - final sockConnect = ref.watch(sockConnectprovider(client)); - - return sockConnect.when( - data: (socket) { - this.socket = socket; - this.socket.pingInterval = const Duration(seconds: 2); - return VISServerConnectedWidget(client: client, socket: this.socket); - }, - error: (e, stk) { - print(e); - ref.refresh(sockConnectprovider(client)); - return Container( - child: const Text('Connection Error', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white))); - }, - loading: () => Container( - child: Text('Connected', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white)))); - } -} diff --git a/lib/vehicle-signals/viss_methods.dart b/lib/vehicle-signals/viss_methods.dart deleted file mode 100644 index 8adcc80..0000000 --- a/lib/vehicle-signals/viss_methods.dart +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_homescreen/vehicle-signals/vss_providers.dart'; -import 'package:flutter_homescreen/vehicle-signals/vss_path.dart'; -import 'package:flutter_homescreen/config.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class VISS { - static const requestId = "test-id"; - - static void init(WebSocket socket, WidgetRef ref) { - authorize(socket, ref); - subscribe(socket, ref, VSSPath.vehicleMediaVolume); - } - - static void update(WebSocket socket, WidgetRef ref) { - get(socket, ref, VSSPath.vehicleMediaVolume); - } - - static void authorize(WebSocket socket, WidgetRef ref) { - final config = ref.read(ConfigStateprovider); - - Map<String, dynamic> map = { - "action": "authorize", - "tokens": config.kuksaAuthToken, - "requestId": requestId - }; - socket.add(jsonEncode(map)); - } - - static void get(WebSocket socket, WidgetRef ref, String path) { - final config = ref.read(ConfigStateprovider); - - Map<String, dynamic> map = { - "action": "get", - "tokens": config.kuksaAuthToken, - "path": path, - "requestId": requestId - }; - socket.add(jsonEncode(map)); - } - - static void set(WebSocket socket, WidgetRef ref, String path, String value) { - final config = ref.read(ConfigStateprovider); - Map<String, dynamic> map = { - "action": "set", - "tokens": config.kuksaAuthToken, - "path": path, - "requestId": requestId, - "value": value - }; - socket.add(jsonEncode(map)); - } - - static void subscribe(WebSocket socket, WidgetRef ref, String path) { - final config = ref.read(ConfigStateprovider); - - Map<String, dynamic> map = { - "action": "subscribe", - "tokens": config.kuksaAuthToken, - "path": path, - "requestId": requestId - }; - socket.add(jsonEncode(map)); - } - - static void parseData(WidgetRef ref, String data) { - Map<String, dynamic> dataMap = jsonDecode(data); - if (dataMap["action"] == "subscription" || dataMap["action"] == "get") { - if (dataMap.containsKey("data")) { - if ((dataMap["data"] as Map<String, dynamic>).containsKey("dp") && - (dataMap["data"] as Map<String, dynamic>).containsKey("path")) { - String path = dataMap["data"]["path"]; - Map<String, dynamic> dp = dataMap["data"]["dp"]; - if (dp.containsKey("value")) { - if (dp["value"] != "---") { - switch (path) { - case VSSPath.vehicleMediaVolume: - ref - .read(vehicleSignalMediaVolumeProvider.notifier) - .update(volume: dp["value"]); - break; - case VSSPath.vehicleInsideTemperature: - ref - .read(vehicleSignalInsideTempProvider.notifier) - .update(temp: dp["value"]); - break; - case VSSPath.vehicleOutsideTemperature: - ref - .read(vehicleSignalOutsideTempProvider.notifier) - .update(temp: dp["value"]); - break; - default: - break; - } - } else { - print("ERROR: Invalid VIS response, data not available"); - } - } else { - print("ERROR: Invalid VIS response, no 'value' key"); - } - } else if ((!dataMap["data"] as Map<String, dynamic>) - .containsKey("path")) { - print("ERROR: Invalid VIS response, no 'path' key"); - } else if ((dataMap["data"] as Map<String, dynamic>) - .containsKey("dp")) { - print("ERROR: Invalid VIS response, no 'dp' key"); - } - } else { - print("ERROR: Invalid VIS response, no 'data' key"); - } - } - } -} diff --git a/lib/vehicle-signals/vss_client.dart b/lib/vehicle-signals/vss_client.dart new file mode 100644 index 0000000..d733c92 --- /dev/null +++ b/lib/vehicle-signals/vss_client.dart @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:io'; +import 'package:meta/meta.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:grpc/grpc.dart'; +import 'package:flutter_homescreen/generated/kuksa/val/v1/val.pbgrpc.dart'; +import 'package:flutter_homescreen/generated/kuksa/val/v1/types.pb.dart'; +import 'package:flutter_homescreen/config.dart'; + +abstract class VssClient { + final KuksaConfig config; + final ClientChannel channel; + final VALClient stub; + final Ref ref; + + // Extenders will likely override this + final List<String> signals = []; + + VssClient({required this.config, required this.channel, required this.stub, required this.ref}); + + // Abstract method extenders must implement + void handleSignalUpdates(EntryUpdate update); + + void run() async { + if (signals.isEmpty) + return; + + var request = SubscribeRequest(); + for (var i = 0; i < signals.length; i++) { + var entry = SubscribeEntry(); + entry.path = signals[i]; + entry.fields.add(Field.FIELD_PATH); + entry.fields.add(Field.FIELD_VALUE); + request.entries.add(entry); + } + + try { + Map<String, String> metadata = {}; + if (config.authorization.isNotEmpty) { + metadata = {'authorization': "Bearer ${config.authorization}" }; + } + var responseStream = stub.subscribe(request, options: CallOptions(metadata: metadata)); + await for (var response in responseStream) { + for (var update in response.updates) { + if (!(update.hasEntry() && update.entry.hasPath())) + continue; + handleSignalUpdates(update); + } + } + } catch (e) { + print(e); + } + } + + void setUint32(String path, int value, [bool actuator = true]) async { + var dp = Datapoint() + ..uint32 = value; + set(path, dp, actuator); + } + + void setInt32(String path, int value, [bool actuator = true]) async { + var dp = Datapoint() + ..int32 = value; + set(path, dp, actuator); + } + + void setBool(String path, bool value, [bool actuator = true]) async { + var dp = Datapoint() + ..bool_12 = value; + set(path, dp, actuator); + } + + void setString(String path, String value, [bool actuator = true]) async { + var dp = Datapoint() + ..string = value; + set(path, dp, actuator); + } + + void setFloat(String path, double value, [bool actuator = true]) async { + var dp = Datapoint() + ..float = value; + set(path, dp, actuator); + } + + void setDouble(String path, double value, [bool actuator = true]) async { + var dp = Datapoint() + ..double_18 = value; + set(path, dp, actuator); + } + + void set(String path, Datapoint dp, bool actuator) async { + var entry = DataEntry() + ..path = path; + var update = EntryUpdate(); + if (actuator) { + entry.actuatorTarget = dp; + update.fields.add(Field.FIELD_ACTUATOR_TARGET); + } else { + entry.value = dp; + update.fields.add(Field.FIELD_VALUE); + } + update.entry = entry; + var request = SetRequest(); + request.updates.add(update); + Map<String, String> metadata = {}; + if (config.authorization.isNotEmpty) { + metadata = {'authorization': "Bearer ${config.authorization}" }; + } + await stub.set(request, options: CallOptions(metadata: metadata)); + } + +} diff --git a/lib/vehicle-signals/vss_provider.dart b/lib/vehicle-signals/vss_provider.dart new file mode 100644 index 0000000..e0fa1b8 --- /dev/null +++ b/lib/vehicle-signals/vss_provider.dart @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:io'; +import 'package:meta/meta.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:grpc/grpc.dart'; +import 'package:flutter_homescreen/generated/kuksa/val/v1/val.pbgrpc.dart'; +import 'package:flutter_homescreen/generated/kuksa/val/v1/types.pb.dart'; +import 'package:flutter_homescreen/config.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_client.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_path.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_signal_providers.dart'; + +class HomescreenVssClient extends VssClient { + @override + final List<String> signals = [ + VSSPath.vehicleMediaVolume, + VSSPath.vehicleInsideTemperature, + VSSPath.vehicleOutsideTemperature + ]; + + HomescreenVssClient({required super.config, required super.channel, required super.stub, required super.ref}); + + @override + void handleSignalUpdates(EntryUpdate update) { + switch (update.entry.path) { + case VSSPath.vehicleMediaVolume: + if (update.entry.value.hasUint32()) { + ref + .read(vehicleSignalMediaVolumeProvider.notifier) + .update(volume: update.entry.value.uint32); + } + break; + case VSSPath.vehicleInsideTemperature: + if (update.entry.value.hasFloat()) { + ref + .read(vehicleSignalInsideTempProvider.notifier) + .update(temp: update.entry.value.float); + } + break; + case VSSPath.vehicleOutsideTemperature: + if (update.entry.value.hasFloat()) { + ref + .read(vehicleSignalOutsideTempProvider.notifier) + .update(temp: update.entry.value.float); + } + break; + default: + print("ERROR: Unexpected path ${update.entry.path}"); + break; + } + } +} + +final vssClientProvider = Provider((ref) { + var config = ref.read(kuksaConfigProvider); + debugPrint("Using ${config.hostname}:${config.port}"); + ChannelCredentials creds; + if (config.use_tls && config.ca_certificate.isNotEmpty) { + print("Using TLS"); + if (config.tls_server_name.isNotEmpty) + creds = ChannelCredentials.secure(certificates: config.ca_certificate, authority: config.tls_server_name); + else + creds = ChannelCredentials.secure(certificates: config.ca_certificate); + } else { + creds = ChannelCredentials.insecure(); + } + final channel = ClientChannel(config.hostname, + port: config.port, + options: ChannelOptions(credentials: creds)); + + final stub = VALClient(channel); + + return HomescreenVssClient(config: config, channel: channel, stub: stub, ref: ref); +}); diff --git a/lib/vehicle-signals/vss_providers.dart b/lib/vehicle-signals/vss_signal_providers.dart index 630a273..8f0bfc1 100644 --- a/lib/vehicle-signals/vss_providers.dart +++ b/lib/vehicle-signals/vss_signal_providers.dart @@ -1,35 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -//import 'dart:ffi'; import 'dart:io'; import 'package:meta/meta.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -@immutable -class VISServerSocket { - const VISServerSocket({required this.socket}); - - final WebSocket? socket; - - VISServerSocket copyWith({WebSocket? socket}) { - return VISServerSocket(socket: socket ?? this.socket); - } -} - -class VISServerSocketNotifier extends StateNotifier<VISServerSocket> { - VISServerSocketNotifier() : super(_initialValue); - - static final VISServerSocket _initialValue = VISServerSocket(socket: null); - - void update(WebSocket socket) { - state = state.copyWith(socket: socket); - } -} - -final VISServerSocketProvider = - StateNotifierProvider<VISServerSocketNotifier, VISServerSocket>((ref) { - return VISServerSocketNotifier(); -}); - // Media Volume @immutable @@ -60,11 +33,6 @@ class VehicleSignalMediaVolumeNotifier } } -//final vehicleSignalMediaVolumeProvider = StateNotifierProvider< -// VehicleSignalMediaVolumeNotifier, VehicleSignalMediaVolume>((ref) { -// return VehicleSignalMediaVolumeNotifier(); -//}); - final vehicleSignalMediaVolumeProvider = StateNotifierProvider< VehicleSignalMediaVolumeNotifier, VehicleSignalMediaVolume>((ref) => VehicleSignalMediaVolumeNotifier()); |