aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoger Zanoni <rzanoni@igalia.com>2023-12-28 20:07:05 -0300
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>2024-01-29 12:07:20 +0000
commitc323ab8fde212120d8d1914d453afeb55b3576e5 (patch)
tree2f3565da1e623d69297b00d2a5e1e2b261692810
parent5f1b6075982b872b5db4e2195e53d19529278d5c (diff)
Update HVAC app to use grpc-web instead of websockets
Adapt the HTML5 applications to use kuksa.val service Bug-AGL: SPEC-4599 Signed-off-by: Roger Zanoni <rzanoni@igalia.com> Change-Id: I3e36a6c08041db8fb59fd7f20497c1c156bbb2f7
-rw-r--r--.gitignore1
-rw-r--r--package.json8
-rw-r--r--proto/types.proto288
-rw-r--r--proto/val.proto115
-rw-r--r--src/index.html2
-rw-r--r--src/js/buttons.js33
-rw-r--r--src/js/fan_speed.js7
-rw-r--r--src/js/kuksa.js232
-rw-r--r--src/js/paths.js12
-rw-r--r--src/js/seats.js7
-rw-r--r--src/js/temperature.js9
-rw-r--r--webpack.config.js3
12 files changed, 619 insertions, 98 deletions
diff --git a/.gitignore b/.gitignore
index b887236..cbad92c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ $RECYCLE.BIN/
.sourcemaps/
package-lock.json
build
+src/generated
.DS_Store
Thumbs.db
diff --git a/package.json b/package.json
index 06006e6..7a171b4 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"scripts": {
"webpack": "webpack",
"build": "webpack",
- "start": "webpack-dev-server"
+ "start": "webpack-dev-server",
+ "generate-grpc": "mkdirp ./src/generated && protoc -I./proto types.proto val.proto --js_out=import_style=commonjs:./src/generated --grpc-web_out=import_style=commonjs,mode=grpcwebtext:./src/generated"
},
"homepage": "https://git.automotivelinux.org/apps/html5-hvac/",
"repository": {
@@ -32,6 +33,11 @@
"webpack-dev-server": "^4.8.1"
},
"dependencies": {
+ "google-protobuf": "^3.21.2",
+ "grpc-web": "^1.5.0",
+ "mkdirp": "^3.0.1",
+ "protoc-gen-grpc-web": "^1.4.2",
+ "protoc-gen-js": "^3.21.2",
"uuid": "^9.0.0"
}
}
diff --git a/proto/types.proto b/proto/types.proto
new file mode 100644
index 0000000..8914e7a
--- /dev/null
+++ b/proto/types.proto
@@ -0,0 +1,288 @@
+/********************************************************************************
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License 2.0 which is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+syntax = "proto3";
+
+// I added V1 as in databroker. Is this good practice?
+package kuksa.val.v1;
+import "google/protobuf/timestamp.proto";
+
+option go_package = "kuksa/val/v1";
+
+// Describes a VSS entry
+// When requesting an entry, the amount of information returned can
+// be controlled by specifying either a `View` or a set of `Field`s.
+message DataEntry {
+ // Defines the full VSS path of the entry.
+ string path = 1; // [field: FIELD_PATH]
+
+ // The value (datapoint)
+ Datapoint value = 2; // [field: FIELD_VALUE]
+
+ // Actuator target (only used if the entry is an actuator)
+ Datapoint actuator_target = 3; // [field: FIELD_ACTUATOR_TARGET]
+
+ // Metadata for this entry
+ Metadata metadata = 10; // [field: FIELD_METADATA]
+}
+
+message Datapoint {
+ google.protobuf.Timestamp timestamp = 1;
+
+ oneof value {
+ string string = 11;
+ bool bool = 12;
+ sint32 int32 = 13;
+ sint64 int64 = 14;
+ uint32 uint32 = 15;
+ uint64 uint64 = 16;
+ float float = 17;
+ double double = 18;
+ StringArray string_array = 21;
+ BoolArray bool_array = 22;
+ Int32Array int32_array = 23;
+ Int64Array int64_array = 24;
+ Uint32Array uint32_array = 25;
+ Uint64Array uint64_array = 26;
+ FloatArray float_array = 27;
+ DoubleArray double_array = 28;
+ }
+}
+
+message Metadata {
+ // Data type
+ // The VSS data type of the entry (i.e. the value, min, max etc).
+ //
+ // NOTE: protobuf doesn't have int8, int16, uint8 or uint16 which means
+ // that these values must be serialized as int32 and uint32 respectively.
+ DataType data_type = 11; // [field: FIELD_METADATA_DATA_TYPE]
+
+ // Entry type
+ EntryType entry_type = 12; // [field: FIELD_METADATA_ENTRY_TYPE]
+
+ // Description
+ // Describes the meaning and content of the entry.
+ optional string description = 13; // [field: FIELD_METADATA_DESCRIPTION]
+
+ // Comment [optional]
+ // A comment can be used to provide additional informal information
+ // on a entry.
+ optional string comment = 14; // [field: FIELD_METADATA_COMMENT]
+
+ // Deprecation [optional]
+ // Whether this entry is deprecated. Can contain recommendations of what
+ // to use instead.
+ optional string deprecation = 15; // [field: FIELD_METADATA_DEPRECATION]
+
+ // Unit [optional]
+ // The unit of measurement
+ optional string unit = 16; // [field: FIELD_METADATA_UNIT]
+
+ // Value restrictions [optional]
+ // Restrict which values are allowed.
+ // Only restrictions matching the DataType {datatype} above are valid.
+ ValueRestriction value_restriction = 17; // [field: FIELD_METADATA_VALUE_RESTRICTION]
+
+ // Entry type specific metadata
+ oneof entry_specific {
+ Actuator actuator = 20; // [field: FIELD_METADATA_ACTUATOR]
+ Sensor sensor = 30; // [field: FIELD_METADATA_SENSOR]
+ Attribute attribute = 40; // [field: FIELD_METADATA_ATTRIBUTE]
+ }
+}
+
+///////////////////////
+// Actuator specific fields
+message Actuator {
+ // Nothing for now
+}
+
+////////////////////////
+// Sensor specific
+message Sensor {
+ // Nothing for now
+}
+
+////////////////////////
+// Attribute specific
+message Attribute {
+ // Nothing for now.
+}
+
+// Value restriction
+//
+// One ValueRestriction{type} for each type, since
+// they don't make sense unless the types match
+//
+message ValueRestriction {
+ oneof type {
+ ValueRestrictionString string = 21;
+ // For signed VSS integers
+ ValueRestrictionInt signed = 22;
+ // For unsigned VSS integers
+ ValueRestrictionUint unsigned = 23;
+ // For floating point VSS values (float and double)
+ ValueRestrictionFloat floating_point = 24;
+ }
+}
+
+message ValueRestrictionInt {
+ optional sint64 min = 1;
+ optional sint64 max = 2;
+ repeated sint64 allowed_values = 3;
+}
+
+message ValueRestrictionUint {
+ optional uint64 min = 1;
+ optional uint64 max = 2;
+ repeated uint64 allowed_values = 3;
+}
+
+message ValueRestrictionFloat {
+ optional double min = 1;
+ optional double max = 2;
+
+ // allowed for doubles/floats not recommended
+ repeated double allowed_values = 3;
+}
+
+// min, max doesn't make much sense for a string
+message ValueRestrictionString {
+ repeated string allowed_values = 3;
+}
+
+// VSS Data type of a signal
+//
+// Protobuf doesn't support int8, int16, uint8 or uint16.
+// These are mapped to int32 and uint32 respectively.
+//
+enum DataType {
+ DATA_TYPE_UNSPECIFIED = 0;
+ DATA_TYPE_STRING = 1;
+ DATA_TYPE_BOOLEAN = 2;
+ DATA_TYPE_INT8 = 3;
+ DATA_TYPE_INT16 = 4;
+ DATA_TYPE_INT32 = 5;
+ DATA_TYPE_INT64 = 6;
+ DATA_TYPE_UINT8 = 7;
+ DATA_TYPE_UINT16 = 8;
+ DATA_TYPE_UINT32 = 9;
+ DATA_TYPE_UINT64 = 10;
+ DATA_TYPE_FLOAT = 11;
+ DATA_TYPE_DOUBLE = 12;
+ DATA_TYPE_TIMESTAMP = 13;
+ DATA_TYPE_STRING_ARRAY = 20;
+ DATA_TYPE_BOOLEAN_ARRAY = 21;
+ DATA_TYPE_INT8_ARRAY = 22;
+ DATA_TYPE_INT16_ARRAY = 23;
+ DATA_TYPE_INT32_ARRAY = 24;
+ DATA_TYPE_INT64_ARRAY = 25;
+ DATA_TYPE_UINT8_ARRAY = 26;
+ DATA_TYPE_UINT16_ARRAY = 27;
+ DATA_TYPE_UINT32_ARRAY = 28;
+ DATA_TYPE_UINT64_ARRAY = 29;
+ DATA_TYPE_FLOAT_ARRAY = 30;
+ DATA_TYPE_DOUBLE_ARRAY = 31;
+ DATA_TYPE_TIMESTAMP_ARRAY = 32;
+}
+
+// Entry type
+enum EntryType {
+ ENTRY_TYPE_UNSPECIFIED = 0;
+ ENTRY_TYPE_ATTRIBUTE = 1;
+ ENTRY_TYPE_SENSOR = 2;
+ ENTRY_TYPE_ACTUATOR = 3;
+}
+
+// A `View` specifies a set of fields which should
+// be populated in a `DataEntry` (in a response message)
+enum View {
+ VIEW_UNSPECIFIED = 0; // Unspecified. Equivalent to VIEW_CURRENT_VALUE unless `fields` are explicitly set.
+ VIEW_CURRENT_VALUE = 1; // Populate DataEntry with value.
+ VIEW_TARGET_VALUE = 2; // Populate DataEntry with actuator target.
+ VIEW_METADATA = 3; // Populate DataEntry with metadata.
+ VIEW_FIELDS = 10; // Populate DataEntry only with requested fields.
+ VIEW_ALL = 20; // Populate DataEntry with everything.
+}
+
+// A `Field` corresponds to a specific field of a `DataEntry`.
+//
+// It can be used to:
+// * populate only specific fields of a `DataEntry` response.
+// * specify which fields of a `DataEntry` should be set as
+// part of a `Set` request.
+// * subscribe to only specific fields of a data entry.
+// * convey which fields of an updated `DataEntry` have changed.
+enum Field {
+ FIELD_UNSPECIFIED = 0; // "*" i.e. everything
+ FIELD_PATH = 1; // path
+ FIELD_VALUE = 2; // value
+ FIELD_ACTUATOR_TARGET = 3; // actuator_target
+ FIELD_METADATA = 10; // metadata.*
+ FIELD_METADATA_DATA_TYPE = 11; // metadata.data_type
+ FIELD_METADATA_DESCRIPTION = 12; // metadata.description
+ FIELD_METADATA_ENTRY_TYPE = 13; // metadata.entry_type
+ FIELD_METADATA_COMMENT = 14; // metadata.comment
+ FIELD_METADATA_DEPRECATION = 15; // metadata.deprecation
+ FIELD_METADATA_UNIT = 16; // metadata.unit
+ FIELD_METADATA_VALUE_RESTRICTION = 17; // metadata.value_restriction.*
+ FIELD_METADATA_ACTUATOR = 20; // metadata.actuator.*
+ FIELD_METADATA_SENSOR = 30; // metadata.sensor.*
+ FIELD_METADATA_ATTRIBUTE = 40; // metadata.attribute.*
+}
+
+// Error response shall be an HTTP-like code.
+// Should follow https://www.w3.org/TR/viss2-transport/#status-codes.
+message Error {
+ uint32 code = 1;
+ string reason = 2;
+ string message = 3;
+}
+
+// Used in get/set requests to report errors for specific entries
+message DataEntryError {
+ string path = 1; // vss path
+ Error error = 2;
+}
+
+message StringArray {
+ repeated string values = 1;
+}
+
+message BoolArray {
+ repeated bool values = 1;
+}
+
+message Int32Array {
+ repeated sint32 values = 1;
+}
+
+message Int64Array {
+ repeated sint64 values = 1;
+}
+
+message Uint32Array {
+ repeated uint32 values = 1;
+}
+
+message Uint64Array {
+ repeated uint64 values = 1;
+}
+
+message FloatArray {
+ repeated float values = 1;
+}
+
+message DoubleArray {
+ repeated double values = 1;
+}
diff --git a/proto/val.proto b/proto/val.proto
new file mode 100644
index 0000000..2d65b7c
--- /dev/null
+++ b/proto/val.proto
@@ -0,0 +1,115 @@
+/********************************************************************************
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License 2.0 which is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+syntax = "proto3";
+
+package kuksa.val.v1;
+
+option go_package = "kuksa/val/v1";
+
+import "types.proto";
+
+// Note on authorization:
+// Tokens (auth-token or auth-uuid) are sent as (GRPC / http2) metadata.
+//
+// The auth-token is a JWT compliant token as the examples found here:
+// https://github.com/eclipse/kuksa.val/tree/master/kuksa_certificates/jwt
+//
+// See also https://github.com/eclipse/kuksa.val/blob/master/doc/jwt.md
+//
+// Upon reception of auth-token, server shall generate an auth-uuid in metadata
+// that the client can use instead of auth-token in subsequent calls.
+
+service VAL {
+ // Get entries
+ rpc Get(GetRequest) returns (GetResponse);
+
+ // Set entries
+ rpc Set(SetRequest) returns (SetResponse);
+
+ // Subscribe to a set of entries
+ //
+ // Returns a stream of notifications.
+ //
+ // InvalidArgument is returned if the request is malformed.
+ rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse);
+
+ // Shall return information that allows the client to determine
+ // what server/server implementation/version it is talking to
+ // eg. kuksa-databroker 0.5.1
+ rpc GetServerInfo(GetServerInfoRequest) returns (GetServerInfoResponse);
+}
+
+// Define which data we want
+message EntryRequest {
+ string path = 1;
+ View view = 2;
+ repeated Field fields = 3;
+}
+
+// Request a set of entries.
+message GetRequest {
+ repeated EntryRequest entries = 1;
+}
+
+// Global errors are specified in `error`.
+// Errors for individual entries are specified in `errors`.
+message GetResponse {
+ repeated DataEntry entries = 1;
+ repeated DataEntryError errors = 2;
+ Error error = 3;
+}
+
+// Define the data we want to set
+message EntryUpdate {
+ DataEntry entry = 1;
+ repeated Field fields = 2;
+}
+
+// A list of entries to be updated
+message SetRequest {
+ repeated EntryUpdate updates = 1;
+}
+
+// Global errors are specified in `error`.
+// Errors for individual entries are specified in `errors`.
+message SetResponse {
+ Error error = 1;
+ repeated DataEntryError errors = 2;
+}
+
+// Define what to subscribe totime
+message SubscribeEntry {
+ string path = 1;
+ View view = 2;
+ repeated Field fields = 3;
+}
+
+// Subscribe to changes in datapoints.
+message SubscribeRequest {
+ repeated SubscribeEntry entries = 1;
+}
+
+// A subscription response
+message SubscribeResponse {
+ repeated EntryUpdate updates = 1;
+}
+
+message GetServerInfoRequest {
+ // Nothing yet
+}
+
+message GetServerInfoResponse {
+ string name = 1;
+ string version = 2;
+}
diff --git a/src/index.html b/src/index.html
index c8a5eed..ce61ba0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -27,7 +27,7 @@
</div>
<div class="fanSpeed">
<div class="fanSpeedContainer">
- <input id="FanSpeed" type="range" min="1" value="1" max="100" onchange="FANSPEED.set()">
+ <input id="FanSpeed" type="range" min="1" value="1" max="100" onchange="FANSPEED.set(this.value)">
<progress value="1" max="100"></progress>
</div>
<div class="label">
diff --git a/src/js/buttons.js b/src/js/buttons.js
index 76d2143..c593445 100644
--- a/src/js/buttons.js
+++ b/src/js/buttons.js
@@ -14,15 +14,6 @@
* limitations under the License.
*/
-
-
-var values = {
- ac: false,
- recirculation: false,
- rear: false,
- front: false
-};
-
var paths = {
ac: PATHS.airConditioning,
recirculation: PATHS.recirculation,
@@ -30,7 +21,7 @@ var paths = {
front: PATHS.frontDefroster,
};
-var nodes = {}
+var nodes = new Map();
export function init() {
nodes[PATHS.airConditioning] = document.getElementById('ac');
@@ -40,10 +31,15 @@ export function init() {
nodes['up'] = document.getElementById('up');
nodes['down'] = document.getElementById('down');
nodes['right'] = document.getElementById('right');
+
+ nodes.forEach(function(node, path) {
+ node.setAttribute('value', false);
+ });
}
-export function update(path, value) {
+export function update(path, dp) {
if (path == PATHS.leftAirDistribution) {
+ var value = dp.getString();
if (value == 'UP') {
nodes['up'].setAttribute('value', true);
nodes['down'].setAttribute('value', false);
@@ -60,20 +56,25 @@ export function update(path, value) {
}
} else {
var node = nodes[path];
+ var value = dp.getBool();
node.setAttribute('value', value);
}
}
export function toggle(node) {
var key = node.getAttribute('key');
- values[key] = !values[key];
+ if (KUKSA.isLocked(paths[key])) {
+ return;
+ }
+
if (key == 'up') {
- KUKSA.set(PATHS.leftAirDistribution, 'UP');
+ KUKSA.setString(PATHS.leftAirDistribution, 'UP');
} else if (key == 'down') {
- KUKSA.set(PATHS.leftAirDistribution, 'DOWN');
+ KUKSA.setString(PATHS.leftAirDistribution, 'DOWN');
} else if (key == 'right') {
- KUKSA.set(PATHS.leftAirDistribution, 'MIDDLE');
+ KUKSA.setString(PATHS.leftAirDistribution, 'MIDDLE');
} else {
- KUKSA.set(paths[key], values[key]);
+ var value = node.getAttribute('value').toLowerCase() == 'true';
+ KUKSA.setBool(paths[key], !value);
}
}
diff --git a/src/js/fan_speed.js b/src/js/fan_speed.js
index fa0ed01..74e4748 100644
--- a/src/js/fan_speed.js
+++ b/src/js/fan_speed.js
@@ -17,7 +17,8 @@
var value = 0;
var node = null;
-export function update(path, value) {
+export function update(path, dp) {
+ var value = dp.getUint32();
node.value = value;
node.parentNode.getElementsByTagName('progress')[0].value = value;
}
@@ -26,6 +27,6 @@ export function init() {
node = document.getElementById('FanSpeed');
}
-export function set() {
- KUKSA.set(PATHS.leftFanSpeed, node.value);
+export function set(value) {
+ KUKSA.setUInt32(PATHS.leftFanSpeed, value);
}
diff --git a/src/js/kuksa.js b/src/js/kuksa.js
index 9e9cc22..8542dcb 100644
--- a/src/js/kuksa.js
+++ b/src/js/kuksa.js
@@ -16,108 +16,214 @@
import { v4 as uuidv4 } from 'uuid';
-const DEFAULT_TARGET = "wss://localhost:8090";
+const DEFAULT_TARGET = "https://localhost:8888";
// TODO: use an application token when needed
// currently using https://github.com/eclipse/kuksa.val/blob/master/kuksa_certificates/jwt/super-admin.json.token
const TEST_TOKEN =
- 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJrdWtzYS52YWwiLCJpc3MiOiJFY2xpcHNlIEtVS1NBIERldiIsImFkbWluIjp0cnVlLCJtb2RpZnlUcmVlIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTc2NzIyNTU5OSwia3Vrc2EtdnNzIjp7IioiOiJydyJ9fQ.p2cnFGH16QoQ14l6ljPVKggFXZKmD-vrw8G6Vs6DvAokjsUG8FHh-F53cMsE-GDjyZH_1_CrlDCnbGlqjsFbgAylqA7IAJWp9_N6dL5p8DHZTwlZ4IV8L1CtCALs7XVqvcQKHCCzB63Y8PgVDCAqpQSRb79JPVD4pZwkBKpOknfEY5y9wfbswZiRKdgz7o61_oFnd-yywpse-23HD6v0htThVF1SuGL1PuvGJ8p334nt9bpkZO3gaTh1xVD_uJMwHzbuBCF33_f-I5QMZO6bVooXqGfe1zvl3nDrPEjq1aPulvtP8RgREYEqE6b2hB8jouTiC_WpE3qrdMw9sfWGFbm04qC-2Zjoa1yYSXoxmYd0SnliSYHAad9aXoEmFENezQV-of7sc-NX1-2nAXRAEhaqh0IRuJwB4_sG7SvQmnanwkz-sBYxKqkoFpOsZ6hblgPDOPYY2NAsZlYkjvAL2mpiInrsmY_GzGsfwPeAx31iozImX75rao8rm-XucAmCIkRlpBz6MYKCjQgyRz3UtZCJ2DYF4lKqTjphEAgclbYZ7KiCuTn9HualwtEmVzHHFneHMKl7KnRQk-9wjgiyQ5nlsVpCCblg6JKr9of4utuPO3cBvbjhB4_ueQ40cpWVOICcOLS7_w0i3pCq1ZKDEMrYDJfz87r2sU9kw1zeFQk';
+ 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJsb2NhbCBkZXYiLCJpc3MiOiJjcmVhdGVUb2tlbi5weSIsImF1ZCI6WyJrdWtzYS52YWwiXSwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3NjcyMjU1OTksInNjb3BlIjoiYWN0dWF0ZSBwcm92aWRlIn0.x-bUZwDCC663wGYrWCYjQZwQWhN1CMuKgxuIN5dUF_izwMutiqF6Xc-tnXgZa93BbT3I74WOMk4awKHBUSTWekGs3-qF6gajorbat6n5180TOqvNu4CXuIPZN5zpngf4id3smMkKOT699tPnSEbmlkj4vk-mIjeOAU-FcYA-VbkKBTsjvfFgKa2OdB5h9uZARBg5Rx7uBN3JsH1I6j9zoLid184Ewa6bhU2qniFt5iPsGJniNsKsRrrndN1KzthO13My44s56yvwSHIOrgDGbXdja_eLuOVOq9pHCjCtorPScgEuUUE4aldIuML-_j397taNP9Y3VZYVvofEK7AuiePTbzwxrZ1RAjK74h1-4ued3A2gUTjr5BsRlc9b7eLZzxLJkrqdfGAzBh_rtrB7p32TbvpjeFP30NW6bB9JS43XACUUm_S_RcyI7BLuUdnFyQDQr6l6sRz9XayYXceilHdCxbAVN0HVnBeui5Bb0mUZYIRZeY8k6zcssmokANTD8ZviDMpKlOU3t5AlXJ0nLkgyMhV9IUTwPUv6F8BTPc-CquJCUNbTyo4ywTSoODWbm3PmQ3Y46gWF06xqnB4wehLscBdVk3iAihQp3tckGhMnx5PI_Oy7utIncr4pRCMos63TnBkfrl7d43cHQTuK0kO76EWtv4ODEHgLvEAv4HA';
var kuksa_context = {
authToken: TEST_TOKEN,
- socket: undefined,
+ client: undefined,
target: DEFAULT_TARGET,
+ subscribe_entries: [],
+ subscribe_stream: null,
+ locks: new Map()
}
var pathToHandler = null;
-function logReceivedMessage(data) {
- console.log('Received message:', data)
+function updateVehicleInfo(path, dp) {
+ if (!pathToHandler.has(path)) {
+ console.log('Handler not found for', path);
+ return;
+ }
+
+ var handler = pathToHandler.get(path);
+ handler.update(path, dp)
}
-function logConnectionStatus(status, event) {
- console.log('Connection status:', status);
+function send(action, values) {
}
-function kuksaws_onopen(event) {
- logConnectionStatus('Connected.', event);
-
- authorize();
- subscribe(PATHS.leftAirDistribution);
- subscribe(PATHS.leftFanSpeed);
- subscribe(PATHS.leftSeatWarmer);
- subscribe(PATHS.rightSeatWarmer);
- subscribe(PATHS.leftSeatTemperature);
- subscribe(PATHS.rightSeatTemperature);
- subscribe(PATHS.airConditioning);
- subscribe(PATHS.recirculation);
- subscribe(PATHS.frontDefroster);
- subscribe(PATHS.rearDefroster);
+function add_subscribe_entry(path) {
+ console.log('Adding subscribe entry for ' + path + '...')
+ var entry = new VAL.SubscribeEntry();
+ entry.setPath(path);
+ entry.setView(TYPES.View.VIEW_ALL);
+ entry.addFields(TYPES.Field.FIELD_PATH);
+ entry.addFields(TYPES.Field.FIELD_VALUE);
+ kuksa_context.subscribe_entries.push(entry);
}
-function kuksaws_onerror(event) {
- logConnectionStatus('Failed to connect:', event);
+function subscribe() {
+ if (kuksa_context.client == undefined) {
+ console.log("Client not initialized.");
+ return;
+ }
+ var metadata = {'authorization': 'Bearer ' + kuksa_context.authToken };
+
+ var request = new VAL.SubscribeRequest();
+ for (var i in kuksa_context.subscribe_entries) {
+ var entry = kuksa_context.subscribe_entries[i];
+ entry.setView(TYPES.View.VIEW_CURRENT_VALUE);
+ request.addEntries(entry);
+ }
+
+ kuksa_context.subscribe_stream = kuksa_context.client.subscribe(request, metadata);
+
+ kuksa_context.subscribe_stream.on('data', function(response) {
+ var updates = response.getUpdatesList();
+ for (var i in updates) {
+ var update = updates[i];
+ var entry = update.getEntry();
+ var path = entry.getPath();
+ var dp = entry.getValue();
+
+ if (!(entry && path && dp)) {
+ continue;
+ }
+
+ lock(path);
+ updateVehicleInfo(path, dp);
+ unlock(path);
+ }
+ });
+
+ kuksa_context.subscribe_stream.on('error', function(error) {
+ console.log("Error code: " + error.code + " message: " + error.message);
+ // if an error happens here, the databroker will drop the subscriber, so
+ // we need to subscribe again
+ subscribe();
+ });
}
-function kuksaws_onmessage(event) {
- logReceivedMessage(event.data);
+// TODO: investigate why an empty response is always being returned on the
+// response
+export function get(path) {
+ if (kuksa_context.client == undefined) {
+ console.log("Client not initialized.");
+ return;
+ }
+ if (isLocked(path)) {
+ return;
+ }
- var jsonData = JSON.parse(event.data);
+ var metadata = {'authorization': 'Bearer ' + kuksa_context.authToken };
+
+ var request = new VAL.GetRequest();
+ var entry = new VAL.EntryRequest();
+ entry.setPath(path);
+ entry.setView(TYPES.View.VIEW_ALL);
+ entry.addFields(TYPES.Field.FIELD_METADATA);
+ entry.addFields(TYPES.Field.FIELD_VALUE);
+ request.addEntries(entry);
+
+ kuksa_context.client.get(request, metadata, function(error, response) {
+ if (error) {
+ console.log("Get error, code: " + error.code);
+ return;
+ }
+ if (!response) {
+ return;
+ }
+ var entries = response.getEntriesList();
+ for (var i in entries) {
+ var entry = entries[i];
+ var path = entry.getPath();
+ var dp = entry.getValue();
+
+ if (!(entry && path && dp)) {
+ continue;
+ }
+ lock(path);
+ updateVehicleInfo(path, dp);
+ unlock(path);
+ }
+ });
+}
- if (jsonData.action == 'get' ||
- jsonData.action =='subscription') {
- updateVehicleInfo(jsonData);
+export function set(path, dp) {
+ if (kuksa_context.client == undefined) {
+ console.log("Client not initialized.");
+ return;
+ }
+ if (isLocked(path)) {
+ return;
}
+ lock(path);
+
+ var metadata = {'authorization': 'Bearer ' + kuksa_context.authToken };
+
+ var entry = new TYPES.DataEntry();
+ entry.setPath(path);
+ entry.setValue(dp);
+
+ var update = new VAL.EntryUpdate();
+ update.addFields(TYPES.Field.FIELD_PATH);
+ update.addFields(TYPES.Field.FIELD_VALUE);
+ update.setEntry(entry);
+
+ var request = new VAL.SetRequest();
+ request.addUpdates(update);
+
+ kuksa_context.client.set(request, metadata, function(error, response) {
+ // don't unlock updates here, only when we get a value from
+ // the subscription updates
+ });
}
-function updateVehicleInfo(message) {
- var value = message.data.dp.value;
- var path = message.data.path;
+export function init(handlers) {
+ console.log("Initializing kuka-val module...");
+ pathToHandler = new Map(handlers);
+ pathToHandler.forEach(function(handler, path) {
+ add_subscribe_entry(path);
+ });
- if (!pathToHandler.has(path)) {
- console.log('Handler not found for', path);
- return;
+ if (kuksa_context.client == undefined) {
+ console.log("Creating kuksa-val client...");
+ kuksa_context.client = new VAL_WEB.VALClient(kuksa_context.target);
+
+ subscribe();
}
+}
- var handler = pathToHandler.get(path);
- handler.update(path, value)
+export function setInt32(path, value) {
+ var dp = new TYPES.Datapoint();
+ dp.setInt32(value);
+ set(path, dp);
}
-function send(action, values) {
- var uuid = uuidv4();
- var data = Object.assign({
- action: action,
- tokens: kuksa_context.authToken,
- requestId: uuid,
- }, values);
- var message = JSON.stringify(data);
- console.log('Sent message:', message);
- kuksa_context.socket.send(message);
- return uuid;
+export function setUInt32(path, value) {
+ var dp = new TYPES.Datapoint();
+ dp.setUint32(value);
+ set(path, dp);
}
-function subscribe(path) {
- return send('subscribe', { path: path });
+export function setBool(path, value) {
+ var dp = new TYPES.Datapoint();
+ dp.setBool(value);
+ set(path, dp);
}
-function authorize() {
- return send('authorize');
+export function setString(path, value) {
+ var dp = new TYPES.Datapoint();
+ dp.setString(value);
+ set(path, dp);
}
-export function get(path) {
- return send('get', { path: path });
+export function lock(path) {
+ kuksa_context.locks[path] = true;
}
-export function set(path, value) {
- return send('set', { path: path, value: value });
+export function unlock(path) {
+ kuksa_context.locks[path] = false;
}
-export function init(handlers) {
- pathToHandler = new Map(handlers);
- if (kuksa_context.socket == undefined) {
- kuksa_context.socket = new WebSocket(kuksa_context.target);
- kuksa_context.socket.onopen = kuksaws_onopen;
- kuksa_context.socket.onerror = kuksaws_onerror;
- kuksa_context.socket.onmessage = kuksaws_onmessage;
+export function isLocked(path) {
+ if (!kuksa_context.locks.has(path)) {
+ kuksa_context.locks[path] = false;
}
+ return kuksa_context.locks[path];
}
diff --git a/src/js/paths.js b/src/js/paths.js
index 4f3c5d3..3001b51 100644
--- a/src/js/paths.js
+++ b/src/js/paths.js
@@ -15,14 +15,14 @@
*/
// https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/Cabin/SingleHVACStation.vspec
-export const leftAirDistribution = 'Vehicle.Cabin.HVAC.Station.Row1.Left.AirDistribution';
-export const leftFanSpeed = 'Vehicle.Cabin.HVAC.Station.Row1.Left.FanSpeed';
-export const leftSeatTemperature = 'Vehicle.Cabin.Seat.Row1.Pos1.Heating';
-export const rightSeatTemperature = 'Vehicle.Cabin.Seat.Row1.Pos2.Heating';
+export const leftAirDistribution = 'Vehicle.Cabin.HVAC.Station.Row1.Driver.AirDistribution';
+export const leftFanSpeed = 'Vehicle.Cabin.HVAC.Station.Row1.Driver.FanSpeed';
+export const leftSeatTemperature = 'Vehicle.Cabin.Seat.Row1.DriverSide.Heating';
+export const rightSeatTemperature = 'Vehicle.Cabin.Seat.Row1.PassengerSide.Heating';
// https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/Cabin/SingleSeat.vspec
-export const leftSeatWarmer = 'Vehicle.Cabin.Seat.Row1.Pos1.Switch.IsWarmerEngaged';
-export const rightSeatWarmer = 'Vehicle.Cabin.Seat.Row1.Pos2.Switch.IsWarmerEngaged';
+export const leftSeatWarmer = 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsWarmerEngaged';
+export const rightSeatWarmer = 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsWarmerEngaged';
// https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/Cabin/HVAC.vspec
export const recirculation = 'Vehicle.Cabin.HVAC.IsRecirculationActive';
diff --git a/src/js/seats.js b/src/js/seats.js
index 3afc481..91b45f6 100644
--- a/src/js/seats.js
+++ b/src/js/seats.js
@@ -30,7 +30,8 @@ export function init() {
controls.rightSeatWarmer = document.getElementById('RightChair');
}
-export function update(path, value) {
+export function update(path, dp) {
+ var value = dp.getBool();
switch (path) {
case PATHS.leftSeatWarmer:
values.leftSeatWarmer = value;
@@ -44,8 +45,8 @@ export function update(path, value) {
}
export function left() {
- KUKSA.set(PATHS.leftSeatWarmer, !values.leftSeatWarmer);
+ KUKSA.setBool(PATHS.leftSeatWarmer, !values.leftSeatWarmer);
}
export function right() {
- KUKSA.set(PATHS.rightSeatWarmer, !values.rightSeatWarmer);
+ KUKSA.setBool(PATHS.rightSeatWarmer, !values.rightSeatWarmer);
}
diff --git a/src/js/temperature.js b/src/js/temperature.js
index 1e35ddf..2d29b7c 100644
--- a/src/js/temperature.js
+++ b/src/js/temperature.js
@@ -40,7 +40,8 @@ function createTemperatureElement() {
return element;
}
-export function update(path, value) {
+export function update(path, dp) {
+ var value = dp.getInt32();
var temperature, node;
if (path == PATHS.rightSeatTemperature) {
values.rightTemperature = value;
@@ -69,8 +70,7 @@ export function left() {
isScrolling = setTimeout(function() {
var index = Math.round(controls.leftTemperatureNode.scrollTop / elementHeight);
values.leftTemperature = temperatures[index];
- console.log('LEFT', values.leftTemperature);
- KUKSA.set(PATHS.leftSeatTemperature, values.leftTemperature);
+ KUKSA.setInt32(PATHS.leftSeatTemperature, values.leftTemperature);
}, 100);
}
@@ -80,8 +80,7 @@ export function right() {
isScrolling = setTimeout(function() {
var index = Math.round(controls.rightTemperatureNode.scrollTop / elementHeight);
values.rightTemperature = temperatures[index];
- console.log('RIGHT', values.rightTemperature);
- KUKSA.set(PATHS.rightSeatTemperature, values.rightTemperature);
+ KUKSA.setInt32(PATHS.rightSeatTemperature, values.rightTemperature);
}, 100);
}
diff --git a/webpack.config.js b/webpack.config.js
index 43bc54c..9a4ca38 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -13,6 +13,9 @@ module.exports = {
SEATS: './src/js/seats.js',
FANSPEED: './src/js/fan_speed.js',
KUKSA: './src/js/kuksa.js',
+ VAL_WEB: './src/generated/val_grpc_web_pb.js',
+ VAL: './src/generated/val_pb.js',
+ TYPES: './src/generated/types_pb.js',
BUTTONS: './src/js/buttons.js',
TEMPERATURE: './src/js/temperature.js'
},