From 77a2502be9f1f1f790910a95df12145a17a3bf8a Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Mon, 23 Mar 2020 22:45:13 +0100 Subject: [PATCH] Implemented websocket connection with backend on sensors --- smart-hut/src/client_server.js | 140 ++++++++++++++++-- .../src/components/dashboard/DevicePanel.js | 2 + .../components/dashboard/devices/Sensor.js | 12 +- 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/smart-hut/src/client_server.js b/smart-hut/src/client_server.js index f6f7a34..b439dcd 100644 --- a/smart-hut/src/client_server.js +++ b/smart-hut/src/client_server.js @@ -1,33 +1,149 @@ +// vim: set ts=2 sw=2 et tw=80: + import axios from "axios"; let config = "http://localhost:8080/"; var tkn = localStorage.getItem("token"); +/** the ServiceSocket instance valid for the current session */ +var socket; + // requests data devices /* { - params : data, - device: 'tipoDiDevice', - id: se serve + params : data, + device: 'tipoDiDevice', + id: se serve } - device routes: - - buttonDimmer - - dimmableLight - - knobDimmer - - motionSensor - - regularLight - - sensor - - smartPlug - - switch + device routes: + - buttonDimmer + - dimmableLight + - knobDimmer + - motionSensor + - regularLight + - sensor + - smartPlug + - switch */ +/** The number of times a connection to the socket was tried */ +var retries = 0; + +/** Class to handle connection with the sensor socket */ +class ServiceSocket { + /** + * Create a new sensor socket connection + * @param {string} token - The JWT token (needed for authentication) + * @param {Object.|null} callbacks - A callback map from + * device id to callback function + */ + constructor(token, callbacks) { + this.token = token; + this.authenticated = false; + this.callbacks = callbacks || {}; + + this.connection = new WebSocket("ws://localhost:8080/sensor-socket"); + + this.connection.onopen = (evt) => { + this.connection.send(JSON.stringify({ token })); + }; + + this.connection.onmessage = (evt) => { + let data = JSON.parse(evt.data); + + if (!this.authenticated) { + if (data.authenticated) { + this.authenticated = true; + retries = 0; + } else { + console.error("socket authentication failed"); + } + } else { + if (data.id && this.callbacks[data.id]) { + this.callbacks[data.id].forEach((f) => f(data)); + } + } + }; + + this.connection.onerror = (evt) => { + if (retries >= 5) { + console.error("too many socket connection retries"); + return; + } + retries++; + socket = new ServiceSocket(this.token, this.callbacks); + }; + } + + /** + * Registers a new callback function to be called when updates on the device + * with the id given are recieved + * @param {number} id - the id of the device to check updates for + * @param {function} stateCallback - a function that recieves a device as the + * first parameter, that will be called whenever a update is recieved + */ + subscribe(id, stateCallback) { + if (this.callbacks[id] === undefined) { + this.callbacks[id] = []; + } + + this.callbacks[id].push(stateCallback); + } + + /** + * Unregisters a function previously registered with `subscribe(...)`. + * @param {number} id - the id of the device to stop checking updates for + * @param {function} stateCallback - the callback to unregister + */ + unsubscribe(id, stateCallback) { + this.callbacks[id].splice(this.callbacks[id].indexOf(stateCallback), 1); + } + + /** + * Closes the underlying websocket connection + */ + close() { + this.connection.close(); + } +} + +if (tkn) { + socket = new ServiceSocket(tkn); +} + export var call = { setToken: function (token) { tkn = token; + if (tkn) { + if (socket) { + socket.close(); + } + socket = new ServiceSocket(tkn); + } + }, + + /** + * Registers a new callback function to be called when updates on the device + * with the id given are recieved + * @param {number} id - the id of the device to check updates for + * @param {function} stateCallback - a function that recieves a device as the + * first parameter, that will be called whenever a update is recieved + */ + socketSubscribe: function (id, callback) { + socket.subscribe(id, callback); + }, + + /** + * Unregisters a function previously registered with `subscribe(...)`. + * @param {number} id - the id of the device to stop checking updates for + * @param {function} stateCallback - the callback to unregister + */ + socketUnsubscribe: function (id, callback) { + socket.unsubscribe(id, callback); }, login: function (data, headers) { return axios.post(config + "auth/login", data); diff --git a/smart-hut/src/components/dashboard/DevicePanel.js b/smart-hut/src/components/dashboard/DevicePanel.js index f90de13..4473904 100644 --- a/smart-hut/src/components/dashboard/DevicePanel.js +++ b/smart-hut/src/components/dashboard/DevicePanel.js @@ -1,3 +1,5 @@ +// vim: set ts=2 sw=2 et tw=80: + import React, { Component } from "react"; import { Grid } from "semantic-ui-react"; import { editButtonStyle, panelStyle } from "./devices/styleComponents"; diff --git a/smart-hut/src/components/dashboard/devices/Sensor.js b/smart-hut/src/components/dashboard/devices/Sensor.js index 22c62e5..9270642 100644 --- a/smart-hut/src/components/dashboard/devices/Sensor.js +++ b/smart-hut/src/components/dashboard/devices/Sensor.js @@ -9,8 +9,8 @@ /* OPTIONAL STATE - error: 2.4 - + error: 2.4 + ±{this.state.error} @@ -28,6 +28,7 @@ import { import { sensorText, style, valueStyle } from "./SensorStyle"; import Settings from "./DeviceSettings"; import { StyledDiv } from "./styleComponents"; +import { call } from "../../../client_server"; export default class Sensor extends Component { constructor(props) { @@ -37,6 +38,13 @@ export default class Sensor extends Component { value: 0, }; this.units = ""; + this.stateCallback = (e) => this.setState(Object.assign(this.state, e)); + + call.socketSubscribe(this.props.device.id, this.stateCallback); + } + + componentWillUnmount() { + call.socketUnsubscribe(this.props.device.id, this.stateCallback); } setName = () => {