From b0c869c0461741413af9e6a24f6e156717b6362f Mon Sep 17 00:00:00 2001 From: Roger Zanoni Date: Wed, 2 Nov 2022 10:16:03 +0100 Subject: Adapt the demo to use kuksa.val service - The "AUTO" element was removed because there's no compatible VSS signal. - Removed the indicators below the seats and now they work only to toggle the state of the seat warmer. - The air distribution buttons now work as a radio group as there is only one AirDistribution setting per cabin. Bug-AGL: SPEC-4599 Signed-off-by: Roger Zanoni Change-Id: Ibfaa477a7c27627ac524f39dd809b7a2195c7c9f --- package.json | 4 +- src/index.html | 37 +++----- src/index.js | 21 ++++- src/js/AFB.js | 215 ---------------------------------------------- src/js/buttons.js | 69 ++++++++++++--- src/js/chair.js | 33 ------- src/js/fan_speed.js | 16 ++-- src/js/kuksa.js | 123 ++++++++++++++++++++++++++ src/js/paths.js | 31 +++++++ src/js/seats.js | 51 +++++++++++ src/js/temperature.js | 116 +++++++++++++++---------- src/styles/landscape.scss | 10 +-- src/styles/main.scss | 32 +++---- webpack.config.js | 7 +- 14 files changed, 392 insertions(+), 373 deletions(-) delete mode 100644 src/js/AFB.js delete mode 100644 src/js/chair.js create mode 100644 src/js/kuksa.js create mode 100644 src/js/paths.js create mode 100644 src/js/seats.js diff --git a/package.json b/package.json index ba76bd5..06006e6 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,7 @@ "webpack-cli": "^4.9.2", "webpack-dev-server": "^4.8.1" }, - "dependencies": {} + "dependencies": { + "uuid": "^9.0.0" + } } diff --git a/src/index.html b/src/index.html index 88ea3fa..c8a5eed 100644 --- a/src/index.html +++ b/src/index.html @@ -27,7 +27,7 @@
- +
@@ -36,39 +36,26 @@
- + - + - + - + - + diff --git a/src/index.js b/src/index.js index 67f800a..53ee56b 100644 --- a/src/index.js +++ b/src/index.js @@ -14,12 +14,25 @@ * limitations under the License. */ -/* JS */ -import './js/AFB.js'; - /* CSS */ import './styles/app.scss'; document.addEventListener('DOMContentLoaded', function(){ + SEATS.init(); TEMPERATURE.init(); -}); \ No newline at end of file + FANSPEED.init(); + BUTTONS.init(); + + KUKSA.init([ + [PATHS.leftSeatWarmer, SEATS], + [PATHS.rightSeatWarmer, SEATS], + [PATHS.leftSeatTemperature, TEMPERATURE], + [PATHS.rightSeatTemperature, TEMPERATURE], + [PATHS.leftFanSpeed, FANSPEED], + [PATHS.airConditioning, BUTTONS], + [PATHS.recirculation, BUTTONS], + [PATHS.frontDefroster, BUTTONS], + [PATHS.rearDefroster, BUTTONS], + [PATHS.leftAirDistribution, BUTTONS], + ]); +}); diff --git a/src/js/AFB.js b/src/js/AFB.js deleted file mode 100644 index d6e6bfa..0000000 --- a/src/js/AFB.js +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2017, 2018 "IoT.bzh" - * Author: José Bollo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -AFB = function(base, initialtoken){ - -if (typeof base != "object") - base = { base: base, token: initialtoken }; - -var initial = { - base: base.base || "api", - token: base.token || initialtoken || "HELLO", - host: base.host || window.location.host, - url: base.url || undefined -}; - -var urlws = initial.url || "ws://"+initial.host+"/"+initial.base; - -/*********************************************/ -/**** ****/ -/**** AFB_context ****/ -/**** ****/ -/*********************************************/ -var AFB_context; -{ - var UUID = undefined; - var TOKEN = initial.token; - - var context = function(token, uuid) { - this.token = token; - this.uuid = uuid; - } - - context.prototype = { - get token() {return TOKEN;}, - set token(tok) {if(tok) TOKEN=tok;}, - get uuid() {return UUID;}, - set uuid(id) {if(id) UUID=id;} - }; - - AFB_context = new context(); -} -/*********************************************/ -/**** ****/ -/**** AFB_websocket ****/ -/**** ****/ -/*********************************************/ -var AFB_websocket; -{ - var CALL = 2; - var RETOK = 3; - var RETERR = 4; - var EVENT = 5; - - var PROTO1 = "x-afb-ws-json1"; - - AFB_websocket = function(on_open, on_abort) { - var u = urlws, p = '?'; - if (AFB_context.token) { - u = u + '?x-afb-token=' + AFB_context.token; - p = '&'; - } - if (AFB_context.uuid) - u = u + p + 'x-afb-uuid=' + AFB_context.uuid; - this.ws = new WebSocket(u, [ PROTO1 ]); - this.url = u; - this.pendings = {}; - this.awaitens = {}; - this.counter = 0; - this.ws.onopen = onopen.bind(this); - this.ws.onerror = onerror.bind(this); - this.ws.onclose = onclose.bind(this); - this.ws.onmessage = onmessage.bind(this); - this.onopen = on_open; - this.onabort = on_abort; - } - - function onerror(event) { - var f = this.onabort; - if (f) { - delete this.onopen; - delete this.onabort; - f && f(this); - } - this.onerror && this.onerror(this); - } - - function onopen(event) { - var f = this.onopen; - delete this.onopen; - delete this.onabort; - f && f(this); - } - - function onclose(event) { - var err = { - jtype: 'afb-reply', - request: { - status: 'disconnected', - info: 'server hung up' - } - }; - for (var id in this.pendings) { - try { this.pendings[id][1](err); } catch (x) {/*NOTHING*/} - } - this.pendings = {}; - this.onclose && this.onclose(); - } - - function fire(awaitens, name, data) { - var a = awaitens[name]; - if (a) - a.forEach(function(handler){handler(data);}); - var i = name.indexOf("/"); - if (i >= 0) { - a = awaitens[name.substring(0,i)]; - if (a) - a.forEach(function(handler){handler(data);}); - } - a = awaitens["*"]; - if (a) - a.forEach(function(handler){handler(data);}); - } - - function reply(pendings, id, ans, offset) { - if (id in pendings) { - var p = pendings[id]; - delete pendings[id]; - try { p[offset](ans); } catch (x) {/*TODO?*/} - } - } - - function onmessage(event) { - var obj = JSON.parse(event.data); - var code = obj[0]; - var id = obj[1]; - var ans = obj[2]; - AFB_context.token = obj[3]; - switch (code) { - case RETOK: - reply(this.pendings, id, ans, 0); - break; - case RETERR: - reply(this.pendings, id, ans, 1); - break; - case EVENT: - default: - fire(this.awaitens, id, ans); - break; - } - } - - function close() { - this.ws.close(); - this.ws.onopen = - this.ws.onerror = - this.ws.onclose = - this.ws.onmessage = - this.onopen = - this.onabort = function(){}; - } - - function call(method, request, callid) { - return new Promise((function(resolve, reject){ - var id, arr; - if (callid) { - id = String(callid); - if (id in this.pendings) - throw new Error("pending callid("+id+") exists"); - } else { - do { - id = String(this.counter = 4095 & (this.counter + 1)); - } while (id in this.pendings); - } - this.pendings[id] = [ resolve, reject ]; - arr = [CALL, id, method, request ]; - if (AFB_context.token) arr.push(AFB_context.token); - this.ws.send(JSON.stringify(arr)); - }).bind(this)); - } - - function onevent(name, handler) { - var id = name; - var list = this.awaitens[id] || (this.awaitens[id] = []); - list.push(handler); - } - - AFB_websocket.prototype = { - close: close, - call: call, - onevent: onevent - }; -} -/*********************************************/ -/**** ****/ -/**** ****/ -/**** ****/ -/*********************************************/ -return { - context: AFB_context, - ws: AFB_websocket -}; -}; diff --git a/src/js/buttons.js b/src/js/buttons.js index 5fcb521..76d2143 100644 --- a/src/js/buttons.js +++ b/src/js/buttons.js @@ -14,25 +14,66 @@ * limitations under the License. */ -var buttons = { + + +var values = { ac: false, - auto: false, - circulation: false, - down: false, - up: false, - right: false, + recirculation: false, rear: false, front: false }; -function update(node, value) { - node.setAttribute('value', value); +var paths = { + ac: PATHS.airConditioning, + recirculation: PATHS.recirculation, + rear: PATHS.rearDefroster, + front: PATHS.frontDefroster, +}; + +var nodes = {} + +export function init() { + nodes[PATHS.airConditioning] = document.getElementById('ac'); + nodes[PATHS.recirculation] = document.getElementById('recirculation'); + nodes[PATHS.frontDefroster] = document.getElementById('front'); + nodes[PATHS.rearDefroster] = document.getElementById('rear'); + nodes['up'] = document.getElementById('up'); + nodes['down'] = document.getElementById('down'); + nodes['right'] = document.getElementById('right'); } -module.exports = { - toggle: function(node) { - var key = node.getAttribute('key'); - buttons[key] = !buttons[key]; - update(node, buttons[key]); +export function update(path, value) { + if (path == PATHS.leftAirDistribution) { + if (value == 'UP') { + nodes['up'].setAttribute('value', true); + nodes['down'].setAttribute('value', false); + nodes['right'].setAttribute('value', false); + } else if (value == 'DOWN') { + nodes['down'].setAttribute('value', true); + nodes['up'].setAttribute('value', false); + nodes['right'].setAttribute('value', false); + + } else if (value == 'MIDDLE') { + nodes['right'].setAttribute('value', true); + nodes['up'].setAttribute('value', false); + nodes['down'].setAttribute('value', false); + } + } else { + var node = nodes[path]; + node.setAttribute('value', value); } -} \ No newline at end of file +} + +export function toggle(node) { + var key = node.getAttribute('key'); + values[key] = !values[key]; + if (key == 'up') { + KUKSA.set(PATHS.leftAirDistribution, 'UP'); + } else if (key == 'down') { + KUKSA.set(PATHS.leftAirDistribution, 'DOWN'); + } else if (key == 'right') { + KUKSA.set(PATHS.leftAirDistribution, 'MIDDLE'); + } else { + KUKSA.set(paths[key], values[key]); + } +} diff --git a/src/js/chair.js b/src/js/chair.js deleted file mode 100644 index 5cd26e8..0000000 --- a/src/js/chair.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 Igalia, S.L. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var left = 0; -var right = 0; - -function update(node, value){ - node.setAttribute('value', value); -} - -module.exports = { - left: function(node) { - left = (left + 1) % 3; - update(node, left); - }, - right: function(node) { - right = (right + 1) % 3; - update(node, right); - }, -} \ No newline at end of file diff --git a/src/js/fan_speed.js b/src/js/fan_speed.js index 8b131b5..fa0ed01 100644 --- a/src/js/fan_speed.js +++ b/src/js/fan_speed.js @@ -15,15 +15,17 @@ */ var value = 0; +var node = null; -function update(node, value) { +export function update(path, value) { node.value = value; node.parentNode.getElementsByTagName('progress')[0].value = value; } -module.exports = { - set: function(node) { - value = node.value; - update(node, value); - } -} \ No newline at end of file +export function init() { + node = document.getElementById('FanSpeed'); +} + +export function set() { + KUKSA.set(PATHS.leftFanSpeed, node.value); +} diff --git a/src/js/kuksa.js b/src/js/kuksa.js new file mode 100644 index 0000000..9e9cc22 --- /dev/null +++ b/src/js/kuksa.js @@ -0,0 +1,123 @@ +/* + * Copyright 2022 Igalia, S.L. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidv4 } from 'uuid'; + +const DEFAULT_TARGET = "wss://localhost:8090"; + +// 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'; + +var kuksa_context = { + authToken: TEST_TOKEN, + socket: undefined, + target: DEFAULT_TARGET, +} + +var pathToHandler = null; + +function logReceivedMessage(data) { + console.log('Received message:', data) +} + +function logConnectionStatus(status, event) { + console.log('Connection status:', status); +} + +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 kuksaws_onerror(event) { + logConnectionStatus('Failed to connect:', event); +} + +function kuksaws_onmessage(event) { + logReceivedMessage(event.data); + + var jsonData = JSON.parse(event.data); + + if (jsonData.action == 'get' || + jsonData.action =='subscription') { + updateVehicleInfo(jsonData); + } +} + +function updateVehicleInfo(message) { + var value = message.data.dp.value; + var path = message.data.path; + + if (!pathToHandler.has(path)) { + console.log('Handler not found for', path); + return; + } + + var handler = pathToHandler.get(path); + handler.update(path, value) +} + +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; +} + +function subscribe(path) { + return send('subscribe', { path: path }); +} + +function authorize() { + return send('authorize'); +} + +export function get(path) { + return send('get', { path: path }); +} + +export function set(path, value) { + return send('set', { path: path, value: value }); +} + +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; + } +} diff --git a/src/js/paths.js b/src/js/paths.js new file mode 100644 index 0000000..4f3c5d3 --- /dev/null +++ b/src/js/paths.js @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Igalia, S.L. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 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'; + +// 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'; + +// https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/Cabin/HVAC.vspec +export const recirculation = 'Vehicle.Cabin.HVAC.IsRecirculationActive'; +export const airConditioning = 'Vehicle.Cabin.HVAC.IsAirConditioningActive'; +export const frontDefroster = 'Vehicle.Cabin.HVAC.IsFrontDefrosterActive'; +export const rearDefroster = 'Vehicle.Cabin.HVAC.IsRearDefrosterActive'; diff --git a/src/js/seats.js b/src/js/seats.js new file mode 100644 index 0000000..3afc481 --- /dev/null +++ b/src/js/seats.js @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Igalia, S.L. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var values = { + leftAirDistribution: 'middle', + leftSeatWarmer: false, + rightSeatWarmer: false, +}; + +var controls = { + leftSeatWarmer: null, + rightSeatWarmer: null, +}; + +export function init() { + controls.leftSeatWarmer = document.getElementById('LeftChair'); + controls.rightSeatWarmer = document.getElementById('RightChair'); +} + +export function update(path, value) { + switch (path) { + case PATHS.leftSeatWarmer: + values.leftSeatWarmer = value; + controls.leftSeatWarmer.setAttribute('value', values.leftSeatWarmer); + break; + case PATHS.rightSeatWarmer: + values.rightSeatWarmer = value; + controls.rightSeatWarmer.setAttribute('value', values.rightSeatWarmer); + break; + } +} + +export function left() { + KUKSA.set(PATHS.leftSeatWarmer, !values.leftSeatWarmer); +} +export function right() { + KUKSA.set(PATHS.rightSeatWarmer, !values.rightSeatWarmer); +} diff --git a/src/js/temperature.js b/src/js/temperature.js index 3a394a1..1e35ddf 100644 --- a/src/js/temperature.js +++ b/src/js/temperature.js @@ -14,8 +14,10 @@ * limitations under the License. */ -var left = 22; -var right = 22; +var values = { + leftTemperature: 22, + rightTemperature: 22, +} var lowTemperature = 15; var hiTemperature = 30; @@ -24,6 +26,12 @@ var temperatures = []; var isScrolling; var elementHeight; +var controls = { + leftTemperatureNode: null, + rightTemperatureNode: null, +}; + + function createTemperatureElement() { var element = document.createElement('div'); element.classList = ['temperature']; @@ -32,7 +40,21 @@ function createTemperatureElement() { return element; } -function update(node, index) { +export function update(path, value) { + var temperature, node; + if (path == PATHS.rightSeatTemperature) { + values.rightTemperature = value; + temperature = values.rightTemperature; + node = controls.rightTemperatureNode; + } else { + values.leftTemperature = value; + temperature = values.leftTemperature; + node = controls.leftTemperatureNode; + } + setSemperature(node, temperatures.indexOf(temperature)); +} + +function setSemperature(node, index) { node.scrollTop = index*elementHeight; for( var i=0; i