iobroker.statechart 0.1.1 → 0.2.0

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/main.js DELETED
@@ -1,279 +0,0 @@
1
- "use strict";
2
-
3
- /*
4
- * Created with @iobroker/create-adapter v3.1.5
5
- */
6
-
7
- // The adapter-core module gives you access to the core ioBroker functions
8
- // you need to create an adapter
9
- const utils = require("@iobroker/adapter-core");
10
- const { ChartRuntime } = require("./lib/chart-runtime");
11
-
12
- class Statechart extends utils.Adapter {
13
- /**
14
- * @param {Partial<utils.AdapterOptions>} [options] - Adapter options
15
- */
16
- constructor(options) {
17
- super({
18
- ...options,
19
- name: "statechart",
20
- });
21
- /** @type {Map<string, ChartRuntime>} running charts by chart ID */
22
- this.runtimes = new Map();
23
- /** @type {Set<string>} foreign states we are subscribed to */
24
- this.subscribedInputs = new Set();
25
- /** chart (re)starts are executed one after another */
26
- this.chartQueue = Promise.resolve();
27
- /** @type {{ latitude: number; longitude: number } | null} for the astro helper functions */
28
- this.location = null;
29
-
30
- this.on("ready", this.onReady.bind(this));
31
- this.on("stateChange", this.onStateChange.bind(this));
32
- this.on("objectChange", this.onObjectChange.bind(this));
33
- this.on("unload", this.onUnload.bind(this));
34
- }
35
-
36
- /**
37
- * Is called when databases are connected and adapter received configuration.
38
- */
39
- async onReady() {
40
- await this.setObjectNotExistsAsync("charts", {
41
- type: "folder",
42
- common: { name: "Charts" },
43
- native: {},
44
- });
45
- await this.loadLocation();
46
-
47
- const objects = await this.getForeignObjectsAsync(`${this.namespace}.charts.*`, "channel");
48
- for (const [id, obj] of Object.entries(objects)) {
49
- const chartId = this.getChartId(id);
50
- if (chartId) {
51
- // continue where the chart was before the adapter stopped
52
- await this.startChart(chartId, obj, true);
53
- }
54
- }
55
-
56
- // the editor saves charts as objects, restart a chart (in its initial state) whenever it changes
57
- this.subscribeObjects("charts.*");
58
- }
59
-
60
- /**
61
- * Reads the location of the ioBroker system settings, used by isDaytime() and astroTime()
62
- */
63
- async loadLocation() {
64
- const config = await this.getForeignObjectAsync("system.config");
65
- const latitude = parseFloat(String(config?.common?.latitude ?? ""));
66
- const longitude = parseFloat(String(config?.common?.longitude ?? ""));
67
- if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
68
- this.location = { latitude, longitude };
69
- } else {
70
- this.log.info("No location configured in the system settings, isDaytime() and astroTime() do not work");
71
- }
72
- }
73
-
74
- /**
75
- * Extracts the chart ID from the ID of a chart object
76
- *
77
- * @param {string} id - Object ID
78
- * @returns {string | undefined} undefined if the ID is no chart object
79
- */
80
- getChartId(id) {
81
- const prefix = `${this.namespace}.charts.`;
82
- if (!id.startsWith(prefix)) {
83
- return undefined;
84
- }
85
- const chartId = id.substring(prefix.length);
86
- return chartId.includes(".") ? undefined : chartId;
87
- }
88
-
89
- /**
90
- * @param {string} chartId - Chart ID
91
- * @param {ioBroker.Object | null | undefined} obj - Chart object
92
- * @param {boolean} [restore] - Continue with the persisted state instead of the initial state
93
- */
94
- async startChart(chartId, obj, restore = false) {
95
- const chart = obj?.type === "channel" ? obj.native?.chart : undefined;
96
- if (!chart) {
97
- return;
98
- }
99
-
100
- await this.createChartStates(chartId);
101
- // e.g. a new chart, which was just created in the editor: nothing to execute, but no error either
102
- const empty = !chart.states?.length;
103
- if (!chart.enabled || empty) {
104
- if (empty) {
105
- this.log.warn(`Chart "${chart.name}" has no states, it is not started`);
106
- } else {
107
- this.log.info(`Chart "${chart.name}" is paused`);
108
- }
109
- // no state is active, so the editor does not highlight the last active state anymore
110
- await this.setState(`charts.${chartId}.activeState`, "", true);
111
- await this.setState(`charts.${chartId}.activeStateIds`, "[]", true);
112
- return;
113
- }
114
-
115
- const runtime = new ChartRuntime(chartId, chart, {
116
- readState: async id => (await this.getForeignStateAsync(id))?.val ?? null,
117
- writeState: async (id, val, ack = false) => {
118
- await this.setForeignStateAsync(id, val, ack);
119
- },
120
- sendTo: (instance, command, message) => this.sendTo(instance, command, message),
121
- getLocation: () => this.location,
122
- onActiveChange: ({ names, ids }) => {
123
- this.setState(`charts.${chartId}.activeState`, names, true);
124
- this.setState(`charts.${chartId}.activeStateIds`, JSON.stringify(ids), true);
125
- },
126
- persist: data => {
127
- this.setState(`charts.${chartId}.snapshot`, JSON.stringify(data), true);
128
- },
129
- loadPersisted: async () => {
130
- const state = await this.getStateAsync(`charts.${chartId}.snapshot`);
131
- return typeof state?.val === "string" && state.val ? JSON.parse(state.val) : null;
132
- },
133
- log: this.log,
134
- });
135
- try {
136
- await runtime.start({ restore });
137
- } catch (err) {
138
- this.log.error(`Cannot start chart "${chart.name}": ${err.message}`);
139
- return;
140
- }
141
- this.runtimes.set(chartId, runtime);
142
- this.updateSubscriptions();
143
- this.log.info(`Chart "${chart.name}" started`);
144
- }
145
-
146
- /**
147
- * @param {string} chartId - Chart ID
148
- */
149
- stopChart(chartId) {
150
- const runtime = this.runtimes.get(chartId);
151
- if (runtime) {
152
- runtime.stop();
153
- this.runtimes.delete(chartId);
154
- this.updateSubscriptions();
155
- }
156
- }
157
-
158
- /**
159
- * Creates the states which show the current status of a chart
160
- *
161
- * @param {string} chartId - Chart ID
162
- */
163
- async createChartStates(chartId) {
164
- await this.setObjectNotExistsAsync(`charts.${chartId}.activeState`, {
165
- type: "state",
166
- common: {
167
- name: "Active state",
168
- type: "string",
169
- role: "text",
170
- read: true,
171
- write: false,
172
- },
173
- native: {},
174
- });
175
- await this.setObjectNotExistsAsync(`charts.${chartId}.snapshot`, {
176
- type: "state",
177
- common: {
178
- name: "Stored state of the chart, restored after a restart of the adapter",
179
- type: "string",
180
- role: "json",
181
- read: true,
182
- write: false,
183
- },
184
- native: {},
185
- });
186
- await this.setObjectNotExistsAsync(`charts.${chartId}.activeStateIds`, {
187
- type: "state",
188
- common: {
189
- name: "IDs of all active states",
190
- type: "string",
191
- role: "json",
192
- read: true,
193
- write: false,
194
- },
195
- native: {},
196
- });
197
- }
198
-
199
- /**
200
- * Subscribes to all inputs of the running charts and unsubscribes from the ones no longer used
201
- */
202
- updateSubscriptions() {
203
- const needed = new Set([...this.runtimes.values()].flatMap(runtime => runtime.inputStateIds));
204
- for (const id of needed) {
205
- if (!this.subscribedInputs.has(id)) {
206
- this.subscribeForeignStates(id);
207
- }
208
- }
209
- for (const id of this.subscribedInputs) {
210
- if (!needed.has(id)) {
211
- this.unsubscribeForeignStates(id);
212
- }
213
- }
214
- this.subscribedInputs = needed;
215
- }
216
-
217
- /**
218
- * Is called when adapter shuts down - callback has to be called under any circumstances!
219
- *
220
- * @param {() => void} callback - Callback function
221
- */
222
- onUnload(callback) {
223
- try {
224
- for (const runtime of this.runtimes.values()) {
225
- runtime.stop();
226
- }
227
- this.runtimes.clear();
228
- callback();
229
- } catch (error) {
230
- this.log.error(`Error during unloading: ${error.message}`);
231
- callback();
232
- }
233
- }
234
-
235
- /**
236
- * Is called if a subscribed object changes
237
- *
238
- * @param {string} id - Object ID
239
- * @param {ioBroker.Object | null | undefined} obj - Object
240
- */
241
- onObjectChange(id, obj) {
242
- const chartId = this.getChartId(id);
243
- if (!chartId) {
244
- return;
245
- }
246
- this.chartQueue = this.chartQueue
247
- .then(async () => {
248
- this.stopChart(chartId);
249
- await this.startChart(chartId, obj);
250
- })
251
- .catch(err => this.log.error(`Cannot restart chart ${chartId}: ${err.message}`));
252
- }
253
-
254
- /**
255
- * Is called if a subscribed state changes
256
- *
257
- * @param {string} id - State ID
258
- * @param {ioBroker.State | null | undefined} state - State object
259
- */
260
- onStateChange(id, state) {
261
- if (!state) {
262
- return;
263
- }
264
- for (const runtime of this.runtimes.values()) {
265
- runtime.handleInput(id, state.val, { ack: state.ack });
266
- }
267
- }
268
- }
269
-
270
- if (require.main !== module) {
271
- // Export the constructor in compact mode
272
- /**
273
- * @param {Partial<utils.AdapterOptions>} [options] - Adapter options
274
- */
275
- module.exports = options => new Statechart(options);
276
- } else {
277
- // otherwise start the instance directly
278
- new Statechart();
279
- }