Better connection state stuff
This commit is contained in:
parent
472df851f4
commit
63689e14ff
@ -1,7 +1,7 @@
|
||||
import * as classNames from "classnames";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Grid, Header, Icon, Item } from "semantic-ui-react";
|
||||
import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
|
||||
|
||||
import { injectState, StateBase } from "@app/state";
|
||||
import { ConnectionState as ConState, SprinklersDevice } from "@common/sprinklers";
|
||||
@ -13,12 +13,21 @@ const ConnectionState = observer(({ connectionState, className }:
|
||||
const connected = connectionState.isConnected;
|
||||
const classes = classNames({
|
||||
connectionState: true,
|
||||
connected: connected, /* tslint:disable-line:object-literal-shorthand */
|
||||
disconnected: !connected,
|
||||
connected: connected === true,
|
||||
disconnected: connected === false,
|
||||
unknown: connected === null,
|
||||
}, className);
|
||||
let connectionText: string;
|
||||
let iconName: SemanticICONS = "unlinkify";
|
||||
if (connected) {
|
||||
connectionText = "Connected";
|
||||
iconName = "linkify";
|
||||
} else if (connected === null) {
|
||||
connectionText = "Unknown";
|
||||
iconName = "question";
|
||||
} else if (connectionState.noPermission) {
|
||||
connectionText = "No permission for this device";
|
||||
iconName = "ban";
|
||||
} else if (connectionState.serverToBroker) {
|
||||
connectionText = "Device Disconnected";
|
||||
} else if (connectionState.clientToServer) {
|
||||
@ -28,7 +37,7 @@ const ConnectionState = observer(({ connectionState, className }:
|
||||
}
|
||||
return (
|
||||
<div className={classes}>
|
||||
<Icon name={connected ? "linkify" : "unlinkify"}/>
|
||||
<Icon name={iconName}/>
|
||||
{connectionText}
|
||||
</div>
|
||||
);
|
||||
@ -64,7 +73,9 @@ class DeviceView extends React.Component<DeviceViewProps> {
|
||||
<Item.Meta>
|
||||
Raspberry Pi Grinklers Device
|
||||
</Item.Meta>
|
||||
<SectionRunnerView sectionRunner={sectionRunner} sections={sections}/>
|
||||
{connectionState.isAvailable &&
|
||||
<SectionRunnerView sectionRunner={sectionRunner} sections={sections}/>}
|
||||
{connectionState.isAvailable &&
|
||||
<Grid>
|
||||
<Grid.Column mobile="16" tablet="16" computer="8">
|
||||
<SectionTable sections={sections}/>
|
||||
@ -73,7 +84,10 @@ class DeviceView extends React.Component<DeviceViewProps> {
|
||||
<RunSectionForm device={this.device} uiStore={uiStore}/>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
}
|
||||
{connectionState.isAvailable &&
|
||||
<ProgramTable programs={programs} sections={sections}/>
|
||||
}
|
||||
</Item.Content>
|
||||
</Item>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { update } from "serializr";
|
||||
import { action, autorun, observable, when } from "mobx";
|
||||
|
||||
import logger from "@common/logger";
|
||||
import { ErrorCode } from "@common/sprinklers/ErrorCode";
|
||||
@ -7,7 +8,6 @@ import * as requests from "@common/sprinklers/requests";
|
||||
import * as schema from "@common/sprinklers/schema/index";
|
||||
import { seralizeRequest } from "@common/sprinklers/schema/requests";
|
||||
import * as ws from "@common/sprinklers/websocketData";
|
||||
import { action, autorun, observable } from "mobx";
|
||||
|
||||
const log = logger.child({ source: "websocket" });
|
||||
|
||||
@ -23,14 +23,8 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
|
||||
super();
|
||||
this.api = api;
|
||||
this._id = id;
|
||||
autorun(() => {
|
||||
this.connectionState.serverToBroker = api.connectionState.serverToBroker;
|
||||
this.connectionState.clientToServer = api.connectionState.clientToServer;
|
||||
if (!api.connectionState.isConnected) {
|
||||
this.connectionState.brokerToDevice = null;
|
||||
} else {
|
||||
this.subscribe();
|
||||
}
|
||||
when(() => api.connectionState.isConnected, () => {
|
||||
this.subscribe();
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,6 +43,17 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
|
||||
this.api.socket.send(JSON.stringify(subscribeRequest));
|
||||
}
|
||||
|
||||
onSubscribeResponse(data: ws.IDeviceSubscribeResponse) {
|
||||
this.connectionState.serverToBroker = true;
|
||||
this.connectionState.clientToServer = true;
|
||||
if (data.result === "success") {
|
||||
this.connectionState.hasPermission = true;
|
||||
this.connectionState.brokerToDevice = false;
|
||||
} else if (data.result === "noPermission") {
|
||||
this.connectionState.hasPermission = false;
|
||||
}
|
||||
}
|
||||
|
||||
makeRequest(request: requests.Request): Promise<requests.Response> {
|
||||
return this.api.makeDeviceCall(this.id, request);
|
||||
}
|
||||
@ -197,6 +202,9 @@ export class WebSocketApiClient implements s.ISprinklersApi {
|
||||
}
|
||||
log.trace({ data }, "websocket message");
|
||||
switch (data.type) {
|
||||
case "deviceSubscribeResponse":
|
||||
this.onDeviceSubscribeResponse(data);
|
||||
break;
|
||||
case "deviceUpdate":
|
||||
this.onDeviceUpdate(data);
|
||||
break;
|
||||
@ -211,6 +219,14 @@ export class WebSocketApiClient implements s.ISprinklersApi {
|
||||
}
|
||||
}
|
||||
|
||||
private onDeviceSubscribeResponse(data: ws.IDeviceSubscribeResponse) {
|
||||
const device = this.devices.get(data.deviceId);
|
||||
if (!device) {
|
||||
return log.warn({ data }, "invalid deviceSubscribeResponse received");
|
||||
}
|
||||
device.onSubscribeResponse(data);
|
||||
}
|
||||
|
||||
private onDeviceUpdate(data: ws.IDeviceUpdate) {
|
||||
const device = this.devices.get(data.deviceId);
|
||||
if (!device) {
|
||||
|
@ -19,9 +19,22 @@ export class ConnectionState {
|
||||
*/
|
||||
@observable brokerToDevice: boolean | null = null;
|
||||
|
||||
@computed get isConnected(): boolean {
|
||||
/**
|
||||
* Represents if whoever is trying to access this device has permission to access it.
|
||||
* Is null if there is no concept of access involved.
|
||||
*/
|
||||
@observable hasPermission: boolean | null = null;
|
||||
|
||||
@computed get noPermission() {
|
||||
return this.hasPermission === false;
|
||||
}
|
||||
|
||||
@computed get isAvailable(): boolean {
|
||||
if (this.hasPermission === false) {
|
||||
return false;
|
||||
}
|
||||
if (this.brokerToDevice != null) {
|
||||
return this.brokerToDevice;
|
||||
return true;
|
||||
}
|
||||
if (this.serverToBroker != null) {
|
||||
return this.serverToBroker;
|
||||
@ -31,4 +44,20 @@ export class ConnectionState {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@computed get isConnected(): boolean | null {
|
||||
if (this.hasPermission === false) {
|
||||
return false;
|
||||
}
|
||||
if (this.brokerToDevice != null) {
|
||||
return this.brokerToDevice;
|
||||
}
|
||||
if (this.serverToBroker != null) {
|
||||
return this.serverToBroker;
|
||||
}
|
||||
if (this.clientToServer != null) {
|
||||
return this.clientToServer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { autorun, observable } from "mobx";
|
||||
import * as mqtt from "mqtt";
|
||||
import { update } from "serializr";
|
||||
|
||||
@ -6,7 +7,6 @@ import * as s from "@common/sprinklers";
|
||||
import * as requests from "@common/sprinklers/requests";
|
||||
import * as schema from "@common/sprinklers/schema";
|
||||
import { seralizeRequest } from "@common/sprinklers/schema/requests";
|
||||
import { autorun, observable } from "mobx";
|
||||
|
||||
const log = logger.child({ source: "mqtt" });
|
||||
|
||||
@ -161,14 +161,30 @@ class MqttSprinklersDevice extends s.SprinklersDevice {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
doSubscribe() {
|
||||
doSubscribe(): Promise<void> {
|
||||
const topics = subscriptions.map((filter) => this.prefix + filter);
|
||||
this.apiClient.client.subscribe(topics, { qos: 1 });
|
||||
return new Promise((resolve, reject) => {
|
||||
this.apiClient.client.subscribe(topics, { qos: 1 }, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
doUnsubscribe() {
|
||||
doUnsubscribe(): Promise<void> {
|
||||
const topics = subscriptions.map((filter) => this.prefix + filter);
|
||||
this.apiClient.client.unsubscribe(topics);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.apiClient.client.unsubscribe(topics, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(topic: string, payload: string) {
|
||||
|
@ -6,6 +6,12 @@ export interface IError {
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface IDeviceSubscribeResponse {
|
||||
type: "deviceSubscribeResponse";
|
||||
deviceId: string;
|
||||
result: "success" | "noPermission";
|
||||
}
|
||||
|
||||
export interface IDeviceUpdate {
|
||||
type: "deviceUpdate";
|
||||
deviceId: string;
|
||||
@ -23,9 +29,8 @@ export interface IBrokerConnectionUpdate {
|
||||
brokerConnected: boolean;
|
||||
}
|
||||
|
||||
export type IServerMessage = IError | IDeviceUpdate | IDeviceCallResponse | IBrokerConnectionUpdate;
|
||||
|
||||
export type SubscriptionType = "deviceUpdate" | "brokerConnectionUpdate";
|
||||
export type IServerMessage = IError | IDeviceSubscribeResponse | IDeviceUpdate | IDeviceCallResponse |
|
||||
IBrokerConnectionUpdate;
|
||||
|
||||
export interface IDeviceSubscribeRequest {
|
||||
type: "deviceSubscribeRequest";
|
||||
|
@ -82,20 +82,29 @@ export class WebSocketClient {
|
||||
}
|
||||
|
||||
private deviceSubscribeRequest(data: ws.IDeviceSubscribeRequest) {
|
||||
// TODO: somehow validate this device id?
|
||||
const deviceId = data.deviceId;
|
||||
if (this.deviceSubscriptions.indexOf(deviceId) !== -1) {
|
||||
return;
|
||||
let result: ws.IDeviceSubscribeResponse["result"];
|
||||
if (deviceId !== "grinklers") { // TODO: somehow validate this device id?
|
||||
result = "noPermission";
|
||||
} else {
|
||||
if (this.deviceSubscriptions.indexOf(deviceId) !== -1) {
|
||||
return;
|
||||
}
|
||||
this.deviceSubscriptions.push(deviceId);
|
||||
const device = this.state.mqttClient.getDevice(deviceId);
|
||||
log.debug({ deviceId, userId: this.userId }, "websocket client subscribed to device");
|
||||
this.disposers.push(autorun(() => {
|
||||
const json = serialize(schema.sprinklersDevice, device);
|
||||
log.trace({ device: json });
|
||||
const updateData: ws.IDeviceUpdate = { type: "deviceUpdate", deviceId, data: json };
|
||||
this.socket.send(JSON.stringify(updateData));
|
||||
}, { delay: 100 }));
|
||||
result = "success";
|
||||
}
|
||||
this.deviceSubscriptions.push(deviceId);
|
||||
const device = this.state.mqttClient.getDevice(deviceId);
|
||||
log.debug({ deviceId, userId: this.userId }, "websocket client subscribed to device");
|
||||
this.disposers.push(autorun(() => {
|
||||
const json = serialize(schema.sprinklersDevice, device);
|
||||
log.trace({ device: json });
|
||||
const updateData: ws.IDeviceUpdate = { type: "deviceUpdate", deviceId, data: json };
|
||||
this.socket.send(JSON.stringify(updateData));
|
||||
}, { delay: 100 }));
|
||||
const response: ws.IDeviceSubscribeResponse = {
|
||||
type: "deviceSubscribeResponse", deviceId, result,
|
||||
};
|
||||
this.socket.send(JSON.stringify(response));
|
||||
}
|
||||
|
||||
private async deviceCallRequest(data: ws.IDeviceCallRequest): Promise<void> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user