-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.mjs
159 lines (135 loc) · 4.36 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import "dotenv/config";
import { SystemAccessPoint } from "freeathome-local-api-client";
import fetch from "node-fetch";
// Determine whether log output shall be formatted as JSONL or logfmt.
const useLogfmt = process.argv.findIndex((e) => e.startsWith("--logfmt")) >= 0;
// The base factor in milliseconds for the exponential backoff
const delayFactor = 200;
// The maximum number of times the attempt is made to establish a websocket connection.
const maxWsRetryCount = 10;
// Setup fetch
globalThis.fetch = fetch;
// formats an object as logfmt
function logfmt(data) {
var line = "";
for (let key in data) {
let value = data[key];
let is_null = false;
if (value === null) {
is_null = true;
value = "";
} else value = value.toString();
const needs_quoting = value.indexOf(" ") > -1 || value.indexOf("=") > -1;
const needs_escaping = value.indexOf('"') > -1 || value.indexOf("\\") > -1;
if (needs_escaping) value = value.replace(/["\\]/g, "\\$&");
if (needs_quoting) value = '"' + value + '"';
if (value === "" && !is_null) value = '""';
line += key + "=" + value + " ";
}
return line.trim();
}
// This function waits for a second
async function pause() {
return new Promise((res) => setTimeout(res, 1000));
}
// format and output message
function processMessage(message) {
const keys = Object.keys(
message["00000000-0000-0000-0000-000000000000"].datapoints
);
if (!keys.length) return;
keys.forEach((value) => {
const match = value.match(
/^([a-z0-9]{12})\/(ch[\da-f]{4})\/([io]dp\d{4})$/i
);
if (!match) {
const message = `Ignored datapoint ${value}: Unexpected format`;
console.error(
useLogfmt ? logfmt({ severity: "error", msg: message }) : message
);
return;
}
const update = {
severity: useLogfmt ? "info" : undefined,
device: match[1],
channel: match[2],
datapoint: match[3],
value: message["00000000-0000-0000-0000-000000000000"].datapoints[value],
};
console.log(
useLogfmt ? logfmt(update) : JSON.stringify(update, null, null)
);
});
}
// Create a loop function that waits for interrupt
let shutdownTriggered = false;
async function runLoop() {
while (!shutdownTriggered) {
await pause();
}
}
// Ignore logs from the SysAP
const logger = {
debug: () => {},
error: (message) =>
console.error(
useLogfmt ? logfmt({ severity: "error", msg: message }) : message
),
log: (message) =>
console.log(
useLogfmt ? logfmt({ severity: "info", msg: message }) : message
),
warn: (message) =>
console.warn(
useLogfmt ? logfmt({ severity: "warn", msg: message }) : message
),
};
// Connect to system access point and web socket
const sysAp = new SystemAccessPoint(
process.env.SYSAP_HOST,
process.env.SYSAP_USER_ID,
process.env.SYSAP_PASSWORD,
false,
false,
logger
);
// React to web socket events
let wsConnectionAttempt = 0;
sysAp.on("websocket-open", () => {
wsConnectionAttempt = 0;
});
sysAp.on("websocket-close", (code, reason) => {
if (code === 1000) return;
let message = `Websocket to System Access Point was closed with code ${code.toString()}: ${reason.toString()}`;
console.warn(
useLogfmt ? logfmt({ severity: "warn", msg: message }) : message
);
if (wsConnectionAttempt >= maxWsRetryCount) {
message =
"Maximum retry count exceeded. Will not try to reconnect to websocket again.";
console.error(
useLogfmt ? logfmt({ severity: "error", msg: message }) : message
);
return;
}
const delay = delayFactor * 2 ** wsConnectionAttempt++;
message = `Attempting to reconnect in ${delay}ms [${wsConnectionAttempt}/${maxWsRetryCount}]`;
console.warn(
useLogfmt ? logfmt({ severity: "warn", msg: message }) : message
);
setTimeout(() => sysAp.connectWebSocket(), delay);
});
// Subscribe to web socket events
const subscription = sysAp
.getWebSocketMessages()
.subscribe((message) => processMessage(message));
sysAp.connectWebSocket();
// Trap SIGINT and initialized
process.on("SIGINT", () => {
shutdownTriggered = true;
});
// keep the script runnning until SIGINT is received.
await runLoop();
// Shutdown
sysAp.disconnectWebSocket();
subscription.unsubscribe();