Update mixer app to use grpc-web instead of websockets 27/29427/2
authorRoger Zanoni <rzanoni@igalia.com>
Wed, 3 Jan 2024 16:52:10 +0000 (13:52 -0300)
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>
Mon, 29 Jan 2024 12:07:45 +0000 (12:07 +0000)
Adapt the HTML5 applications to use kuksa.val service

Bug-AGL: SPEC-4599

Signed-off-by: Roger Zanoni <rzanoni@igalia.com>
Change-Id: Ic67ff781be4080c45bdbc6abe20d696aeda08eb1

.gitignore
package.json
proto/types.proto [new file with mode: 0644]
proto/val.proto [new file with mode: 0644]
src/js/kuksa.js
src/js/volume.js
webpack.config.js

index b887236..cbad92c 100644 (file)
@@ -35,6 +35,7 @@ $RECYCLE.BIN/
 .sourcemaps/
 package-lock.json
 build
+src/generated
 
 .DS_Store
 Thumbs.db
index 2ef9c2f..011b82d 100644 (file)
@@ -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-mixer",
     "repository": {
         "webpack-dev-server": "^4.8.1"
     },
     "dependencies": {
-        "mustache": "^4.2.0"
+        "google-protobuf": "^3.21.2",
+        "grpc-web": "^1.5.0",
+        "mustache": "^4.2.0",
+        "protoc-gen-grpc-web": "^1.4.2",
+        "protoc-gen-js": "^3.21.2"
     }
 }
diff --git a/proto/types.proto b/proto/types.proto
new file mode 100644 (file)
index 0000000..8914e7a
--- /dev/null
@@ -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 (file)
index 0000000..2d65b7c
--- /dev/null
@@ -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;
+}
index bbd14fa..8542dcb 100644 (file)
 
 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;
+    }
 
-function logConnectionStatus(status, event) {
-    console.log('Connection status:', status);
+    var handler = pathToHandler.get(path);
+    handler.update(path, dp)
 }
 
-function kuksaws_onopen(event) {
-    logConnectionStatus('Connected.', event);
+function send(action, values) {
+}
 
-    authorize();
-    subscribe(PATHS.volume);
+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, onopen) {
-    pathToHandler = new Map(handlers);
-    if (kuksa_context.socket == undefined) {
-        kuksa_context.socket = new WebSocket(kuksa_context.target);
-        kuksa_context.socket.onopen = function() {
-            kuksaws_onopen();
-            if (onopen) {
-               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];
 }
index 0bbb481..749496b 100644 (file)
@@ -35,21 +35,22 @@ export function setValue(node, value) {
 // all the below functions need to be update to use
 // the correct paths/elements
 export function increase(node) {
-    KUKSA.set(PATHS.volume, getValue(node)+5);
+    KUKSA.setUInt32(PATHS.volume, getValue(node)+5);
 }
 
 export function decrease(node) {
-    KUKSA.set(PATHS.volume, getValue(node)-5);
+    KUKSA.setUInt32(PATHS.volume, getValue(node)-5);
 }
 
 export function change(node) {
-    KUKSA.set(PATHS.volume, node.value);
+    KUKSA.setUInt32(PATHS.volume, node.value);
 }
 
-export function update(path, value) {
+export function update(path, dp) {
+    var value = dp.getUint32();
     setValue(document.getElementById('progress-MAIN'), value);
 }
 
 export function init() {
-    KUKSA.set(PATHS.volume, 20);
+    KUKSA.setUInt32(PATHS.volume, 20);
 }
index 6a5c57c..6b15f00 100644 (file)
@@ -10,6 +10,9 @@ module.exports = {
     entry: {
         PATHS: './src/js/paths.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',
         VOLUME: './src/js/volume.js',
         index: './src/index.js'
     },