motion-master-client 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.eslintrc.json +18 -0
- package/jest.config.ts +15 -0
- package/motion-master.proto +1822 -0
- package/package.json +2 -17
- package/project.json +41 -0
- package/src/lib/cia402.ts +112 -0
- package/src/lib/device-log-line.ts +5 -0
- package/src/lib/device-parameter.spec.ts +85 -0
- package/src/lib/device-parameter.ts +79 -0
- package/src/lib/device.ts +10 -0
- package/src/lib/examples/get-firmware-version-for-all-devices.ts +18 -0
- package/src/lib/hardware-description.spec.ts +253 -0
- package/src/lib/hardware-description.ts +84 -0
- package/src/lib/logger.ts +5 -0
- package/src/lib/monitoring-config.ts +6 -0
- package/src/lib/{monitoring-entry.d.ts → monitoring-entry.ts} +5 -4
- package/src/lib/motion-master-client.ts +126 -0
- package/src/lib/motion-master-pub-sub-client.ts +94 -0
- package/src/lib/motion-master-pub-sub-socket.ts +33 -0
- package/src/lib/motion-master-pub-sub-web-socket.ts +71 -0
- package/src/lib/motion-master-pub-sub-worker-socket.ts +46 -0
- package/src/lib/motion-master-req-res-client.spec.ts +740 -0
- package/src/lib/motion-master-req-res-client.ts +1974 -0
- package/src/lib/motion-master-req-res-socket.ts +55 -0
- package/src/lib/motion-master-req-res-web-socket.ts +131 -0
- package/src/lib/motion-master-req-res-worker-socket.ts +75 -0
- package/src/lib/operators.ts +93 -0
- package/src/lib/options.ts +38 -0
- package/src/lib/parameter.spec.ts +81 -0
- package/src/lib/parameter.ts +123 -0
- package/src/lib/product-id-range.ts +8 -0
- package/src/lib/request-status-resolver.ts +403 -0
- package/src/lib/system-log-line.ts +8 -0
- package/src/lib/types.ts +123 -0
- package/src/lib/urls.ts +6 -0
- package/src/lib/util.ts +267 -0
- package/tsconfig.json +23 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +20 -0
- package/typedoc.json +10 -0
- package/src/index.js +0 -27
- package/src/index.js.map +0 -1
- package/src/lib/cia402.d.ts +0 -30
- package/src/lib/cia402.js +0 -112
- package/src/lib/cia402.js.map +0 -1
- package/src/lib/device-log-line.d.ts +0 -5
- package/src/lib/device-log-line.js +0 -3
- package/src/lib/device-log-line.js.map +0 -1
- package/src/lib/device-parameter.d.ts +0 -56
- package/src/lib/device-parameter.js +0 -39
- package/src/lib/device-parameter.js.map +0 -1
- package/src/lib/device.d.ts +0 -9
- package/src/lib/device.js +0 -3
- package/src/lib/device.js.map +0 -1
- package/src/lib/examples/get-firmware-version-for-all-devices.d.ts +0 -1
- package/src/lib/examples/get-firmware-version-for-all-devices.js +0 -15
- package/src/lib/examples/get-firmware-version-for-all-devices.js.map +0 -1
- package/src/lib/hardware-description.d.ts +0 -37
- package/src/lib/hardware-description.js +0 -48
- package/src/lib/hardware-description.js.map +0 -1
- package/src/lib/logger.d.ts +0 -1
- package/src/lib/logger.js +0 -8
- package/src/lib/logger.js.map +0 -1
- package/src/lib/monitoring-config.d.ts +0 -6
- package/src/lib/monitoring-config.js +0 -3
- package/src/lib/monitoring-config.js.map +0 -1
- package/src/lib/monitoring-entry.js +0 -3
- package/src/lib/monitoring-entry.js.map +0 -1
- package/src/lib/motion-master-client.d.ts +0 -18
- package/src/lib/motion-master-client.js +0 -73
- package/src/lib/motion-master-client.js.map +0 -1
- package/src/lib/motion-master-pub-sub-client.d.ts +0 -16
- package/src/lib/motion-master-pub-sub-client.js +0 -68
- package/src/lib/motion-master-pub-sub-client.js.map +0 -1
- package/src/lib/motion-master-pub-sub-socket.d.ts +0 -28
- package/src/lib/motion-master-pub-sub-socket.js +0 -3
- package/src/lib/motion-master-pub-sub-socket.js.map +0 -1
- package/src/lib/motion-master-pub-sub-web-socket.d.ts +0 -13
- package/src/lib/motion-master-pub-sub-web-socket.js +0 -61
- package/src/lib/motion-master-pub-sub-web-socket.js.map +0 -1
- package/src/lib/motion-master-pub-sub-worker-socket.d.ts +0 -13
- package/src/lib/motion-master-pub-sub-worker-socket.js +0 -37
- package/src/lib/motion-master-pub-sub-worker-socket.js.map +0 -1
- package/src/lib/motion-master-req-res-client.d.ts +0 -956
- package/src/lib/motion-master-req-res-client.js +0 -1525
- package/src/lib/motion-master-req-res-client.js.map +0 -1
- package/src/lib/motion-master-req-res-socket.d.ts +0 -38
- package/src/lib/motion-master-req-res-socket.js +0 -3
- package/src/lib/motion-master-req-res-socket.js.map +0 -1
- package/src/lib/motion-master-req-res-web-socket.d.ts +0 -20
- package/src/lib/motion-master-req-res-web-socket.js +0 -100
- package/src/lib/motion-master-req-res-web-socket.js.map +0 -1
- package/src/lib/motion-master-req-res-worker-socket.d.ts +0 -15
- package/src/lib/motion-master-req-res-worker-socket.js +0 -53
- package/src/lib/motion-master-req-res-worker-socket.js.map +0 -1
- package/src/lib/operators.d.ts +0 -21
- package/src/lib/operators.js +0 -71
- package/src/lib/operators.js.map +0 -1
- package/src/lib/options.d.ts +0 -34
- package/src/lib/options.js +0 -38
- package/src/lib/options.js.map +0 -1
- package/src/lib/parameter.d.ts +0 -65
- package/src/lib/parameter.js +0 -70
- package/src/lib/parameter.js.map +0 -1
- package/src/lib/request-status-resolver.d.ts +0 -4
- package/src/lib/request-status-resolver.js +0 -345
- package/src/lib/request-status-resolver.js.map +0 -1
- package/src/lib/system-log-line.d.ts +0 -8
- package/src/lib/system-log-line.js +0 -3
- package/src/lib/system-log-line.js.map +0 -1
- package/src/lib/types.d.ts +0 -51
- package/src/lib/types.js +0 -23
- package/src/lib/types.js.map +0 -1
- package/src/lib/urls.d.ts +0 -3
- package/src/lib/urls.js +0 -10
- package/src/lib/urls.js.map +0 -1
- package/src/lib/util.d.ts +0 -30
- package/src/lib/util.js +0 -279
- package/src/lib/util.js.map +0 -1
- /package/src/{index.d.ts → index.ts} +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { isEqual } from 'lodash';
|
|
2
|
+
|
|
3
|
+
export interface HardwareComponent {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
serialNumber: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type AssemblyComponent = HardwareComponent;
|
|
10
|
+
export type DeviceComponent = HardwareComponent;
|
|
11
|
+
|
|
12
|
+
export interface Assembly {
|
|
13
|
+
name: string;
|
|
14
|
+
id: string;
|
|
15
|
+
version: string;
|
|
16
|
+
serialNumber: string;
|
|
17
|
+
components: AssemblyComponent[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HardwareDevice {
|
|
21
|
+
name: string;
|
|
22
|
+
id: string;
|
|
23
|
+
version: string;
|
|
24
|
+
keyId: string;
|
|
25
|
+
serialNumber: string;
|
|
26
|
+
macAddress: string;
|
|
27
|
+
components: DeviceComponent[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @link https://docs.google.com/document/d/1N00-ZjxHkjoWNIsnoTtza9CUHrxm8-e54298DXLuD94
|
|
32
|
+
*/
|
|
33
|
+
export interface HardwareDescription {
|
|
34
|
+
fileVersion: string;
|
|
35
|
+
assembly?: Assembly;
|
|
36
|
+
device: HardwareDevice;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getIdFromHardwareDescription(hd: HardwareDescription): string {
|
|
40
|
+
return hd.assembly
|
|
41
|
+
? hd.assembly.id
|
|
42
|
+
: hd.device.id;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getApiIdentifierFromHardwareDescription(hd: HardwareDescription, skipKeyId = false): string {
|
|
46
|
+
let apiId = hd.assembly
|
|
47
|
+
? `${hd.assembly.id}-${hd.assembly.version}`
|
|
48
|
+
: `${hd.device.id}-${hd.device.version}`;
|
|
49
|
+
|
|
50
|
+
if (!skipKeyId && hd.device.keyId) {
|
|
51
|
+
apiId += `-${hd.device.keyId}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return apiId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getNameFromHardwareDescription(hd: HardwareDescription): string {
|
|
58
|
+
return hd.assembly
|
|
59
|
+
? hd.assembly.name
|
|
60
|
+
: hd.device.name;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getSerialNumberFromHardwareDescription(hd: HardwareDescription): string {
|
|
64
|
+
return hd.assembly
|
|
65
|
+
? hd.assembly.serialNumber
|
|
66
|
+
: hd.device.serialNumber;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getAllComponentsFromHardwareDescription(hd: HardwareDescription): HardwareComponent[] {
|
|
70
|
+
return hd.assembly
|
|
71
|
+
? [...hd.assembly.components, ...hd.device.components]
|
|
72
|
+
: hd.device.components;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function hardwareComponentsMatch(a: HardwareComponent[], b: HardwareComponent[]): boolean {
|
|
76
|
+
if (a.length === 0 && b.length === 0) {
|
|
77
|
+
return false; // edge case: when both components are empty they don't match since there are no components to compare
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ac = a.map((c) => `${c.name}-${c.version[0]}`.replace(/\s/g, '').toLocaleLowerCase()).sort();
|
|
81
|
+
const bc = b.map((c) => `${c.name}-${c.version[0]}`.replace(/\s/g, '').toLocaleLowerCase()).sort();
|
|
82
|
+
|
|
83
|
+
return isEqual(ac, bc);
|
|
84
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
2
|
import { MonitoringConfig } from "./monitoring-config";
|
|
3
3
|
import { MotionMasterMessage, ParameterValueType } from "./types";
|
|
4
|
+
|
|
4
5
|
export interface MonitoringEntry {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
config: MonitoringConfig;
|
|
7
|
+
interval: number;
|
|
8
|
+
values$: Observable<ParameterValueType[]>;
|
|
9
|
+
parameters: MotionMasterMessage.Request.GetDeviceParameterValues.IParameter[];
|
|
9
10
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { combineLatest, defer, first, firstValueFrom, map, mergeMap, Observable, timeout } from "rxjs";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { MonitoringConfig } from "./monitoring-config";
|
|
4
|
+
import { MotionMasterPubSubClient } from "./motion-master-pub-sub-client";
|
|
5
|
+
import { MotionMasterPubSubSocket } from "./motion-master-pub-sub-socket";
|
|
6
|
+
import { MotionMasterPubSubWebSocket } from "./motion-master-pub-sub-web-socket";
|
|
7
|
+
import { MotionMasterReqResClient } from "./motion-master-req-res-client";
|
|
8
|
+
import { MotionMasterReqResSocket } from "./motion-master-req-res-socket";
|
|
9
|
+
import { MotionMasterReqResWebSocket } from "./motion-master-req-res-web-socket";
|
|
10
|
+
import { mapMonitoringParameterValuesStatusMessageToParameterValues, selectMotionMasterMessageByTopic } from "./operators";
|
|
11
|
+
import { DeviceRef, MotionMasterMessage, ParameterValueType } from "./types";
|
|
12
|
+
|
|
13
|
+
export function createMotionMasterClient(hostname: string) {
|
|
14
|
+
const reqResSocket = new MotionMasterReqResWebSocket();
|
|
15
|
+
const pubSubSocket = new MotionMasterPubSubWebSocket();
|
|
16
|
+
|
|
17
|
+
const client = new MotionMasterClient(reqResSocket, pubSubSocket);
|
|
18
|
+
|
|
19
|
+
const clientId = v4();
|
|
20
|
+
reqResSocket.open(`ws://${hostname}:63524?clientId=${clientId}`);
|
|
21
|
+
pubSubSocket.open(`ws://${hostname}:63525?clientId=${clientId}`);
|
|
22
|
+
|
|
23
|
+
return client;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class MotionMasterClient {
|
|
27
|
+
|
|
28
|
+
readonly request: MotionMasterReqResClient;
|
|
29
|
+
readonly monitor: MotionMasterPubSubClient;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
readonly reqResSocket: MotionMasterReqResSocket,
|
|
33
|
+
readonly pubSubSocket: MotionMasterPubSubSocket,
|
|
34
|
+
) {
|
|
35
|
+
this.request = new MotionMasterReqResClient(this.reqResSocket);
|
|
36
|
+
this.monitor = new MotionMasterPubSubClient(this.pubSubSocket);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* This observable will emit true when both req/res and pub/sub sockets are opened.
|
|
41
|
+
* If any socket gets closed, this observable will emit false.
|
|
42
|
+
* This is a hot observable that will on subscription emit the last value of sockets opened.
|
|
43
|
+
*/
|
|
44
|
+
socketsOpened$ = combineLatest([this.reqResSocket.opened$, this.pubSubSocket.opened$]).pipe(
|
|
45
|
+
map(([reqResSocketOpened, pubSubSocketOpened]) => reqResSocketOpened && pubSubSocketOpened),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Client is ready when both req/res and pub/sub sockets are opened.
|
|
50
|
+
* If both sockets are already opened, this function will immediately resolve to true.
|
|
51
|
+
* Otherwise, it will wait up to requestTimeout for both sockets to open, or throw an error.
|
|
52
|
+
*/
|
|
53
|
+
async whenReady(requestTimeout = 5000) {
|
|
54
|
+
return firstValueFrom(
|
|
55
|
+
this.socketsOpened$.pipe(
|
|
56
|
+
first(Boolean),
|
|
57
|
+
timeout(requestTimeout),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
startMonitoring(
|
|
63
|
+
ids: string[] | [DeviceRef, number, number][],
|
|
64
|
+
interval: number,
|
|
65
|
+
config?: MonitoringConfig,
|
|
66
|
+
requestTimeout = 5000,
|
|
67
|
+
) {
|
|
68
|
+
return defer(() => {
|
|
69
|
+
const topic = config?.topic ?? v4();
|
|
70
|
+
const messageId = config?.messageId ?? v4();
|
|
71
|
+
|
|
72
|
+
if (!config) {
|
|
73
|
+
config = { messageId, topic, bufferSize: 1, distinct: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.request.resolveGetParameterValuesIds(ids).pipe(
|
|
77
|
+
mergeMap((refs) => {
|
|
78
|
+
if ((new Set(refs.map(([deviceAddress]) => deviceAddress))).size !== 1) {
|
|
79
|
+
throw new Error('Start monitoring only supports a single device');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const deviceAddress = refs[0][0];
|
|
83
|
+
|
|
84
|
+
const props: MotionMasterMessage.Request.IStartMonitoringDeviceParameterValues = {
|
|
85
|
+
getDeviceParameterValues: {
|
|
86
|
+
deviceAddress,
|
|
87
|
+
parameters: refs.map(([, index, subindex]) => ({ index, subindex, loadFromCache: false })),
|
|
88
|
+
sendProgress: false,
|
|
89
|
+
},
|
|
90
|
+
interval,
|
|
91
|
+
topic,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return this.request.startMonitoringDeviceParameterValues(props, requestTimeout, messageId);
|
|
95
|
+
}),
|
|
96
|
+
).subscribe();
|
|
97
|
+
|
|
98
|
+
this.monitor.subscribe(config);
|
|
99
|
+
|
|
100
|
+
const values$ = this.monitor.socket.data$.pipe(
|
|
101
|
+
selectMotionMasterMessageByTopic(config.topic),
|
|
102
|
+
mapMonitoringParameterValuesStatusMessageToParameterValues(),
|
|
103
|
+
map((values) => values.slice(1)), // remove fixed timestamp prepended by Motion Master
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return new Observable<ParameterValueType[]>((subscriber) => {
|
|
107
|
+
const subscription = values$.subscribe(subscriber);
|
|
108
|
+
return () => {
|
|
109
|
+
subscription.unsubscribe();
|
|
110
|
+
this.stopMonitoring(messageId);
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
stopMonitoring(messageId: string): void {
|
|
117
|
+
this.request.stopMonitoringDeviceParameterValues({ startMonitoringRequestId: messageId });
|
|
118
|
+
this.monitor.unsubscribe(messageId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
closeSockets() {
|
|
122
|
+
this.reqResSocket.close();
|
|
123
|
+
this.pubSubSocket.close();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { bufferCount, distinctUntilChanged, filter, map, Observable, Subject, Subscription } from "rxjs";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { MonitoringConfig } from "./monitoring-config";
|
|
4
|
+
import { MotionMasterPubSubSocket } from "./motion-master-pub-sub-socket";
|
|
5
|
+
import { selectMotionMasterMessageByTopic, selectMotionMasterMessageStatusByKey } from "./operators";
|
|
6
|
+
import { MotionMasterMessage } from "./types";
|
|
7
|
+
import * as equal from "fast-deep-equal";
|
|
8
|
+
import { MotionMasterPubSubWorkerSocket } from "./motion-master-pub-sub-worker-socket";
|
|
9
|
+
|
|
10
|
+
export class MotionMasterPubSubClient {
|
|
11
|
+
|
|
12
|
+
readonly data$ = new Subject<[string, MotionMasterMessage[]]>();
|
|
13
|
+
|
|
14
|
+
private subscriptions = new Map<string, Subscription>();
|
|
15
|
+
|
|
16
|
+
readonly notification$ = this.socket.data$.pipe(
|
|
17
|
+
selectMotionMasterMessageByTopic('notification'),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
readonly systemEvent$ = this.notification$.pipe(
|
|
21
|
+
selectMotionMasterMessageStatusByKey<MotionMasterMessage.Status.SystemEvent>('systemEvent'),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
readonly deviceEvent$ = this.notification$.pipe(
|
|
25
|
+
selectMotionMasterMessageStatusByKey<MotionMasterMessage.Status.DeviceEvent>('deviceEvent'),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
public readonly socket: MotionMasterPubSubSocket,
|
|
30
|
+
) { }
|
|
31
|
+
|
|
32
|
+
subscribe(config: MonitoringConfig) {
|
|
33
|
+
if (!config.messageId) {
|
|
34
|
+
config = { ...config, messageId: v4() };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.socket instanceof MotionMasterPubSubWorkerSocket) {
|
|
38
|
+
this.socket.worker.postMessage({ subscribe: config });
|
|
39
|
+
} else {
|
|
40
|
+
const { bufferSize = 1, distinct = false, messageId, topic } = config;
|
|
41
|
+
|
|
42
|
+
let observable: Observable<MotionMasterMessage> = this.socket.data$.pipe(
|
|
43
|
+
filter((data) => data[0] === topic),
|
|
44
|
+
map((data) => data[1]),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (distinct) {
|
|
48
|
+
observable = observable.pipe(
|
|
49
|
+
distinctUntilChanged((prev, curr) => {
|
|
50
|
+
if (prev instanceof MotionMasterMessage && curr instanceof MotionMasterMessage) {
|
|
51
|
+
return equal(
|
|
52
|
+
prev?.status?.monitoringParameterValues?.deviceParameterValues?.parameterValues,
|
|
53
|
+
curr?.status?.monitoringParameterValues?.deviceParameterValues?.parameterValues,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const subscription = observable.pipe(
|
|
62
|
+
bufferCount(bufferSize),
|
|
63
|
+
).subscribe((data) => {
|
|
64
|
+
this.data$.next([topic, data]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.subscriptions.set(messageId, subscription);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return config.messageId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
unsubscribe(messageId: string) {
|
|
74
|
+
if (this.socket instanceof MotionMasterPubSubWorkerSocket) {
|
|
75
|
+
this.socket.worker.postMessage({ unsubcribe: { messageId } });
|
|
76
|
+
} else {
|
|
77
|
+
const subscription = this.subscriptions.get(messageId);
|
|
78
|
+
if (subscription) {
|
|
79
|
+
subscription.unsubscribe();
|
|
80
|
+
this.subscriptions.delete(messageId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
unsubscribeAll() {
|
|
86
|
+
if (this.socket instanceof MotionMasterPubSubWorkerSocket) {
|
|
87
|
+
this.socket.worker.postMessage({ unsubscribeAll: true });
|
|
88
|
+
} else {
|
|
89
|
+
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
90
|
+
this.subscriptions.clear();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BehaviorSubject, Observable } from "rxjs";
|
|
2
|
+
import { MotionMasterMessage } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface MotionMasterPubSubSocket {
|
|
5
|
+
/**
|
|
6
|
+
* Emits a boolean value when socket gets opened or closed.
|
|
7
|
+
*/
|
|
8
|
+
readonly opened$: BehaviorSubject<boolean>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Incoming data is a tuple of topic and decoded message.
|
|
12
|
+
*/
|
|
13
|
+
readonly data$: Observable<[string, MotionMasterMessage]>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* URL set in call to open.
|
|
17
|
+
*/
|
|
18
|
+
get url(): string | undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Open socket.
|
|
22
|
+
*
|
|
23
|
+
* The connected$ and eventually alive$ observables will emit true values.
|
|
24
|
+
*/
|
|
25
|
+
open(url: string): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Close socket.
|
|
29
|
+
*
|
|
30
|
+
* The opened$ and alive$ observables will emit false values.
|
|
31
|
+
*/
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BehaviorSubject, Subject } from "rxjs";
|
|
2
|
+
import { webSocket, WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
import { MotionMasterPubSubSocket } from "./motion-master-pub-sub-socket";
|
|
5
|
+
import { MotionMasterMessage } from "./types";
|
|
6
|
+
|
|
7
|
+
export class MotionMasterPubSubWebSocket implements MotionMasterPubSubSocket {
|
|
8
|
+
|
|
9
|
+
readonly opened$ = new BehaviorSubject<boolean>(false);
|
|
10
|
+
|
|
11
|
+
readonly data$ = new Subject<[string, MotionMasterMessage]>();
|
|
12
|
+
|
|
13
|
+
private webSocket$?: WebSocketSubject<[string, MotionMasterMessage]>;
|
|
14
|
+
|
|
15
|
+
private _url?: string;
|
|
16
|
+
|
|
17
|
+
get url(): string | undefined {
|
|
18
|
+
return this._url;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
open(url: string): void {
|
|
22
|
+
if (this.opened$.getValue() === true) {
|
|
23
|
+
throw new Error('MotionMasterPubSubWebSocket is already opened!');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this._url = url;
|
|
27
|
+
|
|
28
|
+
this.webSocket$ = this.createWebSocket(url);
|
|
29
|
+
|
|
30
|
+
this.webSocket$.subscribe({
|
|
31
|
+
next: (value) => this.data$.next(value),
|
|
32
|
+
error: () => {
|
|
33
|
+
// ignore
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
close(): void {
|
|
39
|
+
this.webSocket$?.unsubscribe();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private createWebSocket(url: string) {
|
|
43
|
+
const decoder = new TextDecoder('utf-8');
|
|
44
|
+
|
|
45
|
+
const webSocketConfig: WebSocketSubjectConfig<[string, MotionMasterMessage]> = {
|
|
46
|
+
binaryType: 'arraybuffer',
|
|
47
|
+
closeObserver: {
|
|
48
|
+
next: () => {
|
|
49
|
+
this.opened$.next(false);
|
|
50
|
+
logger.info(`Connection closed ${url}`);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
deserializer: (e: MessageEvent<ArrayBuffer>): [string, MotionMasterMessage] => {
|
|
54
|
+
const end = new Uint8Array(e.data, 0, 1)[0] + 1;
|
|
55
|
+
const topic = decoder.decode(e.data.slice(1, end)); // topic starts from the 2nd byte
|
|
56
|
+
const message = MotionMasterMessage.decode(new Uint8Array(e.data.slice(end)));
|
|
57
|
+
return [topic, message];
|
|
58
|
+
},
|
|
59
|
+
openObserver: {
|
|
60
|
+
next: () => {
|
|
61
|
+
this.opened$.next(true);
|
|
62
|
+
logger.info(`Connection opened ${url}`);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
url,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return webSocket(webSocketConfig);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BehaviorSubject, Subject } from "rxjs";
|
|
2
|
+
import { logger } from "./logger";
|
|
3
|
+
import { MotionMasterPubSubSocket } from "./motion-master-pub-sub-socket";
|
|
4
|
+
import { selectMotionMasterMessageByTopic, selectMotionMasterMessageStatusByKey } from "./operators";
|
|
5
|
+
import { MotionMasterMessage } from "./types";
|
|
6
|
+
|
|
7
|
+
export class MotionMasterPubSubWorkerSocket implements MotionMasterPubSubSocket {
|
|
8
|
+
|
|
9
|
+
opened$ = new BehaviorSubject(false);
|
|
10
|
+
|
|
11
|
+
data$ = new Subject<[string, MotionMasterMessage]>();
|
|
12
|
+
|
|
13
|
+
private _url?: string;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
public readonly worker: Worker,
|
|
17
|
+
) { }
|
|
18
|
+
|
|
19
|
+
get url(): string | undefined {
|
|
20
|
+
return this._url;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
open(url: string): void {
|
|
24
|
+
this._url = url;
|
|
25
|
+
|
|
26
|
+
this.worker.onmessage = ({ data }) => {
|
|
27
|
+
if ('opened' in data) {
|
|
28
|
+
const { opened } = data;
|
|
29
|
+
this.opened$.next(opened);
|
|
30
|
+
logger.info(opened ? `Opened pub/sub 🔌 ${url}` : `Closed pub/sub 🔌 ${url}`);
|
|
31
|
+
} else if ('topic' in data) {
|
|
32
|
+
const { topic, messages }: { topic: string, messages: MotionMasterMessage[] } = data;
|
|
33
|
+
messages.forEach((message) => this.data$.next([topic, message]));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.worker.postMessage({
|
|
38
|
+
open: { url }
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
close(): void {
|
|
43
|
+
this.worker.postMessage({ close: true })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|