aboutsummaryrefslogtreecommitdiffstats
path: root/lib/vehicle-signals
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2023-09-14 14:01:24 -0400
committerScott Murray <scott.murray@konsulko.com>2023-09-14 14:37:02 -0400
commitcbbb9f40e283d12f6c52ad28609516f390316f7a (patch)
treed8d2cd8f4641299dd35a3138c0e28f11443928d6 /lib/vehicle-signals
parent5ce59ba69f1451ec18c565b7b18301856553f574 (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.dart29
-rw-r--r--lib/vehicle-signals/viss_connected_widget.dart65
-rw-r--r--lib/vehicle-signals/viss_connection_widget.dart41
-rw-r--r--lib/vehicle-signals/viss_methods.dart116
-rw-r--r--lib/vehicle-signals/vss_client.dart113
-rw-r--r--lib/vehicle-signals/vss_provider.dart75
-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());